Skip to content

Commit 5348c20

Browse files
pythongh-125115 : Refactor the pdb parsing issue so positional arguments can pass through (python#140933)
1 parent e33afa7 commit 5348c20

File tree

3 files changed

+48
-36
lines changed

3 files changed

+48
-36
lines changed

Lib/pdb.py

Lines changed: 43 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3548,7 +3548,15 @@ def exit_with_permission_help_text():
35483548
sys.exit(1)
35493549

35503550

3551-
def main():
3551+
def parse_args():
3552+
# We want pdb to be as intuitive as possible to users, so we need to do some
3553+
# heuristic parsing to deal with ambiguity.
3554+
# For example:
3555+
# "python -m pdb -m foo -p 1" should pass "-p 1" to "foo".
3556+
# "python -m pdb foo.py -m bar" should pass "-m bar" to "foo.py".
3557+
# "python -m pdb -m foo -m bar" should pass "-m bar" to "foo".
3558+
# This require some customized parsing logic to find the actual debug target.
3559+
35523560
import argparse
35533561

35543562
parser = argparse.ArgumentParser(
@@ -3559,28 +3567,48 @@ def main():
35593567
color=True,
35603568
)
35613569

3562-
# We need to maunally get the script from args, because the first positional
3563-
# arguments could be either the script we need to debug, or the argument
3564-
# to the -m module
3570+
# Get all the commands out first. For backwards compatibility, we allow
3571+
# -c commands to be after the target.
35653572
parser.add_argument('-c', '--command', action='append', default=[], metavar='command', dest='commands',
35663573
help='pdb commands to execute as if given in a .pdbrc file')
3567-
parser.add_argument('-m', metavar='module', dest='module')
3568-
parser.add_argument('-p', '--pid', type=int, help="attach to the specified PID", default=None)
35693574

3570-
if len(sys.argv) == 1:
3575+
opts, args = parser.parse_known_args()
3576+
3577+
if not args:
35713578
# If no arguments were given (python -m pdb), print the whole help message.
35723579
# Without this check, argparse would only complain about missing required arguments.
3580+
# We need to add the arguments definitions here to get a proper help message.
3581+
parser.add_argument('-m', metavar='module', dest='module')
3582+
parser.add_argument('-p', '--pid', type=int, help="attach to the specified PID", default=None)
35733583
parser.print_help()
35743584
sys.exit(2)
3585+
elif args[0] == '-p' or args[0] == '--pid':
3586+
# Attach to a pid
3587+
parser.add_argument('-p', '--pid', type=int, help="attach to the specified PID", default=None)
3588+
opts, args = parser.parse_known_args()
3589+
if args:
3590+
# For --pid, any extra arguments are invalid.
3591+
parser.error(f"unrecognized arguments: {' '.join(args)}")
3592+
elif args[0] == '-m':
3593+
# Debug a module, we only need the first -m module argument.
3594+
# The rest is passed to the module itself.
3595+
parser.add_argument('-m', metavar='module', dest='module')
3596+
opt_module = parser.parse_args(args[:2])
3597+
opts.module = opt_module.module
3598+
args = args[2:]
3599+
elif args[0].startswith('-'):
3600+
# Invalid argument before the script name.
3601+
invalid_args = list(itertools.takewhile(lambda a: a.startswith('-'), args))
3602+
parser.error(f"unrecognized arguments: {' '.join(invalid_args)}")
35753603

3576-
opts, args = parser.parse_known_args()
3604+
# Otherwise it's debugging a script and we already parsed all -c commands.
3605+
3606+
return opts, args
35773607

3578-
if opts.pid:
3579-
# If attaching to a remote pid, unrecognized arguments are not allowed.
3580-
# This will raise an error if there are extra unrecognized arguments.
3581-
opts = parser.parse_args()
3582-
if opts.module:
3583-
parser.error("argument -m: not allowed with argument --pid")
3608+
def main():
3609+
opts, args = parse_args()
3610+
3611+
if getattr(opts, 'pid', None) is not None:
35843612
try:
35853613
attach(opts.pid, opts.commands)
35863614
except RuntimeError:
@@ -3592,30 +3620,10 @@ def main():
35923620
except PermissionError:
35933621
exit_with_permission_help_text()
35943622
return
3595-
elif opts.module:
3596-
# If a module is being debugged, we consider the arguments after "-m module" to
3597-
# be potential arguments to the module itself. We need to parse the arguments
3598-
# before "-m" to check if there is any invalid argument.
3599-
# e.g. "python -m pdb -m foo --spam" means passing "--spam" to "foo"
3600-
# "python -m pdb --spam -m foo" means passing "--spam" to "pdb" and is invalid
3601-
idx = sys.argv.index('-m')
3602-
args_to_pdb = sys.argv[1:idx]
3603-
# This will raise an error if there are invalid arguments
3604-
parser.parse_args(args_to_pdb)
3605-
else:
3606-
# If a script is being debugged, then pdb expects the script name as the first argument.
3607-
# Anything before the script is considered an argument to pdb itself, which would
3608-
# be invalid because it's not parsed by argparse.
3609-
invalid_args = list(itertools.takewhile(lambda a: a.startswith('-'), args))
3610-
if invalid_args:
3611-
parser.error(f"unrecognized arguments: {' '.join(invalid_args)}")
3612-
3613-
if opts.module:
3623+
elif getattr(opts, 'module', None) is not None:
36143624
file = opts.module
36153625
target = _ModuleTarget(file)
36163626
else:
3617-
if not args:
3618-
parser.error("no module or script to run")
36193627
file = args.pop(0)
36203628
if file.endswith('.pyz'):
36213629
target = _ZipTarget(file)

Lib/test/test_pdb.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3974,7 +3974,10 @@ def test_run_module_with_args(self):
39743974
commands = """
39753975
continue
39763976
"""
3977-
self._run_pdb(["calendar", "-m"], commands, expected_returncode=2)
3977+
self._run_pdb(["calendar", "-m"], commands, expected_returncode=1)
3978+
3979+
_, stderr = self._run_pdb(["-m", "calendar", "-p", "1"], commands)
3980+
self.assertIn("unrecognized arguments: -p", stderr)
39783981

39793982
stdout, _ = self._run_pdb(["-m", "calendar", "1"], commands)
39803983
self.assertIn("December", stdout)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Refactor the :mod:`pdb` parsing issue so positional arguments can pass through intuitively.

0 commit comments

Comments
 (0)