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 def analyze_xcu(all_commands
):
106 for filename
in XCU_FILES
:
108 with
open(filename
) as fh
:
112 if '<node oor:name="Popups">' in line
:
115 elif popups
is True and line
== ' </node>':
118 if '<node oor:name=".uno:' not in line
:
122 tmp
= line
.split('"')
123 command_name
= tmp
[1]
126 while '</node>' not in line
:
130 except StopIteration:
131 print("Warning: couldn't find '</node>' line in %s" % filename
,
134 if '<prop oor:name="Label"' in line
:
136 elif '<prop oor:name="ContextLabel"' in line
:
137 label
= 'contextlabel'
138 elif '<prop oor:name="TooltipLabel"' in line
:
139 label
= 'tooltiplabel'
140 elif '<value xml:lang="en-US">' in line
:
141 labeltext
= line
.replace('<value xml:lang="en-US">', '').replace('</value>', '').strip()
142 elif '<prop oor:name="TargetURL"' in line
:
145 if command_ok
is True and popups
is False:
146 if command_name
not in all_commands
:
147 all_commands
[command_name
] = newcommand(command_name
)
149 all_commands
[command_name
]['xcufile'] = XCU_FILES
.index(filename
)
150 all_commands
[command_name
]['xculinenumber'] = cmdln
151 all_commands
[command_name
][label
] = labeltext
.replace('~', '')
152 all_commands
[command_name
]['xcuoccurs'] += 1
155 def analyze_hxx(all_commands
):
156 for filename
in HXX_FILES
:
157 with
open(filename
) as fh
:
162 if not line
.startswith('// Slot Nr. '):
166 # // Slot Nr. 0 : 5502
167 # SFX_NEW_SLOT_ARG( basctl_Shell,SID_SAVEASDOC,SfxGroupId::Document,
169 tmp
= line
.split(':')
170 command_id
= tmp
[1].strip()
174 tmp
= line
.split(',')
176 command_group
= tmp
[2].split('::')[1]
184 mode
+= 'U' if 'AUTOUPDATE' in line
else ''
185 mode
+= 'M' if 'MENUCONFIG' in line
else ''
186 mode
+= 'T' if 'TOOLBOXCONFIG' in line
else ''
187 mode
+= 'A' if 'ACCELCONFIG' in line
else ''
197 tmp
= line
.split('"')
199 command_name
= '.uno:' + tmp
[1]
201 print("Warning: expected \" in line '%s' from file %s" % (line
.strip(), filename
),
203 command_name
= '.uno:'
205 if command_name
not in all_commands
:
206 all_commands
[command_name
] = newcommand(command_name
)
208 all_commands
[command_name
]['hxxfile'] = HXX_FILES
.index(filename
)
209 all_commands
[command_name
]['hxxlinenumber'] = cmdln
210 all_commands
[command_name
]['numericid'] = command_id
211 all_commands
[command_name
]['resourceid'] = command_rid
212 all_commands
[command_name
]['group'] = command_group
213 all_commands
[command_name
]['mode'] = mode
214 all_commands
[command_name
]['hxxoccurs'] += 1
218 def analyze_sdi(all_commands
):
219 def SplitArguments(params
):
220 # Split a string like : SfxStringItem Name SID_CHART_NAME,SfxStringItem Range SID_CHART_SOURCE,SfxBoolItem ColHeaders FN_PARAM_1,SfxBoolItem RowHeaders FN_PARAM_2
221 # in : Name (string)\nRange (string)\nRowHeaders (bool)
224 params
= params
.strip(' ,').replace(', ', ',') # At least 1 case of ', ' in svx/sdi/svx.sdi line 3592
226 for p
in params
.split(','):
232 if 'String' in elems
[0]:
234 elif 'Bool' in elems
[0]:
236 elif 'Int16' in elems
[0]:
237 split
+= ' (integer)'
238 elif 'Int32' in elems
[0]:
241 split
+= ' (' + elems
[0].replace('Sfx', '').replace('Svx', '').replace('Item', '').lower() + ')'
244 for filename
in SDI_FILES
:
246 comment
, square
, command
, param
= False, False, False, False
247 with
open(filename
) as fh
:
250 line
= line
.replace(' ', ' ').strip() # Anomaly met in svx/sdi/svx.sdi
251 if line
.startswith('//'):
253 elif comment
is False and line
.startswith('/*') and not line
.endswith('*/'):
255 elif comment
is True and line
.endswith('*/'):
257 elif comment
is False and line
.startswith('/*') and line
.endswith('*/'):
259 elif comment
is True:
261 elif square
is False and line
.startswith('['):
265 elif square
is True and line
.endswith(']'):
266 all_commands
[command_name
]['mode'] = mode
269 squaremode
= line
.strip(',;').split()
270 if len(squaremode
) == 3:
271 mode
+= 'U' if squaremode
[0] == 'AutoUpdate' and squaremode
[2] == 'TRUE' else ''
272 mode
+= 'M' if squaremode
[0] == 'MenuConfig' and squaremode
[2] == 'TRUE' else ''
273 mode
+= 'T' if squaremode
[0] == 'ToolBoxConfig' and squaremode
[2] == 'TRUE' else ''
274 mode
+= 'A' if squaremode
[0] == 'AccelConfig' and squaremode
[2] == 'TRUE' else ''
275 elif comment
is False and square
is False and command
is False and len(line
) == 0:
277 elif command
is False:
278 command_name
= '.uno:' + line
.split(' ')[1]
279 if command_name
not in all_commands
:
280 all_commands
[command_name
] = newcommand(command_name
)
281 all_commands
[command_name
]['sdifile'] = SDI_FILES
.index(filename
)
282 all_commands
[command_name
]['sdilinenumber'] = ln
283 all_commands
[command_name
]['sdioccurs'] += 1
284 if len(all_commands
[command_name
]['resourceid']) == 0:
285 all_commands
[command_name
]['resourceid'] = line
.split(' ')[2]
287 elif command
is True and (line
== '' or line
== '()'):
289 elif command
is True and (param
is True or line
.startswith('(')) and line
.endswith(')'):
291 params
+= line
.strip(' (),').replace(', ', ',') # At least 1 case of ", " in svx/sdi/svx.sdi line 8767
292 # At least 1 case of "( " in sw/sdi/swriter.sdi line 5477
294 params
= line
.strip(' (),').replace(', ', ',') # At least 1 case in sw/sdi/swriter.sdi line 7083
295 all_commands
[command_name
]['arguments'] = SplitArguments(params
)
298 elif command
is True and line
.startswith('('): # Arguments always on 1 line, except in some cases (cfr.BasicIDEAppear)
299 params
= line
.strip(' ()').replace(', ', ',')
305 def categorize(all_commands
):
306 # Clean black listed commands
307 for command
in BLACKLIST
:
308 cmd
= '.uno:' + command
309 if cmd
in all_commands
:
310 del all_commands
[cmd
]
311 # Set category based on the file name where the command was found first
312 for cmd
in all_commands
:
313 command
= all_commands
[cmd
]
314 cxcu
, chxx
, csdi
= '', '', ''
315 fxcu
= command
['xcufile']
317 cxcu
= os
.path
.basename(XCU_FILES
[fxcu
]).split('.')[0].replace('Commands', '')
318 fhxx
= command
['hxxfile']
320 chxx
= os
.path
.basename(HXX_FILES
[fhxx
]).split('.')[0]
321 fsdi
= command
['sdifile']
323 csdi
= os
.path
.basename(SDI_FILES
[fsdi
]).split('.')[0]
331 # Exceptions on general rule
332 if cat
== 'Generic' and chxx
== 'basslots':
334 command
['module'] = MODULES
[cat
]
337 def print_output(all_commands
):
339 # Return the longest string among the arguments
340 return max(args
, key
= len)
343 # Build string identifying the sources
344 xcufile
, xculinenumber
, hxxfile
, hxxlinenumber
, sdifile
, sdilinenumber
= 2, 3, 8, 10, 14, 16
346 if cmd
[xcufile
] >= 0:
347 src
+= '[' + REPO
+ XCU_FILES
[cmd
[xcufile
]].replace(srcdir
, '') + '#' + str(cmd
[xculinenumber
]) + ' XCU]'
348 if cmd
[sdifile
] >= 0:
349 src
+= ' [' + REPO
+ SDI_FILES
[cmd
[sdifile
]].replace(srcdir
, '') + '#' + str(cmd
[sdilinenumber
]) + ' SDI]'
350 if cmd
[hxxfile
] >= 0:
351 file = str(cmd
[hxxfile
] + 1 + len(XCU_FILES
) + len(SDI_FILES
))
352 src
+= ' <span title="File (' + file + ') line ' + str(cmd
[hxxlinenumber
]) + '">[[#hxx' + file + '|HXX]]</span>'
355 # Sort by category and command name
357 for cmd
in all_commands
:
358 cmdlist
= tuple(all_commands
[cmd
].values())
359 commands_list
.append(cmdlist
)
360 sorted_by_command
= sorted(commands_list
, key
= lambda cmd
: cmd
[0])
361 sorted_by_module
= sorted(sorted_by_command
, key
= lambda cmd
: cmd
[1])
363 # Produce tabular output
364 unocommand
, module
, label
, contextlabel
, tooltiplabel
, arguments
, resourceid
, numericid
, group
, mode
= 0, 1, 5, 6, 7, 18, 11, 12, 13, 17
366 for cmd
in sorted_by_module
:
367 # Format bottom and header
368 if lastmodule
!= cmd
[module
]:
369 if len(lastmodule
) > 0:
372 lastmodule
= cmd
[module
]
373 print('=== %s ===\n' % lastmodule
)
375 print('{| class="wikitable sortable" width="100%"')
377 print('! scope="col" | Dispatch command')
378 print('! scope="col" | Description')
379 print('! scope="col" | Group')
380 print('! scope="col" | Arguments')
381 print('! scope="col" | Internal<br>name (value)')
382 print('! scope="col" | Mode')
383 print('! scope="col" | Source<br>files')
385 print('| ' + cmd
[unocommand
].replace('&', '\n&'))
386 print('| ' + longest(cmd
[label
], cmd
[contextlabel
], cmd
[tooltiplabel
]))
387 print('| ' + cmd
[group
])
388 print('| ' + cmd
[arguments
].replace('\\n', '\n'))
389 if len(cmd
[numericid
]) == 0:
390 print('| ' + cmd
[resourceid
])
392 print('| ' + cmd
[resourceid
] + ' (' + cmd
[numericid
] + ')')
393 print('| ' + cmd
[mode
])
394 print('| ' + sources(cmd
))
396 # List the source files
397 print('== Source files ==\n')
399 for i
in range(len(XCU_FILES
)):
401 print(f
'({fn}) {REPO}{XCU_FILES[i]}\n'.replace(srcdir
, ''))
403 for i
in range(len(SDI_FILES
)):
405 print(f
'({fn}) {REPO}{SDI_FILES[i]}\n'.replace(srcdir
, ''))
407 for i
in range(len(HXX_FILES
)):
409 print(f
'<span id="hxx{fn}">({fn}) {HXX_FILES[i]}</span>\n'.replace(builddir
, ''))
416 analyze_xcu(all_commands
)
418 analyze_hxx(all_commands
)
420 analyze_sdi(all_commands
)
422 categorize(all_commands
)
424 print_output(all_commands
)
426 if __name__
== '__main__':