Skip to content

Commit ea796a0

Browse files
Refactor the pdb parsing issue so positional arguments can pass through
1 parent 29b38b7 commit ea796a0

2 files changed

Lines changed: 47 additions & 37 deletions

File tree

Lib/pdb.py

Lines changed: 43 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3542,7 +3542,15 @@ def exit_with_permission_help_text():
35423542
sys.exit(1)
35433543

35443544

3545-
def main():
3545+
def parse_args():
3546+
# We want pdb to be as intuitive as possible to users, so we need to do some
3547+
# heuristic parsing to deal with ambiguity.
3548+
# For example:
3549+
# "python -m pdb -m foo -p 1" should pass "-p 1" to "foo".
3550+
# "python -m pdb foo.py -m bar" should pass "-m bar" to "foo.py".
3551+
# "python -m pdb -m foo -m bar" should pass "-m bar" to "foo".
3552+
# This require some customized parsing logic to find the actual debug target.
3553+
35463554
import argparse
35473555

35483556
parser = argparse.ArgumentParser(
@@ -3553,58 +3561,57 @@ def main():
35533561
color=True,
35543562
)
35553563

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

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

3570-
opts, args = parser.parse_known_args()
3598+
# Otherwise it's debugging a script and we already parsed all -c commands.
3599+
3600+
return opts, args
35713601

3572-
if opts.pid:
3573-
# If attaching to a remote pid, unrecognized arguments are not allowed.
3574-
# This will raise an error if there are extra unrecognized arguments.
3575-
opts = parser.parse_args()
3576-
if opts.module:
3577-
parser.error("argument -m: not allowed with argument --pid")
3602+
def main():
3603+
opts, args = parse_args()
3604+
3605+
if getattr(opts, 'pid', None) is not None:
35783606
try:
35793607
attach(opts.pid, opts.commands)
35803608
except PermissionError as e:
35813609
exit_with_permission_help_text()
35823610
return
3583-
elif opts.module:
3584-
# If a module is being debugged, we consider the arguments after "-m module" to
3585-
# be potential arguments to the module itself. We need to parse the arguments
3586-
# before "-m" to check if there is any invalid argument.
3587-
# e.g. "python -m pdb -m foo --spam" means passing "--spam" to "foo"
3588-
# "python -m pdb --spam -m foo" means passing "--spam" to "pdb" and is invalid
3589-
idx = sys.argv.index('-m')
3590-
args_to_pdb = sys.argv[1:idx]
3591-
# This will raise an error if there are invalid arguments
3592-
parser.parse_args(args_to_pdb)
3593-
else:
3594-
# If a script is being debugged, then pdb expects the script name as the first argument.
3595-
# Anything before the script is considered an argument to pdb itself, which would
3596-
# be invalid because it's not parsed by argparse.
3597-
invalid_args = list(itertools.takewhile(lambda a: a.startswith('-'), args))
3598-
if invalid_args:
3599-
parser.error(f"unrecognized arguments: {' '.join(invalid_args)}")
3600-
sys.exit(2)
3601-
3602-
if opts.module:
3611+
elif getattr(opts, 'module', None) is not None:
36033612
file = opts.module
36043613
target = _ModuleTarget(file)
36053614
else:
3606-
if not args:
3607-
parser.error("no module or script to run")
36083615
file = args.pop(0)
36093616
if file.endswith('.pyz'):
36103617
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)

0 commit comments

Comments
 (0)