-
-
Notifications
You must be signed in to change notification settings - Fork 33.2k
gh-130453: pygettext: Extend support for specifying custom keywords #130463
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
74b0235
619cad5
184232e
4dd889b
6a46ce7
bb50cfe
83a21e0
d861c84
18d29cb
a3ef55b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| # SOME DESCRIPTIVE TITLE. | ||
| # Copyright (C) YEAR ORGANIZATION | ||
| # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. | ||
| # | ||
| msgid "" | ||
| msgstr "" | ||
| "Project-Id-Version: PACKAGE VERSION\n" | ||
| "POT-Creation-Date: 2000-01-01 00:00+0000\n" | ||
| "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | ||
| "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | ||
| "Language-Team: LANGUAGE <[email protected]>\n" | ||
| "MIME-Version: 1.0\n" | ||
| "Content-Type: text/plain; charset=UTF-8\n" | ||
| "Content-Transfer-Encoding: 8bit\n" | ||
| "Generated-By: pygettext.py 1.5\n" | ||
|
|
||
|
|
||
| #: custom_keywords.py:9 custom_keywords.py:10 | ||
| msgid "bar" | ||
| msgstr "" | ||
|
|
||
| #: custom_keywords.py:12 | ||
| msgid "cat" | ||
| msgid_plural "cats" | ||
| msgstr[0] "" | ||
| msgstr[1] "" | ||
|
|
||
| #: custom_keywords.py:13 | ||
| msgid "dog" | ||
| msgid_plural "dogs" | ||
| msgstr[0] "" | ||
| msgstr[1] "" | ||
|
|
||
| #: custom_keywords.py:15 | ||
| msgctxt "context" | ||
| msgid "bar" | ||
| msgstr "" | ||
|
|
||
| #: custom_keywords.py:17 | ||
| msgctxt "context" | ||
| msgid "cat" | ||
| msgid_plural "cats" | ||
| msgstr[0] "" | ||
| msgstr[1] "" | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| from gettext import ( | ||
| gettext as foo, | ||
| ngettext as nfoo, | ||
| pgettext as pfoo, | ||
| npgettext as npfoo, | ||
| gettext as bar, | ||
| ) | ||
|
|
||
| foo('bar') | ||
| foo('bar', 'baz') | ||
|
|
||
| nfoo('cat', 'cats', 1) | ||
| nfoo('dog', 'dogs') | ||
|
|
||
| pfoo('context', 'bar') | ||
|
|
||
| npfoo('context', 'cat', 'cats', 1) | ||
|
|
||
| # This is an unknown keyword and should be ignored | ||
| bar('baz') | ||
|
|
||
| # 'nfoo' requires at least 2 arguments | ||
| nfoo('dog') | ||
|
|
||
| # 'pfoo' requires at least 2 arguments | ||
| pfoo('context') | ||
|
|
||
| # 'npfoo' requires at least 3 arguments | ||
| npfoo('context') | ||
| npfoo('context', 'cat') |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Extend support for specifying custom keywords in :program:`pygettext`. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -294,6 +294,89 @@ def getFilesForName(name): | |
| } | ||
|
|
||
|
|
||
| def parse_spec(spec): | ||
| """Parse a keyword spec string into a dictionary. | ||
|
|
||
| The keyword spec format defines the name of the gettext function and the | ||
| positions of the arguments that correspond to msgid, msgid_plural, and | ||
| msgctxt. The format is as follows: | ||
|
|
||
| name - the name of the gettext function, assumed to | ||
| have a single argument that is the msgid. | ||
| name:pos1 - the name of the gettext function and the position | ||
| of the msgid argument. | ||
| name:pos1,pos2 - the name of the gettext function and the positions | ||
| of the msgid and msgid_plural arguments. | ||
| name:pos1,pos2c - the name of the gettext function and the positions | ||
| of the msgid and msgctxt arguments. | ||
| name:pos1,pos2,pos3c - the name of the gettext function and the | ||
| positions of the msgid, msgid_plural, and | ||
| msgctxt arguments. | ||
|
|
||
| As an example, the spec 'foo:1,2,3c' means that the function foo has three | ||
| arguments, the first one is the msgid, the second one is the msgid_plural, | ||
| and the third one is the msgctxt. The positions are 1-based. | ||
|
|
||
| The msgctxt argument can appear in any position, but it can only appear | ||
| once. For example, the keyword specs 'foo:3c,1,2' and 'foo:1,2,3c' are | ||
| equivalent. | ||
|
|
||
| See https://www.gnu.org/software/gettext/manual/gettext.html | ||
| for more information. | ||
| """ | ||
| parts = spec.strip().split(':', 1) | ||
| if len(parts) == 1: | ||
| name = parts[0] | ||
| return name, {0: 'msgid'} | ||
|
|
||
| name, args = parts | ||
| if not args: | ||
| raise ValueError(f'Invalid keyword spec {spec!r}: ' | ||
| 'missing argument positions') | ||
|
|
||
| result = {} | ||
| for arg in args.split(','): | ||
| arg = arg.strip() | ||
| is_context = False | ||
| if arg.endswith('c'): | ||
| is_context = True | ||
| arg = arg[:-1] | ||
|
|
||
| try: | ||
| pos = int(arg) - 1 | ||
| except ValueError as e: | ||
| raise ValueError(f'Invalid keyword spec {spec!r}: ' | ||
| 'position is not an integer') from e | ||
|
|
||
| if pos < 0: | ||
| raise ValueError(f'Invalid keyword spec {spec!r}: ' | ||
| 'argument positions must be strictly positive') | ||
|
|
||
| for k, v in result.items(): | ||
| if v == pos: | ||
| raise ValueError(f'Invalid keyword spec {spec!r}: ' | ||
| 'duplicate positions') | ||
|
|
||
| if is_context: | ||
| if 'msgctxt' in result: | ||
| raise ValueError(f'Invalid keyword spec {spec!r}: ' | ||
| 'msgctxt can only appear once') | ||
| result['msgctxt'] = pos | ||
| elif 'msgid' not in result: | ||
| result['msgid'] = pos | ||
| elif 'msgid_plural' not in result: | ||
| result['msgid_plural'] = pos | ||
| else: | ||
| raise ValueError(f'Invalid keyword spec {spec!r}: ' | ||
| 'too many positions') | ||
|
|
||
| if 'msgid' not in result and 'msgctxt' in result: | ||
| raise ValueError(f'Invalid keyword spec {spec!r}: ' | ||
| 'msgctxt cannot appear without msgid') | ||
|
|
||
| return name, {v: k for k, v in result.items()} | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it be simpler to build There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did that in d861c84, let me know if you like it better like that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It was just a question. I am fine with both variants. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just wanted to let you see the difference :) I don't have a strong preference either, let's stick with the current version, then? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, I tried implementing some followup work on top of this PR (support for the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So I did the right thing by letting the PR lie down for two days. 😄 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, good call 🙂 And thanks for your super thorough reviews! It's really appreciated |
||
|
|
||
|
|
||
| @dataclass(frozen=True) | ||
| class Location: | ||
| filename: str | ||
|
|
@@ -568,7 +651,7 @@ class Options: | |
| # defaults | ||
| extractall = 0 # FIXME: currently this option has no effect at all. | ||
| escape = 0 | ||
| keywords = [] | ||
| keywords = set() | ||
| outpath = '' | ||
| outfile = 'messages.pot' | ||
| writelocations = 1 | ||
|
|
@@ -602,7 +685,7 @@ class Options: | |
| elif opt in ('-D', '--docstrings'): | ||
| options.docstrings = 1 | ||
| elif opt in ('-k', '--keyword'): | ||
| options.keywords.append(arg) | ||
| options.keywords.add(arg) | ||
tomasr8 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| elif opt in ('-K', '--no-default-keywords'): | ||
| no_default_keywords = True | ||
| elif opt in ('-n', '--add-location'): | ||
|
|
@@ -646,7 +729,10 @@ class Options: | |
| make_escapes(not options.escape) | ||
|
|
||
| # calculate all keywords | ||
| options.keywords = {kw: {0: 'msgid'} for kw in options.keywords} | ||
| try: | ||
| options.keywords = dict(parse_spec(spec) for spec in options.keywords) | ||
| except ValueError as e: | ||
| raise SystemExit(e) | ||
|
||
| if not no_default_keywords: | ||
| options.keywords |= DEFAULTKEYWORDS | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.