-
-
Notifications
You must be signed in to change notification settings - Fork 34.5k
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 1 commit
74b0235
619cad5
184232e
4dd889b
6a46ce7
bb50cfe
83a21e0
d861c84
18d29cb
a3ef55b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
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 |
|---|---|---|
|
|
@@ -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()} | ||
|
Member
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
Member
Author
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
Member
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.
Member
Author
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?
Member
Author
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
Member
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. 😄
Member
Author
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
|
||
| 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) | ||
|
Member
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. Other errors cause
Member
Author
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. Indeed, though I believe Though if you prefer to be consistent, I can change it to
Member
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, it just for consistency (also,
Member
Author
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. Updated in 18d29cb to use |
||
| if not no_default_keywords: | ||
| options.keywords |= DEFAULTKEYWORDS | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.