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 is_legal_remap(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(r'^[\~\/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(r'^[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
246 REMAP_PATTERN = re.compile(r'^([\~\/A-Za-z]|_|__)[\w\/]*' + REMAP + '.*')
247
249 """
250 Validates that arg is a legal remap according to U{http://wiki.ros.org/Remapping%20Arguments}.
251 """
252 if arg is None:
253 return False
254 m = REMAP_PATTERN.match(arg)
255 return m is not None and m.group(0) == arg
256
258 """
259 Put name in canonical form. Extra slashes '//' are removed and
260 name is returned without any trailing slash, e.g. /foo/bar
261 @param name: ROS name
262 @type name: str
263 """
264 if not name or name == SEP:
265 return name
266 elif name[0] == SEP:
267 return '/' + '/'.join([x for x in name.split(SEP) if x])
268 else:
269 return '/'.join([x for x in name.split(SEP) if x])
270
272 """
273 Resolve a ROS name to its global, canonical form. Private ~names
274 are resolved relative to the node name.
275
276 @param name: name to resolve.
277 @type name: str
278 @param namespace_: node name to resolve relative to.
279 @type namespace_: str
280 @param remappings: Map of resolved remappings. Use None to indicate no remapping.
281 @return: Resolved name. If name is empty/None, resolve_name
282 returns parent namespace_. If namespace_ is empty/None,
283 @rtype: str
284 """
285 if not name:
286 return namespace(namespace_)
287
288 name = canonicalize_name(name)
289 if name[0] == SEP:
290 resolved_name = name
291 elif is_private(name):
292
293 resolved_name = canonicalize_name(namespace_ + SEP + name[1:])
294 else:
295 resolved_name = namespace(namespace_) + name
296
297
298
299
300 if remappings and resolved_name in remappings:
301 return remappings[resolved_name]
302 else:
303 return resolved_name
304
306 """
307 Name resolver for scripts. Supports :envvar:`ROS_NAMESPACE`. Does not
308 support remapping arguments.
309
310 :param name: name to resolve, ``str``
311 :param script_name: name of script. script_name must not
312 contain a namespace., ``str``
313 :returns: resolved name, ``str``
314 """
315 if not name:
316 return get_ros_namespace()
317
318 if is_global(name):
319 return name
320
321 elif is_private(name):
322 return ns_join(make_caller_id(script_name), name[1:])
323 return get_ros_namespace() + name
324
326 """
327 Generate a ROS-legal 'anonymous' name
328
329 @param id: prefix for anonymous name
330 @type id: str
331 """
332 import socket, random
333 name = "%s_%s_%s_%s"%(id, socket.gethostname(), os.getpid(), random.randint(0, sys.maxsize))
334
335
336
337 name = name.replace('.', '_')
338 name = name.replace('-', '_')
339 return name.replace(':', '_')
340