@@ -31,15 +31,13 @@ class ArgumentResolver:
3131 files : dockerblade .FileSystem
3232 context : Dict [str , Any ] = attr .ib (default = None )
3333
34- def _resolve_arg (self , s : str ) -> str :
34+ def _resolve_substitution_arg (self , s : str ) -> str :
3535 """
3636 Raises
3737 ------
3838 EnvNotFoundError
3939 if a given environment variable is not found.
4040 """
41- shell = self .shell
42- context = self .context
4341 logger .debug (f"resolving substitution argument: { s } " )
4442 s = s [2 :- 1 ]
4543 logger .debug (f"stripped delimiters: { s } " )
@@ -49,31 +47,49 @@ def _resolve_arg(self, s: str) -> str:
4947 # we deal with find in a later stage
5048 if kind == 'find' :
5149 return f'$({ s } )'
52- if kind == 'env' :
53- return shell .environ (params [0 ])
54- if kind == 'optenv' :
55- try :
56- return shell .environ (params [0 ])
57- except dockerblade .exceptions .EnvNotFoundError :
58- return ' ' .join (params [1 :])
59- if kind == 'dirname' :
60- try :
61- dirname = os .path .dirname (context ['filename' ])
62- except KeyError :
63- m = 'filename is not provided by the launch context'
64- raise SubstitutionError (m )
65- dirname = os .path .normpath (dirname )
66- return dirname
67- if kind == 'arg' :
50+ elif kind == 'env' :
51+ var = params [0 ]
52+ return self ._resolve_env (var )
53+ elif kind == 'optenv' :
54+ var = params [0 ]
55+ default = ' ' .join (params [1 :])
56+ return self ._resolve_optenv (var , default )
57+ elif kind == 'dirname' :
58+ return self ._resolve_dirname ()
59+ elif kind == 'arg' :
6860 arg_name = params [0 ]
69- if 'arg' not in context or arg_name not in context ['arg' ]:
70- m = f'arg not supplied to launch context [{ arg_name } ]'
71- raise SubstitutionError (m )
72- return context ['arg' ][arg_name ]
73-
74- # TODO $(anon name)
61+ return self ._resolve_arg (arg_name )
62+ elif kind == 'anon' :
63+ return self ._resolve_anon (params [0 ])
7564 return s
7665
66+ def _resolve_dirname (self ) -> str :
67+ try :
68+ dirname = os .path .dirname (self .context ['filename' ])
69+ except KeyError :
70+ m = 'filename is not provided by the launch context'
71+ raise SubstitutionError (m )
72+ return os .path .normpath (dirname )
73+
74+ def _resolve_anon (self , name : str ) -> str :
75+ raise NotImplementedError
76+
77+ def _resolve_env (self , var : str ) -> str :
78+ return self .shell .environ (var )
79+
80+ def _resolve_optenv (self , var : str , default : str ) -> str :
81+ try :
82+ return self .shell .environ (var )
83+ except dockerblade .exceptions .EnvNotFoundError :
84+ return default
85+
86+ def _resolve_arg (self , arg_name : str ) -> str :
87+ context = self .context
88+ if 'arg' not in context or arg_name not in context ['arg' ]:
89+ m = f'arg not supplied to launch context [{ arg_name } ]'
90+ raise SubstitutionError (m )
91+ return context ['arg' ][arg_name ]
92+
7793 def _find_package_path (self , package : str ) -> str :
7894 cmd = f'rospack find { shlex .quote (package )} '
7995 try :
@@ -125,7 +141,7 @@ def _find_resource(self, package: str, path: str) -> str:
125141 raise SubstitutionError (m )
126142 return path_in_package
127143
128- def _resolve_find (self , package : str , path : str ) -> str :
144+ def _resolve_find (self , package : str , path : str = '' ) -> str :
129145 logger .debug (f'resolving find: { package } ' )
130146 path_original = path
131147
@@ -153,12 +169,42 @@ def _resolve_find(self, package: str, path: str) -> str:
153169 resolved_path = self ._find_package_path (package ) + path_original
154170 return resolved_path
155171
172+ def _resolve_eval (self , attribute_string : str ) -> str :
173+ logger .debug (f'resolving eval: { attribute_string } ' )
174+ assert attribute_string .startswith ('$(eval ' )
175+ assert attribute_string [- 1 ] == ')'
176+ eval_string = attribute_string [7 :- 1 ]
177+
178+ if '__' in attribute_string :
179+ m = ("$(eval ...): refusing to evaluate potentially dangerous "
180+ "expression -- must not contain double underscores" )
181+ raise SubstitutionError (m )
182+
183+ _builtins = {x : __builtins__ [x ] # type: ignore
184+ for x in ('dict' , 'float' , 'int' , 'list' , 'map' )}
185+ _locals = {
186+ 'true' : True ,
187+ 'True' : True ,
188+ 'false' : False ,
189+ 'False' : False ,
190+ '__builtins__' : _builtins ,
191+ 'arg' : self ._resolve_arg ,
192+ 'anon' : self ._resolve_anon ,
193+ 'dirname' : self ._resolve_dirname ,
194+ 'env' : self ._resolve_env ,
195+ 'find' : self ._resolve_find ,
196+ 'optenv' : self ._resolve_optenv
197+ }
198+
199+ result = str (eval (eval_string , {}, _locals ))
200+ logger .debug (f'resolved eval [{ attribute_string } ]: { result } ' )
201+ return result
202+
156203 def resolve (self , s : str ) -> str :
157204 """Resolves a given argument string."""
158- # TODO $(eval ...)
159205 if s .startswith ('$(eval ' ) and s [- 1 ] == ')' :
160- raise NotImplementedError
161- s = R_ARG .sub (lambda m : self ._resolve_arg (m .group (0 )), s )
206+ return self . _resolve_eval ( s )
207+ s = R_ARG .sub (lambda m : self ._resolve_substitution_arg (m .group (0 )), s )
162208
163209 def process_find_arg (match : Match [str ]) -> str :
164210 # split tag and optional trailing path
0 commit comments