Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Various fixes #98

Open
wants to merge 5 commits into
base: melodic-devel
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions cfg/Test.cfg
Original file line number Diff line number Diff line change
@@ -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)
243 changes: 125 additions & 118 deletions src/dynamic_reconfigure/parameter_generator_catkin.py
Original file line number Diff line number Diff line change
@@ -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<double>::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('<node name="$nodename" pkg="$pkgname" type="$nodename">').substitute(
pkgname=self.pkgname, nodename=self.nodename), file=f)
for param in self.group.get_parameters():
print(Template(' <param name="$name" type="$type" value="$default" />').substitute(param), file=f)
print('</node>', 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('<node name="$nodename" pkg="$pkgname" type="$nodename">').substitute(
pkgname=self.pkgname, nodename=self.nodename), file=f)
for param in self.group.get_parameters():
print(Template(' <param name="$name" type="$type" value="$default" />').substitute(param), file=f)
print('</node>', 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('<node name="$nodename" pkg="$pkgname" type="$nodename">').substitute(
pkgname=self.pkgname, nodename=self.nodename), file=f)
for param in self.group.get_parameters():
print(Template(' <param name="$name" type="$type" value="$default" />').substitute(param), file=f)
print('</node>', 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('<node name="$nodename" pkg="$pkgname" type="$nodename">').substitute(
pkgname=self.pkgname, nodename=self.nodename), file=f)
for param in self.group.get_parameters():
print(Template(' <param name="$name" type="$type" value="$default" />').substitute(param), file=f)
print('</node>', 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