1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 """
19 flumotion.launch.parse: A parsing library for flumotion-launch syntax.
20 """
21
22 import copy
23 import sys
24
25 from flumotion.common import dag, registry
26 from flumotion.manager import config
27
28 __all__ = ['parse_args']
29 __version__ = "$Rev$"
30
31
35
36
38 __slots__ = ('type', 'name', 'properties', 'plugs', 'source',
39 'clock_priority', 'config_entry', '_reg')
40
59
61 self.config_entry = config.ConfigEntryComponent(
62 self.name,
63 None,
64 self.type,
65 None,
66 self.properties,
67 self.plugs,
68 None,
69 [(None, feedId) for feedId in self.source],
70 None,
71 None,
72 None)
73
76
77
79
81 self._names = {}
82 self._last_component = None
83 self.components = {}
84 assert not self
85
87 i = self._names.get(type, 0)
88 self._names[type] = i + 1
89 return '%s%d' % (type, i)
90
91 - def add(self, type):
94
97
100
102 assert self._last_component
103 return self._last_component
104
106 return self.components.keys()
107
125
130
132 return self.components[key]
133
135 self.components[key] = val
136
138 return key in self.components
139
141 return len(self.components)
142
145
146
148
149 - def __init__(self, get_last_component):
150
151
152 self.links = []
153 self._tmp = None
154 self.get_last_component = get_last_component
155
157 return bool(self._tmp)
158
159 - def link(self, feedercompname=None, feeder=None, eatercompname=None,
160 eater=None):
161 if feedercompname:
162 assert not self._tmp
163 self._tmp = [feedercompname, feeder, eatercompname, eater]
164 elif feeder:
165 err('how did i get here?')
166 elif eatercompname:
167 if not self._tmp:
168 err('Invalid grammar: trying to link, but no feeder component')
169 self._tmp[2] = eatercompname
170 if eater:
171 self._tmp[3] = eater
172 elif eater:
173 if not self._tmp:
174 err('Invalid grammar: trying to link, but no feeder component')
175 self._tmp[3] = eater
176 else:
177
178 if not self._tmp:
179 self._tmp = [self.get_last_component(), None, None, None]
180 else:
181 if not self._tmp[0]:
182 self._tmp[0] = self.get_last_component()
183
184 if self._tmp and self._tmp[0] and self._tmp[2]:
185 self.links.append(self._tmp)
186 self._tmp = None
187
189 if self._tmp:
190 err('Invalid grammar: uncompleted link from %s.%s' % self._tmp[:2])
191 else:
192 return self.links
193
195 for link in self.get_links():
196 assert link[0] and link[2]
197 if not link[0] in component_types:
198 err('Invalid grammar: no feeder component %s to link from' % (
199 link[0], ))
200 if not link[2] in component_types:
201 err('Invalid grammar: no eater component %s to link to' % (
202 link[2], ))
203
204 reg = registry.getRegistry()
205 for link in self.get_links():
206 compname = link[0]
207 comptype = component_types[compname]
208 compreg = reg.getComponent(comptype)
209 if link[1]:
210 if not link[1] in compreg.getFeeders():
211 err('Component %s has no feeder named %s' % (
212 compname, link[1]))
213
214 else:
215 if not compreg.getFeeders():
216 err('Component %s has no feeders' % compname)
217 link[1] = compreg.getFeeders()[0]
218
219 for link in self.get_links():
220 compname = link[2]
221 comptype = component_types[compname]
222 compreg = reg.getComponent(comptype)
223 eaters = compreg.getEaters()
224 if link[3]:
225 if not link[3] in [x.getName() for x in eaters]:
226 err('Component %s has no eater named %s' % (
227 compname, link[3]))
228
229 else:
230 if not eaters:
231 err('Component %s has no eaters' % compname)
232 link[3] = eaters[0].getName()
233
234 feeders = dict([(name, []) for name in component_types])
235 for link in self.get_links():
236 feeders[link[2]].append('%s:%s' % (link[0], link[1]))
237 return feeders
238
241
243 for link in self.links:
244 print '%s:%s => %s:%s' % tuple(link)
245
246
248 if ',' not in arg:
249 return arg[1:], []
250 plugname, plugargs = arg.split(',', 1)
251 return plugname[1:], parse_props(plugargs)
252
253
255 """
256 Splits a property line respecting compound properties.
257 Ex: a1=[c1=d1,c2=[e1=[g1=h1],e2=f2]],a2=b2
258 -> [("a1", [("c1", "d1"),
259 ("c2", [("e1", [("g1", "h1")]),
260 ("e2", "f2")])],
261 ("a2", "b2")]
262 """
263 start = 0
264 level = 0
265 result = []
266 for i, c in enumerate(props):
267 if c == '[':
268 level += 1
269 continue
270 if c == ']':
271 level -= 1
272 continue
273 if c == ',' and level == 0:
274 result.append(props[start:i])
275 start = i + 1
276 continue
277 if level == 0:
278 result.append(props[start:])
279 else:
280 raise ValueError(props)
281 return [parse_prop(v) for v in result]
282
283
285 """
286 Parses a property.
287 Supports compounds properties.
288 """
289 prop = arg[:arg.index('=')]
290 val = arg[arg.index('=')+1:]
291 if not prop or not val:
292 err('Invalid property setting: %s' % arg)
293 if val[0] == '[' and val[-1] == ']':
294 val = parse_props(val[1:-1])
295 else:
296 val = sloppy_unescape(val, "[]")
297 return prop, val
298
299
301 """
302 Permissively unescapes a string.
303
304 Examples with \ as escape character,
305 E as escaped character and X as a non-escaped character:
306 X -> X
307 \E -> E
308 \\ -> \
309 \X -> \X
310 X\ -> X\
311 E\ -> E\
312 \\\E -> \E
313 \\\X -> \\X
314 """
315 res = []
316 escaping = False
317 escaped = set(list(escaped))
318 escaped.add(escape)
319 for char in value:
320 if escaping:
321 if char in escaped:
322 res.append(char)
323 escaping = False
324 continue
325 res.append(escape)
326 res.append(char)
327 escaping = False
328 continue
329 if char == escape:
330 escaping = True
331 continue
332 res.append(char)
333 if escaping:
334 res.append(escape)
335 return ''.join(res)
336
337
339
340 def assert_in_component(msg):
341 if linker.pending() or not components:
342 err('Invalid grammar: %s' % msg)
343
344 if arg == '!':
345 if not components:
346 err('Invalid grammar: `!\' without feeder component')
347 linker.link()
348
349 elif arg[0] == '/':
350 assert_in_component('Plug %s does not follow a component' % arg)
351 plug, props = parse_plug(arg)
352 components.add_plug_to_current(plug, props)
353
354 elif arg.find('=') != -1:
355 assert_in_component('Property %s does not follow a component' % arg)
356 prop, val = parse_prop(arg)
357 components.add_prop_to_current(prop, val)
358
359 elif arg.find('.') != -1:
360 t = arg.split('.')
361 if len(t) != 2:
362 err('Invalid grammar: bad eater/feeder specification: %s' % arg)
363 t = [z or None for z in t]
364 if linker.pending():
365 linker.link(eatercompname=t[0], eater=t[1])
366 elif components:
367 linker.link(feedercompname=t[0] or components.last(), feeder=t[1])
368 else:
369 err('Invalid grammar: trying to link from feeder %s but '
370 'no feeder component' % arg)
371
372 else:
373 components.add(arg)
374 if linker.pending():
375 linker.link(eatercompname=components.last())
376
377
379 """Parse flumotion-launch arguments.
380
381 Parse flumotion-launch arguments, returning a list of component
382 configs.
383
384 A component config is what we will pass to a component when we
385 create it. It is a dict:
386
387 - 'name': component name
388 - 'type': component type
389 - 'properties': dict of property name => property value
390 - 'feed': list of [feeder name,...]
391 - 'source': list of [feeder name,...], (optional)
392 - 'clock-master': clock master or None
393 - 'plugs': dict of socket name => plug config
394 """
395
396 if not args:
397 err('Usage: flumotion-launch COMPONENT [! COMPONENT]...')
398
399 components = ComponentStore()
400
401 linker = Linker(components.last)
402
403 args.reverse()
404 while args:
405 parse_arg(args.pop().strip(), components, linker)
406
407 feeders = linker.resolve_links(dict([(name, components[name].type)
408 for name in components]))
409
410 for compname in feeders:
411 components[compname].source = feeders[compname]
412 components.complete_and_verify_configs()
413
414 return components.sorted_configs(linker.get_sort_order())
415