complain if running with py2
[blender.git] / source / blender / makesrna / rna_cleanup / rna_cleaner.py
1 #! /usr/bin/env python3
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][-4:] == '.txt' or sys.argv[1][-3:] == '.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, 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         rna_api[index] = [comment, changed, bclass, bfrom, bto, kwcheck, btype, description]
171         props_length = list(map(len,props)) # lengths
172         props_length_max = list(map(max,zip(props_length_max,props_length)))    # max lengths
173     return (rna_api,props_length_max)
174
175
176 def get_props(input_filename):
177     if input_filename[-4:] == '.txt':
178         props_list,props_length_max = get_props_from_txt(input_filename)
179     elif input_filename[-3:] == '.py':
180         props_list,props_length_max = get_props_from_py(input_filename)
181     return (props_list,props_length_max)
182
183         
184 def sort(props_list, sort_priority):
185     """
186     reminder
187     props=[comment, changed, bclass, bfrom, bto, kwcheck, btype, description]
188     """
189
190     # order based on the i-th element in lists
191     i = sort_choices.index(sort_priority)
192     if i == 0:
193         props_list = sorted(props_list, key=lambda p: p[i], reverse=True)
194     else:
195         props_list = sorted(props_list, key=lambda p: p[i])
196         
197     print ('\nSorted by %s.' % font_bold(sort_priority))
198     return props_list
199
200
201 def file_basename(input_filename):
202     # if needed will use os.path
203     if input_filename[-4:] == '.txt':
204         if input_filename[-9:] == '_work.txt':
205             base_filename = input_filename[:-9]
206         else:
207             base_filename = input_filename[:-4]
208     elif input_filename[-3:] == '.py':
209         if input_filename[-8:] == '_work.py':
210             base_filename = input_filename[:-8]
211         else:
212             base_filename = input_filename[:-3]
213     return base_filename
214
215
216 def write_files(basename, props_list, props_length_max):
217     """
218     Writes in 3 files:
219       * output_filename_work.txt: formatted as txt input file (can be edited)
220       * output_filename_work.py:  formatted for readability (can be edited)
221       * rna_api.py: unformatted, just as final output
222     """
223
224     f_rna = open("rna_api.py",'w')
225     f_txt = open(basename + '_work.txt','w')
226     f_py = open(basename + '_work.py','w')
227
228     # reminder: props=[comment, changed, bclass, bfrom, bto, kwcheck, btype, description]
229     # [comment *] ToolSettings.snap_align_rotation -> use_snap_align_rotation:    boolean    [Align rotation with the snapping target]
230     rna = py = txt = ''
231     props_list = [['NOTE', 'CHANGED', 'CLASS', 'FROM', 'TO', 'KEYWORD-CHECK', 'TYPE', 'DESCRIPTION']] + props_list
232     for props in props_list:
233         #txt
234         if props[0] != '': txt +=  '%s * ' % props[0]   # comment
235         txt +=  '%s.%s -> %s:   %s  %s\n' % tuple(props[2:5] + props[6:])   # skipping keyword-check
236         # rna_api
237         if props[0] == 'NOTE': indent = '#   '
238         else: indent = '    '
239         rna += indent + '("%s", "%s", "%s", "%s", "%s"),\n' % tuple(props[2:5] + props[6:])    
240         # py
241         blanks = [' '* (x[0]-x[1]) for x in zip(props_length_max,list(map(len,props)))]
242         props = ['"%s"%s'%(x[0],x[1]) for x in zip(props,blanks)]
243         py += indent + '(%s, %s, %s, %s, %s, %s, %s, %s),\n' % tuple(props)
244
245     f_txt.write(txt)
246     f_py.write("rna_api = [\n%s]\n" % py)
247     f_rna.write("rna_api = [\n%s]\n" % rna)
248
249     f_txt.close()
250     f_py.close()
251     f_rna.close()
252
253     print ('\nSaved %s, %s and %s.\n' % (font_bold(f_txt.name), font_bold(f_py.name), font_bold(f_rna.name) ) )
254
255
256 def main():
257
258     global sort_choices, default_sort_choice
259     global kw_prefixes, kw
260
261     sort_choices = ['note','changed','class','from','to','kw']
262     default_sort_choice = sort_choices[0]
263     #kw_prefixes = ['invert','is','lock','show','show_only','use','use_only']
264     #kw = ['hide','select','layer','state']
265     kw_prefixes = ['has','invert','is','lock','layers','show','show_only','states','use','use_only']
266     kw = ['layers','states','value']
267
268     input_filename, sort_priority = check_commandline()
269     props_list,props_length_max = get_props(input_filename)
270     props_list = sort(props_list,sort_priority)
271
272     output_basename = file_basename(input_filename)
273     write_files(output_basename, props_list,props_length_max)
274
275
276 if __name__=='__main__':
277     import sys
278     if not sys.version.startswith("3"):
279         print("Incorrect python version, use python 3!")
280     else:
281         main()
282