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