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
20 # It is assumed that the script is called from $BUILDDIR;
21 # and __file__ refers to the script location in $SRCDIR.
22 # This allows to use it in separate builddir configuration.
23 srcdir
= os
.path
.dirname(os
.path
.realpath(__file__
)) + '/..' # go up from /bin
24 builddir
= os
.getcwd()
26 REPO
= 'https://opengrok.libreoffice.org/xref/core'
28 BLACKLIST
= ('_SwitchViewShell0', '_SwitchViewShell1', '_SwitchViewShell2', '_SwitchViewShell3', '_SwitchViewShell4')
30 XCU_DIR
= srcdir
+ '/officecfg/registry/data/org/openoffice/Office/UI/'
31 XCU_FILES
= ( XCU_DIR
+ 'BasicIDECommands.xcu',
32 XCU_DIR
+ 'CalcCommands.xcu',
33 XCU_DIR
+ 'ChartCommands.xcu',
34 XCU_DIR
+ 'DbuCommands.xcu',
35 XCU_DIR
+ 'DrawImpressCommands.xcu',
36 XCU_DIR
+ 'GenericCommands.xcu',
37 XCU_DIR
+ 'MathCommands.xcu',
38 XCU_DIR
+ 'ReportCommands.xcu',
39 XCU_DIR
+ 'WriterCommands.xcu')
41 HXX_DIR
= builddir
+ '/workdir/SdiTarget/'
42 HXX_FILES
= ( HXX_DIR
+ 'basctl/sdi/basslots.hxx',
43 HXX_DIR
+ 'sc/sdi/scslots.hxx',
44 HXX_DIR
+ 'sd/sdi/sdgslots.hxx',
45 HXX_DIR
+ 'sd/sdi/sdslots.hxx',
46 HXX_DIR
+ 'sfx2/sdi/sfxslots.hxx',
47 HXX_DIR
+ 'starmath/sdi/smslots.hxx',
48 HXX_DIR
+ 'svx/sdi/svxslots.hxx',
49 HXX_DIR
+ 'sw/sdi/swslots.hxx')
51 SDI_FILES
= ( srcdir
+ '/sc/sdi/scalc.sdi',
52 srcdir
+ '/sd/sdi/sdraw.sdi',
53 srcdir
+ '/sfx2/sdi/sfx.sdi',
54 srcdir
+ '/starmath/sdi/smath.sdi',
55 srcdir
+ '/svx/sdi/svx.sdi',
56 srcdir
+ '/sw/sdi/swriter.sdi')
58 # Category is defined by the 1st file where the command has been found. Precedence: 1. xcu, 2. hxx, 3. sdi.
59 MODULES
= {'BasicIDE': 'Basic IDE, Forms, Dialogs',
63 'DrawImpress': 'Draw / Impress',
68 'basslots': 'Basic IDE, Forms, Dialogs',
70 'sdgslots': 'Draw / Impress',
71 'sdslots': 'Draw / Impress',
77 'sdraw': 'Draw / Impress',
83 def newcommand(unocommand
):
84 cmd
= {'unocommand': unocommand
,
106 if cmd
.startswith('FocusToFindbar'):
107 cmd
= 'vnd.sun.star.findbar:' + cmd
112 def analyze_xcu(all_commands
):
113 for filename
in XCU_FILES
:
115 with
open(filename
) as fh
:
119 if '<node oor:name="Popups">' in line
:
122 elif popups
is True and line
== ' </node>':
125 if '<node oor:name=".uno:' not in line
and '<node oor:name="vnd.' not in line
:
129 tmp
= line
.split('"')
130 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()
150 if command_name
not in all_commands
:
151 all_commands
[command_name
] = newcommand(command_name
)
153 all_commands
[command_name
]['xcufile'] = XCU_FILES
.index(filename
)
154 all_commands
[command_name
]['xculinenumber'] = cmdln
155 all_commands
[command_name
][label
] = labeltext
.replace('~', '')
156 all_commands
[command_name
]['xcuoccurs'] += 1
159 def analyze_hxx(all_commands
):
160 for filename
in HXX_FILES
:
161 with
open(filename
) as fh
:
166 if not line
.startswith('// Slot Nr. '):
170 # // Slot Nr. 0 : 5502
171 # SFX_NEW_SLOT_ARG( basctl_Shell,SID_SAVEASDOC,SfxGroupId::Document,
173 tmp
= line
.split(':')
174 command_id
= tmp
[1].strip()
178 tmp
= line
.split(',')
180 command_group
= tmp
[2].split('::')[1]
188 mode
+= 'U' if 'AUTOUPDATE' in line
else ''
189 mode
+= 'M' if 'MENUCONFIG' in line
else ''
190 mode
+= 'T' if 'TOOLBOXCONFIG' in line
else ''
191 mode
+= 'A' if 'ACCELCONFIG' in line
else ''
201 tmp
= line
.split('"')
203 command_name
= get_uno(tmp
[1])
205 print("Warning: expected \" in line '%s' from file %s" % (line
.strip(), filename
),
207 command_name
= '.uno:'
209 if command_name
not in all_commands
:
210 all_commands
[command_name
] = newcommand(command_name
)
212 all_commands
[command_name
]['hxxfile'] = HXX_FILES
.index(filename
)
213 all_commands
[command_name
]['hxxlinenumber'] = cmdln
214 all_commands
[command_name
]['numericid'] = command_id
215 all_commands
[command_name
]['resourceid'] = command_rid
216 all_commands
[command_name
]['group'] = command_group
217 all_commands
[command_name
]['mode'] = mode
218 all_commands
[command_name
]['hxxoccurs'] += 1
222 def analyze_sdi(all_commands
):
223 def SplitArguments(params
):
224 # Split a string like : SfxStringItem Name SID_CHART_NAME,SfxStringItem Range SID_CHART_SOURCE,SfxBoolItem ColHeaders FN_PARAM_1,SfxBoolItem RowHeaders FN_PARAM_2
225 # in : Name (string)\nRange (string)\nRowHeaders (bool)
228 params
= params
.strip(' ,').replace(', ', ',') # At least 1 case of ', ' in svx/sdi/svx.sdi line 3592
230 for p
in params
.split(','):
236 if 'String' in elems
[0]:
238 elif 'Bool' in elems
[0]:
240 elif 'Int16' in elems
[0]:
241 split
+= ' (integer)'
242 elif 'Int32' in elems
[0]:
245 split
+= ' (' + elems
[0].replace('Sfx', '').replace('Svx', '').replace('Item', '').lower() + ')'
248 for filename
in SDI_FILES
:
250 comment
, square
, command
, param
= False, False, False, False
251 with
open(filename
) as fh
:
254 line
= line
.replace(' ', ' ').strip() # Anomaly met in svx/sdi/svx.sdi
255 if line
.startswith('//'):
257 elif comment
is False and line
.startswith('/*') and not line
.endswith('*/'):
259 elif comment
is True and line
.endswith('*/'):
261 elif comment
is False and line
.startswith('/*') and line
.endswith('*/'):
263 elif comment
is True:
265 elif square
is False and line
.startswith('['):
269 elif square
is True and line
.endswith(']'):
270 all_commands
[command_name
]['mode'] = mode
273 squaremode
= line
.strip(',;').split()
274 if len(squaremode
) == 3:
275 mode
+= 'U' if squaremode
[0] == 'AutoUpdate' and squaremode
[2] == 'TRUE' else ''
276 mode
+= 'M' if squaremode
[0] == 'MenuConfig' and squaremode
[2] == 'TRUE' else ''
277 mode
+= 'T' if squaremode
[0] == 'ToolBoxConfig' and squaremode
[2] == 'TRUE' else ''
278 mode
+= 'A' if squaremode
[0] == 'AccelConfig' and squaremode
[2] == 'TRUE' else ''
279 elif comment
is False and square
is False and command
is False and len(line
) == 0:
281 elif command
is False:
282 command_name
= get_uno(line
.split(' ')[1])
283 if command_name
not in all_commands
:
284 all_commands
[command_name
] = newcommand(command_name
)
285 all_commands
[command_name
]['sdifile'] = SDI_FILES
.index(filename
)
286 all_commands
[command_name
]['sdilinenumber'] = ln
287 all_commands
[command_name
]['sdioccurs'] += 1
288 if len(all_commands
[command_name
]['resourceid']) == 0:
289 all_commands
[command_name
]['resourceid'] = line
.split(' ')[2]
291 elif command
is True and (line
== '' or line
== '()'):
293 elif command
is True and (param
is True or line
.startswith('(')) and line
.endswith(')'):
295 params
+= line
.strip(' (),').replace(', ', ',') # At least 1 case of ", " in svx/sdi/svx.sdi line 8767
296 # At least 1 case of "( " in sw/sdi/swriter.sdi line 5477
298 params
= line
.strip(' (),').replace(', ', ',') # At least 1 case in sw/sdi/swriter.sdi line 7083
299 all_commands
[command_name
]['arguments'] = SplitArguments(params
)
302 elif command
is True and line
.startswith('('): # Arguments always on 1 line, except in some cases (cfr.BasicIDEAppear)
303 params
= line
.strip(' ()').replace(', ', ',')
309 def categorize(all_commands
):
310 # Clean black listed commands
311 for command
in BLACKLIST
:
312 cmd
= get_uno(command
)
313 if cmd
in all_commands
:
314 del all_commands
[cmd
]
315 # Set category based on the file name where the command was found first
316 for cmd
in all_commands
:
317 command
= all_commands
[cmd
]
318 cxcu
, chxx
, csdi
= '', '', ''
319 fxcu
= command
['xcufile']
321 cxcu
= os
.path
.basename(XCU_FILES
[fxcu
]).split('.')[0].replace('Commands', '')
322 fhxx
= command
['hxxfile']
324 chxx
= os
.path
.basename(HXX_FILES
[fhxx
]).split('.')[0]
325 fsdi
= command
['sdifile']
327 csdi
= os
.path
.basename(SDI_FILES
[fsdi
]).split('.')[0]
335 # Exceptions on general rule
336 if cat
== 'Generic' and chxx
== 'basslots':
338 command
['module'] = MODULES
[cat
]
341 def print_output(all_commands
):
343 # Return the longest string among the arguments
344 return max(args
, key
= len)
347 # Build string identifying the sources
348 xcufile
, xculinenumber
, hxxfile
, hxxlinenumber
, sdifile
, sdilinenumber
= 2, 3, 8, 10, 14, 16
350 if cmd
[xcufile
] >= 0:
351 src
+= '[' + REPO
+ XCU_FILES
[cmd
[xcufile
]].replace(srcdir
, '') + '#' + str(cmd
[xculinenumber
]) + ' XCU]'
352 if cmd
[sdifile
] >= 0:
353 src
+= ' [' + REPO
+ SDI_FILES
[cmd
[sdifile
]].replace(srcdir
, '') + '#' + str(cmd
[sdilinenumber
]) + ' SDI]'
354 if cmd
[hxxfile
] >= 0:
355 file = str(cmd
[hxxfile
] + 1 + len(XCU_FILES
) + len(SDI_FILES
))
356 src
+= ' <span title="File (' + file + ') line ' + str(cmd
[hxxlinenumber
]) + '">[[#hxx' + file + '|HXX]]</span>'
359 # Sort by category and command name
361 for cmd
in all_commands
:
362 cmdlist
= tuple(all_commands
[cmd
].values())
363 commands_list
.append(cmdlist
)
364 sorted_by_command
= sorted(commands_list
, key
= lambda cmd
: cmd
[0])
365 sorted_by_module
= sorted(sorted_by_command
, key
= lambda cmd
: cmd
[1])
367 # Produce tabular output
368 unocommand
, module
, label
, contextlabel
, tooltiplabel
, arguments
, resourceid
, numericid
, group
, mode
= 0, 1, 5, 6, 7, 18, 11, 12, 13, 17
370 for cmd
in sorted_by_module
:
371 # Format bottom and header
372 if lastmodule
!= cmd
[module
]:
373 if len(lastmodule
) > 0:
376 lastmodule
= cmd
[module
]
377 print('=== %s ===\n' % lastmodule
)
379 print('{| class="wikitable sortable" width="100%"')
381 print('! scope="col" | Dispatch command')
382 print('! scope="col" | Description')
383 print('! scope="col" | Group')
384 print('! scope="col" | Arguments')
385 print('! scope="col" | Internal<br>name (value)')
386 print('! scope="col" | Mode')
387 print('! scope="col" | Source<br>files')
389 print('| ' + cmd
[unocommand
].replace('&', '\n&'))
390 print('| ' + longest(cmd
[label
], cmd
[contextlabel
], cmd
[tooltiplabel
]))
391 print('| ' + cmd
[group
])
392 print('| ' + cmd
[arguments
].replace('\\n', '\n'))
393 if len(cmd
[numericid
]) == 0:
394 print('| ' + cmd
[resourceid
])
396 print('| ' + cmd
[resourceid
] + ' (' + cmd
[numericid
] + ')')
397 print('| ' + cmd
[mode
])
398 print('| ' + sources(cmd
))
400 # List the source files
401 print('== Source files ==\n')
403 for i
in range(len(XCU_FILES
)):
405 print(f
'({fn}) {REPO}{XCU_FILES[i]}\n'.replace(srcdir
, ''))
407 for i
in range(len(SDI_FILES
)):
409 print(f
'({fn}) {REPO}{SDI_FILES[i]}\n'.replace(srcdir
, ''))
411 for i
in range(len(HXX_FILES
)):
413 print(f
'<span id="hxx{fn}">({fn}) {HXX_FILES[i]}</span>\n'.replace(builddir
, ''))
420 analyze_xcu(all_commands
)
422 analyze_hxx(all_commands
)
424 analyze_sdi(all_commands
)
426 categorize(all_commands
)
428 print_output(all_commands
)
430 if __name__
== '__main__':