Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/genshi/template/directives.py: 28%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

308 statements  

1# -*- coding: utf-8 -*- 

2# 

3# Copyright (C) 2006-2009 Edgewall Software 

4# All rights reserved. 

5# 

6# This software is licensed as described in the file COPYING, which 

7# you should have received as part of this distribution. The terms 

8# are also available at http://genshi.edgewall.org/wiki/License. 

9# 

10# This software consists of voluntary contributions made by many 

11# individuals. For the exact contribution history, see the revision 

12# history and logs, available at http://genshi.edgewall.org/log/. 

13 

14"""Implementation of the various template directives.""" 

15 

16from genshi.compat import add_metaclass, text_type 

17from genshi.core import QName, Stream 

18from genshi.path import Path 

19from genshi.template.base import TemplateRuntimeError, TemplateSyntaxError, \ 

20 EXPR, _apply_directives, _eval_expr 

21from genshi.template.eval import Expression, _ast, _parse 

22 

23__all__ = ['AttrsDirective', 'ChooseDirective', 'ContentDirective', 

24 'DefDirective', 'ForDirective', 'IfDirective', 'MatchDirective', 

25 'OtherwiseDirective', 'ReplaceDirective', 'StripDirective', 

26 'WhenDirective', 'WithDirective'] 

27__docformat__ = 'restructuredtext en' 

28 

29 

30class DirectiveMeta(type): 

31 """Meta class for template directives.""" 

32 

33 def __new__(cls, name, bases, d): 

34 d['tagname'] = name.lower().replace('directive', '') 

35 return type.__new__(cls, name, bases, d) 

36 

37 

38@add_metaclass(DirectiveMeta) 

39class Directive(object): 

40 """Abstract base class for template directives. 

41  

42 A directive is basically a callable that takes three positional arguments: 

43 ``ctxt`` is the template data context, ``stream`` is an iterable over the 

44 events that the directive applies to, and ``directives`` is is a list of 

45 other directives on the same stream that need to be applied. 

46  

47 Directives can be "anonymous" or "registered". Registered directives can be 

48 applied by the template author using an XML attribute with the 

49 corresponding name in the template. Such directives should be subclasses of 

50 this base class that can be instantiated with the value of the directive 

51 attribute as parameter. 

52  

53 Anonymous directives are simply functions conforming to the protocol 

54 described above, and can only be applied programmatically (for example by 

55 template filters). 

56 """ 

57 __slots__ = ['expr'] 

58 

59 def __init__(self, value, template=None, namespaces=None, lineno=-1, 

60 offset=-1): 

61 self.expr = self._parse_expr(value, template, lineno, offset) 

62 

63 @classmethod 

64 def attach(cls, template, stream, value, namespaces, pos): 

65 """Called after the template stream has been completely parsed. 

66  

67 :param template: the `Template` object 

68 :param stream: the event stream associated with the directive 

69 :param value: the argument value for the directive; if the directive was 

70 specified as an element, this will be an `Attrs` instance 

71 with all specified attributes, otherwise it will be a 

72 `unicode` object with just the attribute value 

73 :param namespaces: a mapping of namespace URIs to prefixes 

74 :param pos: a ``(filename, lineno, offset)`` tuple describing the 

75 location where the directive was found in the source 

76  

77 This class method should return a ``(directive, stream)`` tuple. If 

78 ``directive`` is not ``None``, it should be an instance of the `Directive` 

79 class, and gets added to the list of directives applied to the substream 

80 at runtime. `stream` is an event stream that replaces the original 

81 stream associated with the directive. 

82 """ 

83 return cls(value, template, namespaces, *pos[1:]), stream 

84 

85 def __call__(self, stream, directives, ctxt, **vars): 

86 """Apply the directive to the given stream. 

87  

88 :param stream: the event stream 

89 :param directives: a list of the remaining directives that should 

90 process the stream 

91 :param ctxt: the context data 

92 :param vars: additional variables that should be made available when 

93 Python code is executed 

94 """ 

95 raise NotImplementedError 

96 

97 def __repr__(self): 

98 expr = '' 

99 if getattr(self, 'expr', None) is not None: 

100 expr = ' "%s"' % self.expr.source 

101 return '<%s%s>' % (type(self).__name__, expr) 

102 

103 @classmethod 

104 def _parse_expr(cls, expr, template, lineno=-1, offset=-1): 

105 """Parses the given expression, raising a useful error message when a 

106 syntax error is encountered. 

107 """ 

108 try: 

109 return expr and Expression(expr, template.filepath, lineno, 

110 lookup=template.lookup) or None 

111 except SyntaxError as err: 

112 err.msg += ' in expression "%s" of "%s" directive' % (expr, 

113 cls.tagname) 

114 raise TemplateSyntaxError(err, template.filepath, lineno, 

115 offset + (err.offset or 0)) 

116 

117 

118def _assignment(ast): 

119 """Takes the AST representation of an assignment, and returns a 

120 function that applies the assignment of a given value to a dictionary. 

121 """ 

122 def _names(node): 

123 if isinstance(node, _ast.Tuple): 

124 return tuple([_names(child) for child in node.elts]) 

125 elif isinstance(node, _ast.Name): 

126 return node.id 

127 def _assign(data, value, names=_names(ast)): 

128 if type(names) is tuple: 

129 for idx in range(len(names)): 

130 _assign(data, value[idx], names[idx]) 

131 else: 

132 data[names] = value 

133 return _assign 

134 

135 

136class AttrsDirective(Directive): 

137 """Implementation of the ``py:attrs`` template directive. 

138  

139 The value of the ``py:attrs`` attribute should be a dictionary or a sequence 

140 of ``(name, value)`` tuples. The items in that dictionary or sequence are 

141 added as attributes to the element: 

142  

143 >>> from genshi.template import MarkupTemplate 

144 >>> tmpl = MarkupTemplate('''<ul xmlns:py="http://genshi.edgewall.org/"> 

145 ... <li py:attrs="foo">Bar</li> 

146 ... </ul>''') 

147 >>> print(tmpl.generate(foo={'class': 'collapse'})) 

148 <ul> 

149 <li class="collapse">Bar</li> 

150 </ul> 

151 >>> print(tmpl.generate(foo=[('class', 'collapse')])) 

152 <ul> 

153 <li class="collapse">Bar</li> 

154 </ul> 

155  

156 If the value evaluates to ``None`` (or any other non-truth value), no 

157 attributes are added: 

158  

159 >>> print(tmpl.generate(foo=None)) 

160 <ul> 

161 <li>Bar</li> 

162 </ul> 

163 """ 

164 __slots__ = [] 

165 

166 def __call__(self, stream, directives, ctxt, **vars): 

167 def _generate(): 

168 kind, (tag, attrib), pos = next(stream) 

169 attrs = _eval_expr(self.expr, ctxt, vars) 

170 if attrs: 

171 if isinstance(attrs, Stream): 

172 try: 

173 attrs = next(iter(attrs)) 

174 except StopIteration: 

175 attrs = [] 

176 elif not isinstance(attrs, list): # assume it's a dict 

177 attrs = attrs.items() 

178 attrib |= [ 

179 (QName(n), v is not None and text_type(v).strip() or None) 

180 for n, v in attrs 

181 ] 

182 yield kind, (tag, attrib), pos 

183 for event in stream: 

184 yield event 

185 

186 return _apply_directives(_generate(), directives, ctxt, vars) 

187 

188 

189class ContentDirective(Directive): 

190 """Implementation of the ``py:content`` template directive. 

191  

192 This directive replaces the content of the element with the result of 

193 evaluating the value of the ``py:content`` attribute: 

194  

195 >>> from genshi.template import MarkupTemplate 

196 >>> tmpl = MarkupTemplate('''<ul xmlns:py="http://genshi.edgewall.org/"> 

197 ... <li py:content="bar">Hello</li> 

198 ... </ul>''') 

199 >>> print(tmpl.generate(bar='Bye')) 

200 <ul> 

201 <li>Bye</li> 

202 </ul> 

203 """ 

204 __slots__ = [] 

205 

206 @classmethod 

207 def attach(cls, template, stream, value, namespaces, pos): 

208 if type(value) is dict: 

209 raise TemplateSyntaxError('The content directive can not be used ' 

210 'as an element', template.filepath, 

211 *pos[1:]) 

212 expr = cls._parse_expr(value, template, *pos[1:]) 

213 return None, [stream[0], (EXPR, expr, pos), stream[-1]] 

214 

215 

216class DefDirective(Directive): 

217 """Implementation of the ``py:def`` template directive. 

218  

219 This directive can be used to create "Named Template Functions", which 

220 are template snippets that are not actually output during normal 

221 processing, but rather can be expanded from expressions in other places 

222 in the template. 

223  

224 A named template function can be used just like a normal Python function 

225 from template expressions: 

226  

227 >>> from genshi.template import MarkupTemplate 

228 >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"> 

229 ... <p py:def="echo(greeting, name='world')" class="message"> 

230 ... ${greeting}, ${name}! 

231 ... </p> 

232 ... ${echo('Hi', name='you')} 

233 ... </div>''') 

234 >>> print(tmpl.generate(bar='Bye')) 

235 <div> 

236 <p class="message"> 

237 Hi, you! 

238 </p> 

239 </div> 

240  

241 If a function does not require parameters, the parenthesis can be omitted 

242 in the definition: 

243  

244 >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"> 

245 ... <p py:def="helloworld" class="message"> 

246 ... Hello, world! 

247 ... </p> 

248 ... ${helloworld()} 

249 ... </div>''') 

250 >>> print(tmpl.generate(bar='Bye')) 

251 <div> 

252 <p class="message"> 

253 Hello, world! 

254 </p> 

255 </div> 

256 """ 

257 __slots__ = ['name', 'args', 'star_args', 'dstar_args', 'defaults'] 

258 

259 def __init__(self, args, template, namespaces=None, lineno=-1, offset=-1): 

260 Directive.__init__(self, None, template, namespaces, lineno, offset) 

261 ast = _parse(args).body 

262 self.args = [] 

263 self.star_args = None 

264 self.dstar_args = None 

265 self.defaults = {} 

266 if isinstance(ast, _ast.Call): 

267 self.name = ast.func.id 

268 for arg in ast.args: 

269 if hasattr(_ast, 'Starred') and isinstance(arg, _ast.Starred): 

270 # Python 3.5+ 

271 self.star_args = arg.value.id 

272 else: 

273 # only names 

274 self.args.append(arg.id) 

275 for kwd in ast.keywords: 

276 if kwd.arg is None: 

277 # Python 3.5+ 

278 self.dstar_args = kwd.value.id 

279 else: 

280 self.args.append(kwd.arg) 

281 exp = Expression(kwd.value, template.filepath, 

282 lineno, lookup=template.lookup) 

283 self.defaults[kwd.arg] = exp 

284 if getattr(ast, 'starargs', None): 

285 self.star_args = ast.starargs.id 

286 if getattr(ast, 'kwargs', None): 

287 self.dstar_args = ast.kwargs.id 

288 else: 

289 self.name = ast.id 

290 

291 @classmethod 

292 def attach(cls, template, stream, value, namespaces, pos): 

293 if type(value) is dict: 

294 value = value.get('function') 

295 return super(DefDirective, cls).attach(template, stream, value, 

296 namespaces, pos) 

297 

298 def __call__(self, stream, directives, ctxt, **vars): 

299 stream = list(stream) 

300 

301 def function(*args, **kwargs): 

302 scope = {} 

303 args = list(args) # make mutable 

304 for name in self.args: 

305 if args: 

306 scope[name] = args.pop(0) 

307 else: 

308 if name in kwargs: 

309 val = kwargs.pop(name) 

310 else: 

311 val = _eval_expr(self.defaults.get(name), ctxt, vars) 

312 scope[name] = val 

313 if not self.star_args is None: 

314 scope[self.star_args] = args 

315 if not self.dstar_args is None: 

316 scope[self.dstar_args] = kwargs 

317 ctxt.push(scope) 

318 for event in _apply_directives(stream, directives, ctxt, vars): 

319 yield event 

320 ctxt.pop() 

321 function.__name__ = self.name 

322 

323 # Store the function reference in the bottom context frame so that it 

324 # doesn't get popped off before processing the template has finished 

325 # FIXME: this makes context data mutable as a side-effect 

326 ctxt.frames[-1][self.name] = function 

327 

328 return [] 

329 

330 def __repr__(self): 

331 return '<%s "%s">' % (type(self).__name__, self.name) 

332 

333 

334class ForDirective(Directive): 

335 """Implementation of the ``py:for`` template directive for repeating an 

336 element based on an iterable in the context data. 

337  

338 >>> from genshi.template import MarkupTemplate 

339 >>> tmpl = MarkupTemplate('''<ul xmlns:py="http://genshi.edgewall.org/"> 

340 ... <li py:for="item in items">${item}</li> 

341 ... </ul>''') 

342 >>> print(tmpl.generate(items=[1, 2, 3])) 

343 <ul> 

344 <li>1</li><li>2</li><li>3</li> 

345 </ul> 

346 """ 

347 __slots__ = ['assign', 'filename'] 

348 

349 def __init__(self, value, template, namespaces=None, lineno=-1, offset=-1): 

350 if ' in ' not in value: 

351 raise TemplateSyntaxError('"in" keyword missing in "for" directive', 

352 template.filepath, lineno, offset) 

353 assign, value = value.split(' in ', 1) 

354 ast = _parse(assign, 'exec') 

355 value = 'iter(%s)' % value.strip() 

356 self.assign = _assignment(ast.body[0].value) 

357 self.filename = template.filepath 

358 Directive.__init__(self, value, template, namespaces, lineno, offset) 

359 

360 @classmethod 

361 def attach(cls, template, stream, value, namespaces, pos): 

362 if type(value) is dict: 

363 value = value.get('each') 

364 return super(ForDirective, cls).attach(template, stream, value, 

365 namespaces, pos) 

366 

367 def __call__(self, stream, directives, ctxt, **vars): 

368 iterable = _eval_expr(self.expr, ctxt, vars) 

369 if iterable is None: 

370 return 

371 

372 assign = self.assign 

373 scope = {} 

374 stream = list(stream) 

375 for item in iterable: 

376 assign(scope, item) 

377 ctxt.push(scope) 

378 for event in _apply_directives(stream, directives, ctxt, vars): 

379 yield event 

380 ctxt.pop() 

381 

382 def __repr__(self): 

383 return '<%s>' % type(self).__name__ 

384 

385 

386class IfDirective(Directive): 

387 """Implementation of the ``py:if`` template directive for conditionally 

388 excluding elements from being output. 

389  

390 >>> from genshi.template import MarkupTemplate 

391 >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"> 

392 ... <b py:if="foo">${bar}</b> 

393 ... </div>''') 

394 >>> print(tmpl.generate(foo=True, bar='Hello')) 

395 <div> 

396 <b>Hello</b> 

397 </div> 

398 """ 

399 __slots__ = [] 

400 

401 @classmethod 

402 def attach(cls, template, stream, value, namespaces, pos): 

403 if type(value) is dict: 

404 value = value.get('test') 

405 return super(IfDirective, cls).attach(template, stream, value, 

406 namespaces, pos) 

407 

408 def __call__(self, stream, directives, ctxt, **vars): 

409 value = _eval_expr(self.expr, ctxt, vars) 

410 if value: 

411 return _apply_directives(stream, directives, ctxt, vars) 

412 return [] 

413 

414 

415class MatchDirective(Directive): 

416 """Implementation of the ``py:match`` template directive. 

417 

418 >>> from genshi.template import MarkupTemplate 

419 >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"> 

420 ... <span py:match="greeting"> 

421 ... Hello ${select('@name')} 

422 ... </span> 

423 ... <greeting name="Dude" /> 

424 ... </div>''') 

425 >>> print(tmpl.generate()) 

426 <div> 

427 <span> 

428 Hello Dude 

429 </span> 

430 </div> 

431 """ 

432 __slots__ = ['path', 'namespaces', 'hints'] 

433 

434 def __init__(self, value, template, hints=None, namespaces=None, 

435 lineno=-1, offset=-1): 

436 Directive.__init__(self, None, template, namespaces, lineno, offset) 

437 self.path = Path(value, template.filepath, lineno) 

438 self.namespaces = namespaces or {} 

439 self.hints = hints or () 

440 

441 @classmethod 

442 def attach(cls, template, stream, value, namespaces, pos): 

443 hints = [] 

444 if type(value) is dict: 

445 if value.get('buffer', '').lower() == 'false': 

446 hints.append('not_buffered') 

447 if value.get('once', '').lower() == 'true': 

448 hints.append('match_once') 

449 if value.get('recursive', '').lower() == 'false': 

450 hints.append('not_recursive') 

451 value = value.get('path') 

452 return cls(value, template, frozenset(hints), namespaces, *pos[1:]), \ 

453 stream 

454 

455 def __call__(self, stream, directives, ctxt, **vars): 

456 ctxt._match_templates.append((self.path.test(ignore_context=True), 

457 self.path, list(stream), self.hints, 

458 self.namespaces, directives)) 

459 return [] 

460 

461 def __repr__(self): 

462 return '<%s "%s">' % (type(self).__name__, self.path.source) 

463 

464 

465class ReplaceDirective(Directive): 

466 """Implementation of the ``py:replace`` template directive. 

467  

468 This directive replaces the element with the result of evaluating the 

469 value of the ``py:replace`` attribute: 

470  

471 >>> from genshi.template import MarkupTemplate 

472 >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"> 

473 ... <span py:replace="bar">Hello</span> 

474 ... </div>''') 

475 >>> print(tmpl.generate(bar='Bye')) 

476 <div> 

477 Bye 

478 </div> 

479  

480 This directive is equivalent to ``py:content`` combined with ``py:strip``, 

481 providing a less verbose way to achieve the same effect: 

482  

483 >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"> 

484 ... <span py:content="bar" py:strip="">Hello</span> 

485 ... </div>''') 

486 >>> print(tmpl.generate(bar='Bye')) 

487 <div> 

488 Bye 

489 </div> 

490 """ 

491 __slots__ = [] 

492 

493 @classmethod 

494 def attach(cls, template, stream, value, namespaces, pos): 

495 if type(value) is dict: 

496 value = value.get('value') 

497 if not value: 

498 raise TemplateSyntaxError('missing value for "replace" directive', 

499 template.filepath, *pos[1:]) 

500 expr = cls._parse_expr(value, template, *pos[1:]) 

501 return None, [(EXPR, expr, pos)] 

502 

503 

504class StripDirective(Directive): 

505 """Implementation of the ``py:strip`` template directive. 

506  

507 When the value of the ``py:strip`` attribute evaluates to ``True``, the 

508 element is stripped from the output 

509  

510 >>> from genshi.template import MarkupTemplate 

511 >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"> 

512 ... <div py:strip="True"><b>foo</b></div> 

513 ... </div>''') 

514 >>> print(tmpl.generate()) 

515 <div> 

516 <b>foo</b> 

517 </div> 

518  

519 Leaving the attribute value empty is equivalent to a truth value. 

520  

521 This directive is particulary interesting for named template functions or 

522 match templates that do not generate a top-level element: 

523  

524 >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"> 

525 ... <div py:def="echo(what)" py:strip=""> 

526 ... <b>${what}</b> 

527 ... </div> 

528 ... ${echo('foo')} 

529 ... </div>''') 

530 >>> print(tmpl.generate()) 

531 <div> 

532 <b>foo</b> 

533 </div> 

534 """ 

535 __slots__ = [] 

536 

537 def __call__(self, stream, directives, ctxt, **vars): 

538 def _generate(): 

539 if not self.expr or _eval_expr(self.expr, ctxt, vars): 

540 next(stream) # skip start tag 

541 previous = next(stream) 

542 for event in stream: 

543 yield previous 

544 previous = event 

545 else: 

546 for event in stream: 

547 yield event 

548 return _apply_directives(_generate(), directives, ctxt, vars) 

549 

550 

551class ChooseDirective(Directive): 

552 """Implementation of the ``py:choose`` directive for conditionally selecting 

553 one of several body elements to display. 

554  

555 If the ``py:choose`` expression is empty the expressions of nested 

556 ``py:when`` directives are tested for truth. The first true ``py:when`` 

557 body is output. If no ``py:when`` directive is matched then the fallback 

558 directive ``py:otherwise`` will be used. 

559  

560 >>> from genshi.template import MarkupTemplate 

561 >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/" 

562 ... py:choose=""> 

563 ... <span py:when="0 == 1">0</span> 

564 ... <span py:when="1 == 1">1</span> 

565 ... <span py:otherwise="">2</span> 

566 ... </div>''') 

567 >>> print(tmpl.generate()) 

568 <div> 

569 <span>1</span> 

570 </div> 

571  

572 If the ``py:choose`` directive contains an expression, the nested 

573 ``py:when`` directives are tested for equality to the ``py:choose`` 

574 expression: 

575  

576 >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/" 

577 ... py:choose="2"> 

578 ... <span py:when="1">1</span> 

579 ... <span py:when="2">2</span> 

580 ... </div>''') 

581 >>> print(tmpl.generate()) 

582 <div> 

583 <span>2</span> 

584 </div> 

585  

586 Behavior is undefined if a ``py:choose`` block contains content outside a 

587 ``py:when`` or ``py:otherwise`` block. Behavior is also undefined if a 

588 ``py:otherwise`` occurs before ``py:when`` blocks. 

589 """ 

590 __slots__ = ['matched', 'value'] 

591 

592 @classmethod 

593 def attach(cls, template, stream, value, namespaces, pos): 

594 if type(value) is dict: 

595 value = value.get('test') 

596 return super(ChooseDirective, cls).attach(template, stream, value, 

597 namespaces, pos) 

598 

599 def __call__(self, stream, directives, ctxt, **vars): 

600 info = [False, bool(self.expr), None] 

601 if self.expr: 

602 info[2] = _eval_expr(self.expr, ctxt, vars) 

603 ctxt._choice_stack.append(info) 

604 for event in _apply_directives(stream, directives, ctxt, vars): 

605 yield event 

606 ctxt._choice_stack.pop() 

607 

608 

609class WhenDirective(Directive): 

610 """Implementation of the ``py:when`` directive for nesting in a parent with 

611 the ``py:choose`` directive. 

612  

613 See the documentation of the `ChooseDirective` for usage. 

614 """ 

615 __slots__ = ['filename'] 

616 

617 def __init__(self, value, template, namespaces=None, lineno=-1, offset=-1): 

618 Directive.__init__(self, value, template, namespaces, lineno, offset) 

619 self.filename = template.filepath 

620 

621 @classmethod 

622 def attach(cls, template, stream, value, namespaces, pos): 

623 if type(value) is dict: 

624 value = value.get('test') 

625 return super(WhenDirective, cls).attach(template, stream, value, 

626 namespaces, pos) 

627 

628 def __call__(self, stream, directives, ctxt, **vars): 

629 info = ctxt._choice_stack and ctxt._choice_stack[-1] 

630 if not info: 

631 raise TemplateRuntimeError('"when" directives can only be used ' 

632 'inside a "choose" directive', 

633 self.filename, *(next(stream))[2][1:]) 

634 if info[0]: 

635 return [] 

636 if not self.expr and not info[1]: 

637 raise TemplateRuntimeError('either "choose" or "when" directive ' 

638 'must have a test expression', 

639 self.filename, *(next(stream))[2][1:]) 

640 if info[1]: 

641 value = info[2] 

642 if self.expr: 

643 matched = value == _eval_expr(self.expr, ctxt, vars) 

644 else: 

645 matched = bool(value) 

646 else: 

647 matched = bool(_eval_expr(self.expr, ctxt, vars)) 

648 info[0] = matched 

649 if not matched: 

650 return [] 

651 

652 return _apply_directives(stream, directives, ctxt, vars) 

653 

654 

655class OtherwiseDirective(Directive): 

656 """Implementation of the ``py:otherwise`` directive for nesting in a parent 

657 with the ``py:choose`` directive. 

658  

659 See the documentation of `ChooseDirective` for usage. 

660 """ 

661 __slots__ = ['filename'] 

662 

663 def __init__(self, value, template, namespaces=None, lineno=-1, offset=-1): 

664 Directive.__init__(self, None, template, namespaces, lineno, offset) 

665 self.filename = template.filepath 

666 

667 def __call__(self, stream, directives, ctxt, **vars): 

668 info = ctxt._choice_stack and ctxt._choice_stack[-1] 

669 if not info: 

670 raise TemplateRuntimeError('an "otherwise" directive can only be ' 

671 'used inside a "choose" directive', 

672 self.filename, *(next(stream))[2][1:]) 

673 if info[0]: 

674 return [] 

675 info[0] = True 

676 

677 return _apply_directives(stream, directives, ctxt, vars) 

678 

679 

680class WithDirective(Directive): 

681 """Implementation of the ``py:with`` template directive, which allows 

682 shorthand access to variables and expressions. 

683  

684 >>> from genshi.template import MarkupTemplate 

685 >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"> 

686 ... <span py:with="y=7; z=x+10">$x $y $z</span> 

687 ... </div>''') 

688 >>> print(tmpl.generate(x=42)) 

689 <div> 

690 <span>42 7 52</span> 

691 </div> 

692 """ 

693 __slots__ = ['vars'] 

694 

695 def __init__(self, value, template, namespaces=None, lineno=-1, offset=-1): 

696 Directive.__init__(self, None, template, namespaces, lineno, offset) 

697 self.vars = [] 

698 value = value.strip() 

699 try: 

700 ast = _parse(value, 'exec') 

701 for node in ast.body: 

702 if not isinstance(node, _ast.Assign): 

703 raise TemplateSyntaxError('only assignment allowed in ' 

704 'value of the "with" directive', 

705 template.filepath, lineno, offset) 

706 self.vars.append(([_assignment(n) for n in node.targets], 

707 Expression(node.value, template.filepath, 

708 lineno, lookup=template.lookup))) 

709 except SyntaxError as err: 

710 err.msg += ' in expression "%s" of "%s" directive' % (value, 

711 self.tagname) 

712 raise TemplateSyntaxError(err, template.filepath, lineno, 

713 offset + (err.offset or 0)) 

714 

715 @classmethod 

716 def attach(cls, template, stream, value, namespaces, pos): 

717 if type(value) is dict: 

718 value = value.get('vars') 

719 return super(WithDirective, cls).attach(template, stream, value, 

720 namespaces, pos) 

721 

722 def __call__(self, stream, directives, ctxt, **vars): 

723 frame = {} 

724 ctxt.push(frame) 

725 for targets, expr in self.vars: 

726 value = _eval_expr(expr, ctxt, vars) 

727 for assign in targets: 

728 assign(frame, value) 

729 for event in _apply_directives(stream, directives, ctxt, vars): 

730 yield event 

731 ctxt.pop() 

732 

733 def __repr__(self): 

734 return '<%s>' % (type(self).__name__)