#!/usr/bin/env python3 # This file is part of the LibreOffice project. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. """ Script to generate https://wiki.documentfoundation.org/Development/DispatchCommands 3 types of source files are scanned to identify and describe a list of relevant UNO commands: - .hxx files: containing the symbolic and numeric id's, and the respective modes and groups - .xcu files; containing several english labels as they appear in menus or tooltips - .sdi files: containing a list of potential arguments for the commands, and their types """ import os REPO = 'https://opengrok.libreoffice.org/xref/core/' BLACKLIST = ('_SwitchViewShell0', '_SwitchViewShell1', '_SwitchViewShell2', '_SwitchViewShell3', '_SwitchViewShell4') XCU_DIR = 'officecfg/registry/data/org/openoffice/Office/UI/' XCU_FILES = ( XCU_DIR + 'BasicIDECommands.xcu', XCU_DIR + 'CalcCommands.xcu', XCU_DIR + 'ChartCommands.xcu', XCU_DIR + 'DbuCommands.xcu', XCU_DIR + 'DrawImpressCommands.xcu', XCU_DIR + 'GenericCommands.xcu', XCU_DIR + 'MathCommands.xcu', XCU_DIR + 'ReportCommands.xcu', XCU_DIR + 'WriterCommands.xcu') HXX_DIR = './workdir/SdiTarget/' HXX_FILES = ( HXX_DIR + 'basctl/sdi/basslots.hxx', HXX_DIR + 'sc/sdi/scslots.hxx', HXX_DIR + 'sd/sdi/sdgslots.hxx', HXX_DIR + 'sd/sdi/sdslots.hxx', HXX_DIR + 'sfx2/sdi/sfxslots.hxx', HXX_DIR + 'starmath/sdi/smslots.hxx', HXX_DIR + 'svx/sdi/svxslots.hxx', HXX_DIR + 'sw/sdi/swslots.hxx') SDI_FILES = ( 'sc/sdi/scalc.sdi', 'sd/sdi/sdraw.sdi', 'sfx2/sdi/sfx.sdi', 'starmath/sdi/smath.sdi', 'svx/sdi/svx.sdi', 'sw/sdi/swriter.sdi') # Category is defined by the 1st file where the command has been found. Precedence: 1. xcu, 2. hxx, 3. sdi. MODULES = {'BasicIDE': 'Basic IDE, Forms, Dialogs', 'Calc': 'Calc', 'Chart': 'Charts', 'Dbu': 'Base', 'DrawImpress': 'Draw / Impress', 'Generic': 'Global', 'Math': 'Math', 'Report': 'Reports', 'Writer': 'Writer', 'basslots': 'Basic IDE, Forms, Dialogs', 'scslots': 'Calc', 'sdgslots': 'Draw / Impress', 'sdslots': 'Draw / Impress', 'sfxslots': 'Global', 'smslots': 'Math', 'svxslots': 'Global', 'swslots': 'Writer', 'scalc': 'Calc', 'sdraw': 'Draw / Impress', 'sfx': 'Global', 'smath': 'Math', 'svx': 'Global', 'swriter': 'Writer'} def newcommand(unocommand): cmd = {'unocommand': unocommand, 'module': '', 'xcufile': -1, 'xculinenumber': 0, 'xcuoccurs': 0, 'label': '', 'contextlabel': '', 'tooltiplabel': '', 'hxxfile': -1, 'hxxoccurs': 0, 'hxxlinenumber': 0, 'resourceid': '', 'numericid': '', 'group': '', 'sdifile': -1, 'sdioccurs': 0, 'sdilinenumber': 0, 'mode': '', 'arguments': ''} return cmd def analyze_xcu(all_commands): for filename in XCU_FILES: ln = 0 with open(filename) as fh: popups = False for line in fh: ln += 1 if '' in line: popups = True continue elif popups is True and line == ' ': popups = False continue if '' not in line: try: line = next(fh) ln += 1 except StopIteration: print("Warning: couldn't find '' line in %s" % filename, file=sys.stderr) break if '' in line: labeltext = line.replace('', '').replace('', '').strip() elif ' 0: for p in params.split(','): if len(split) > 0: split += CR elems = p.split() if len(elems) >= 2: split += elems[1] if 'String' in elems[0]: split += ' (string)' elif 'Bool' in elems[0]: split += ' (bool)' elif 'Int16' in elems[0]: split += ' (integer)' elif 'Int32' in elems[0]: split += ' (long)' else: split += ' (' + elems[0].replace('Sfx', '').replace('Svx', '').replace('Item', '').lower() + ')' return split for filename in SDI_FILES: ln = 0 comment, square, command, param = False, False, False, False with open(filename) as fh: for line in fh: ln += 1 line = line.replace(' ', ' ').strip() # Anomaly met in svx/sdi/svx.sdi if line.startswith('//'): pass elif comment is False and line.startswith('/*') and not line.endswith('*/'): comment = True elif comment is True and line.endswith('*/'): comment = False elif comment is False and line.startswith('/*') and line.endswith('*/'): pass elif comment is True: pass elif square is False and line.startswith('['): square = True mode = '' command = False elif square is True and line.endswith(']'): all_commands[command_name]['mode'] = mode square = False elif square is True: squaremode = line.strip(',;').split() if len(squaremode) == 3: mode += 'U' if squaremode[0] == 'AutoUpdate' and squaremode[2] == 'TRUE' else '' mode += 'M' if squaremode[0] == 'MenuConfig' and squaremode[2] == 'TRUE' else '' mode += 'T' if squaremode[0] == 'ToolBoxConfig' and squaremode[2] == 'TRUE' else '' mode += 'A' if squaremode[0] == 'AccelConfig' and squaremode[2] == 'TRUE' else '' elif comment is False and square is False and command is False and len(line) == 0: pass elif command is False: command_name = '.uno:' + line.split(' ')[1] if command_name not in all_commands: all_commands[command_name] = newcommand(command_name) all_commands[command_name]['sdifile'] = SDI_FILES.index(filename) all_commands[command_name]['sdilinenumber'] = ln all_commands[command_name]['sdioccurs'] += 1 if len(all_commands[command_name]['resourceid']) == 0: all_commands[command_name]['resourceid'] = line.split(' ')[2] command = True elif command is True and (line == '' or line == '()'): command = False elif command is True and (param is True or line.startswith('(')) and line.endswith(')'): if param: params += line.strip(' (),').replace(', ', ',') # At least 1 case of ", " in svx/sdi/svx.sdi line 8767 # At least 1 case of "( " in sw/sdi/swriter.sdi line 5477 else: params = line.strip(' (),').replace(', ', ',') # At least 1 case in sw/sdi/swriter.sdi line 7083 all_commands[command_name]['arguments'] = SplitArguments(params) command = False param = False elif command is True and line.startswith('('): # Arguments always on 1 line, except in some cases (cfr.BasicIDEAppear) params = line.strip(' ()').replace(', ', ',') param = True elif param is True: params += line def categorize(all_commands): # Clean black listed commands for command in BLACKLIST: cmd = '.uno:' + command if cmd in all_commands: del all_commands[cmd] # Set category based on the file name where the command was found first for cmd in all_commands: command = all_commands[cmd] cxcu, chxx, csdi = '', '', '' fxcu = command['xcufile'] if fxcu > -1: cxcu = os.path.basename(XCU_FILES[fxcu]).split('.')[0].replace('Commands', '') fhxx = command['hxxfile'] if fhxx > -1: chxx = os.path.basename(HXX_FILES[fhxx]).split('.')[0] fsdi = command['sdifile'] if fsdi > -1: csdi = os.path.basename(SDI_FILES[fsdi]).split('.')[0] # General rule: if len(cxcu) > 0: cat = cxcu elif len(chxx) > 0: cat = chxx else: cat = csdi # Exceptions on general rule if cat == 'Generic' and chxx == 'basslots': cat = chxx command['module'] = MODULES[cat] def print_output(all_commands): def longest(*args): # Return the longest string among the arguments return max(args, key = len) # def sources(cmd): # Build string identifying the sources xcufile, xculinenumber, hxxfile, hxxlinenumber, sdifile, sdilinenumber = 2, 3, 8, 10, 14, 16 src = '' if cmd[xcufile] >= 0: src += '[' + REPO + XCU_FILES[cmd[xcufile]] + '#' + str(cmd[xculinenumber]) + ' XCU]' if cmd[sdifile] >= 0: src += ' [' + REPO + SDI_FILES[cmd[sdifile]] + '#' + str(cmd[sdilinenumber]) + ' SDI]' if cmd[hxxfile] >= 0: file = str(cmd[hxxfile] + 1 + len(XCU_FILES) + len(SDI_FILES)) src += ' [[#hxx' + file + '|HXX]]' return src.strip() # # Sort by category and command name commands_list = [] for cmd in all_commands: cmdlist = tuple(all_commands[cmd].values()) commands_list.append(cmdlist) sorted_by_command = sorted(commands_list, key = lambda cmd: cmd[0]) sorted_by_module = sorted(sorted_by_command, key = lambda cmd: cmd[1]) # # Produce tabular output unocommand, module, label, contextlabel, tooltiplabel, arguments, resourceid, numericid, group, mode = 0, 1, 5, 6, 7, 18, 11, 12, 13, 17 lastmodule = '' for cmd in sorted_by_module: # Format bottom and header if lastmodule != cmd[module]: if len(lastmodule) > 0: print('\n|-\n|}\n') print('') lastmodule = cmd[module] print('=== %s ===\n' % lastmodule) print('') print('{| class="wikitable sortable" width="100%"') print('|-') print('! scope="col" | Dispatch command') print('! scope="col" | Description') print('! scope="col" | Group') print('! scope="col" | Arguments') print('! scope="col" | Internal
name (value)') print('! scope="col" | Mode') print('! scope="col" | Source
files') print('|-\n') print('| ' + cmd[unocommand].replace('&', '\n&')) print('| ' + longest(cmd[label], cmd[contextlabel], cmd[tooltiplabel])) print('| ' + cmd[group]) print('| ' + cmd[arguments].replace('\\n', '\n')) if len(cmd[numericid]) == 0: print('| ' + cmd[resourceid]) else: print('| ' + cmd[resourceid] + ' (' + cmd[numericid] + ')') print('| ' + cmd[mode]) print('| ' + sources(cmd)) print('|-\n|}\n') # List the source files print('== Source files ==\n') fn = 0 for i in range(len(XCU_FILES)): fn += 1 print(f'({fn}) {REPO}{XCU_FILES[i]}\n') print('\n') for i in range(len(SDI_FILES)): fn += 1 print(f'({fn}) {REPO}{SDI_FILES[i]}\n') print('\n') for i in range(len(HXX_FILES)): fn += 1 print(f'({fn}) {HXX_FILES[i][2:]}\n') print('
') def main(): all_commands = {} analyze_xcu(all_commands) analyze_hxx(all_commands) analyze_sdi(all_commands) categorize(all_commands) print_output(all_commands) if __name__ == '__main__': main()