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]
131 while '</node>' not in line
:
135 except StopIteration:
136 print("Warning: couldn't find '</node>' line in %s" % filename
,
139 if '<prop oor:name="Label"' in line
:
141 elif '<prop oor:name="ContextLabel"' in line
:
142 label
= 'contextlabel'
143 elif '<prop oor:name="TooltipLabel"' in line
:
144 label
= 'tooltiplabel'
145 elif '<value xml:lang="en-US">' in line
:
146 labeltext
= line
.replace('<value xml:lang="en-US">', '').replace('</value>', '').strip()
149 if command_name
not in all_commands
:
150 all_commands
[command_name
] = newcommand(command_name
)
152 all_commands
[command_name
]['xcufile'] = XCU_FILES
.index(filename
)
153 all_commands
[command_name
]['xculinenumber'] = cmdln
154 all_commands
[command_name
][label
] = labeltext
.replace('~', '')
155 all_commands
[command_name
]['xcuoccurs'] += 1
158 def analyze_hxx(all_commands
):
159 for filename
in HXX_FILES
:
160 with
open(filename
) as fh
:
165 if not line
.startswith('// Slot Nr. '):
169 # // Slot Nr. 0 : 5502
170 # SFX_NEW_SLOT_ARG( basctl_Shell,SID_SAVEASDOC,SfxGroupId::Document,
172 tmp
= line
.split(':')
173 command_id
= tmp
[1].strip()
177 tmp
= line
.split(',')
179 command_group
= tmp
[2].split('::')[1]
187 mode
+= 'U' if 'AUTOUPDATE' in line
else ''
188 mode
+= 'M' if 'MENUCONFIG' in line
else ''
189 mode
+= 'T' if 'TOOLBOXCONFIG' in line
else ''
190 mode
+= 'A' if 'ACCELCONFIG' in line
else ''
200 tmp
= line
.split('"')
202 command_name
= get_uno(tmp
[1])
204 print("Warning: expected \" in line '%s' from file %s" % (line
.strip(), filename
),
206 command_name
= '.uno:'
208 if command_name
not in all_commands
:
209 all_commands
[command_name
] = newcommand(command_name
)
211 all_commands
[command_name
]['hxxfile'] = HXX_FILES
.index(filename
)
212 all_commands
[command_name
]['hxxlinenumber'] = cmdln
213 all_commands
[command_name
]['numericid'] = command_id
214 all_commands
[command_name
]['resourceid'] = command_rid
215 all_commands
[command_name
]['group'] = command_group
216 all_commands
[command_name
]['mode'] = mode
217 all_commands
[command_name
]['hxxoccurs'] += 1
221 def analyze_sdi(all_commands
):
222 def SplitArguments(params
):
223 # Split a string like : SfxStringItem Name SID_CHART_NAME,SfxStringItem Range SID_CHART_SOURCE,SfxBoolItem ColHeaders FN_PARAM_1,SfxBoolItem RowHeaders FN_PARAM_2
224 # in : Name (string)\nRange (string)\nRowHeaders (bool)
227 params
= params
.strip(' ,').replace(', ', ',') # At least 1 case of ', ' in svx/sdi/svx.sdi line 3592
229 for p
in params
.split(','):
235 if 'String' in elems
[0]:
237 elif 'Bool' in elems
[0]:
239 elif 'Int16' in elems
[0]:
240 split
+= ' (integer)'
241 elif 'Int32' in elems
[0]:
244 split
+= ' (' + elems
[0].replace('Sfx', '').replace('Svx', '').replace('Item', '').lower() + ')'
247 for filename
in SDI_FILES
:
249 comment
, square
, command
, param
= False, False, False, False
250 with
open(filename
) as fh
:
253 line
= line
.replace(' ', ' ').strip() # Anomaly met in svx/sdi/svx.sdi
254 if line
.startswith('//'):
256 elif comment
is False and line
.startswith('/*') and not line
.endswith('*/'):
258 elif comment
is True and line
.endswith('*/'):
260 elif comment
is False and line
.startswith('/*') and line
.endswith('*/'):
262 elif comment
is True:
264 elif square
is False and line
.startswith('['):
268 elif square
is True and line
.endswith(']'):
269 all_commands
[command_name
]['mode'] = mode
272 squaremode
= line
.strip(',;').split()
273 if len(squaremode
) == 3:
274 mode
+= 'U' if squaremode
[0] == 'AutoUpdate' and squaremode
[2] == 'TRUE' else ''
275 mode
+= 'M' if squaremode
[0] == 'MenuConfig' and squaremode
[2] == 'TRUE' else ''
276 mode
+= 'T' if squaremode
[0] == 'ToolBoxConfig' and squaremode
[2] == 'TRUE' else ''
277 mode
+= 'A' if squaremode
[0] == 'AccelConfig' and squaremode
[2] == 'TRUE' else ''
278 elif comment
is False and square
is False and command
is False and len(line
) == 0:
280 elif command
is False:
281 command_name
= get_uno(line
.split(' ')[1])
282 if command_name
not in all_commands
:
283 all_commands
[command_name
] = newcommand(command_name
)
284 all_commands
[command_name
]['sdifile'] = SDI_FILES
.index(filename
)
285 all_commands
[command_name
]['sdilinenumber'] = ln
286 all_commands
[command_name
]['sdioccurs'] += 1
287 if len(all_commands
[command_name
]['resourceid']) == 0:
288 all_commands
[command_name
]['resourceid'] = line
.split(' ')[2]
290 elif command
is True and (line
== '' or line
== '()'):
292 elif command
is True and (param
is True or line
.startswith('(')) and line
.endswith(')'):
294 params
+= line
.strip(' (),').replace(', ', ',') # At least 1 case of ", " in svx/sdi/svx.sdi line 8767
295 # At least 1 case of "( " in sw/sdi/swriter.sdi line 5477
297 params
= line
.strip(' (),').replace(', ', ',') # At least 1 case in sw/sdi/swriter.sdi line 7083
298 all_commands
[command_name
]['arguments'] = SplitArguments(params
)
301 elif command
is True and line
.startswith('('): # Arguments always on 1 line, except in some cases (cfr.BasicIDEAppear)
302 params
= line
.strip(' ()').replace(', ', ',')
308 def categorize(all_commands
):
309 # Clean black listed commands
310 for command
in BLACKLIST
:
311 cmd
= get_uno(command
)
312 if cmd
in all_commands
:
313 del all_commands
[cmd
]
314 # Set category based on the file name where the command was found first
315 for cmd
in all_commands
:
316 command
= all_commands
[cmd
]
317 cxcu
, chxx
, csdi
= '', '', ''
318 fxcu
= command
['xcufile']
320 cxcu
= os
.path
.basename(XCU_FILES
[fxcu
]).split('.')[0].replace('Commands', '')
321 fhxx
= command
['hxxfile']
323 chxx
= os
.path
.basename(HXX_FILES
[fhxx
]).split('.')[0]
324 fsdi
= command
['sdifile']
326 csdi
= os
.path
.basename(SDI_FILES
[fsdi
]).split('.')[0]
334 # Exceptions on general rule
335 if cat
== 'Generic' and chxx
== 'basslots':
337 command
['module'] = MODULES
[cat
]
340 def print_output(all_commands
):
342 # Return the longest string among the arguments
343 return max(args
, key
= len)
346 # Build string identifying the sources
347 xcufile
, xculinenumber
, hxxfile
, hxxlinenumber
, sdifile
, sdilinenumber
= 2, 3, 8, 10, 14, 16
349 if cmd
[xcufile
] >= 0:
350 src
+= '[' + REPO
+ XCU_FILES
[cmd
[xcufile
]].replace(srcdir
, '') + '#' + str(cmd
[xculinenumber
]) + ' XCU]'
351 if cmd
[sdifile
] >= 0:
352 src
+= ' [' + REPO
+ SDI_FILES
[cmd
[sdifile
]].replace(srcdir
, '') + '#' + str(cmd
[sdilinenumber
]) + ' SDI]'
353 if cmd
[hxxfile
] >= 0:
354 file = str(cmd
[hxxfile
] + 1 + len(XCU_FILES
) + len(SDI_FILES
))
355 src
+= ' <span title="File (' + file + ') line ' + str(cmd
[hxxlinenumber
]) + '">[[#hxx' + file + '|HXX]]</span>'
358 # Sort by category and command name
360 for cmd
in all_commands
:
361 cmdlist
= tuple(all_commands
[cmd
].values())
362 commands_list
.append(cmdlist
)
363 sorted_by_command
= sorted(commands_list
, key
= lambda cmd
: cmd
[0])
364 sorted_by_module
= sorted(sorted_by_command
, key
= lambda cmd
: cmd
[1])
366 # Produce tabular output
367 unocommand
, module
, label
, contextlabel
, tooltiplabel
, arguments
, resourceid
, numericid
, group
, mode
= 0, 1, 5, 6, 7, 18, 11, 12, 13, 17
369 for cmd
in sorted_by_module
:
370 # Format bottom and header
371 if lastmodule
!= cmd
[module
]:
372 if len(lastmodule
) > 0:
375 lastmodule
= cmd
[module
]
376 print('=== %s ===\n' % lastmodule
)
378 print('{| class="wikitable sortable" width="100%"')
380 print('! scope="col" | Dispatch command')
381 print('! scope="col" | Description')
382 print('! scope="col" | Group')
383 print('! scope="col" | Arguments')
384 print('! scope="col" | Internal<br>name (value)')
385 print('! scope="col" | Mode')
386 print('! scope="col" | Source<br>files')
388 print('| ' + cmd
[unocommand
].replace('&', '\n&'))
389 print('| ' + longest(cmd
[label
], cmd
[contextlabel
], cmd
[tooltiplabel
]))
390 print('| ' + cmd
[group
])
391 print('| ' + cmd
[arguments
].replace('\\n', '\n'))
392 if len(cmd
[numericid
]) == 0:
393 print('| ' + cmd
[resourceid
])
395 print('| ' + cmd
[resourceid
] + ' (' + cmd
[numericid
] + ')')
396 print('| ' + cmd
[mode
])
397 print('| ' + sources(cmd
))
399 # List the source files
400 print('== Source files ==\n')
402 for i
in range(len(XCU_FILES
)):
404 print(f
'({fn}) {REPO}{XCU_FILES[i]}\n'.replace(srcdir
, ''))
406 for i
in range(len(SDI_FILES
)):
408 print(f
'({fn}) {REPO}{SDI_FILES[i]}\n'.replace(srcdir
, ''))
410 for i
in range(len(HXX_FILES
)):
412 print(f
'<span id="hxx{fn}">({fn}) {HXX_FILES[i]}</span>\n'.replace(builddir
, ''))
419 analyze_xcu(all_commands
)
421 analyze_hxx(all_commands
)
423 analyze_sdi(all_commands
)
425 categorize(all_commands
)
427 print_output(all_commands
)
429 if __name__
== '__main__':