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/.
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
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',
62 'DrawImpress': 'Draw / Impress',
67 'basslots': 'Basic IDE, Forms, Dialogs',
69 'sdgslots': 'Draw / Impress',
70 'sdslots': 'Draw / Impress',
76 'sdraw': 'Draw / Impress',
82 def newcommand(unocommand
):
83 cmd
= {'unocommand': unocommand
,
105 if cmd
.startswith('FocusToFindbar'):
106 cmd
= 'vnd.sun.star.findbar:' + cmd
111 def analyze_xcu(all_commands
):
112 for filename
in XCU_FILES
:
114 with
open(filename
) as fh
:
118 if '<node oor:name="Popups">' in line
:
121 elif popups
is True and line
== ' </node>':
124 if '<node oor:name=".uno:' not in line
and '<node oor:name="vnd.' not in line
:
128 tmp
= line
.split('"')
129 command_name
= tmp
[1]
132 while '</node>' not in line
:
136 except StopIteration:
137 print("Warning: couldn't find '</node>' line in %s" % filename
,
140 if '<prop oor:name="Label"' in line
:
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
:
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
:
168 if not line
.startswith('// Slot Nr. '):
172 # // Slot Nr. 0 : 5502
173 # SFX_NEW_SLOT_ARG( basctl_Shell,SID_SAVEASDOC,SfxGroupId::Document,
175 tmp
= line
.split(':')
176 command_id
= tmp
[1].strip()
180 tmp
= line
.split(',')
182 command_group
= tmp
[2].split('::')[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 ''
203 tmp
= line
.split('"')
205 command_name
= get_uno(tmp
[1])
207 print("Warning: expected \" in line '%s' from file %s" % (line
.strip(), filename
),
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
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)
230 params
= params
.strip(' ,').replace(', ', ',') # At least 1 case of ', ' in svx/sdi/svx.sdi line 3592
232 for p
in params
.split(','):
238 if 'String' in elems
[0]:
240 elif 'Bool' in elems
[0]:
242 elif 'Int16' in elems
[0]:
243 split
+= ' (integer)'
244 elif 'Int32' in elems
[0]:
247 split
+= ' (' + elems
[0].replace('Sfx', '').replace('Svx', '').replace('Item', '').lower() + ')'
250 for filename
in SDI_FILES
:
252 comment
, square
, command
, param
= False, False, False, False
253 with
open(filename
) as fh
:
256 line
= line
.replace(' ', ' ').strip() # Anomaly met in svx/sdi/svx.sdi
257 if line
.startswith('//'):
259 elif comment
is False and line
.startswith('/*') and not line
.endswith('*/'):
261 elif comment
is True and line
.endswith('*/'):
263 elif comment
is False and line
.startswith('/*') and line
.endswith('*/'):
265 elif comment
is True:
267 elif square
is False and line
.startswith('['):
271 elif square
is True and line
.endswith(']'):
272 all_commands
[command_name
]['mode'] = mode
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:
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]
293 elif command
is True and (line
== '' or line
== '()'):
295 elif command
is True and (param
is True or line
.startswith('(')) and line
.endswith(')'):
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
300 params
= line
.strip(' (),').replace(', ', ',') # At least 1 case in sw/sdi/swriter.sdi line 7083
301 all_commands
[command_name
]['arguments'] = SplitArguments(params
)
304 elif command
is True and line
.startswith('('): # Arguments always on 1 line, except in some cases (cfr.BasicIDEAppear)
305 params
= line
.strip(' ()').replace(', ', ',')
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']
323 cxcu
= os
.path
.basename(XCU_FILES
[fxcu
]).split('.')[0].replace('Commands', '')
324 fhxx
= command
['hxxfile']
326 chxx
= os
.path
.basename(HXX_FILES
[fhxx
]).split('.')[0]
327 fsdi
= command
['sdifile']
329 csdi
= os
.path
.basename(SDI_FILES
[fsdi
]).split('.')[0]
337 # Exceptions on general rule
338 if cat
== 'Generic' and chxx
== 'basslots':
340 command
['module'] = MODULES
[cat
]
343 def print_output(all_commands
):
345 # Return the longest string among the arguments
346 return max(args
, key
= len)
349 # Build string identifying the sources
350 xcufile
, xculinenumber
, hxxfile
, hxxlinenumber
, sdifile
, sdilinenumber
= 2, 3, 8, 10, 14, 16
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>'
361 # Sort by category and command name
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
372 for cmd
in sorted_by_module
:
373 # Format bottom and header
374 if lastmodule
!= cmd
[module
]:
375 if len(lastmodule
) > 0:
378 lastmodule
= cmd
[module
]
379 print('=== %s ===\n' % lastmodule
)
381 print('{| class="wikitable sortable" width="100%"')
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')
391 print('| ' + cmd
[unocommand
].replace('&', '\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
])
398 print('| ' + cmd
[resourceid
] + ' (' + cmd
[numericid
] + ')')
399 print('| ' + cmd
[mode
])
400 print('| ' + sources(cmd
))
402 # List the source files
403 print('== Source files ==\n')
405 for i
in range(len(XCU_FILES
)):
407 print(f
'({fn}) {REPO}{XCU_FILES[i]}\n'.replace(srcdir
, ''))
409 for i
in range(len(SDI_FILES
)):
411 print(f
'({fn}) {REPO}{SDI_FILES[i]}\n'.replace(srcdir
, ''))
413 for i
in range(len(HXX_FILES
)):
415 print(f
'<span id="hxx{fn}">({fn}) {HXX_FILES[i]}</span>\n'.replace(builddir
, ''))
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__':