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