diff --git a/cfg/Test.cfg b/cfg/Test.cfg index 74ad0308..05b4107d 100755 --- a/cfg/Test.cfg +++ b/cfg/Test.cfg @@ -52,7 +52,7 @@ gen.add("int_", int_t, 1, "Int parameter",0, -10, 10) gen.add("double_", double_t, 2, "double parameter", 0, -2, 10) gen.add("double_no_minmax", double_t, 2, "double parameter without boundaries", 1) gen.add("double_no_max", double_t, 2, "double parameter without max value", 2, 0) -gen.add("str_", str_t, 4, "String parameter","foo") +gen.add("str_", str_t, 4, "String parameter","foo, default=\"this is it\"") gen.add("mstr_", str_t, 4, "Multibyte String parameter","bar") gen.add("bool_", bool_t, 8, "Boolean parameter",False) gen.add("level", int_t, 16, "Contains the level of the previous change",0) @@ -67,9 +67,9 @@ group2.add("group2_string", str_t, 0, "A third level group parameter", "test str group2.add("some_other", str_t, 0, "Something", "AAAAAAGGHH") group2.add("variable", bool_t,0, "A boolean", True) group3 = group2.add_group("Group3") -group3.add("deep_var_int", int_t, 0, "Were very far down now", 0, 0, 3, edit_method = enum) -group3.add("deep_var_bool", bool_t, 0, "Were even farther down now!!", True) -group3.add("deep_var_double", double_t, 0, "Were super far down now!!", -1.0) +group3.add("deep_var_int", int_t, 0, "We're very far down now", 0, 0, 3, edit_method = enum) +group3.add("deep_var_bool", bool_t, 0, "We're even farther down now!!", True) +group3.add("deep_var_double", double_t, 0, "We're \"super\" far down now!!", -1.0) group12 = gen.add_group("Upper Group 2") group12.add("some_param", int_t, 0, "Some param", 20) diff --git a/src/dynamic_reconfigure/parameter_generator_catkin.py b/src/dynamic_reconfigure/parameter_generator_catkin.py index 5041e3e3..a0087cc1 100644 --- a/src/dynamic_reconfigure/parameter_generator_catkin.py +++ b/src/dynamic_reconfigure/parameter_generator_catkin.py @@ -50,6 +50,7 @@ import string import sys import re +import textwrap # LINEDEBUG="#line" LINEDEBUG = "//#line" @@ -63,20 +64,27 @@ id = 0 -def check_description(description): - quotes = ['"', "'"] - for quote in quotes: - if description.find(quote) != -1: - raise Exception(r"""quotes not allowed in description string `%s`""" % description) - - def check_name(name): pattern = r'^[a-zA-Z][a-zA-Z0-9_]*$' if not re.match(pattern, name): raise Exception("The name of field \'%s\' does not follow the ROS naming conventions, see http://wiki.ros.org/ROS/Patterns/Conventions" % name) +def cquote(s): + """ quote a string for inclusion in C source """ + escape = { + '\\': r'\\', + '"': r'\"', + '\r': r'\r', + '\n': r'\n', + '\0': r'\0' + } + return '"{}"'.format(re.sub( + '|'.join(re.escape(c) for c in escape), + lambda m: escape[m.group(0)], + s + )) -class ParameterGenerator: +class ParameterGenerator(object): minval = { 'int': -0x80000000, # 'INT_MIN', 'double': '-std::numeric_limits::infinity()', @@ -98,7 +106,7 @@ class ParameterGenerator: 'bool': False, } - class Group: + class Group(object): instances = {} def __init__(self, gen, name, type, state, id, parent): @@ -246,21 +254,19 @@ def __init__(self): id = 1 self.constants = [] if len(sys.argv) < 5: - msg = """ -ahhhh! Unexpected command line syntax! + raise SystemExit(textwrap.dedent("""\ + ahhhh! Unexpected command line syntax! -Are you trying to call a dynamic_reconfigure configuration generation script -directly? When you are using dynamic_reconfigure with python, you don't ever -need to invoke the configuration generator script yourself; it loads -automatically. If you are using dynamic_reconfigure from C++, you need to -add a call to generate_dynamic_reconfigure_options() in your CMakeLists.txt + Are you trying to call a dynamic_reconfigure configuration generation script + directly? When you are using dynamic_reconfigure with python, you don't ever + need to invoke the configuration generator script yourself; it loads + automatically. If you are using dynamic_reconfigure from C++, you need to + add a call to generate_dynamic_reconfigure_options() in your CMakeLists.txt -For an example, see http://wiki.ros.org/dynamic_reconfigure/Tutorials + For an example, see http://wiki.ros.org/dynamic_reconfigure/Tutorials -Have a nice day - """ - print(msg) - sys.exit(1) + Have a nice day + """)) self.dynconfpath = sys.argv[1] # FIXME this is awful self.binary_dir = sys.argv[2] self.cpp_gen_dir = sys.argv[3] @@ -275,7 +281,6 @@ def const(self, name, type, value, descr): 'srcfile': inspect.getsourcefile(inspect.currentframe().f_back.f_code), 'description': descr } - check_description(descr) self.fill_type(newconst) self.check_type(newconst, 'value') self.constants.append(newconst) @@ -284,7 +289,6 @@ def const(self, name, type, value, descr): def enum(self, constants, description): if len(set(const['type'] for const in constants)) != 1: raise Exception("Inconsistent types in enum!") - check_description(description) return repr({'enum': constants, 'enum_description': description}) # Wrap add and add_group for the default group @@ -344,85 +348,82 @@ def generate(self, pkgname, nodename, name): def generatewikidoc(self): self.mkdirabs(os.path.join(self.binary_dir, "docs")) - f = open(os.path.join(self.binary_dir, "docs", self.msgname + ".wikidoc"), 'w') - print( - """# Autogenerated param section. Do not hand edit. - param { - group.0 { - name=Dynamically Reconfigurable Parameters - desc=See the [[dynamic_reconfigure]] package for details on dynamically reconfigurable parameters.""", file=f) - i = -1 - for param in self.group.get_parameters(): - i = i + 1 - range = "" - try: - enum = eval(param['edit_method'])['enum'] - range = ", ".join(Template("$name ($value): $description").substitute(const) for const in enum) - range = "Possible values are: " + range - except: - if param['type'] == int_t or param['type'] == double_t: - range = Template("Range: $min to $max").substitute(param) - print(Template( - """$i.name= ~$name - $i.default= $default - $i.type= $type - $i.desc=$description $range""" - ).substitute(param, range=range, i=i), file=f) - print("}\n}\n# End of autogenerated section. You may edit below.", file=f) - f.close() + with open(os.path.join(self.binary_dir, "docs", self.msgname + ".wikidoc"), 'w') as f: + print(textwrap.dedent("""\ + # Autogenerated param section. Do not hand edit. + param { + group.0 { + name=Dynamically Reconfigurable Parameters + desc=See the [[dynamic_reconfigure]] package for details on dynamically reconfigurable parameters.""" + ), file=f) + i = -1 + for param in self.group.get_parameters(): + i = i + 1 + range = "" + try: + enum = eval(param['edit_method'])['enum'] + range = ", ".join(Template("$name ($value): $description").substitute(const) for const in enum) + range = "Possible values are: " + range + except: + if param['type'] == int_t or param['type'] == double_t: + range = Template("Range: $min to $max").substitute(param) + print(Template(textwrap.dedent("""\ + $i.name= ~$name + $i.default= $default + $i.type= $type + $i.desc=$description $range""" + )).substitute(param, range=range, i=i), file=f) + print("}\n}\n# End of autogenerated section. You may edit below.", file=f) def generateusage(self): self.mkdirabs("docs") - f = open(os.path.join(self.binary_dir, "docs", self.msgname + "-usage.dox"), 'w') - # print("/**", file=f) - print("\\subsubsection usage Usage", file=f) - print('\\verbatim', file=f) - print(Template('').substitute( - pkgname=self.pkgname, nodename=self.nodename), file=f) - for param in self.group.get_parameters(): - print(Template(' ').substitute(param), file=f) - print('', file=f) - print('\\endverbatim', file=f) - print('', file=f) - # print("*/", file=f) - f.close() + with open(os.path.join(self.binary_dir, "docs", self.msgname + "-usage.dox"), 'w') as f: + # print("/**", file=f) + print("\\subsubsection usage Usage", file=f) + print('\\verbatim', file=f) + print(Template('').substitute( + pkgname=self.pkgname, nodename=self.nodename), file=f) + for param in self.group.get_parameters(): + print(Template(' ').substitute(param), file=f) + print('', file=f) + print('\\endverbatim', file=f) + print('', file=f) + # print("*/", file=f) def generatedoc(self): self.mkdirabs("docs") dir_path = os.path.join(self.binary_dir, "docs") self.mkdirabs(dir_path) - f = open(os.path.join(dir_path, self.msgname + ".dox"), 'w') - # print("/**", file=f) - print("\\subsubsection parameters ROS parameters", file=f) - print("", file=f) - print("Reads and maintains the following parameters on the ROS server", file=f) - print("", file=f) - for param in self.group.get_parameters(): - print(Template("- \\b \"~$name\" : \\b [$type] $description min: $min, default: $default, max: $max").substitute(param), file=f) - print("", file=f) - # print("*/", file=f) - f.close() + with open(os.path.join(dir_path, self.msgname + ".dox"), 'w') as f: + # print("/**", file=f) + print("\\subsubsection parameters ROS parameters", file=f) + print("", file=f) + print("Reads and maintains the following parameters on the ROS server", file=f) + print("", file=f) + for param in self.group.get_parameters(): + print(Template("- \\b \"~$name\" : \\b [$type] $description min: $min, default: $default, max: $max").substitute(param), file=f) + print("", file=f) + # print("*/", file=f) def generateusage(self): self.mkdirabs("docs") - f = open(os.path.join(self.binary_dir, "docs", self.msgname + "-usage.dox"), 'w') - # print("/**", file=f) - print("\\subsubsection usage Usage", file=f) - print('\\verbatim', file=f) - print(Template('').substitute( - pkgname=self.pkgname, nodename=self.nodename), file=f) - for param in self.group.get_parameters(): - print(Template(' ').substitute(param), file=f) - print('', file=f) - print('\\endverbatim', file=f) - print("", file=f) - # print("*/", file=f) - f.close() + with open(os.path.join(self.binary_dir, "docs", self.msgname + "-usage.dox"), 'w') as f: + # print("/**", file=f) + print("\\subsubsection usage Usage", file=f) + print('\\verbatim', file=f) + print(Template('').substitute( + pkgname=self.pkgname, nodename=self.nodename), file=f) + for param in self.group.get_parameters(): + print(Template(' ').substitute(param), file=f) + print('', file=f) + print('\\endverbatim', file=f) + print("", file=f) + # print("*/", file=f) def crepr(self, param, val): type = param["type"] if type == 'str': - return '"' + val + '"' + return cquote(val) if type in ['int', 'double']: return str(val) if type == 'bool': @@ -441,12 +442,15 @@ def crepr(self, param, val): # if 'float' in types: # return str(val) - def appendline(self, list, text, param, value=None): + def appendline(self, list, text, param, value=None, **kwarg): if value is None: val = "" else: val = self.crepr(param, param[value]) - list.append(Template('${doline} $srcline "$srcfile"\n ' + text).safe_substitute(param, v=val, doline=LINEDEBUG, configname=self.name)) + list.append( + Template('${doline} $srcline "$srcfile"\n ' + text) + .safe_substitute(param, v=val, doline=LINEDEBUG, configname=self.name, **kwarg + )) def appendgroup(self, list, group): subgroups = [] @@ -470,16 +474,15 @@ def generatecpp(self): templatelines = [] templatefilesafe = templatefile.replace('\\', '\\\\') # line directive does backslash expansion. curline = 1 - f = open(templatefile) - for line in f: - curline = curline + 1 - templatelines.append(Template(line).safe_substitute(linenum=curline, filename=templatefilesafe)) - f.close() + with open(templatefile) as f: + for line in f: + curline = curline + 1 + templatelines.append(Template(line).safe_substitute(linenum=curline, filename=templatefilesafe)) + template = ''.join(templatelines) # Write the configuration manipulator. self.mkdirabs(self.cpp_gen_dir) - f = open(os.path.join(self.cpp_gen_dir, self.name + "Config.h"), 'w') paramdescr = [] groups = [] members = [] @@ -501,11 +504,17 @@ def write_params(group): paramdescr, group.to_dict()['name'] + ".abstract_parameters.push_back(${configname}Config::AbstractParamDescriptionConstPtr(new ${configname}Config::ParamDescription<${ctype}>(\"${name}\", \"${type}\", ${level}, " - "\"${description}\", \"${edit_method}\", &${configname}Config::${name})));", param) + "${description}, ${edit_method}, &${configname}Config::${name})));", + param, + description=cquote(param['description']), + edit_method=cquote(param['edit_method'])) self.appendline( paramdescr, "__param_descriptions__.push_back(${configname}Config::AbstractParamDescriptionConstPtr(new ${configname}Config::ParamDescription<${ctype}>(\"${name}\", \"${type}\", ${level}, " - "\"${description}\", \"${edit_method}\", &${configname}Config::${name})));", param) + "${description}, ${edit_method}, &${configname}Config::${name})));", + param, + description=cquote(param['description']), + edit_method=cquote(param['edit_method'])) for g in group.groups: write_params(g) @@ -524,11 +533,11 @@ def write_params(group): members = '\n'.join(members) constants = '\n'.join(constants) groups = '\n'.join(groups) - f.write(Template(template).substitute( - uname=self.name.upper(), - configname=self.name, pkgname=self.pkgname, paramdescr=paramdescr, - members=members, groups=groups, doline=LINEDEBUG, constants=constants)) - f.close() + with open(os.path.join(self.cpp_gen_dir, self.name + "Config.h"), 'w') as f: + f.write(Template(template).substitute( + uname=self.name.upper(), + configname=self.name, pkgname=self.pkgname, paramdescr=paramdescr, + members=members, groups=groups, doline=LINEDEBUG, constants=constants)) print("Wrote header file in " + os.path.join(self.cpp_gen_dir, self.name + "Config.h")) # def deleteoneobsolete(self, file): @@ -602,22 +611,20 @@ def replace_infinity(self, config_dict): def generatepy(self): # Read the configuration manipulator template and insert line numbers and file name into template. templatefile = os.path.join(self.dynconfpath, "templates", "ConfigType.py.template") - f = open(templatefile) - template = f.read() - f.close() + with open(templatefile) as f: + template = f.read() # Write the configuration manipulator. self.mkdirabs(os.path.join(self.py_gen_dir, "cfg")) - f = open(os.path.join(self.py_gen_dir, "cfg", self.name + "Config.py"), 'w') - pycfgdata = self.replace_infinity(self.group.to_dict()) - f.write(Template(template).substitute( - name=self.name, - pkgname=self.pkgname, pycfgdata=pycfgdata)) - for const in self.constants: - f.write(Template("${configname}_${name} = $v\n").substitute( - const, v=repr(const['value']), - configname=self.name)) - f.close() - - f = open(os.path.join(self.py_gen_dir, "cfg", "__init__.py"), 'a') - f.close() + with open(os.path.join(self.py_gen_dir, "cfg", self.name + "Config.py"), 'w') as f: + pycfgdata = self.replace_infinity(self.group.to_dict()) + f.write(Template(template).substitute( + name=self.name, + pkgname=self.pkgname, pycfgdata=pycfgdata)) + for const in self.constants: + f.write(Template("${configname}_${name} = $v\n").substitute( + const, v=repr(const['value']), + configname=self.name)) + + with open(os.path.join(self.py_gen_dir, "cfg", "__init__.py"), 'a'): + pass