diff --git a/Tools/autotest/param_metadata/htmlemit.py b/Tools/autotest/param_metadata/htmlemit.py index f7284d060e..66639dcbc7 100644 --- a/Tools/autotest/param_metadata/htmlemit.py +++ b/Tools/autotest/param_metadata/htmlemit.py @@ -5,7 +5,7 @@ Emit docs in a form acceptable to the old Ardupilot wordpress docs site from param import known_param_fields, known_units from emit import Emit -import cgi +import html class HtmlEmit(Emit): @@ -59,7 +59,7 @@ DO NOT EDIT t += '\n\n

%s

' % tag if d.get('User', None) == 'Advanced': t += 'Note: This parameter is for advanced users
' - t += "\n\n

%s

\n" % cgi.escape(param.Description) + t += "\n\n

%s

\n" % html.escape(param.Description) t += "\n" self.t += t diff --git a/Tools/autotest/param_metadata/jsonemit.py b/Tools/autotest/param_metadata/jsonemit.py new file mode 100644 index 0000000000..cb584c3ce6 --- /dev/null +++ b/Tools/autotest/param_metadata/jsonemit.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python + +import json +import copy +from emit import Emit + + +# Emit APM documentation in JSON format +class JSONEmit(Emit): + def __init__(self): + Emit.__init__(self) + json_fname = 'apm.pdef.json' + self.f = open(json_fname, mode='w') + self.content = {"json": {"version": 0}} + self.name = '' + + def close(self): + json.dump(self.content, self.f, indent=2, sort_keys=True) + self.f.close() + + def jsonFromKeyList(self, main_key, dictionary): + json_object = {} + if main_key in dictionary: + values = dictionary[main_key] + for value in values.split(','): + key, description = value.split(":") + json_object[key.strip()] = description.strip() + return json_object + + def emit(self, g): + content = {} + + # Copy content to avoid any modification + g = copy.deepcopy(g) + + # Get vehicle name + if 'truename' in g.__dict__: + self.name = g.__dict__['truename'] + self.content[self.name] = {} + + # Check all params available + for param in g.params: + param_json = {} + + # Get display name + if hasattr(param, 'DisplayName'): + # i.e. ArduPlane (ArduPlane:FOOPARM) + param_json['displayName'] = param.DisplayName + + # Get description + if hasattr(param, 'Description'): + param_json['description'] = param.Description + + # Get user type + if hasattr(param, 'User'): + # i.e. Standard or Advanced + param_json['user'] = param.User + + # Get param name and and remove key + name = param.__dict__.pop('name') + if ':' in name: + name = name.split(':')[1] + + # Remove real_path key + if 'real_path' in param.__dict__: + param.__dict__.pop('real_path') + + # Get range section if available + range_json = {} + if 'Range' in param.__dict__: + range = param.__dict__['Range'].split(' ') + range_json['low'] = range[0] + range_json['high'] = range[1] + param.__dict__.pop('Range') + + # Get bitmask section if available + bitmask_json = self.jsonFromKeyList('Bitmask', param.__dict__) + if(bitmask_json): + param.__dict__.pop('Bitmask') + + # get value section if availables + values_json = self.jsonFromKeyList('Values', param.__dict__) + if(values_json): + param.__dict__.pop('Values') + + # Set actual content + content[name] = param.__dict__ + + # Set range if available + if(range_json): + content[name]['Range'] = range_json + + # Set bitmask if available + if(bitmask_json): + content[name]['Bitmask'] = bitmask_json + + # Set values if available + if(values_json): + content[name]['Values'] = values_json + + # Update main content with actual content + for key in content: + self.content[self.name][key] = content[key] diff --git a/Tools/autotest/param_metadata/param.py b/Tools/autotest/param_metadata/param.py index 2e070ca092..28cb01f2df 100644 --- a/Tools/autotest/param_metadata/param.py +++ b/Tools/autotest/param_metadata/param.py @@ -31,6 +31,7 @@ known_param_fields = [ 'Bitmask', 'Volatile', 'ReadOnly', + 'Calibration', ] # Follow SI units conventions from: diff --git a/Tools/autotest/param_metadata/param_parse.py b/Tools/autotest/param_metadata/param_parse.py index 694361afcd..b72f002cf8 100755 --- a/Tools/autotest/param_metadata/param_parse.py +++ b/Tools/autotest/param_metadata/param_parse.py @@ -13,6 +13,7 @@ from rstemit import RSTEmit from wikiemit import WikiEmit from xmlemit import XmlEmit from mdemit import MDEmit +from jsonemit import JSONEmit parser = ArgumentParser(description="Parse ArduPilot parameters.") parser.add_argument("-v", "--verbose", dest='verbose', action='store_true', default=False, help="show debugging output") @@ -26,19 +27,19 @@ parser.add_argument("--format", dest='output_format', action='store', default='all', - choices=['all', 'html', 'rst', 'wiki', 'xml', 'edn', 'md'], + choices=['all', 'html', 'rst', 'wiki', 'xml', 'json', 'edn', 'md'], help="what output format to use") args = parser.parse_args() # Regular expressions for parsing the parameter metadata -prog_param = re.compile(r"@Param: (\w+).*((?:\n[ \t]*// @(\w+)(?:{([^}]+)})?: (.*))+)(?:\n\n|\n[ \t]+[A-Z])", re.MULTILINE) +prog_param = re.compile(r"@Param: (\w+).*((?:\n[ \t]*// @(\w+)(?:{([^}]+)})?: (.*))+)(?:\n[ \t\r]*\n|\n[ \t]+[A-Z])", re.MULTILINE) # match e.g @Value: 0=Unity, 1=Koala, 17=Liability -prog_param_fields = re.compile(r"[ \t]*// @(\w+): (.*)") +prog_param_fields = re.compile(r"[ \t]*// @(\w+): ([^\r\n]*)") # match e.g @Value{Copter}: 0=Volcano, 1=Peppermint -prog_param_tagged_fields = re.compile(r"[ \t]*// @(\w+){([^}]+)}: (.*)") +prog_param_tagged_fields = re.compile(r"[ \t]*// @(\w+){([^}]+)}: ([^\r\n]*)") prog_groups = re.compile(r"@Group: *(\w+).*((?:\n[ \t]*// @(Path): (\S+))+)", re.MULTILINE) @@ -53,6 +54,12 @@ vehicle_paths.sort(reverse=True) vehicles = [] libraries = [] +# AP_Vehicle also has parameters rooted at "", but isn't referenced +# from the vehicle in any way: +ap_vehicle_lib = Library("") # the "" is tacked onto the front of param name +setattr(ap_vehicle_lib, "Path", os.path.join('..', 'libraries', 'AP_Vehicle', 'AP_Vehicle.cpp')) +libraries.append(ap_vehicle_lib) + error_count = 0 current_param = None current_file = None @@ -69,14 +76,14 @@ def error(str_to_print): global error_count error_count += 1 if current_file is not None: - print("In %s" % current_file) + print("Error in %s" % current_file) if current_param is not None: print("At param %s" % current_param) print(str_to_print) truename_map = { - "APMrover2": "Rover", + "Rover": "Rover", "ArduSub": "Sub", "ArduCopter": "Copter", "ArduPlane": "Plane", @@ -124,7 +131,7 @@ for vehicle in vehicles: for field in fields: field_list.append(field[0]) if field[0] in known_param_fields: - value = re.sub('@PREFIX@', "", field[1]) + value = re.sub('@PREFIX@', "", field[1]).rstrip() setattr(p, field[0], value) else: error("param: unknown parameter metadata field '%s'" % field[0]) @@ -165,7 +172,7 @@ def process_library(vehicle, library, pathprefix=None): p_text = f.read() f.close() else: - error("Path %s not found for library %s" % (path, library.name)) + error("Path %s not found for library %s (fname=%s)" % (path, library.name, libraryfname)) continue param_matches = prog_param.findall(p_text) @@ -278,7 +285,8 @@ def validate(param): if (hasattr(param, "Range")): rangeValues = param.__dict__["Range"].split(" ") if (len(rangeValues) != 2): - error("Invalid Range values for %s" % (param.name)) + error("Invalid Range values for %s (%s)" % + (param.name, param.__dict__["Range"])) return min_value = rangeValues[0] max_value = rangeValues[1] @@ -288,6 +296,15 @@ def validate(param): if not is_number(max_value): error("Max value not number: %s %s" % (param.name, max_value)) return + # Check for duplicate in @value field + if (hasattr(param, "Values")): + valueList = param.__dict__["Values"].split(",") + values = [] + for i in valueList: + i = i.replace(" ","") + values.append(i.partition(":")[0]) + if (len(values) != len(set(values))): + print("Duplicate values found") # Validate units if (hasattr(param, "Units")): if (param.__dict__["Units"] != "") and (param.__dict__["Units"] not in known_units): @@ -335,6 +352,8 @@ def do_emit(emit): if args.emit_params: + if args.output_format == 'all' or args.output_format == 'json': + do_emit(JSONEmit()) if args.output_format == 'all' or args.output_format == 'xml': do_emit(XmlEmit()) if args.output_format == 'all' or args.output_format == 'wiki': diff --git a/Tools/autotest/param_metadata/rstemit.py b/Tools/autotest/param_metadata/rstemit.py index e54f1cf2f9..1b0bc7e085 100644 --- a/Tools/autotest/param_metadata/rstemit.py +++ b/Tools/autotest/param_metadata/rstemit.py @@ -3,7 +3,7 @@ from __future__ import print_function import re from param import known_param_fields, known_units from emit import Emit -import cgi +import html # Emit docs in a RST format @@ -241,7 +241,7 @@ Complete Parameter List headings = [] row = [] - for field in param.__dict__.keys(): + for field in sorted(param.__dict__.keys()): if field not in ['name', 'DisplayName', 'Description', 'User'] and field in known_param_fields: headings.append(field) if field in field_table_info and Emit.prog_values_field.match(param.__dict__[field]): @@ -256,9 +256,9 @@ Complete Parameter List # convert the abreviated unit into a full # textual one: units = known_units[abreviated_units] - row.append(cgi.escape(units)) + row.append(html.escape(units)) else: - row.append(cgi.escape(param.__dict__[field])) + row.append(html.escape(param.__dict__[field])) if len(row): ret += "\n\n" + self.tablify([row], headings=headings) + "\n\n" self.t += ret + "\n" diff --git a/Tools/autotest/param_metadata/xmlemit.py b/Tools/autotest/param_metadata/xmlemit.py index f2fa294ca1..3d7d86e674 100644 --- a/Tools/autotest/param_metadata/xmlemit.py +++ b/Tools/autotest/param_metadata/xmlemit.py @@ -46,6 +46,9 @@ class XmlEmit(Emit): if hasattr(param, 'User'): t += ' user=%s' % quoteattr(param.User) # i.e. Standard or Advanced + if hasattr(param, 'Calibration'): + t += ' calibration=%s' % quoteattr(param.Calibration) # i.e. Standard or Advanced + t += ">\n" # Add values as chidren of this node