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
2 changes: 1 addition & 1 deletion Doc/library/dis.rst
Original file line number Diff line number Diff line change
Expand Up @@ -768,7 +768,7 @@ not have to be) the original ``STACK[-2]``.
end = STACK.pop()
start = STACK.pop()
container = STACK.pop()
values = STACK.pop()
value = STACK.pop()
container[start:end] = value

.. versionadded:: 3.12
Expand Down
38 changes: 34 additions & 4 deletions Lib/argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -688,11 +688,41 @@ def _expand_help(self, action):
params[name] = value.__name__
if params.get('choices') is not None:
params['choices'] = ', '.join(map(str, params['choices']))
# Before interpolating, wrap the values with color codes

t = self._theme
for name, value in params.items():
params[name] = f"{t.interpolated_value}{value}{t.reset}"
return help_string % params

result = help_string % params

if not t.reset:
return result

# Match format specifiers like: %s, %d, %(key)s, etc.
fmt_spec = r'''
%
(?:
% # %% escape
|
(?:\((?P<key>[^)]*)\))? # key
[-#0\ +]* # flags
(?:\*|\d+)? # width
(?:\.(?:\*|\d+))? # precision
[hlL]? # length modifier
[diouxXeEfFgGcrsa] # conversion type
)
'''

def colorize(match):
spec, key = match.group(0, 'key')
if spec == '%%':
return '%'
if key is not None:
# %(key)... - format and colorize
formatted = spec % {key: params[key]}
return f'{t.interpolated_value}{formatted}{t.reset}'
# bare %s etc. - format with full params dict, no colorization
return spec % params

return _re.sub(fmt_spec, colorize, help_string, flags=_re.VERBOSE)

def _iter_indented_subactions(self, action):
try:
Expand Down
32 changes: 32 additions & 0 deletions Lib/test/test_argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -7663,6 +7663,38 @@ def test_backtick_markup_special_regex_chars(self):
help_text = parser.format_help()
self.assertIn(f'{prog_extra}grep "foo.*bar" | sort{reset}', help_text)

def test_help_with_format_specifiers(self):
# GH-142950: format specifiers like %x should work with color=True
parser = argparse.ArgumentParser(prog='PROG', color=True)
parser.add_argument('--hex', type=int, default=255,
help='hex: %(default)x, alt: %(default)#x')
parser.add_argument('--zero', type=int, default=7,
help='zero: %(default)05d')
parser.add_argument('--str', default='test',
help='str: %(default)s')
parser.add_argument('--pct', type=int, default=50,
help='pct: %(default)d%%')
parser.add_argument('--literal', help='literal: 100%%')
parser.add_argument('--prog', help='prog: %(prog)s')
parser.add_argument('--type', type=int, help='type: %(type)s')
parser.add_argument('--choices', choices=['a', 'b'],
help='choices: %(choices)s')

help_text = parser.format_help()

interp = self.theme.interpolated_value
reset = self.theme.reset

self.assertIn(f'hex: {interp}ff{reset}', help_text)
self.assertIn(f'alt: {interp}0xff{reset}', help_text)
self.assertIn(f'zero: {interp}00007{reset}', help_text)
self.assertIn(f'str: {interp}test{reset}', help_text)
self.assertIn(f'pct: {interp}50{reset}%', help_text)
self.assertIn('literal: 100%', help_text)
self.assertIn(f'prog: {interp}PROG{reset}', help_text)
self.assertIn(f'type: {interp}int{reset}', help_text)
self.assertIn(f'choices: {interp}a, b{reset}', help_text)

def test_print_help_uses_target_file_for_color_decision(self):
parser = argparse.ArgumentParser(prog='PROG', color=True)
parser.add_argument('--opt')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix regression in :mod:`argparse` where format specifiers in help strings raised :exc:`ValueError`.
14 changes: 7 additions & 7 deletions Python/bytecodes.c
Original file line number Diff line number Diff line change
Expand Up @@ -1349,7 +1349,7 @@ dummy_func(
PyObject *receiver_o = PyStackRef_AsPyObjectBorrow(receiver);
PyObject *retval_o;
assert(frame->owner != FRAME_OWNED_BY_INTERPRETER);
if ((tstate->interp->eval_frame == NULL) &&
if (!IS_PEP523_HOOKED(tstate) &&
(Py_TYPE(receiver_o) == &PyGen_Type || Py_TYPE(receiver_o) == &PyCoro_Type) &&
gen_try_set_executing((PyGenObject *)receiver_o))
{
Expand Down Expand Up @@ -2596,7 +2596,7 @@ dummy_func(
PyObject *owner_o = PyStackRef_AsPyObjectBorrow(owner);

assert((oparg & 1) == 0);
DEOPT_IF(tstate->interp->eval_frame);
DEOPT_IF(IS_PEP523_HOOKED(tstate));
PyTypeObject *cls = Py_TYPE(owner_o);
assert(type_version != 0);
DEOPT_IF(FT_ATOMIC_LOAD_UINT_RELAXED(cls->tp_version_tag) != type_version);
Expand Down Expand Up @@ -3746,7 +3746,7 @@ dummy_func(
}
// Check if the call can be inlined or not
if (Py_TYPE(callable_o) == &PyFunction_Type &&
tstate->interp->eval_frame == NULL &&
!IS_PEP523_HOOKED(tstate) &&
((PyFunctionObject *)callable_o)->vectorcall == _PyFunction_Vectorcall)
{
int code_flags = ((PyCodeObject*)PyFunction_GET_CODE(callable_o))->co_flags;
Expand Down Expand Up @@ -3938,7 +3938,7 @@ dummy_func(
}

op(_CHECK_PEP_523, (--)) {
DEOPT_IF(tstate->interp->eval_frame);
DEOPT_IF(IS_PEP523_HOOKED(tstate));
}

op(_CHECK_FUNCTION_EXACT_ARGS, (callable, self_or_null, unused[oparg] -- callable, self_or_null, unused[oparg])) {
Expand Down Expand Up @@ -3974,7 +3974,7 @@ dummy_func(
}

op(_PUSH_FRAME, (new_frame -- )) {
assert(tstate->interp->eval_frame == NULL);
assert(!IS_PEP523_HOOKED(tstate));
_PyInterpreterFrame *temp = PyStackRef_Unwrap(new_frame);
DEAD(new_frame);
SYNC_SP();
Expand Down Expand Up @@ -4607,7 +4607,7 @@ dummy_func(
int positional_args = total_args - (int)PyTuple_GET_SIZE(kwnames_o);
// Check if the call can be inlined or not
if (Py_TYPE(callable_o) == &PyFunction_Type &&
tstate->interp->eval_frame == NULL &&
!IS_PEP523_HOOKED(tstate) &&
((PyFunctionObject *)callable_o)->vectorcall == _PyFunction_Vectorcall)
{
int code_flags = ((PyCodeObject*)PyFunction_GET_CODE(callable_o))->co_flags;
Expand Down Expand Up @@ -4851,7 +4851,7 @@ dummy_func(
}
else {
if (Py_TYPE(func) == &PyFunction_Type &&
tstate->interp->eval_frame == NULL &&
!IS_PEP523_HOOKED(tstate) &&
((PyFunctionObject *)func)->vectorcall == _PyFunction_Vectorcall) {
PyObject *callargs = PyStackRef_AsPyObjectSteal(callargs_st);
assert(PyTuple_CheckExact(callargs));
Expand Down
2 changes: 2 additions & 0 deletions Python/ceval_macros.h
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,8 @@ do { \
#define CHECK_CURRENT_CACHED_VALUES(N) ((void)0)
#endif

#define IS_PEP523_HOOKED(tstate) (tstate->interp->eval_frame != NULL)

static inline int
check_periodics(PyThreadState *tstate) {
_Py_CHECK_EMSCRIPTEN_SIGNALS_PERIODICALLY();
Expand Down
10 changes: 5 additions & 5 deletions Python/executor_cases.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading