rosruby 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. data/bin/rubyroscore +5 -0
  2. data/lib/ros.rb +25 -0
  3. data/lib/ros/duration.rb +63 -0
  4. data/lib/ros/graph_manager.rb +408 -0
  5. data/lib/ros/log.rb +72 -0
  6. data/lib/ros/master.rb +408 -0
  7. data/lib/ros/master_proxy.rb +256 -0
  8. data/lib/ros/message.rb +65 -0
  9. data/lib/ros/name.rb +88 -0
  10. data/lib/ros/node.rb +442 -0
  11. data/lib/ros/package.rb +144 -0
  12. data/lib/ros/parameter_manager.rb +127 -0
  13. data/lib/ros/parameter_subscriber.rb +47 -0
  14. data/lib/ros/publisher.rb +96 -0
  15. data/lib/ros/rate.rb +41 -0
  16. data/lib/ros/ros.rb +10 -0
  17. data/lib/ros/roscore.rb +29 -0
  18. data/lib/ros/service.rb +37 -0
  19. data/lib/ros/service_client.rb +83 -0
  20. data/lib/ros/service_server.rb +92 -0
  21. data/lib/ros/slave_proxy.rb +153 -0
  22. data/lib/ros/subscriber.rb +119 -0
  23. data/lib/ros/tcpros/client.rb +108 -0
  24. data/lib/ros/tcpros/header.rb +89 -0
  25. data/lib/ros/tcpros/message.rb +74 -0
  26. data/lib/ros/tcpros/server.rb +137 -0
  27. data/lib/ros/tcpros/service_client.rb +104 -0
  28. data/lib/ros/tcpros/service_server.rb +132 -0
  29. data/lib/ros/time.rb +109 -0
  30. data/lib/ros/topic.rb +47 -0
  31. data/lib/ros/xmlrpcserver.rb +40 -0
  32. data/samples/add_two_ints_client.rb +25 -0
  33. data/samples/add_two_ints_server.rb +20 -0
  34. data/samples/gui.rb +126 -0
  35. data/samples/sample_log.rb +16 -0
  36. data/samples/sample_param.rb +20 -0
  37. data/samples/sample_publisher.rb +20 -0
  38. data/samples/sample_subscriber.rb +19 -0
  39. data/scripts/genmsg_ruby.py +1135 -0
  40. data/scripts/genmsg_ruby.pyc +0 -0
  41. data/scripts/gensrv_ruby.py +105 -0
  42. data/scripts/gensrv_ruby.pyc +0 -0
  43. data/scripts/rosruby_genmsg.py +67 -0
  44. data/scripts/run-test.rb +21 -0
  45. data/test/test_header.rb +36 -0
  46. data/test/test_log.rb +45 -0
  47. data/test/test_master_proxy.rb +73 -0
  48. data/test/test_message.rb +13 -0
  49. data/test/test_node.rb +166 -0
  50. data/test/test_package.rb +10 -0
  51. data/test/test_param.rb +27 -0
  52. data/test/test_pubsub.rb +154 -0
  53. data/test/test_rate.rb +16 -0
  54. data/test/test_service.rb +34 -0
  55. data/test/test_slave_proxy.rb +49 -0
  56. data/test/test_time.rb +39 -0
  57. metadata +170 -0
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'ros'
4
+
5
+ def main
6
+ node = ROS::Node.new('/hoge')
7
+ sleep(1)
8
+ node.loginfo('some information')
9
+ node.logdebug('some debug information')
10
+ node.logerr('some error information')
11
+ node.logerror('some error information')
12
+ node.logfatal('some fatal information')
13
+ sleep(1)
14
+ end
15
+
16
+ main
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'ros'
4
+ require 'std_msgs/String'
5
+
6
+ def main
7
+ node = ROS::Node.new('/rosruby/sample_publisher')
8
+ publisher = node.advertise('/chatter', Std_msgs::String)
9
+ sleep(1)
10
+ msg = Std_msgs::String.new
11
+
12
+ while node.ok?
13
+ msg.data = "local param = #{node.get_param('~message')}, global = #{node.get_param('/message')}"
14
+ publisher.publish(msg)
15
+ node.loginfo(msg.data)
16
+ sleep (1.0)
17
+ end
18
+ end
19
+
20
+ main
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'ros'
4
+ require 'std_msgs/String'
5
+
6
+ def main
7
+ node = ROS::Node.new('/rosruby/sample_publisher')
8
+ publisher = node.advertise('/chatter', Std_msgs::String)
9
+ sleep(1)
10
+ msg = Std_msgs::String.new
11
+ i = 0
12
+ while node.ok?
13
+ msg.data = "Hello, rosruby!: #{i}"
14
+ publisher.publish(msg)
15
+ sleep(1.0)
16
+ i += 1
17
+ end
18
+ end
19
+
20
+ main
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'ros'
4
+ require 'std_msgs/String'
5
+
6
+ def main
7
+ node = ROS::Node.new('/rosruby/sample_subscriber')
8
+ node.subscribe('/chatter', Std_msgs::String) do |msg|
9
+ puts "message come! = \'#{msg.data}\'"
10
+ end
11
+
12
+ while node.ok?
13
+ node.spin_once
14
+ sleep(1)
15
+ end
16
+
17
+ end
18
+
19
+ main
@@ -0,0 +1,1135 @@
1
+ #! /usr/bin/env python
2
+ # Software License Agreement (BSD License)
3
+ #
4
+ # Copyright (c) 2012, Takashi Ogura
5
+ #
6
+ # based on
7
+ #
8
+ # Copyright (c) 2008, Willow Garage, Inc.
9
+ # All rights reserved.
10
+ #
11
+ # Redistribution and use in source and binary forms, with or without
12
+ # modification, are permitted provided that the following conditions
13
+ # are met:
14
+ #
15
+ # * Redistributions of source code must retain the above copyright
16
+ # notice, this list of conditions and the following disclaimer.
17
+ # * Redistributions in binary form must reproduce the above
18
+ # copyright notice, this list of conditions and the following
19
+ # disclaimer in the documentation and/or other materials provided
20
+ # with the distribution.
21
+ # * Neither the name of Willow Garage, Inc. nor the names of its
22
+ # contributors may be used to endorse or promote products derived
23
+ # from this software without specific prior written permission.
24
+ #
25
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
27
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
28
+ # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
29
+ # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
30
+ # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
31
+ # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
32
+ # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
33
+ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34
+ # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
35
+ # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36
+ # POSSIBILITY OF SUCH DAMAGE.
37
+ #
38
+ # Revision $Id:$
39
+
40
+ """
41
+ Library for Ruby message generation.
42
+
43
+ The structure of the serialization descends several levels of serializers:
44
+ - msg_generator: generator for an individual msg file
45
+ - serialize_fn_generator: generator for msg.serialize()
46
+ - serializer_generator
47
+ - field-type-specific serializers
48
+ - deserialize_fn_generator: generator for msg.deserialize()
49
+ - serializer_generator
50
+ - field-type-specific serializers
51
+ """
52
+
53
+ # NOTE: genpy must be in the roslib package as placing it in rospy
54
+ # creates circular deps
55
+
56
+ import os
57
+ import shutil
58
+ import atexit
59
+ import itertools
60
+ import sys
61
+ import tempfile
62
+ import traceback
63
+ import struct
64
+
65
+ try:
66
+ from cStringIO import StringIO # Python 2.x
67
+ except ImportError:
68
+ from io import StringIO # Python 3.x
69
+
70
+ import roslib.exceptions
71
+ import roslib.gentools
72
+ import roslib.msgs
73
+ import roslib.packages #for get_pkg_dir
74
+
75
+ # indent width
76
+ INDENT = ' '
77
+
78
+ class MsgGenerationException(roslib.exceptions.ROSLibException):
79
+ """
80
+ Exception type for errors in roslib.genpy
81
+ """
82
+ pass
83
+
84
+ def get_registered_ex(type_):
85
+ """
86
+ wrapper for roslib.msgs.get_registered that wraps unknown types with a MsgGenerationException
87
+ @param type_: ROS message type
88
+ @type type_: str
89
+ """
90
+ try:
91
+ return roslib.msgs.get_registered(type_)
92
+ except KeyError:
93
+ raise MsgGenerationException("Unknown type [%s]. Please check that the manifest.xml correctly declares dependencies."%type_)
94
+
95
+ ################################################################################
96
+ # Primitive type handling for ROS builtin types
97
+
98
+ PYTHON_RUBY_DICT = { #see python module struct
99
+ 'b': 'c',
100
+ 'B': 'C',
101
+ 'h' : 's',
102
+ 'H' : 'S',
103
+ 'i' : 'l',
104
+ 'I' : 'L',
105
+ 'q' : 'q',
106
+ 'Q' : 'Q',
107
+ 'f': 'f',
108
+ 'd': 'd'
109
+ }
110
+
111
+ SIMPLE_TYPES_DICT = { #see python module struct
112
+ 'int8': 'b',
113
+ 'uint8': 'B',
114
+ # Python 2.6 adds in '?' for C99 _Bool, which appears equivalent to an uint8,
115
+ # thus, we use uint8
116
+ 'bool': 'B',
117
+ 'int16' : 'h',
118
+ 'uint16' : 'H',
119
+ 'int32' : 'i',
120
+ 'uint32' : 'I',
121
+ 'int64' : 'q',
122
+ 'uint64' : 'Q',
123
+ 'float32': 'f',
124
+ 'float64': 'd',
125
+ # deprecated
126
+ 'char' : 'B', #unsigned
127
+ 'byte' : 'b', #signed
128
+ }
129
+
130
+ ## Simple types are primitives with fixed-serialization length
131
+ SIMPLE_TYPES = list(SIMPLE_TYPES_DICT.keys()) #py3k
132
+
133
+ def is_simple(type_):
134
+ """
135
+ @return bool: True if type is a 'simple' type, i.e. is of
136
+ fixed/known serialization length. This is effectively all primitive
137
+ types except for string
138
+ @rtype: bool
139
+ """
140
+ return type_ in SIMPLE_TYPES
141
+
142
+ def is_special(type_):
143
+ """
144
+ @return True if type_ is a special type (i.e. builtin represented as a class instead of a primitive)
145
+ @rtype: bool
146
+ """
147
+ return type_ in _SPECIAL_TYPES
148
+
149
+ def get_special(type_):
150
+ """
151
+ @return: special type handler for type_ or None
152
+ @rtype: L{Special}
153
+ """
154
+ return _SPECIAL_TYPES.get(type_, None)
155
+
156
+ ################################################################################
157
+ # Special type handling for ROS builtin types that are not primitives
158
+
159
+ class Special:
160
+
161
+ def __init__(self, constructor, post_deserialize, import_str):
162
+ """
163
+ @param constructor: expression to instantiate new type instance for deserialization
164
+ @type constructor: str
165
+ @param post_Deserialize: format string for expression to evaluate on type instance after deserialization is complete.
166
+ @type post_Deserialize: str
167
+ variable name will be passed in as the single argument to format string.
168
+ @param import_str: import to include if type is present
169
+ @type import_str: str
170
+ """
171
+ self.constructor = constructor
172
+ self.post_deserialize = post_deserialize
173
+ self.import_str = import_str
174
+
175
+ def get_post_deserialize(self, varname):
176
+ """
177
+ @return: Post-deserialization code to executed (unindented) or
178
+ None if no post-deserialization is required
179
+ @rtype: str
180
+ """
181
+ if self.post_deserialize:
182
+ return self.post_deserialize%varname
183
+ else:
184
+ return None
185
+
186
+ _SPECIAL_TYPES = {
187
+ roslib.msgs.HEADER: Special('Std_msgs::Header.new', None, 'require "std_msgs/Header"'),
188
+ roslib.msgs.TIME: Special('ROS::Time.new', None, 'require "ros/time"'),
189
+ roslib.msgs.DURATION: Special('ROS::Duration.new', None, 'require "ros/duration"'),
190
+ }
191
+
192
+ ################################################################################
193
+ # utilities
194
+
195
+ # #671
196
+ def default_value(field_type, default_package):
197
+ """
198
+ Compute default value for field_type
199
+ @param default_package: default package
200
+ @type default_package: str
201
+ @param field_type str: ROS .msg field type
202
+ @type field_type: ROS .msg field type
203
+ @return: default value encoded in Python string representation
204
+ @rtype: str
205
+ """
206
+ if field_type in ['byte', 'int8', 'int16', 'int32', 'int64',\
207
+ 'char', 'uint8', 'uint16', 'uint32', 'uint64']:
208
+ return '0'
209
+ elif field_type in ['float32', 'float64']:
210
+ return '0.0'
211
+ elif field_type == 'string':
212
+ # strings, char[], and uint8s are all optimized to be strings
213
+ return "''"
214
+ elif field_type == 'bool':
215
+ return 'false'
216
+ elif field_type.endswith(']'): # array type
217
+ base_type, is_array, array_len = roslib.msgs.parse_type(field_type)
218
+ if base_type in ['char', 'uint8']:
219
+ # strings, char[], and uint8s are all optimized to be strings
220
+ if array_len is not None:
221
+ return "[0].pack('c')*%s"%array_len
222
+ else:
223
+ return "''"
224
+ elif array_len is None: #var-length
225
+ return '[]'
226
+ else: # fixed-length, fill values
227
+ def_val = default_value(base_type, default_package)
228
+ return '[' + ','.join(itertools.repeat(def_val, array_len)) + ']'
229
+ else:
230
+ return compute_constructor(default_package, field_type)
231
+
232
+ def flatten(msg):
233
+ """
234
+ Flattens the msg spec so that embedded message fields become
235
+ direct references. The resulting MsgSpec isn't a true/legal
236
+ L{MsgSpec} and should only be used for serializer generation
237
+ @param msg: msg to flatten
238
+ @type msg: L{MsgSpec}
239
+ @return: flatten message
240
+ @rtype: L{MsgSpec}
241
+ """
242
+ new_types = []
243
+ new_names = []
244
+ for t, n in zip(msg.types, msg.names):
245
+ #flatten embedded types - note: bug #59
246
+ if roslib.msgs.is_registered(t):
247
+ msg_spec = flatten(roslib.msgs.get_registered(t))
248
+ new_types.extend(msg_spec.types)
249
+ for n2 in msg_spec.names:
250
+ new_names.append(n+'.'+n2)
251
+ else:
252
+ #I'm not sure if it's a performance win to flatten fixed-length arrays
253
+ #as you get n __getitems__ method calls vs. a single *array call
254
+ new_types.append(t)
255
+ new_names.append(n)
256
+ return roslib.msgs.MsgSpec(new_types, new_names, msg.constants, msg.text)
257
+
258
+ def make_ruby_safe(spec):
259
+ """
260
+ Remap field/constant names in spec to avoid collision with Python reserved words.
261
+ @param spec: msg spec to map to new, python-safe field names
262
+ @type spec: L{MsgSpec}
263
+ @return: python-safe message specification
264
+ @rtype: L{MsgSpec}
265
+ """
266
+ new_c = [roslib.msgs.Constant(c.type, _remap_reserved(c.name), c.val, c.val_text) for c in spec.constants]
267
+ return roslib.msgs.MsgSpec(spec.types, [_remap_reserved(n) for n in spec.names], new_c, spec.text)
268
+
269
+ def _remap_reserved(field_name):
270
+ """
271
+ Map field_name to a python-safe representation, if necessary
272
+ @param field_name: msg field name
273
+ @type field_name: str
274
+ @return: remapped name
275
+ @rtype: str
276
+ """
277
+ # include 'self' as well because we are within a class instance
278
+ if field_name in ['BEGIN', 'END', 'alias', 'and', 'begin', 'break', 'case', 'class', 'def', 'defined?', 'do', 'else', 'elsif', 'end', 'ensure', 'false', 'for', 'if', 'in', 'module', 'next', 'nil', 'not', 'or', 'redo', 'rescue', 'retry', 'return', 'self', 'super', 'then', 'true', 'undef', 'unless', 'until', 'when', 'while', 'yield']:
279
+ return field_name + "_"
280
+ return field_name
281
+
282
+ ################################################################################
283
+ # (de)serialization routines
284
+
285
+ def compute_struct_pattern(types):
286
+ """
287
+ @param types: type names
288
+ @type types: [str]
289
+ @return: format string for struct if types are all simple. Otherwise, return None
290
+ @rtype: str
291
+ """
292
+ if not types: #important to filter None and empty first
293
+ return None
294
+ try:
295
+ return ''.join([SIMPLE_TYPES_DICT[t] for t in types])
296
+ except:
297
+ return None
298
+
299
+ def compute_post_deserialize(type_, varname):
300
+ """
301
+ Compute post-deserialization code for type_, if necessary
302
+ @return: code to execute post-deserialization (unindented), or None if not necessary.
303
+ @rtype: str
304
+ """
305
+ s = get_special(type_)
306
+ if s is not None:
307
+ return s.get_post_deserialize(varname)
308
+
309
+ def compute_constructor(package, type_):
310
+ """
311
+ Compute python constructor expression for specified message type implementation
312
+ @param package str: package that type is being imported into. Used
313
+ to resolve type_ if package is not specified.
314
+ @type package: str
315
+ @param type_: message type
316
+ @type type_: str
317
+ """
318
+ if is_special(type_):
319
+ return get_special(type_).constructor
320
+ else:
321
+ base_pkg, base_type_ = compute_pkg_type(package, type_)
322
+ if not roslib.msgs.is_registered("%s/%s"%(base_pkg,base_type_)):
323
+ return None
324
+ else:
325
+ return '%s::%s.new'%(base_pkg.capitalize(), base_type_)
326
+
327
+ def compute_pkg_type(package, type_):
328
+ """
329
+ @param package: package that type is being imported into
330
+ @type package: str
331
+ @param type: message type (package resource name)
332
+ @type type: str
333
+ @return (str, str): python package and type name
334
+ """
335
+ splits = type_.split(roslib.msgs.SEP)
336
+ if len(splits) == 1:
337
+ return package, splits[0]
338
+ elif len(splits) == 2:
339
+ return tuple(splits)
340
+ else:
341
+ raise MsgGenerationException("illegal message type: %s"%type_)
342
+
343
+ def compute_import(package, type_):
344
+ """
345
+ Compute python import statement for specified message type implementation
346
+ @param package: package that type is being imported into
347
+ @type package: str
348
+ @param type_: message type (package resource name)
349
+ @type type_: str
350
+ @return: list of import statements (no newline) required to use type_ from package
351
+ @rtype: [str]
352
+ """
353
+ # orig_base_type is the unresolved type
354
+ orig_base_type = roslib.msgs.base_msg_type(type_) # strip array-suffix
355
+ # resolve orig_base_type based on the current package context.
356
+ # base_type is the resolved type stripped of any package name.
357
+ # pkg is the actual package of type_.
358
+ pkg, base_type = compute_pkg_type(package, orig_base_type)
359
+ type_str = "%s/%s"%(pkg, base_type) # compute fully-qualified type
360
+ # important: have to do is_builtin check first. We do this check
361
+ # against the unresolved type builtins/specials are never
362
+ # relative. This requires some special handling for Header, which has
363
+ # two names (Header and std_msgs/Header).
364
+ if roslib.msgs.is_builtin(orig_base_type) or \
365
+ roslib.msgs.is_header_type(orig_base_type):
366
+ # of the builtin types, only special types require import
367
+ # handling. we switch to base_type as special types do not
368
+ # include package names.
369
+ if is_special(base_type):
370
+ retval = [get_special(base_type).import_str]
371
+ else:
372
+ retval = []
373
+ elif not roslib.msgs.is_registered(type_str):
374
+ retval = []
375
+ else:
376
+ retval = ['require "%s/%s"'%(pkg, base_type)]
377
+ for t in get_registered_ex(type_str).types:
378
+ sub = compute_import(package, t)
379
+ retval.extend([x for x in sub if not x in retval])
380
+ return retval
381
+
382
+ def compute_full_text_escaped(gen_deps_dict):
383
+ """
384
+ Same as roslib.gentools.compute_full_text, except that the
385
+ resulting text is escaped to be safe for Python's triple-quote string
386
+ quoting
387
+
388
+ @param get_deps_dict: dictionary returned by get_dependencies call
389
+ @type get_deps_dict: dict
390
+ @return: concatenated text for msg/srv file and embedded msg/srv types. Text will be escaped for triple-quote
391
+ @rtype: str
392
+ """
393
+ msg_definition = roslib.gentools.compute_full_text(gen_deps_dict)
394
+ msg_definition = msg_definition.replace('"', '\\"')
395
+ return msg_definition
396
+
397
+ def reduce_pattern(pattern):
398
+ """
399
+ Optimize the struct format pattern.
400
+ @param pattern: struct pattern
401
+ @type pattern: str
402
+ @return: optimized struct pattern
403
+ @rtype: str
404
+ """
405
+ if not pattern or len(pattern) == 1 or '%' in pattern:
406
+ return pattern
407
+ prev = pattern[0]
408
+ count = 1
409
+ new_pattern = ''
410
+ nums = [str(i) for i in range(0, 9)]
411
+ for c in pattern[1:]:
412
+ if c == prev and not c in nums:
413
+ count += 1
414
+ else:
415
+ if count > 1:
416
+ new_pattern = new_pattern + str(count) + prev
417
+ else:
418
+ new_pattern = new_pattern + prev
419
+ prev = c
420
+ count = 1
421
+ if count > 1:
422
+ new_pattern = new_pattern + str(count) + c
423
+ else:
424
+ new_pattern = new_pattern + prev
425
+ return new_pattern
426
+
427
+ ## @param expr str: string python expression that is evaluated for serialization
428
+ ## @return str: python call to write value returned by expr to serialization buffer
429
+ def serialize(expr):
430
+ return "buff.write(%s)"%expr
431
+
432
+ # int32 is very common due to length serialization, so it is special cased
433
+ def int32_pack(var):
434
+ """
435
+ @param var: variable name
436
+ @type var: str
437
+ @return: struct packing code for an int32
438
+ """
439
+ return serialize('@@struct_L.pack(%s)'%var)
440
+
441
+ # int32 is very common due to length serialization, so it is special cased
442
+ def int32_unpack(var, buff):
443
+ """
444
+ @param var: variable name
445
+ @type var: str
446
+ @return: struct unpacking code for an int32
447
+ """
448
+ return '(%s,) = @@struct_L.unpack(%s)'%(var, buff)
449
+
450
+ #NOTE: '<' = little endian
451
+ def pack(pattern, vars):
452
+ """
453
+ create struct.pack call for when pattern is a string pattern
454
+ @param pattern: pattern for pack
455
+ @type pattern: str
456
+ @param vars: name of variables to pack
457
+ @type vars: str
458
+ """
459
+ # - store pattern in context
460
+ pattern = reduce_pattern(pattern)
461
+ add_pattern(pattern)
462
+ return serialize("@@struct_%s.pack(%s)"%(convert_to_ruby_pattern(pattern),
463
+ vars))
464
+ def pack2(pattern, vars):
465
+ """
466
+ create struct.pack call for when pattern is the name of a variable
467
+ @param pattern: name of variable storing string pattern
468
+ @type pattern: struct
469
+ @param vars: name of variables to pack
470
+ @type vars: str
471
+ """
472
+ return serialize("%s.pack(%s)"%(vars, pattern))
473
+ def unpack(var, pattern, buff):
474
+ """
475
+ create struct.unpack call for when pattern is a string pattern
476
+ @param var: name of variable to unpack
477
+ @type var: str
478
+ @param pattern: pattern for pack
479
+ @type pattern: str
480
+ @param buff: buffer to unpack from
481
+ @type buff: str
482
+ """
483
+ # - store pattern in context
484
+ pattern = reduce_pattern(pattern)
485
+ add_pattern(pattern)
486
+ return var + " = @@struct_%s.unpack(%s)"%(convert_to_ruby_pattern(pattern),
487
+ buff)
488
+ def unpack2(var, pattern, buff):
489
+ """
490
+ Create struct.unpack call for when pattern refers to variable
491
+ @param var: variable the stores the result of unpack call
492
+ @type var: str
493
+ @param pattern: name of variable that unpack will read from
494
+ @type pattern: str
495
+ @param buff: buffer that the unpack reads from
496
+ @type buff: StringIO
497
+ """
498
+ return "%s = %s.unpack(%s)"%(var, buff, pattern)
499
+
500
+ ################################################################################
501
+ # numpy support
502
+
503
+ # this could obviously be directly generated, but it's nice to abstract
504
+
505
+ ## maps ros msg types to numpy types
506
+ _NUMPY_DTYPE = {
507
+ 'float32': 'numpy.float32',
508
+ 'float64': 'numpy.float64',
509
+ 'bool': 'numpy.bool',
510
+ 'int8': 'numpy.int8',
511
+ 'int16': 'numpy.int16',
512
+ 'int32': 'numpy.int32',
513
+ 'int64': 'numpy.int64',
514
+ 'uint8': 'numpy.uint8',
515
+ 'uint16': 'numpy.uint16',
516
+ 'uint32': 'numpy.uint32',
517
+ 'uint64': 'numpy.uint64',
518
+ # deprecated type
519
+ 'char' : 'numpy.uint8',
520
+ 'byte' : 'numpy.int8',
521
+ }
522
+
523
+ ################################################################################
524
+ # (De)serialization generators
525
+
526
+ _serial_context = ''
527
+ _context_stack = []
528
+
529
+ _counter = 0
530
+ def next_var():
531
+ # we could optimize this by reusing vars once the context is popped
532
+ global _counter
533
+ _counter += 1
534
+ return '_v%s'%_counter
535
+
536
+ def push_context(context):
537
+ """
538
+ Push new variable context onto context stack. The context stack
539
+ manages field-reference context for serialization, e.g. 'self.foo'
540
+ vs. 'self.bar.foo' vs. 'var.foo'
541
+ """
542
+ global _serial_context, _context_stack
543
+ _context_stack.append(_serial_context)
544
+ _serial_context = context
545
+
546
+ def pop_context():
547
+ """
548
+ Pop variable context from context stack. The context stack manages
549
+ field-reference context for serialization, e.g. 'self.foo'
550
+ vs. 'self.bar.foo' vs. 'var.foo'
551
+ """
552
+ global _serial_context
553
+ _serial_context = _context_stack.pop()
554
+
555
+ _context_patterns = []
556
+ def add_pattern(p):
557
+ """
558
+ Record struct pattern that's been used for (de)serialization
559
+ """
560
+ _context_patterns.append(p)
561
+ def clear_patterns():
562
+ """
563
+ Clear record of struct pattern that have been used for (de)serialization
564
+ """
565
+ del _context_patterns[:]
566
+ def get_patterns():
567
+ """
568
+ @return: record of struct pattern that have been used for (de)serialization
569
+ """
570
+ return _context_patterns[:]
571
+
572
+ # These are the workhorses of the message generation. The generators
573
+ # are implemented as iterators, where each iteration value is a line
574
+ # of Python code. The generators will invoke underlying generators,
575
+ # using the context stack to manage any changes in variable-naming, so
576
+ # that code can be reused as much as possible.
577
+
578
+ def len_serializer_generator(var, is_string, serialize):
579
+ """
580
+ Generator for array-length serialization (32-bit, little-endian unsigned integer)
581
+ @param var: variable name
582
+ @type var: str
583
+ @param is_string: if True, variable is a string type
584
+ @type is_string: bool
585
+ @param serialize bool: if True, generate code for
586
+ serialization. Other, generate code for deserialization
587
+ @type serialize: bool
588
+ """
589
+ if serialize:
590
+ yield "length = %s.length"%var
591
+ # NOTE: it's more difficult to save a call to struct.pack with
592
+ # the array length as we are already using *array_val to pass
593
+ # into struct.pack as *args. Although it's possible that
594
+ # Python is not optimizing it, it is potentially worse for
595
+ # performance to attempt to combine
596
+ if not is_string:
597
+ yield int32_pack("length")
598
+ else:
599
+ yield "start = end_point"
600
+ yield "end_point += 4"
601
+ yield int32_unpack('length', 'str[start..(end_point-1)]') #4 = struct.calcsize('<i')
602
+
603
+ def string_serializer_generator(package, type_, name, serialize):
604
+ """
605
+ Generator for string types. similar to arrays, but with more
606
+ efficient call to struct.pack.
607
+ @param name: spec field name
608
+ @type name: str
609
+ @param serialize: if True, generate code for
610
+ serialization. Other, generate code for deserialization
611
+ @type serialize: bool
612
+ """
613
+ # don't optimize in deserialization case as assignment doesn't
614
+ # work
615
+ if _serial_context and serialize:
616
+ # optimize as string serialization accesses field twice
617
+ yield "_x = %s%s"%(_serial_context, name)
618
+ var = "_x"
619
+ else:
620
+ var = _serial_context+name
621
+
622
+ # the length generator is a noop if serialize is True as we
623
+ # optimize the serialization call.
624
+ base_type, is_array, array_len = roslib.msgs.parse_type(type_)
625
+ # - don't serialize length for fixed-length arrays of bytes
626
+ if base_type not in ['uint8', 'char'] or array_len is None:
627
+ for y in len_serializer_generator(var, True, serialize):
628
+ yield y #serialize string length
629
+
630
+ if serialize:
631
+ #serialize length and string together
632
+
633
+ #check to see if its a uint8/char type, in which case we need to convert to string before serializing
634
+ base_type, is_array, array_len = roslib.msgs.parse_type(type_)
635
+ if base_type in ['uint8', 'char']:
636
+ yield "# - if encoded as a list instead, serialize as bytes instead of string"
637
+ if array_len is None:
638
+ yield "if type(%s) in [list, tuple]"%var
639
+ yield INDENT+pack2('"La#{length}"', "[length, *%s]"%var)
640
+ yield "else"
641
+ yield INDENT+pack2('"La#{length}"', "[length, %s]"%var)
642
+ yield "end"
643
+ else:
644
+ yield "if type(%s) in [list, tuple]"%var
645
+ yield INDENT+pack('C%s'%array_len, "*%s"%var)
646
+ yield "else"
647
+ yield INDENT+pack('C%s'%array_len, var)
648
+ yield "end"
649
+ else:
650
+ # py3k: struct.pack() now only allows bytes for the s string pack code.
651
+ # FIXME: for py3k, this needs to be w/ encode, but this interferes with actual byte data
652
+ #yield pack2("'<I%ss'%length", "length, %s.encode()"%var) #Py3k bugfix (see http://docs.python.org/dev/whatsnew/3.2.html#porting-to-python-3-2)
653
+ yield pack2('"La#{length}"', "[length, %s]"%var)
654
+ else:
655
+ yield "start = end_point"
656
+ if array_len is not None:
657
+ yield "end_point += %s" % array_len
658
+ else:
659
+ yield "end_point += length"
660
+ yield "%s = str[start..(end_point-1)]" % var
661
+
662
+ def array_serializer_generator(package, type_, name, serialize, is_numpy):
663
+ """
664
+ Generator for array types
665
+ @raise MsgGenerationException: if array spec is invalid
666
+ """
667
+ base_type, is_array, array_len = roslib.msgs.parse_type(type_)
668
+ if not is_array:
669
+ raise MsgGenerationException("Invalid array spec: %s"%type_)
670
+ var_length = array_len is None
671
+
672
+ # handle fixed-size byte arrays could be slightly more efficient
673
+ # as we recalculated the length in the generated code.
674
+ if base_type in ['char', 'uint8']: #treat unsigned int8 arrays as string type
675
+ for y in string_serializer_generator(package, type_, name, serialize):
676
+ yield y
677
+ return
678
+
679
+ var = _serial_context+name
680
+ try:
681
+ # yield length serialization, if necessary
682
+ if var_length:
683
+ for y in len_serializer_generator(var, False, serialize):
684
+ yield y #serialize array length
685
+ length = None
686
+ else:
687
+ length = array_len
688
+
689
+ #optimization for simple arrays
690
+ if is_simple(base_type):
691
+ if var_length:
692
+ pattern = compute_struct_pattern([base_type])
693
+ yield 'pattern = "%s#{length}"'%convert_to_ruby_pattern(pattern)
694
+ if serialize:
695
+ if is_numpy:
696
+ yield pack_numpy(var)
697
+ else:
698
+ yield pack2('pattern', "*"+var)
699
+ else:
700
+ yield "start = end_point"
701
+ yield 'end_point += ROS::Struct::calc_size("#{pattern}")'
702
+ if is_numpy:
703
+ dtype = _NUMPY_DTYPE[base_type]
704
+ yield unpack_numpy(var, 'length', dtype, 'str[start..(end_point-1)]')
705
+ else:
706
+ yield unpack2(var, 'pattern', 'str[start..(end_point-1)]')
707
+ else:
708
+ pattern = "%s%s"%(length, compute_struct_pattern([base_type]))
709
+ if serialize:
710
+ if is_numpy:
711
+ yield pack_numpy(var)
712
+ else:
713
+ yield pack(pattern, "*"+var)
714
+ else:
715
+ yield "start = end_point"
716
+ yield "end_point += %s"%struct.calcsize('%s'%convert_to_ruby_pattern(pattern))
717
+ if is_numpy:
718
+ dtype = _NUMPY_DTYPE[base_type]
719
+ yield unpack_numpy(var, length, dtype, 'str[start..(end_point-1)]')
720
+ else:
721
+ yield unpack(var, pattern, 'str[start..(end_point-1)]')
722
+ if not serialize and base_type == 'bool':
723
+ # convert uint8 to bool
724
+ if base_type == 'bool':
725
+ yield "%s = map(bool, %s)"%(var, var)
726
+
727
+ else:
728
+ #generic recursive serializer
729
+ #NOTE: this is functionally equivalent to the is_registered branch of complex_serializer_generator
730
+
731
+ # choose a unique temporary variable for iterating
732
+ loop_var = 'val%s'%len(_context_stack)
733
+
734
+ # compute the variable context and factory to use
735
+ if base_type == 'string':
736
+ push_context('')
737
+ factory = string_serializer_generator(package, base_type, loop_var, serialize)
738
+ else:
739
+ push_context('%s.'%loop_var)
740
+ factory = serializer_generator(package, get_registered_ex(base_type), serialize, is_numpy)
741
+
742
+ if serialize:
743
+ yield 'for %s in %s'%(loop_var, var)
744
+ else:
745
+ yield '%s = []'%var
746
+ if var_length:
747
+ yield 'length.times do'
748
+ else:
749
+ yield '%s.times do'%length
750
+ if base_type != 'string':
751
+ yield INDENT + '%s = %s'%(loop_var, compute_constructor(package, base_type))
752
+ for y in factory:
753
+ yield INDENT + y
754
+ if not serialize:
755
+ yield INDENT + '%s.push(%s)'%(var, loop_var)
756
+ pop_context()
757
+ yield 'end'
758
+
759
+ except MsgGenerationException:
760
+ raise #re-raise
761
+ except Exception as e:
762
+ raise MsgGenerationException(e) #wrap
763
+
764
+ def complex_serializer_generator(package, type_, name, serialize, is_numpy):
765
+ """
766
+ Generator for serializing complex type
767
+ @param serialize: if True, generate serialization
768
+ code. Otherwise, deserialization code.
769
+ @type serialize: bool
770
+ @param is_numpy: if True, generate serializer code for numpy
771
+ datatypes instead of Python lists
772
+ @type is_numpy: bool
773
+ @raise MsgGenerationException: if type is not a valid
774
+ """
775
+ # ordering of these statements is important as we mutate the type
776
+ # string we are checking throughout. parse_type strips array
777
+ # brackets, then we check for the 'complex' builtin types (string,
778
+ # time, duration, Header), then we canonicalize it to an embedded
779
+ # message type.
780
+ _, is_array, _ = roslib.msgs.parse_type(type_)
781
+
782
+ #Array
783
+ if is_array:
784
+ for y in array_serializer_generator(package, type_, name, serialize, is_numpy):
785
+ yield y
786
+ #Embedded Message
787
+ elif type_ == 'string':
788
+ for y in string_serializer_generator(package, type_, name, serialize):
789
+ yield y
790
+ else:
791
+ if not is_special(type_):
792
+ # canonicalize type
793
+ pkg, base_type = compute_pkg_type(package, type_)
794
+ type_ = "%s/%s"%(pkg, base_type)
795
+ if roslib.msgs.is_registered(type_):
796
+ # descend data structure ####################
797
+ ctx_var = next_var()
798
+ yield "%s = %s"%(ctx_var, _serial_context+name)
799
+ push_context(ctx_var+'.')
800
+ # unoptimized code
801
+ #push_context(_serial_context+name+'.')
802
+ for y in serializer_generator(package, get_registered_ex(type_), serialize, is_numpy):
803
+ yield y #recurs on subtype
804
+ pop_context()
805
+ else:
806
+ #Invalid
807
+ raise MsgGenerationException("Unknown type: %s. Package context is %s"%(type_, package))
808
+
809
+ def simple_serializer_generator(spec, start, end_point, serialize): #primitives that can be handled with struct
810
+ """
811
+ Generator (de)serialization code for multiple fields from spec
812
+ @param spec: MsgSpec
813
+ @type spec: MsgSpec
814
+ @param start: first field to serialize
815
+ @type start: int
816
+ @param end: last field to serialize
817
+ @type end: int
818
+ """
819
+ # optimize member var access
820
+ if end_point - start > 1 and _serial_context.endswith('.'):
821
+ yield '_x = '+_serial_context[:-1]
822
+ vars_ = '_x.' + (', _x.').join(spec.names[start:end_point])
823
+ else:
824
+ vars_ = _serial_context + (', '+_serial_context).join(spec.names[start:end_point])
825
+
826
+ pattern = compute_struct_pattern(spec.types[start:end_point])
827
+ if serialize:
828
+ yield pack(pattern, vars_)
829
+ else:
830
+ yield "start = end_point"
831
+ yield "end_point += ROS::Struct::calc_size('%s')"%convert_to_ruby_pattern(reduce_pattern(pattern))
832
+ yield unpack('(%s,)'%vars_, pattern, 'str[start..(end_point-1)]')
833
+
834
+ # convert uint8 to bool. this doesn't add much value as Python
835
+ # equality test on a field will return that True == 1, but I
836
+ # want to be consistent with bool
837
+ bool_vars = [(f, t) for f, t in zip(spec.names[start:end_point], spec.types[start:end_point]) if t == 'bool']
838
+ for f, t in bool_vars:
839
+ #TODO: could optimize this as well
840
+ var = _serial_context+f
841
+ yield "%s = bool(%s)"%(var, var)
842
+
843
+ def serializer_generator(package, spec, serialize, is_numpy):
844
+ """
845
+ Python generator that yields un-indented python code for
846
+ (de)serializing MsgSpec. The code this yields is meant to be
847
+ included in a class method and cannot be used
848
+ standalone. serialize_fn_generator and deserialize_fn_generator
849
+ wrap method to provide appropriate class field initializations.
850
+ @param package: name of package the spec is being used in
851
+ @type package: str
852
+ @param serialize: if True, yield serialization
853
+ code. Otherwise, yield deserialization code.
854
+ @type serialize: bool
855
+ @param is_numpy: if True, generate serializer code for numpy datatypes instead of Python lists
856
+ @type is_numpy: bool
857
+ """
858
+ # Break spec into chunks of simple (primitives) vs. complex (arrays, etc...)
859
+ # Simple types are batch serialized using the python struct module.
860
+ # Complex types are individually serialized
861
+ if spec is None:
862
+ raise MsgGenerationException("spec is none")
863
+ names, types = spec.names, spec.types
864
+ if serialize and not len(names): #Empty
865
+ yield "pass"
866
+ return
867
+
868
+ # iterate through types. whenever we encounter a non-simple type,
869
+ # yield serializer for any simple types we've encountered until
870
+ # then, then yield the complex type serializer
871
+ curr = 0
872
+ for (i, type_) in enumerate(types):
873
+ if not is_simple(type_):
874
+ if i != curr: #yield chunk of simples
875
+ for y in simple_serializer_generator(spec, curr, i, serialize):
876
+ yield y
877
+ curr = i+1
878
+ for y in complex_serializer_generator(package, type_, names[i], serialize, is_numpy):
879
+ yield y
880
+ if curr < len(types): #yield rest of simples
881
+ for y in simple_serializer_generator(spec, curr, len(types), serialize):
882
+ yield y
883
+
884
+ def serialize_fn_generator(package, spec, is_numpy=False):
885
+ """
886
+ generator for body of serialize() function
887
+ @param is_numpy: if True, generate serializer code for numpy
888
+ datatypes instead of Python lists
889
+ @type is_numpy: bool
890
+ """
891
+ # method-var context #########
892
+ yield "begin"
893
+ push_context('@')
894
+ #NOTE: we flatten the spec for optimal serialization
895
+ for y in serializer_generator(package, flatten(spec), True, is_numpy):
896
+ yield " "+y
897
+ pop_context()
898
+ yield """rescue => exception
899
+ raise "some erro in serialize: #{exception}"
900
+ """
901
+ yield "end"
902
+ # done w/ method-var context #
903
+
904
+ def deserialize_fn_generator(package, spec, is_numpy=False):
905
+ """
906
+ generator for body of deserialize() function
907
+ @param is_numpy: if True, generate serializer code for numpy
908
+ datatypes instead of Python lists
909
+ @type is_numpy: bool
910
+ """
911
+ yield "begin"
912
+
913
+ #Instantiate embedded type classes
914
+ for type_, name in spec.fields():
915
+ if roslib.msgs.is_registered(type_):
916
+ yield " if @%s == nil"%name
917
+ yield " @%s = %s"%(name, compute_constructor(package, type_))
918
+ yield " end"
919
+ yield " end_point = 0" #initialize var
920
+
921
+ # method-var context #########
922
+ push_context('@')
923
+ #NOTE: we flatten the spec for optimal serialization
924
+ for y in serializer_generator(package, flatten(spec), False, is_numpy):
925
+ yield " "+y
926
+ pop_context()
927
+ # done w/ method-var context #
928
+
929
+ # generate post-deserialization code
930
+ for type_, name in spec.fields():
931
+ code = compute_post_deserialize(type_, "@%s"%name)
932
+ if code:
933
+ yield " %s"%code
934
+
935
+ yield " return self"
936
+ yield "rescue => exception"
937
+ yield """ raise \"message DeserializationError: #{exception}\" #most likely buffer underfill
938
+ end"""
939
+
940
+ def msg_generator_internal(package, name, spec):
941
+ """
942
+ Python code generator for .msg files. Takes in a package name,
943
+ message name, and message specification and generates a Python
944
+ message class.
945
+
946
+ @param package: name of package for message
947
+ @type package: str
948
+ @param name: base type name of message, e.g. 'Empty', 'String'
949
+ @type name: str
950
+ @param spec: parsed .msg specification
951
+ @type spec: L{MsgSpec}
952
+ """
953
+
954
+ # #2990: have to compute md5sum before any calls to make_python_safe
955
+
956
+ # generate dependencies dictionary. omit files calculation as we
957
+ # rely on in-memory MsgSpecs instead so that we can generate code
958
+ # for older versions of msg files
959
+ try:
960
+ gendeps_dict = roslib.gentools.get_dependencies(spec, package, compute_files=False)
961
+ except roslib.msgs.MsgSpecException as e:
962
+ raise MsgGenerationException("Cannot generate .msg for %s/%s: %s"%(package, name, str(e)))
963
+ md5sum = roslib.gentools.compute_md5(gendeps_dict)
964
+
965
+ # remap spec names to be Python-safe
966
+ spec = make_ruby_safe(spec)
967
+ spec_names = spec.names
968
+
969
+ # for not capital class like tfMessages
970
+ capitalized_name = name[0].upper() + name[1:]
971
+
972
+ # #1807 : this will be much cleaner when msggenerator library is
973
+ # rewritten to not use globals
974
+ clear_patterns()
975
+
976
+ yield '# autogenerated by genmsg_ruby from %s.msg. Do not edit.'%name
977
+ yield "require 'ros/message'\n"
978
+ import_strs = []
979
+ for t in spec.types:
980
+ import_strs.extend(compute_import(package, t))
981
+ import_strs = set(import_strs)
982
+ for i in import_strs:
983
+ if i:
984
+ yield i
985
+
986
+ yield ''
987
+
988
+ yield "module %s\n"%package.capitalize()
989
+
990
+ fulltype = '%s%s%s'%(package, roslib.msgs.SEP, name)
991
+
992
+ #Yield data class first, e.g. Point2D
993
+ yield 'class %s <::ROS::Message'%capitalized_name
994
+
995
+ yield """ def self.md5sum
996
+ \"%s\"
997
+ end
998
+ """%(md5sum)
999
+ yield """ def self.type
1000
+ \"%s\"
1001
+ end
1002
+ """%(fulltype)
1003
+ if spec.has_header():
1004
+ bool_val = 'true'
1005
+ else:
1006
+ bool_val = 'false'
1007
+ yield """ def has_header?
1008
+ %s
1009
+ end
1010
+ """%bool_val
1011
+ # note: we introduce an extra newline to protect the escaping from quotes in the message
1012
+ yield """ def message_definition
1013
+ \"%s\n\"
1014
+ end"""%compute_full_text_escaped(gendeps_dict)
1015
+
1016
+ if spec.constants:
1017
+ yield ' # Pseudo-constants'
1018
+ for c in spec.constants:
1019
+ if c.type == 'string':
1020
+ val = c.val
1021
+ if '"' in val and "'" in val:
1022
+ # crude escaping of \ and "
1023
+ escaped = c.val.replace('\\', '\\\\')
1024
+ escaped = escaped.replace('\"', '\\"')
1025
+ yield ' %s = "%s"'%(c.name, escaped)
1026
+ elif '"' in val: #use raw encoding for prettiness
1027
+ yield " %s = r'%s'"%(c.name, val)
1028
+ elif "'" in val: #use raw encoding for prettiness
1029
+ yield ' %s = r"%s"'%(c.name, val)
1030
+ else:
1031
+ yield " %s = '%s'"%(c.name, val)
1032
+ else:
1033
+ yield ' %s = %s'%(c.name, c.val)
1034
+ yield ''
1035
+ yield " attr_accessor "+", ".join([":"+x for x in spec_names])+"\n"
1036
+
1037
+ yield '_REPLACE_FOR_STRUCT_'
1038
+ if len(spec_names):
1039
+ yield " @@struct_L = ::ROS::Struct.new(\"L\")"
1040
+ yield " @@slot_types = ['"+"','".join(spec.types)+"']"
1041
+ else:
1042
+ yield " @@struct_L = ::ROS::Struct.new(\"L\")"
1043
+ yield " @@slot_types = []"
1044
+
1045
+ yield """
1046
+ def initialize
1047
+ # Constructor. Any message fields that are implicitly/explicitly
1048
+ # set to None will be assigned a default value. The recommend
1049
+ # use is keyword arguments as this is more robust to future message
1050
+ # changes. You cannot mix in-order arguments and keyword arguments.
1051
+ #
1052
+ # The available fields are:
1053
+ # %s
1054
+ #
1055
+ # @param args: complete set of field values, in .msg order
1056
+ # @param kwds: use keyword arguments corresponding to message field names
1057
+ # to set specific fields.
1058
+ #
1059
+ """%','.join(spec_names)
1060
+ if len(spec_names):
1061
+ yield " # message fields cannot be None, assign default values for those that are"
1062
+ if len(spec_names) > 0:
1063
+ for (t, s) in zip(spec.types, spec_names):
1064
+ yield " @%s = %s"%(s, default_value(t, package))
1065
+ yield " end"
1066
+ yield """
1067
+ def _get_types
1068
+ # internal API method
1069
+ return @slot_types
1070
+ end
1071
+
1072
+ def serialize(buff)
1073
+ # serialize message into buffer
1074
+ # @param buff: buffer
1075
+ # @type buff: StringIO"""
1076
+ for y in serialize_fn_generator(package, spec):
1077
+ yield " "+ y
1078
+ yield " end"
1079
+ yield """
1080
+ def deserialize(str)
1081
+ # unpack serialized message in str into this message instance
1082
+ # @param str: byte array of serialized message
1083
+ # @type str: str
1084
+ """
1085
+ for y in deserialize_fn_generator(package, spec):
1086
+ yield " " + y
1087
+ yield " end"
1088
+ yield "end # end of class"
1089
+ yield "end # end of module"
1090
+
1091
+ import copy
1092
+ import re
1093
+
1094
+ def convert_to_ruby_pattern(python_pattern):
1095
+ ruby_pattern = copy.copy(python_pattern)
1096
+ # get python key
1097
+ for (py, ru) in PYTHON_RUBY_DICT.iteritems():
1098
+ ruby_pattern = re.sub(py, ru, ruby_pattern)
1099
+ # swap number and char
1100
+ return re.sub(r"([0-9]+)([A-Za-z])", r'\2\1', ruby_pattern)
1101
+
1102
+ def msg_generator(package, base_name, spec):
1103
+ generated = ''
1104
+ for l in msg_generator_internal(package, base_name, spec):
1105
+ generated += l + "\n"
1106
+
1107
+ patterns = get_patterns()
1108
+ structs = ''
1109
+ for p in set(patterns):
1110
+ if p == 'I':
1111
+ continue
1112
+ ruby_p = convert_to_ruby_pattern(p)
1113
+ var_name = ' @@struct_%s'%ruby_p
1114
+ structs += '%s = ::ROS::Struct.new("%s")\n'%(var_name, ruby_p)
1115
+ clear_patterns()
1116
+ import re
1117
+ return re.sub('_REPLACE_FOR_STRUCT_', structs, generated, 1)
1118
+
1119
+ def gen_msg(path, output_dir_prefix=None):
1120
+ f = os.path.abspath(path)
1121
+ (package_dir, package) = roslib.packages.get_dir_pkg(f)
1122
+ (name, spec) = roslib.msgs.load_from_file(f, package)
1123
+ base_name = roslib.names.resource_name_base(name)
1124
+ if not output_dir_prefix:
1125
+ output_dir_prefix = '%s/msg_gen/ruby'%package_dir
1126
+ output_dir = '%s/%s'%(output_dir_prefix, package)
1127
+ if not os.path.exists(output_dir):
1128
+ os.makedirs(output_dir)
1129
+ out = open('%s/%s.rb'%(output_dir, base_name), 'w')
1130
+ out.write(msg_generator(package, base_name, spec))
1131
+ out.close()
1132
+
1133
+ if __name__ == '__main__':
1134
+ for arg in sys.argv[1:]:
1135
+ gen_msg(arg)