##// END OF EJS Templates
i18n: ignore doctest part to avoid warning at "make update-pot"...
FUJIWARA Katsunori -
r33816:726dd73d default
parent child Browse files
Show More
@@ -1,152 +1,159 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 sys
28 import sys
28
29
29
30
30 def escape(s):
31 def escape(s):
31 # The order is important, the backslash must be escaped first
32 # The order is important, the backslash must be escaped first
32 # since the other replacements introduce new backslashes
33 # since the other replacements introduce new backslashes
33 # themselves.
34 # themselves.
34 s = s.replace('\\', '\\\\')
35 s = s.replace('\\', '\\\\')
35 s = s.replace('\n', '\\n')
36 s = s.replace('\n', '\\n')
36 s = s.replace('\r', '\\r')
37 s = s.replace('\r', '\\r')
37 s = s.replace('\t', '\\t')
38 s = s.replace('\t', '\\t')
38 s = s.replace('"', '\\"')
39 s = s.replace('"', '\\"')
39 return s
40 return s
40
41
41
42
42 def normalize(s):
43 def normalize(s):
43 # This converts the various Python string types into a format that
44 # This converts the various Python string types into a format that
44 # is appropriate for .po files, namely much closer to C style.
45 # is appropriate for .po files, namely much closer to C style.
45 lines = s.split('\n')
46 lines = s.split('\n')
46 if len(lines) == 1:
47 if len(lines) == 1:
47 s = '"' + escape(s) + '"'
48 s = '"' + escape(s) + '"'
48 else:
49 else:
49 if not lines[-1]:
50 if not lines[-1]:
50 del lines[-1]
51 del lines[-1]
51 lines[-1] = lines[-1] + '\n'
52 lines[-1] = lines[-1] + '\n'
52 lines = map(escape, lines)
53 lines = map(escape, lines)
53 lineterm = '\\n"\n"'
54 lineterm = '\\n"\n"'
54 s = '""\n"' + lineterm.join(lines) + '"'
55 s = '""\n"' + lineterm.join(lines) + '"'
55 return s
56 return s
56
57
57
58
58 def poentry(path, lineno, s):
59 def poentry(path, lineno, s):
59 return ('#: %s:%d\n' % (path, lineno) +
60 return ('#: %s:%d\n' % (path, lineno) +
60 'msgid %s\n' % normalize(s) +
61 'msgid %s\n' % normalize(s) +
61 'msgstr ""\n')
62 'msgstr ""\n')
62
63
64 doctestre = re.compile(r'^ +>>> ', re.MULTILINE)
63
65
64 def offset(src, doc, name, default):
66 def offset(src, doc, name, default):
65 """Compute offset or issue a warning on stdout."""
67 """Compute offset or issue a warning on stdout."""
68 # remove doctest part, in order to avoid backslash mismatching
69 m = doctestre.search(doc)
70 if m:
71 doc = doc[:m.start()]
72
66 # Backslashes in doc appear doubled in src.
73 # Backslashes in doc appear doubled in src.
67 end = src.find(doc.replace('\\', '\\\\'))
74 end = src.find(doc.replace('\\', '\\\\'))
68 if end == -1:
75 if end == -1:
69 # This can happen if the docstring contains unnecessary escape
76 # This can happen if the docstring contains unnecessary escape
70 # sequences such as \" in a triple-quoted string. The problem
77 # sequences such as \" in a triple-quoted string. The problem
71 # is that \" is turned into " and so doc wont appear in src.
78 # is that \" is turned into " and so doc wont appear in src.
72 sys.stderr.write("warning: unknown offset in %s, assuming %d lines\n"
79 sys.stderr.write("warning: unknown offset in %s, assuming %d lines\n"
73 % (name, default))
80 % (name, default))
74 return default
81 return default
75 else:
82 else:
76 return src.count('\n', 0, end)
83 return src.count('\n', 0, end)
77
84
78
85
79 def importpath(path):
86 def importpath(path):
80 """Import a path like foo/bar/baz.py and return the baz module."""
87 """Import a path like foo/bar/baz.py and return the baz module."""
81 if path.endswith('.py'):
88 if path.endswith('.py'):
82 path = path[:-3]
89 path = path[:-3]
83 if path.endswith('/__init__'):
90 if path.endswith('/__init__'):
84 path = path[:-9]
91 path = path[:-9]
85 path = path.replace('/', '.')
92 path = path.replace('/', '.')
86 mod = __import__(path)
93 mod = __import__(path)
87 for comp in path.split('.')[1:]:
94 for comp in path.split('.')[1:]:
88 mod = getattr(mod, comp)
95 mod = getattr(mod, comp)
89 return mod
96 return mod
90
97
91
98
92 def docstrings(path):
99 def docstrings(path):
93 """Extract docstrings from path.
100 """Extract docstrings from path.
94
101
95 This respects the Mercurial cmdtable/table convention and will
102 This respects the Mercurial cmdtable/table convention and will
96 only extract docstrings from functions mentioned in these tables.
103 only extract docstrings from functions mentioned in these tables.
97 """
104 """
98 mod = importpath(path)
105 mod = importpath(path)
99 if mod.__doc__:
106 if mod.__doc__:
100 src = open(path).read()
107 src = open(path).read()
101 lineno = 1 + offset(src, mod.__doc__, path, 7)
108 lineno = 1 + offset(src, mod.__doc__, path, 7)
102 print(poentry(path, lineno, mod.__doc__))
109 print(poentry(path, lineno, mod.__doc__))
103
110
104 functions = list(getattr(mod, 'i18nfunctions', []))
111 functions = list(getattr(mod, 'i18nfunctions', []))
105 functions = [(f, True) for f in functions]
112 functions = [(f, True) for f in functions]
106
113
107 cmdtable = getattr(mod, 'cmdtable', {})
114 cmdtable = getattr(mod, 'cmdtable', {})
108 if not cmdtable:
115 if not cmdtable:
109 # Maybe we are processing mercurial.commands?
116 # Maybe we are processing mercurial.commands?
110 cmdtable = getattr(mod, 'table', {})
117 cmdtable = getattr(mod, 'table', {})
111 functions.extend((c[0], False) for c in cmdtable.itervalues())
118 functions.extend((c[0], False) for c in cmdtable.itervalues())
112
119
113 for func, rstrip in functions:
120 for func, rstrip in functions:
114 if func.__doc__:
121 if func.__doc__:
115 funcmod = inspect.getmodule(func)
122 funcmod = inspect.getmodule(func)
116 extra = ''
123 extra = ''
117 if funcmod.__package__ == funcmod.__name__:
124 if funcmod.__package__ == funcmod.__name__:
118 extra = '/__init__'
125 extra = '/__init__'
119 actualpath = '%s%s.py' % (funcmod.__name__.replace('.', '/'), extra)
126 actualpath = '%s%s.py' % (funcmod.__name__.replace('.', '/'), extra)
120
127
121 src = inspect.getsource(func)
128 src = inspect.getsource(func)
122 name = "%s.%s" % (actualpath, func.__name__)
129 name = "%s.%s" % (actualpath, func.__name__)
123 lineno = inspect.getsourcelines(func)[1]
130 lineno = inspect.getsourcelines(func)[1]
124 doc = func.__doc__
131 doc = func.__doc__
125 origdoc = getattr(func, '_origdoc', '')
132 origdoc = getattr(func, '_origdoc', '')
126 if rstrip:
133 if rstrip:
127 doc = doc.rstrip()
134 doc = doc.rstrip()
128 origdoc = origdoc.rstrip()
135 origdoc = origdoc.rstrip()
129 if origdoc:
136 if origdoc:
130 lineno += offset(src, origdoc, name, 1)
137 lineno += offset(src, origdoc, name, 1)
131 else:
138 else:
132 lineno += offset(src, doc, name, 1)
139 lineno += offset(src, doc, name, 1)
133 print(poentry(actualpath, lineno, doc))
140 print(poentry(actualpath, lineno, doc))
134
141
135
142
136 def rawtext(path):
143 def rawtext(path):
137 src = open(path).read()
144 src = open(path).read()
138 print(poentry(path, 1, src))
145 print(poentry(path, 1, src))
139
146
140
147
141 if __name__ == "__main__":
148 if __name__ == "__main__":
142 # It is very important that we import the Mercurial modules from
149 # It is very important that we import the Mercurial modules from
143 # the source tree where hggettext is executed. Otherwise we might
150 # the source tree where hggettext is executed. Otherwise we might
144 # accidentally import and extract strings from a Mercurial
151 # accidentally import and extract strings from a Mercurial
145 # installation mentioned in PYTHONPATH.
152 # installation mentioned in PYTHONPATH.
146 sys.path.insert(0, os.getcwd())
153 sys.path.insert(0, os.getcwd())
147 from mercurial import demandimport; demandimport.enable()
154 from mercurial import demandimport; demandimport.enable()
148 for path in sys.argv[1:]:
155 for path in sys.argv[1:]:
149 if path.endswith('.txt'):
156 if path.endswith('.txt'):
150 rawtext(path)
157 rawtext(path)
151 else:
158 else:
152 docstrings(path)
159 docstrings(path)
General Comments 0
You need to be logged in to leave comments. Login now