Merge branch 'release/2.0.0'
diff --git a/.env b/.env
new file mode 100644
index 0000000..91eaaee
--- /dev/null
+++ b/.env
@@ -0,0 +1,43 @@
+#!/bin/bash
+OPEN_PROJECT_NAME="isort"
+
+if [ "$PROJECT_NAME" = "$OPEN_PROJECT_NAME" ]; then
+    return
+fi
+
+export PROJECT_NAME=$OPEN_PROJECT_NAME
+export PROJECT_DIR="$PWD"
+
+# Let's make sure this is a hubflow enabled repo
+yes | git hf init >/dev/null 2>/dev/null
+
+# Quick directory switching
+alias root="cd $PROJECT_DIR"
+alias project="root; cd $PROJECT_NAME"
+
+# Commands
+alias test="root; py.test"
+alias install="_install_project"
+alias distribute="sudo python setup.py sdist upload"
+alias leave="_leave_project"
+
+function _install_project()
+{
+    CURRENT_DIRECTORY="$PWD"
+    root
+    sudo python setup.py install
+    cp kate_plugin.py ~/.kde/share/apps/kate/pate/isort_plugin.py >/dev/null 2>/dev/null
+    cd $CURRENT_DIRECTORY
+}
+
+function _leave_project()
+{
+    export PROJECT_NAME=""
+    export PROJECT_DIR=""
+
+    unalias root
+    unalias project
+    unalias test
+    unalias install
+    unalias leave
+}
diff --git a/.gitignore b/.gitignore
index 4b66cf3..04c6f1b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,6 +26,7 @@
 .tox
 nosetests.xml
 htmlcov
+.cache
 
 # Translations
 *.mo
diff --git a/README.md b/README.md
index 37aa214..4a83309 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@
 isort your python imports for you so you don't have to.
 
 isort is a Python utility / library to sort imports alphabetically, and automatically separated into sections.
-It provides a command line utility, Python library, and Kate plugin to quickly sort all your imports.
+It provides a command line utility, Python library, Vim plugin, Sublime plugin, and Kate plugin to quickly sort all your imports.
 
 Before isort:
 
@@ -83,6 +83,16 @@
 
     menu > Python > Sort Imports
 
+Installing isort's Vim plugin
+===================
+The Vim plugin for isort is maintained by @fisadev with installation directions located on the dedicated vim-isort repository
+here: https://github.com/fisadev/vim-isort#installation
+
+Installing isort's Sublime plugin
+===================
+The sublime plugin for isort is maintained by @thijsdezoete with installation directions located on the dedicated sublime-text-isort-plugin
+repository here: https://github.com/thijsdezoete/sublime-text-isort-plugin#install
+
 Installing isort's Kate plugin
 ===================
 To install the kate plugin you must either have pate installed or the very latest version of Kate:
@@ -199,14 +209,49 @@
 
 To make isort ignore a single import simply add a comment at the end of the import line containing the text 'isort:skip'
 
-    import module # isort:skip
+    import module  # isort:skip
 
 or
 
-    from xyz import (abc, # isort:skip
+    from xyz import (abc,  # isort:skip
                      yo,
                      hey)
 
+Adding an import to multiple files
+======================
+
+isort makes it easy to add an import statement across multiple files, while being assured it's correctly placed.
+
+from the command line:
+
+    isort -a "from __future__ import print_function" *.py
+
+from within Kate:
+
+    ctrl+]
+
+or:
+
+    menu > Python > Add Import
+
+Removing an import from multiple files
+======================
+
+isort makes it easy to remove an import from multiple files, without having to be concerned with how it was originally
+formatted
+
+from the command line:
+
+    isort -r "os.system" *.py
+
+from within Kate:
+
+    ctrl+shift+]
+
+or:
+
+    menu > Python > Remove Import
+
 Why isort?
 ======================
 
diff --git a/isort/__init__.py b/isort/__init__.py
index a2aa0bc..9d68f91 100644
--- a/isort/__init__.py
+++ b/isort/__init__.py
@@ -25,4 +25,4 @@
 from . import settings
 from .isort import SortImports
 
-__version__ = "1.3.2"
+__version__ = "2.0.0"
diff --git a/isort/isort.py b/isort/isort.py
index 6a77b68..cfbc6e3 100644
--- a/isort/isort.py
+++ b/isort/isort.py
@@ -75,6 +75,8 @@
             return
 
         self.in_lines = file_contents.split("\n")
+        for add_import in self.config['add_imports']:
+            self.in_lines.append(add_import)
         self.number_of_lines = len(self.in_lines)
 
         self.out_lines = []
@@ -95,7 +97,7 @@
                 output_file.write(self.output)
 
     def place_module(self, moduleName):
-        """Trys to determine if a module is a python std import,
+        """Tries to determine if a module is a python std import,
            third party import, or project code:
            if it can't determine - it assumes it is project code
         """
@@ -122,7 +124,7 @@
             fixed_module_name = moduleName.replace('.', '/')
             base_path = prefix + "/" + fixed_module_name
             if (os.path.exists(base_path + ".py") or os.path.exists(base_path + ".so") or
-                (os.path.exists(base_path) and os.path.isdir(base_path))):
+               (os.path.exists(base_path) and os.path.isdir(base_path))):
                 if "site-packages" in prefix or "dist-packages" in prefix:
                     return Sections.THIRDPARTY
                 elif "python2" in prefix.lower() or "python3" in prefix.lower():
@@ -160,7 +162,7 @@
     def _module_key(module_name, config):
         module_name = str(module_name).lower()
         return "{0}{1}".format(module_name in config['force_to_top'] and "A" or "B",
-                               config['length_sort'] and len(module_name) or module_name)
+                               config['length_sort'] and (str(len(module_name)) + ":" + module_name) or module_name)
 
     def _add_formatted_imports(self):
         """ Adds the imports back to the file
@@ -170,9 +172,12 @@
         output = []
         for section in Sections.ALL:
             straight_modules = list(self.imports[section]['straight'])
-            straight_modules.sort(key=lambda key: self._module_key(key, self.config))
+            straight_modules = natsorted(straight_modules, key=lambda key: self._module_key(key, self.config))
 
             for module in straight_modules:
+                if module in self.config['remove_imports']:
+                    continue
+
                 if module in self.as_map:
                     output.append("import {0} as {1}".format(module, self.as_map[module]))
                 else:
@@ -181,9 +186,16 @@
             from_modules = list(self.imports[section]['from'].keys())
             from_modules = natsorted(from_modules, key=lambda key: self._module_key(key, self.config))
             for module in from_modules:
+                if module in self.config['remove_imports']:
+                    continue
+
                 import_start = "from {0} import ".format(module)
                 from_imports = list(self.imports[section]['from'][module])
                 from_imports = natsorted(from_imports, key=lambda key: self._module_key(key, self.config))
+                if self.config['remove_imports']:
+                    from_imports = [line for line in from_imports if not "{0}.{1}".format(module, line) in
+                                    self.config['remove_imports']]
+
                 for from_import in copy.copy(from_imports):
                     import_as = self.as_map.get(module + "." + from_import, False)
                     if import_as:
@@ -266,50 +278,47 @@
         """
             Parses a python file taking out and categorizing imports
         """
-        while True:
-            if self._at_end():
-                return None
-
+        while not self._at_end():
             line = self._get_line()
             import_type = self._import_type(line)
-            if import_type:
-                if self.import_index == -1:
-                    self.import_index = self.index - 1
-
-                import_string = self._strip_comments(line)
-                if "(" in line and not self._at_end():
-                    while not line.strip().endswith(")") and not self._at_end():
-                        line = self._strip_comments(self._get_line())
-                        import_string += "\n" + line
-                else:
-                    while line.strip().endswith("\\"):
-                        line = self._strip_comments(self._get_line())
-                        import_string += "\n" + line
-
-                import_string = import_string.replace("_import", "[[i]]")
-                for remove_syntax in ['\\', '(', ')', ",", 'from ', 'import ']:
-                    import_string = import_string.replace(remove_syntax, " ")
-                import_string = import_string.replace("[[i]]", "_import")
-
-                imports = import_string.split()
-                if "as" in imports:
-                    while "as" in imports:
-                        index = imports.index('as')
-                        if import_type == "from":
-                            self.as_map[imports[0] + "." + imports[index -1]] = imports[index + 1]
-                        else:
-                            self.as_map[imports[index -1]] = imports[index + 1]
-                        del imports[index:index + 2]
-                if import_type == "from":
-                    impot_from = imports.pop(0)
-                    root = self.imports[self.place_module(impot_from)][import_type]
-                    if root.get(impot_from, False):
-                        root[impot_from].update(imports)
-                    else:
-                        root[impot_from] = set(imports)
-                else:
-                    for module in imports:
-                        self.imports[self.place_module(module)][import_type].add(module)
-
-            else:
+            if not import_type:
                 self.out_lines.append(line)
+                continue
+
+            if self.import_index == -1:
+                self.import_index = self.index - 1
+
+            import_string = self._strip_comments(line)
+            if "(" in line and not self._at_end():
+                while not line.strip().endswith(")") and not self._at_end():
+                    line = self._strip_comments(self._get_line())
+                    import_string += "\n" + line
+            else:
+                while line.strip().endswith("\\"):
+                    line = self._strip_comments(self._get_line())
+                    import_string += "\n" + line
+
+            import_string = import_string.replace("_import", "[[i]]")
+            for remove_syntax in ['\\', '(', ')', ",", 'from ', 'import ']:
+                import_string = import_string.replace(remove_syntax, " ")
+            import_string = import_string.replace("[[i]]", "_import")
+
+            imports = import_string.split()
+            if "as" in imports:
+                while "as" in imports:
+                    index = imports.index('as')
+                    if import_type == "from":
+                        self.as_map[imports[0] + "." + imports[index -1]] = imports[index + 1]
+                    else:
+                        self.as_map[imports[index -1]] = imports[index + 1]
+                    del imports[index:index + 2]
+            if import_type == "from":
+                import_from = imports.pop(0)
+                root = self.imports[self.place_module(import_from)][import_type]
+                if root.get(import_from, False):
+                    root[import_from].update(imports)
+                else:
+                    root[import_from] = set(imports)
+            else:
+                for module in imports:
+                    self.imports[self.place_module(module)][import_type].add(module)
diff --git a/isort/settings.py b/isort/settings.py
index a0e767c..e7ddcbe 100644
--- a/isort/settings.py
+++ b/isort/settings.py
@@ -36,17 +36,36 @@
     HANGING_INDENT = 2
     VERTICAL_HANGING_INDENT = 3
 
-
+# Note that none of these lists must be complete as they are simply fallbacks for when included auto-detection fails.
 default = {'force_to_top': [],
            'skip': ['__init__.py', ],
            'line_length': 80,
-           'known_standard_library': ['os', 'sys', 'time', 'copy', 're', '__builtin__', 'thread', 'signal', 'gc',
-                                      'exceptions', 'email'],
+           'known_standard_library': ["abc", "anydbm", "argparse", "array", "asynchat", "asyncore", "atexit", "base64",
+                                      "BaseHTTPServer", "bisect", "bz2", "calendar", "cgitb", "cmd", "codecs",
+                                      "collections", "commands", "compileall", "ConfigParser", "contextlib", "Cookie",
+                                      "copy", "cPickle", "cProfile", "cStringIO", "csv", "datetime", "dbhash", "dbm",
+                                      "decimal", "difflib", "dircache", "dis", "doctest", "dumbdbm", "EasyDialogs",
+                                      "exceptions", "filecmp", "fileinput", "fnmatch", "fractions", "functools", "gc",
+                                      "gdbm", "getopt", "getpass", "gettext", "glob", "grp", "gzip", "hashlib", "heapq",
+                                      "hmac", "imaplib", "imp", "inspect", "itertools", "json", "linecache", "locale",
+                                      "logging", "mailbox", "math", "mhlib", "mmap", "multiprocessing", "operator",
+                                      "optparse", "os", "pdb", "pickle", "pipes", "pkgutil", "platform", "plistlib",
+                                      "pprint", "profile", "pstats", "pwd", "pyclbr", "pydoc", "Queue", "random",
+                                      "re", "readline", "resource", "rlcompleter", "robotparser", "sched", "select",
+                                      "shelve", "shlex", "shutil", "signal", "SimpleXMLRPCServer", "site",
+                                      "sitecustomize", "smtpd", "smtplib", "socket", "SocketServer", "sqlite3",
+                                      "string", "StringIO", "struct", "subprocess", "sys", "sysconfig", "tabnanny",
+                                      "tarfile", "tempfile", "textwrap", "threading", "time", "timeit", "trace",
+                                      "traceback", "unittest", "urllib", "urllib2", "urlparse", "usercustomize", "uuid",
+                                      "warnings", "weakref", "webbrowser", "whichdb", "xml", "xmlrpclib", "zipfile",
+                                      "zipimport", "zlib"],
            'known_third_party': ['google.appengine.api'],
            'known_first_party': [],
            'multi_line_output': MultiLineOutput.GRID,
            'indent': ' ' * 4,
-           'length_sort': False}
+           'length_sort': False,
+           'add_imports': [],
+           'remove_imports': []}
 
 try:
     from configparser import SafeConfigParser
diff --git a/kate_plugin.py b/kate_plugin.py
index 25efaf4..b4e841e 100644
--- a/kate_plugin.py
+++ b/kate_plugin.py
@@ -22,13 +22,45 @@
 """
 
 from isort import SortImports
-from PyKDE4.ktexteditor import KTextEditor
 
 import kate
 
+try:
+    from PySide import QtGui
+except ImportError:
+    from PyQt4 import QtGui
+
 
 @kate.action(text="Sort Imports", shortcut="Ctrl+[", menu="Python")
-def sortImports():
+def sort_imports():
     document = kate.activeDocument()
+    view = document.activeView()
+    position = view.cursorPosition()
     document.setText(SortImports(file_contents=document.text()).output)
-    document.activeView().setCursorPosition(KTextEditor.Cursor(0, 0))
+    view.setCursorPosition(position)
+
+
+@kate.action(text="Add Import", shortcut="Ctrl+]", menu="Python")
+def add_imports():
+    text, ok = QtGui.QInputDialog.getText(None,
+                                          'Add Import',
+                                          'Enter an import line to add (example: from os import path):')
+    if ok:
+        document = kate.activeDocument()
+        view = document.activeView()
+        position = view.cursorPosition()
+        document.setText(SortImports(file_contents=document.text(), add_imports=text.split(";")).output)
+        view.setCursorPosition(position)
+
+
+@kate.action(text="Remove Import", shortcut="Ctrl+Shift+]", menu="Python")
+def remove_imports():
+    text, ok = QtGui.QInputDialog.getText(None,
+                                          'Remove Import',
+                                          'Enter an import line to remove (example: os.path):')
+    if ok:
+        document = kate.activeDocument()
+        view = document.activeView()
+        position = view.cursorPosition()
+        document.setText(SortImports(file_contents=document.text(), remove_imports=text.split(";")).output)
+        view.setCursorPosition(position)
diff --git a/scripts/isort b/scripts/isort
index 5ddabf2..21aac59 100755
--- a/scripts/isort
+++ b/scripts/isort
@@ -26,6 +26,10 @@
                     dest="multi_line_output", type=int, choices=[0, 1, 2, 3])
 parser.add_argument("-i", "--indent", help="String to place for indents defaults to '    ' (4 spaces).",
                     dest="indent", type=str)
+parser.add_argument("-a", "--add_import", dest="add_imports", action="append",
+                    help="Adds the specified import line to all files, automatically determining correct placement.")
+parser.add_argument("-r", "--remove_import", dest="remove_imports", action="append",
+                    help="Removes the specified import from all files.")
 parser.add_argument("-ls", "--length_sort", help="Sort imports by their string length.",
                     dest="length_sort", action="store_true", default=False)
 parser.add_argument('--version', action='version', version='isort {0}'.format(__version__))
diff --git a/setup.py b/setup.py
index d4ba1e1..90ef579 100755
--- a/setup.py
+++ b/setup.py
@@ -6,12 +6,12 @@
     from distutils.core import setup
 
 setup(name='isort',
-      version='1.3.2',
+      version='2.0.0',
       description='A Python utility / library to sort Python imports.',
       author='Timothy Crosley',
       author_email='timothy.crosley@gmail.com',
       url='https://github.com/timothycrosley/isort',
-      download_url='https://github.com/timothycrosley/isort/archive/1.3.2.tar.gz',
+      download_url='https://github.com/timothycrosley/isort/archive/2.0.0.tar.gz',
       license="MIT",
       scripts=['scripts/isort'],
       packages=['isort'],
diff --git a/test_isort.py b/test_isort.py
new file mode 100644
index 0000000..801c619
--- /dev/null
+++ b/test_isort.py
@@ -0,0 +1,333 @@
+'''
+    test_isort.py
+
+    Tests all major functionality of the isort library
+    Should be ran using py.test by simply running by.test in the isort project directory
+
+    Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+    documentation files (the "Software"), to deal in the Software without restriction, including without limitation
+    the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+    to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be included in all copies or
+    substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+    TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+    THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+    OTHER DEALINGS IN THE SOFTWARE.
+'''
+
+from __future__ import (absolute_import, division, print_function,
+                        unicode_literals)
+
+from isort.isort import SortImports
+from isort.settings import MultiLineOutput
+
+REALLY_LONG_IMPORT = ("from third_party import lib1, lib2, lib3, lib4, lib5, lib6, lib7, lib8, lib9, lib10, lib11,"
+                      "lib12, lib13, lib14, lib15, lib16, lib17, lib18, lib20, lib21, lib22")
+
+
+def test_happy_path():
+    """
+        Test the most basic use case, straight imports no code, simply not organized by category.
+    """
+    test_input = ("import sys\n"
+                  "import os\n"
+                  "import myproject.test\n"
+                  "import django.settings")
+    test_output = SortImports(file_contents=test_input, known_third_party=['django']).output
+    assert test_output == ("import os\n"
+                           "import sys\n"
+                           "\n"
+                           "import django.settings\n"
+                           "\n"
+                           "import myproject.test")
+
+
+def test_code_intermixed():
+    """
+        Defines what should happen when isort encounters imports intermixed with code
+        (it should pull them all to the top).
+    """
+    test_input = ("import sys\n"
+                  "print('yo')\n"
+                  "print('I like to put code between imports cause I want stuff to break')\n"
+                  "import myproject.test\n")
+    test_output = SortImports(file_contents=test_input).output
+    assert test_output == ("import sys\n"
+                           "\n"
+                           "import myproject.test\n"
+                           "\n"
+                           "print('yo')\n"
+                           "print('I like to put code between imports cause I want stuff to break')\n")
+
+
+def test_correct_space_between_imports():
+    """
+        Ensure after imports a correct amount of space (in newlines) is enforced
+        (2 for method, class, or decorator definitions 1 for anything else)
+    """
+    test_input_method = ("import sys\n"
+                         "def my_method():\n"
+                         "    print('hello world')\n")
+    test_output_method = SortImports(file_contents=test_input_method).output
+    assert test_output_method == ("import sys\n"
+                                  "\n"
+                                  "\n"
+                                  "def my_method():\n"
+                                  "    print('hello world')\n")
+
+    test_input_decorator = ("import sys\n"
+                            "@my_decorator\n"
+                            "def my_method():\n"
+                            "    print('hello world')\n")
+    test_output_decorator = SortImports(file_contents=test_input_decorator).output
+    assert test_output_decorator == ("import sys\n"
+                                     "\n"
+                                     "\n"
+                                     "@my_decorator\n"
+                                     "def my_method():\n"
+                                     "    print('hello world')\n")
+
+    test_input_class = ("import sys\n"
+                        "class MyClass(object):\n"
+                        "    pass\n")
+    test_output_class = SortImports(file_contents=test_input_class).output
+    assert test_output_class == ("import sys\n"
+                                 "\n"
+                                 "\n"
+                                 "class MyClass(object):\n"
+                                 "    pass\n")
+
+    test_input_other = ("import sys\n"
+                        "print('yo')\n")
+    test_output_other = SortImports(file_contents=test_input_other).output
+    assert test_output_other == ("import sys\n"
+                                 "\n"
+                                 "print('yo')\n")
+
+
+def test_sort_on_number():
+    """
+        Ensure numbers get sorted logically (10 > 9 not the other way around).
+    """
+    test_input = ("import lib10\n"
+                  "import lib9\n")
+    test_output = SortImports(file_contents=test_input).output
+    assert test_output == ("import lib9\n"
+                           "import lib10")
+
+
+def test_line_length():
+    """
+        Ensure isort enforces the set line_length
+    """
+    assert len(SortImports(file_contents=REALLY_LONG_IMPORT, line_length=80).output.split("\n")[0]) <= 80
+    assert len(SortImports(file_contents=REALLY_LONG_IMPORT, line_length=120).output.split("\n")[0]) <= 120
+
+    test_output = SortImports(file_contents=REALLY_LONG_IMPORT, line_length=42).output
+    assert test_output == ("from third_party import (lib1, lib2, lib3,\n"
+                           "                         lib4, lib5, lib6,\n"
+                           "                         lib7, lib8, lib9,\n"
+                           "                         lib10, lib11,\n"
+                           "                         lib12, lib13,\n"
+                           "                         lib14, lib15,\n"
+                           "                         lib16, lib17,\n"
+                           "                         lib18, lib20,\n"
+                           "                         lib21, lib22)")
+
+
+def test_output_modes():
+    """
+        Test setting isort to use various output modes works as expected
+    """
+    test_output_grid = SortImports(file_contents=REALLY_LONG_IMPORT,
+                                   multi_line_output=MultiLineOutput.GRID, line_length=40).output
+    assert test_output_grid == ("from third_party import (lib1, lib2,\n"
+                                "                         lib3, lib4,\n"
+                                "                         lib5, lib6,\n"
+                                "                         lib7, lib8,\n"
+                                "                         lib9, lib10,\n"
+                                "                         lib11, lib12,\n"
+                                "                         lib13, lib14,\n"
+                                "                         lib15, lib16,\n"
+                                "                         lib17, lib18,\n"
+                                "                         lib20, lib21,\n"
+                                "                         lib22)")
+
+    test_output_vertical = SortImports(file_contents=REALLY_LONG_IMPORT,
+                                       multi_line_output=MultiLineOutput.VERTICAL, line_length=40).output
+    assert test_output_vertical == ("from third_party import (lib1,\n"
+                                    "                         lib2,\n"
+                                    "                         lib3,\n"
+                                    "                         lib4,\n"
+                                    "                         lib5,\n"
+                                    "                         lib6,\n"
+                                    "                         lib7,\n"
+                                    "                         lib8,\n"
+                                    "                         lib9,\n"
+                                    "                         lib10,\n"
+                                    "                         lib11,\n"
+                                    "                         lib12,\n"
+                                    "                         lib13,\n"
+                                    "                         lib14,\n"
+                                    "                         lib15,\n"
+                                    "                         lib16,\n"
+                                    "                         lib17,\n"
+                                    "                         lib18,\n"
+                                    "                         lib20,\n"
+                                    "                         lib21,\n"
+                                    "                         lib22)")
+
+    test_output_hanging_indent = SortImports(file_contents=REALLY_LONG_IMPORT,
+                                             multi_line_output=MultiLineOutput.HANGING_INDENT,
+                                             line_length=40, indent="    ").output
+    assert test_output_hanging_indent == ("from third_party import  lib1, lib2, \\\n"
+                                          "    lib3, lib4, lib5, lib6, lib7, \\\n"
+                                          "    lib8, lib9, lib10, lib11, lib12, \\\n"
+                                          "    lib13, lib14, lib15, lib16, lib17, \\\n"
+                                          "    lib18, lib20, lib21, lib22")
+
+    test_output_vertical_indent = SortImports(file_contents=REALLY_LONG_IMPORT,
+                                              multi_line_output=MultiLineOutput.VERTICAL_HANGING_INDENT,
+                                              line_length=40, indent="    ").output
+    test_output_vertical_indent == ("from third_party import (\n"
+                                    "    lib1,\n"
+                                    "    lib2,\n"
+                                    "    lib3,\n"
+                                    "    lib4,\n"
+                                    "    lib5,\n"
+                                    "    lib6,\n"
+                                    "    lib7,\n"
+                                    "    lib8,\n"
+                                    "    lib9,\n"
+                                    "    lib10,\n"
+                                    "    lib11,\n"
+                                    "    lib12,\n"
+                                    "    lib13,\n"
+                                    "    lib14,\n"
+                                    "    lib15,\n"
+                                    "    lib16,\n"
+                                    "    lib17,\n"
+                                    "    lib18,\n"
+                                    "    lib20,\n"
+                                    "    lib21,\n"
+                                    "    lib22\n"
+                                    ")")
+
+
+def test_length_sort():
+    """
+        Test setting isort to sort on length instead of alphabetically.
+    """
+    test_input = ("import medium_sizeeeeeeeeeeeeee\n"
+                  "import shortie\n"
+                  "import looooooooooooooooooooooooooooooooooooooong\n"
+                  "import medium_sizeeeeeeeeeeeeea")
+    test_output = SortImports(file_contents=test_input, length_sort=True).output
+    assert test_output == ("import shortie\n"
+                           "import medium_sizeeeeeeeeeeeeea\n"
+                           "import medium_sizeeeeeeeeeeeeee\n"
+                           "import looooooooooooooooooooooooooooooooooooooong")
+
+
+def test_convert_hanging():
+    """
+        Ensure that isort will convert hanging indents to correct indent method.
+    """
+    test_input = ("from third_party import  lib1, lib2, \\\n"
+                  "    lib3, lib4, lib5, lib6, lib7, \\\n"
+                  "    lib8, lib9, lib10, lib11, lib12, \\\n"
+                  "    lib13, lib14, lib15, lib16, lib17, \\\n"
+                  "    lib18, lib20, lib21, lib22")
+    test_output = SortImports(file_contents=test_input, multi_line_output=MultiLineOutput.GRID,
+                              line_length=40).output
+    assert test_output == ("from third_party import (lib1, lib2,\n"
+                           "                         lib3, lib4,\n"
+                           "                         lib5, lib6,\n"
+                           "                         lib7, lib8,\n"
+                           "                         lib9, lib10,\n"
+                           "                         lib11, lib12,\n"
+                           "                         lib13, lib14,\n"
+                           "                         lib15, lib16,\n"
+                           "                         lib17, lib18,\n"
+                           "                         lib20, lib21,\n"
+                           "                         lib22)")
+
+
+def test_custom_indent():
+    """
+        Ensure setting a custom indent will work as expected.
+    """
+    test_output = SortImports(file_contents=REALLY_LONG_IMPORT, multi_line_output=MultiLineOutput.HANGING_INDENT,
+                              line_length=40, indent="  ").output
+    assert test_output == ("from third_party import  lib1, lib2, \\\n"
+                           "  lib3, lib4, lib5, lib6, lib7, lib8, \\\n"
+                           "  lib9, lib10, lib11, lib12, lib13, \\\n"
+                           "  lib14, lib15, lib16, lib17, lib18, \\\n"
+                           "  lib20, lib21, lib22")
+
+
+def test_skip():
+    """
+        Ensure skipping a single import will work as expected.
+    """
+    test_input = ("import myproject\n"
+                  "import django\n"
+                  "print('hey')\n"
+                  "import sys  # isort:skip this import needs to be placed here")
+
+    test_output = SortImports(file_contents=test_input, known_third_party=['django']).output
+    assert test_output == ("import django\n"
+                           "\n"
+                           "import myproject\n"
+                           "\n"
+                           "print('hey')\n"
+                           "import sys  # isort:skip this import needs to be placed here")
+
+
+def test_force_to_top():
+    """
+        Ensure forcing a single import to the top of its category works as expected.
+    """
+    test_input = ("import lib6\n"
+                  "import lib2\n"
+                  "import lib5\n"
+                  "import lib1")
+    test_output = SortImports(file_contents=test_input, force_to_top=['lib5']).output
+    assert test_output == ("import lib5\n"
+                           "import lib1\n"
+                           "import lib2\n"
+                           "import lib6")
+
+
+def test_add_imports():
+    """
+        Ensures adding imports works as expected.
+    """
+    test_input = ("import lib6\n"
+                  "import lib2\n"
+                  "import lib5\n"
+                  "import lib1")
+    test_output = SortImports(file_contents=test_input, add_imports=['import lib4', 'import lib7']).output
+    assert test_output == ("import lib1\n"
+                           "import lib2\n"
+                           "import lib4\n"
+                           "import lib5\n"
+                           "import lib6\n"
+                           "import lib7")
+
+
+def test_remove_imports():
+    """
+        Ensures removing imports works as expected.
+    """
+    test_input = ("import lib6\n"
+                  "import lib2\n"
+                  "import lib5\n"
+                  "import lib1")
+    test_output = SortImports(file_contents=test_input, remove_imports=['lib2', 'lib6']).output
+    assert test_output == ("import lib1\n"
+                           "import lib5")