Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 43 additions & 36 deletions Lib/pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -3543,7 +3543,15 @@ def exit_with_permission_help_text():
sys.exit(1)


def main():
def parse_args():
# We want pdb to be as intuitive as possible to users, so we need to do some
# heuristic parsing to deal with ambiguity.
# For example:
# "python -m pdb -m foo -p 1" should pass "-p 1" to "foo".
# "python -m pdb foo.py -m bar" should pass "-m bar" to "foo.py".
# "python -m pdb -m foo -m bar" should pass "-m bar" to "foo".
# This require some customized parsing logic to find the actual debug target.

import argparse

parser = argparse.ArgumentParser(
Expand All @@ -3554,58 +3562,57 @@ def main():
color=True,
)

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

if len(sys.argv) == 1:
opts, args = parser.parse_known_args()

if not args:
# If no arguments were given (python -m pdb), print the whole help message.
# Without this check, argparse would only complain about missing required arguments.
# We need to add the arguments definitions here to get a proper help message.
parser.add_argument('-m', metavar='module', dest='module')
parser.add_argument('-p', '--pid', type=int, help="attach to the specified PID", default=None)
parser.print_help()
sys.exit(2)
elif args[0] == '-p' or args[0] == '--pid':
# Attach to a pid
parser.add_argument('-p', '--pid', type=int, help="attach to the specified PID", default=None)
opts, args = parser.parse_known_args()
if args:
# For --pid, any extra arguments are invalid.
parser.error(f"unrecognized arguments: {' '.join(args)}")
elif args[0] == '-m':
# Debug a module, we only need the first -m module argument.
# The rest is passed to the module itself.
parser.add_argument('-m', metavar='module', dest='module')
opt_module = parser.parse_args(args[:2])
opts.module = opt_module.module
args = args[2:]
elif args[0].startswith('-'):
# Invalid argument before the script name.
invalid_args = list(itertools.takewhile(lambda a: a.startswith('-'), args))
parser.error(f"unrecognized arguments: {' '.join(invalid_args)}")

opts, args = parser.parse_known_args()
# Otherwise it's debugging a script and we already parsed all -c commands.

return opts, args

if opts.pid:
# If attaching to a remote pid, unrecognized arguments are not allowed.
# This will raise an error if there are extra unrecognized arguments.
opts = parser.parse_args()
if opts.module:
parser.error("argument -m: not allowed with argument --pid")
def main():
opts, args = parse_args()

if getattr(opts, 'pid', None) is not None:
try:
attach(opts.pid, opts.commands)
except PermissionError as e:
exit_with_permission_help_text()
return
elif opts.module:
# If a module is being debugged, we consider the arguments after "-m module" to
# be potential arguments to the module itself. We need to parse the arguments
# before "-m" to check if there is any invalid argument.
# e.g. "python -m pdb -m foo --spam" means passing "--spam" to "foo"
# "python -m pdb --spam -m foo" means passing "--spam" to "pdb" and is invalid
idx = sys.argv.index('-m')
args_to_pdb = sys.argv[1:idx]
# This will raise an error if there are invalid arguments
parser.parse_args(args_to_pdb)
else:
# If a script is being debugged, then pdb expects the script name as the first argument.
# Anything before the script is considered an argument to pdb itself, which would
# be invalid because it's not parsed by argparse.
invalid_args = list(itertools.takewhile(lambda a: a.startswith('-'), args))
if invalid_args:
parser.error(f"unrecognized arguments: {' '.join(invalid_args)}")
sys.exit(2)

if opts.module:
elif getattr(opts, 'module', None) is not None:
file = opts.module
target = _ModuleTarget(file)
else:
if not args:
parser.error("no module or script to run")
file = args.pop(0)
if file.endswith('.pyz'):
target = _ZipTarget(file)
Expand Down
5 changes: 4 additions & 1 deletion Lib/test/test_pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -3974,7 +3974,10 @@ def test_run_module_with_args(self):
commands = """
continue
"""
self._run_pdb(["calendar", "-m"], commands, expected_returncode=2)
self._run_pdb(["calendar", "-m"], commands, expected_returncode=1)

_, stderr = self._run_pdb(["-m", "calendar", "-p", "1"], commands)
self.assertIn("unrecognized arguments: -p", stderr)

stdout, _ = self._run_pdb(["-m", "calendar", "1"], commands)
self.assertIn("December", stdout)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Refactor the :mod:`pdb` parsing issue so positional arguments can pass through intuitively.
Loading