Package flumotion :: Package launch :: Module parse
[hide private]

Source Code for Module flumotion.launch.parse

  1  # -*- Mode: Python -*- 
  2  # vi:si:et:sw=4:sts=4:ts=4 
  3   
  4  # Flumotion - a streaming media server 
  5  # Copyright (C) 2004,2005,2006,2007,2008,2009 Fluendo, S.L. 
  6  # Copyright (C) 2010,2011 Flumotion Services, S.A. 
  7  # All rights reserved. 
  8  # 
  9  # This file may be distributed and/or modified under the terms of 
 10  # the GNU Lesser General Public License version 2.1 as published by 
 11  # the Free Software Foundation. 
 12  # This file is distributed without any warranty; without even the implied 
 13  # warranty of merchantability or fitness for a particular purpose. 
 14  # See "LICENSE.LGPL" in the source distribution for more information. 
 15  # 
 16  # Headers in this file shall remain intact. 
 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   
32 -def err(x):
33 sys.stderr.write(x + '\n') 34 raise SystemExit(1)
35 36
37 -class Component(object):
38 __slots__ = ('type', 'name', 'properties', 'plugs', 'source', 39 'clock_priority', 'config_entry', '_reg') 40
41 - def __init__(self, type, name):
42 self.type = type 43 self.name = name 44 self.properties = [] 45 self.plugs = [] 46 self.source = [] 47 48 self.config_entry = None 49 50 r = registry.getRegistry() 51 if not r.hasComponent(self.type): 52 err('Unknown component type: %s' % self.type) 53 54 self._reg = r.getComponent(self.type) 55 if self._reg.getNeedsSynchronization(): 56 self.clock_priority = self._reg.getClockPriority() 57 else: 58 self.clock_priority = None
59
60 - def complete_and_verify(self):
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
74 - def as_config_dict(self):
75 return copy.deepcopy(self.config_entry.config)
76 77
78 -class ComponentStore:
79
80 - def __init__(self):
81 self._names = {} 82 self._last_component = None 83 self.components = {} 84 assert not self # make sure that i am false if empty
85
86 - def _make_name(self, type):
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):
92 self._last_component = name = self._make_name(type) 93 self.components[name] = Component(type, name)
94
95 - def add_plug_to_current(self, type, props):
96 self[self.last()].plugs.append((type, props))
97
98 - def add_prop_to_current(self, key, val):
99 self[self.last()].properties.append((key, val))
100
101 - def last(self):
102 assert self._last_component 103 return self._last_component
104
105 - def names(self):
106 return self.components.keys()
107
109 for name in self.components: 110 self.components[name].complete_and_verify() 111 112 # hackily stolen from config.ConfigXML.parseFlow, definitely 113 # hackariffic 114 115 need_sync = [(x.clock_priority, x) for x in self.components.values() 116 if x.clock_priority] 117 need_sync.sort() 118 need_sync = [x[1] for x in need_sync] 119 120 if need_sync: 121 master = need_sync[-1] 122 for x in need_sync: 123 x.config_entry.config['clock-master'] = ( 124 master.config_entry.config['avatarId'])
125
126 - def sorted_configs(self, partial_orders):
127 sort = dag.topological_sort 128 return [self[name].as_config_dict() 129 for name in sort(self.names(), partial_orders)]
130
131 - def __getitem__(self, key):
132 return self.components[key]
133
134 - def __setitem__(self, key, val):
135 self.components[key] = val
136
137 - def __contains__(self, key):
138 return key in self.components
139
140 - def __len__(self):
141 return len(self.components)
142
143 - def __iter__(self):
144 return self.components.__iter__()
145 146
147 -class Linker:
148
149 - def __init__(self, get_last_component):
150 # links: [(feedercomponentname, feeder, 151 # eatercomponentname, eater), ...] 152 self.links = [] 153 self._tmp = None 154 self.get_last_component = get_last_component
155
156 - def pending(self):
157 return bool(self._tmp)
158 187 193 238
239 - def get_sort_order(self):
240 return [(link[0], link[2]) for link in self.get_links()]
241
242 - def dump(self):
243 for link in self.links: 244 print '%s:%s => %s:%s' % tuple(link)
245 246
247 -def parse_plug(arg):
248 if ',' not in arg: 249 return arg[1:], [] 250 plugname, plugargs = arg.split(',', 1) 251 return plugname[1:], parse_props(plugargs)
252 253
254 -def parse_props(props):
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
284 -def parse_prop(arg):
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
300 -def sloppy_unescape(value, escaped, escape='\\'):
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
338 -def parse_arg(arg, components, linker):
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
378 -def parse_args(args):
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() # so we can pop from the tail 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