#!/usr/bin/python2.7
"""Tool for processing pytd files.

pytd is a type declaration language for Python. Each .py file can have an
accompanying .pytd file that specifies classes, argument types, return types
and exceptions.
This binary processes pytd files, typically to optimize them.

Usage:
  pytd_tool [flags] <inputfile> <outputfile>
"""

import optparse
import sys

from pytype import utils
from pytype.pyi import parser
from pytype.pytd import optimize
from pytype.pytd import pytd
from pytype.pytd.parse import builtins


def parse_options(args):
  """Use optparse to parse command line options."""
  o = optparse.OptionParser("Usage: %prog [options] infile.pytd outfile.pytd")
  o.add_option(
      "-O", "--optimize", action="store_true",
      dest="optimize", default=False,
      help="Optimize pytd file.")
  o.add_option(
      "--lossy", action="store_true",
      dest="lossy", default=False,
      help="Allow lossy optimizations, such as merging classes.")
  o.add_option(
      "--max-union", type="int", action="store",
      dest="max_union", default=4,
      help="Maximum number of objects in an 'or' clause.\nUse with --lossy.")
  o.add_option(
      "--use-abcs", action="store_true",
      dest="use_abcs", default=False,
      help="Inject abstract bases classes for type merging.\nUse with --lossy.")
  o.add_option(
      "--remove-mutable", action="store_true",
      dest="remove_mutable", default=False,
      help="Remove mutable parameters.")
  o.add_option(
      "-V", "--python_version", type="string", action="store",
      dest="python_version", default="2.7",
      help=("Python version to target (\"major.minor\", e.g. \"2.7\")"))
  o.add_option(
      "--multiline-args", action="store_true",
      dest="multiline_args", default=False,
      help="Print function arguments one to a line.")
  options, filenames = o.parse_args(args)
  return options, filenames


def main():
  options, filenames = parse_options(sys.argv)
  unused_executable = filenames.pop(0)
  python_version = utils.split_version(options.python_version)
  try:
    utils.validate_version(python_version)
  except utils.UsageError as e:
    sys.stderr.write("Usage error: %s\n" % utils.message(e))
    sys.exit(1)

  if len(filenames) == 1:
    filename_in, filename_out = filenames[0], None
  elif len(filenames) == 2:
    filename_in, filename_out = filenames
  else:
    sys.stderr.write("Need one or two filenames")
    sys.exit(1)

  with open(filename_in) as fi:
    sourcecode = fi.read()
    try:
      parsed = parser.parse_string(sourcecode, filename=filename_in,
                                   python_version=python_version)
    except parser.ParseError as e:
      sys.stderr.write(str(e))
      sys.exit(1)

  if options.optimize:
    parsed = optimize.Optimize(parsed,
                               builtins.GetBuiltinsPyTD(python_version),
                               lossy=options.lossy,
                               use_abcs=options.use_abcs,
                               max_union=options.max_union,
                               remove_mutable=options.remove_mutable,
                               can_do_lookup=False)

  if filename_out is not None:
    out_text = pytd.Print(parsed, options.multiline_args)
    if filename_out == "-":
      sys.stdout.write(out_text)
    else:
      with open(filename_out, "w") as out:
        out.write(out_text)


if __name__ == "__main__":
  main()
