#!/usr/bin/env python3
"""
tidy-imports *.py
tidy-imports < foo.py

Automatically improves python import statements.

  - Adds missing imports and mandatory imports.
  - Removes unused imports.
  - Nicely formats imports (sorts, aligns, wraps).

If filenames are given on the command line, rewrites them.  Otherwise, if
stdin is not a tty, read from stdin and write to stdout.

Only top-level import statements are touched.

"""

# pyflyby/tidy-imports
# Copyright (C) 2011, 2012, 2014 Karl Chen.
# License: MIT http://opensource.org/licenses/MIT

from __future__ import print_function, absolute_import, division, with_statement

from   distutils.spawn          import find_executable
import subprocess
import sys

from   pyflyby._cmdline         import hfmt, parse_args, process_actions
from   pyflyby._imports2s       import (canonicalize_imports,
                                        fix_unused_and_missing_imports,
                                        replace_star_imports,
                                        transform_imports)
from   pyflyby._log             import logger


def main():
    def addopts(parser):
        parser.add_option('--add-missing',
                          default=True, action='store_true',
                          help=hfmt('''
                              (Default) Add missing imports.'''))
        parser.add_option('--no-add-missing', dest='add_missing',
                          default=True, action='store_false',
                          help=hfmt('''
                              Don't add missing imports.'''))
        parser.add_option('--remove-unused',
                          default="AUTOMATIC", action='store_true',
                          help=hfmt('''
                              Remove unused imports
                              (default unless filename == __init__.py).'''))
        parser.add_option('--no-remove-unused', dest='remove_unused',
                          action='store_false',
                          help=hfmt('''
                              Don't remove unused imports
                              (default if filename == __init__.py).'''))
        parser.add_option('--add-mandatory',
                          default=True, action='store_true',
                          help=hfmt('''
                              (Default) Add mandatory imports.'''))
        parser.add_option('--no-add-mandatory', dest='add_mandatory',
                          default=True, action='store_false',
                          help=hfmt('''
                              Don't add mandatory imports.'''))
        parser.add_option('--replace-star-imports',
                          default=False, action='store_true',
                          help=hfmt('''
                              Replace 'from foo.bar import *' with full list
                              of imports before removing unused imports.'''))
        parser.add_option('--no-replace-star-imports',
                          dest='replace_star_imports',
                          action='store_false',
                          help=hfmt('''
                              (Default) Don't replace 'from foo.bar import
                              *'.'''))
        parser.add_option('--canonicalize',
                          default=True, action='store_true',
                          help=hfmt('''
                              (Default) Replace imports with canonical
                              equivalent imports, according to database.'''))
        parser.add_option('--no-canonicalize', dest='canonicalize',
                          default=True, action='store_false',
                          help=hfmt('''
                              Don't canonicalize imports.'''))
        parser.add_option('--py23-fallback', dest='py23_fallback',
                         default=True, action='store_true',
                         help=hfmt('''
                             (Default) Automatically fallback to
                             python2/python3 if the source file has a syntax
                             error.'''))
        parser.add_option('--no-py23-fallback', dest='py23_fallback',
                         default=True, action='store_false',
                         help=hfmt('''
                             Do not automatically fallback to
                             python2/python3 if the source file has a syntax
                             error.'''))


        def transform_callback(option, opt_str, value, group):
            k, v = value.split("=", 1)
            group.values.transformations[k] = v
        parser.add_option("--transform", action='callback',
                          type="string", callback=transform_callback,
                          metavar="OLD=NEW",
                          dest="transformations", default={},
                          help=hfmt('''
                                Replace OLD with NEW in imports.
                                May be specified multiple times.'''))
        def no_add_callback(option, opt_str, value, group):
            group.values.add_missing = False
            group.values.add_mandatory = False
        parser.add_option('--no-add', action='callback',
                          callback=no_add_callback,
                          help=hfmt('''
                              Equivalent to --no-add-missing
                              --no-add-mandatory.'''))
    options, args = parse_args(
        addopts, import_format_params=True, modify_action_params=True)
    def modify(x):
        if options.canonicalize:
            x = canonicalize_imports(x, params=options.params)
        if options.transformations:
            x = transform_imports(x, options.transformations,
                                  params=options.params)
        if options.replace_star_imports:
            x = replace_star_imports(x, params=options.params)
        return fix_unused_and_missing_imports(
            x, params=options.params,
            add_missing=options.add_missing,
            remove_unused=options.remove_unused,
            add_mandatory=options.add_mandatory,
            )

    if options.py23_fallback:
        try:
            process_actions(args, options.actions, modify,
                            reraise_exceptions=SyntaxError)
        except SyntaxError as e:
            python = 'python2' if sys.version_info[0] == 3 else 'python3'
            python_full = find_executable(python)
            if not python_full:
                logger.error("Fallback failed: could not find %s", python)
                raise
            logger.info("SyntaxError detected ({}), falling back to {}".format(
                e, python))
            args = [python_full] + sys.argv + ['--no-py23-fallback']
            try:
                raise SystemExit(subprocess.call(args))
            except KeyboardInterrupt:
                sys.exit(1)
    else:
        process_actions(args, options.actions, modify)


if __name__ == '__main__':
    main()
