##// END OF EJS Templates
i18n: ignore docstring for modules under mercurial...
FUJIWARA Katsunori -
r33818:d5ef1760 default
parent child Browse files
Show More
@@ -1,161 +1,161 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 ('#: %s:%d\n' % (path, lineno) +
60 return ('#: %s:%d\n' % (path, lineno) +
61 'msgid %s\n' % normalize(s) +
61 'msgid %s\n' % normalize(s) +
62 'msgstr ""\n')
62 'msgstr ""\n')
63
63
64 doctestre = re.compile(r'^ +>>> ', re.MULTILINE)
64 doctestre = re.compile(r'^ +>>> ', re.MULTILINE)
65
65
66 def offset(src, doc, name, default):
66 def offset(src, doc, name, default):
67 """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
68 # remove doctest part, in order to avoid backslash mismatching
69 m = doctestre.search(doc)
69 m = doctestre.search(doc)
70 if m:
70 if m:
71 doc = doc[:m.start()]
71 doc = doc[:m.start()]
72
72
73 # Backslashes in doc appear doubled in src.
73 # Backslashes in doc appear doubled in src.
74 end = src.find(doc.replace('\\', '\\\\'))
74 end = src.find(doc.replace('\\', '\\\\'))
75 if end == -1:
75 if end == -1:
76 # This can happen if the docstring contains unnecessary escape
76 # This can happen if the docstring contains unnecessary escape
77 # sequences such as \" in a triple-quoted string. The problem
77 # sequences such as \" in a triple-quoted string. The problem
78 # 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.
79 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"
80 % (name, default))
80 % (name, default))
81 return default
81 return default
82 else:
82 else:
83 return src.count('\n', 0, end)
83 return src.count('\n', 0, end)
84
84
85
85
86 def importpath(path):
86 def importpath(path):
87 """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."""
88 if path.endswith('.py'):
88 if path.endswith('.py'):
89 path = path[:-3]
89 path = path[:-3]
90 if path.endswith('/__init__'):
90 if path.endswith('/__init__'):
91 path = path[:-9]
91 path = path[:-9]
92 path = path.replace('/', '.')
92 path = path.replace('/', '.')
93 mod = __import__(path)
93 mod = __import__(path)
94 for comp in path.split('.')[1:]:
94 for comp in path.split('.')[1:]:
95 mod = getattr(mod, comp)
95 mod = getattr(mod, comp)
96 return mod
96 return mod
97
97
98
98
99 def docstrings(path):
99 def docstrings(path):
100 """Extract docstrings from path.
100 """Extract docstrings from path.
101
101
102 This respects the Mercurial cmdtable/table convention and will
102 This respects the Mercurial cmdtable/table convention and will
103 only extract docstrings from functions mentioned in these tables.
103 only extract docstrings from functions mentioned in these tables.
104 """
104 """
105 mod = importpath(path)
105 mod = importpath(path)
106 if mod.__doc__:
106 if not path.startswith('mercurial/') and mod.__doc__:
107 src = open(path).read()
107 src = open(path).read()
108 lineno = 1 + offset(src, mod.__doc__, path, 7)
108 lineno = 1 + offset(src, mod.__doc__, path, 7)
109 print(poentry(path, lineno, mod.__doc__))
109 print(poentry(path, lineno, mod.__doc__))
110
110
111 functions = list(getattr(mod, 'i18nfunctions', []))
111 functions = list(getattr(mod, 'i18nfunctions', []))
112 functions = [(f, True) for f in functions]
112 functions = [(f, True) for f in functions]
113
113
114 cmdtable = getattr(mod, 'cmdtable', {})
114 cmdtable = getattr(mod, 'cmdtable', {})
115 if not cmdtable:
115 if not cmdtable:
116 # Maybe we are processing mercurial.commands?
116 # Maybe we are processing mercurial.commands?
117 cmdtable = getattr(mod, 'table', {})
117 cmdtable = getattr(mod, 'table', {})
118 functions.extend((c[0], False) for c in cmdtable.itervalues())
118 functions.extend((c[0], False) for c in cmdtable.itervalues())
119
119
120 for func, rstrip in functions:
120 for func, rstrip in functions:
121 if func.__doc__:
121 if func.__doc__:
122 docobj = func # this might be a proxy to provide formatted doc
122 docobj = func # this might be a proxy to provide formatted doc
123 func = getattr(func, '_origfunc', func)
123 func = getattr(func, '_origfunc', func)
124 funcmod = inspect.getmodule(func)
124 funcmod = inspect.getmodule(func)
125 extra = ''
125 extra = ''
126 if funcmod.__package__ == funcmod.__name__:
126 if funcmod.__package__ == funcmod.__name__:
127 extra = '/__init__'
127 extra = '/__init__'
128 actualpath = '%s%s.py' % (funcmod.__name__.replace('.', '/'), extra)
128 actualpath = '%s%s.py' % (funcmod.__name__.replace('.', '/'), extra)
129
129
130 src = inspect.getsource(func)
130 src = inspect.getsource(func)
131 name = "%s.%s" % (actualpath, func.__name__)
131 name = "%s.%s" % (actualpath, func.__name__)
132 lineno = inspect.getsourcelines(func)[1]
132 lineno = inspect.getsourcelines(func)[1]
133 doc = docobj.__doc__
133 doc = docobj.__doc__
134 origdoc = getattr(docobj, '_origdoc', '')
134 origdoc = getattr(docobj, '_origdoc', '')
135 if rstrip:
135 if rstrip:
136 doc = doc.rstrip()
136 doc = doc.rstrip()
137 origdoc = origdoc.rstrip()
137 origdoc = origdoc.rstrip()
138 if origdoc:
138 if origdoc:
139 lineno += offset(src, origdoc, name, 1)
139 lineno += offset(src, origdoc, name, 1)
140 else:
140 else:
141 lineno += offset(src, doc, name, 1)
141 lineno += offset(src, doc, name, 1)
142 print(poentry(actualpath, lineno, doc))
142 print(poentry(actualpath, lineno, doc))
143
143
144
144
145 def rawtext(path):
145 def rawtext(path):
146 src = open(path).read()
146 src = open(path).read()
147 print(poentry(path, 1, src))
147 print(poentry(path, 1, src))
148
148
149
149
150 if __name__ == "__main__":
150 if __name__ == "__main__":
151 # It is very important that we import the Mercurial modules from
151 # It is very important that we import the Mercurial modules from
152 # the source tree where hggettext is executed. Otherwise we might
152 # the source tree where hggettext is executed. Otherwise we might
153 # accidentally import and extract strings from a Mercurial
153 # accidentally import and extract strings from a Mercurial
154 # installation mentioned in PYTHONPATH.
154 # installation mentioned in PYTHONPATH.
155 sys.path.insert(0, os.getcwd())
155 sys.path.insert(0, os.getcwd())
156 from mercurial import demandimport; demandimport.enable()
156 from mercurial import demandimport; demandimport.enable()
157 for path in sys.argv[1:]:
157 for path in sys.argv[1:]:
158 if path.endswith('.txt'):
158 if path.endswith('.txt'):
159 rawtext(path)
159 rawtext(path)
160 else:
160 else:
161 docstrings(path)
161 docstrings(path)
General Comments 0
You need to be logged in to leave comments. Login now