<?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="generator" content="Docutils 0.5: http://docutils.sourceforge.net/" /> <title>CLY Developer's Guide</title> <style type="text/css"> .highlight .c { color: #008800; font-style: italic } /* Comment */ .highlight .err { border: 1px solid #FF0000 } /* Error */ .highlight .k { color: #AA22FF; font-weight: bold } /* Keyword */ .highlight .o { color: #666666 } /* Operator */ .highlight .cm { color: #008800; font-style: italic } /* Comment.Multiline */ .highlight .cp { color: #008800 } /* Comment.Preproc */ .highlight .c1 { color: #008800; font-style: italic } /* Comment.Single */ .highlight .cs { color: #008800; font-weight: bold } /* Comment.Special */ .highlight .gd { color: #A00000 } /* Generic.Deleted */ .highlight .ge { font-style: italic } /* Generic.Emph */ .highlight .gr { color: #FF0000 } /* Generic.Error */ .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ .highlight .gi { color: #00A000 } /* Generic.Inserted */ .highlight .go { color: #808080 } /* Generic.Output */ .highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ .highlight .gs { font-weight: bold } /* Generic.Strong */ .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ .highlight .gt { color: #0040D0 } /* Generic.Traceback */ .highlight .kc { color: #AA22FF; font-weight: bold } /* Keyword.Constant */ .highlight .kd { color: #AA22FF; font-weight: bold } /* Keyword.Declaration */ .highlight .kp { color: #AA22FF } /* Keyword.Pseudo */ .highlight .kr { color: #AA22FF; font-weight: bold } /* Keyword.Reserved */ .highlight .kt { color: #AA22FF; font-weight: bold } /* Keyword.Type */ .highlight .m { color: #666666 } /* Literal.Number */ .highlight .s { color: #BB4444 } /* Literal.String */ .highlight .na { color: #BB4444 } /* Name.Attribute */ .highlight .nb { color: #AA22FF } /* Name.Builtin */ .highlight .nc { color: #0000FF } /* Name.Class */ .highlight .no { color: #880000 } /* Name.Constant */ .highlight .nd { color: #AA22FF } /* Name.Decorator */ .highlight .ni { color: #999999; font-weight: bold } /* Name.Entity */ .highlight .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ .highlight .nf { color: #00A000 } /* Name.Function */ .highlight .nl { color: #A0A000 } /* Name.Label */ .highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ .highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ .highlight .nv { color: #B8860B } /* Name.Variable */ .highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ .highlight .mf { color: #666666 } /* Literal.Number.Float */ .highlight .mh { color: #666666 } /* Literal.Number.Hex */ .highlight .mi { color: #666666 } /* Literal.Number.Integer */ .highlight .mo { color: #666666 } /* Literal.Number.Oct */ .highlight .sb { color: #BB4444 } /* Literal.String.Backtick */ .highlight .sc { color: #BB4444 } /* Literal.String.Char */ .highlight .sd { color: #BB4444; font-style: italic } /* Literal.String.Doc */ .highlight .s2 { color: #BB4444 } /* Literal.String.Double */ .highlight .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ .highlight .sh { color: #BB4444 } /* Literal.String.Heredoc */ .highlight .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ .highlight .sx { color: #008000 } /* Literal.String.Other */ .highlight .sr { color: #BB6688 } /* Literal.String.Regex */ .highlight .s1 { color: #BB4444 } /* Literal.String.Single */ .highlight .ss { color: #B8860B } /* Literal.String.Symbol */ .highlight .bp { color: #AA22FF } /* Name.Builtin.Pseudo */ .highlight .vc { color: #B8860B } /* Name.Variable.Class */ .highlight .vg { color: #B8860B } /* Name.Variable.Global */ .highlight .vi { color: #B8860B } /* Name.Variable.Instance */ .highlight .il { color: #666666 } /* Literal.Number.Integer.Long */ /* :Author: David Goodger :Contact: goodger@users.sourceforge.net :Date: $Date$ :Revision: $Revision$ :Copyright: This stylesheet has been placed in the public domain. Default cascading style sheet for the HTML output of Docutils. See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to customize this style sheet. */ /* used to remove borders from tables and images */ .borderless, table.borderless td, table.borderless th { border: 0 } table.borderless td, table.borderless th { /* Override padding for "table.docutils td" with "! important". The right padding separates the table cells. */ padding: 0 0.5em 0 0 ! important } .first { /* Override more specific margin styles with "! important". */ margin-top: 0 ! important } .last, .with-subtitle { margin-bottom: 0 ! important } .hidden { display: none } a.toc-backref { text-decoration: none ; color: black } blockquote.epigraph { margin: 2em 5em ; } dl.docutils dd { margin-bottom: 0.5em } /* Uncomment (and remove this text!) to get bold-faced definition list terms dl.docutils dt { font-weight: bold } */ div.abstract { margin: 2em 5em } div.abstract p.topic-title { font-weight: bold ; text-align: center } div.admonition, div.attention, div.caution, div.danger, div.error, div.hint, div.important, div.note, div.tip, div.warning { margin: 2em ; border: medium outset ; padding: 1em } div.admonition p.admonition-title, div.hint p.admonition-title, div.important p.admonition-title, div.note p.admonition-title, div.tip p.admonition-title { font-weight: bold ; font-family: sans-serif } div.attention p.admonition-title, div.caution p.admonition-title, div.danger p.admonition-title, div.error p.admonition-title, div.warning p.admonition-title { color: red ; font-weight: bold ; font-family: sans-serif } /* Uncomment (and remove this text!) to get reduced vertical space in compound paragraphs. div.compound .compound-first, div.compound .compound-middle { margin-bottom: 0.5em } div.compound .compound-last, div.compound .compound-middle { margin-top: 0.5em } */ div.dedication { margin: 2em 5em ; text-align: center ; font-style: italic } div.dedication p.topic-title { font-weight: bold ; font-style: normal } div.figure { margin-left: 2em ; margin-right: 2em } div.footer, div.header { clear: both; font-size: smaller } div.line-block { display: block ; margin-top: 1em ; margin-bottom: 1em } div.line-block div.line-block { margin-top: 0 ; margin-bottom: 0 ; margin-left: 1.5em } div.sidebar { margin-left: 1em ; border: medium outset ; padding: 1em ; background-color: #ffffee ; width: 40% ; float: right ; clear: right } div.sidebar p.rubric { font-family: sans-serif ; font-size: medium } div.system-messages { margin: 5em } div.system-messages h1 { color: red } div.system-message { border: medium outset ; padding: 1em } div.system-message p.system-message-title { color: red ; font-weight: bold } div.topic { margin: 2em } h1.section-subtitle, h2.section-subtitle, h3.section-subtitle, h4.section-subtitle, h5.section-subtitle, h6.section-subtitle { margin-top: 0.4em } h1.title { text-align: center } h2.subtitle { text-align: center } hr.docutils { width: 75% } img.align-left { clear: left } img.align-right { clear: right } ol.simple, ul.simple { margin-bottom: 1em } ol.arabic { list-style: decimal } ol.loweralpha { list-style: lower-alpha } ol.upperalpha { list-style: upper-alpha } ol.lowerroman { list-style: lower-roman } ol.upperroman { list-style: upper-roman } p.attribution { text-align: right ; margin-left: 50% } p.caption { font-style: italic } p.credits { font-style: italic ; font-size: smaller } p.label { white-space: nowrap } p.rubric { font-weight: bold ; font-size: larger ; color: maroon ; text-align: center } p.sidebar-title { font-family: sans-serif ; font-weight: bold ; font-size: larger } p.sidebar-subtitle { font-family: sans-serif ; font-weight: bold } p.topic-title { font-weight: bold } pre.address { margin-bottom: 0 ; margin-top: 0 ; font-family: serif ; font-size: 100% } pre.literal-block, pre.doctest-block { margin-left: 2em ; margin-right: 2em ; background-color: #eeeeee } span.classifier { font-family: sans-serif ; font-style: oblique } span.classifier-delimiter { font-family: sans-serif ; font-weight: bold } span.interpreted { font-family: sans-serif } span.option { white-space: nowrap } span.pre { white-space: pre } span.problematic { color: red } span.section-subtitle { /* font-size relative to parent (h1..h6 element) */ font-size: 80% } table.citation { border-left: solid 1px gray; margin-left: 1px } table.docinfo { margin: 2em 4em } table.docutils { margin-top: 0.5em ; margin-bottom: 0.5em } table.footnote { border-left: solid 1px black; margin-left: 1px } table.docutils td, table.docutils th, table.docinfo td, table.docinfo th { padding-left: 0.5em ; padding-right: 0.5em ; vertical-align: top } table.docutils th.field-name, table.docinfo th.docinfo-name { font-weight: bold ; text-align: left ; white-space: nowrap ; padding-left: 0 } h1 tt.docutils, h2 tt.docutils, h3 tt.docutils, h4 tt.docutils, h5 tt.docutils, h6 tt.docutils { font-size: 100% } tt.docutils { background-color: #eeeeee } ul.auto-toc { list-style-type: none } </style> </head> <body> <div class="document" id="cly-developer-s-guide"> <h1 class="title">CLY Developer's Guide</h1> <div class="contents topic" id="contents"> <p class="topic-title first">Contents</p> <ul class="simple"> <li><a class="reference internal" href="#overview" id="id1">Overview</a></li> <li><a class="reference internal" href="#experimenting" id="id2">Experimenting</a></li> <li><a class="reference internal" href="#building-a-grammar-from-python" id="id3">Building a Grammar from Python</a><ul> <li><a class="reference internal" href="#matching-input" id="id4">Matching Input</a></li> <li><a class="reference internal" href="#grammar-branching" id="id5">Grammar Branching</a></li> <li><a class="reference internal" href="#collecting-variables" id="id6">Collecting Variables</a></li> <li><a class="reference internal" href="#variable-types" id="id7">Variable Types</a></li> <li><a class="reference internal" href="#executing-callbacks" id="id8">Executing Callbacks</a></li> <li><a class="reference internal" href="#implementing-custom-completion" id="id9">Implementing Custom Completion</a></li> <li><a class="reference internal" href="#adding-help" id="id10">Adding Help</a></li> <li><a class="reference internal" href="#types-of-nodes" id="id11">Types of Nodes</a></li> <li><a class="reference internal" href="#node-attributes-and-constructor-arguments" id="id12">Node Attributes and Constructor Arguments</a></li> <li><a class="reference internal" href="#context" id="id13">Context</a></li> </ul> </li> <li><a class="reference internal" href="#defining-a-grammar-in-xml" id="id14">Defining a Grammar in XML</a><ul> <li><a class="reference internal" href="#parsing-an-xml-grammar" id="id15">Parsing an XML Grammar</a></li> <li><a class="reference internal" href="#xml-attribute-type-conversion" id="id16">XML Attribute Type Conversion</a></li> </ul> </li> <li><a class="reference internal" href="#parsing" id="id17">Parsing</a><ul> <li><a class="reference internal" href="#parse-context" id="id18">Parse Context</a></li> <li><a class="reference internal" href="#passing-user-defined-objects-to-callbacks" id="id19">Passing User-defined Objects to Callbacks</a></li> </ul> </li> </ul> </div> <div class="section" id="overview"> <h1><a class="toc-backref" href="#id1">Overview</a></h1> <p>CLY provides a convenient means to easily build command-line interfaces. This is achieved by defining a CLY grammar in either Python or XML then constructing a CLY parser object from the grammar. The parser parses user input against the grammar, executes callbacks, provides completion, and so on.</p> <p>A built-in terminal interface based on readline is provided.</p> </div> <div class="section" id="experimenting"> <h1><a class="toc-backref" href="#id2">Experimenting</a></h1> <p>The quickest way to experiment with CLY is by passing a <tt class="docutils literal"><span class="pre">Grammar</span></tt> or <tt class="docutils literal"><span class="pre">Parser</span></tt> object to the <tt class="docutils literal"><span class="pre">interact</span></tt> function:</p> <div class="highlight"><pre><span class="kn">from</span> <span class="nn">cly</span> <span class="kn">import</span> <span class="n">interact</span> <span class="n">interact</span><span class="p">(</span><span class="n">grammar_or_parser</span><span class="p">)</span> </pre></div> <p>This will start a readline console using the given grammar. Press <tt class="docutils literal"><span class="pre">?</span></tt> at any time for contextual help, <tt class="docutils literal"><span class="pre"><Tab></span></tt> to attempt completion, and <tt class="docutils literal"><span class="pre"><Ctrl-D></span></tt> (EOF) on an empty line to terminate the shell.</p> <p>Now, on with the nitty gritty.</p> </div> <div class="section" id="building-a-grammar-from-python"> <h1><a class="toc-backref" href="#id3">Building a Grammar from Python</a></h1> <div class="section" id="matching-input"> <h2><a class="toc-backref" href="#id4">Matching Input</a></h2> <p>The syntax of a shell is defined as a tree of <tt class="docutils literal"><span class="pre">Node</span></tt> objects. Each node matches a token of user input defined by the <tt class="docutils literal"><span class="pre">pattern</span></tt> attribute of the <tt class="docutils literal"><span class="pre">Node</span></tt> and defaults to the <tt class="docutils literal"><span class="pre">Node</span></tt> name. <tt class="docutils literal"><span class="pre">pattern</span></tt> is treated a regular expression. For example, the following grammar would match the token <tt class="docutils literal"><span class="pre">one</span></tt>:</p> <div class="highlight"><pre><span class="n">grammar</span> <span class="o">=</span> <span class="n">Grammar</span><span class="p">(</span> <span class="n">one</span><span class="o">=</span><span class="n">Node</span><span class="p">(</span><span class="s">'One'</span><span class="p">)</span> <span class="p">)</span> </pre></div> <p>Because nodes are hierarchical, child nodes are only considered for matching after the parent has matched and consumed a token. The following grammar would match the tokens <tt class="docutils literal"><span class="pre">parent</span> <span class="pre">child</span></tt>:</p> <div class="highlight"><pre><span class="n">grammar</span> <span class="o">=</span> <span class="n">Grammar</span><span class="p">(</span> <span class="n">parent</span><span class="o">=</span><span class="n">Node</span><span class="p">(</span><span class="s">'Parent'</span><span class="p">)(</span> <span class="n">child</span><span class="o">=</span><span class="n">Node</span><span class="p">(</span><span class="s">'Child'</span><span class="p">)</span> <span class="p">)</span> <span class="p">)</span> </pre></div> <p>As mentioned, the name of the node is used as the default pattern to match against input tokens. This can be overridden by passing <tt class="docutils literal"><span class="pre">pattern=<regex></span></tt> to the constructor. The following example will match one or more digits:</p> <div class="highlight"><pre><span class="n">grammar</span> <span class="o">=</span> <span class="n">Grammar</span><span class="p">(</span> <span class="n">number</span><span class="o">=</span><span class="n">Node</span><span class="p">(</span><span class="s">'Number'</span><span class="p">,</span> <span class="n">pattern</span><span class="o">=</span><span class="s">r'\d+'</span><span class="p">),</span> <span class="p">)</span> </pre></div> </div> <div class="section" id="grammar-branching"> <h2><a class="toc-backref" href="#id5">Grammar Branching</a></h2> <p>It's a common requirement that paths in the grammar be executed multiple times, whether that be to enter a list of IP addresses, allow multiple commands at a branch, etc. In CLY this is achieved with the <tt class="docutils literal"><span class="pre">Alias</span></tt> node. An alias node, as its name suggests, aliases its target at the current location, effectively merging two branches of the grammar.</p> <p>Here's an example:</p> <div class="highlight"><pre><span class="n">grammar</span> <span class="o">=</span> <span class="n">Grammar</span><span class="p">(</span> <span class="n">one</span><span class="o">=</span><span class="n">Node</span><span class="p">(</span><span class="s">'One'</span><span class="p">)(</span> <span class="n">Alias</span><span class="p">(</span><span class="s">'/three'</span><span class="p">),</span> <span class="n">two</span><span class="o">=</span><span class="n">Node</span><span class="p">(</span><span class="s">'Two'</span><span class="p">)(</span> <span class="p">)</span> <span class="p">),</span> <span class="n">three</span><span class="o">=</span><span class="n">Node</span><span class="p">(</span><span class="s">'Three'</span><span class="p">),</span> <span class="p">)</span> </pre></div> <p>This will alias the node <tt class="docutils literal"><span class="pre">/three</span></tt> underneath <tt class="docutils literal"><span class="pre">/one</span></tt>. That is, this grammar will match the input <tt class="docutils literal"><span class="pre">one</span> <span class="pre">three</span></tt>.</p> <p>Nodes are referenced by their full or relative path, and globs may be used to alias multiple nodes at once. The following example will alias everything at <tt class="docutils literal"><span class="pre">/three/*</span></tt>, underneath <tt class="docutils literal"><span class="pre">/one/</span></tt>:</p> <div class="highlight"><pre><span class="n">grammar</span> <span class="o">=</span> <span class="n">Grammar</span><span class="p">(</span> <span class="n">one</span><span class="o">=</span><span class="n">Node</span><span class="p">(</span><span class="s">'One'</span><span class="p">)(</span> <span class="n">Alias</span><span class="p">(</span><span class="s">'../../three/*'</span><span class="p">),</span> <span class="n">two</span><span class="o">=</span><span class="n">Node</span><span class="p">(</span><span class="s">'Two'</span><span class="p">),</span> <span class="p">),</span> <span class="n">three</span><span class="o">=</span><span class="n">Node</span><span class="p">(</span><span class="s">'Three'</span><span class="p">)(</span> <span class="n">four</span><span class="o">=</span><span class="n">Node</span><span class="p">(</span><span class="s">'Four'</span><span class="p">),</span> <span class="n">five</span><span class="o">=</span><span class="n">Node</span><span class="p">(</span><span class="s">'Five'</span><span class="p">),</span> <span class="p">),</span> <span class="p">)</span> </pre></div> <p>By default, nodes may only be traversed once per parse run. This can be overridden by passing <tt class="docutils literal"><span class="pre">traversals=<count></span></tt> to node constructors. If <tt class="docutils literal"><span class="pre">traversals</span> <span class="pre">==</span> <span class="pre">0</span></tt> then there are no limits set. If <tt class="docutils literal"><span class="pre">traversals</span> <span class="pre">></span> <span class="pre">1</span></tt> then <em>the variable will be stored as a list</em>.</p> </div> <div class="section" id="collecting-variables"> <h2><a class="toc-backref" href="#id6">Collecting Variables</a></h2> <p>Matching input is great, but if you want your grammar to be useful you're going to want it to do something with it. This is where the <tt class="docutils literal"><span class="pre">Variable</span></tt> class comes in: it stores matching input tokens into the parse context for later use as arguments to execution callbacks.</p> <p>Here's an example of a variable matching a number:</p> <div class="highlight"><pre><span class="n">grammar</span> <span class="o">=</span> <span class="n">Grammar</span><span class="p">(</span> <span class="n">number</span><span class="o">=</span><span class="n">Variable</span><span class="p">(</span><span class="s">'Number'</span><span class="p">,</span> <span class="n">pattern</span><span class="o">=</span><span class="s">r'\d+'</span><span class="p">),</span> <span class="p">)</span> </pre></div> <p>The matched input token is stored by the <tt class="docutils literal"><span class="pre">Variable.selected()</span></tt> method, into the <tt class="docutils literal"><span class="pre">vars</span></tt> dictionary of the context.</p> <p>When parsing terminates on an action, the context variables are passed as keyword arguments to the final callback. For example, given the input <tt class="docutils literal"><span class="pre">1234</span></tt> the above grammar would execute the callback with <tt class="docutils literal"><span class="pre">callback(number='1234')</span></tt></p> <p>It's as simple as that.</p> </div> <div class="section" id="variable-types"> <h2><a class="toc-backref" href="#id7">Variable Types</a></h2> <p>By default, variables are passed to callbacks as strings. Having to then manually convert these arguments to whatever type you want would be tedious, so CLY allows end users to customise the value passed to the callback by subclassing <tt class="docutils literal"><span class="pre">Variable</span></tt> and overriding the <tt class="docutils literal"><span class="pre">parse()</span></tt> method. The default behaviour for this method is to pass all matching input text as a string.</p> <p>Continuing the example from the previous section:</p> <div class="highlight"><pre><span class="k">class</span> <span class="nc">Number</span><span class="p">(</span><span class="n">Variable</span><span class="p">):</span> <span class="n">pattern</span> <span class="o">=</span> <span class="s">r'\d+'</span> <span class="k">def</span> <span class="nf">parse</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context</span><span class="p">,</span> <span class="n">match</span><span class="p">):</span> <span class="k">return</span> <span class="nb">int</span><span class="p">(</span><span class="n">match</span><span class="o">.</span><span class="n">group</span><span class="p">())</span> </pre></div> <p>Where <tt class="docutils literal"><span class="pre">context</span></tt> is a <tt class="docutils literal"><span class="pre">cly.parser.Context</span></tt> object and <tt class="docutils literal"><span class="pre">match</span></tt> is a <tt class="docutils literal"><span class="pre">re.Match</span></tt> object.</p> <p>Now our callback will be executed with <tt class="docutils literal"><span class="pre">callback(number=1234)</span></tt>. Of course, much more complex conversions can occur, including IP address parsing, E-Mail parsing, etc. A set of commonly used variable types is included in <tt class="docutils literal"><span class="pre">cly.builder</span></tt>.</p> <p>Note how the <tt class="docutils literal"><span class="pre">pattern</span></tt> attribute may be a class member as a convenience.</p> </div> <div class="section" id="executing-callbacks"> <h2><a class="toc-backref" href="#id8">Executing Callbacks</a></h2> <p>Now that we have collected variables from the user into our context, we want to execute a callback with those variables. This is achieved with the <tt class="docutils literal"><span class="pre">Action</span></tt> node and matches the end of input.</p> <p>Here's an example:</p> <div class="highlight"><pre><span class="k">def</span> <span class="nf">print_number</span><span class="p">(</span><span class="n">number</span><span class="p">):</span> <span class="k">print</span> <span class="n">number</span> <span class="n">grammar</span> <span class="o">=</span> <span class="n">Grammar</span><span class="p">(</span> <span class="n">number</span><span class="o">=</span><span class="n">Number</span><span class="p">(</span><span class="s">'A number'</span><span class="p">)(</span> <span class="n">Action</span><span class="p">(</span><span class="s">'Print number'</span><span class="p">,</span> <span class="n">print_number</span><span class="p">),</span> <span class="p">)</span> <span class="p">)</span> </pre></div> <p>In addition to all of the attributes included with <tt class="docutils literal"><span class="pre">Node</span></tt>, <tt class="docutils literal"><span class="pre">Action</span></tt> also has the <tt class="docutils literal"><span class="pre">with_user_context</span></tt> flag. This can be used to pass a user-defined context variable (as opposed to the parser context) to the callback as the first argument. Refer to <a class="reference internal" href="#passing-user-defined-objects-to-callbacks">Passing User-defined Objects to Callbacks</a> for more information about user contexts.</p> </div> <div class="section" id="implementing-custom-completion"> <h2><a class="toc-backref" href="#id9">Implementing Custom Completion</a></h2> <p>To implement your own custom completion, override <tt class="docutils literal"><span class="pre">Node.candidates(context,</span> <span class="pre">text)</span></tt>. This can be achieved by passing <tt class="docutils literal"><span class="pre">candidates=<callable></span></tt> to the <tt class="docutils literal"><span class="pre">Node</span></tt> constructor or by normal subclassing. Unless <tt class="docutils literal"><span class="pre">match_candidates=True</span></tt> though, the node will still match any input that matches its <tt class="docutils literal"><span class="pre">pattern</span></tt>. Setting this flag to <tt class="docutils literal"><span class="pre">False</span></tt> will constrain the node to only match valid candidates.</p> <p>The <cite>canidates()</cite> method itself must return a list of strings <em>with trailing whitespace</em> that match the <tt class="docutils literal"><span class="pre">text</span></tt> argument. <tt class="docutils literal"><span class="pre">context</span></tt> is a parser context object, described elsewhere.</p> <p>Here's an example of how one could implement a kill command:</p> <div class="highlight"><pre><span class="kn">import</span> <span class="nn">os</span> <span class="kn">import</span> <span class="nn">signal</span> <span class="kn">from</span> <span class="nn">cly</span> <span class="kn">import</span> <span class="o">*</span> <span class="c"># Build dictionary of signal_name:signal</span> <span class="n">signals</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">([(</span><span class="n">s</span><span class="p">[</span><span class="mf">3</span><span class="p">:],</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">signal</span><span class="p">,</span> <span class="n">s</span><span class="p">))</span> <span class="k">for</span> <span class="n">s</span> <span class="ow">in</span> <span class="nb">dir</span><span class="p">(</span><span class="n">signal</span><span class="p">)</span> <span class="k">if</span> <span class="n">s</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s">'SIG'</span><span class="p">)</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">s</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s">'SIG_'</span><span class="p">)])</span> <span class="k">def</span> <span class="nf">complete_signals</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="n">text</span><span class="p">):</span> <span class="k">return</span> <span class="p">[</span><span class="n">s</span> <span class="o">+</span> <span class="s">' '</span> <span class="k">for</span> <span class="n">s</span> <span class="ow">in</span> <span class="n">signals</span><span class="o">.</span><span class="n">keys</span><span class="p">()</span> <span class="k">if</span> <span class="n">s</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="n">text</span><span class="p">)]</span> <span class="k">def</span> <span class="nf">kill</span><span class="p">(</span><span class="n">pid</span><span class="p">,</span> <span class="n">signal</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span> <span class="k">try</span><span class="p">:</span> <span class="n">os</span><span class="o">.</span><span class="n">kill</span><span class="p">(</span><span class="n">pid</span><span class="p">,</span> <span class="n">signals</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">signal</span><span class="p">,</span> <span class="n">signals</span><span class="p">[</span><span class="s">'TERM'</span><span class="p">]))</span> <span class="k">except</span> <span class="ne">OSError</span><span class="p">,</span> <span class="n">e</span><span class="p">:</span> <span class="k">print</span> <span class="s">'error:'</span><span class="p">,</span> <span class="n">e</span> <span class="n">grammar</span> <span class="o">=</span> <span class="n">Grammar</span><span class="p">(</span> <span class="n">kill</span><span class="o">=</span><span class="n">Node</span><span class="p">(</span> <span class="n">pid</span><span class="o">=</span><span class="n">Integer</span><span class="p">(</span><span class="s">'PID to kill'</span><span class="p">)(</span> <span class="n">action</span><span class="o">=</span><span class="n">Action</span><span class="p">(</span><span class="s">'Send signal'</span><span class="p">,</span> <span class="n">kill</span><span class="p">),</span> <span class="n">signal</span><span class="o">=</span><span class="n">Variable</span><span class="p">(</span><span class="s">'Signal to send to process'</span><span class="p">,</span> <span class="n">candidates</span><span class="o">=</span><span class="n">complete_signals</span><span class="p">,</span> <span class="n">match_candidates</span><span class="o">=</span><span class="bp">True</span><span class="p">)(</span> <span class="n">Alias</span><span class="p">(</span><span class="s">'/kill/pid/action'</span><span class="p">),</span> <span class="p">),</span> <span class="p">),</span> <span class="p">),</span> <span class="p">)</span> <span class="n">interact</span><span class="p">(</span><span class="n">grammar</span><span class="p">)</span> </pre></div> </div> <div class="section" id="adding-help"> <h2><a class="toc-backref" href="#id10">Adding Help</a></h2> <p>The first argument to every CLY grammar node <strong>must</strong> be either a help string or a callable that returns an iterable of <tt class="docutils literal"><span class="pre">(key,</span> <span class="pre">help)</span></tt> tuples. This is used to construct contextual help when a user presses <cite>?</cite>.</p> <p>In the vast majority of cases a simple string, or possibly a pair, will be sufficient. For when it is not, the convenience class <tt class="docutils literal"><span class="pre">cly.parser.Help</span></tt> is available to construct help, either from a single pair:</p> <div class="highlight"><pre><span class="n">one</span><span class="o">=</span><span class="n">Node</span><span class="p">(</span><span class="n">Help</span><span class="o">.</span><span class="n">pair</span><span class="p">(</span><span class="s">'one'</span><span class="p">,</span> <span class="s">'Command 1'</span><span class="p">))</span> </pre></div> <p>or from a list of tuples:</p> <div class="highlight"><pre><span class="n">help</span> <span class="o">=</span> <span class="p">[(</span><span class="s">'one'</span><span class="p">,</span> <span class="s">'Command 1'</span><span class="p">),</span> <span class="p">(</span><span class="s">'1'</span><span class="p">,</span> <span class="s">'Command 1'</span><span class="p">)]</span> <span class="n">one</span><span class="o">=</span><span class="n">Node</span><span class="p">(</span><span class="n">Help</span><span class="p">(</span><span class="n">help</span><span class="p">),</span> <span class="n">pattern</span><span class="o">=</span><span class="s">r'one|1'</span><span class="p">)</span> </pre></div> </div> <div class="section" id="types-of-nodes"> <h2><a class="toc-backref" href="#id11">Types of Nodes</a></h2> <p>CLY includes a whole suite of builtin node types, which can be broken down into the following groups:</p> <dl class="docutils"> <dt><tt class="docutils literal"><span class="pre">Node</span></tt></dt> <dd>The base grammar node. These nodes in the grammar are purely for routing the grammar to other nodes. They have no side-effects.</dd> <dt><tt class="docutils literal"><span class="pre">Grammar</span></tt></dt> <dd>The root of the grammar. Contains all other nodes and acts purely as a container and a match for the beginning of input.</dd> <dt><tt class="docutils literal"><span class="pre">Group</span></tt></dt> <dd>A convenience class used to apply attributes to a group of nodes.</dd> <dt><tt class="docutils literal"><span class="pre">Alias</span></tt></dt> <dd>Allow branches of the grammar to be included in other locations. The only argument to <tt class="docutils literal"><span class="pre">Alias</span></tt> is the relative or absolute path of the branch to be included. This path can take the form of a glob in order to include multiple nodes. Use this to create optional arguments.</dd> <dt><tt class="docutils literal"><span class="pre">Variable</span></tt></dt> <dd>Variable nodes insert their matching input into the <tt class="docutils literal"><span class="pre">vars</span></tt> dictionary of the <tt class="docutils literal"><span class="pre">Context</span></tt>, after being parsed by the <tt class="docutils literal"><span class="pre">parse()</span></tt> method. If the <tt class="docutils literal"><span class="pre">Variable</span></tt> attribute <tt class="docutils literal"><span class="pre">traversals</span></tt> is not 1, values are collected into a list rather than a scalar. CLY includes a number of potentially useful <tt class="docutils literal"><span class="pre">Variable</span></tt> subclasses such as <tt class="docutils literal"><span class="pre">URI</span></tt>, <tt class="docutils literal"><span class="pre">Integer</span></tt>, <tt class="docutils literal"><span class="pre">Float</span></tt>, etc.</dd> <dt><tt class="docutils literal"><span class="pre">Action</span></tt></dt> <dd>An action matches the end of a line and is used to execute a callback. It passes any <tt class="docutils literal"><span class="pre">vars</span></tt> parsed by previous <tt class="docutils literal"><span class="pre">Variable</span></tt> nodes through to the callback as arguments.</dd> </dl> <p>Full details are available in the <a class="reference external" href="http://swapoff.org/cly/docs">API documentation</a>.</p> </div> <div class="section" id="node-attributes-and-constructor-arguments"> <h2><a class="toc-backref" href="#id12">Node Attributes and Constructor Arguments</a></h2> <p>Each node has a set of attributes that define its behaviour, from the regular expression used to match input, through to the number of times the node can be traversed in a parse context.</p> <p>These attributes can be set in four ways:</p> <p>By passing keyword arguments to the constructor:</p> <div class="highlight"><pre><span class="n">Node</span><span class="p">(</span><span class="s">'Help'</span><span class="p">,</span> <span class="n">pattern</span><span class="o">=</span><span class="s">r'.+'</span><span class="p">)</span> </pre></div> <p>By "calling" the node with keyword arguments:</p> <div class="highlight"><pre><span class="n">Node</span><span class="p">(</span><span class="s">'Help'</span><span class="p">)(</span><span class="n">pattern</span><span class="o">=</span><span class="s">r'.+'</span><span class="p">)</span> </pre></div> <p>With the special <tt class="docutils literal"><span class="pre">Group</span></tt> node. This node will set attributes on <em>all</em> descendants:</p> <div class="highlight"><pre><span class="n">Group</span><span class="p">(</span><span class="n">traversals</span><span class="o">=</span><span class="mf">0</span><span class="p">)(</span> <span class="n">one</span><span class="o">=</span><span class="n">Node</span><span class="p">(</span><span class="s">'One'</span><span class="p">),</span> <span class="n">two</span><span class="o">=</span><span class="n">Node</span><span class="p">(</span><span class="s">'Two'</span><span class="p">),</span> <span class="p">)</span> </pre></div> <p>By subclassing the node and defining the attribute as a class attribute:</p> <div class="highlight"><pre><span class="k">class</span> <span class="nc">Any</span><span class="p">(</span><span class="n">Node</span><span class="p">):</span> <span class="n">pattern</span> <span class="o">=</span> <span class="s">r'.+'</span> </pre></div> <p>For details on what attributes are available for each node class, refer to the <a class="reference external" href="http://swapoff.org/cly/docs">API documentation</a>.</p> </div> <div class="section" id="context"> <h2><a class="toc-backref" href="#id13">Context</a></h2> <p>Each command is parsed within a context. The context stores state information such as variables collected, number of traversals of nodes, cursor location, etc. It is most useful when overriding default <tt class="docutils literal"><span class="pre">Node</span></tt> behaviour, where the <tt class="docutils literal"><span class="pre">vars</span></tt> data member may be inspected for variables that have already been collected.</p> </div> </div> <div class="section" id="defining-a-grammar-in-xml"> <h1><a class="toc-backref" href="#id14">Defining a Grammar in XML</a></h1> <p>CLY XML grammars are simply a one-to-one mapping of XML elements to Python <tt class="docutils literal"><span class="pre">cly.builder.Node</span></tt> objects. Attributes of each element are passed as arguments to the <tt class="docutils literal"><span class="pre">Node</span></tt> constructor.</p> <p>Here's a simple example of a grammar defining a single "echo" command:</p> <div class="highlight"><pre><span class="cp"><?xml version="1.0"?></span> <span class="nt"><grammar</span> <span class="na">xmlns=</span><span class="s">"http://swapoff.org/cly/xml"</span><span class="nt">></span> <span class="nt"><node</span> <span class="na">name=</span><span class="s">"echo"</span><span class="nt">></span> <span class="nt"><variable</span> <span class="na">name=</span><span class="s">"text"</span><span class="nt">></span> <span class="nt"><action</span> <span class="na">callback=</span><span class="s">"echo"</span> <span class="na">pattern=</span><span class="s">".+"</span><span class="nt">/></span> <span class="nt"></variable></span> <span class="nt"></node></span> <span class="nt"></grammar></span> </pre></div> <div class="section" id="parsing-an-xml-grammar"> <h2><a class="toc-backref" href="#id15">Parsing an XML Grammar</a></h2> <p>Calling <tt class="docutils literal"><span class="pre">Grammar.from_xml()</span></tt> with an XML string as the first argument will build a new <tt class="docutils literal"><span class="pre">Grammar</span></tt> object from that XML. An optional second argument <tt class="docutils literal"><span class="pre">extra_nodes</span></tt> can be used to pass a list of extra <tt class="docutils literal"><span class="pre">Node</span></tt> sub-classes to recognise as elements. All other keyword arguments will be treated as local symbols when evaluating attribute values in the XML grammar.</p> <p>Here's an example that uses the previously defined XML grammar to implement an "echo" command:</p> <div class="highlight"><pre><span class="kn">from</span> <span class="nn">cly</span> <span class="kn">import</span> <span class="n">Grammar</span><span class="p">,</span> <span class="n">interact</span> <span class="k">def</span> <span class="nf">echo</span><span class="p">(</span><span class="n">text</span><span class="p">):</span> <span class="k">print</span> <span class="n">text</span> <span class="n">xml_grammar</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="s">'example.xml'</span><span class="p">)</span><span class="o">.</span><span class="n">read</span><span class="p">()</span> <span class="n">grammar</span> <span class="o">=</span> <span class="n">Grammar</span><span class="o">.</span><span class="n">from_xml</span><span class="p">(</span><span class="n">xml_grammar</span><span class="p">,</span> <span class="n">echo</span><span class="o">=</span><span class="n">echo</span><span class="p">)</span> <span class="n">interact</span><span class="p">(</span><span class="n">grammar</span><span class="p">)</span> </pre></div> <p>Here's a slightly more complex example with looping and grouping:</p> <div class="highlight"><pre><span class="cp"><?xml version="1.0"?></span> <span class="nt"><grammar</span> <span class="na">xmlns=</span><span class="s">"http://swapoff.org/cly/xml"</span><span class="nt">></span> <span class="nt"><node</span> <span class="na">name=</span><span class="s">"echo"</span><span class="nt">></span> <span class="nt"><group</span> <span class="na">traversals=</span><span class="s">"0"</span><span class="nt">></span> <span class="nt"><variable</span> <span class="na">name=</span><span class="s">"text"</span><span class="nt">></span> <span class="nt"><alias</span> <span class="na">target=</span><span class="s">"/echo/*"</span><span class="nt">/></span> <span class="nt"><action</span> <span class="na">callback=</span><span class="s">"echo"</span><span class="nt">/></span> <span class="nt"></variable></span> <span class="nt"></group></span> <span class="nt"></node></span> <span class="nt"></grammar></span> </pre></div> <p>The Python code is identical.</p> </div> <div class="section" id="xml-attribute-type-conversion"> <h2><a class="toc-backref" href="#id16">XML Attribute Type Conversion</a></h2> <p>All <tt class="docutils literal"><span class="pre">Node</span></tt> constructor arguments that are not strings but are known to the <tt class="docutils literal"><span class="pre">Parser</span></tt> will be evaluated as Python code, meaning integer arguments will be converted to integers, callback arguments may contain lambdas, etc.</p> <p>In this example <tt class="docutils literal"><span class="pre">traversals</span></tt> will be converted to an integer and passed to the <tt class="docutils literal"><span class="pre">Node</span></tt> constructor:</p> <div class="highlight"><pre><span class="cp"><?xml version="1.0"?></span> <span class="nt"><grammar></span> <span class="nt"><node</span> <span class="na">traversals=</span><span class="s">"0"</span><span class="nt">></span> ... <span class="nt"></node></span> <span class="nt"></grammar></span> </pre></div> <p>This evaluation can also be forced by using the <tt class="docutils literal"><span class="pre">eval</span></tt> XML namespace:</p> <div class="highlight"><pre><span class="cp"><?xml version="1.0"?></span> <span class="nt"><grammar</span> <span class="na">xmlns=</span><span class="s">"http://swapoff.org/cly/xml"</span> <span class="na">xmlns:eval=</span><span class="s">"http://swapoff.org/cly/xml-eval"</span><span class="nt">></span> <span class="nt"><node</span> <span class="na">eval:help=</span><span class="s">"'HELLO WORLD'.title()"</span><span class="nt">/></span> ... <span class="nt"></node></span> <span class="nt"></grammar></span> </pre></div> </div> </div> <div class="section" id="parsing"> <h1><a class="toc-backref" href="#id17">Parsing</a></h1> <p>A grammar is simply a data structure. To actually utilise it one needs to bind it to a <tt class="docutils literal"><span class="pre">Parser</span></tt> object and parse some input with it. The parser takes care of creating a <tt class="docutils literal"><span class="pre">Context</span></tt> for each parse run, parsing the input, and executing any callbacks.</p> <div class="section" id="parse-context"> <h2><a class="toc-backref" href="#id18">Parse Context</a></h2> <p>A parse context is created automatically when input is parsed. It contains all the information needed to parse input tokens, including the current cursor position in the input stream, the current node in the grammar, variables collected and a history of nodes traversed.</p> <p>Basic usage is:</p> <div class="highlight"><pre><span class="n">parser</span> <span class="o">=</span> <span class="n">Parser</span><span class="p">(</span><span class="n">grammar</span><span class="p">)</span> <span class="n">context</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="s">'some input text'</span><span class="p">)</span> <span class="k">print</span> <span class="n">context</span><span class="o">.</span><span class="n">vars</span> </pre></div> <p>If the input is invalid the context will have consumed as much input as possible. The attributes <tt class="docutils literal"><span class="pre">parsed</span></tt> and <tt class="docutils literal"><span class="pre">remaining</span></tt> contain how much text has been consumed and remains, respectively. The context has a number of additional attributes and methods that are useful to both <tt class="docutils literal"><span class="pre">Node</span></tt> implementations and <tt class="docutils literal"><span class="pre">Parser</span></tt> users. These are documented in detail in the API documentation.</p> </div> <div class="section" id="passing-user-defined-objects-to-callbacks"> <h2><a class="toc-backref" href="#id19">Passing User-defined Objects to Callbacks</a></h2> <p>Sometimes it's useful to be able to pass an arbitrary object through to the grammar callbacks. This is achieved by first enabling this behaviour on the desired action nodes by either passing <tt class="docutils literal"><span class="pre">with_user_context=True</span></tt> to the constructor or enabled across the entire grammar by doing the same on the <tt class="docutils literal"><span class="pre">Parser</span></tt> constructor. Once enabled, simply pass the object to the <tt class="docutils literal"><span class="pre">execute()</span></tt> method via the <tt class="docutils literal"><span class="pre">user_context</span></tt> parameter. That object will then be provided to all action callbacks as the first parameter.</p> <p>One way of applying this is by binding all callbacks to methods on a single <em>class</em>, then passing an instance of that class as the context:</p> <div class="highlight"><pre><span class="k">class</span> <span class="nc">A</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span> <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">name</span> <span class="k">def</span> <span class="nf">one</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">one</span><span class="p">):</span> <span class="k">print</span> <span class="s">"One:"</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="p">,</span> <span class="n">one</span> <span class="k">def</span> <span class="nf">two</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">two</span><span class="p">):</span> <span class="k">print</span> <span class="s">"Two:"</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="p">,</span> <span class="n">two</span> <span class="n">grammar</span> <span class="o">=</span> <span class="n">Grammar</span><span class="p">(</span> <span class="n">one</span><span class="o">=</span><span class="n">Variable</span><span class="p">(</span><span class="s">'One'</span><span class="p">)(</span> <span class="n">Action</span><span class="p">(</span><span class="s">'Execute one'</span><span class="p">,</span> <span class="n">A</span><span class="o">.</span><span class="n">one</span><span class="p">),</span> <span class="p">),</span> <span class="n">two</span><span class="o">=</span><span class="n">Variable</span><span class="p">(</span><span class="s">'Two'</span><span class="p">)(</span> <span class="n">Action</span><span class="p">(</span><span class="s">'Execute two'</span><span class="p">,</span> <span class="n">A</span><span class="o">.</span><span class="n">two</span><span class="p">),</span> <span class="p">),</span> <span class="p">)</span> <span class="n">a</span> <span class="o">=</span> <span class="n">A</span><span class="p">(</span><span class="s">'a'</span><span class="p">)</span> <span class="n">b</span> <span class="o">=</span> <span class="n">A</span><span class="p">(</span><span class="s">'b'</span><span class="p">)</span> <span class="n">parser</span> <span class="o">=</span> <span class="n">Parser</span><span class="p">(</span><span class="n">grammar</span><span class="p">,</span> <span class="n">with_user_context</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span> <span class="n">parser</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s">'one'</span><span class="p">,</span> <span class="n">user_context</span><span class="o">=</span><span class="n">a</span><span class="p">)</span> <span class="n">parser</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s">'two'</span><span class="p">,</span> <span class="n">user_context</span><span class="o">=</span><span class="n">b</span><span class="p">)</span> </pre></div> <p>This will output:</p> <pre class="literal-block"> One: a one One: b two </pre> </div> </div> </div> </body> </html>