Make ElementsDeck the default deck for Math first start
[LibreOffice.git] / bin / list-dispatch-commands.py
blobdad2f01ffee26ba0ca4010c186e4f18e590d1e57
1 #!/usr/bin/env python3
3 # This file is part of the LibreOffice project.
5 # This Source Code Form is subject to the terms of the Mozilla Public
6 # License, v. 2.0. If a copy of the MPL was not distributed with this
7 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 """
10 Script to generate https://wiki.documentfoundation.org/Development/DispatchCommands
11 3 types of source files are scanned to identify and describe a list of relevant UNO commands:
12 - .hxx files: containing the symbolic and numeric id's, and the respective modes and groups
13 - .xcu files; containing several english labels as they appear in menus or tooltips
14 - .sdi files: containing a list of potential arguments for the commands, and their types
15 """
17 import os
19 REPO = 'https://opengrok.libreoffice.org/xref/core/'
21 BLACKLIST = ('_SwitchViewShell0', '_SwitchViewShell1', '_SwitchViewShell2', '_SwitchViewShell3', '_SwitchViewShell4')
23 XCU_DIR = 'officecfg/registry/data/org/openoffice/Office/UI/'
24 XCU_FILES = ( XCU_DIR + 'BasicIDECommands.xcu',
25 XCU_DIR + 'CalcCommands.xcu',
26 XCU_DIR + 'ChartCommands.xcu',
27 XCU_DIR + 'DbuCommands.xcu',
28 XCU_DIR + 'DrawImpressCommands.xcu',
29 XCU_DIR + 'GenericCommands.xcu',
30 XCU_DIR + 'MathCommands.xcu',
31 XCU_DIR + 'ReportCommands.xcu',
32 XCU_DIR + 'WriterCommands.xcu')
34 HXX_DIR = './workdir/SdiTarget/'
35 HXX_FILES = ( HXX_DIR + 'basctl/sdi/basslots.hxx',
36 HXX_DIR + 'sc/sdi/scslots.hxx',
37 HXX_DIR + 'sd/sdi/sdgslots.hxx',
38 HXX_DIR + 'sd/sdi/sdslots.hxx',
39 HXX_DIR + 'sfx2/sdi/sfxslots.hxx',
40 HXX_DIR + 'starmath/sdi/smslots.hxx',
41 HXX_DIR + 'svx/sdi/svxslots.hxx',
42 HXX_DIR + 'sw/sdi/swslots.hxx')
44 SDI_FILES = ( 'sc/sdi/scalc.sdi',
45 'sd/sdi/sdraw.sdi',
46 'sfx2/sdi/sfx.sdi',
47 'starmath/sdi/smath.sdi',
48 'svx/sdi/svx.sdi',
49 'sw/sdi/swriter.sdi')
51 # Category is defined by the 1st file where the command has been found. Precedence: 1. xcu, 2. hxx, 3. sdi.
52 MODULES = {'BasicIDE': 'Basic IDE, Forms, Dialogs',
53 'Calc': 'Calc',
54 'Chart': 'Charts',
55 'Dbu': 'Base',
56 'DrawImpress': 'Draw / Impress',
57 'Generic': 'Global',
58 'Math': 'Math',
59 'Report': 'Reports',
60 'Writer': 'Writer',
61 'basslots': 'Basic IDE, Forms, Dialogs',
62 'scslots': 'Calc',
63 'sdgslots': 'Draw / Impress',
64 'sdslots': 'Draw / Impress',
65 'sfxslots': 'Global',
66 'smslots': 'Math',
67 'svxslots': 'Global',
68 'swslots': 'Writer',
69 'scalc': 'Calc',
70 'sdraw': 'Draw / Impress',
71 'sfx': 'Global',
72 'smath': 'Math',
73 'svx': 'Global',
74 'swriter': 'Writer'}
76 def newcommand(unocommand):
77 cmd = {'unocommand': unocommand,
78 'module': '',
79 'xcufile': -1,
80 'xculinenumber': 0,
81 'xcuoccurs': 0,
82 'label': '',
83 'contextlabel': '',
84 'tooltiplabel': '',
85 'hxxfile': -1,
86 'hxxoccurs': 0,
87 'hxxlinenumber': 0,
88 'resourceid': '',
89 'numericid': '',
90 'group': '',
91 'sdifile': -1,
92 'sdioccurs': 0,
93 'sdilinenumber': 0,
94 'mode': '',
95 'arguments': ''}
96 return cmd
99 def analyze_xcu(all_commands):
100 for filename in XCU_FILES:
101 ln = 0
102 with open(filename) as fh:
103 popups = False
104 for line in fh:
105 ln += 1
106 if '<node oor:name="Popups">' in line:
107 popups = True
108 continue
109 elif popups is True and line == ' </node>':
110 popups = False
111 continue
112 if '<node oor:name=".uno:' not in line:
113 continue
115 cmdln = ln
116 tmp = line.split('"')
117 command_name = tmp[1]
118 command_ok = True
120 while '</node>' not in line:
121 try:
122 line = next(fh)
123 ln += 1
124 except StopIteration:
125 print("Warning: couldn't find '</node>' line in %s" % filename,
126 file=sys.stderr)
127 break
128 if '<prop oor:name="Label"' in line:
129 label = 'label'
130 elif '<prop oor:name="ContextLabel"' in line:
131 label = 'contextlabel'
132 elif '<prop oor:name="TooltipLabel"' in line:
133 label = 'tooltiplabel'
134 elif '<value xml:lang="en-US">' in line:
135 labeltext = line.replace('<value xml:lang="en-US">', '').replace('</value>', '').strip()
136 elif '<prop oor:name="TargetURL"' in line:
137 command_ok = False
139 if command_ok is True and popups is False:
140 if command_name not in all_commands:
141 all_commands[command_name] = newcommand(command_name)
143 all_commands[command_name]['xcufile'] = XCU_FILES.index(filename)
144 all_commands[command_name]['xculinenumber'] = cmdln
145 all_commands[command_name][label] = labeltext.replace('~', '')
146 all_commands[command_name]['xcuoccurs'] += 1
149 def analyze_hxx(all_commands):
150 for filename in HXX_FILES:
151 with open(filename) as fh:
152 ln = 0
153 mode = ''
154 for line in fh:
155 ln += 1
156 if not line.startswith('// Slot Nr. '):
157 continue
159 # Parse sth like
160 # // Slot Nr. 0 : 5502
161 # SFX_NEW_SLOT_ARG( basctl_Shell,SID_SAVEASDOC,SfxGroupId::Document,
162 cmdln = ln
163 tmp = line.split(':')
164 command_id = tmp[1].strip()
166 line = next(fh)
167 ln += 1
168 tmp = line.split(',')
169 command_rid = tmp[1]
170 command_group = tmp[2].split('::')[1]
172 next(fh)
173 ln += 1
174 next(fh)
175 ln += 1
176 line = next(fh)
177 ln += 1
178 mode += 'U' if 'AUTOUPDATE' in line else ''
179 mode += 'M' if 'MENUCONFIG' in line else ''
180 mode += 'T' if 'TOOLBOXCONFIG' in line else ''
181 mode += 'A' if 'ACCELCONFIG' in line else ''
183 next(fh)
184 ln += 1
185 next(fh)
186 ln += 1
187 line = next(fh)
188 ln += 1
189 if '"' not in line:
190 line = next(fh)
191 tmp = line.split('"')
192 try:
193 command_name = '.uno:' + tmp[1]
194 except IndexError:
195 print("Warning: expected \" in line '%s' from file %s" % (line.strip(), filename),
196 file=sys.stderr)
197 command_name = '.uno:'
199 if command_name not in all_commands:
200 all_commands[command_name] = newcommand(command_name)
202 all_commands[command_name]['hxxfile'] = HXX_FILES.index(filename)
203 all_commands[command_name]['hxxlinenumber'] = cmdln
204 all_commands[command_name]['numericid'] = command_id
205 all_commands[command_name]['resourceid'] = command_rid
206 all_commands[command_name]['group'] = command_group
207 all_commands[command_name]['mode'] = mode
208 all_commands[command_name]['hxxoccurs'] += 1
209 mode = ''
212 def analyze_sdi(all_commands):
213 def SplitArguments(params):
214 # Split a string like : SfxStringItem Name SID_CHART_NAME,SfxStringItem Range SID_CHART_SOURCE,SfxBoolItem ColHeaders FN_PARAM_1,SfxBoolItem RowHeaders FN_PARAM_2
215 # in : Name (string)\nRange (string)\nRowHeaders (bool)
216 CR = '<br>'
217 split = ''
218 params = params.strip(' ,').replace(', ', ',') # At least 1 case of ', ' in svx/sdi/svx.sdi line 3592
219 if len(params) > 0:
220 for p in params.split(','):
221 if len(split) > 0:
222 split += CR
223 elems = p.split()
224 if len(elems) >= 2:
225 split += elems[1]
226 if 'String' in elems[0]:
227 split += ' (string)'
228 elif 'Bool' in elems[0]:
229 split += ' (bool)'
230 elif 'Int16' in elems[0]:
231 split += ' (integer)'
232 elif 'Int32' in elems[0]:
233 split += ' (long)'
234 else:
235 split += ' (' + elems[0].replace('Sfx', '').replace('Svx', '').replace('Item', '').lower() + ')'
236 return split
238 for filename in SDI_FILES:
239 ln = 0
240 comment, square, command, param = False, False, False, False
241 with open(filename) as fh:
242 for line in fh:
243 ln += 1
244 line = line.replace(' ', ' ').strip() # Anomaly met in svx/sdi/svx.sdi
245 if line.startswith('//'):
246 pass
247 elif comment is False and line.startswith('/*') and not line.endswith('*/'):
248 comment = True
249 elif comment is True and line.endswith('*/'):
250 comment = False
251 elif comment is False and line.startswith('/*') and line.endswith('*/'):
252 pass
253 elif comment is True:
254 pass
255 elif square is False and line.startswith('['):
256 square = True
257 mode = ''
258 command = False
259 elif square is True and line.endswith(']'):
260 all_commands[command_name]['mode'] = mode
261 square = False
262 elif square is True:
263 squaremode = line.strip(',;').split()
264 if len(squaremode) == 3:
265 mode += 'U' if squaremode[0] == 'AutoUpdate' and squaremode[2] == 'TRUE' else ''
266 mode += 'M' if squaremode[0] == 'MenuConfig' and squaremode[2] == 'TRUE' else ''
267 mode += 'T' if squaremode[0] == 'ToolBoxConfig' and squaremode[2] == 'TRUE' else ''
268 mode += 'A' if squaremode[0] == 'AccelConfig' and squaremode[2] == 'TRUE' else ''
269 elif comment is False and square is False and command is False and len(line) == 0:
270 pass
271 elif command is False:
272 command_name = '.uno:' + line.split(' ')[1]
273 if command_name not in all_commands:
274 all_commands[command_name] = newcommand(command_name)
275 all_commands[command_name]['sdifile'] = SDI_FILES.index(filename)
276 all_commands[command_name]['sdilinenumber'] = ln
277 all_commands[command_name]['sdioccurs'] += 1
278 if len(all_commands[command_name]['resourceid']) == 0:
279 all_commands[command_name]['resourceid'] = line.split(' ')[2]
280 command = True
281 elif command is True and (line == '' or line == '()'):
282 command = False
283 elif command is True and (param is True or line.startswith('(')) and line.endswith(')'):
284 if param:
285 params += line.strip(' (),').replace(', ', ',') # At least 1 case of ", " in svx/sdi/svx.sdi line 8767
286 # At least 1 case of "( " in sw/sdi/swriter.sdi line 5477
287 else:
288 params = line.strip(' (),').replace(', ', ',') # At least 1 case in sw/sdi/swriter.sdi line 7083
289 all_commands[command_name]['arguments'] = SplitArguments(params)
290 command = False
291 param = False
292 elif command is True and line.startswith('('): # Arguments always on 1 line, except in some cases (cfr.BasicIDEAppear)
293 params = line.strip(' ()').replace(', ', ',')
294 param = True
295 elif param is True:
296 params += line
299 def categorize(all_commands):
300 # Clean black listed commands
301 for command in BLACKLIST:
302 cmd = '.uno:' + command
303 if cmd in all_commands:
304 del all_commands[cmd]
305 # Set category based on the file name where the command was found first
306 for cmd in all_commands:
307 command = all_commands[cmd]
308 cxcu, chxx, csdi = '', '', ''
309 fxcu = command['xcufile']
310 if fxcu > -1:
311 cxcu = os.path.basename(XCU_FILES[fxcu]).split('.')[0].replace('Commands', '')
312 fhxx = command['hxxfile']
313 if fhxx > -1:
314 chxx = os.path.basename(HXX_FILES[fhxx]).split('.')[0]
315 fsdi = command['sdifile']
316 if fsdi > -1:
317 csdi = os.path.basename(SDI_FILES[fsdi]).split('.')[0]
318 # General rule:
319 if len(cxcu) > 0:
320 cat = cxcu
321 elif len(chxx) > 0:
322 cat = chxx
323 else:
324 cat = csdi
325 # Exceptions on general rule
326 if cat == 'Generic' and chxx == 'basslots':
327 cat = chxx
328 command['module'] = MODULES[cat]
331 def print_output(all_commands):
332 def longest(*args):
333 # Return the longest string among the arguments
334 return max(args, key = len)
336 def sources(cmd):
337 # Build string identifying the sources
338 xcufile, xculinenumber, hxxfile, hxxlinenumber, sdifile, sdilinenumber = 2, 3, 8, 10, 14, 16
339 src = ''
340 if cmd[xcufile] >= 0:
341 src += '[' + REPO + XCU_FILES[cmd[xcufile]] + '#' + str(cmd[xculinenumber]) + ' XCU]'
342 if cmd[sdifile] >= 0:
343 src += ' [' + REPO + SDI_FILES[cmd[sdifile]] + '#' + str(cmd[sdilinenumber]) + ' SDI]'
344 if cmd[hxxfile] >= 0:
345 file = str(cmd[hxxfile] + 1 + len(XCU_FILES) + len(SDI_FILES))
346 src += ' <span title="File (' + file + ') line ' + str(cmd[hxxlinenumber]) + '">[[#hxx' + file + '|HXX]]</span>'
347 return src.strip()
349 # Sort by category and command name
350 commands_list = []
351 for cmd in all_commands:
352 cmdlist = tuple(all_commands[cmd].values())
353 commands_list.append(cmdlist)
354 sorted_by_command = sorted(commands_list, key = lambda cmd: cmd[0])
355 sorted_by_module = sorted(sorted_by_command, key = lambda cmd: cmd[1])
357 # Produce tabular output
358 unocommand, module, label, contextlabel, tooltiplabel, arguments, resourceid, numericid, group, mode = 0, 1, 5, 6, 7, 18, 11, 12, 13, 17
359 lastmodule = ''
360 for cmd in sorted_by_module:
361 # Format bottom and header
362 if lastmodule != cmd[module]:
363 if len(lastmodule) > 0:
364 print('\n|-\n|}\n')
365 print('</small>')
366 lastmodule = cmd[module]
367 print('=== %s ===\n' % lastmodule)
368 print('<small>')
369 print('{| class="wikitable sortable" width="100%"')
370 print('|-')
371 print('! scope="col" | Dispatch command')
372 print('! scope="col" | Description')
373 print('! scope="col" | Group')
374 print('! scope="col" | Arguments')
375 print('! scope="col" | Internal<br>name (value)')
376 print('! scope="col" | Mode')
377 print('! scope="col" | Source<br>files')
378 print('|-\n')
379 print('| ' + cmd[unocommand].replace('&amp;', '\n&'))
380 print('| ' + longest(cmd[label], cmd[contextlabel], cmd[tooltiplabel]))
381 print('| ' + cmd[group])
382 print('| ' + cmd[arguments].replace('\\n', '\n'))
383 if len(cmd[numericid]) == 0:
384 print('| ' + cmd[resourceid])
385 else:
386 print('| ' + cmd[resourceid] + ' (' + cmd[numericid] + ')')
387 print('| ' + cmd[mode])
388 print('| ' + sources(cmd))
389 print('|-\n|}\n')
390 # List the source files
391 print('== Source files ==\n')
392 fn = 0
393 for i in range(len(XCU_FILES)):
394 fn += 1
395 print(f'({fn}) {REPO}{XCU_FILES[i]}\n')
396 print('\n')
397 for i in range(len(SDI_FILES)):
398 fn += 1
399 print(f'({fn}) {REPO}{SDI_FILES[i]}\n')
400 print('\n')
401 for i in range(len(HXX_FILES)):
402 fn += 1
403 print(f'<span id="hxx{fn}">({fn}) {HXX_FILES[i][2:]}</span>\n')
404 print('</small>')
407 def main():
408 all_commands = {}
410 analyze_xcu(all_commands)
412 analyze_hxx(all_commands)
414 analyze_sdi(all_commands)
416 categorize(all_commands)
418 print_output(all_commands)
420 if __name__ == '__main__':
421 main()