Attachment 'findtext.py'
Download 1 #!/usr/bin/env python
2 """ Find texts in python source tree using Python compiler package
3
4 Usage: findtext.py path
5
6 Find all calls to gettext function in the source tree and collect the
7 texts in a dict. Use compiler to create an abstract syntax tree from
8 each source file, then find the nodes for gettext function call, and
9 get the text from the call.
10
11 Localized texts are used usually translated during runtime by gettext
12 functions and apear in the source as _('text...'). TextFinder class
13 finds calls to the '_' function in any namespace, or your prefered
14 gettext function.
15
16 Note that TextFinder will only retrive text from function calls with
17 a constant argument like _("text"). Calls like _("text" % locals()),
18 _("text 1" + "text 2") are marked as bad call in the report, and the
19 text is not retrived into the dictionary.
20
21 Note also that texts in source can appear several times in the same file
22 or different files, but they will only apear once in the dictinary that
23 this tool creates.
24
25 What is missing from this tool is the rather simple machinary to create
26 a language file from the dictionary. This machinary exist allready in
27 MoinMoin [http://moin.sf.net] which was tool was written for.
28
29
30 findtext - Find texts in python source tree
31
32 Copyright (C) 2003 Nir Soffer
33
34 Based on code by Seo Sanghyeon and the python compiler package.
35
36 This program is free software; you can redistribute it and/or modify
37 it under the terms of the GNU General Public License as published by
38 the Free Software Foundation; either version 2 of the License, or
39 (at your option) any later version.
40
41 This program is distributed in the hope that it will be useful, but
42 WITHOUT ANY WARRANTY; without even the implied warranty of
43 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
44 General Public License for more details:
45 http://www.gnu.org/licenses/gpl.html
46 """
47
48 import sys
49 import os
50
51 import compiler
52 from compiler.ast import Name, Const, CallFunc, Getattr
53
54
55 class TextFinder:
56 """ Walk through AST tree and collect text from gettext calls """
57
58 def __init__(self, name='_'):
59 """ Init with the gettext function name or '_'
60
61 Each time a text is found, we check if we allready have it in the
62 dictionary. If we have it, we count the item as duplicate.
63 """
64 self._name = name # getText function name
65 self._dictionary = {} # Unique texts in the found texts
66 self._found = 0 # All good calls including duplicates
67 self._bad = 0 # Bad calls: _('%s' % var) or _('a' + 'b')
68
69 def visitModule(self, node):
70 """ Start the visit from the top level
71
72 Reset node cache. Node cache is used to prevent us from visiting
73 the same node twice.
74 """
75 self._visited = {}
76 self.walk(node)
77
78 def parseNode(self, node):
79 """ Parse function call nodes and collect text """
80 if node.__class__ == CallFunc and node.args:
81 child = node.node
82 klass = child.__class__
83
84 if (# Stanard call _('text')
85 (klass == Name and child.name == self._name) or
86 # A call to an object attribue: object._('text')
87 (klass == Getattr and child.attrname == self._name)):
88 if node.args[0].__class__ == Const:
89 # Good call with a constant _('text')
90 self.addText(node.args[0].value)
91 else:
92 # Bad call like _('a' + 'b')
93 self._bad = self._bad + 1
94 return 1
95 return 0
96
97 def walk(self, node):
98 """ Walk thourgh all nodes """
99 if self._visited.has_key(node):
100 # We visited this node allready
101 return
102
103 self._visited[node] = True
104 if not self.parseNode(node):
105 for child in node.getChildNodes():
106 self.walk(child)
107
108 def addText(self, text):
109 """ Add text to dictionary and count found texts.
110
111 Note that number of texts in dictionary could be different from
112 the number of texts found, because some texts apear several
113 times in the code.
114
115 """
116
117 self._found = self._found + 1
118 self._dictionary[text] = text
119
120 def dictionary(self):
121 return self._dictionary
122
123 def bad(self):
124 return self._bad
125
126 def found(self):
127 return self._found
128
129
130 def visit(path, visitor):
131 tree = compiler.parseFile(path)
132 compiler.walk(tree, visitor)
133
134
135 if __name__ == '__main__':
136 if not sys.argv[1:2]:
137 print 'Usage %s path' % __file__
138 sys.exit(1)
139
140 textFinder = TextFinder()
141 top = sys.argv[1]
142 found = 0
143 unique = 0
144 bad = 0
145
146 print
147 print 'Find texts in %(top)s:' % locals()
148 print
149
150 for root, dirs, files in os.walk(top):
151 for name in files:
152 if name.endswith('.py'):
153 path = os.path.join(root, name)
154 visit(path, textFinder)
155
156 # Report each file results
157 new_unique = len(textFinder.dictionary()) - unique
158 new_found = textFinder.found() - found
159 print '%(path)s: %(new_unique)d (of %(new_found)d)' % locals()
160
161 # warn about bad calls - These should be fixed!
162 new_bad = textFinder.bad() - bad
163 if new_bad:
164 print '### Warning: %(new_bad)d bad call(s)' % locals()
165 print
166
167 unique = unique + new_unique
168 bad = bad + new_bad
169 found = found + new_found
170
171 print
172 print ('%(unique)d unique texts in dictionary of '
173 '%(found)d texts in source') % locals()
174 if bad:
175 print '### %(bad)d bad calls' % locals()
176
Attached Files
To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.You are not allowed to attach a file to this page.