Package tdi :: Package tools :: Module template
[frames] | no frames]

Source Code for Module tdi.tools.template

  1  # -*- coding: ascii -*- 
  2  u""" 
  3  :Copyright: 
  4   
  5   Copyright 2013 - 2014 
  6   Andr\xe9 Malo or his licensors, as applicable 
  7   
  8  :License: 
  9   
 10   Licensed under the Apache License, Version 2.0 (the "License"); 
 11   you may not use this file except in compliance with the License. 
 12   You may obtain a copy of the License at 
 13   
 14       http://www.apache.org/licenses/LICENSE-2.0 
 15   
 16   Unless required by applicable law or agreed to in writing, software 
 17   distributed under the License is distributed on an "AS IS" BASIS, 
 18   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 19   See the License for the specific language governing permissions and 
 20   limitations under the License. 
 21   
 22  ================ 
 23   Template Tools 
 24  ================ 
 25   
 26  Template Tools. 
 27  """ 
 28  __author__ = u"Andr\xe9 Malo" 
 29  __docformat__ = "restructuredtext en" 
 30   
 31  import pprint as _pprint 
 32   
 33  from tdi import _exceptions 
 34  from tdi import util as _util 
35 36 37 -class TemplateUndecided(_exceptions.DependencyError):
38 """ 39 Template could not be determined, because the decision is ambiguous 40 41 The exception argument is a dict mapping undecidable overlay names to 42 the respective set of template names 43 """
44
45 46 -class TemplateListProxy(object):
47 """ 48 Proxy `TemplateList` instances for lazy creation 49 50 :IVariables: 51 `_list` : ``callable`` 52 List creator 53 54 `__list` : ``list`` 55 List cache (if possible) 56 """ 57
58 - def __init__(self, creator, autoreload):
59 """ 60 Initialization 61 62 :Parameters: 63 `creator` : ``callable`` 64 List creator function 65 66 `autoreload` : ``bool`` 67 Autoreload possible? 68 """ 69 if autoreload: 70 create = creator 71 else: 72 self.__list = None 73 def create(): 74 """ self-destroying creator """ 75 if self.__list is None: 76 self.__list = creator() 77 self._list = lambda: self.__list 78 return self.__list
79 self._list = create
80
81 - def __getattr__(self, name):
82 return getattr(self._list(), name)
83
84 - def __len__(self):
85 return len(self._list())
86
87 - def __repr__(self):
88 return repr(self._list())
89
90 - def __str__(self):
91 return str(self._list())
92
93 - def __add__(self, other):
94 return self._list() + other
95
96 - def __mul__(self, other):
97 return self._list() * other
98
99 - def __getitem__(self, index):
100 return self._list()[index]
101
102 - def __contains__(self, item):
103 return item in self._list()
104
105 - def __hash__(self):
106 return hash(self._list())
107
108 - def __cmp__(self, other):
109 return cmp(self._list(), other)
110
111 - def __iter__(self):
112 return iter(self._list())
113
114 115 -class TemplateList(list):
116 """ 117 Container for template names 118 119 This class contains the resulting template list, generated by the 120 layout code. 121 122 :IVariables: 123 `missing` : ``list`` or ``None`` 124 Missing overlays 125 """ 126 missing = None 127
128 - def __init__(*args, **kwargs): # pylint: disable = E0211
129 """ 130 Initialization 131 132 :Keywords: 133 `MISSING` : ``iterable`` 134 Missing overlay list 135 """ 136 self, args = args[0], args[1:] 137 missing = kwargs.pop('MISSING', None) 138 if kwargs: 139 raise TypeError("Unrecognized keywords") 140 super(TemplateList, self).__init__(*args) 141 self.missing = list(missing or ()) or None
142
143 - def __repr__(self):
144 """ 145 Debug representation 146 147 :Return: The debug string 148 :Rtype: ``str`` 149 """ 150 return "%s(%s%s,%sMISSING=%s%s)" % ( 151 self.__class__.__name__, 152 self and '\n' or '', 153 _pprint.pformat(list(self)), 154 self and '\n\n ' or ' ', 155 self.missing and '\n' or ' ', 156 _pprint.pformat(self.missing) 157 )
158 159 @classmethod
160 - def discover(cls, loader, names, use=None, ignore=None):
161 """ 162 Disover templates and create a new template list 163 164 :Parameters: 165 `loader` : `Loader` 166 Template loader 167 168 `names` : ``iterable`` 169 Base names. These templates are always added first, in order and 170 define the initial list of overlays to discover. 171 172 `use` : ``dict`` 173 Extra target mapping (overlay name -> template name). This is 174 used, before the global overlay mapping is asked. Pass ambiguous 175 overlay decisions here, or disable certain overlays by passing 176 ``None`` as name. 177 178 `ignore` : ``iterable`` 179 List of template names to ignore completely. 180 181 :Return: Template list 182 :Rtype: `TemplateList` 183 184 :Exceptions: 185 - `TemplateUndecided` : Ambiguous template decisions 186 - `DependencyCycle` : A dependency cycle occured 187 """ 188 result, missing, undecided = discover( 189 loader, names, use=use, ignore=ignore 190 ) 191 if undecided: 192 raise TemplateUndecided(undecided) 193 return cls(result, MISSING=missing)
194
195 196 -class Layout(object):
197 """ 198 Create template lists based on a start configuration 199 200 :IVariables: 201 `_base` : ``tuple`` 202 Base template list 203 204 `_use` : ``dict`` 205 extra overlay -> filename mapping 206 207 `_ignore` : ``frozenset`` 208 Template names to ignore 209 """ 210
211 - def __init__(self, loader, *base, **kwargs):
212 """ 213 Initialization 214 215 :Parameters: 216 `loader` : `Loader` 217 Template loader 218 219 `base` : ``tuple`` 220 Base template list 221 222 `kwargs` : ``dict`` 223 Keywords 224 225 :Keywords: 226 `use` : ``dict`` 227 extra overlay -> filename mapping 228 229 `ignore` : ``iterable`` 230 template names to ignore 231 232 `cls` : ``callable`` 233 template list factory. If omitted or ``None``, `TemplateList` is 234 used. 235 236 `lazy` : ``bool`` 237 Lazy loading? 238 """ 239 use = kwargs.pop('use', None) 240 ignore = kwargs.pop('ignore', None) 241 cls = kwargs.pop('cls', None) 242 lazy = kwargs.pop('lazy', None) 243 if kwargs: 244 raise TypeError("Unrecognized keywords") 245 self._base = base 246 self._use = dict(use or ()) 247 self._ignore = frozenset(ignore or ()) 248 self._loader = loader 249 self._cls = cls is None and TemplateList or cls 250 self._lazy = lazy is None and True or bool(lazy)
251
252 - def extend(self, *base, **kwargs):
253 """ 254 Extend the layout and create a new one. 255 256 :Parameters: 257 `base` : ``tuple`` 258 Base template list 259 260 `kwargs` : ``dict`` 261 Keywords 262 263 :Keywords: 264 `use` : ``dict`` 265 extra overlay -> filename mapping 266 267 `ignore` : ``iterable`` 268 template names to ignore 269 270 `consider` : ``iterable`` 271 Template names to "unignore" 272 273 :Return: New layout 274 :Rtype: `Layout` 275 """ 276 use = kwargs.pop('use', None) 277 ignore = kwargs.pop('ignore', None) 278 consider = kwargs.pop('consider', None) 279 if kwargs: 280 raise TypeError("Unrecognized keywords") 281 newbase = tuple(self._base) + base 282 newuse = self._use 283 if use: 284 newuse = dict(newuse) 285 newuse.update(use) 286 newignore = self._ignore 287 if ignore or consider: 288 newignore = set(newignore) 289 if ignore: 290 newignore.update(set(ignore)) 291 if consider: 292 newignore -= set(consider) 293 return self.__class__(self._loader, *newbase, **dict( 294 use=newuse, ignore=newignore, cls=self._cls, lazy=self._lazy 295 ))
296
297 - def __call__(self, *names, **kwargs):
298 """ 299 Create a template list from this layout 300 301 :Parameters: 302 `names` : ``tuple`` 303 Base template list 304 305 `kwargs` : ``dict`` 306 Keywords 307 308 :Keywords: 309 `use` : ``dict`` 310 extra overlay -> filename mapping 311 312 `ignore` : ``iterable`` 313 template names to ignore 314 315 `consider` : ``iterable`` 316 Template names to "unignore" 317 318 :Return: template list 319 :Rtype: `TemplateList` 320 """ 321 use_ = kwargs.pop('use', None) 322 ignore_ = kwargs.pop('ignore', None) 323 consider = kwargs.pop('consider', None) 324 if kwargs: 325 raise TypeError("Unrecognized keywords") 326 base = tuple(self._base) + names 327 use = dict(self._use) 328 if use_: 329 use.update(use_) 330 ignore = set(self._ignore) 331 if ignore_: 332 ignore.update(set(ignore_)) 333 if consider: 334 ignore -= set(consider) 335 336 lazy, autoreload = self._lazy, self._loader.autoreload() 337 def make_creator(base, use, ignore): 338 """ Make a new template list creator """ 339 cls, loader = self._cls, self._loader 340 def creator(): 341 """ Create """ 342 return cls.discover(loader, base, use=use, ignore=ignore)
343 return creator
344 creator = make_creator(base, use, ignore) 345 if not lazy and not autoreload: 346 return creator() 347 result = TemplateListProxy(creator, autoreload) 348 if not lazy: 349 iter(result) # trigger list creation 350 return result 351
352 353 -def distinct_overlays(tpl):
354 """ 355 Extract distinct overlay names of a template 356 357 Overlay names available both as target and source within the template 358 are discarded. 359 360 :Parameters: 361 `tpl` : `tdi.template.Template` 362 Template to inspect 363 364 :Return: set(targets), set(sources) 365 :Rtype: ``tuple`` 366 """ 367 targets = set(tpl.target_overlay_names) 368 sources = set(tpl.source_overlay_names) 369 return targets - sources, sources - targets
370
371 372 -def discover(loader, names, use=None, ignore=None):
373 """ 374 Find templates to use and order them topologically correct 375 376 :Parameters: 377 `loader` : `Loader` 378 Template loader 379 380 `names` : ``iterable`` 381 Base names. These templates are always added first, in order and 382 define the initial list of overlays to discover. 383 384 `use` : ``dict`` 385 Extra target mapping (overlay name -> template name). This is 386 used, before the global overlay mapping is asked. Pass ambiguous 387 overlay decisions here, or disable certain overlays by passing 388 ``None`` as name. 389 390 `ignore` : ``iterable`` 391 List of template names to ignore completely. 392 393 :Return: list(template names), set(missing overlays), 394 dict(undecidable overlays -> possible template names) 395 :Rtype: ``tuple`` 396 397 :Exceptions: 398 - `DependencyCycle` : A dependency cycle occured 399 """ 400 # pylint: disable = R0912 401 402 names, missing, undecided = list(names), set(), {} 403 if not names: 404 return names, missing, undecided 405 406 overlays = lambda x: distinct_overlays(loader.load(x)) 407 available = loader.available() 408 409 names.reverse() 410 dep = names.pop() 411 use, ignore = dict(use or ()), set(ignore or ()) 412 targets, graph = set(overlays(dep)[0]), _util.DependencyGraph() 413 414 # initial templates 415 while names: 416 tname = names.pop() 417 ttargets, tsources = overlays(tname) 418 targets -= tsources 419 targets |= ttargets 420 graph.add(dep, tname) 421 dep = tname 422 423 # automatic templates 424 targets = dict((target, set([dep])) for target in targets) 425 while targets: 426 target, deps = targets.popitem() 427 ttargets = None 428 429 if target in use: 430 tname = use[target] 431 if tname is None: 432 missing.add(target) 433 continue 434 else: 435 ttargets, tsources = overlays(tname) 436 if target not in tsources: 437 raise AssertionError('"use" source %r not in %r' % ( 438 target, tname 439 )) 440 else: 441 tnames = [ 442 tname 443 for tname in available.get(target) or () 444 if tname not in ignore 445 ] 446 if not tnames: 447 missing.add(target) 448 continue 449 elif len(tnames) > 1: 450 undecided[target] = tuple(sorted(tnames)) 451 continue 452 tname = tnames[0] 453 454 if ttargets is None: 455 ttargets = overlays(tname)[0] 456 for dep in deps: 457 graph.add(dep, tname) 458 for target in ttargets: 459 if target not in targets: 460 targets[target] = set() 461 targets[target].add(tname) 462 463 return graph.resolve(), missing, undecided
464
465 466 -class Loader(object):
467 """ 468 Find, load and select templates 469 470 :IVariables: 471 `_available` : ``dict`` or ``None`` 472 The mapping cache. This dict contains the overlay -> template mapping. 473 If ``None``, the dict is created during the next call of the 474 ``available`` method. 475 476 `_registered` : ``set`` 477 List of template names registered for autoreload 478 479 `_load` : callable 480 Loader 481 482 `_list` : callable 483 Lister 484 485 `_select` : callable 486 Selector 487 """ 488
489 - def __init__(self, list, load, select): # pylint: disable = W0622
490 """ 491 Initialization 492 493 :Parameters: 494 `list` : callable 495 Template lister. This function is called without parameters and 496 expected to return a list of all template names available. 497 Template names are hashable tokens (such as strings) identifying 498 the templates. They are passed to the `load` function if the 499 template needs to be loaded. 500 501 `load` : callable 502 Template loader. This function is called with a template name as 503 parameter and expected to return the actual template object. 504 505 `select` : callable 506 Template selector. This function is called with two parameters: 507 The loader instance (self) and the template name. It is expected 508 to return a bool value, which decides whether this template is 509 in the pool for automatic templates or not. 510 """ 511 self._available = None 512 self._registered = set() 513 self._load = load 514 self._list = list 515 self._select = select
516
517 - def autoreload(self):
518 """ 519 Autoreload templates? 520 521 :Return: Autoreloading available? 522 :Rtype: ``bool`` 523 """ 524 return bool(self._registered)
525
526 - def _callback(self, _):
527 """ Autoupdate callback - reset the source mapping """ 528 self._available = None
529
530 - def load(self, name):
531 """ 532 Load a single template and register the autoupdate callback 533 534 :Parameters: 535 `name` : hashable 536 Template name 537 538 :Return: The template 539 :Rtype: `tdi.template.Template` 540 """ 541 tpl = self._load(name) 542 if name not in self._registered: 543 register = getattr(tpl, 'autoupdate_register_callback', None) 544 if register is not None: 545 register(self._callback) 546 self._registered.add(name) 547 return tpl
548
549 - def available(self):
550 """ 551 Determine automatic overlay -> template name mapping 552 553 This method should only list the automatic overlay mappings. 554 555 :Return: source overlay name -> set(template name) 556 :Rtype: ``dict`` 557 """ 558 result = self._available 559 if result is None: 560 result = {} 561 for name in self._list(): 562 if self._select(self, name): 563 tpl = self.load(name) 564 for source in distinct_overlays(tpl)[1]: 565 if source not in result: 566 result[source] = set() 567 result[source].add(name) 568 self._available = result 569 return result
570