generate_message_processors.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 # Software License Agreement (BSD)
3 #
4 # @author Paul Bovbel <pbovbel@clearpath.ai>
5 # @copyright (c) 2016, Clearpath Robotics, Inc., All rights reserved.
6 #
7 # Redistribution and use in source and binary forms, with or without modification, are permitted provided that
8 # the following conditions are met:
9 # * Redistributions of source code must retain the above copyright notice, this list of conditions and the
10 # following disclaimer.
11 # * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
12 # following disclaimer in the documentation and/or other materials provided with the distribution.
13 # * Neither the name of Clearpath Robotics nor the names of its contributors may be used to endorse or
14 # promote products derived from this software without specific prior written permission.
15 #
16 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
17 # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
18 # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
19 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
20 # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
22 # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
23 # POSSIBILITY OF SUCH DAMAGE.
24 import argparse
25 import re
26 import pprint
27 
28 from Cheetah.Template import Template
29 from roslib.message import get_message_class, get_service_class
30 from collections import namedtuple
31 
32 pp = pprint.PrettyPrinter(indent=1)
33 
34 
35 # Convert python field notation (poses[].header.frame_id) into C++ container iterations.
36 def generate_cpp_iteration(field, default_accessor, processor):
37  accessor = default_accessor
38  prefix = str()
39  suffix = str()
40  indent_level = 1
41 
42  # TODO move these templates out of code and into Cheetah template
43  prefix_template = '{0}for(auto {1} : {2}) \n{0}{{\n'
44  suffix_template = '{0}}}\n'
45  result_template = '{0}->process({1}{2});\n'
46 
47  # Iterate over array fields, and unroll into container iterations
48  while (field.count('[].') > 0):
49  splitfield = field.split('[].', 1)
50 
51  container = accessor + splitfield[0]
52  accessor = splitfield[0].split('.')[-1].rstrip('s')+get_accessor(indent_level)
53  field = ''.join(splitfield[1:])
54 
55  prefix = prefix + prefix_template.format(
56  format_indent(indent_level), accessor.rstrip(get_accessor(indent_level)), container)
57  suffix = suffix_template.format(format_indent(indent_level)) + suffix
58  indent_level += 1
59 
60  inset = format_indent(indent_level) + result_template.format(processor, accessor, field)
61 
62  result = prefix + inset + suffix
63  return result
64 
65 
66 # Provide indentation as required
67 def format_indent(indent_level):
68  return ''.join([' ' for s in xrange(indent_level)])
69 
70 
71 # Get C++ style accessor. Pointers only live at the base indent level.
72 def get_accessor(indent_level):
73  if indent_level == 0:
74  return '->'
75  else:
76  return '.'
77 
78 
79 # Recursively search through the message/service class for fields with a name and/or type
80 # Match by regex pattern.
81 def find_fields(msg_srv_class, field_name_pattern=None, field_type_pattern=None):
82  fields = []
83 
84  if field_name_pattern is None:
85  field_name_pattern = ".*"
86  if field_type_pattern is None:
87  field_type_pattern = ".*"
88 
89  name_regex = re.compile(field_name_pattern)
90  type_regex = re.compile(field_type_pattern)
91 
92  # ROS msg/srv creates python definitions via __slots__
93  if hasattr(msg_srv_class, '__slots__'):
94  for (field_name, field_type) in zip(msg_srv_class.__slots__, msg_srv_class._slot_types):
95  # If this field is a frame ID, add it to the output list
96  if name_regex.match(field_name) is not None and type_regex.match(field_type) is not None:
97  fields.append(field_name)
98 
99  elif not is_primitive_msg(field_type):
100  # If this field is another ROS message type, look inside it for more frame IDs
101  if is_msg_array(field_type):
102  # If this field is a message array,
103  field_name += "[]"
104  field_type = field_type.rstrip("[]")
105  child_fields = find_fields(get_message_class(field_type), field_name_pattern, field_type_pattern)
106  for child_field in child_fields:
107  fields.append("{0}.{1}".format(field_name, child_field))
108 
109  return fields
110 
111 
112 def is_primitive_msg(field_type):
113  return field_type.find('/') == -1
114 
115 
116 def is_msg_array(field_type):
117  return field_type[-2:] == '[]'
118 
119 
120 if __name__ == "__main__":
121  parser = argparse.ArgumentParser(description='Generate message processor headers and sources.')
122  parser.add_argument('pkg_name', metavar='package_name', help='Package Name')
123  parser.add_argument('--msg-names', metavar='*.msg', nargs='*', help='Message file paths')
124  parser.add_argument('--srv-names', metavar='*.srv', nargs='*', help='Service file paths')
125  parser.add_argument('--cpp-tmpl', metavar='*.cpp.tmpl', help='Source template file')
126  parser.add_argument('--h-tmpl', metavar='*.h.tmpl', help='Header template file')
127  parser.add_argument('--cpp-out', metavar='*.cpp', help='Output source file')
128  parser.add_argument('--h-out', metavar='*.h', help='Output header file')
129 
130  args = parser.parse_args()
131  if not args.cpp_out:
132  args.cpp_out = args.pkg_name + "_message_processor.cpp"
133  if not args.h_out:
134  args.h_out = args.pkg_name + "_message_processor.h"
135 
136  # Generator configuration
137  FIELD_NAME_FILTER = 'field_name_filter'
138  FIELD_TYPE_FILTER = 'field_type_filter'
139  MESSAGES = 'msgs'
140  SERVICES = 'srvs'
141 
142  ServiceComponent = namedtuple("ServiceComponent", "name accessor member_class")
143  SERVICE_COMPONENTS = [ServiceComponent(name="req", accessor="req.", member_class="_request_class"),
144  ServiceComponent(name="res", accessor="res.", member_class="_response_class")]
145 
146  # Define processors to generate. For example:
147  # frame_id_processor:
148  # - References C++ class message_relay::FrameIdProcessor
149  # - Captures fields with a name that contains 'frame_id', and are of type 'string'
150  # time_processor:
151  # - References C++ class message_relay::TimeProcessor
152  # - Captures fields of type 'time'
153  processors = {
154  'frame_id_processor': {
155  FIELD_NAME_FILTER: '.*frame_id.*',
156  FIELD_TYPE_FILTER: 'string',
157  },
158  'time_processor': {
159  FIELD_NAME_FILTER: None,
160  FIELD_TYPE_FILTER: 'time',
161  },
162  }
163 
164  for processor_name, processor in processors.iteritems():
165  processor[MESSAGES] = {}
166  for msg_name in args.msg_names:
167  msg_base = msg_name.partition('/')[2]
168  msg_class = get_message_class(msg_name)
169  fields = find_fields(msg_srv_class=msg_class, field_name_pattern=processor[FIELD_NAME_FILTER],
170  field_type_pattern=processor[FIELD_TYPE_FILTER])
171  field_processors = [generate_cpp_iteration(field, 'msg->', processor_name) for field in fields]
172  processor[MESSAGES][msg_base] = field_processors
173 
174  # Generate code for processing service request and responses
175  processor[SERVICES] = {}
176  for srv_name in args.srv_names:
177  srv_base = srv_name.partition('/')[2]
178  srv_class = get_service_class(srv_name)
179  processor[SERVICES][srv_base] = {}
180 
181  for component in SERVICE_COMPONENTS:
182  if hasattr(srv_class, component.member_class):
183  fields = find_fields(msg_srv_class=getattr(srv_class, component.member_class),
184  field_name_pattern=processor[FIELD_NAME_FILTER],
185  field_type_pattern=processor[FIELD_TYPE_FILTER])
186  field_processors = [generate_cpp_iteration(field, component.accessor, processor_name) for
187  field in fields]
188  processor[SERVICES][srv_base][component.name] = field_processors
189 
190  template_namespace = {}
191  template_namespace['processors'] = processors
192  template_namespace['pkg_name'] = args.pkg_name
193  template_namespace[MESSAGES] = [msg_name.partition('/')[2] for msg_name in args.msg_names]
194  template_namespace[SERVICES] = [srv_name.partition('/')[2] for srv_name in args.srv_names]
195 
196  # Fill .cpp and .h template with generated message processors
197  pp.pprint("Generating templates for package " + args.pkg_name)
198  with open(args.cpp_tmpl, 'r') as f:
199  source_template = Template(f.read(), searchList=[template_namespace])
200  with open(args.h_tmpl, 'r') as f:
201  header_template = Template(f.read(), searchList=[template_namespace])
202 
203  with open(args.cpp_out, 'w') as f:
204  f.write(str(source_template))
205  with open(args.h_out, 'w') as f:
206  f.write(str(header_template))
def generate_cpp_iteration(field, default_accessor, processor)
def find_fields(msg_srv_class, field_name_pattern=None, field_type_pattern=None)


message_relay
Author(s):
autogenerated on Wed Jul 17 2019 03:27:53