##// END OF EJS Templates
py3: hggettext...
Dan Villiom Podlaski Christiansen -
r46390:bea8cf87 stable
parent child Browse files
Show More
@@ -1,171 +1,171 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 #
2 #
3 # hggettext - carefully extract docstrings for Mercurial
3 # hggettext - carefully extract docstrings for Mercurial
4 #
4 #
5 # Copyright 2009 Matt Mackall <mpm@selenic.com> and others
5 # Copyright 2009 Matt Mackall <mpm@selenic.com> and others
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 # The normalize function is taken from pygettext which is distributed
10 # The normalize function is taken from pygettext which is distributed
11 # with Python under the Python License, which is GPL compatible.
11 # with Python under the Python License, which is GPL compatible.
12
12
13 """Extract docstrings from Mercurial commands.
13 """Extract docstrings from Mercurial commands.
14
14
15 Compared to pygettext, this script knows about the cmdtable and table
15 Compared to pygettext, this script knows about the cmdtable and table
16 dictionaries used by Mercurial, and will only extract docstrings from
16 dictionaries used by Mercurial, and will only extract docstrings from
17 functions mentioned therein.
17 functions mentioned therein.
18
18
19 Use xgettext like normal to extract strings marked as translatable and
19 Use xgettext like normal to extract strings marked as translatable and
20 join the message cataloges to get the final catalog.
20 join the message cataloges to get the final catalog.
21 """
21 """
22
22
23 from __future__ import absolute_import, print_function
23 from __future__ import absolute_import, print_function
24
24
25 import inspect
25 import inspect
26 import os
26 import os
27 import re
27 import re
28 import sys
28 import sys
29
29
30
30
31 def escape(s):
31 def escape(s):
32 # The order is important, the backslash must be escaped first
32 # The order is important, the backslash must be escaped first
33 # since the other replacements introduce new backslashes
33 # since the other replacements introduce new backslashes
34 # themselves.
34 # themselves.
35 s = s.replace('\\', '\\\\')
35 s = s.replace('\\', '\\\\')
36 s = s.replace('\n', '\\n')
36 s = s.replace('\n', '\\n')
37 s = s.replace('\r', '\\r')
37 s = s.replace('\r', '\\r')
38 s = s.replace('\t', '\\t')
38 s = s.replace('\t', '\\t')
39 s = s.replace('"', '\\"')
39 s = s.replace('"', '\\"')
40 return s
40 return s
41
41
42
42
43 def normalize(s):
43 def normalize(s):
44 # This converts the various Python string types into a format that
44 # This converts the various Python string types into a format that
45 # is appropriate for .po files, namely much closer to C style.
45 # is appropriate for .po files, namely much closer to C style.
46 lines = s.split('\n')
46 lines = s.split('\n')
47 if len(lines) == 1:
47 if len(lines) == 1:
48 s = '"' + escape(s) + '"'
48 s = '"' + escape(s) + '"'
49 else:
49 else:
50 if not lines[-1]:
50 if not lines[-1]:
51 del lines[-1]
51 del lines[-1]
52 lines[-1] = lines[-1] + '\n'
52 lines[-1] = lines[-1] + '\n'
53 lines = map(escape, lines)
53 lines = map(escape, lines)
54 lineterm = '\\n"\n"'
54 lineterm = '\\n"\n"'
55 s = '""\n"' + lineterm.join(lines) + '"'
55 s = '""\n"' + lineterm.join(lines) + '"'
56 return s
56 return s
57
57
58
58
59 def poentry(path, lineno, s):
59 def poentry(path, lineno, s):
60 return (
60 return (
61 '#: %s:%d\n' % (path, lineno)
61 '#: %s:%d\n' % (path, lineno)
62 + 'msgid %s\n' % normalize(s)
62 + 'msgid %s\n' % normalize(s)
63 + 'msgstr ""\n'
63 + 'msgstr ""\n'
64 )
64 )
65
65
66
66
67 doctestre = re.compile(r'^ +>>> ', re.MULTILINE)
67 doctestre = re.compile(r'^ +>>> ', re.MULTILINE)
68
68
69
69
70 def offset(src, doc, name, lineno, default):
70 def offset(src, doc, name, lineno, default):
71 """Compute offset or issue a warning on stdout."""
71 """Compute offset or issue a warning on stdout."""
72 # remove doctest part, in order to avoid backslash mismatching
72 # remove doctest part, in order to avoid backslash mismatching
73 m = doctestre.search(doc)
73 m = doctestre.search(doc)
74 if m:
74 if m:
75 doc = doc[: m.start()]
75 doc = doc[: m.start()]
76
76
77 # Backslashes in doc appear doubled in src.
77 # Backslashes in doc appear doubled in src.
78 end = src.find(doc.replace('\\', '\\\\'))
78 end = src.find(doc.replace('\\', '\\\\'))
79 if end == -1:
79 if end == -1:
80 # This can happen if the docstring contains unnecessary escape
80 # This can happen if the docstring contains unnecessary escape
81 # sequences such as \" in a triple-quoted string. The problem
81 # sequences such as \" in a triple-quoted string. The problem
82 # is that \" is turned into " and so doc wont appear in src.
82 # is that \" is turned into " and so doc wont appear in src.
83 sys.stderr.write(
83 sys.stderr.write(
84 "%s:%d:warning:"
84 "%s:%d:warning:"
85 " unknown docstr offset, assuming %d lines\n"
85 " unknown docstr offset, assuming %d lines\n"
86 % (name, lineno, default)
86 % (name, lineno, default)
87 )
87 )
88 return default
88 return default
89 else:
89 else:
90 return src.count('\n', 0, end)
90 return src.count('\n', 0, end)
91
91
92
92
93 def importpath(path):
93 def importpath(path):
94 """Import a path like foo/bar/baz.py and return the baz module."""
94 """Import a path like foo/bar/baz.py and return the baz module."""
95 if path.endswith('.py'):
95 if path.endswith('.py'):
96 path = path[:-3]
96 path = path[:-3]
97 if path.endswith('/__init__'):
97 if path.endswith('/__init__'):
98 path = path[:-9]
98 path = path[:-9]
99 path = path.replace('/', '.')
99 path = path.replace('/', '.')
100 mod = __import__(path)
100 mod = __import__(path)
101 for comp in path.split('.')[1:]:
101 for comp in path.split('.')[1:]:
102 mod = getattr(mod, comp)
102 mod = getattr(mod, comp)
103 return mod
103 return mod
104
104
105
105
106 def docstrings(path):
106 def docstrings(path):
107 """Extract docstrings from path.
107 """Extract docstrings from path.
108
108
109 This respects the Mercurial cmdtable/table convention and will
109 This respects the Mercurial cmdtable/table convention and will
110 only extract docstrings from functions mentioned in these tables.
110 only extract docstrings from functions mentioned in these tables.
111 """
111 """
112 mod = importpath(path)
112 mod = importpath(path)
113 if not path.startswith('mercurial/') and mod.__doc__:
113 if not path.startswith('mercurial/') and mod.__doc__:
114 with open(path) as fobj:
114 with open(path) as fobj:
115 src = fobj.read()
115 src = fobj.read()
116 lineno = 1 + offset(src, mod.__doc__, path, 1, 7)
116 lineno = 1 + offset(src, mod.__doc__, path, 1, 7)
117 print(poentry(path, lineno, mod.__doc__))
117 print(poentry(path, lineno, mod.__doc__))
118
118
119 functions = list(getattr(mod, 'i18nfunctions', []))
119 functions = list(getattr(mod, 'i18nfunctions', []))
120 functions = [(f, True) for f in functions]
120 functions = [(f, True) for f in functions]
121
121
122 cmdtable = getattr(mod, 'cmdtable', {})
122 cmdtable = getattr(mod, 'cmdtable', {})
123 if not cmdtable:
123 if not cmdtable:
124 # Maybe we are processing mercurial.commands?
124 # Maybe we are processing mercurial.commands?
125 cmdtable = getattr(mod, 'table', {})
125 cmdtable = getattr(mod, 'table', {})
126 functions.extend((c[0], False) for c in cmdtable.itervalues())
126 functions.extend((c[0], False) for c in cmdtable.values())
127
127
128 for func, rstrip in functions:
128 for func, rstrip in functions:
129 if func.__doc__:
129 if func.__doc__:
130 docobj = func # this might be a proxy to provide formatted doc
130 docobj = func # this might be a proxy to provide formatted doc
131 func = getattr(func, '_origfunc', func)
131 func = getattr(func, '_origfunc', func)
132 funcmod = inspect.getmodule(func)
132 funcmod = inspect.getmodule(func)
133 extra = ''
133 extra = ''
134 if funcmod.__package__ == funcmod.__name__:
134 if funcmod.__package__ == funcmod.__name__:
135 extra = '/__init__'
135 extra = '/__init__'
136 actualpath = '%s%s.py' % (funcmod.__name__.replace('.', '/'), extra)
136 actualpath = '%s%s.py' % (funcmod.__name__.replace('.', '/'), extra)
137
137
138 src = inspect.getsource(func)
138 src = inspect.getsource(func)
139 lineno = inspect.getsourcelines(func)[1]
139 lineno = inspect.getsourcelines(func)[1]
140 doc = docobj.__doc__
140 doc = docobj.__doc__
141 origdoc = getattr(docobj, '_origdoc', '')
141 origdoc = getattr(docobj, '_origdoc', '')
142 if rstrip:
142 if rstrip:
143 doc = doc.rstrip()
143 doc = doc.rstrip()
144 origdoc = origdoc.rstrip()
144 origdoc = origdoc.rstrip()
145 if origdoc:
145 if origdoc:
146 lineno += offset(src, origdoc, actualpath, lineno, 1)
146 lineno += offset(src, origdoc, actualpath, lineno, 1)
147 else:
147 else:
148 lineno += offset(src, doc, actualpath, lineno, 1)
148 lineno += offset(src, doc, actualpath, lineno, 1)
149 print(poentry(actualpath, lineno, doc))
149 print(poentry(actualpath, lineno, doc))
150
150
151
151
152 def rawtext(path):
152 def rawtext(path):
153 with open(path) as f:
153 with open(path) as f:
154 src = f.read()
154 src = f.read()
155 print(poentry(path, 1, src))
155 print(poentry(path, 1, src))
156
156
157
157
158 if __name__ == "__main__":
158 if __name__ == "__main__":
159 # It is very important that we import the Mercurial modules from
159 # It is very important that we import the Mercurial modules from
160 # the source tree where hggettext is executed. Otherwise we might
160 # the source tree where hggettext is executed. Otherwise we might
161 # accidentally import and extract strings from a Mercurial
161 # accidentally import and extract strings from a Mercurial
162 # installation mentioned in PYTHONPATH.
162 # installation mentioned in PYTHONPATH.
163 sys.path.insert(0, os.getcwd())
163 sys.path.insert(0, os.getcwd())
164 from mercurial import demandimport
164 from mercurial import demandimport
165
165
166 demandimport.enable()
166 demandimport.enable()
167 for path in sys.argv[1:]:
167 for path in sys.argv[1:]:
168 if path.endswith('.txt'):
168 if path.endswith('.txt'):
169 rawtext(path)
169 rawtext(path)
170 else:
170 else:
171 docstrings(path)
171 docstrings(path)
General Comments 0
You need to be logged in to leave comments. Login now