making PO/POT/... update scripts py3.2 compatible + making it almost win compatible
[blender-staging.git] / po / update_msg.py
1 # $Id$
2 # ***** BEGIN GPL LICENSE BLOCK *****
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 #
18 # ***** END GPL LICENSE BLOCK *****
19
20 # <pep8 compliant>
21
22 # Write out messages.txt from blender
23
24 # Execite:
25 #   blender --background --python po/update_msg.py
26
27 import os
28
29 CURRENT_DIR = os.path.abspath(os.path.dirname(__file__))
30 SOURCE_DIR = os.path.normpath(os.path.abspath(os.path.join(CURRENT_DIR, "..")))
31
32 FILE_NAME_MESSAGES = os.path.join(CURRENT_DIR, "messages.txt")
33
34
35 def dump_messages_rna(messages):
36     import bpy
37
38     # -------------------------------------------------------------------------
39     # Function definitions
40
41     def walkProperties(properties):
42         import bpy
43         for prop in properties:
44             messages.add(prop.name)
45             messages.add(prop.description)
46
47             if isinstance(prop, bpy.types.EnumProperty):
48                 for item in prop.enum_items:
49                     messages.add(item.name)
50                     messages.add(item.description)
51
52     def walkRNA(bl_rna):
53         if bl_rna.name and bl_rna.name != bl_rna.identifier:
54             messages.add(bl_rna.name)
55
56         if bl_rna.description:
57             messages.add(bl_rna.description)
58
59         walkProperties(bl_rna.properties)
60
61     def walkClass(cls):
62         walkRNA(cls.bl_rna)
63
64     def walk_keymap_hierarchy(hier):
65         for lvl in hier:
66             messages.add(lvl[0])
67
68             if lvl[3]:
69                 walk_keymap_hierarchy(lvl[3])
70
71     # -------------------------------------------------------------------------
72     # Dump Messages
73
74     for cls in type(bpy.context).__base__.__subclasses__():
75         walkClass(cls)
76
77     for cls in bpy.types.Space.__subclasses__():
78         walkClass(cls)
79
80     for cls in bpy.types.Operator.__subclasses__():
81         walkClass(cls)
82
83     from bpy_extras.keyconfig_utils import KM_HIERARCHY
84
85     walk_keymap_hierarchy(KM_HIERARCHY)
86
87
88     ## XXX. what is this supposed to do, we wrote the file already???
89     #_walkClass(bpy.types.SpaceDopeSheetEditor)
90
91
92 def dump_messages_pytext(messages):
93     """ dumps text inlined in the python user interface: eg.
94
95         layout.prop("someprop", text="My Name")
96     """
97     import ast
98
99     # -------------------------------------------------------------------------
100     # Gather function names
101
102     import bpy
103     # key: func_id
104     # val: [(arg_kw, arg_pos), (arg_kw, arg_pos), ...]
105     func_translate_args = {}
106
107     # so far only 'text' keywords, but we may want others translated later
108     translate_kw = ("text", )
109
110     for func_id, func in bpy.types.UILayout.bl_rna.functions.items():
111         # check it has a 'text' argument
112         for (arg_pos, (arg_kw, arg)) in enumerate(func.parameters.items()):
113             if ((arg_kw in translate_kw) and
114                 (arg.is_output == False) and
115                 (arg.type == 'STRING')):
116
117                 func_translate_args.setdefault(func_id, []).append((arg_kw,
118                                                                     arg_pos))
119     # print(func_translate_args)
120
121     # -------------------------------------------------------------------------
122     # Function definitions
123
124     def extract_strings(fp, node_container):
125         """ Recursively get strings, needed incase we have "Blah" + "Blah",
126             passed as an argument in that case it wont evaluate to a string.
127         """
128         for node in ast.walk(node_container):
129             if type(node) == ast.Str:
130                 eval_str = ast.literal_eval(node)
131                 if eval_str:
132                     # print("%s:%d: %s" % (fp, node.lineno, eval_str))  # testing
133                     messages.add(eval_str)
134
135     def extract_strings_from_file(fn):
136         filedata = open(fn, 'r', encoding="utf8")
137         root_node = ast.parse(filedata.read(), fn, 'exec')
138         filedata.close()
139
140         for node in ast.walk(root_node):
141             if type(node) == ast.Call:
142                 # print("found function at")
143                 # print("%s:%d" % (fn, node.lineno))
144
145                 # lambda's
146                 if type(node.func) == ast.Name:
147                     continue
148
149                 # getattr(self, con.type)(context, box, con)
150                 if not hasattr(node.func, "attr"):
151                     continue
152
153                 translate_args = func_translate_args.get(node.func.attr, ())
154
155                 # do nothing if not found
156                 for arg_kw, arg_pos in translate_args:
157                     if arg_pos < len(node.args):
158                         extract_strings(fn, node.args[arg_pos])
159                     else:
160                         for kw in node.keywords:
161                             if kw.arg == arg_kw:
162                                 extract_strings(fn, kw.value)
163
164     # -------------------------------------------------------------------------
165     # Dump Messages
166
167     mod_dir = os.path.join(SOURCE_DIR, "release", "scripts", "startup", "bl_ui")
168
169     files = [os.path.join(mod_dir, f)
170              for f in os.listdir(mod_dir)
171              if not f.startswith("_")
172              if f.endswith("py")
173              ]
174
175     for fn in files:
176         extract_strings_from_file(fn)
177
178
179 def dump_messages():
180     messages = {""}
181
182     # get strings from RNA
183     dump_messages_rna(messages)
184
185     # get strings from UI layout definitions text="..." args
186     dump_messages_pytext(messages)
187
188     messages.remove("")
189
190     message_file = open(FILE_NAME_MESSAGES, 'w', encoding="utf8")
191     message_file.writelines("\n".join(sorted(messages)))
192     message_file.close()
193
194     print("Written %d messages to: %r" % (len(messages), FILE_NAME_MESSAGES))
195
196
197 def main():
198
199     try:
200         import bpy
201     except ImportError:
202         print("This script must run from inside blender")
203         return
204
205     dump_messages()
206
207
208 if __name__ == "__main__":
209     print("\n\n *** Running %r *** \n" % __file__)
210     main()