1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 """
36 Library for manipulating ROS Names. See U{http://ros.org/wiki/Names}.
37 """
38
39 import os
40 import sys
41
42 from .rosenv import ROS_NAMESPACE
43
44
45 MSG_EXT = '.msg'
46 SRV_EXT = '.srv'
47
48 SEP = '/'
49 GLOBALNS = '/'
50 PRIV_NAME = '~'
51 REMAP = ":="
52 ANYTYPE = '*'
53
54 if sys.hexversion > 0x03000000:
56 return isinstance(s, str)
57 else:
59 """
60 Small helper version to check an object is a string in a way that works
61 for both Python 2 and 3
62 """
63 return isinstance(s, basestring)
64
66 """
67 @param env: environment dictionary (defaults to os.environ)
68 @type env: dict
69 @param argv: command-line arguments (defaults to sys.argv)
70 @type argv: [str]
71 @return: ROS namespace of current program
72 @rtype: str
73 """
74
75 if argv is None:
76 argv = sys.argv
77 for a in argv:
78 if a.startswith('__ns:='):
79 return make_global_ns(a[len('__ns:='):])
80 if env is None:
81 env = os.environ
82 return make_global_ns(env.get(ROS_NAMESPACE, GLOBALNS))
83
85 """
86 Resolve a local name to the caller ID based on ROS environment settings (i.e. ROS_NAMESPACE)
87
88 @param name: local name to calculate caller ID from, e.g. 'camera', 'node'
89 @type name: str
90 @return: caller ID based on supplied local name
91 @rtype: str
92 """
93 return make_global_ns(ns_join(get_ros_namespace(), name))
94
96 """
97 Convert name to a global name with a trailing namespace separator.
98
99 @param name: ROS resource name. Cannot be a ~name.
100 @type name: str
101 @return str: name as a global name, e.g. 'foo' -> '/foo/'.
102 This does NOT resolve a name.
103 @rtype: str
104 @raise ValueError: if name is a ~name
105 """
106 if is_private(name):
107 raise ValueError("cannot turn [%s] into a global name"%name)
108 if not is_global(name):
109 name = SEP + name
110 if name[-1] != SEP:
111 name = name + SEP
112 return name
113
115 """
116 Test if name is a global graph resource name.
117
118 @param name: must be a legal name in canonical form
119 @type name: str
120 @return: True if name is a globally referenced name (i.e. /ns/name)
121 @rtype: bool
122 """
123 return name and name[0] == SEP
124
126 """
127 Test if name is a private graph resource name.
128
129 @param name: must be a legal name in canonical form
130 @type name: str
131 @return bool: True if name is a privately referenced name (i.e. ~name)
132 """
133 return name and name[0] == PRIV_NAME
134
136 """
137 Get the namespace of name. The namespace is returned with a
138 trailing slash in order to favor easy concatenation and easier use
139 within the global context.
140
141 @param name: name to return the namespace of. Must be a legal
142 name. NOTE: an empty name will return the global namespace.
143 @type name: str
144 @return str: Namespace of name. For example, '/wg/node1' returns '/wg/'. The
145 global namespace is '/'.
146 @rtype: str
147 @raise ValueError: if name is invalid
148 """
149 "map name to its namespace"
150 if name is None:
151 raise ValueError('name')
152 if not isstring(name):
153 raise TypeError('name')
154 if not name:
155 return SEP
156 elif name[-1] == SEP:
157 name = name[:-1]
158 return name[:name.rfind(SEP)+1] or SEP
159
161 """
162 Join a namespace and name. If name is unjoinable (i.e. ~private or
163 /global) it will be returned without joining
164
165 @param ns: namespace ('/' and '~' are both legal). If ns is the empty string, name will be returned.
166 @type ns: str
167 @param name str: a legal name
168 @return str: name concatenated to ns, or name if it is
169 unjoinable.
170 @rtype: str
171 """
172 if is_private(name) or is_global(name):
173 return name
174 if ns == PRIV_NAME:
175 return PRIV_NAME + name
176 if not ns:
177 return name
178 if ns[-1] == SEP:
179 return ns + name
180 return ns + SEP + name
181
183 """
184 Load name mappings encoded in command-line arguments. This will filter
185 out any parameter assignment mappings.
186
187 @param argv: command-line arguments
188 @type argv: [str]
189 @return: name->name remappings.
190 @rtype: dict {str: str}
191 """
192 mappings = {}
193 for arg in argv:
194 if REMAP in arg:
195 try:
196 src, dst = [x.strip() for x in arg.split(REMAP)]
197 if src and dst:
198 if len(src) > 1 and src[0] == '_' and src[1] != '_':
199
200 pass
201 else:
202 mappings[src] = dst
203 except:
204
205 sys.stderr.write("ERROR: Invalid remapping argument '%s'\n"%arg)
206 return mappings
207
208
209
210
211
212 import re
213
214
215 NAME_LEGAL_CHARS_P = re.compile('^[\~\/A-Za-z][\w\/]*$')
217 """
218 Check if name is a legal ROS name for graph resources
219 (alphabetical character followed by alphanumeric, underscore, or
220 forward slashes). This constraint is currently not being enforced,
221 but may start getting enforced in later versions of ROS.
222
223 @param name: Name
224 @type name: str
225 """
226
227 if name is None:
228 return False
229
230 if name == '':
231 return True
232 m = NAME_LEGAL_CHARS_P.match(name)
233 return m is not None and m.group(0) == name and not '//' in name
234
235 BASE_NAME_LEGAL_CHARS_P = re.compile('^[A-Za-z][\w]*$')
237 """
238 Validates that name is a legal base name for a graph resource. A base name has
239 no namespace context, e.g. "node_name".
240 """
241 if name is None:
242 return False
243 m = BASE_NAME_LEGAL_CHARS_P.match(name)
244 return m is not None and m.group(0) == name
245
247 """
248 Put name in canonical form. Extra slashes '//' are removed and
249 name is returned without any trailing slash, e.g. /foo/bar
250 @param name: ROS name
251 @type name: str
252 """
253 if not name or name == SEP:
254 return name
255 elif name[0] == SEP:
256 return '/' + '/'.join([x for x in name.split(SEP) if x])
257 else:
258 return '/'.join([x for x in name.split(SEP) if x])
259
261 """
262 Resolve a ROS name to its global, canonical form. Private ~names
263 are resolved relative to the node name.
264
265 @param name: name to resolve.
266 @type name: str
267 @param namespace_: node name to resolve relative to.
268 @type namespace_: str
269 @param remappings: Map of resolved remappings. Use None to indicate no remapping.
270 @return: Resolved name. If name is empty/None, resolve_name
271 returns parent namespace_. If namespace_ is empty/None,
272 @rtype: str
273 """
274 if not name:
275 return namespace(namespace_)
276
277 name = canonicalize_name(name)
278 if name[0] == SEP:
279 resolved_name = name
280 elif is_private(name):
281
282 resolved_name = canonicalize_name(namespace_ + SEP + name[1:])
283 else:
284 resolved_name = namespace(namespace_) + name
285
286
287
288
289 if remappings and resolved_name in remappings:
290 return remappings[resolved_name]
291 else:
292 return resolved_name
293
295 """
296 Name resolver for scripts. Supports :envvar:`ROS_NAMESPACE`. Does not
297 support remapping arguments.
298
299 :param name: name to resolve, ``str``
300 :param script_name: name of script. script_name must not
301 contain a namespace., ``str``
302 :returns: resolved name, ``str``
303 """
304 if not name:
305 return get_ros_namespace()
306
307 if is_global(name):
308 return name
309
310 elif is_private(name):
311 return ns_join(make_caller_id(script_name), name[1:])
312 return get_ros_namespace() + name
313
315 """
316 Generate a ROS-legal 'anonymous' name
317
318 @param id: prefix for anonymous name
319 @type id: str
320 """
321 import socket, random
322 name = "%s_%s_%s_%s"%(id, socket.gethostname(), os.getpid(), random.randint(0, sys.maxsize))
323
324
325
326 name = name.replace('.', '_')
327 name = name.replace('-', '_')
328 return name.replace(':', '_')
329