Changeset 406
- Timestamp:
- 05/01/07 23:27:36 (2 years ago)
- Files:
-
- cly/trunk/cly/console.py (added)
- cly/trunk/cly/__init__.py (modified) (16 diffs)
- cly/trunk/cly/interactive.py (modified) (2 diffs)
- cly/trunk/cly/validators.py (modified) (7 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
cly/trunk/cly/__init__.py
r403 r406 26 26 """ Report a parse error. Output is formatted using string templates, 27 27 where variables are passed as arguments to the constructor. 28 28 29 29 >>> print ParseError(Context(None, 'foo bar'), "remaining=$remaining, time=$time", time=123) 30 30 remaining=foo bar, time=123 … … 56 56 class Node(object): 57 57 """ The base class for all grammar nodes. 58 58 59 59 Document strings are not optional: 60 60 >>> Node() … … 82 82 order = 0 83 83 group = 0 84 # Input must match return value of `candidates()`. 84 85 match_candidates = False 85 match_help = False86 # Number of times this node can be traversed in a given context 86 87 traversals = 1 87 88 88 89 def __init__(self, help, pattern=None, name=None, separator=None, **options): 89 90 """Construct a new CLY grammar node. 90 91 91 92 `help` can be either a help string, or a callable returning an iterable 92 93 of (key, help) pairs. … … 117 118 118 119 def _set_name(self, name): 120 """Set the name of this node. If the Node does not have an existing 121 matching pattern associated with it, a pattern will be created using 122 the name.""" 119 123 self._name = name 120 124 if isinstance(name, basestring) and self.pattern is None: … … 165 169 key=lambda i: (i.group, i.order, i.name)) 166 170 for child in children: 167 yield child171 yield child 168 172 169 173 def children(self, context, follow=False): … … 182 186 if follow: 183 187 for branch in follow_branch(child): 184 if child.valid(context):188 if branch.valid(context): 185 189 yield branch 186 190 else: … … 307 311 class Alias(Node): 308 312 """ An alias for another node. 309 313 310 314 An Alias overrides the follow() method to return aliased Nodes. Globs are 311 315 supported. … … 324 328 Node.__init__(self, "<alias for '%s'>" % alias, **options) 325 329 self._alias = alias 330 331 def valid(self, context): 332 alias = self.parser.find(self.alias) 333 return alias.valid(context) 334 335 def selected(self, context, match): 336 """ This node was selected by the parser. """ 337 alias = self.parser.find(self.alias) 338 context.traverse(alias) 326 339 327 340 def follow(self, context): … … 339 352 return '<%s:%s for %s>' % (self.__class__.__name__, self.path(), 340 353 self.alias) 354 341 355 342 356 class Action(Node): … … 379 393 else: 380 394 return self.callback(**context.vars) 381 395 382 396 def callback(self, *args, **kwargs): 383 raise UnexpectedEOL(None) 397 raise UnexpectedEOL(None) 384 398 385 399 def selected(self, context, match): … … 391 405 class Validator(Node): 392 406 """Validate and record the users input in the `vars` member of the context. 393 The Node name is used as the variable name. If `accumulate` is True, the 394 resultant variable will be a list.""" 407 The Node name is used as the variable name. If `traversals` is > 1 the 408 validator will accumulate values into a list. 409 """ 395 410 396 411 pattern = r'\w+' 397 accumulate = False398 412 399 413 def valid(self, context): 400 if self. accumulate is Trueor \401 (self. accumulate is Falseand self.name not in context.vars) or \402 len(context.vars.get(self.name, [])) < self. accumulate:414 if self.traversals > 1 or \ 415 (self.traversals == 1 and self.name not in context.vars) or \ 416 len(context.vars.get(self.name, [])) < self.traversals: 403 417 return Node.valid(self, context) 404 418 return False … … 410 424 raise ValidationError(context, token=match.group(), 411 425 exception=unicode(e)) 412 if self. accumulate:426 if self.traversals > 1: 413 427 context.vars.setdefault(self.name, []).append(value) 414 428 else: 415 429 context.vars[self.name] = value 430 return Node.selected(self, context, match) 416 431 417 432 def validator(self, match): … … 454 469 class LazyHelp(Help): 455 470 """ Lazily generate help from a Node. 456 471 457 472 If the Node does not have a custom pattern, the help will be in the form 458 473 (name, text), otherwise it will be in the form (<name>, text). 459 460 474 """ 461 475 def __init__(self, node, text): … … 579 593 def traversed(self, node): 580 594 """ How many times has node been traversed in this context? """ 581 return self._traversed.get(node.path , 0)595 return self._traversed.get(node.path(), 0) 582 596 583 597 def __repr__(self): … … 595 609 def __iter__(self): 596 610 """ Walk every node in the grammar. 597 611 598 612 >>> from cly import * 599 613 >>> parser = Parser(Grammar(one=Node('One'), two=Node('Two', three=Node('Three')))) … … 615 629 def parse(self, command, user_context=None): 616 630 """ Parse command using the current grammar. 617 631 618 632 >>> parser = Parser(Grammar(one=Node('One'), two=Node('Two', three=Node('Three', 619 633 ... action=Action('Do stuff', lambda: "foo bar"))))) cly/trunk/cly/interactive.py
r356 r406 26 26 def __init__(self, parser, application='cly', prompt=None, 27 27 user_context=None, history_file=None, history_length=500, 28 completion_delimiters='`~!#$%^&*()=+[{]}\|;\'",<>? \t'): 28 completion_key='tab', 29 completion_delimiters='`!#$%^&*()=+[{]}\|;\'",<>? \t'): 29 30 if prompt is None: 30 31 prompt = application + '> ' … … 48 49 pass 49 50 50 readline.parse_and_bind(" tab: complete")51 readline.parse_and_bind("%s: complete" % completion_key) 51 52 readline.set_completer_delims(self.completion_delimiters) 52 53 readline.set_completer(Interact.__cli_completion) cly/trunk/cly/validators.py
r355 r406 110 110 class IP(Validator): 111 111 """ Match an IP address. 112 112 113 113 >>> from cly import * 114 114 >>> parser = Parser(Grammar(foo=IP('Foo'))) … … 157 157 class EMail(Validator): 158 158 """ Match an E-Mail address. 159 159 160 160 >>> from cly import * 161 161 >>> parser = Parser(Grammar(foo=EMail('Foo'))) … … 168 168 class LocalFile(Validator): 169 169 """ Match and provide completion candidates for local files. 170 170 171 171 >>> from cly import * 172 172 >>> parser = Parser(Grammar(foo=LocalFile('Foo'))) … … 181 181 def match(self, context): 182 182 match = Validator.match(self, context) 183 if match and os.path.exists( match.group()):183 if match and os.path.exists(os.path.expanduser(match.group())): 184 184 return match 185 185 … … 188 188 from fnmatch import fnmatch 189 189 190 if text.startswith('~'): 191 if '/' in text: 192 short_home = text[:text.index('/')] 193 else: 194 short_home = text 195 expanded_home = os.path.expanduser(short_home) 196 text = os.path.expanduser(text) 197 else: 198 short_home = None 199 200 text = os.path.expanduser(text) 190 201 dir = os.path.dirname(text) or os.path.curdir 191 202 file = os.path.basename(text) … … 195 206 if file.startswith(cwd): 196 207 return file[len(cwd):] 208 if short_home and file.startswith(expanded_home): 209 return short_home + file[len(expanded_home):] 197 210 return file 198 211 … … 223 236 for f in candidates if self.dotfiles or f[0] != '.'] 224 237 238 225 239 if __name__ == '__main__': 226 240 import doctest
