Repository: timothycrosley/isort
Pull request #294 (2015-08-06 21:27:19)

Sort all imports alphabetically
Fixes: https://github.com/timothycrosley/isort/issues/271

As it is a bit of an outsider from the rest of the methods I decided to use this approach (basically doing all the sort by myself).

I hope it's acceptable. I event wrote a test, which, btw. was easy and fun! Thanks for having a nice test suite easy to extend!


Patch:
diff --git a/isort/isort.py b/isort/isort.py
index 0acfd815..65ce7399 100644
--- a/isort/isort.py
+++ b/isort/isort.py
@@ -131,7 +131,10 @@ class SortImports(object):
         self._first_comment_index_end = -1
         self._parse()
         if self.import_index != -1:
-            self._add_formatted_imports()
+            if self.config.get('force_alphabetical_sort', False):
+                self._sort_alphabetically()
+            else:
+                self._add_formatted_imports()
 
         self.length_change = len(self.out_lines) - self.original_length
         while self.out_lines and self.out_lines[-1].strip() == "":
@@ -503,6 +506,40 @@ class SortImports(object):
                         new_out_lines.append("")
             self.out_lines = new_out_lines
 
+    def _sort_alphabetically(self):
+        """Adds the imports back to the file sorted alphabetically."""
+        from_output = []
+        straight_output = []
+        for section in itertools.chain(self.sections, self.config['forced_separate']):
+            straight_modules = list(self.imports[section]['straight'])
+            from_modules = list(self.imports[section]['from'].keys())
+
+            self._add_from_imports(from_modules, section, from_output)
+            self._add_straight_imports(straight_modules, section, straight_output)
+
+        new_from_output = []
+        new_straight_output = []
+        for line in from_output:
+            for element in line.split('\n'):
+                new_from_output.append(element)
+        for line in straight_output:
+            for element in line.split('\n'):
+                new_straight_output.append(element)
+
+
+        sorted_from = sorted(new_from_output, key=lambda s: s.lower())
+        sorted_straight = sorted(new_straight_output, key=lambda s: s.lower())
+        output = sorted_from + ['', ] + sorted_straight
+
+        while [character.strip() for character in output[-1:]] == [""]:
+            output.pop()
+
+        output_at = 0
+        if self.import_index < self.original_length:
+            output_at = self.import_index
+        elif self._first_comment_index_end != -1 and self._first_comment_index_start <= 2:
+            output_at = self._first_comment_index_end
+        self.out_lines[output_at:0] = output
 
     def _output_grid(self, statement, imports, white_space, indent, line_length, comments):
         statement += "(" + imports.pop(0)
diff --git a/isort/main.py b/isort/main.py
index b1e1ed48..a62fdfed 100755
--- a/isort/main.py
+++ b/isort/main.py
@@ -173,6 +173,8 @@ def create_parser():
                         help="Specifies how long lines that are wrapped should be, if not set line_length is used.")
     parser.add_argument('-fgw', '--force-grid-wrap',  action='store_true', dest="force_grid_wrap",
                         help='Force from imports to be grid wrapped regardless of line length')
+    parser.add_argument('-fas', '--force-alphabetical-sort',  action='store_true', dest="force_alphabetical_sort",
+                        help='Force all imports to be sorted as a single section')
 
     arguments = dict((key, value) for (key, value) in itemsview(vars(parser.parse_args())) if value)
     return arguments


Test patch:
diff --git a/test_isort.py b/test_isort.py
index fb87545b..fd981563 100644
--- a/test_isort.py
+++ b/test_isort.py
@@ -1454,3 +1454,23 @@ def test_comment_at_top_of_file():
     test_input = ("# -*- coding: utf-8 -*-\n"
                   "from django.db import models\n")
     assert SortImports(file_contents=test_input).output == test_input
+
+
+def test_alphabetic_sorting():
+    """Test to ensure isort correctly handles top of file comments"""
+    test_input = ("from django.contrib.gis.geos import GEOSException\n"
+                  "from plone.app.testing import getRoles\n"
+                  "from plone.app.testing import ManageRoles\n"
+                  "from plone.app.testing import setRoles\n"
+                  "from Products.CMFPlone import utils\n"
+                  "\n"
+                  "import ABC\n"
+                  "import unittest\n"
+                  "import Zope\n")
+    options = {'force_single_line': True,
+               'force_alphabetical_sort': True, }
+    assert SortImports(file_contents=test_input, **options).output == test_input
+
+    test_input = ("# -*- coding: utf-8 -*-\n"
+                  "from django.db import models\n")
+    assert SortImports(file_contents=test_input).output == test_input