From 0743116b8e747a6c95d186dd7e9ad9d60c972e05 Mon Sep 17 00:00:00 2001 From: Kilian Weishaupt <kilian.weishaupt@iws.uni-stuttgart.de> Date: Sun, 1 Nov 2020 12:17:12 +0100 Subject: [PATCH] [doc] Add python script to automatically update parameterlist.txt --- bin/doc/getparameterlist.py | 173 ++++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 bin/doc/getparameterlist.py diff --git a/bin/doc/getparameterlist.py b/bin/doc/getparameterlist.py new file mode 100644 index 0000000000..8ecd8e3a70 --- /dev/null +++ b/bin/doc/getparameterlist.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python3 + +""" +Automatically updates parameterlist.txt by searching all *.hh files +for usage of getParam or getParamFromGroup. +""" + +import os +import itertools +from collections import OrderedDict +from shutil import copyfile + +# find the content of the given string between the first matching pair of opening/closing keys +def getEnclosedContent(string, openKey, closeKey): + + # cut off everything before the first occurence of openKey + string = openKey + string.partition(openKey)[2] + + # get content between mathing pair + rest = string.partition(closeKey) + result, rest = rest[0] + closeKey, rest[2] + while result.count(openKey) != result.count(closeKey): + rest = rest.partition(closeKey) + if rest[1] == '': raise IOError('Could not get content between "{}" and "{}" in given string "{}"'.format(openKey, closeKey, string)) + result, rest = result + rest[0] + closeKey, rest[2] + + return result.partition(openKey)[2].rpartition(closeKey)[0] + +# extract a parameter from a given line +def extractParamName(line): + + # split occurrences of getParam<T>(CALLARGS) or getParamFromGroup<T>(CALLARGS) + # into the template arguments T and the function arguments CALLARGS + if 'getParamFromGroup<' in line: + line = line.split('getParamFromGroup')[1] + hasGroup = True + elif 'getParam<' in line: + line = line.split('getParam')[1] + hasGroup = False + else: + return {} + + # TODO: Support this also + if line.count('getParam') > 1: + raise IOError('Cannot process multiple occurrences of "getParam" in one line') + + # remove trailing spaces and cut off everything behind semicolon + line = line.strip('\n').strip(' ').split(';')[0] + + # extract template arg between '<' and '>' + paramType = getEnclosedContent(line, '<', '>') + + # extract function arguments + functionArgs = line.partition('<' + paramType + '>')[2] + functionArgs = getEnclosedContent(functionArgs, '(', ')') + + if hasGroup: functionArgs = functionArgs.partition(',')[2] + functionArgs = functionArgs.partition(',') + paramName = functionArgs[0] + defaultValue = None if not functionArgs[2] else functionArgs[2] + + paramType = paramType.strip(' ') + paramName = paramName.strip(' ') + if (defaultValue): defaultValue = defaultValue.strip(' ') + + # if interior spaces occur in the parameter name, we can't identify it + if paramName[0] != '"' or paramName[-1] != '"' or ' ' in paramName: + raise IOError("Could not correctly process parameter name") + + return {'paramType': paramType, 'paramName': paramName.strip('"'), 'defaultValue': defaultValue} + +# extract all parameters from a given file +def getParamsFromFile(file): + parameters = [] + errors = {} + with open(file) as f: + for lineIdx, line in enumerate(f): + try: + param = extractParamName(line); + if param: parameters.append(param); + except IOError as e: + errors[lineIdx] = {'line': line.strip(), 'message': e} + + # print encountered errors + if errors: + print('\n\n{} paramter{} in file {} could not be retrieved automatically. Please check them yourself:'.format(len(errors), 's' if len(errors) > 1 else '', file)) + for lineIdx in errors: + print("\n\t-> line {}: {}".format(lineIdx, errors[lineIdx]['line'])) + print("\t\t-> error message: {}".format(errors[lineIdx]['message'])) + + return parameters + +# search all *.hh files for parameters +parameters = [] +rootDir = os.path.dirname(os.path.abspath(__file__)) + "/../../dumux" +for root, _, files in os.walk(rootDir): + for file in files: + if os.path.splitext(file)[1] == ".hh" and os.path.splitext(file)[0] != 'parameters': + parameters.append(getParamsFromFile(os.path.join(root, file))) + +# flatten the list +parameters = list(itertools.chain.from_iterable(parameters)) + +tableEntriesWithoutGroup = [] +tableEntriesWithGroup = [] +previousGroupEntry = None +for entry in sorted(parameters, key=lambda p: p['paramName']): + paramName = entry['paramName'].split('.') + if len(paramName) == 1: + group = None + else: + group = paramName[0].strip('"') + + groupEntry = group if group else '-' + if groupEntry != previousGroupEntry: + previousGroupEntry = groupEntry + if groupEntry != '-': + groupEntry = '\\b ' + groupEntry + + paramType = entry['paramType'] + paramName = '.'.join(paramName[1:]).strip('"') if group else paramName[0].strip('"') + + defaultValue = entry['defaultValue'] + if not defaultValue: + defaultValue = '' + + tableEntry = ' * | {} | {} | {} | {} | TODO: explanation |'.format(groupEntry, paramName , paramType , defaultValue) + + if group: + tableEntriesWithGroup.append(tableEntry) + else: + tableEntriesWithoutGroup.append(tableEntry) + + +# Remove duplicates. This does not work if one of the duplicate entries is the first in a group due to \b. +# We deal with this case later. +tableEntriesWithoutGroup = list(OrderedDict.fromkeys(tableEntriesWithoutGroup)) +tableEntriesWithGroup = list(OrderedDict.fromkeys(tableEntriesWithGroup)) + +# Remove left-over duplicates from above +for i, entry in enumerate(tableEntriesWithGroup): + if '\\b' in entry: + entry = entry.replace('\\b ', '') + if entry == tableEntriesWithGroup[i+1]: + del tableEntriesWithGroup[i+1] + +# combine entries +tableEntries = tableEntriesWithoutGroup + tableEntriesWithGroup + +# make a backup of the old parameterlist.txt file +copyfile(rootDir + '/../doc/doxygen/extradoc/parameterlist.txt', rootDir + '/../doc/doxygen/extradoc/parameterlist_old.txt') + +header = """/*! + *\\file + *\ingroup Parameter + * + *\\brief List of currently useable run-time parameters + * + * The listed run-time parameters are available in general, + * but we point out that a certain model might not be able + * to use every parameter! + * + * | Group | Parameter | Type | Default Value | Explanation | + * | :- | :- | :- | :- | :- | + * | - | ParameterFile | std::string| executable.input | name of the parameter file | +""" + +# overwrite the old parameterlist.txt file +with open(rootDir + '/../doc/doxygen/extradoc/parameterlist.txt', "w") as outputfile: + outputfile.write(header) + for e in tableEntries: + outputfile.write(e + '\n') + outputfile.write(' */\n') -- GitLab