#!/usr/bin/env python3
# generated by make_simp.py on 2020-10-29T17:55:35.460453
"""
simp.py sorts Python imports simply.

"""
from __future__ import print_function
#!/usr/bin/env python
"""
Myers diff of two lists or files

Inspired by
https://gist.github.com/adamnew123456/37923cf53f51d6b9af32a539cdfa7cc4
"""
import argparse

KEEP, INSERT, REMOVE, OMIT = 'kiro'

__version__ = '0.9.1'
__all__ = ('diff',)

_DEFAULT_FORMATS = {
    KEEP: ' %s',
    INSERT: '+%s',
    REMOVE: '-%s',
    OMIT: '(...%s skipped...)',
}


def diff(a, b, context=None, format=None):
    """
    Return the Myers diff of two lists.

    The result is a list of (action, line) pairs,
    where ``action`` is one of ``'kiro'``, for keep,
    insert, remove, or omit.

    ARGUMENTS:
       a, b:
         The two files to compare

       context:
         How many lines of context to keep between blocks of changes?
         ``None``, the default, means keep all unchanged lines.
         ``0`` means throw away all the unchanged lines

       format: if non-empty, this format dictionary is used to format
         each diff entry

    """
    diff = _myers(a, b)

    if context is not None:
        diff = list(_compact(diff, context))

    if format:
        fmt = dict(_DEFAULT_FORMATS)
        if format is not True:
            fmt.update(format)

        diff = [(fmt[d] % e).rstrip() for d, e in diff]

    return diff


def _myers(a, b):
    front = {1: (0, [])}

    for d in range(0, len(a) + len(b) + 1):
        for k in range(-d, d + 1, 2):
            go_down = k == -d or (k != d and front[k - 1][0] < front[k + 1][0])

            if go_down:
                old_x, history = front[k + 1]
                x = old_x
            else:
                old_x, history = front[k - 1]
                x = old_x + 1
            y = x - k

            history = history[:]

            if 1 <= y <= len(b) and go_down:
                history.append((INSERT, b[y - 1]))
            elif 1 <= x <= len(a):
                history.append((REMOVE, a[x - 1]))

            while x < len(a) and y < len(b) and a[x] == b[y]:
                x += 1
                y += 1
                history.append((KEEP, a[x - 1]))

            if x >= len(a) and y >= len(b):
                return history

            front[k] = x, history

    # TODO: is this possible to reach?
    raise ValueError('Unable to compute diff')


def _compact(diff, context):
    queue = []
    results = []

    def omit():
        omitted = len(queue) - context
        if omitted > 0:
            results.append((OMIT, str(omitted)))

    for line in diff:
        if line[0] is KEEP:
            queue.append(line)
        else:
            if queue:
                omit()
                if context > 0:
                    results.extend(queue[-context:])
                queue[:] = []
            results.append(line)

    if queue:
        results.extend(queue[:context])
        omit()

    return results

import argparse
import itertools
import os
import subprocess
import sys

_SUB = {'stderr': subprocess.DEVNULL, 'encoding': 'utf8'}
_MSG = 'Sort import statements with simp'
assert len(_MSG) <= 50


def simp(targets, commit=False, dry_run=True, fail=False):
    if dry_run:
        commit = False

    if commit:
        try:
            cmd = 'git', 'diff-index', '--quiet', 'HEAD', '--'
            subprocess.check_output(cmd)
        except subprocess.CalledProcessError:
            print(
                'simp --commit: Cannot commit with changes in the workspace',
                file=sys.stderr,
            )
            return 1

    disordered = 0
    for count, path in _all_files(targets):
        with open(path) as fp:
            lines = list(fp)

        sorted_lines = _sort_imports(lines)
        if lines == sorted_lines:
            continue

        if fail:
            return 1

        disordered += 1
        if dry_run:
            if count:
                print('', 50 * '-', '', sep='\n')

            print(path + ':')
            mdiff = diff(lines, sorted_lines, context=2, format=True)
            print('', *mdiff, sep='\n')

        print(path)
        if dry_run:
            continue

        with open(path, 'w') as fp:
            print(*sorted_lines, sep='\n', file=fp)

    if not disordered:
        print('All sorted')
        return

    if commit:
        cmd = 'git', 'commit', '-am', _MSG
        print()
        print(subprocess.check_output(cmd).decode(), end='')

    else:
        msg = '\n%d of %d Python file%s had unsorted includes'
        s = '' if count == 1 else 's'
        print(msg % (disordered, count, s))


def _plural(n, item, plural=None):
    if n != 1:
        item = plural if plural else item + 's'
    return '%d %s' % (n, item)


def _sort_imports(lines):
    def is_comment(s):
        s = s.strip()
        return not s or s.startswith('#')

    def is_import(s):
        return s.startswith(('from ', 'import ')) and '__future__' not in s

    def still_import(s):
        return is_import(s) or is_comment(s)

    before = list(itertools.takewhile((lambda s: not is_import(s)), lines))
    lines = lines[len(before) :]

    imports = list(itertools.takewhile(still_import, lines))
    after = lines[len(imports) :]

    while imports and is_comment(imports[-1]):
        after.insert(0, imports.pop())

    comments = [s for s in imports if is_comment(s)]
    imports = [s for s in imports if not is_comment(s)]

    return before + comments + sorted(set(imports)) + after


def _all_files(args):
    for arg in args:
        if os.path.isdir(arg):
            for i in _one_tree(arg):
                yield i
        elif arg.endswith('.py') or '.' not in arg:
            yield arg
        else:
            print('Do not understand file', arg, file=sys.stderr)


def _one_tree(root):
    for directory, sub_dirs, files in os.walk(root):
        if directory == root:
            sub_dirs[:] = (i for i in sub_dirs if i not in ('build', 'dist'))

        sub_dirs[:] = (i for i in sub_dirs if not i.startswith('.'))
        files[:] = (i for i in files if not i.startswith('.'))

        for f in files:
            if f.endswith('.py'):
                yield os.path.join(directory, f)


def parser():
    p = argparse.ArgumentParser(description=_DESCRIPTION, epilog=_EPILOG)

    p.add_argument('targets', default=['.'], nargs='*', help=_TARGETS_HELP)
    p.add_argument('--commit', '-c', action='store_true', help=_COMMIT_HELP)
    p.add_argument('--dry_run', '-d', action='store_true', help=_DRY_RUN_HELP)
    p.add_argument('--fail', '-f', action='store_true', help=_FAIL_HELP)

    return p


def parse_args(args=None):
    return parser().parse_args(args)


_DESCRIPTION = """
Sort the import directives in Python files, excluding __future__.
"""

_EPILOG = """I wanted to sort my Python includes with no fuss.  ``simp`` finds
the first block of unindented import statements, and sorts them.  Any comments
between import lines bubble up to the top in their original order."""

_FAIL_HELP = """If set, the program fails if any changes need to be made.
This is useful for a commit hook to check if all imports are sorted."""

_TARGETS_HELP = """\
One or more Python files or directories with Python files.
Without arguments, runs simp on the current directory.
"""

_DRY_RUN_HELP = """\
If set, do not make the changes to the Python files, but just
list the diffs."""

_COMMIT_HELP = 'Git commit the changes'


if __name__ == '__main__':
    code = simp(**vars(parse_args()))
    if code:
        sys.exit(code)
