Changeset 355
- Timestamp:
- 10/11/06 21:11:30 (2 years ago)
- Files:
-
- cly/trunk/cly/__init__.py (modified) (23 diffs)
- cly/trunk/cly/interactive.py (modified) (4 diffs)
- cly/trunk/cly/validators.py (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
cly/trunk/cly/__init__.py
r352 r355 69 69 The default is for the node to match the name of the Node: 70 70 >>> a = Node('Something', name='something') 71 >>> a.pattern .pattern== a.name71 >>> a.pattern == a.name 72 72 True 73 73 … … 77 77 pattern = None 78 78 separator = r'\s+|\s*$' 79 # Ordering within a group 79 80 order = 0 80 81 group = 0 81 82 match_candidates = False 83 match_help = False 84 traversals = 1 82 85 83 86 def __init__(self, doc, pattern=None, name=None, separator=None, **options): 84 87 """Construct a new CLY grammar node. 85 88 86 `doc` can be either a help string or a tuple of (command, help), for87 specifying the command text to match. If command is not provided, the88 Node name is used.89 `doc` can be either a help string, a tuple of (command, help), or a 90 lambda returning either of these, for specifying the command text to 91 match. If command is not provided, the Node name is used. 89 92 90 93 `pattern` is a regular expression for matching user input.""" … … 144 147 key=lambda i: (i.group, i.order, i.name)) 145 148 for child in children: 146 yield child149 yield child 147 150 148 151 def children(self, context, follow=False): 149 152 """ Iterate over child nodes, optionally following branches. """ 153 def follow_branch(child): 154 branches = list(child.follow(context)) 155 if branches: 156 for branch in branches: 157 yield branch 158 follow_branch(branch) 159 else: 160 yield child 161 150 162 for child in self: 151 if follow: 152 followed = child.branch(context) 153 while followed is not None: 154 child = followed 155 followed = followed.branch(context) 156 yield child 163 if child.valid(context): 164 if follow: 165 for branch in follow_branch(child): 166 if child.valid(context): 167 yield branch 168 else: 169 yield child 170 171 def follow(self, context): 172 """ Return alternative Nodes to traverse. """ 173 return [] 157 174 158 175 def chosen(self, context, match): 159 176 """ This node was chosen by the parser. """ 177 context.traverse(self) 160 178 161 179 def next(self, context): … … 173 191 Must include separator in determining whether a match was 174 192 successful. """ 193 if not self.valid(context): 194 return None 175 195 match = self._pattern.match(context.command, context.cursor) 176 196 if match: … … 236 256 [('one', 'One')] 237 257 """ 238 if isinstance(self.doc, (tuple, list)): 239 yield self.doc 258 doc = self.doc 259 while callable(doc): 260 doc = doc(context) 261 if isinstance(doc, (tuple, list)): 262 yield doc 240 263 else: 241 264 if self.name == self.pattern: 242 yield (self.name, self.doc)265 yield (self.name, doc) 243 266 else: 244 yield ('<%s>' % self.name, self.doc)267 yield ('<%s>' % self.name, doc) 245 268 246 269 def candidates(self, context, text): … … 256 279 if key[0] != '<' and key.startswith(text): 257 280 yield key + ' ' 258 259 def branch(self, context):260 """ Return an alternative Node to traverse, or None. """261 return None262 281 263 282 def find(self, path): … … 281 300 raise InvalidNodePath(posixpath.join(self.path(), path.strip('/'))) 282 301 302 def valid(self, context): 303 """ Is this node valid in the given context? """ 304 # Node is invalid if traversed more than self.traversals times 305 return context.traversed(self) < self.traversals 306 283 307 def __repr__(self): 284 308 return '<%s:%s>' % (self.__class__.__name__, self.path() or '<root>') … … 288 312 """ An alias for another node. 289 313 290 An Alias overrides the branch() method to return the aliased Node. 314 An Alias overrides the follow() method to return aliased Nodes. Globs are 315 supported. 291 316 292 317 >>> top = Node('Top', name='top', one=Node('One'), two=Node('Two', … … 296 321 """ 297 322 323 pattern = '' 298 324 alias = property(lambda self: posixpath.normpath( 299 325 posixpath.join(self.path(), self._alias))) … … 303 329 self._alias = alias 304 330 305 def branch(self, context): 306 return self.parser.find(self.alias) 331 def follow(self, context): 332 try: 333 yield self.parser.find(self.alias) 334 except InvalidNodePath: 335 from fnmatch import fnmatch 336 start = self.parser.find(posixpath.dirname(self.alias)) 337 match = posixpath.basename(self.alias) 338 for child in start.children(context, True): 339 if fnmatch(child.name, match): 340 yield child 307 341 308 342 def __repr__(self): … … 311 345 312 346 class Action(Node): 313 """ Action node, matches EOL. If a callable is provided as the first 314 argument, it is used as the execute member, and its docstring used as the 315 help for this node. Otherwise the `callback` keyword arg will be used as 316 the callable. If `with_context` is true, the context object will be passed 317 as the first argument. 347 """ Action node, matches EOL. The `callback` arg will be used as the 348 callable. If `with_context` is true, the context object will be passed as 349 the first argument. 318 350 319 351 >>> def write_text(): 320 ... \"\"\"Write some text\"\"\"321 352 ... print "some text" 322 >>> grammar = Grammar(action=Action( write_text))353 >>> grammar = Grammar(action=Action("Write some text", write_text)) 323 354 >>> node = grammar.find('action') 324 355 >>> node.doc … … 333 364 with_context = False 334 365 335 def __init__(self, thing, *args, **kwargs): 336 if callable(thing): 337 doc = pydoc.getdoc(thing) or thing.__name__.replace('_', ' ').title() 338 doc = doc.splitlines()[0] 339 Node.__init__(self, doc, callback=thing, *args, **kwargs) 340 else: 341 Node.__init__(self, thing, *args, **kwargs) 366 def __init__(self, doc, callback, *args, **kwargs): 367 Node.__init__(self, doc, callback=callback, *args, **kwargs) 342 368 343 369 def help(self, context): … … 358 384 raise UnexpectedEOL(None) 359 385 386 def chosen(self, context, match): 387 # We don't "traverse" Action nodes, because they are always terminal, 388 # and if we do they get excluded from help. 389 pass 390 360 391 361 392 class Validator(Node): … … 366 397 accumulate = False 367 398 399 def valid(self, context): 400 if not self.accumulate and self.name in context.vars: 401 return False 402 if self.accumulate and isinstance(self.accumulate, int) and \ 403 len(context.vars.get(self.name, [])) >= self.accumulate: 404 return False 405 return Node.valid(self, context) 406 368 407 def chosen(self, context, match): 369 408 try: … … 396 435 self.help = [] 397 436 self.node = node 437 438 def add_help(node): 439 node_help = sorted(node.help(context)) 440 for help in node_help: 441 self.help.append((node.group, node.order, help[0], help[1])) 442 398 443 for child in node.children(context, follow=True): 399 child_help = sorted(child.help(context)) 400 for help in child_help: 401 self.help.append((child.group, child.order, help[0], help[1])) 444 add_help(child) 445 402 446 self.help.sort() 403 447 … … 405 449 """ Iterate over each (key, help) help pair. 406 450 407 >>> help = Help(None, Grammar(one=Node('One'), 451 >>> context = Context(None, None) 452 >>> help = Help(context, Grammar(one=Node('One'), 408 453 ... two=Node(('<two>', 'Two'), group=2))) 409 454 >>> list(help) … … 418 463 419 464 >>> import sys 420 >>> help = Help(None, Grammar(one=Node('One'), 465 >>> context = Context(None, None) 466 >>> help = Help(context, Grammar(one=Node('One'), 421 467 ... two=Node(('<two>', 'Two'), group=2))) 422 468 >>> help.format(sys.stdout) … … 447 493 self.user = user 448 494 self.vars = {} 495 self._traversed = {} 449 496 self.trail = [] 450 497 … … 470 517 self.cursor += distance 471 518 472 def candidates(self, text ):519 def candidates(self, text=''): 473 520 """ Return potential candidates from children of last successfully 474 521 parsed node. """ … … 481 528 node. """ 482 529 return Help(self, self.last_node) 530 531 def traverse(self, node): 532 """ Mark node as traversed in this context. """ 533 path = node.path() 534 self._traversed.setdefault(path, 0) 535 self._traversed[path] += 1 536 537 def traversed(self, node): 538 """ How many times has node been traversed in this context? """ 539 return self._traversed.get(node.path, 0) 483 540 484 541 def __repr__(self): … … 518 575 519 576 >>> parser = Parser(Grammar(one=Node('One'), two=Node('Two', three=Node('Three', 520 ... action=Action( lambda: "foo bar")))))577 ... action=Action('Do stuff', lambda: "foo bar"))))) 521 578 >>> context = parser.parse('two three') 522 579 >>> context … … 538 595 continue 539 596 submatch = subnode.match(context) 540 if submatch :597 if submatch is not None: 541 598 return parse(subnode, submatch) 542 599 else: … … 551 608 552 609 >>> parser = Parser(Grammar(one=Node('One'), two=Node('Two', three=Node('Three', 553 ... action=Action( lambda: "foo bar")))))610 ... action=Action('Do stuff', lambda: "foo bar"))))) 554 611 >>> parser.execute('two three') 555 612 'foo bar' cly/trunk/cly/interactive.py
r352 r355 5 5 import cly.rlext 6 6 7 8 def print_error(*message): 9 if sys.stderr.isatty(): 10 print >>sys.stderr, '\033[31m\033[1m%s\033[0m' % ' '.join(message) 11 else: 12 print >>sys.stderr, ' '.join(message) 13 7 14 8 15 class Interact(object): … … 24 31 25 32 Interact.__parser = parser 26 self.application = application27 self.prompt = prompt28 self.user_context = user_context29 self.history_file = history_file30 self.history_length = history_length31 self.completion_delimiters = completion_delimiters33 Interact.prompt = prompt 34 Interact.application = application 35 Interact.user_context = user_context 36 Interact.history_file = history_file 37 Interact.history_length = history_length 38 Interact.completion_delimiters = completion_delimiters 32 39 33 40 try: … … 66 73 context.execute() 67 74 except cly.ParseError, e: 68 if sys.stderr.isatty(): 69 print >>sys.stderr, '\033[31m\033[1merror: %s\033[0m' % str(e) 70 else: 71 print >>sys.stderr, 'error: %s' % str(e) 75 print_error('error:', str(e)) 72 76 return context 73 77 … … 125 129 context = Interact.__parser.parse(command) 126 130 if context.remaining.strip(): 127 cly.rlext.cursor(context.cursor) 131 print 132 candidates = [help[0] for help in context.help()] 133 text = '%s^ invalid token (candidates are %s)' % (' ' * (context.cursor + len(Interact.prompt)), ', '.join(candidates)) 134 print_error(text) 135 cly.rlext.force_redisplay() 136 return 128 137 help = context.help() 129 138 print cly/trunk/cly/validators.py
r352 r355 43 43 'http://www.example.com/test/;test?a=10&b=10#fragment' 44 44 """ 45 pattern = r"""(([a-zA-Z][0-9a-zA-Z+\\-\\.]*:)?/{0,2}[0-9a-zA-Z;/?:@&=+$\\.\\-_!~*'()%]+) ?(#[0-9a-zA-Z;/?:@&=+$\\.\\-_!~*'()%]+)?"""45 pattern = r"""(([a-zA-Z][0-9a-zA-Z+\\-\\.]*:)?/{0,2}[0-9a-zA-Z;/?:@&=+$\\.\\-_!~*'()%]+)(#[0-9a-zA-Z;/?:@&=+$\\.\\-_!~*'()%]+)?""" 46 46 47 47 def __init__(self, doc, scheme='', allow_fragments=1, *argl, **argd): … … 59 59 >>> from cly import * 60 60 >>> parser = Parser(Grammar(foo=LDAPDN('Foo'))) 61 >>> parser.parse('cn=Manager, dc=example,dc=com').vars['foo']62 'cn=Manager, dc=example,dc=com'63 """ 64 pattern = r'(\w+=\w+) \s*(?:,\s*(\w+=\w+))*'61 >>> parser.parse('cn=Manager,dc=example,dc=com').vars['foo'] 62 'cn=Manager,dc=example,dc=com' 63 """ 64 pattern = r'(\w+=\w+)(?:,(\w+=\w+))*' 65 65 66 66
