You've already forked python-uncompyle6
mirror of
https://github.com/rocky/python-uncompyle6.git
synced 2025-08-04 01:09:52 +08:00
Redo uncompyel6 options ...
Use click now and make more like decompyle3
This commit is contained in:
@@ -1,15 +1,19 @@
|
||||
#!/usr/bin/env python
|
||||
# Mode: -*- python -*-
|
||||
#
|
||||
# Copyright (c) 2015-2017, 2019-2020, 2023 by Rocky Bernstein
|
||||
# Copyright (c) 2015-2017, 2019-2020, 2023-2024
|
||||
# by Rocky Bernstein
|
||||
# Copyright (c) 2000-2002 by hartmut Goebel <h.goebel@crazy-compilers.com>
|
||||
#
|
||||
from __future__ import print_function
|
||||
|
||||
import getopt
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from typing import List
|
||||
|
||||
import click
|
||||
from xdis.version_info import version_tuple_to_str
|
||||
|
||||
from uncompyle6 import verify
|
||||
from uncompyle6.main import main, status_msg
|
||||
@@ -17,150 +21,162 @@ from uncompyle6.version import __version__
|
||||
|
||||
program = "uncompyle6"
|
||||
|
||||
__doc__ = """
|
||||
Usage:
|
||||
%s [OPTIONS]... [ FILE | DIR]...
|
||||
%s [--help | -h | --V | --version]
|
||||
|
||||
Examples:
|
||||
%s foo.pyc bar.pyc # decompile foo.pyc, bar.pyc to stdout
|
||||
%s -o . foo.pyc bar.pyc # decompile to ./foo.pyc_dis and ./bar.pyc_dis
|
||||
%s -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
|
||||
--compile | -c <python-file>
|
||||
attempts a decompilation after compiling <python-file>
|
||||
-d print timestamps
|
||||
-p <integer> use <integer> number of processes
|
||||
-r recurse directories looking for .pyc and .pyo files
|
||||
--fragments use fragments deparser
|
||||
--verify compare generated source with input byte-code
|
||||
--verify-run compile generated source, run it and check exit code
|
||||
--syntax-verify compile generated source
|
||||
--linemaps generated line number correspondencies between byte-code
|
||||
and generated source output
|
||||
--encoding <encoding>
|
||||
use <encoding> in generated source according to pep-0263
|
||||
--help show this message
|
||||
|
||||
Debugging Options:
|
||||
--asm | -a include byte-code (disables --verify)
|
||||
--grammar | -g show matching grammar
|
||||
--tree={before|after}
|
||||
-t {before|after} include syntax before (or after) tree transformation
|
||||
(disables --verify)
|
||||
--tree++ | -T add template rules to --tree=before when possible
|
||||
|
||||
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)
|
||||
""" % (
|
||||
(program,) * 5
|
||||
)
|
||||
|
||||
program = "uncompyle6"
|
||||
|
||||
|
||||
def usage():
|
||||
print(__doc__)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def main_bin():
|
||||
recurse_dirs = False
|
||||
numproc = 0
|
||||
outfile = "-"
|
||||
out_base = None
|
||||
source_paths = []
|
||||
timestamp = False
|
||||
timestampfmt = "# %Y.%m.%d %H:%M:%S %Z"
|
||||
# __doc__ = """
|
||||
# Usage:
|
||||
# %s [OPTIONS]... [ FILE | DIR]...
|
||||
# %s [--help | -h | --V | --version]
|
||||
|
||||
try:
|
||||
opts, pyc_paths = getopt.getopt(
|
||||
sys.argv[1:],
|
||||
"hac:gtTdrVo:p:",
|
||||
"help asm compile= grammar linemaps recurse "
|
||||
"timestamp tree= tree+ "
|
||||
"fragments verify verify-run version "
|
||||
"syntax-verify "
|
||||
"showgrammar encoding=".split(" "),
|
||||
# Examples:
|
||||
# %s foo.pyc bar.pyc # decompile foo.pyc, bar.pyc to stdout
|
||||
# %s -o . foo.pyc bar.pyc # decompile to ./foo.pyc_dis and ./bar.pyc_dis
|
||||
# %s -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
|
||||
# --compile | -c <python-file>
|
||||
# attempts a decompilation after compiling <python-file>
|
||||
# -d print timestamps
|
||||
# -p <integer> use <integer> number of processes
|
||||
# -r recurse directories looking for .pyc and .pyo files
|
||||
# --fragments use fragments deparser
|
||||
# --verify compare generated source with input byte-code
|
||||
# --verify-run compile generated source, run it and check exit code
|
||||
# --syntax-verify compile generated source
|
||||
# --linemaps generated line number correspondencies between byte-code
|
||||
# and generated source output
|
||||
# --encoding <encoding>
|
||||
# use <encoding> in generated source according to pep-0263
|
||||
# --help show this message
|
||||
|
||||
# Debugging Options:
|
||||
# --asm | -a include byte-code (disables --verify)
|
||||
# --grammar | -g show matching grammar
|
||||
# --tree={before|after}
|
||||
# -t {before|after} include syntax before (or after) tree transformation
|
||||
# (disables --verify)
|
||||
# --tree++ | -T add template rules to --tree=before when possible
|
||||
|
||||
# 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)
|
||||
# """ % (
|
||||
# (program,) * 5
|
||||
# )
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.option(
|
||||
"--asm++/--no-asm++",
|
||||
"-A",
|
||||
"asm_plus",
|
||||
default=False,
|
||||
help="show xdis assembler and tokenized assembler",
|
||||
)
|
||||
@click.option("--asm/--no-asm", "-a", default=False)
|
||||
@click.option("--grammar/--no-grammar", "-g", "show_grammar", default=False)
|
||||
@click.option("--tree/--no-tree", "-t", default=False)
|
||||
@click.option(
|
||||
"--tree++/--no-tree++",
|
||||
"-T",
|
||||
"tree_plus",
|
||||
default=False,
|
||||
help="show parse tree and Abstract Syntax Tree",
|
||||
)
|
||||
@click.option(
|
||||
"--linemaps/--no-linemaps",
|
||||
default=False,
|
||||
help="show line number correspondencies between byte-code "
|
||||
"and generated source output",
|
||||
)
|
||||
@click.option(
|
||||
"--verify",
|
||||
type=click.Choice(["run", "syntax"]),
|
||||
default=None,
|
||||
)
|
||||
@click.option(
|
||||
"--recurse/--no-recurse",
|
||||
"-r",
|
||||
"recurse_dirs",
|
||||
default=False,
|
||||
)
|
||||
@click.option(
|
||||
"--output",
|
||||
"-o",
|
||||
"outfile",
|
||||
type=click.Path(
|
||||
exists=True, file_okay=True, dir_okay=True, writable=True, resolve_path=True
|
||||
),
|
||||
required=False,
|
||||
)
|
||||
@click.version_option(version=__version__)
|
||||
@click.option(
|
||||
"--start-offset",
|
||||
"start_offset",
|
||||
default=0,
|
||||
help="start decomplation at offset; default is 0 or the starting offset.",
|
||||
)
|
||||
@click.version_option(version=__version__)
|
||||
@click.option(
|
||||
"--stop-offset",
|
||||
"stop_offset",
|
||||
default=-1,
|
||||
help="stop decomplation when seeing an offset greater or equal to this; default is "
|
||||
"-1 which indicates no stopping point.",
|
||||
)
|
||||
@click.argument("files", nargs=-1, type=click.Path(readable=True), required=True)
|
||||
def main_bin(
|
||||
asm: bool,
|
||||
asm_plus: bool,
|
||||
show_grammar,
|
||||
tree: bool,
|
||||
tree_plus: bool,
|
||||
linemaps: bool,
|
||||
verify,
|
||||
recurse_dirs: bool,
|
||||
outfile,
|
||||
start_offset: int,
|
||||
stop_offset: int,
|
||||
files,
|
||||
):
|
||||
"""
|
||||
Cross Python bytecode decompiler for Python bytecode up to Python 3.8.
|
||||
"""
|
||||
|
||||
version_tuple = sys.version_info[0:2]
|
||||
if version_tuple < (3, 7):
|
||||
print(
|
||||
f"Error: This version of the {program} runs from Python 3.7 or greater."
|
||||
f"You need another branch of this code for Python before 3.7."
|
||||
f""" \n\tYou have version: {version_tuple_to_str()}."""
|
||||
)
|
||||
except getopt.GetoptError as e:
|
||||
print("%s: %s" % (os.path.basename(sys.argv[0]), e), file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
|
||||
options = {
|
||||
"showasm": None
|
||||
}
|
||||
for opt, val in opts:
|
||||
if opt in ("-h", "--help"):
|
||||
print(__doc__)
|
||||
sys.exit(0)
|
||||
elif opt in ("-V", "--version"):
|
||||
print("%s %s" % (program, __version__))
|
||||
sys.exit(0)
|
||||
elif opt == "--verify":
|
||||
options["do_verify"] = "strong"
|
||||
elif opt == "--syntax-verify":
|
||||
options["do_verify"] = "weak"
|
||||
elif opt == "--fragments":
|
||||
options["do_fragments"] = True
|
||||
elif opt == "--verify-run":
|
||||
options["do_verify"] = "verify-run"
|
||||
elif opt == "--linemaps":
|
||||
options["do_linemaps"] = True
|
||||
elif opt in ("--asm", "-a"):
|
||||
if options["showasm"] == None:
|
||||
options["showasm"] = "after"
|
||||
else:
|
||||
options["showasm"] = "both"
|
||||
options["do_verify"] = None
|
||||
elif opt in ("--tree", "-t"):
|
||||
if "showast" not in options:
|
||||
options["showast"] = {}
|
||||
if val == "before":
|
||||
options["showast"][val] = True
|
||||
elif val == "after":
|
||||
options["showast"][val] = True
|
||||
else:
|
||||
options["showast"]["before"] = True
|
||||
options["do_verify"] = None
|
||||
elif opt in ("--tree+", "-T"):
|
||||
if "showast" not in options:
|
||||
options["showast"] = {}
|
||||
options["showast"]["after"] = True
|
||||
options["showast"]["before"] = True
|
||||
options["do_verify"] = None
|
||||
elif opt in ("--grammar", "-g"):
|
||||
options["showgrammar"] = True
|
||||
elif opt == "-o":
|
||||
outfile = val
|
||||
elif opt in ("--timestamp", "-d"):
|
||||
timestamp = True
|
||||
elif opt in ("--compile", "-c"):
|
||||
source_paths.append(val)
|
||||
elif opt == "-p":
|
||||
numproc = int(val)
|
||||
elif opt in ("--recurse", "-r"):
|
||||
recurse_dirs = True
|
||||
elif opt == "--encoding":
|
||||
options["source_encoding"] = val
|
||||
else:
|
||||
print(opt, file=sys.stderr)
|
||||
usage()
|
||||
numproc = 0
|
||||
out_base = None
|
||||
|
||||
# expand directory if specified
|
||||
out_base = None
|
||||
source_paths: List[str] = []
|
||||
timestamp = False
|
||||
timestampfmt = "# %Y.%m.%d %H:%M:%S %Z"
|
||||
pyc_paths = files
|
||||
|
||||
# Expand directory if "recurse" was specified.
|
||||
if recurse_dirs:
|
||||
expanded_files = []
|
||||
for f in pyc_paths:
|
||||
@@ -194,15 +210,32 @@ def main_bin():
|
||||
out_base = outfile
|
||||
outfile = None
|
||||
|
||||
# A second -a turns show_asm="after" into show_asm="before"
|
||||
if asm_plus or asm:
|
||||
asm_opt = "both" if asm_plus else "after"
|
||||
else:
|
||||
asm_opt = None
|
||||
|
||||
if timestamp:
|
||||
print(time.strftime(timestampfmt))
|
||||
|
||||
if numproc <= 1:
|
||||
show_ast = {"before": tree or tree_plus, "after": tree_plus}
|
||||
try:
|
||||
result = main(
|
||||
src_base, out_base, pyc_paths, source_paths, outfile, **options
|
||||
src_base,
|
||||
out_base,
|
||||
pyc_paths,
|
||||
source_paths,
|
||||
outfile,
|
||||
showasm=asm_opt,
|
||||
showgrammar=show_grammar,
|
||||
showast=show_ast,
|
||||
do_verify=verify,
|
||||
do_linemaps=linemaps,
|
||||
start_offset=start_offset,
|
||||
stop_offset=stop_offset,
|
||||
)
|
||||
result = [options.get("do_verify", None)] + list(result)
|
||||
if len(pyc_paths) > 1:
|
||||
mess = status_msg(*result)
|
||||
print("# " + mess)
|
||||
|
Reference in New Issue
Block a user