diff --git a/Lib/test/test_tools/i18n_data/messages.pot b/Lib/test/test_tools/i18n_data/messages.pot index e8167acfc0742b..d5c368e3d82f01 100644 --- a/Lib/test/test_tools/i18n_data/messages.pot +++ b/Lib/test/test_tools/i18n_data/messages.pot @@ -33,8 +33,8 @@ msgid "" " multiline!\n" msgstr "" -#: messages.py:46 messages.py:89 messages.py:90 messages.py:93 messages.py:94 -#: messages.py:99 messages.py:100 messages.py:101 +#: messages.py:46 messages.py:92 messages.py:93 messages.py:96 messages.py:97 +#: messages.py:102 messages.py:103 messages.py:104 msgid "foo" msgid_plural "foos" msgstr[0] "" @@ -80,18 +80,18 @@ msgstr "" msgid "default value" msgstr "" -#: messages.py:91 messages.py:92 messages.py:95 messages.py:96 +#: messages.py:94 messages.py:95 messages.py:98 messages.py:99 msgctxt "context" msgid "foo" msgid_plural "foos" msgstr[0] "" msgstr[1] "" -#: messages.py:102 +#: messages.py:105 msgid "domain foo" msgstr "" -#: messages.py:118 messages.py:119 +#: messages.py:121 messages.py:122 msgid "world" msgid_plural "worlds" msgstr[0] "" diff --git a/Lib/test/test_tools/i18n_data/messages.py b/Lib/test/test_tools/i18n_data/messages.py index 9457bcb8611020..fe7c2c304ee8ee 100644 --- a/Lib/test/test_tools/i18n_data/messages.py +++ b/Lib/test/test_tools/i18n_data/messages.py @@ -85,6 +85,9 @@ def _(x="don't extract me"): pass +def func(): + """Docstring...""" + # Other gettext functions gettext("foo") ngettext("foo", "foos", 1) diff --git a/Lib/test/test_tools/i18n_data/skipdocstrings.py b/Lib/test/test_tools/i18n_data/skipdocstrings.py new file mode 100644 index 00000000000000..bdf498b506dc49 --- /dev/null +++ b/Lib/test/test_tools/i18n_data/skipdocstrings.py @@ -0,0 +1,9 @@ +def test(x): + """'I think he’s had a heart attack!' + 'IF THERE’S ONE THING I CAN’T STAND IS PEOPLE HAVING HEART ATTACKS!!!' + 'I’m fine, now!' + 'Good. Now we’re getting somewhere.' + """ + +class Foo: + """Docstring""" diff --git a/Lib/test/test_tools/i18n_data/skipdocstrings.txt b/Lib/test/test_tools/i18n_data/skipdocstrings.txt new file mode 100644 index 00000000000000..3e545dc7999de4 --- /dev/null +++ b/Lib/test/test_tools/i18n_data/skipdocstrings.txt @@ -0,0 +1,6 @@ +fileloc.py + +skipdocstrings.py +messages.py +messages.py +test*.py \ No newline at end of file diff --git a/Lib/test/test_tools/i18n_data/testfile.py b/Lib/test/test_tools/i18n_data/testfile.py new file mode 100644 index 00000000000000..9bbf8849508ca5 --- /dev/null +++ b/Lib/test/test_tools/i18n_data/testfile.py @@ -0,0 +1,2 @@ +def func(): + """Get the Holy Hand Grenade!""" diff --git a/Lib/test/test_tools/test_i18n.py b/Lib/test/test_tools/test_i18n.py index 8416b1bad825eb..625bb1ce738ade 100644 --- a/Lib/test/test_tools/test_i18n.py +++ b/Lib/test/test_tools/test_i18n.py @@ -589,6 +589,8 @@ def extract_from_snapshots(): 'messages.py': (), 'fileloc.py': ('--docstrings',), 'docstrings.py': ('--docstrings',), + ('docstrings.py', 'skipdocstrings.py', 'testfile.py', 'docstrings.pot'): ('--docstrings', + f'--exclude-docstrings={DATA_DIR}{os.path.sep}skipdocstrings.txt'), 'comments.py': ('--add-comments=i18n:',), 'custom_keywords.py': ('--keyword=foo', '--keyword=nfoo:1,2', '--keyword=pfoo:1c,2', @@ -606,18 +608,20 @@ def extract_from_snapshots(): for filename, args in snapshots.items(): if isinstance(filename, tuple): - filename, output_file = filename + *filenames, output_file = filename output_file = DATA_DIR / output_file - input_file = DATA_DIR / filename + input_files = [DATA_DIR / file for file in filenames] else: - input_file = DATA_DIR / filename - output_file = input_file.with_suffix('.pot') - contents = input_file.read_bytes() + input_files = [DATA_DIR / filename] + output_file = input_files[0].with_suffix('.pot') + with temp_cwd(None): - Path(input_file.name).write_bytes(contents) + for input_file in input_files: + contents = input_file.read_bytes() + Path(input_file.name).write_bytes(contents) assert_python_ok('-Xutf8', Test_pygettext.script, *args, - input_file.name) - yield (input_file, output_file, + *[file.name for file in input_files]) + yield (input_files, output_file, Path('messages.pot').read_text(encoding='utf-8')) diff --git a/Misc/NEWS.d/next/Tools-Demos/2025-04-26-19-26-00.gh-issue-130197.sjdhsja.rst b/Misc/NEWS.d/next/Tools-Demos/2025-04-26-19-26-00.gh-issue-130197.sjdhsja.rst new file mode 100644 index 00000000000000..0e1d69409c30bc --- /dev/null +++ b/Misc/NEWS.d/next/Tools-Demos/2025-04-26-19-26-00.gh-issue-130197.sjdhsja.rst @@ -0,0 +1,2 @@ +Fix and test the ``--exclude-docstrings`` option in :program:`pygettext`. +Note: the option was also renamed from ``--no-docstrings`` for clarity. diff --git a/Tools/i18n/pygettext.py b/Tools/i18n/pygettext.py index f46b05067d7fde..4f276aca3f2f96 100755 --- a/Tools/i18n/pygettext.py +++ b/Tools/i18n/pygettext.py @@ -96,7 +96,7 @@ -o filename --output=filename Rename the default output file from messages.pot to filename. If - filename is `-' then the output is sent to standard out. + filename is '-' then the output is sent to standard out. -p dir --output-dir=dir @@ -110,7 +110,7 @@ Solaris # File: filename, line: line-number GNU #: filename:line - The style name is case insensitive. GNU style is the default. + The style name is case-insensitive. GNU style is the default. -v --verbose @@ -131,15 +131,18 @@ appear on a line by itself in the file. -X filename - --no-docstrings=filename + --exclude-docstrings= + This is only useful in conjunction with the -D option above. Specify a file that contains a list of files (one per line) that - should not have their docstrings extracted. This is only useful in - conjunction with the -D option above. + should not have their docstrings extracted. fnmatch-style patterns are + supported. -If `inputfile' is -, standard input is read. + +If 'inputfile' is -, standard input is read. """ import ast +import fnmatch import getopt import glob import importlib.machinery @@ -188,7 +191,7 @@ def make_escapes(pass_nonascii): global escapes, escape if pass_nonascii: # Allow non-ascii characters to pass through so that e.g. 'msgid - # "Höhe"' would not result in 'msgid "H\366he"'. Otherwise we + # "Höhe"' would not result in 'msgid "H\366he"'. Otherwise, we # escape any character outside the 32..126 range. escape = escape_ascii else: @@ -483,7 +486,8 @@ def visit_Call(self, node): def _extract_docstring(self, node): if (not self.options.docstrings or - self.options.nodocstrings.get(self.filename)): + any(fnmatch.fnmatch(self.filename, pattern) + for pattern in self.options.nodocstrings)): return docstring = ast.get_docstring(node) @@ -692,7 +696,7 @@ def main(): 'help', 'keyword=', 'no-default-keywords', 'add-location', 'no-location', 'output=', 'output-dir=', 'style=', 'verbose', 'version', 'width=', 'exclude-file=', - 'docstrings', 'no-docstrings', + 'docstrings', 'exclude-docstrings=', ]) except getopt.error as msg: usage(1, msg) @@ -714,7 +718,7 @@ class Options: width = 78 excludefilename = '' docstrings = 0 - nodocstrings = {} + nodocstrings = set() comment_tags = set() options = Options() @@ -766,16 +770,11 @@ class Options: usage(1, f'--width argument must be an integer: {arg}') elif opt in ('-x', '--exclude-file'): options.excludefilename = arg - elif opt in ('-X', '--no-docstrings'): - fp = open(arg) - try: - while 1: - line = fp.readline() - if not line: - break - options.nodocstrings[line[:-1]] = 1 - finally: - fp.close() + elif opt in ('-X', '--exclude-docstrings'): + with open(arg) as nodocstrings_file: + for line in nodocstrings_file: + line = line.strip() + options.nodocstrings.add(line) options.comment_tags = tuple(options.comment_tags)