Skip to content

Commit e4ac512

Browse files
committed
Handle -m parsing better
We ignore options after -m module name and preserve that to for module argument parsing. Beef up options.
1 parent 6d4b989 commit e4ac512

File tree

3 files changed

+105
-22
lines changed

3 files changed

+105
-22
lines changed

test/unit/test_options.py

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,18 +44,31 @@ def test_options():
4444
dbg, _ = setup_unit_test_debugger()
4545
dbg_opts_set = {"proc_opts", "from_ipython"}
4646

47+
print("\n1: no options: trepan3k")
48+
4749
# Test with no options. See that we have the some expected
4850
# keys getting set.
49-
opts, dbg_opts, sys_argv = process_options("1.0", [__file__])
51+
opts, dbg_opts, sys_argv = process_options("1", ["trepan3k"])
5052
diff_set = option_key_set - set(vars(opts).keys())
5153
assert diff_set == set(), "expecting at least these options keys set"
5254
assert (
5355
dbg_opts_set - set(dbg_opts.keys())
5456
) == set(), "expecting at least these processor keys set"
57+
assert sys.argv == [], "expecting all of sys.argv to be consumed"
58+
59+
print("2: trepan3k with program and argument")
60+
61+
opts, dbg_opts, sys_argv = process_options("2", ["trepan3k foo bar"])
62+
assert diff_set == set(), "expecting at least these options keys set"
63+
assert (
64+
dbg_opts_set - set(dbg_opts.keys())
65+
) == set(), "expecting at least these processor keys set"
66+
assert sys.argv == [], "expecting all of sys.argv to be consumed"
67+
68+
print("3: trepan3k more one option. A boolean option option with string argument")
5569

56-
# Try with more than one option, a boolean option and a string option.
57-
arg_str = f"{__file__} --fntrace --cd=/tmp"
58-
opts, dbg_opts, sys_argv = process_options("1.1", arg_str.split())
70+
arg_str = "trepan3k --fntrace --cd=/tmp"
71+
opts, dbg_opts, sys_argv = process_options("3", arg_str.split())
5972
assert opts.cd == "/tmp"
6073
postprocess_options(dbg, opts)
6174
assert dbg.settings["printset"] == frozenset(
@@ -64,7 +77,15 @@ def test_options():
6477

6578
# Try with an invalid style option and see that it is
6679
# rejected in postprocess option
67-
arg_str = f"{__file__} --style=fafdsaXYZZY"
68-
opts, dbg_opts, sys_argv = process_options("1.3", arg_str.split())
80+
print("4: trepan3k with invalid style")
81+
arg_str = "trepan3k --style=fafdsaXYZZY"
82+
opts, dbg_opts, sys_argv = process_options("4", arg_str.split())
6983
postprocess_options(dbg, opts)
84+
7085
# FIXME figure out a reasonable test for postprocess_options()
86+
87+
print("5: trepan3k with -m (module) and arguments to module")
88+
arg_str = "trepan3k --trace -m dis --host 0.0.0.0"
89+
opts, dbg_opts, sys_argv = process_options("5", arg_str.split())
90+
assert sys_argv == ['--host', '0.0.0.0']
91+
assert opts.module == "dis", "should have found module name"

trepan/__main__.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env python3
22
# -*- coding: iso-8859-1 -*-
3-
# Copyright (C) 2008-2010, 2013-2018, 2020-2025 Rocky Bernstein
3+
# Copyright (C) 2008-2010, 2013-2018, 2020-2026 Rocky Bernstein
44
55
#
66
# This program is free software: you can redistribute it and/or modify
@@ -80,13 +80,20 @@ def main(dbg=None, sys_argv=list(sys.argv)):
8080
# options that belong to this debugger. The original options to
8181
# invoke the debugger and script are in global sys_argv
8282

83+
if opts.module is not None:
84+
mainpyfile = opts.module
85+
if len(sys_argv) == 0:
86+
sys_argv.append(mainpyfile)
87+
else:
88+
sys_argv[0] = mainpyfile
89+
8390
if len(sys_argv) == 0:
8491
# No program given to debug. Set to go into a command loop
8592
# anyway
8693
mainpyfile = None
8794
else:
8895
mainpyfile = sys_argv[0] # Get script filename.
89-
if not osp.isfile(mainpyfile):
96+
if not osp.isfile(mainpyfile) and opts.module is None:
9097
mainpyfile = whence_file(mainpyfile)
9198
is_readable = readable(mainpyfile)
9299
if is_readable is None:
@@ -264,8 +271,10 @@ def write_wrapper(*args, **kwargs):
264271
# use non-optimized alternative.
265272
mainpyfile_noopt = pyficache.resolve_name_to_path(mainpyfile)
266273
if mainpyfile != mainpyfile_noopt and readable(mainpyfile_noopt):
267-
print(f"{__title__}: Compiled Python script given and we can't use that.")
268-
print(f"{__title__}: Substituting non-compiled name: {mainpyfile_noopt}")
274+
if opts.module is None:
275+
# We are not debugging module.
276+
print(f"{__title__}: Compiled Python script given and we can't use that.")
277+
print(f"{__title__}: Substituting non-compiled name: {mainpyfile_noopt}")
269278
mainpyfile = mainpyfile_noopt
270279
pass
271280

@@ -287,7 +296,7 @@ def write_wrapper(*args, **kwargs):
287296
# right.
288297

289298
try:
290-
if dbg.program_sys_argv and mainpyfile:
299+
if (dbg.program_sys_argv or opts.module) and mainpyfile:
291300
normal_termination = dbg.run_script(mainpyfile)
292301
if not normal_termination:
293302
break

trepan/options.py

Lines changed: 64 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# -*- coding: iso-8859-1 -*-
2-
# Copyright (C) 2013-2015, 2023-2025 Rocky Bernstein <[email protected]>
2+
# Copyright (C) 2013-2015, 2023-2026 Rocky Bernstein <[email protected]>
33
#
44
# This program is free software: you can redistribute it and/or modify
55
# it under the terms of the GNU General Public License as published by
@@ -15,6 +15,7 @@
1515
# along with this program. If not, see <http://www.gnu.org/licenses/>.
1616

1717
import codecs
18+
import importlib
1819
import os
1920
import sys
2021
from optparse import OptionParser
@@ -208,6 +209,14 @@ def process_options(pkg_version: str, sys_argv: list, option_list=None):
208209
help="Don't register this as a global debugger.",
209210
)
210211

212+
optparser.add_option(
213+
"-m",
214+
dest="module",
215+
action="store",
216+
type="string",
217+
metavar="module",
218+
help="Run library module as a script.",
219+
)
211220
optparser.add_option(
212221
"--main",
213222
dest="main",
@@ -353,8 +362,17 @@ def process_options(pkg_version: str, sys_argv: list, option_list=None):
353362

354363
sys.argv = sys_argv
355364

356-
# Here is where we *parse* arguments
357-
(opts, sys.argv) = optparser.parse_args(sys_argv[1:])
365+
# If the -m option is present, only parse options before it and
366+
# leave -m and the following module/args alone.
367+
if "-m" in sys_argv[1:]:
368+
m_index_end = sys_argv.index("-m") + 2
369+
# Parse only the arguments before '-m mod'
370+
(opts, _) = optparser.parse_args(sys_argv[1:m_index_end])
371+
# Preserve the -m and everything after as the script/module args
372+
sys.argv = sys_argv[m_index_end :]
373+
else:
374+
# Here is where we *parse* arguments
375+
(opts, sys.argv) = optparser.parse_args(sys_argv[1:])
358376

359377
if opts.edit_mode not in ("vi", "emacs"):
360378
sys.stderr.write(
@@ -410,6 +428,21 @@ def process_options(pkg_version: str, sys_argv: list, option_list=None):
410428
sys.exit(2)
411429
pass
412430

431+
if opts.module:
432+
try:
433+
module = importlib.import_module(opts.module)
434+
if module.__file__.endswith("__init__.py"):
435+
main_module = f"{opts.module}.__main__"
436+
importlib.import_module(main_module)
437+
opts.module = main_module
438+
except ImportError:
439+
print(f"No module named {opts.module}")
440+
sys.exit(3)
441+
except Exception as e:
442+
print(f"Unexpected exception importing {opts.module}:\n\t{e}")
443+
sys.exit(4)
444+
pass
445+
413446
return opts, dbg_opts, sys.argv
414447

415448

@@ -483,19 +516,39 @@ def postprocess_options(dbg, opts):
483516
import pprint
484517

485518
def doit(version, arg_str):
486-
print(f"options '{arg_str}'")
519+
print(f"Test {version}")
520+
print(f"options: '{arg_str}':")
487521
args = arg_str.split()
488522
opts, _, _ = process_options(version, args)
489523
pp.pprint(vars(opts))
524+
print(f"sys.argv: {sys.argv})")
490525
print("")
491526
return
492527

493528
pp = pprint.PrettyPrinter(indent=4)
494-
doit("1.1", "__file__")
495-
doit("1.2", f"{__file__} foo bar")
496-
doit("1.3", f"{__file__} --server")
497-
doit("1.3", f"{__file__} --command {__file__} bar baz")
498-
doit("1.4", f"{__file__} --server --client")
499-
doit("1.5", f"{__file__} --style=emacs")
500-
doit("1.6", f"{__file__} --help") # exits, so must be last
529+
530+
doit("1: no options.",
531+
"trepan3k")
532+
doit("2; program and argument",
533+
"trepan3k foo bar")
534+
doit("3: one trepan3k option",
535+
"trepan3k --server")
536+
537+
doit("4: --command option",
538+
f"trepan3k --command {__file__} bar baz")
539+
540+
doit("5", "trepan3k --server --client")
541+
doit("6", "trepan3k --style=emacs")
542+
543+
# -F bytes foo.pyc should be passed to pydisasm
544+
doit("7: trepan3k option with program having its own options",
545+
"trepan3k --trace pydisasm -F bytes foo.pyc")
546+
547+
# Options after -m pydebug should be passed to pydebug
548+
doit("8: -m option with arguments to module",
549+
"trepan3k --trace -m dis --host 0.0.0.0")
550+
551+
# --help exits, so must be last
552+
doit("9: show help",
553+
"trepan3k --help")
501554
pass

0 commit comments

Comments
 (0)