cly.builder

Classes for constructing CLY grammars.

class Node()

The base class for all grammar nodes.

Any :class:`Node` instances passed to the constructor will become children of this node in the grammar hierarchy. :class:`Node`s as keyword arguments will be named after their keyword, while positional arguments will be provided auto-generated names. This is generally only useful for "control" nodes, such as :class:`Alias`.

System Message: ERROR/3 (<string>, line 3); backlink

Unknown interpreted text role "class".

System Message: ERROR/3 (<string>, line 3); backlink

Unknown interpreted text role "class".

System Message: ERROR/3 (<string>, line 3); backlink

Unknown interpreted text role "class".

Supported keyword arguments:

help:

string or callable returning a list of (key, help) tuples A help string or a callable returning an iterable of (key, help) pairs. There is a useful class called Help which can be used for this purpose.

name:

The name of the node. If ommitted the keyword argument name used in the parent Node is used. The node name also defines the node path and the default pattern to match against if not explicitly provided:

>>> Node(name='something')
<Node:/something>

The following constructor arguments are also class variables, and as such can be overridden at the class level by subclasses of Node. Useful If you find yourself using a particular pattern repeatedly.

pattern:

The regular expression used to match user input. If not provided, the node name is used:

>>> a = Node(name='something')
>>> a.pattern == a.name
True
separator:

A regular expression used to match the text separating this node and the next.

group:

Nodes with the same group value will be collated visually. Generally a number, but can also be a string or any other comparable object.

order:

Within a group, nodes are normally ordered alphabetically. This can be overridden by setting this to a value other than 0.

match_candidates:
 

Modifies the behaviour of the parser when matching completion candidates.

The :meth:`candidates` method returns a list of words that match at the current token, which are then used for completion. If match_candidates=True the allowed input will be explicitly constrainted to just these candidates.

System Message: ERROR/3 (<string>, line 54); backlink

Unknown interpreted text role "meth".

match_candidates will be set automatically if :meth:`candidates` is provided.

System Message: ERROR/3 (<string>, line 59); backlink

Unknown interpreted text role "meth".

Useful for situations where you have a general regex pattern (eg. a pattern matching files) but a known set of matches at this point (eg. files in the current directory).

cull_candidates:
 

If True (the default) :meth:`candidates` may return a static list of candidates that is automatically culled based on the text being matched. This avoids a lot of boiler plate code.

System Message: ERROR/3 (<string>, line 67); backlink

Unknown interpreted text role "meth".

>>> a = Node(candidates=['one', 'two'])
>>> print list(a.candidates(None, ''))
['one ', 'two ']
>>> print list(a.candidates(None, 'o'))
['one ']
traversals:

The number of times this node can match in any parse context. :class:`Alias` nodes allow for multiple traversal.

System Message: ERROR/3 (<string>, line 78); backlink

Unknown interpreted text role "class".

If traversals=0 the node will match an infinite number of times.

label:

Specify the global label for this node. This can be used by the :class:`Alias` to refer to nodes by label rather than path.

System Message: ERROR/3 (<string>, line 84); backlink

Unknown interpreted text role "class".

__init__(self, **anonymous, *kwargs)

(Not documented)

help(self, context)

Return help for node.

returns:A sequence of tuples in the form (lhs, help).

__call__(self, **anonymous, *options)

Update or add options and child nodes.

Positional arguments are treated as anonymous child nodes, while keyword arguments can either be named child nodes or attribute updates for this node. See __init__ for more information on attributes.

>>> top = Node(name='top')
>>> top(subnode=Node())
<Node:/top>
>>> top.find('/subnode')
<Node:/top/subnode>

__iter__(self)

Iterate over child nodes, ignoring context.

>>> tree = Node()(two=Node(), three=Node())
>>> list(tree)
[<Node:/three>, <Node:/two>]

__setitem__(self, key, child)

Emulate dictionary set.

>>> node = Node()
>>> node['two'] = Node()
>>> list(node.walk())
[<Node:/>, <Node:/two>]

__getitem__(self, key)

Emulate dictionary get.

>>> node = Node()(two=Node())
>>> node['two']
<Node:/two>

__delitem__(self, key)

Emulate dictionary delete.

>>> node = Node(two=Node(), three=Node())
>>> list(node.walk())
[<Node:/>, <Node:/three>, <Node:/two>]
>>> del node['two']
>>> list(node.walk())
[<Node:/>, <Node:/three>]

__contains__(self, key)

Emulate dictionary key existence test.

>>> node = Node(two=Node(), three=Node())
>>> 'two' in node
True

walk(self, predicate=None)

Perform a recursive walk of the grammar tree.

>>> tree = Node(two=Node(three=Node(), four=Node()))
>>> list(tree.walk())
[<Node:/>, <Node:/two>, <Node:/two/four>, <Node:/two/three>]

children(self, context, follow=False)

Iterate over child nodes, optionally follow()ing branches.

>>> from cly import *
>>> grammar = Grammar(two=Node(three=Node(),
...                            four=Node()),
...                            five=Alias(target='../two/*'))
>>> parser = Parser(grammar)
>>> context = Context(parser, None)
>>> list(grammar.children(context))
[<Alias:/five for /two/*>, <Node:/two>]
>>> list(grammar.children(context, follow=True))
[<Node:/two/four>, <Node:/two/three>, <Node:/two>]

follow(self, context)

Return alternative Nodes to traverse.

The children() method calls this method when follow=True to expand aliased nodes, although it could be used for other purposes.

selected(self, context, match)

This node was selected by the parser.

By default, informs the context that the node has been traversed.

next(self, context)

Return an iterable over the set of next candidate nodes.

match(self, context)

Does this node match the current token?

Must return a regex match object or None for no match. If match_candidates is true the token must also match one of the values returned by candidates().

Must include separator in determining whether a match was successful.

advance(self, context)

Advance context cursor based on this nodes match.

visible(self, context)

Should this node be visible?

terminal(self, context)

This node was selected as a terminal.

depth(self)

The depth of this node in the grammar.

>>> grammar = Grammar(one=Node(), two=Node())
>>> grammar.depth()
0
>>> grammar.find('/two').depth()
1

path(self)

The full grammar path to this node. Path components are separated by a forward slash.

>>> grammar = Grammar(one=Node(), two=Node())
>>> grammar.find('/two').path()
'/two'

candidates(self, context, text)

Return an iterable of completion candidates for the given text. The default is to use the content of self.help().

param text:Text entered so far.
>>> grammar = Grammar(one=Node(), two=Node())
>>> list(grammar.find('/one').candidates(None, 'o'))
['one ']
>>> list(grammar.find('/one').candidates(None, 't'))
[]

find(self, path)

Find a Node by path rooted at this node.

param path:"Path" to the node, or a label.
returns:Found node.
>>> top = Node(name='top', one=Node(),
...            two=Node(three=Node()))
>>> top.find('/two/three')
<Node:/top/two/three>
>>> top.find('/one/bar')
Traceback (most recent call last):
...
InvalidNodePath: /top/one/bar

valid(self, context)

Is this node valid in the given context?

update(self, node)

Merge another node into this one.

If a merging node collides with an existing one, the existing node will be preserved and the merging nodes children merged.

param node:Node to merge into this.

__repr__(self)

(Not documented)

cast_attribute(cls, namespace, name, value)

Define functions for casting attributes to their correct Python type.

Upcalling is necessary.

param namespace:
 The XML namespace of the attribute. Compare against XMLGrammar.EVAL_NS to determine if forced evaluation can/should occur.
param name:Attribute name.
param value:Value to cast.
returns:Tuple of (value, options) where options is a dictionary of extra Node constructor arguments.

attribute_aliases(cls)

Define attribute aliases for this node.

Parent classes are automatically merged, so upcalling is unnecessary.

returns:Mapping of old to new keys.

class Masquerade()

A node that masquerades as other nodes.

Masquerade is a general-purpose tool for dynamically inserting nodes into the grammar at the current location. Implementations should override the masqueraded() method.

Use cases:

  • Conditionally present nodes.
  • Dynamically generated nodes.
  • Aliases for other parts of the grammar.
>>> from cly.parser import Parser
>>> class Privileged(Masquerade):
...     authenticated = False
...     def masqueraded(self, context):
...         if not self.authenticated:
...             return []
...         shutdown = Node(Action(self.shutdown), name='shutdown')
...         return [shutdown]
...     def shutdown(self):
...         print 'shutdown()'
>>> privileged = Privileged()
>>> parser = Parser(Grammar(privileged, one=Node()))
>>> parser.execute('shutdown')
Traceback (most recent call last):
...
InvalidToken: invalid token 'shutdown'
>>> privileged.authenticated = True
>>> parser.execute('shutdown')
shutdown()

masqueraded(self, context)

Return a sequence of all masqueraded nodes.

Masquerades as child nodes by default.

selected(self, context, match)

(Not documented)

follow(self, context)

(Not documented)

visible(self, context)

(Not documented)

valid(self, context)

(Not documented)

class Defaults()

Set variables in a branch.

This is primarily useful in XML grammars.

>>> from cly import *
>>> parser = Parser(Grammar(Defaults(foo=10, bar=20)(baz=Node())))
>>> parser.parse('baz').vars
{'foo': 10, 'bar': 20}

In an XML grammar, all attributes are evaluated. This means that strings must be double quoted.

__init__(self, **kwargs)

(Not documented)

follow(self, context)

(Not documented)

cast_attribute(cls, namespace, name, value)

(Not documented)

class Group()

Group subnodes under a single group ID.

param id:Group ID.

__init__(self, id=None, **args, *kwargs)

(Not documented)

cast_attribute(cls, namespace, name, value)

(Not documented)

class Alias()

An alias for another node, or set of nodes.

An Alias overrides the follow() method to return aliased Nodes. Globs are supported.

param target:Relative or absolute path to the aliased node. If the alias contains glob characters (* or ?) all matching nodes are aliased.
>>> from cly.parser import Parser, Context
>>> parser = Parser(Grammar(one=Node(), two=Node(
...                 three=Node()), four=Alias(target='../one'),
...                 five=Node(six=Alias(target='../../*'))))
>>> alias = parser.find('/four')
>>> alias
<Alias:/four for /one>
>>> context = Context(parser, None)
>>> list(alias.follow(context))
[<Node:/one>]
>>> alias = parser.find('/five/six')
>>> alias
<Alias:/five/six for /*>
>>> list(alias.follow(context))
[<Node:/five>, <Node:/one>, <Node:/one>, <Node:/two>]

__init__(self, target, **anonymous, *kwargs)

(Not documented)

masqueraded(self, context)

Return an iterable of all aliased nodes.

__repr__(self)

(Not documented)

class If()

A set of conditional nodes.

A node that masquerades as its children, if a condition is true.

param test:A callable with the signature test(context). Returns True if masqueraded nodes are accessible.

All other arguments are passed through to the default :class:`Node` constructor.

System Message: ERROR/3 (<string>, line 8); backlink

Unknown interpreted text role "class".
>>> from cly.parser import Parser
>>> active = False
>>> parser = Parser(Grammar(If(lambda c: active, one=Node())))
>>> parser.parse('one')
<Context command:'one' remaining:'one'>
>>> active = True
>>> parser.parse('one')
<Context command:'one' remaining:''>

__init__(self, test, **args, *kwargs)

(Not documented)

masqueraded(self, context)

(Not documented)

test(self, context)

(Not documented)

class Apply()

Apply settings to all ancestor nodes.

Terminates application of settings on any deeper Apply node.

Before applying settings:

>>> top = Node(one=Node(), two=Node(three=Node()))
>>> [node.traversals for node in top.walk()]
[1, 1, 1, 1]

And after applying settings:

>>> apply = Apply(traversals=0)(top)
>>> [node.traversals for node in top.walk()]
[0, 0, 0, 0]

__init__(self, **apply)

(Not documented)

__call__(self, **anonymous, *kwargs)

(Not documented)

class Action()

Matches EOL and executes callback.

param callback:

Callback to execute when the action is chosen.

var with_context:
 

If True, passes the current parse :class:`~cly.parser.Context` as the first argument.

System Message: ERROR/3 (<string>, line 6); backlink

Unknown interpreted text role "class".

>>> from cly.parser import Parser, Context
>>> def write_text():
...     print 'some text'
>>> grammar = Grammar(action=Action(callback=write_text))
>>> parser = Parser(grammar)
>>> context = Context(parser, 'foo bar')
>>> node = grammar.find('/action')
>>> node.help(None)
(('<eol>', ''),)
>>> node.terminal(context)
some text

__init__(self, callback, **anonymous, *kwargs)

(Not documented)

terminal(self, context)

(Not documented)

callback(self, **args, *kwargs)

(Not documented)

selected(self, context, match)

(Not documented)

attribute_aliases(cls)

(Not documented)

cast_attribute(cls, namespace, name, value)

(Not documented)

class Variable()

Parse and record the users input in the vars member of the context.

The node name is used as the variable name unless var_name is provided to the constructor.

If traversals != 1 the variable will accumulate values into a list.

__init__(self, **anonymous, *kwargs)

(Not documented)

valid(self, context)

(Not documented)

selected(self, context, match)

Convert the match to a value with self.parse(), then add the result to the context "vars" member.

Raises ValidationError if the variable raises InvalidMatch.

>>> from cly.parser import Context
>>> c = Context(None, 'foo bar')
>>> v = Variable(name='var')
>>> v.selected(c, re.match(r'\w+', 'test'))
>>> c.vars['var']
'test'

parse(self, context, match)

Parse the match and return a value. Value can be of any type: tuple, list, object, etc.

Must throw a ValidationError if the input is invalid. Alternate variables should override this method.

>>> v = Variable()
>>> v.parse(None, re.match(r'\w+', 'test'))
'test'

class Grammar()

The root node for a grammar.

__init__(self, **anonymous, *kwargs)

(Not documented)

terminal(self, context)

Null-op for empty lines.

class XMLGrammar()

A Grammar that builds its structure from an XML file.

The XML grammar is a simple mapping from element names to :class:`Node` subclasses, and element attributes to constructor arguments. Unlike when building a grammar from :class:`Node` objects, the name must be explicitly provided.

System Message: ERROR/3 (<string>, line 3); backlink

Unknown interpreted text role "class".

System Message: ERROR/3 (<string>, line 3); backlink

Unknown interpreted text role "class".
Arguments:
file:

Filename or file-like object to load XML grammar from.

extra_nodes:

A sequence of extra :class:`Node` subclasses to make available as elements.

System Message: ERROR/3 (<string>, line 10); backlink

Unknown interpreted text role "class".

eg.

<word name="abc" valid="'var' in v" pattern=r"[abc]+"/>

Is roughly equivalent to:

...(
  abc=Word(valid=lambda context: 'var' in context.vars,
           pattern=r'[abc]+')
)

Attributes that are methods on the :class:`Node` will be evaluated as expressions. All variables from the parse :class:`~cly.parser.Context`, "data" dictionary, and keyword arguments (in that order) are available as locals to the evaluated expression, as well as some additional variables:

System Message: ERROR/3 (<string>, line 28); backlink

Unknown interpreted text role "class".

System Message: ERROR/3 (<string>, line 28); backlink

Unknown interpreted text role "class".
c:

:class:`~cly.parser.Context` object.

System Message: ERROR/3 (<string>, line 33); backlink

Unknown interpreted text role "class".

v:

All variables from the :class:`~cly.parser.Context`.

System Message: ERROR/3 (<string>, line 34); backlink

Unknown interpreted text role "class".

d:

The "data" dictionary.

a:

Any positional arguments.

kw:

Any keyword arguments.

v in particular is useful for passing all collected arguments to functions.

Some convenient aliases also exist to make life easier when using XML grammars.

Node aliases:

var:variable
int:integer
str:string

Attribute aliases:

if:valid
exec:callback

eg.

<?xml version="1.0"?>
<grammar xmlns="http://swapoff.org/cly/xml">
  <node name="echo" help="Echo text">
    <action if="defined('text')" exec="echo(**v)"/>
    <group traversals="0">
      <variable name="text" help="Text to echo">
        <alias target="/echo/*"/>
      </variable>
    </group>
  </node>
  <action name="quit" callback="sys.exit(0)" help="Quit"/>
</grammar>

And used with:

def echo(text):
  print text

g = XMLGrammar('example.xml')
interact(g, data={'echo': echo})

__init__(self, file, extra_nodes=None)

(Not documented)

parse_xml(self, parent, xnode)

(Not documented)

parse_element(self, parent, xnode)

(Not documented)

parse_attributes(self, cls, xnode)

(Not documented)

lazy_attr_evaluator(attr, positional_args=None)

Return a callable that lazily evaluates an expression.

Arguments:
attr:Python expression to evaluate as a string.
positional_args:
 List of positional argument names to map to locals.

boolean_cast(value)

Converter for boolean attributes.

group_cast(value)

Parse a group="n" attribute.

class Help()

A callable object representing help for a Node.

Returns an iterable of pairs in the form (key, help).

Arguments:

doc:An iterable of two element tuples in the form (key, help).
>>> h = Help([('a', 'b'), ('b', 'c')])
>>> [i for i in h(None)]
[('a', 'b'), ('b', 'c')]

__init__(self, doc)

(Not documented)

__call__(self, context)

Returns an iterable of two element tuples in the form (key, help).

pair(name, help)

Create a Help object from a single (name, help) pair.

>>> h = Help.pair('a', 'b')
>>> [i for i in h(None)]
[('a', 'b')]

class LazyHelp()

Extract help key from a node.

Used internally by Node when a string is provided as help.

If the Node does not have a custom pattern, the help will be in the form (name, text), otherwise it will be in the form (<name>, text).

__init__(self, node, text)

(Not documented)

__call__(self, context)

Extract help key from node.

>>> node = Node(name='test')
>>> help = LazyHelp(node, 'Moo')
>>> [i for i in help(None)]
[('test', 'Moo')]

class Word()

Matches a Pythonesque variable name.

>>> from cly.parser import Parser
>>> parser = Parser(Grammar(foo=Word()))
>>> parser.parse('a123').vars['foo']
'a123'
>>> parser.parse('123').remaining
'123'

class Keyword()

Matches and stores the node name only.

>>> from cly.parser import Parser
>>> parser = Parser(Grammar(foo=Keyword()))
>>> parser.parse('foo').vars['foo']
'foo'
>>> parser.parse('bar').vars['foo']
Traceback (most recent call last):
...
KeyError: 'foo'

class KeyValue()

Match and store a key value pair.

__init__(self, sep='=', value_pattern='\\S+', **args, *kwargs)

(Not documented)

parse(self, context, match)

(Not documented)

class String()

Matches either a bare word or a quoted string.

>>> from cly.parser import Parser
>>> parser = Parser(Grammar(foo=String()))
>>> parser.parse('"foo bar"').vars['foo']
'foo bar'
>>> parser.parse('foo_bar').vars['foo']
'foo_bar'

parse(self, context, match)

(Not documented)

class URI()

Matches a URI. Result is a string.

>>> from cly.parser import Parser
>>> parser = Parser(Grammar(foo=URI()))
>>> parser.parse('http://www.example.com/test/;test?a=10&b=10#fragment').vars['foo']
'http://www.example.com/test/;test?a=10&b=10#fragment'

__init__(self, scheme='', allow_fragments=1, **anonymous, *kwargs)

(Not documented)

class LDAPDN()

Matches an LDAP DN.

>>> from cly.parser import Parser
>>> parser = Parser(Grammar(foo=LDAPDN()))
>>> parser.parse('cn=Manager,dc=example,dc=com').vars['foo']
'cn=Manager,dc=example,dc=com'

class Integer()

Matches an integer.

>>> from cly.parser import Parser
>>> parser = Parser(Grammar(foo=Integer()))
>>> parser.parse('12345').vars['foo']
12345
>>> parser.parse('123.45').remaining
'123.45'

parse(self, context, match)

(Not documented)

class Boolean()

Matches a boolean.

>>> from cly.parser import Parser
>>> parser = Parser(Grammar(foo=Boolean()))
>>> parser.parse('true').vars['foo']
True
>>> parser.parse('no').vars['foo']
False

parse(self, context, match)

(Not documented)

class Float()

Matches a floating point number.

>>> from cly.parser import Parser
>>> parser = Parser(Grammar(foo=Float()))
>>> parser.parse('12345.34').vars['foo']
12345.34
>>> parser.parse('123.45e10').vars['foo']
1234500000000.0

parse(self, context, match)

(Not documented)

class IP()

Match an IP address.

>>> from cly.parser import Parser
>>> parser = Parser(Grammar(foo=IP()))
>>> parser.parse('123.34.67.89').vars['foo']
'123.34.67.89'

Invalid IP addresses will not match:

>>> parser.parse('123.34.67.899').vars['foo']
Traceback (most recent call last):
...
KeyError: 'foo'

Also matches netmasks:

>>> parser.parse('255.255.255.0').vars['foo']
'255.255.255.0'

class CIDR()

Match a CIDR network representation.

If a netmask is not provided a default of /32 will be used.

>>> from cly import *
>>> parser = Parser(Grammar(foo=CIDR()))
>>> parser.parse('123.34.67.89').vars['foo']
'123.34.67.89/32'
>>> parser.parse('123.34.67.89/24').vars['foo']
'123.34.67.89/24'

parse(self, context, match)

(Not documented)

class Hostname()

Match only a hostname (not an IP address).

Arguments:
parts:The minimum number of host parts required.
suffix:Optional domain suffix to require.
>>> from cly.parser import *

Supports bare hostnames:

>>> parser = Parser(Grammar(foo=Hostname()))
>>> parser.parse('www').vars
{'foo': 'www'}

Fully-qualified names:

>>> parser.parse('www.example.com').vars
{'foo': 'www.example.com'}

IN-ADDR ARPA addresses:

>>> parser.parse('1.1.10.in-addr.arpa').vars
{'foo': '1.1.10.in-addr.arpa'}

But not IP addresses:

>>> parser.parse('10.1.1.1').vars
{}

Suffix checking is also supported:

>>> parser = Parser(Grammar(foo=Hostname(suffix='.example.com')))
>>> parser.parse('www').vars
{}
>>> parser.parse('www.example.com').vars
{'foo': 'www.example.com'}

As well as requiring a minumum number of host parts:

>>> parser = Parser(Grammar(foo=Hostname(parts=2)))
>>> parser.parse('www').vars
{}
>>> parser.parse('www.foo.com').vars
{'foo': 'www.foo.com'}

match(self, context)

(Not documented)

cast_attribute(cls, namespace, name, value)

(Not documented)

class Host()

Match either an IP address or a hostname.

>>> from cly.parser import Parser
>>> parser = Parser(Grammar(foo=Host()))
>>> parser.parse('www.example.com').vars['foo']
'www.example.com'
>>> parser.parse('10.1.1.1').vars['foo']
'10.1.1.1'
>>> parser.parse('1.1.10.in-addr.arpa').vars['foo']
'1.1.10.in-addr.arpa'

class EMail()

Match an E-Mail address.

>>> from cly.parser import Parser
>>> parser = Parser(Grammar(foo=EMail()))
>>> parser.parse('foo@bar.com').vars['foo']
'foo@bar.com'

class File()

Match and provide completion candidates for local files.

>>> from cly.parser import Parser
>>> parser = Parser(Grammar(foo=File(allow_directories=True)))
>>> parser.parse('.').vars['foo']
'.'

match(self, context)

(Not documented)

match_file(self, file, match_directories=True)

(Not documented)

candidates(self, context, text)

Return list of valid file candidates.

class AbsoluteTime()

Parse an absolute time value in the form HH:MM[:SS]].

returns:a datetime.time object.

parse(self, context, match)

(Not documented)

class RelativeTime()

Parse a relative time.

Relative times are specified as an optionally negative float followed by a single character time unit:

[-]NN.N[w|d|h|m|s]

eg.

15m, 3.5d

returns:a datetime.timedelta object.

parse(self, context, match)

(Not documented)

class FixedOffsetTZ()

Fixed offset in minutes east from UTC.

__init__(self, offset, name)

(Not documented)

__str__(self)

(Not documented)

__repr__(self)

(Not documented)

utcoffset(self, dt)

(Not documented)

tzname(self, dt)

(Not documented)

dst(self, dt)

(Not documented)

class Timezone()

Parse a timezone, using pytz if available.

cull_candidates(candidates, text, sep=' ')

Cull candidates that do not start with text.

Returned candidates also have a space appended.

Arguments:
candidates:Sequence of match candidates.
text:Text to match.
sep:Separator to append to match.
>>> cull_candidates(['bob', 'fred', 'barry', 'harry'], 'b')
['bob ', 'barry ']
>>> cull_candidates(cull_candidates(['bob', 'fred', 'barry', 'harry'], 'b'), 'b')
['bob ', 'barry ']