enmerkar-underscore 2.4.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- enmerkar_underscore/__init__.py +96 -0
- enmerkar_underscore/vendor/__init__.py +0 -0
- enmerkar_underscore/vendor/enmerkar/LICENSE +30 -0
- enmerkar_underscore/vendor/enmerkar/enmerkar_extract.py +174 -0
- enmerkar_underscore/vendor/markey/COPYING +28 -0
- enmerkar_underscore/vendor/markey/README.rst +11 -0
- enmerkar_underscore/vendor/markey/__init__.py +0 -0
- enmerkar_underscore/vendor/markey/_compat.py +87 -0
- enmerkar_underscore/vendor/markey/machine.py +119 -0
- enmerkar_underscore/vendor/markey/rules.py +34 -0
- enmerkar_underscore/vendor/markey/tests/__init__.py +0 -0
- enmerkar_underscore/vendor/markey/tests/test_machine.py +15 -0
- enmerkar_underscore/vendor/markey/tests/test_rules.py +21 -0
- enmerkar_underscore/vendor/markey/tests/test_tools.py +96 -0
- enmerkar_underscore/vendor/markey/tools.py +130 -0
- enmerkar_underscore/vendor/markey/underscore.py +54 -0
- enmerkar_underscore-2.4.2.dist-info/METADATA +127 -0
- enmerkar_underscore-2.4.2.dist-info/RECORD +22 -0
- enmerkar_underscore-2.4.2.dist-info/WHEEL +5 -0
- enmerkar_underscore-2.4.2.dist-info/entry_points.txt +2 -0
- enmerkar_underscore-2.4.2.dist-info/licenses/LICENSE +12 -0
- enmerkar_underscore-2.4.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from importlib.metadata import version
|
|
2
|
+
|
|
3
|
+
import django
|
|
4
|
+
from django.template.base import Lexer
|
|
5
|
+
|
|
6
|
+
if django.VERSION[:2] >= (2, 1):
|
|
7
|
+
from django.template.base import TokenType
|
|
8
|
+
TOKEN_TEXT = TokenType.TEXT
|
|
9
|
+
else: # django < 2.1
|
|
10
|
+
from django.template.base import TOKEN_TEXT
|
|
11
|
+
|
|
12
|
+
from django.utils.encoding import force_str
|
|
13
|
+
from .vendor.enmerkar.enmerkar_extract import extract_django
|
|
14
|
+
|
|
15
|
+
from .vendor.markey import underscore
|
|
16
|
+
from .vendor.markey.machine import parse_arguments, tokenize
|
|
17
|
+
from .vendor.markey.tools import TokenStream
|
|
18
|
+
|
|
19
|
+
__version__ = version("enmerkar-underscore")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def extract(fileobj, keywords, comment_tags, options):
|
|
23
|
+
"""Extracts translation messages from underscore template files.
|
|
24
|
+
|
|
25
|
+
This method does also extract django templates. If a template does not
|
|
26
|
+
contain any django translation tags we always fallback to underscore extraction.
|
|
27
|
+
|
|
28
|
+
This is a plugin to Babel, written according to
|
|
29
|
+
http://babel.pocoo.org/docs/messages/#writing-extraction-methods
|
|
30
|
+
|
|
31
|
+
:param fileobj: the file-like object the messages should be extracted
|
|
32
|
+
from
|
|
33
|
+
:param keywords: a list of keywords (i.e. function names) that should
|
|
34
|
+
be recognized as translation functions
|
|
35
|
+
:param comment_tags: a list of translator tags to search for and
|
|
36
|
+
include in the results
|
|
37
|
+
:param options: a dictionary of additional options (optional)
|
|
38
|
+
:return: an iterator over ``(lineno, funcname, message, comments)``
|
|
39
|
+
tuples
|
|
40
|
+
:rtype: ``iterator``
|
|
41
|
+
"""
|
|
42
|
+
encoding = options.get('encoding', 'utf-8')
|
|
43
|
+
|
|
44
|
+
original_position = fileobj.tell()
|
|
45
|
+
|
|
46
|
+
text = fileobj.read().decode(encoding)
|
|
47
|
+
|
|
48
|
+
if django.VERSION[:2] >= (1, 9):
|
|
49
|
+
tokens = Lexer(text).tokenize()
|
|
50
|
+
else:
|
|
51
|
+
tokens = Lexer(text, None).tokenize()
|
|
52
|
+
|
|
53
|
+
vars = [token.token_type != TOKEN_TEXT for token in tokens]
|
|
54
|
+
|
|
55
|
+
could_be_django = any(list(vars))
|
|
56
|
+
|
|
57
|
+
if could_be_django:
|
|
58
|
+
fileobj.seek(original_position)
|
|
59
|
+
iterator = extract_django(fileobj, keywords, comment_tags, options)
|
|
60
|
+
for lineno, funcname, message, comments in iterator:
|
|
61
|
+
yield lineno, funcname, message, comments
|
|
62
|
+
else:
|
|
63
|
+
# Underscore template extraction
|
|
64
|
+
comments = []
|
|
65
|
+
|
|
66
|
+
fileobj.seek(original_position)
|
|
67
|
+
|
|
68
|
+
for lineno, line in enumerate(fileobj, 1):
|
|
69
|
+
funcname = None
|
|
70
|
+
|
|
71
|
+
stream = TokenStream.from_tuple_iter(tokenize(line, underscore.rules))
|
|
72
|
+
while not stream.eof:
|
|
73
|
+
if stream.current.type == 'gettext_begin':
|
|
74
|
+
stream.expect('gettext_begin')
|
|
75
|
+
funcname = stream.expect('func_name').value
|
|
76
|
+
args, kwargs = parse_arguments(stream, 'gettext_end')
|
|
77
|
+
|
|
78
|
+
strings = []
|
|
79
|
+
|
|
80
|
+
for arg, argtype in args:
|
|
81
|
+
if argtype == 'func_string_arg':
|
|
82
|
+
strings.append(force_str(arg))
|
|
83
|
+
else:
|
|
84
|
+
strings.append(None)
|
|
85
|
+
|
|
86
|
+
for arg in kwargs:
|
|
87
|
+
strings.append(None)
|
|
88
|
+
|
|
89
|
+
if len(strings) == 1:
|
|
90
|
+
strings = strings[0]
|
|
91
|
+
else:
|
|
92
|
+
strings = tuple(strings)
|
|
93
|
+
|
|
94
|
+
yield lineno, funcname, strings, []
|
|
95
|
+
|
|
96
|
+
stream.next()
|
|
File without changes
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
Copyright (C) 2013-2014 django-babel Team
|
|
2
|
+
Copyright (C) 2019 Extracover Holdings Limited
|
|
3
|
+
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions
|
|
8
|
+
are met:
|
|
9
|
+
|
|
10
|
+
1. Redistributions of source code must retain the above copyright
|
|
11
|
+
notice, this list of conditions and the following disclaimer.
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright
|
|
13
|
+
notice, this list of conditions and the following disclaimer in
|
|
14
|
+
the documentation and/or other materials provided with the
|
|
15
|
+
distribution.
|
|
16
|
+
3. The name of the author may not be used to endorse or promote
|
|
17
|
+
products derived from this software without specific prior
|
|
18
|
+
written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
|
|
21
|
+
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
22
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
23
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
|
24
|
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
|
26
|
+
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
27
|
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
|
|
28
|
+
IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
|
29
|
+
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
|
30
|
+
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# Vendored from https://github.com/Zegocover/enmerkar (enmerkar 0.7.1)
|
|
2
|
+
# enmerkar/__init__.py uses pkg_resources (a setuptools artifact not guaranteed
|
|
3
|
+
# to be present in Python 3.12+ environments). We only need extract_django, and
|
|
4
|
+
# enmerkar/extract.py has no such dependency, so we vendor it here directly.
|
|
5
|
+
from django.template.base import Lexer
|
|
6
|
+
from django.template.base import TokenType
|
|
7
|
+
|
|
8
|
+
from django.utils.translation import trim_whitespace
|
|
9
|
+
from django.utils.encoding import smart_str
|
|
10
|
+
|
|
11
|
+
from django.utils.translation.template import (
|
|
12
|
+
inline_re, block_re, endblock_re, plural_re, constant_re)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
TOKEN_TEXT = TokenType.TEXT
|
|
16
|
+
TOKEN_VAR = TokenType.VAR
|
|
17
|
+
TOKEN_BLOCK = TokenType.BLOCK
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def join_tokens(tokens, trim=False):
|
|
21
|
+
message = ''.join(tokens)
|
|
22
|
+
if trim:
|
|
23
|
+
message = trim_whitespace(message)
|
|
24
|
+
return message
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def strip_quotes(s):
|
|
28
|
+
if (s[0] == s[-1]) and s.startswith(("'", '"')):
|
|
29
|
+
return s[1:-1]
|
|
30
|
+
return s
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def extract_django(fileobj, keywords, comment_tags, options):
|
|
34
|
+
"""Extract messages from Django template files.
|
|
35
|
+
|
|
36
|
+
:param fileobj: the file-like object the messages should be extracted from
|
|
37
|
+
:param keywords: a list of keywords (i.e. function names) that should
|
|
38
|
+
be recognized as translation functions
|
|
39
|
+
:param comment_tags: a list of translator tags to search for and
|
|
40
|
+
include in the results
|
|
41
|
+
:param options: a dictionary of additional options (optional)
|
|
42
|
+
:return: an iterator over ``(lineno, funcname, message, comments)``
|
|
43
|
+
tuples
|
|
44
|
+
:rtype: ``iterator``
|
|
45
|
+
"""
|
|
46
|
+
intrans = False
|
|
47
|
+
inplural = False
|
|
48
|
+
trimmed = False
|
|
49
|
+
message_context = None
|
|
50
|
+
singular = []
|
|
51
|
+
plural = []
|
|
52
|
+
lineno = 1
|
|
53
|
+
|
|
54
|
+
encoding = options.get('encoding', 'utf8')
|
|
55
|
+
text = fileobj.read().decode(encoding)
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
text_lexer = Lexer(text)
|
|
59
|
+
except TypeError:
|
|
60
|
+
# Django 1.9 changed the way we invoke Lexer; older versions
|
|
61
|
+
# require two parameters.
|
|
62
|
+
text_lexer = Lexer(text, None)
|
|
63
|
+
|
|
64
|
+
for t in text_lexer.tokenize():
|
|
65
|
+
lineno += t.contents.count('\n')
|
|
66
|
+
if intrans:
|
|
67
|
+
if t.token_type == TOKEN_BLOCK:
|
|
68
|
+
endbmatch = endblock_re.match(t.contents)
|
|
69
|
+
pluralmatch = plural_re.match(t.contents)
|
|
70
|
+
if endbmatch:
|
|
71
|
+
if inplural:
|
|
72
|
+
if message_context:
|
|
73
|
+
yield (
|
|
74
|
+
lineno,
|
|
75
|
+
'npgettext',
|
|
76
|
+
[smart_str(message_context),
|
|
77
|
+
smart_str(join_tokens(singular, trimmed)),
|
|
78
|
+
smart_str(join_tokens(plural, trimmed))],
|
|
79
|
+
[],
|
|
80
|
+
)
|
|
81
|
+
else:
|
|
82
|
+
yield (
|
|
83
|
+
lineno,
|
|
84
|
+
'ngettext',
|
|
85
|
+
(smart_str(join_tokens(singular, trimmed)),
|
|
86
|
+
smart_str(join_tokens(plural, trimmed))),
|
|
87
|
+
[])
|
|
88
|
+
else:
|
|
89
|
+
if message_context:
|
|
90
|
+
yield (
|
|
91
|
+
lineno,
|
|
92
|
+
'pgettext',
|
|
93
|
+
[smart_str(message_context),
|
|
94
|
+
smart_str(join_tokens(singular, trimmed))],
|
|
95
|
+
[],
|
|
96
|
+
)
|
|
97
|
+
else:
|
|
98
|
+
yield (
|
|
99
|
+
lineno,
|
|
100
|
+
None,
|
|
101
|
+
smart_str(join_tokens(singular, trimmed)),
|
|
102
|
+
[])
|
|
103
|
+
|
|
104
|
+
intrans = False
|
|
105
|
+
inplural = False
|
|
106
|
+
message_context = None
|
|
107
|
+
singular = []
|
|
108
|
+
plural = []
|
|
109
|
+
elif pluralmatch:
|
|
110
|
+
inplural = True
|
|
111
|
+
else:
|
|
112
|
+
raise SyntaxError('Translation blocks must not include '
|
|
113
|
+
'other block tags: %s' % t.contents)
|
|
114
|
+
elif t.token_type == TOKEN_VAR:
|
|
115
|
+
if inplural:
|
|
116
|
+
plural.append('%%(%s)s' % t.contents)
|
|
117
|
+
else:
|
|
118
|
+
singular.append('%%(%s)s' % t.contents)
|
|
119
|
+
elif t.token_type == TOKEN_TEXT:
|
|
120
|
+
if inplural:
|
|
121
|
+
plural.append(t.contents)
|
|
122
|
+
else:
|
|
123
|
+
singular.append(t.contents)
|
|
124
|
+
else:
|
|
125
|
+
if t.token_type == TOKEN_BLOCK:
|
|
126
|
+
imatch = inline_re.match(t.contents)
|
|
127
|
+
bmatch = block_re.match(t.contents)
|
|
128
|
+
cmatches = constant_re.findall(t.contents)
|
|
129
|
+
if imatch:
|
|
130
|
+
g = imatch.group(1)
|
|
131
|
+
g = strip_quotes(g)
|
|
132
|
+
message_context = imatch.group(3)
|
|
133
|
+
if message_context:
|
|
134
|
+
# strip quotes
|
|
135
|
+
message_context = message_context[1:-1]
|
|
136
|
+
yield (
|
|
137
|
+
lineno,
|
|
138
|
+
'pgettext',
|
|
139
|
+
[smart_str(message_context), smart_str(g)],
|
|
140
|
+
[],
|
|
141
|
+
)
|
|
142
|
+
message_context = None
|
|
143
|
+
else:
|
|
144
|
+
yield lineno, None, smart_str(g), []
|
|
145
|
+
elif bmatch:
|
|
146
|
+
if bmatch.group(2):
|
|
147
|
+
message_context = bmatch.group(2)[1:-1]
|
|
148
|
+
for fmatch in constant_re.findall(t.contents):
|
|
149
|
+
stripped_fmatch = strip_quotes(fmatch)
|
|
150
|
+
yield lineno, None, smart_str(stripped_fmatch), []
|
|
151
|
+
intrans = True
|
|
152
|
+
inplural = False
|
|
153
|
+
trimmed = 'trimmed' in t.split_contents()
|
|
154
|
+
singular = []
|
|
155
|
+
plural = []
|
|
156
|
+
elif cmatches:
|
|
157
|
+
for cmatch in cmatches:
|
|
158
|
+
stripped_cmatch = strip_quotes(cmatch)
|
|
159
|
+
yield lineno, None, smart_str(stripped_cmatch), []
|
|
160
|
+
elif t.token_type == TOKEN_VAR:
|
|
161
|
+
parts = t.contents.split('|')
|
|
162
|
+
cmatch = constant_re.match(parts[0])
|
|
163
|
+
if cmatch:
|
|
164
|
+
stripped_cmatch = strip_quotes(cmatch.group(1))
|
|
165
|
+
yield lineno, None, smart_str(stripped_cmatch), []
|
|
166
|
+
for p in parts[1:]:
|
|
167
|
+
if p.find(':_(') >= 0:
|
|
168
|
+
p1 = p.split(':', 1)[1]
|
|
169
|
+
if p1[0] == '_':
|
|
170
|
+
p1 = p1[1:]
|
|
171
|
+
if p1[0] == '(':
|
|
172
|
+
p1 = p1.strip('()')
|
|
173
|
+
p1 = strip_quotes(p1)
|
|
174
|
+
yield lineno, None, smart_str(p1), []
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
Copyright (C) 2014 Christopher Grebs.
|
|
2
|
+
All rights reserved.
|
|
3
|
+
|
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
|
5
|
+
modification, are permitted provided that the following conditions
|
|
6
|
+
are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright
|
|
9
|
+
notice, this list of conditions and the following disclaimer.
|
|
10
|
+
2. Redistributions in binary form must reproduce the above copyright
|
|
11
|
+
notice, this list of conditions and the following disclaimer in
|
|
12
|
+
the documentation and/or other materials provided with the
|
|
13
|
+
distribution.
|
|
14
|
+
3. The name of the author may not be used to endorse or promote
|
|
15
|
+
products derived from this software without specific prior
|
|
16
|
+
written permission.
|
|
17
|
+
|
|
18
|
+
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
|
|
19
|
+
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
20
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
21
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
|
22
|
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
23
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
|
24
|
+
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
25
|
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
|
|
26
|
+
IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
|
27
|
+
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
|
28
|
+
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
These files were copied here from https://github.com/EnTeQuAk/markey which is a
|
|
2
|
+
project by the same original owner as the project this repo was forked from
|
|
3
|
+
(django-babel-underscore).
|
|
4
|
+
|
|
5
|
+
The original project hasn't had updates in 8 years and while a PR was made to
|
|
6
|
+
make the same fix upstream (https://github.com/EnTeQuAk/markey/pull/6) we can't
|
|
7
|
+
wait for it to get merged before the Redwood release cut (May 2024).
|
|
8
|
+
|
|
9
|
+
To reduce the number of forks we have in play for frontend tech that we have
|
|
10
|
+
already deprecated, we copy the bits we need here and make the updates we need
|
|
11
|
+
to get support for Python 3.11 and 3.12. The content was copied from https://github.com/EnTeQuAk/markey/commit/d9fc042b147824164908f00a24da6ce2c26dcd8e before we added modifications to allow for Python 3.11 support.
|
|
File without changes
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
PY2 = sys.version_info[0] == 2
|
|
4
|
+
PY3 = sys.version_info[0] == 3
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
_identity = lambda x: x
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
if not PY2:
|
|
11
|
+
text_type = str
|
|
12
|
+
string_types = (str,)
|
|
13
|
+
integer_types = (int, )
|
|
14
|
+
unichr = chr
|
|
15
|
+
|
|
16
|
+
text_to_native = lambda s, enc: s
|
|
17
|
+
|
|
18
|
+
iterkeys = lambda d: iter(d.keys())
|
|
19
|
+
itervalues = lambda d: iter(d.values())
|
|
20
|
+
iteritems = lambda d: iter(d.items())
|
|
21
|
+
|
|
22
|
+
from io import StringIO, BytesIO
|
|
23
|
+
import pickle
|
|
24
|
+
|
|
25
|
+
izip = zip
|
|
26
|
+
imap = map
|
|
27
|
+
range_type = range
|
|
28
|
+
|
|
29
|
+
cmp = lambda a, b: (a > b) - (a < b)
|
|
30
|
+
|
|
31
|
+
else:
|
|
32
|
+
text_type = unicode
|
|
33
|
+
string_types = (str, unicode)
|
|
34
|
+
integer_types = (int, long)
|
|
35
|
+
|
|
36
|
+
text_to_native = lambda s, enc: s.encode(enc)
|
|
37
|
+
unichr = unichr
|
|
38
|
+
|
|
39
|
+
iterkeys = lambda d: d.iterkeys()
|
|
40
|
+
itervalues = lambda d: d.itervalues()
|
|
41
|
+
iteritems = lambda d: d.iteritems()
|
|
42
|
+
|
|
43
|
+
from cStringIO import StringIO as BytesIO
|
|
44
|
+
from StringIO import StringIO
|
|
45
|
+
import cPickle as pickle
|
|
46
|
+
|
|
47
|
+
from itertools import izip, imap
|
|
48
|
+
range_type = xrange
|
|
49
|
+
|
|
50
|
+
cmp = cmp
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
number_types = integer_types + (float,)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# Mostly taken from Django.
|
|
57
|
+
def force_text(s, encoding='utf-8', errors='strict'):
|
|
58
|
+
# Handle the common case first for performance reasons.
|
|
59
|
+
if isinstance(s, text_type):
|
|
60
|
+
return s
|
|
61
|
+
try:
|
|
62
|
+
if not isinstance(s, string_types):
|
|
63
|
+
if PY3:
|
|
64
|
+
if isinstance(s, bytes):
|
|
65
|
+
s = text_type(s, encoding, errors)
|
|
66
|
+
else:
|
|
67
|
+
s = text_type(s)
|
|
68
|
+
elif hasattr(s, '__unicode__'):
|
|
69
|
+
s = text_type(s)
|
|
70
|
+
else:
|
|
71
|
+
s = text_type(bytes(s), encoding, errors)
|
|
72
|
+
else:
|
|
73
|
+
# Note: We use .decode() here, instead of six.text_type(s, encoding,
|
|
74
|
+
# errors), so that if s is a SafeBytes, it ends up being a
|
|
75
|
+
# SafeText at the end.
|
|
76
|
+
s = s.decode(encoding, errors)
|
|
77
|
+
except UnicodeDecodeError as e:
|
|
78
|
+
if not isinstance(s, Exception):
|
|
79
|
+
raise UnicodeDecodeError(s, *e.args)
|
|
80
|
+
else:
|
|
81
|
+
# If we get to here, the caller has passed in an Exception
|
|
82
|
+
# subclass populated with non-ASCII bytestring data without a
|
|
83
|
+
# working unicode method. Try to handle this without raising a
|
|
84
|
+
# further exception by individually forcing the exception args
|
|
85
|
+
# to unicode.
|
|
86
|
+
s = ' '.join([force_text(arg, encoding, errors) for arg in s])
|
|
87
|
+
return s
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
from .rules import include
|
|
2
|
+
from ._compat import force_text
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def iter_rules(x, rules):
|
|
6
|
+
for rule in rules[x]:
|
|
7
|
+
if rule.__class__ is include:
|
|
8
|
+
for item in iter_rules(rule, rules):
|
|
9
|
+
yield item
|
|
10
|
+
else:
|
|
11
|
+
yield rule
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def tokenize(string, rules, encoding='utf-8'):
|
|
15
|
+
string = force_text(string)
|
|
16
|
+
|
|
17
|
+
pos = 0
|
|
18
|
+
end = len(string)
|
|
19
|
+
stack = [(None, 'everything')]
|
|
20
|
+
rule_cache = {}
|
|
21
|
+
text_buffer = []
|
|
22
|
+
add_text = text_buffer.append
|
|
23
|
+
push = stack.append
|
|
24
|
+
flatten = ''.join
|
|
25
|
+
|
|
26
|
+
while pos < end:
|
|
27
|
+
state = stack[-1][1]
|
|
28
|
+
if state not in rule_cache:
|
|
29
|
+
rule_cache[state] = list(iter_rules(state, rules))
|
|
30
|
+
for rule in rule_cache[state]:
|
|
31
|
+
m = rule.match(string, pos)
|
|
32
|
+
if m is not None:
|
|
33
|
+
# first flush text that is left in the buffer
|
|
34
|
+
if text_buffer:
|
|
35
|
+
text = flatten(text_buffer)
|
|
36
|
+
if text:
|
|
37
|
+
yield 'text', text
|
|
38
|
+
del text_buffer[:]
|
|
39
|
+
|
|
40
|
+
# now enter the new scopes if entered in a
|
|
41
|
+
# non silent way
|
|
42
|
+
if rule.enter is not None:
|
|
43
|
+
push((rule.enter + '_end', rule.enter))
|
|
44
|
+
yield rule.enter + '_begin', m.group()
|
|
45
|
+
|
|
46
|
+
# now process the data
|
|
47
|
+
if callable(rule.token):
|
|
48
|
+
for item in rule.token(m):
|
|
49
|
+
yield item
|
|
50
|
+
elif rule.token is not None:
|
|
51
|
+
yield rule.token, m.group()
|
|
52
|
+
|
|
53
|
+
# now check if we leave something. if the state was
|
|
54
|
+
# entered non silent, send a close token.
|
|
55
|
+
pos = m.end()
|
|
56
|
+
for x in range(rule.leave):
|
|
57
|
+
announce, item = stack.pop()
|
|
58
|
+
if announce is not None:
|
|
59
|
+
yield announce, m.group()
|
|
60
|
+
|
|
61
|
+
break
|
|
62
|
+
else:
|
|
63
|
+
char = string[pos]
|
|
64
|
+
add_text(char)
|
|
65
|
+
pos += 1
|
|
66
|
+
|
|
67
|
+
# if the text buffer is left filled, we flush it
|
|
68
|
+
if text_buffer:
|
|
69
|
+
text = flatten(text_buffer)
|
|
70
|
+
if text:
|
|
71
|
+
yield 'text', text
|
|
72
|
+
|
|
73
|
+
# if there are things in the stack left we should
|
|
74
|
+
# emit the end tokens
|
|
75
|
+
for announce, item in reversed(stack):
|
|
76
|
+
if announce is not None:
|
|
77
|
+
yield announce, u''
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def parse_arguments(stream, end_token):
|
|
81
|
+
"""
|
|
82
|
+
Helper function for function argument parsing. Pass it a
|
|
83
|
+
`TokenStream` and the delimiter token for the argument section and
|
|
84
|
+
it will extract all position and keyword arguments as well as
|
|
85
|
+
each argument's type info (string literal or not).
|
|
86
|
+
|
|
87
|
+
Returns a ``(args, kwargs)`` tuple.
|
|
88
|
+
"""
|
|
89
|
+
args = []
|
|
90
|
+
kwargs = {}
|
|
91
|
+
keywords = []
|
|
92
|
+
while stream.current.type != end_token:
|
|
93
|
+
if stream.current.type == 'func_string_arg':
|
|
94
|
+
value = stream.current.value.strip()
|
|
95
|
+
for char in ['\'', '"']:
|
|
96
|
+
if value.startswith(char) and value.endswith(char):
|
|
97
|
+
value = value[1: -1]
|
|
98
|
+
break
|
|
99
|
+
stream.next()
|
|
100
|
+
if keywords:
|
|
101
|
+
for keyword in keywords:
|
|
102
|
+
kwargs[keyword] = value
|
|
103
|
+
del keywords[:]
|
|
104
|
+
else:
|
|
105
|
+
args.append((value, 'func_string_arg'))
|
|
106
|
+
elif stream.current.type == 'text':
|
|
107
|
+
args.append((stream.current.value, 'text'))
|
|
108
|
+
stream.next()
|
|
109
|
+
elif stream.current.type == 'func_kwarg':
|
|
110
|
+
keywords.append(stream.current.value)
|
|
111
|
+
stream.next()
|
|
112
|
+
elif stream.current.type == 'func_argument_delimiter':
|
|
113
|
+
stream.next()
|
|
114
|
+
else:
|
|
115
|
+
break
|
|
116
|
+
for keyword in keywords:
|
|
117
|
+
args.append((keyword, 'func_kwarg'))
|
|
118
|
+
|
|
119
|
+
return tuple(args), kwargs
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from ._compat import izip
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def bygroups(*args):
|
|
6
|
+
"""
|
|
7
|
+
Callback creator for bygroup yielding.
|
|
8
|
+
"""
|
|
9
|
+
return lambda m: izip(args, m.groups())
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class include(str):
|
|
13
|
+
"""
|
|
14
|
+
Tells the lexer to include tokens from another set.
|
|
15
|
+
"""
|
|
16
|
+
__slots__ = ()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ruleset(tuple):
|
|
20
|
+
__slots__ = ()
|
|
21
|
+
|
|
22
|
+
def __new__(cls, *args):
|
|
23
|
+
return tuple.__new__(cls, args)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class rule(object):
|
|
27
|
+
"""This represents a parsing rule."""
|
|
28
|
+
__slots__ = ('match', 'token', 'enter', 'leave')
|
|
29
|
+
|
|
30
|
+
def __init__(self, regexp, token=None, enter=None, leave=0):
|
|
31
|
+
self.match = re.compile(regexp, re.U).match
|
|
32
|
+
self.token = token
|
|
33
|
+
self.enter = enter
|
|
34
|
+
self.leave = leave
|
|
File without changes
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from enmerkar_underscore.vendor.markey.machine import tokenize, parse_arguments
|
|
2
|
+
from enmerkar_underscore.vendor.markey.tools import TokenStream
|
|
3
|
+
from enmerkar_underscore.vendor.markey.underscore import rules as underscore_rules
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_parse_arguments():
|
|
7
|
+
line = '<% gettext(varible, "some string", str="foo")'
|
|
8
|
+
stream = TokenStream.from_tuple_iter(tokenize(line, underscore_rules))
|
|
9
|
+
stream.next()
|
|
10
|
+
stream.next()
|
|
11
|
+
stream.expect('gettext_begin')
|
|
12
|
+
stream.expect('func_name').value
|
|
13
|
+
args, kwargs = parse_arguments(stream, 'gettext_end')
|
|
14
|
+
assert args == (('varible', 'text'), ('some string', 'func_string_arg'),)
|
|
15
|
+
assert kwargs == {'str': 'foo'}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
from enmerkar_underscore.vendor.markey.rules import bygroups, rule, ruleset
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_bygroups():
|
|
7
|
+
match = re.match(r'(1)(2)', '12')
|
|
8
|
+
assert list(bygroups(('one',))(match)) == [(('one',), '1')]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class test_rule():
|
|
12
|
+
r = rule(r'(\d)', 'num', 'do_number')
|
|
13
|
+
assert r.match('1').group() == '1'
|
|
14
|
+
assert r.token == 'num'
|
|
15
|
+
assert r.enter == 'do_number'
|
|
16
|
+
assert r.leave == 0
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class test_ruleset():
|
|
20
|
+
rs = ruleset(rule(r'(\d)', 'num', 'do_number'),)
|
|
21
|
+
assert isinstance(rs, tuple)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from six import StringIO
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from enmerkar_underscore.vendor.markey.tools import Token, TokenStream, TokenStreamIterator
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_tokenstream():
|
|
10
|
+
s = TokenStream(iter((Token('a', 1), Token('b', 2), Token('c', 3))))
|
|
11
|
+
assert s.current == Token('a', 1)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_tokenstream_from_tuple_iter():
|
|
15
|
+
# from_tuple_iter
|
|
16
|
+
s = TokenStream.from_tuple_iter(iter((('a', 1), ('b', 2), ('c', 3))))
|
|
17
|
+
assert s.current == Token('a', 1)
|
|
18
|
+
|
|
19
|
+
# iter
|
|
20
|
+
assert isinstance(iter(s), TokenStreamIterator)
|
|
21
|
+
assert tuple(iter(s)) == (Token('a', 1), Token('b', 2), Token('c', 3))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_tokenstream_eof():
|
|
25
|
+
# eof
|
|
26
|
+
s = TokenStream(iter((Token('a', 1), Token('b', 2), Token('c', 3))))
|
|
27
|
+
assert not s.eof
|
|
28
|
+
list(s)
|
|
29
|
+
assert s.eof
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_tokenstream_look_push():
|
|
33
|
+
# look, push
|
|
34
|
+
s = TokenStream(iter((Token('a', 1), Token('b', 2), Token('c', 3))))
|
|
35
|
+
assert s.current == Token('a', 1)
|
|
36
|
+
assert s.look() == Token('b', 2)
|
|
37
|
+
s.next()
|
|
38
|
+
assert s.look() == Token('c', 3)
|
|
39
|
+
s.push(Token('b', 2))
|
|
40
|
+
assert s.look() == Token('b', 2)
|
|
41
|
+
s.push(Token('e', 4), current=True)
|
|
42
|
+
assert s.current == Token('e', 4)
|
|
43
|
+
assert s.look() == Token('b', 2)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_tokenstream_skip_next():
|
|
47
|
+
# skip, next
|
|
48
|
+
s = TokenStream(iter((Token('a', 1), Token('b', 2), Token('c', 3))))
|
|
49
|
+
s.skip(1)
|
|
50
|
+
assert s.current == Token('b', 2)
|
|
51
|
+
s.next()
|
|
52
|
+
assert s.current == Token('c', 3)
|
|
53
|
+
s.push(Token('e', 4))
|
|
54
|
+
assert s.current == Token('c', 3)
|
|
55
|
+
s.next()
|
|
56
|
+
assert s.current == Token('e', 4)
|
|
57
|
+
s.next()
|
|
58
|
+
assert s.current == Token('eof', None)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_tokenstream_expect():
|
|
62
|
+
# expect
|
|
63
|
+
s = TokenStream(iter((Token('a', 1), Token('b', 2), Token('c', 3))))
|
|
64
|
+
assert s.expect('a') == Token('a', 1)
|
|
65
|
+
assert s.expect('b', 2) == Token('b', 2)
|
|
66
|
+
pytest.raises(AssertionError, s.expect, 'e')
|
|
67
|
+
pytest.raises(AssertionError, s.expect, 'c', 5)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_tokenstream_test_shift():
|
|
71
|
+
# test
|
|
72
|
+
s = TokenStream(iter((Token('a', 1), Token('b', 2), Token('c', 3))))
|
|
73
|
+
assert s.test('a')
|
|
74
|
+
s.next()
|
|
75
|
+
assert s.test('b', 2)
|
|
76
|
+
|
|
77
|
+
# shift
|
|
78
|
+
assert s.current == Token('b', 2)
|
|
79
|
+
s.shift(Token('f', 5))
|
|
80
|
+
assert s.current == Token('f', 5)
|
|
81
|
+
s.next()
|
|
82
|
+
assert s.current == Token('b', 2)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_tokenstream_debug():
|
|
86
|
+
stream = StringIO()
|
|
87
|
+
|
|
88
|
+
_original_stdout = sys.stdout
|
|
89
|
+
sys.stdout = stream
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
s = TokenStream(iter((Token('a', 1),)))
|
|
93
|
+
s.debug()
|
|
94
|
+
assert stream.getvalue() == "Token(type='a', value=1)\n"
|
|
95
|
+
finally:
|
|
96
|
+
sys.stdout = _original_stdout
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from collections import namedtuple
|
|
3
|
+
|
|
4
|
+
#: Represents one token.
|
|
5
|
+
Token = namedtuple('Token', ('type', 'value'))
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TokenStreamIterator(object):
|
|
9
|
+
"""
|
|
10
|
+
The iterator for tokenstreams. Iterate over the stream
|
|
11
|
+
until the eof token is reached.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, stream):
|
|
15
|
+
self._stream = stream
|
|
16
|
+
|
|
17
|
+
def __iter__(self):
|
|
18
|
+
return self
|
|
19
|
+
|
|
20
|
+
def __next__(self):
|
|
21
|
+
token = self._stream.current
|
|
22
|
+
if token.type == 'eof':
|
|
23
|
+
raise StopIteration()
|
|
24
|
+
self._stream.next()
|
|
25
|
+
return token
|
|
26
|
+
|
|
27
|
+
# Python 2.x
|
|
28
|
+
next = __next__
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class TokenStream(object):
|
|
32
|
+
"""
|
|
33
|
+
A token stream wraps a generator and supports pushing tokens back.
|
|
34
|
+
It also provides some functions to expect tokens and similar stuff.
|
|
35
|
+
|
|
36
|
+
Important note: Do never push more than one token back to the
|
|
37
|
+
stream. Although the stream object won't stop you
|
|
38
|
+
from doing so, the behavior is undefined. Multiple
|
|
39
|
+
pushed tokens are only used internally!
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(self, generator):
|
|
43
|
+
self._next = lambda: next(generator)
|
|
44
|
+
self._pushed = []
|
|
45
|
+
self.current = Token('initial', '')
|
|
46
|
+
self.next()
|
|
47
|
+
|
|
48
|
+
@classmethod
|
|
49
|
+
def from_tuple_iter(cls, tupleiter):
|
|
50
|
+
return cls(Token(*a) for a in tupleiter)
|
|
51
|
+
|
|
52
|
+
def __iter__(self):
|
|
53
|
+
return TokenStreamIterator(self)
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def eof(self):
|
|
57
|
+
"""Are we at the end of the tokenstream?"""
|
|
58
|
+
return not bool(self._pushed) and self.current.type == 'eof'
|
|
59
|
+
|
|
60
|
+
def debug(self, stream=None):
|
|
61
|
+
"""Displays the tokenized code on the stream provided or stdout."""
|
|
62
|
+
if stream is None:
|
|
63
|
+
stream = sys.stdout
|
|
64
|
+
for token in self:
|
|
65
|
+
stream.write(repr(token) + '\n')
|
|
66
|
+
|
|
67
|
+
def look(self):
|
|
68
|
+
"""See what's the next token."""
|
|
69
|
+
if self._pushed:
|
|
70
|
+
return self._pushed[-1]
|
|
71
|
+
old_token = self.current
|
|
72
|
+
self.next()
|
|
73
|
+
new_token = self.current
|
|
74
|
+
self.current = old_token
|
|
75
|
+
self.push(new_token)
|
|
76
|
+
return new_token
|
|
77
|
+
|
|
78
|
+
def push(self, token, current=False):
|
|
79
|
+
"""Push a token back to the stream (only one!)."""
|
|
80
|
+
self._pushed.append(token)
|
|
81
|
+
if current:
|
|
82
|
+
self.next()
|
|
83
|
+
|
|
84
|
+
def skip(self, n):
|
|
85
|
+
"""Got n tokens ahead."""
|
|
86
|
+
for x in range(n):
|
|
87
|
+
self.next()
|
|
88
|
+
|
|
89
|
+
def next(self):
|
|
90
|
+
"""Go one token ahead."""
|
|
91
|
+
if self._pushed:
|
|
92
|
+
self.current = self._pushed.pop()
|
|
93
|
+
else:
|
|
94
|
+
try:
|
|
95
|
+
self.current = self._next()
|
|
96
|
+
except StopIteration:
|
|
97
|
+
if self.current.type != 'eof':
|
|
98
|
+
self.current = Token('eof', None)
|
|
99
|
+
|
|
100
|
+
def expect(self, type, value=None):
|
|
101
|
+
"""expect a given token."""
|
|
102
|
+
assert self.current.type == type, "expect failed (%s, %s)" % (
|
|
103
|
+
self.current.type, type)
|
|
104
|
+
if value is not None:
|
|
105
|
+
assert self.current.value == value or \
|
|
106
|
+
(value.__class__ is tuple and
|
|
107
|
+
self.current.value in value), "%s != %s" % (type, value)
|
|
108
|
+
try:
|
|
109
|
+
return self.current
|
|
110
|
+
finally:
|
|
111
|
+
self.next()
|
|
112
|
+
|
|
113
|
+
def test(self, type, value=Ellipsis):
|
|
114
|
+
"""Test the current token."""
|
|
115
|
+
return (
|
|
116
|
+
self.current.type == type and
|
|
117
|
+
(value is Ellipsis or self.current.value == value or
|
|
118
|
+
value.__class__ is tuple and
|
|
119
|
+
self.current.value in value))
|
|
120
|
+
|
|
121
|
+
def shift(self, token):
|
|
122
|
+
"""
|
|
123
|
+
Push one token into the stream.
|
|
124
|
+
"""
|
|
125
|
+
old_current = self.current
|
|
126
|
+
self.next()
|
|
127
|
+
self.push(self.current)
|
|
128
|
+
self.push(old_current)
|
|
129
|
+
self.push(token)
|
|
130
|
+
self.next()
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Full functioning underscore template parser.
|
|
2
|
+
|
|
3
|
+
from .rules import ruleset, include, rule, bygroups
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
keywords = frozenset((
|
|
7
|
+
'pluralidx', 'gettext', 'ngettext', 'gettext_noop', 'pgettext',
|
|
8
|
+
'npgettext', '_'
|
|
9
|
+
))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
rules = {
|
|
13
|
+
'everything': ruleset(
|
|
14
|
+
include('inline'),
|
|
15
|
+
include('function'),
|
|
16
|
+
),
|
|
17
|
+
'inline': ruleset(
|
|
18
|
+
rule(r'<%=', enter='interpolate'),
|
|
19
|
+
rule(r'<%-', enter='escape'),
|
|
20
|
+
rule(r'<%', enter='evaluate'),
|
|
21
|
+
),
|
|
22
|
+
'inline_with_func': ruleset(
|
|
23
|
+
include('inline'),
|
|
24
|
+
include('function')
|
|
25
|
+
),
|
|
26
|
+
'function': ruleset(
|
|
27
|
+
rule(r'({keywords})\('.format(keywords='|'.join(keywords)),
|
|
28
|
+
bygroups('func_name'), enter='gettext')
|
|
29
|
+
),
|
|
30
|
+
'gettext': ruleset(
|
|
31
|
+
rule(r'([\"\'])?\)', leave=1),
|
|
32
|
+
include('function_call')
|
|
33
|
+
),
|
|
34
|
+
'evaluate': ruleset(
|
|
35
|
+
rule(r'%>', leave=1),
|
|
36
|
+
include('inline_with_func'),
|
|
37
|
+
),
|
|
38
|
+
'interpolate': ruleset(
|
|
39
|
+
rule(r'%>', leave=1),
|
|
40
|
+
include('inline_with_func'),
|
|
41
|
+
),
|
|
42
|
+
'escape': ruleset(
|
|
43
|
+
rule(r'%>', leave=1),
|
|
44
|
+
include('inline_with_func'),
|
|
45
|
+
),
|
|
46
|
+
# function calls (parse string arguments and implicit strings)
|
|
47
|
+
'function_call': ruleset(
|
|
48
|
+
rule(',', 'func_argument_delimiter'),
|
|
49
|
+
rule('\s+', None),
|
|
50
|
+
rule(r"(?s)('([^'\\]*(?:\\.[^'\\]*)*)'|"
|
|
51
|
+
r'"([^"\\]*(?:\\.[^"\\]*)*)")', 'func_string_arg'),
|
|
52
|
+
rule(r'([\w_]+)\s*=', bygroups('func_kwarg'))
|
|
53
|
+
)
|
|
54
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: enmerkar-underscore
|
|
3
|
+
Version: 2.4.2
|
|
4
|
+
Summary: Implements a underscore extractor for django-babel.
|
|
5
|
+
Author-email: Open edX Project <oscm@openedx.org>
|
|
6
|
+
License-Expression: BSD-3-Clause
|
|
7
|
+
Project-URL: Homepage, https://github.com/openedx/enmerkar-underscore
|
|
8
|
+
Project-URL: Repository, https://github.com/openedx/enmerkar-underscore
|
|
9
|
+
Keywords: enmerkar-underscore,Python,edx
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Framework :: Django
|
|
12
|
+
Classifier: Framework :: Django :: 4.2
|
|
13
|
+
Classifier: Framework :: Django :: 5.2
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Natural Language :: English
|
|
16
|
+
Classifier: Programming Language :: Python
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Requires-Python: >=3.12
|
|
20
|
+
Description-Content-Type: text/x-rst
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: django
|
|
23
|
+
Requires-Dist: babel>=1.3
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
=================================
|
|
27
|
+
Django Babel Underscore Extractor
|
|
28
|
+
=================================
|
|
29
|
+
|
|
30
|
+
.. image:: https://badge.fury.io/py/django-babel-underscore.png
|
|
31
|
+
:target: http://badge.fury.io/py/django-babel-underscore
|
|
32
|
+
|
|
33
|
+
.. image:: https://github.com/openedx/enmerkar-underscore/workflows/Python%20CI/badge.svg?branch=master
|
|
34
|
+
:target: https://github.com/openedx/enmerkar-underscore/actions?query=workflow%3A%22Python+CI%22
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
Implements a underscore extractor for django-babel.
|
|
38
|
+
|
|
39
|
+
* Free software: BSD license
|
|
40
|
+
* Documentation: https://github.com/openedx/enmerkar-underscore
|
|
41
|
+
|
|
42
|
+
Features
|
|
43
|
+
--------
|
|
44
|
+
|
|
45
|
+
* TODO
|
|
46
|
+
|
|
47
|
+
.. :changelog:
|
|
48
|
+
|
|
49
|
+
History
|
|
50
|
+
-------
|
|
51
|
+
|
|
52
|
+
2.4.0 (2025-04-22)
|
|
53
|
+
------------------
|
|
54
|
+
|
|
55
|
+
* Add support for django 5.2
|
|
56
|
+
|
|
57
|
+
2.0.0 (2020-05-29)
|
|
58
|
+
------------------
|
|
59
|
+
|
|
60
|
+
* Drop support for Python 3.8
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
1.0.0 (2020-05-29)
|
|
64
|
+
------------------
|
|
65
|
+
|
|
66
|
+
* Rename the repo
|
|
67
|
+
* Using latest version of enmerkar.
|
|
68
|
+
* Release of pypi
|
|
69
|
+
|
|
70
|
+
0.6.0 (2020-01-07)
|
|
71
|
+
------------------
|
|
72
|
+
|
|
73
|
+
* Adds support for Python 3.6, 3.7
|
|
74
|
+
* Adds support for Django 2.0, 2.1, 2.2 and 3.0.
|
|
75
|
+
* Drop support for Django < 1.11
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
0.5.2 (2018-08-18)
|
|
79
|
+
------------------
|
|
80
|
+
|
|
81
|
+
* Adds support for Django 1.10 and 1.11.
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
0.5.1 (2016-07-15)
|
|
85
|
+
------------------
|
|
86
|
+
|
|
87
|
+
* Fix release
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
0.5.0 (2016-07-15)
|
|
91
|
+
------------------
|
|
92
|
+
|
|
93
|
+
* Remove support for Python 2.6 and Python 3.3
|
|
94
|
+
* Remove support for Django < 1.8
|
|
95
|
+
* Add support for Python 3.5
|
|
96
|
+
* Add support for Django 1.9
|
|
97
|
+
* Now requires markey 0.8
|
|
98
|
+
|
|
99
|
+
0.4.2 (2015-08-28)
|
|
100
|
+
------------------
|
|
101
|
+
|
|
102
|
+
* Update to markey 0.7
|
|
103
|
+
|
|
104
|
+
0.4.1 (2015-08-28)
|
|
105
|
+
------------------
|
|
106
|
+
|
|
107
|
+
* Fix release
|
|
108
|
+
|
|
109
|
+
0.4.0 (2015-08-28)
|
|
110
|
+
------------------
|
|
111
|
+
|
|
112
|
+
* Add support for Django 1.8
|
|
113
|
+
|
|
114
|
+
0.3.0 (2015-03-15)
|
|
115
|
+
------------------
|
|
116
|
+
|
|
117
|
+
* Added `underscore` entry_point for babel.extractors (#1)
|
|
118
|
+
|
|
119
|
+
0.2.0 (2014-12-24)
|
|
120
|
+
------------------
|
|
121
|
+
|
|
122
|
+
* Fix dependency names.
|
|
123
|
+
|
|
124
|
+
0.1.0 (2014-05-25)
|
|
125
|
+
------------------
|
|
126
|
+
|
|
127
|
+
* First release on PyPI.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
enmerkar_underscore/__init__.py,sha256=eM7M4vra7qcytA8VXhN6SNj-PgjMI4Lwx1o-sQ1am0o,3322
|
|
2
|
+
enmerkar_underscore/vendor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
enmerkar_underscore/vendor/enmerkar/LICENSE,sha256=65Z5NCGyb2yADuQ2IAQy0wAPIgQEW7cuXhOwLhY5V34,1465
|
|
4
|
+
enmerkar_underscore/vendor/enmerkar/enmerkar_extract.py,sha256=0cLhMs7ITNLLkwkmqy7taUyjy0YB1OrAqO7HRi7-uvo,6957
|
|
5
|
+
enmerkar_underscore/vendor/markey/COPYING,sha256=zsVkBdUL9ZVl2LNG-wNmF5TKtwiR9qxIgD7zcDbF6PY,1413
|
|
6
|
+
enmerkar_underscore/vendor/markey/README.rst,sha256=BrJFx727YSybIuZUYRUqUGUKeDH7nNvMCovWlOExco8,784
|
|
7
|
+
enmerkar_underscore/vendor/markey/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
enmerkar_underscore/vendor/markey/_compat.py,sha256=51WsDkLYOK99-MZgx0HvpTzPoWixcmoWTPB7mNC3X-A,2409
|
|
9
|
+
enmerkar_underscore/vendor/markey/machine.py,sha256=B8Qe96X4tYimUl8cknjEcFzQKhDPczcPpk9AZR3kLY0,3807
|
|
10
|
+
enmerkar_underscore/vendor/markey/rules.py,sha256=_uHGDO1wczBiKNV4BeG9M4dGI0PRSefxPtnWiT-LXHk,704
|
|
11
|
+
enmerkar_underscore/vendor/markey/tools.py,sha256=zG2sik1k6Jyb83dVv6xoZ0Sh3kbaUA9UrIfqzHflLtw,3735
|
|
12
|
+
enmerkar_underscore/vendor/markey/underscore.py,sha256=aNLTO_iAxobOB6ZWtBOciyNFTiS3rIZ1gShVW7q5TGA,1464
|
|
13
|
+
enmerkar_underscore/vendor/markey/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
+
enmerkar_underscore/vendor/markey/tests/test_machine.py,sha256=rNFe7Bmyjkh_CKR_Ih5PMSrlR2bkJ9rvkcUFpw_f4zA,671
|
|
15
|
+
enmerkar_underscore/vendor/markey/tests/test_rules.py,sha256=1_ZA60aow0H5AYLmE8GKXCnQR_3Oz5IYj5GDcp3R2GI,510
|
|
16
|
+
enmerkar_underscore/vendor/markey/tests/test_tools.py,sha256=RFZtkziapmJpzuQ8julXSMI1q34kB9U4WhANHr-yNQ4,2552
|
|
17
|
+
enmerkar_underscore-2.4.2.dist-info/licenses/LICENSE,sha256=OXqUbEDFudXLg2c9yUtD5V5DOJI2aA1Q0_CoAs2DEIY,1498
|
|
18
|
+
enmerkar_underscore-2.4.2.dist-info/METADATA,sha256=iFbEeYO4idwn-_ezXPQOUwh5NVU4FuAwq75VsXXx6wU,2748
|
|
19
|
+
enmerkar_underscore-2.4.2.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
20
|
+
enmerkar_underscore-2.4.2.dist-info/entry_points.txt,sha256=RrEDKrwUU3vz5RRVRFhECDEcubpmkUvaAaqWkv4HOHM,60
|
|
21
|
+
enmerkar_underscore-2.4.2.dist-info/top_level.txt,sha256=OQ-esflDonOuDESY9Lh7nOhxOpceH_6CI3lbopUABH4,20
|
|
22
|
+
enmerkar_underscore-2.4.2.dist-info/RECORD,,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Copyright (c) 2015, Christopher Grebs
|
|
2
|
+
All rights reserved.
|
|
3
|
+
|
|
4
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
5
|
+
|
|
6
|
+
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
7
|
+
|
|
8
|
+
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
|
9
|
+
|
|
10
|
+
* Neither the name of Django Babel Underscore Extractor nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
|
11
|
+
|
|
12
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
enmerkar_underscore
|