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