#!/usr/bin/env python
# Mode: -*- python -*-
#
# Copyright (c) 2000-2002 by hartmut Goebel <h.goebel@crazy-compilers.com>
# Copyright (c) 2015 by Rocky Bernstein

"""
Usage: uncompyle6 [OPTIONS]... [ FILE | DIR]...

Examples:
  uncompyle6      foo.pyc bar.pyc       # decompile foo.pyc, bar.pyc to stdout
  uncompyle6 -o . foo.pyc bar.pyc       # decompile to ./foo.pyc_dis and ./bar.pyc_dis
  uncompyle6 -o /tmp /usr/lib/python1.5 # decompile whole library

Options:
  -o <path>     output decompiled files to this path:
                if multiple input files are decompiled, the common prefix
                is stripped from these names and the remainder appended to
                <path>
                  uncompyle6 -o /tmp bla/fasel.pyc bla/foo.pyc
                    -> /tmp/fasel.pyc_dis, /tmp/foo.pyc_dis
                  uncompyle6 -o /tmp bla/fasel.pyc bar/foo.pyc
                    -> /tmp/bla/fasel.pyc_dis, /tmp/bar/foo.pyc_dis
                  uncompyle6 -o /tmp /usr/lib/python1.5
                    -> /tmp/smtplib.pyc_dis ... /tmp/lib-tk/FixTk.pyc_dis
  -c <file>     attempts a disassembly after compiling <file>
  -d            print timestamps
  -p <integer>  use <integer> number of processes
  -r            recurse directories looking for .pyc and .pyo files
  --verify      compare generated source with input byte-code
                (requires -o)
  --help        show this message

Debugging Options:
  --asm     -a  include byte-code         (disables --verify)
  --grammar -g  show matching grammar
  --treee   -t  include syntax tree       (disables --verify)

Extensions of generated files:
  '.pyc_dis' '.pyo_dis'   successfully decompiled (and verified if --verify)
    + '_unverified'       successfully decompile but --verify failed
    + '_failed'           decompile failed (contact author for enhancement)
"""

from __future__ import print_function
import sys, os, getopt, time

program =  os.path.basename(__file__)

from uncompyle6 import verify, check_python_version
from uncompyle6.main import main, status_msg

def usage():
    print("""usage:
   %s [--help] [--verify] [--asm] [--tree] [--grammar] [-o <path>] FILE|DIR...
"""  % program)
    sys.exit(1)


check_python_version(program)

showasm = showast = do_verify = recurse_dirs = False
numproc = 0
outfile = '-'
out_base = None
codes = []
timestamp = False
timestampfmt = "# %Y.%m.%d %H:%M:%S %Z"

try:
    opts, files = getopt.getopt(sys.argv[1:], 'hagtdro:c:p:',
                                'help asm grammar recurse timestamp tree verify '
                                'showgrammar'.split(' '))
except getopt.GetoptError as e:
    print('%s: %s' % (os.path.basename(sys.argv[0]), e),  file=sys.stderr)
    sys.exit(-1)

options = {}
for opt, val in opts:
    if opt in ('-h', '--help'):
        print(__doc__)
        sys.exit(0)
    elif opt == '--verify':
        options['do_verify'] = True
    elif opt in ('--asm', '-a'):
        options['showasm'] = True
        options['do_verify'] = False
    elif opt in ('--tree', '-t'):
        options['showast'] = True
        options['do_verify'] = False
    elif opt in ('--grammar', '-g'):
        options['showgrammar'] = True
    elif opt == '-o':
        outfile = val
    elif opt == ('--timestamp', '-d'):
        timestamp = True
    elif opt == '-c':
        codes.append(val)
    elif opt == '-p':
        numproc = int(val)
    elif opt == ('--recurse', '-r'):
        recurse_dirs = True
    else:
        print(opt, file=sys.stderr)
        usage()

# expand directory if specified
if recurse_dirs:
    expanded_files = []
    for f in files:
        if os.path.isdir(f):
            for root, _, dir_files in os.walk(f):
                for df in dir_files:
                    if df.endswith('.pyc') or df.endswith('.pyo'):
                        expanded_files.append(os.path.join(root, df))
    files = expanded_files

# argl, commonprefix works on strings, not on path parts,
# thus we must handle the case with files in 'some/classes'
# and 'some/cmds'
src_base = os.path.commonprefix(files)
if src_base[-1:] != os.sep:
    src_base = os.path.dirname(src_base)
if src_base:
    sb_len = len( os.path.join(src_base, '') )
    files = [f[sb_len:] for f in files]
    del sb_len

if not files:
    print("No files given", file=sys.stderr)
    usage()


if outfile == '-':
    outfile = None # use stdout
elif outfile and os.path.isdir(outfile):
    out_base = outfile; outfile = None
elif outfile and len(files) > 1:
    out_base = outfile; outfile = None

if timestamp:
    print(time.strftime(timestampfmt))

if numproc <= 1:
    try:
        result = main(src_base, out_base, files, codes, outfile,
                      **options)
        if len(files) > 1:
            mess = status_msg(do_verify, *result)
            print('# ' + mess)
            pass
    except (KeyboardInterrupt):
        pass
    except verify.VerifyCmpError:
        raise
else:
    from multiprocessing import Process, Queue

    try:
        from Queue import Empty
    except ImportError:
        from Queue import Empty

    fqueue = Queue(len(files)+numproc)
    for f in files:
        fqueue.put(f)
    for i in range(numproc):
        fqueue.put(None)

    rqueue = Queue(numproc)

    def process_func():
        try:
            (tot_files, okay_files, failed_files, verify_failed_files) = (0, 0, 0, 0)
            while 1:
                f = fqueue.get()
                if f is None:
                    break
                (t, o, f, v) = \
                  main(src_base, out_base, [f], codes, outfile, **options)
                tot_files += t
                okay_files += o
                failed_files += f
                verify_failed_files += v
        except (Empty, KeyboardInterrupt):
            pass
        rqueue.put((tot_files, okay_files, failed_files, verify_failed_files))
        rqueue.close()

    try:
        procs = [Process(target=process_func) for i in range(numproc)]
        for p in procs:
            p.start()
        for p in procs:
            p.join()
        try:
            (tot_files, okay_files, failed_files, verify_failed_files) = (0, 0, 0, 0)
            while True:
                (t, o, f, v) = rqueue.get(False)
                tot_files += t
                okay_files += o
                failed_files += f
                verify_failed_files += v
        except Empty:
            pass
        print('# decompiled %i files: %i okay, %i failed, %i verify failed' %
              (tot_files, okay_files, failed_files, verify_failed_files))
    except (KeyboardInterrupt, OSError):
        pass


if timestamp:
    print(time.strftime(timestampfmt))
