Package Pyblio :: Package Format :: Module DSL
[hide private]
[frames] | no frames]

Source Code for Module Pyblio.Format.DSL

  1  # -*- coding: utf-8 -*- 
  2  # This file is part of pybliographer 
  3  #  
  4  # Copyright (C) 1998-2006 Frederic GOBRY 
  5  # Email : gobry@pybliographer.org 
  6  #           
  7  # This program is free software; you can redistribute it and/or 
  8  # modify it under the terms of the GNU General Public License 
  9  # as published by the Free Software Foundation; either version 2  
 10  # of the License, or (at your option) any later version. 
 11  #    
 12  # This program is distributed in the hope that it will be useful, 
 13  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 14  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 15  # GNU General Public License for more details.  
 16  #  
 17  # You should have received a copy of the GNU General Public License 
 18  # along with this program; if not, write to the Free Software 
 19  # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. 
 20  # 
 21   
 22  """ 
 23  Basic syntactic elements used to format a citation. 
 24   
 25  This module defines the base syntax elements providing the formatting 
 26  domain specific language. 
 27   
 28  """ 
 29   
 30   
 31  from Pyblio.Format import S2 
 32  from Pyblio.Format.S3 import Tag 
 33  from Pyblio.Format.Base import Missing 
 34   
 35  from Pyblio.Attribute import Txo 
 36   
 37  from gettext import gettext as _ 
 38   
39 -def _deferredText(text):
40 """Ensure the parameter is a stage 1 object.""" 41 if isinstance(text, (str, unicode)): 42 return _S1T(text) 43 return text
44 45
46 -class Glue(object):
47 """ A base class that known how to join together multiple 48 fragments of DSL code.""" 49
50 - def __add__(self, other):
51 return _Sum(self, _deferredText(other))
52
53 - def __radd__(self, other):
54 return _Sum(_deferredText(other), self)
55
56 - def __or__ (self, other):
57 return _Or(self, _deferredText(other))
58
59 - def __ror__(self, other):
60 return _Or(_deferredText(other), self)
61 62
63 -class _Sum(Glue):
64
65 - def __init__ (self, a, b):
66 self.a = a 67 self.b = b 68 return
69
70 - def __call__ (self, db, props={}):
71 a = self.a(db, props) 72 b = self.b(db, props) 73 return S2.Sum(a, b)
74
75 - def __repr__ (self):
76 return '_Sum(%s, %s)' % (repr(self.a), 77 repr(self.b))
78
79 -class _Or(Glue):
80
81 - def __init__(self, a, b):
82 self.a = a 83 self.b = b 84 return
85
86 - def __call__(self, db, props={}):
87 a = self.a(db, props) 88 b = self.b(db, props) 89 return S2.Or(a, b)
90
91 - def __repr__(self):
92 return '_Or(%s, %s)' % (repr(self.a), 93 repr(self.b))
94
95 -class _S1T(Glue):
96 """ This is a stage 1 text, ie a text that returns a stage 2 text 97 when called.""" 98
99 - def __init__(self, t):
100 self.t = t 101 return
102
103 - def __call__(self, db, props={}):
104 return S2.Text(self.t)
105
106 - def __repr__(self):
107 return '_S1T(%s)' % repr(self.t)
108 109
110 -def join(middle, last=None):
111 return _Join(middle, last)
112
113 -class _Join(Glue):
114 """ The join operator is used to join together multiple fragments 115 of records:: 116 117 citation = join(middle, last)[part1, part2, ...] 118 119 part1, part2, ... are joined together by inserting 'middle' 120 between them. If a part is missing, it is simply skipped. 121 If no part is available at all, the join fails. 122 123 It is possible to specify a different separator between the last 124 two parts. 125 """ 126
127 - def __init__(self, middle, last):
128 129 self.middle = _deferredText(middle) 130 131 if last: self.last = _deferredText(last) 132 else: self.last = self.middle 133 134 self.children = [] 135 return
136
137 - def __getitem__(self, children):
138 if not isinstance (children, (list, tuple)): 139 children = [children] 140 141 self.children.extend([_deferredText(t) for t in children]) 142 return self
143
144 - def __call__(self, db, props={}):
145 146 return S2.Join(self.middle(db, props), 147 self.last(db, props), 148 [child(db, props) for child in self.children])
149 150
151 -class switch(Glue):
152 """ The switch operator helps in bringing together multiple 153 citation parts, according to the value of a Txo. 154 155 >>> citation = switch('doctype') 156 >>> citation.case(ARTICLE=article, BOOK=book) 157 >>> citation.default(default) 158 159 """ 160
161 - def __init__(self, switch, _cases={}, _default=None):
162 self._switch = switch 163 164 # Warning: we do not affect the default parameter here. Doing 165 # so would lead to weird behavior if it is ever modified later 166 # on. 167 self._cases = {} 168 self._cases.update(_cases) 169 170 self._default = _default 171 return
172
173 - def case(self, **kargs):
174 new = switch(self._switch, self._cases, self._default) 175 176 for k, v in kargs.items(): 177 new._cases[k] = _deferredText(v) 178 return new
179
180 - def default(self, v):
181 new = switch(self._switch, self._cases, self._default) 182 new._default = _deferredText(v) 183 return new
184
185 - def __repr__(self):
186 return 'switch(%s)' % repr(self._switch)
187
188 - def __call__(self, db, props={}):
189 # first of all, get access to the actual Txo being checked. 190 191 parts = self._switch.split('.') 192 193 if len(parts) == 1: 194 a = self._switch 195 try: 196 s = db.schema[a] 197 except KeyError: 198 raise KeyError(_('%s: unknown attribute') % repr(self)) 199 200 def _fetch(record): 201 return record[a][0]
202 203 elif len(parts) == 2: 204 a, q = parts 205 try: 206 s = db.schema[parts[0]].q[parts[1]] 207 except KeyError: 208 raise KeyError(_('%s: unknown attribute') % repr(self)) 209 210 def _fetch(record): 211 return record[a][0].q[q][0]
212 213 214 if s.type is not Txo: 215 raise TypeError(_('%s: attribute is not a txo') % repr(self)) 216 217 group = db.schema.txo[s.group] 218 219 sw = {} 220 221 if self._default: 222 default = self._default(db, props) 223 else: 224 default = None 225 226 227 for name, child in self._cases.items(): 228 try: 229 txo = group.byname(name) 230 except KeyError: 231 raise KeyError(_('%s: unknown txo %s in group %s') % ( 232 repr(self), repr(name), repr(s.group))) 233 234 sw[Txo(txo)] = child(db, props) 235 236 return S2.Switch(_fetch, sw, default) 237 238
239 -class i18n(Glue):
240 """ Translatable content. 241 242 To create translatable content, do: 243 244 >>> citation = i18n(fr=u'En français', 245 en=u'In english', 246 default=u'Zloktagrok') 247 248 >>> compiled = citation(db, props={'ln': 'fr'}) 249 """ 250
251 - def __init__(self, **langs):
252 253 self._langs = {} 254 255 for k, v in langs.iteritems(): 256 if k == 'default': k = '' 257 self._langs[k] = _deferredText(v) 258 return
259
260 - def __call__(self, db, props={}):
261 262 ln = props.get('ln', '') 263 264 try: 265 c = self._langs[ln] 266 except KeyError: 267 c = self._langs[''] 268 269 return c(db, props)
270 271 272 # ================================================== 273 # Attribute accessors 274 # ================================================== 275
276 -class _Validated(Glue):
277 """ Base class for attribute accessors, providing some checks for 278 stage 2.""" 279
280 - def __init__(self, field):
281 self._f = field 282 return
283 284
285 - def __call__(self, db, props={}):
286 """ Return a compiled version of the attribute accessor.""" 287 288 parts = self._f.split('.') 289 if len(parts) == 1: 290 try: 291 s = db.schema[self._f] 292 except KeyError: 293 raise KeyError(_('%s: unknown attribute') % ( 294 repr(self),)) 295 296 return self._fetch_a(self._f) 297 298 elif len(parts) == 2: 299 an, qn = parts 300 try: 301 s = db.schema[an] 302 except KeyError: 303 raise KeyError(_('%s: unknown attribute') % ( 304 repr(self),)) 305 try: 306 q = s.q[parts[1]] 307 except KeyError: 308 raise KeyError(_('%s: unknown qualifier') % ( 309 repr(self),)) 310 311 return self._fetch_q(an, qn) 312 313 314 else: 315 raise SyntaxError(_('%s: illegal attribute syntax') % ( 316 repr(self),))
317 318
319 -class all(_Validated):
320
321 - def __repr__(self):
322 return 'all(%s)' % repr(self._f)
323 324
325 - def _fetch_a(self, f):
326 def _fetch(record): 327 try: 328 return record[f] 329 except (KeyError, IndexError), msg: 330 raise Missing (_('%s: no such attribute in record') % repr(self))
331 332 return _fetch
333
334 - def _fetch_q(self, an, qn):
335 def _fetch(record): 336 try: 337 return record[an][0].q[qn] 338 except (KeyError, IndexError), msg: 339 raise Missing (_('%s: no such attribute in record') % repr(self))
340 341 return _fetch 342
343 -class one(_Validated):
344
345 - def __repr__(self):
346 return 'one(%s)' % repr(self._f)
347 348
349 - def _fetch_a(self, f):
350 def _fetch(record): 351 try: 352 return record[f][0] 353 except (KeyError, IndexError), msg: 354 raise Missing (_('%s: no such attribute in record') % repr(self))
355 356 return _fetch
357
358 - def _fetch_q(self, an, qn):
359 def _fetch(record): 360 try: 361 return record[an][0].q[qn][0] 362 except (KeyError, IndexError), msg: 363 raise Missing (_('%s: no such attribute in record') % repr(self))
364 365 return _fetch 366 367
368 -class _record_key(Glue):
369 - def __call__(self, db, props={}):
370 def _fetch(record): 371 return str(record.key)
372 return _fetch
373 374 record_key = _record_key() 375 376 # ================================================== 377 # Tags 378 # ================================================== 379
380 -class _SynTag(object):
381 """ This is a layout tag before its [] marker. """ 382
383 - def __init__ (self, tag):
384 self.tag = tag.lower () 385 self.attributes = {}
386
387 - def __call__(self, **kw):
388 """Change attributes of this tag. This is implemented using 389 __call__ because it then allows the natural syntax:: 390 391 A (href="http://...") 392 393 """ 394 if not kw: 395 return self 396 397 for k, v in kw.iteritems(): 398 if k[-1] == '_': 399 k = k[:-1] 400 elif k[0] == '_': 401 k = k[1:] 402 self.attributes[k] = _deferredText(v) 403 return self
404
405 - def __add__(self, other):
406 return _Tag('t', [self, _deferredText(other)], {})
407
408 - def __radd__(self, other):
409 return _Tag('t', [_deferredText(other), self], {})
410
411 - def __getitem__ (self, children):
412 if not isinstance(children, (list, tuple)): 413 children = [children] 414 415 children = [_deferredText(child) for child in children] 416 417 return _Tag(self.tag, children, self.attributes)
418
419 -class _Tag(Glue):
420 421 """ This is a layout tag after its [] marker, but before the 422 compilation.""" 423
424 - def __init__ (self, tag, children, attributes):
425 426 self.tag = tag 427 self.children = children 428 self.attributes = attributes
429
430 - def __repr__(self):
431 rstr = '' 432 if self.attributes: 433 rstr += ', attributes=%r' % self.attributes 434 if self.children: 435 rstr += ', children=%s' % repr(self.children) 436 437 return "DSL.Tag(%r%s)" % (self.tag, rstr)
438
439 - def __call__(self, db, props={}):
440 children = [child(db, props) for child in self.children] 441 kwargs = {} 442 for k, v in self.attributes.items(): 443 try: 444 kwargs[k] = v(db, props) 445 except TypeError: 446 print repr(v) 447 448 return Tag(self.tag, children, kwargs)
449
450 - def __add__(self, other):
451 return _Tag('t', [self, _deferredText(other)], {})
452
453 - def __radd__(self, other):
454 return _Tag('t', [_deferredText(other), self], {})
455 456 457
458 -class _Proto(str):
459 """Proto is a string subclass. Instances of Proto, which are constructed 460 with a string, will construct Tag instances in response to __call__ 461 and __getitem__, delegating responsibility to the tag. 462 """ 463 __slots__ = [] 464
465 - def __call__(self, **kw):
466 return _SynTag(self)(**kw)
467
468 - def __getitem__(self, children):
469 return _SynTag(self)[children]
470 471 472 glob = globals () 473 474 for t in ('A', 'B', 'I', 'Small', 'Span'): 475 glob[t] = _Proto(t) 476 477 BR = _Proto('BR')[_S1T('')] 478 479 480 # =================================================== 481 # Helper for building simple additional DSL functions 482 # =================================================== 483 484
485 -def lazy(fn):
486 487 """ Transform a simple function into a lazy function lifted in the 488 formatting system. 489 490 This is only sugar : the initial function must be aware that every 491 argument must be made strict by calling them before use. 492 """ 493 494 class _caller(Glue): 495 def __init__ (self, * args, ** kargs): 496 self.__args = [_deferredText(arg) for arg in args] 497 498 for k, v in kargs.items(): 499 kargs[k] = _deferredText(v) 500 501 self.__kargs = kargs
502 503 def __call__(self, db, props={}): 504 args = [arg(db, props) for arg in self.__args] 505 kargs = {} 506 for k, v in self.__kargs.items(): 507 kargs[k] = v(db, props) 508 509 def _late(record): 510 return fn(record, *args, **kargs) 511 512 return _late 513 514 return _caller 515