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 Fluendo, S.L. (www.fluendo.com). 
  6  # All rights reserved. 
  7   
  8  # This file may be distributed and/or modified under the terms of 
  9  # the GNU General Public License version 2 as published by 
 10  # the Free Software Foundation. 
 11  # This file is distributed without any warranty; without even the implied 
 12  # warranty of merchantability or fitness for a particular purpose. 
 13  # See "LICENSE.GPL" in the source distribution for more information. 
 14   
 15  # Licensees having purchased or holding a valid Flumotion Advanced 
 16  # Streaming Server license may use this file in accordance with the 
 17  # Flumotion Advanced Streaming Server Commercial License Agreement. 
 18  # See "LICENSE.Flumotion" in the source distribution for more information. 
 19   
 20  # Headers in this file shall remain intact. 
 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: 8290 $" 
 34   
 35   
36 -def err(x):
37 sys.stderr.write(x + '\n') 38 raise SystemExit(1)
39 40
41 -class Component(object):
42 __slots__ = ('type', 'name', 'properties', 'plugs', 'source', 43 'clock_priority', 'config_entry', '_reg') 44
45 - def __init__(self, type, name):
46 self.type = type 47 self.name = name 48 self.properties = [] 49 self.plugs = [] 50 self.source = [] 51 52 self.config_entry = None 53 54 r = registry.getRegistry() 55 if not r.hasComponent(self.type): 56 err('Unknown component type: %s' % self.type) 57 58 self._reg = r.getComponent(self.type) 59 if self._reg.getNeedsSynchronization(): 60 self.clock_priority = self._reg.getClockPriority() 61 else: 62 self.clock_priority = None
63
64 - def complete_and_verify(self):
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
78 - def as_config_dict(self):
79 return copy.deepcopy(self.config_entry.config)
80 81
82 -class ComponentStore:
83
84 - def __init__(self):
85 self._names = {} 86 self._last_component = None 87 self.components = {} 88 assert not self # make sure that i am false if empty
89
90 - def _make_name(self, type):
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):
96 self._last_component = name = self._make_name(type) 97 self.components[name] = Component(type, name)
98
99 - def add_plug_to_current(self, type, props):
100 self[self.last()].plugs.append((type, props))
101
102 - def add_prop_to_current(self, key, val):
103 self[self.last()].properties.append((key, val))
104
105 - def last(self):
106 assert self._last_component 107 return self._last_component
108
109 - def names(self):
110 return self.components.keys()
111
113 for name in self.components: 114 self.components[name].complete_and_verify() 115 116 # hackily stolen from config.ConfigXML.parseFlow, definitely 117 # hackariffic 118 119 need_sync = [(x.clock_priority, x) for x in self.components.values() 120 if x.clock_priority] 121 need_sync.sort() 122 need_sync = [x[1] for x in need_sync] 123 124 if need_sync: 125 master = need_sync[-1] 126 for x in need_sync: 127 x.config_entry.config['clock-master'] = ( 128 master.config_entry.config['avatarId'])
129
130 - def sorted_configs(self, partial_orders):
131 sort = dag.topological_sort 132 return [self[name].as_config_dict() 133 for name in sort(self.names(), partial_orders)]
134
135 - def __getitem__(self, key):
136 return self.components[key]
137
138 - def __setitem__(self, key, val):
139 self.components[key] = val
140
141 - def __contains__(self, key):
142 return key in self.components
143
144 - def __len__(self):
145 return len(self.components)
146
147 - def __iter__(self):
148 return self.components.__iter__()
149 150
151 -class Linker:
152
153 - def __init__(self, get_last_component):
154 # links: [(feedercomponentname, feeder, 155 # eatercomponentname, eater), ...] 156 self.links = [] 157 self._tmp = None 158 self.get_last_component = get_last_component
159
160 - def pending(self):
161 return bool(self._tmp)
162 191 197 242
243 - def get_sort_order(self):
244 return [(link[0], link[2]) for link in self.get_links()]
245
246 - def dump(self):
247 for link in self.links: 248 print '%s:%s => %s:%s' % tuple(link)
249 250
251 -def parse_plug(arg):
252 if ',' not in arg: 253 return arg[1:], [] 254 plugname, plugargs = arg.split(',', 1) 255 return plugname[1:], parse_props(plugargs)
256 257
258 -def parse_props(props):
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
288 -def parse_prop(arg):
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
304 -def sloppy_unescape(value, escaped, escape='\\'):
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
342 -def parse_arg(arg, components, linker):
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
382 -def parse_args(args):
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() # so we can pop from the tail 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