more rna renaming for non-animated properties: mainly Texface, Particle & Pointcache...
[blender.git] / source / blender / makesrna / rna_cleanup / rna_cleaner.py
1 #! /usr/bin/env python3.1
2
3 """
4 This script is used to help cleaning RNA api.
5
6 Typical line in the input file (elements in [] are optional).
7
8 [comment *] ToolSettings.snap_align_rotation -> use_snap_align_rotation:    boolean    [Align rotation with the snapping target]
9
10 Geterate output format from blender run this:
11  ./blender.bin --background --python ./release/scripts/modules/rna_info.py 2> source/blender/makesrna/rna_cleanup/out.txt
12 """
13
14
15 def font_bold(mystring):
16     """
17     Formats the string as bold, to be used in printouts.
18     """
19     font_bold = "\033[1m"
20     font_reset = "\033[0;0m"
21     return font_bold + mystring + font_reset
22     
23
24 def usage():
25     """
26     Prints script usage.
27     """
28     import sys
29     scriptname = sys.argv[0]
30     sort_choices_string = '|'.join(sort_choices)
31     message = "\nUSAGE:"
32     message += "\n%s input-file (.txt|.py) order-priority (%s).\n" % (font_bold(scriptname), sort_choices_string)
33     message += "%s -h for help\n" % font_bold(scriptname)
34     print(message)
35     exit()
36
37
38 def help():
39     """
40     Prints script' help.
41     """
42     message = '\nHELP:'
43     message += '\nRun this script to re-format the edits you make in the input file.\n'
44     message += 'Do quick modification to important fields like \'to\' and don\'t care about fields like \'changed\' or \'description\' and save.\n'
45     message += 'The script outputs 3 files:\n'
46     message += '   1) *_clean.txt: is formatted same as the .txt input, can be edited by user.\n'
47     message += '   2) *_clean.py: is formatted same as the .py input, can be edited by user.\n'
48     message += '   3) rna_api.py is not formatted for readability and go under complete check. Can be used for rna cleanup.\n'
49     print(message)
50     usage()
51
52
53 def check_commandline():
54     """
55     Takes parameters from the commandline.
56     """
57     import sys
58     # Usage
59     if len(sys.argv)==1 or len(sys.argv)>3:
60         usage()
61     if sys.argv[1] == '-h':
62         help()
63     elif not (sys.argv[1].endswith(".txt") or sys.argv[1].endswith(".py")):
64         print ('\nBad input file extension... exiting.')
65         usage()
66     else:
67         inputfile = sys.argv[1]
68     if len(sys.argv) == 2:
69         sort_priority = default_sort_choice
70         print ('\nSecond parameter missing: choosing to order by %s.' % font_bold(sort_priority))
71     elif len(sys.argv)==3:
72         sort_priority = sys.argv[2]
73         if sort_priority not in sort_choices:
74             print('\nWrong sort_priority... exiting.')
75             usage()
76     return (inputfile, sort_priority)
77
78
79 def check_prefix(prop, btype):
80     # reminder: props=[comment, changed, bclass, bfrom, bto, kwcheck, btype, description]
81     if btype == "boolean":
82         if '_' in prop:
83             prefix = prop.split('_')[0]
84             if prefix not in kw_prefixes:
85                 return 'BAD-PREFIX: ' + prefix
86             else:
87                 return prefix + '_'
88         elif prop in kw:
89             return 'SPECIAL-KEYWORD: ' + prop
90         else:
91             return 'BAD-KEYWORD: ' + prop
92     else:
93         return ""
94
95
96 def check_if_changed(a,b):
97     if a != b: return 'changed'
98     else: return 'same'
99
100
101 def get_props_from_txt(input_filename):
102     """
103     If the file is *.txt, the script assumes it is formatted as outlined in this script docstring
104     """
105     
106     file=open(input_filename,'r')
107     file_lines=file.readlines()
108     file.close()
109
110     props_list=[]
111     props_length_max=[0,0,0,0,0,0,0,0]
112     
113     done_text = "+"
114     done = 0
115     tot = 0
116     
117     for line in file_lines:
118         
119         # debug
120         #print(line)
121         
122         # empty line or comment
123         if not line.strip():
124             continue
125         
126         if line.startswith("#"):
127             line = line[1:]
128
129         # class
130         [bclass, tail] = [x.strip() for x in line.split('.', 1)]
131
132         # comment
133         if '*' in bclass:
134             [comment, bclass] = [x.strip() for x in bclass.split('*', 1)]
135         else:
136             comment= ''
137
138         # skipping the header if we have one.
139         # the header is assumed to be "NOTE * CLASS.FROM -> TO:   TYPE  DESCRIPTION"
140         if comment == 'NOTE' and bclass == 'CLASS':
141             continue
142
143         # from
144         [bfrom, tail] = [x.strip() for x in tail.split('->', 1)]
145
146         # to
147         [bto, tail] = [x.strip() for x in tail.split(':', 1)]
148
149         # type, description
150         try:
151             [btype, description] = tail.split(None, 1)
152             # make life easy and strip quotes
153             description = description.replace("'", "").replace('"', "").replace("\\", "").strip()
154         except ValueError:
155             [btype, description] = [tail,'NO DESCRIPTION']
156
157         # keyword-check
158         kwcheck = check_prefix(bto, btype)
159
160         # changed
161         changed = check_if_changed(bfrom, bto)
162         
163         # lists formatting
164         props=[comment, changed, bclass, bfrom, bto, kwcheck, btype, description]
165         props_list.append(props)
166         props_length_max=list(map(max,zip(props_length_max,list(map(len,props)))))
167         
168         if done_text in comment:
169             done += 1
170         tot += 1
171     
172     print("Total done %.2f" % (done / tot * 100.0) )
173     
174     return (props_list,props_length_max)
175
176
177 def get_props_from_py(input_filename):
178     """
179     If the file is *.py, the script assumes it contains a python list (as "rna_api=[...]")
180     This means that this script executes the text in the py file with an exec(text).
181     """    
182     # adds the list "rna_api" to this function's scope
183     rna_api = __import__(input_filename[:-3]).rna_api
184
185     props_length_max = [0 for i in rna_api[0]] # this way if the vector will take more elements we are safe
186     for index,props in enumerate(rna_api):
187         comment, changed, bclass, bfrom, bto, kwcheck, btype, description = props
188         kwcheck = check_prefix(bto, btype)   # keyword-check
189         changed = check_if_changed(bfrom, bto)  # changed?
190         description = repr(description)
191         description = description.replace("'", "").replace('"', "").replace("\\", "").strip()
192         rna_api[index] = [comment, changed, bclass, bfrom, bto, kwcheck, btype, description]
193         props_length = list(map(len,props)) # lengths
194         props_length_max = list(map(max,zip(props_length_max,props_length)))    # max lengths
195     return (rna_api,props_length_max)
196
197
198 def get_props(input_filename):
199     if input_filename.endswith(".txt"):
200         props_list,props_length_max = get_props_from_txt(input_filename)
201     elif input_filename.endswith(".py"):
202         props_list,props_length_max = get_props_from_py(input_filename)
203     return (props_list,props_length_max)
204
205         
206 def sort(props_list, sort_priority):
207     """
208     reminder
209     props=[comment, changed, bclass, bfrom, bto, kwcheck, btype, description]
210     """
211
212     # order based on the i-th element in lists
213     if sort_priority == "class.to":
214         props_list = sorted(props_list, key=lambda p: (p[2], p[4]))
215     else:
216         i = sort_choices.index(sort_priority)
217         if i == 0:
218             props_list = sorted(props_list, key=lambda p: p[i], reverse=True)
219         else:
220             props_list = sorted(props_list, key=lambda p: p[i])
221         
222     print ('\nSorted by %s.' % font_bold(sort_priority))
223     return props_list
224
225
226 def file_basename(input_filename):
227     # if needed will use os.path
228     if input_filename.endswith(".txt"):
229         if input_filename.endswith("_work.txt"):
230             base_filename = input_filename.replace("_work.txt", "")
231         else:
232             base_filename = input_filename.replace(".txt", "")
233     elif input_filename.endswith(".py"):
234         if input_filename.endswith("_work.py"):
235             base_filename = input_filename.replace("_work.py", "")
236         else:
237             base_filename = input_filename.replace(".py", "")
238
239     return base_filename
240
241
242 def write_files(basename, props_list, props_length_max):
243     """
244     Writes in 3 files:
245       * output_filename_work.txt: formatted as txt input file (can be edited)
246       * output_filename_work.py:  formatted for readability (can be edited)
247       * rna_api.py: unformatted, just as final output
248     """
249
250     f_rna = open("rna_api.py",'w')
251     f_txt = open(basename + '_work.txt','w')
252     f_py = open(basename + '_work.py','w')
253
254     # reminder: props=[comment, changed, bclass, bfrom, bto, kwcheck, btype, description]
255     # [comment *] ToolSettings.snap_align_rotation -> use_snap_align_rotation:    boolean    [Align rotation with the snapping target]
256     rna = py = txt = ''
257     props_list = [['NOTE', 'CHANGED', 'CLASS', 'FROM', 'TO', 'KEYWORD-CHECK', 'TYPE', 'DESCRIPTION']] + props_list
258     for props in props_list:
259         #txt
260         
261         # quick way we can tell if it changed
262         if props[3] == props[4]: txt += "#"
263         else: txt += " "
264     
265         if props[0] != '': txt +=  '%s * ' % props[0]   # comment
266         txt +=  '%s.%s -> %s:   %s  "%s"\n' % tuple(props[2:5] + props[6:])   # skipping keyword-check
267         # rna_api
268         if props[0] == 'NOTE': indent = '#   '
269         else: indent = '    '
270         rna += indent + '("%s", "%s", "%s", "%s", "%s"),\n' % tuple(props[2:5] + props[6:]) # description is already string formatted
271         # py
272         blanks = [' '* (x[0]-x[1]) for x in zip(props_length_max,list(map(len,props)))]
273         props = [('"%s"%s' if props[-1] != x[0] else "%s%s") % (x[0],x[1]) for x in zip(props,blanks)]
274         py += indent + '(%s, %s, %s, %s, %s, %s, %s, "%s"),\n' % tuple(props)
275
276     f_txt.write(txt)
277     f_py.write("rna_api = [\n%s]\n" % py)
278     f_rna.write("rna_api = [\n%s]\n" % rna)
279     
280     # write useful py script, wont hurt
281     f_py.write("\n'''\n")
282     f_py.write("for p_note, p_changed, p_class, p_from, p_to, p_check, p_type, p_desc in rna_api:\n")
283     f_py.write("    print(p_to)\n")
284     f_py.write("\n'''\n")
285
286     f_txt.close()
287     f_py.close()
288     f_rna.close()
289
290     print ('\nSaved %s, %s and %s.\n' % (font_bold(f_txt.name), font_bold(f_py.name), font_bold(f_rna.name) ) )
291
292
293 def main():
294
295     global sort_choices, default_sort_choice
296     global kw_prefixes, kw
297
298     sort_choices = ['note','changed','class','from','to','kw', 'class.to']
299     default_sort_choice = sort_choices[-1]
300     kw_prefixes = [ 'active','apply','bl','exclude','has','invert','is','lock', \
301                     'pressed','show','show_only','use','use_only','layers','states', 'select']
302     kw = ['active','hide','invert','select','layers','mute','states','use','lock']
303
304     input_filename, sort_priority = check_commandline()
305     props_list,props_length_max = get_props(input_filename)
306     props_list = sort(props_list,sort_priority)
307
308     output_basename = file_basename(input_filename)
309     write_files(output_basename, props_list,props_length_max)
310
311
312 if __name__=='__main__':
313     import sys
314     if not sys.version.startswith("3"):
315         print("Incorrect python version, use python 3!")
316     else:
317         main()
318