Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.
Path: blob/master/Tools/autotest/param_metadata/param_parse.py
Views: 1799
#!/usr/bin/env python12'''Generates parameter metadata files suitable for consumption by3ground control stations and various web services45AP_FLAKE8_CLEAN67'''89from __future__ import print_function10import copy11import os12import re13import sys14from argparse import ArgumentParser1516from param import (Library, Parameter, Vehicle, known_group_fields,17known_param_fields, required_param_fields, required_library_param_fields, known_units)18from htmlemit import HtmlEmit19from rstemit import RSTEmit20from rstlatexpdfemit import RSTLATEXPDFEmit21from xmlemit import XmlEmit22from mdemit import MDEmit23from jsonemit import JSONEmit2425parser = ArgumentParser(description="Parse ArduPilot parameters.")26parser.add_argument("-v", "--verbose", dest='verbose', action='store_true', default=False, help="show debugging output")27parser.add_argument("--vehicle", required=True, help="Vehicle type to generate for")28parser.add_argument("--no-emit",29dest='emit_params',30action='store_false',31default=True,32help="don't emit parameter documention, just validate")33parser.add_argument("--format",34dest='output_format',35action='store',36default='all',37choices=['all', 'html', 'rst', 'rstlatexpdf', 'wiki', 'xml', 'json', 'edn', 'md'],38help="what output format to use")3940args = parser.parse_args()414243# Regular expressions for parsing the parameter metadata4445prog_param = re.compile(r"@Param(?:{([^}]+)})?: (\w+).*((?:\n[ \t]*// @(\w+)(?:{([^}]+)})?: ?(.*))+)(?:\n[ \t\r]*\n|\n[ \t]+[A-Z]|\n\-\-\]\])", re.MULTILINE) # noqa4647# match e.g @Value: 0=Unity, 1=Koala, 17=Liability48prog_param_fields = re.compile(r"[ \t]*// @(\w+): ?([^\r\n]*)")49# match e.g @Value{Copter}: 0=Volcano, 1=Peppermint50prog_param_tagged_fields = re.compile(r"[ \t]*// @(\w+){([^}]+)}: ([^\r\n]*)")5152prog_groups = re.compile(r"@Group: *(\w+).*((?:\n[ \t]*// @(Path): (\S+))+)", re.MULTILINE)5354apm_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), '../../../')555657def find_vehicle_parameter_filepath(vehicle_name):58apm_tools_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), '../../../Tools/')5960vehicle_name_to_dir_name_map = {61"Copter": "ArduCopter",62"Plane": "ArduPlane",63"Tracker": "AntennaTracker",64"Sub": "ArduSub",65}6667# first try ArduCopter/Parameters.cpp68for top_dir in apm_path, apm_tools_path:69path = os.path.join(top_dir, vehicle_name, "Parameters.cpp")70if os.path.exists(path):71return path7273# then see if we can map e.g. Copter -> ArduCopter74if vehicle_name in vehicle_name_to_dir_name_map:75path = os.path.join(top_dir, vehicle_name_to_dir_name_map[vehicle_name], "Parameters.cpp")76if os.path.exists(path):77return path7879raise ValueError("Unable to find parameters file for (%s)" % vehicle_name)808182def debug(str_to_print):83"""Debug output if verbose is set."""84if args.verbose:85print(str_to_print)868788def lua_applets():89'''return list of Library objects for lua applets and drivers'''90lua_lib = Library("", reference="Lua Script", not_rst=True, check_duplicates=True)91dirs = ["libraries/AP_Scripting/applets", "libraries/AP_Scripting/drivers"]92paths = []93for d in dirs:94for root, dirs, files in os.walk(os.path.join(apm_path, d)):95for file in files:96if not file.endswith(".lua"):97continue98f = os.path.join(root, file)99debug("Adding lua path %s" % f)100# the library is expected to have the path as a relative path from within101# a vehicle directory102f = f.replace(apm_path, "../")103paths.append(f)104setattr(lua_lib, "Path", ','.join(paths))105return lua_lib106107108libraries = []109110if args.vehicle != "AP_Periph":111# AP_Vehicle also has parameters rooted at "", but isn't referenced112# from the vehicle in any way:113ap_vehicle_lib = Library("", reference="VEHICLE") # the "" is tacked onto the front of param name114setattr(ap_vehicle_lib, "Path", os.path.join('..', 'libraries', 'AP_Vehicle', 'AP_Vehicle.cpp'))115libraries.append(ap_vehicle_lib)116117libraries.append(lua_applets())118119error_count = 0120current_param = None121current_file = None122123124def error(str_to_print):125"""Show errors."""126global error_count127error_count += 1128if current_file is not None:129print("Error in %s" % current_file)130if current_param is not None:131print("At param %s" % current_param)132print(str_to_print)133134135truename_map = {136"Rover": "Rover",137"ArduSub": "Sub",138"ArduCopter": "Copter",139"ArduPlane": "Plane",140"AntennaTracker": "Tracker",141"AP_Periph": "AP_Periph",142"Blimp": "Blimp",143}144valid_truenames = frozenset(truename_map.values())145truename = truename_map.get(args.vehicle, args.vehicle)146147documentation_tags_which_are_comma_separated_nv_pairs = frozenset([148'Values',149'Bitmask',150])151152vehicle_path = find_vehicle_parameter_filepath(args.vehicle)153154basename = os.path.basename(os.path.dirname(vehicle_path))155path = os.path.normpath(os.path.dirname(vehicle_path))156reference = basename # so links don't break we use ArduCopter157vehicle = Vehicle(truename, path, reference=reference)158debug('Found vehicle type %s' % vehicle.name)159160161def process_vehicle(vehicle):162debug("===\n\n\nProcessing %s" % vehicle.name)163current_file = vehicle.path+'/Parameters.cpp'164165f = open(current_file)166p_text = f.read()167f.close()168group_matches = prog_groups.findall(p_text)169170debug(group_matches)171for group_match in group_matches:172lib = Library(group_match[0])173fields = prog_param_fields.findall(group_match[1])174for field in fields:175if field[0] in known_group_fields:176setattr(lib, field[0], field[1])177else:178error("group: unknown parameter metadata field '%s'" % field[0])179if not any(lib.name == parsed_l.name for parsed_l in libraries):180libraries.append(lib)181182param_matches = []183param_matches = prog_param.findall(p_text)184185for param_match in param_matches:186(only_vehicles, param_name, field_text) = (param_match[0],187param_match[1],188param_match[2])189if len(only_vehicles):190only_vehicles_list = [x.strip() for x in only_vehicles.split(",")]191for only_vehicle in only_vehicles_list:192if only_vehicle not in valid_truenames:193raise ValueError("Invalid only_vehicle %s" % only_vehicle)194if vehicle.truename not in only_vehicles_list:195continue196p = Parameter(vehicle.reference+":"+param_name, current_file)197debug(p.name + ' ')198global current_param199current_param = p.name200fields = prog_param_fields.findall(field_text)201p.__field_text = field_text202field_list = []203for field in fields:204(field_name, field_value) = field205field_list.append(field[0])206if field[0] in known_param_fields:207value = re.sub('@PREFIX@', "", field[1]).rstrip()208if hasattr(p, field_name):209if field_name in documentation_tags_which_are_comma_separated_nv_pairs:210# allow concatenation of (e.g.) bitmask fields211x = eval("p.%s" % field_name)212x += ", "213x += value214value = x215else:216error("%s already has field %s" % (p.name, field_name))217setattr(p, field[0], value)218elif field[0] in frozenset(["CopyFieldsFrom", "CopyValuesFrom"]):219setattr(p, field[0], field[1])220else:221error("param: unknown parameter metadata field '%s'" % field[0])222223if (getattr(p, 'Values', None) is not None and224getattr(p, 'Bitmask', None) is not None):225error("Both @Values and @Bitmask present")226227vehicle.params.append(p)228current_file = None229debug("Processed %u params" % len(vehicle.params))230231232process_vehicle(vehicle)233234debug("Found %u documented libraries" % len(libraries))235236libraries = list(libraries)237238alllibs = libraries[:]239240241def process_library(vehicle, library, pathprefix=None):242'''process one library'''243paths = library.Path.split(',')244for path in paths:245path = path.strip()246global current_file247current_file = path248debug("\n Processing file '%s'" % path)249if pathprefix is not None:250libraryfname = os.path.join(pathprefix, path)251elif path.find('/') == -1:252libraryfname = os.path.join(vehicle.path, path)253else:254libraryfname = os.path.normpath(os.path.join(apm_path + '/libraries/' + path))255if path and os.path.exists(libraryfname):256f = open(libraryfname)257p_text = f.read()258f.close()259else:260error("Path %s not found for library %s (fname=%s)" % (path, library.name, libraryfname))261continue262263param_matches = prog_param.findall(p_text)264debug("Found %u documented parameters" % len(param_matches))265for param_match in param_matches:266(only_vehicles, param_name, field_text) = (param_match[0],267param_match[1],268param_match[2])269if len(only_vehicles):270only_vehicles_list = [x.strip() for x in only_vehicles.split(",")]271for only_vehicle in only_vehicles_list:272if only_vehicle not in valid_truenames:273raise ValueError("Invalid only_vehicle %s" % only_vehicle)274if vehicle.name not in only_vehicles_list:275continue276p = Parameter(library.name+param_name, current_file)277debug(p.name + ' ')278global current_param279current_param = p.name280fields = prog_param_fields.findall(field_text)281p.__field_text = field_text282field_list = []283for field in fields:284(field_name, field_value) = field285field_list.append(field[0])286if field[0] in known_param_fields:287value = re.sub('@PREFIX@', library.name, field[1])288if hasattr(p, field_name):289if field_name in documentation_tags_which_are_comma_separated_nv_pairs:290# allow concatenation of (e.g.) bitmask fields291x = eval("p.%s" % field_name)292x += ", "293x += value294value = x295else:296error("%s already has field %s" % (p.name, field_name))297setattr(p, field[0], value)298elif field[0] in frozenset(["CopyFieldsFrom", "CopyValuesFrom"]):299setattr(p, field[0], field[1])300else:301error("param: unknown parameter metadata field %s" % field[0])302303debug("matching %s" % field_text)304fields = prog_param_tagged_fields.findall(field_text)305# a parameter is considered to be vehicle-specific if306# there does not exist a Values: or Values{VehicleName}307# for that vehicle but @Values{OtherVehicle} exists.308seen_values_or_bitmask_for_this_vehicle = False309seen_values_or_bitmask_for_other_vehicle = False310for field in fields:311only_for_vehicles = field[1].split(",")312only_for_vehicles = [some_vehicle.rstrip().lstrip() for some_vehicle in only_for_vehicles]313delta = set(only_for_vehicles) - set(truename_map.values())314if len(delta):315error("Unknown vehicles (%s)" % delta)316debug("field[0]=%s vehicle=%s field[1]=%s only_for_vehicles=%s\n" %317(field[0], vehicle.name, field[1], str(only_for_vehicles)))318if field[0] not in known_param_fields:319error("tagged param: unknown parameter metadata field '%s'" % field[0])320continue321if vehicle.name not in only_for_vehicles:322if len(only_for_vehicles) and field[0] in documentation_tags_which_are_comma_separated_nv_pairs:323seen_values_or_bitmask_for_other_vehicle = True324continue325326append_value = False327if field[0] in documentation_tags_which_are_comma_separated_nv_pairs:328if vehicle.name in only_for_vehicles:329if seen_values_or_bitmask_for_this_vehicle:330append_value = hasattr(p, field[0])331seen_values_or_bitmask_for_this_vehicle = True332else:333if seen_values_or_bitmask_for_this_vehicle:334continue335append_value = hasattr(p, field[0])336337value = re.sub('@PREFIX@', library.name, field[2])338if append_value:339setattr(p, field[0], getattr(p, field[0]) + ',' + value)340else:341setattr(p, field[0], value)342343if (getattr(p, 'Values', None) is not None and344getattr(p, 'Bitmask', None) is not None):345error("Both @Values and @Bitmask present")346347if (getattr(p, 'Values', None) is None and348getattr(p, 'Bitmask', None) is None):349# values and Bitmask available for this vehicle350if seen_values_or_bitmask_for_other_vehicle:351# we've (e.g.) seen @Values{Copter} when we're352# processing for Rover, and haven't seen either353# @Values: or @Vales{Rover} - so we omit this354# parameter on the assumption that it is not355# applicable for this vehicle.356continue357358if getattr(p, 'Vector3Parameter', None) is not None:359params_to_add = []360for axis in 'X', 'Y', 'Z':361new_p = copy.copy(p)362new_p.change_name(p.name + "_" + axis)363for a in ["Description"]:364if hasattr(new_p, a):365current = getattr(new_p, a)366setattr(new_p, a, current + " (%s-axis)" % axis)367params_to_add.append(new_p)368else:369params_to_add = [p]370371for p in params_to_add:372p.path = path # Add path. Later deleted - only used for duplicates373if library.check_duplicates and library.has_param(p.name):374error("Duplicate parameter %s in %s" % (p.name, library.name))375continue376library.params.append(p)377378group_matches = prog_groups.findall(p_text)379debug("Found %u groups" % len(group_matches))380debug(group_matches)381done_groups = dict()382for group_match in group_matches:383group = group_match[0]384debug("Group: %s" % group)385do_append = True386if group in done_groups:387# this is to handle cases like the RangeFinder388# parameters, where the wasp stuff gets tack into the389# same RNGFND1_ group390lib = done_groups[group]391do_append = False392else:393lib = Library(group)394done_groups[group] = lib395396fields = prog_param_fields.findall(group_match[1])397for field in fields:398if field[0] in known_group_fields:399setattr(lib, field[0], field[1])400elif field[0] in ["CopyFieldsFrom", "CopyValuesFrom"]:401setattr(p, field[0], field[1])402else:403error("unknown parameter metadata field '%s'" % field[0])404if not any(lib.name == parsed_l.name for parsed_l in libraries):405if do_append:406lib.set_name(library.name + lib.name)407debug("Group name: %s" % lib.name)408process_library(vehicle, lib, os.path.dirname(libraryfname))409if do_append:410alllibs.append(lib)411412current_file = None413414415for library in libraries:416debug("===\n\n\nProcessing library %s" % library.name)417418if hasattr(library, 'Path'):419process_library(vehicle, library)420else:421error("Skipped: no Path found")422423debug("Processed %u documented parameters" % len(library.params))424425# sort libraries by name426alllibs = sorted(alllibs, key=lambda x: x.name)427428libraries = alllibs429430431def is_number(numberString):432try:433float(numberString)434return True435except ValueError:436return False437438439def clean_param(param):440if (hasattr(param, "Values")):441valueList = param.Values.split(",")442new_valueList = []443for i in valueList:444(start, sep, end) = i.partition(":")445if sep != ":":446raise ValueError("Expected a colon separator in (%s)" % (i,))447if len(end) == 0:448raise ValueError("Expected a colon-separated string, got (%s)" % i)449end = end.strip()450start = start.strip()451new_valueList.append(":".join([start, end]))452param.Values = ",".join(new_valueList)453454if hasattr(param, "Vector3Parameter"):455delattr(param, "Vector3Parameter")456457458def do_copy_values(vehicle_params, libraries, param):459if not hasattr(param, "CopyValuesFrom"):460return461462# so go and find the values...463wanted_name = param.CopyValuesFrom464if hasattr(param, 'Vector3Parameter'):465suffix = param.name[-2:]466wanted_name += suffix467468del param.CopyValuesFrom469for x in vehicle_params:470name = x.name471(v, name) = name.split(":")472if name != wanted_name:473continue474param.Values = x.Values475return476477for lib in libraries:478for x in lib.params:479if x.name != wanted_name:480continue481param.Values = x.Values482return483484error("Did not find value to copy (%s wants %s)" %485(param.name, wanted_name))486487488def do_copy_fields(vehicle_params, libraries, param):489do_copy_values(vehicle_params, libraries, param)490491if not hasattr(param, 'CopyFieldsFrom'):492return493494# so go and find the values...495wanted_name = param.CopyFieldsFrom496del param.CopyFieldsFrom497498if hasattr(param, 'Vector3Parameter'):499suffix = param.name[-2:]500wanted_name += suffix501502for x in vehicle_params:503name = x.name504(v, name) = name.split(":")505if name != wanted_name:506continue507for field in dir(x):508if hasattr(param, field):509# override510continue511if field.startswith("__") or field in frozenset(["name", "real_path"]):512# internal methods like __ne__513continue514setattr(param, field, getattr(x, field))515return516517for lib in libraries:518for x in lib.params:519if x.name != wanted_name:520continue521for field in dir(x):522if hasattr(param, field):523# override524continue525if field.startswith("__") or field in frozenset(["name", "real_path"]):526# internal methods like __ne__527continue528setattr(param, field, getattr(x, field))529return530531error("Did not find value to copy (%s wants %s)" %532(param.name, wanted_name))533534535def validate(param, is_library=False):536"""537Validates the parameter meta data.538"""539global current_file540current_file = param.real_path541global current_param542current_param = param.name543# Validate values544if (hasattr(param, "Range")):545rangeValues = param.__dict__["Range"].split(" ")546if (len(rangeValues) != 2):547error("Invalid Range values for %s (%s)" %548(param.name, param.__dict__["Range"]))549return550min_value = rangeValues[0]551max_value = rangeValues[1]552if not is_number(min_value):553error("Min value not number: %s %s" % (param.name, min_value))554return555if not is_number(max_value):556error("Max value not number: %s %s" % (param.name, max_value))557return558# Check for duplicate in @value field559if (hasattr(param, "Values")):560valueList = param.__dict__["Values"].split(",")561values = []562for i in valueList:563i = i.replace(" ", "")564values.append(i.partition(":")[0])565if (len(values) != len(set(values))):566error("Duplicate values found" + str({x for x in values if values.count(x) > 1}))567# Validate units568if (hasattr(param, "Units")):569if (param.__dict__["Units"] != "") and (param.__dict__["Units"] not in known_units):570error("unknown units field '%s'" % param.__dict__["Units"])571# Validate User572if (hasattr(param, "User")):573if param.User.strip() not in ["Standard", "Advanced"]:574error("unknown user (%s)" % param.User.strip())575576if (hasattr(param, "Description")):577if not param.Description or not param.Description.strip():578error("Empty Description (%s)" % param)579580required_fields = required_param_fields581if is_library:582required_fields = required_library_param_fields583for req_field in required_fields:584if not getattr(param, req_field, False):585error("missing parameter metadata field '%s' in %s" % (req_field, param.__field_text))586587588# handle CopyFieldsFrom and CopyValuesFrom:589for param in vehicle.params:590do_copy_fields(vehicle.params, libraries, param)591for library in libraries:592for param in library.params:593do_copy_fields(vehicle.params, libraries, param)594595for param in vehicle.params:596clean_param(param)597598for param in vehicle.params:599validate(param)600601# Find duplicate names in library and fix up path602for library in libraries:603param_names_seen = set()604param_names_duplicate = set()605# Find duplicates:606for param in library.params:607if param.name in param_names_seen: # is duplicate608param_names_duplicate.add(param.name)609param_names_seen.add(param.name)610# Fix up path for duplicates611for param in library.params:612if param.name in param_names_duplicate:613param.path = param.path.rsplit('/')[-1].rsplit('.')[0]614else:615# not a duplicate, so delete attribute.616delattr(param, "path")617618for library in libraries:619for param in library.params:620clean_param(param)621622for library in libraries:623for param in library.params:624validate(param, is_library=True)625626if not args.emit_params:627sys.exit(error_count)628629all_emitters = {630'json': JSONEmit,631'xml': XmlEmit,632'html': HtmlEmit,633'rst': RSTEmit,634'rstlatexpdf': RSTLATEXPDFEmit,635'md': MDEmit,636}637638try:639from ednemit import EDNEmit640all_emitters['edn'] = EDNEmit641except ImportError:642# if the user wanted edn only then don't hide any errors643if args.output_format == 'edn':644raise645646if args.verbose:647print("Unable to emit EDN, install edn_format and pytz if edn is desired")648649# filter to just the ones we want to emit:650emitters_to_use = []651for emitter_name in all_emitters.keys():652if args.output_format == 'all' or args.output_format == emitter_name:653emitters_to_use.append(emitter_name)654655# actually invoke each emitter:656for emitter_name in emitters_to_use:657emit = all_emitters[emitter_name]()658659emit.emit(vehicle)660661emit.start_libraries()662663# create a single parameter list for all SIM_ parameters (for rst to use)664sim_params = []665for library in libraries:666if library.name.startswith("SIM_"):667sim_params.extend(library.params)668sim_params = sorted(sim_params, key=lambda x : x.name)669670for library in libraries:671if library.params:672# we sort the parameters in the SITL library to avoid673# rename, and on the assumption that an asciibetical sort674# gives a good layout:675if emitter_name == 'rst':676if library.not_rst:677continue678if library.name == 'SIM_':679library = copy.deepcopy(library)680library.params = sim_params681elif library.name.startswith('SIM_'):682continue683emit.emit(library)684685emit.close()686687sys.exit(error_count)688689690