<?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 Tutorial</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-tutorial"> <h1 class="title">CLY Tutorial</h1> <div class="contents topic" id="contents"> <p class="topic-title first">Contents</p> <ul class="simple"> <li><a class="reference internal" href="#before-you-start" id="id1">Before you start</a></li> <li><a class="reference internal" href="#step-one-fundamentals" id="id2">Step One - Fundamentals</a></li> <li><a class="reference internal" href="#step-two-interaction" id="id3">Step Two - Interaction</a></li> <li><a class="reference internal" href="#step-three-and-the-first-command-ment-shall-be-quit" id="id4">Step Three - And the first <em>command</em>-ment shall be <tt class="docutils literal"><span class="pre">quit</span></tt></a></li> <li><a class="reference internal" href="#step-four-variables" id="id5">Step Four - Variables</a></li> <li><a class="reference internal" href="#step-five-recursion" id="id6">Step Five - Recursion</a></li> <li><a class="reference internal" href="#step-six-completion" id="id7">Step Six - Completion</a></li> <li><a class="reference internal" href="#conclusion" id="id8">Conclusion</a></li> </ul> </div> <div class="section" id="before-you-start"> <h1><a class="toc-backref" href="#id1">Before you start</a></h1> <p>As with most things, it is usually better in the long run if you think about your design before implementation. With that formality out of the way, the beauty of CLY, and Python in general, is that it is easy and fun to experiment with.</p> <p>For the purposes of this tutorial I will implement a basic "shell", with the commands <tt class="docutils literal"><span class="pre">cat</span></tt> and <tt class="docutils literal"><span class="pre">quit</span></tt>. <tt class="docutils literal"><span class="pre">cat</span></tt> will have full tab completion of files and directories.</p> </div> <div class="section" id="step-one-fundamentals"> <h1><a class="toc-backref" href="#id2">Step One - Fundamentals</a></h1> <p>The first step is just to import the bare essentials required to get a command line interface working. As one of CLY's main goals is to minimize the amount of code required to implement a command line interface, this is fairly small.</p> <div class="highlight"><pre><span class="kn">from</span> <span class="nn">cly</span> <span class="kn">import</span> <span class="o">*</span> </pre></div> </div> <div class="section" id="step-two-interaction"> <h1><a class="toc-backref" href="#id3">Step Two - Interaction</a></h1> <p>Making your program interactive is as simple as calling the <tt class="docutils literal"><span class="pre">interact()</span></tt> function with a grammar:</p> <div class="highlight"><pre><span class="kn">from</span> <span class="nn">cly</span> <span class="kn">import</span> <span class="o">*</span> <span class="n">grammar</span> <span class="o">=</span> <span class="n">Grammar</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>This calls <tt class="docutils literal"><span class="pre">interact()</span></tt> with an empty grammar, which won't do much at this stage, but will allow interaction.</p> <p>CLY uses readline for its interactive terminal, so all normal readline options apply including emacs and vi modes. Command and variable completion can be triggered by pressing <tt class="docutils literal"><span class="pre"><Tab></span></tt> (this can be overridden), pressing <tt class="docutils literal"><span class="pre">?</span></tt> at any time will display context-sensitive help, and finally, you can press <tt class="docutils literal"><span class="pre"><Ctrl-D></span></tt> to terminate the console interface.</p> </div> <div class="section" id="step-three-and-the-first-command-ment-shall-be-quit"> <h1><a class="toc-backref" href="#id4">Step Three - And the first <em>command</em>-ment shall be <tt class="docutils literal"><span class="pre">quit</span></tt></a></h1> <p>The first command we will implement is <tt class="docutils literal"><span class="pre">quit</span></tt>, as it is the most basic.</p> <div class="highlight"><pre><span class="kn">import</span> <span class="nn">sys</span> <span class="kn">from</span> <span class="nn">cly</span> <span class="kn">import</span> <span class="o">*</span> <span class="k">def</span> <span class="nf">do_quit</span><span class="p">():</span> <span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mf">0</span><span class="p">)</span> <span class="n">grammar</span> <span class="o">=</span> <span class="n">Grammar</span><span class="p">(</span> <span class="n">quit</span><span class="o">=</span><span class="n">Node</span><span class="p">(</span><span class="s">'Quit'</span><span class="p">)(</span> <span class="n">Action</span><span class="p">(</span><span class="s">'Quit'</span><span class="p">,</span> <span class="n">do_quit</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> <p><strong>Note:</strong> The apparent redundancy of having two <em>Quit</em> nodes is explained by the behaviour of the <tt class="docutils literal"><span class="pre">Action</span></tt> node. This node matches the end of the current command. For example, the following would execute <tt class="docutils literal"><span class="pre">quit()</span></tt> when an empty line is passed as the command:</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">Action</span><span class="p">(</span><span class="s">'Quit'</span><span class="p">,</span> <span class="n">do_quit</span><span class="p">),</span> <span class="p">)</span> </pre></div> <p>Which is generally not what one wants.</p> </div> <div class="section" id="step-four-variables"> <h1><a class="toc-backref" href="#id5">Step Four - Variables</a></h1> <p>It's all well and good to call a function with no arguments, but you'll often want to pass user inputted variables along to each <tt class="docutils literal"><span class="pre">Action</span></tt>. This is where the <tt class="docutils literal"><span class="pre">Variable</span></tt> class hierarchy comes in. The class matches user input and stores the (potentially transformed into another type) value in the <tt class="docutils literal"><span class="pre">vars</span></tt> dictionary of the current parse <tt class="docutils literal"><span class="pre">Context</span></tt>. When an <tt class="docutils literal"><span class="pre">Action</span></tt> node is selected it passes these variables on to its callback as arguments.</p> <div class="highlight"><pre><span class="kn">import</span> <span class="nn">os</span> <span class="kn">import</span> <span class="nn">sys</span> <span class="kn">from</span> <span class="nn">cly</span> <span class="kn">import</span> <span class="o">*</span> <span class="k">def</span> <span class="nf">do_quit</span><span class="p">():</span> <span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mf">0</span><span class="p">)</span> <span class="k">def</span> <span class="nf">do_cat</span><span class="p">(</span><span class="nb">file</span><span class="p">):</span> <span class="k">print</span> <span class="nb">open</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">expanduser</span><span class="p">(</span><span class="nb">file</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="p">(</span> <span class="n">quit</span><span class="o">=</span><span class="n">Node</span><span class="p">(</span><span class="s">'Quit'</span><span class="p">)(</span> <span class="n">Action</span><span class="p">(</span><span class="s">'Quit'</span><span class="p">,</span> <span class="n">do_quit</span><span class="p">),</span> <span class="p">),</span> <span class="n">cat</span><span class="o">=</span><span class="n">Node</span><span class="p">(</span><span class="s">'Concatenate files'</span><span class="p">)(</span> <span class="nb">file</span><span class="o">=</span><span class="n">Variable</span><span class="p">(</span><span class="s">'File to concatenate'</span><span class="p">,</span> <span class="n">pattern</span><span class="o">=</span><span class="s">r'\S+'</span><span class="p">)(</span> <span class="n">Action</span><span class="p">(</span><span class="s">'Concatenate files'</span><span class="p">,</span> <span class="n">do_cat</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="step-five-recursion"> <h1><a class="toc-backref" href="#id6">Step Five - Recursion</a></h1> <p>This is cool, but what if we want to be able to "cat" multiple files?</p> <div class="highlight"><pre><span class="kn">import</span> <span class="nn">os</span> <span class="kn">import</span> <span class="nn">sys</span> <span class="kn">from</span> <span class="nn">cly</span> <span class="kn">import</span> <span class="o">*</span> <span class="k">def</span> <span class="nf">do_quit</span><span class="p">():</span> <span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mf">0</span><span class="p">)</span> <span class="k">def</span> <span class="nf">do_cat</span><span class="p">(</span><span class="n">files</span><span class="p">):</span> <span class="k">for</span> <span class="nb">file</span> <span class="ow">in</span> <span class="n">files</span><span class="p">:</span> <span class="k">print</span> <span class="nb">open</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">expanduser</span><span class="p">(</span><span class="nb">file</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="p">(</span> <span class="n">quit</span><span class="o">=</span><span class="n">Node</span><span class="p">(</span><span class="s">'Quit'</span><span class="p">)(</span> <span class="n">Action</span><span class="p">(</span><span class="s">'Quit'</span><span class="p">,</span> <span class="n">do_quit</span><span class="p">),</span> <span class="p">),</span> <span class="n">cat</span><span class="o">=</span><span class="n">Node</span><span class="p">(</span><span class="s">'Concatenate files'</span><span class="p">)(</span> <span class="n">files</span><span class="o">=</span><span class="n">Variable</span><span class="p">(</span><span class="s">'File to concatenate'</span><span class="p">,</span> <span class="n">pattern</span><span class="o">=</span><span class="s">r'\S+'</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">Action</span><span class="p">(</span><span class="s">'Concatenate files'</span><span class="p">,</span> <span class="n">do_cat</span><span class="p">),</span> <span class="n">Alias</span><span class="p">(</span><span class="s">'..'</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> <p>Each <tt class="docutils literal"><span class="pre">Node</span></tt> has a <tt class="docutils literal"><span class="pre">traversals</span></tt> member which specifies the number of times that node can be traversed in a parse context. This is <tt class="docutils literal"><span class="pre">1</span></tt> by default, but if we set this to <tt class="docutils literal"><span class="pre">0</span></tt> the node may be traversed any number of times.</p> <p>The second new feature is the <tt class="docutils literal"><span class="pre">Alias</span></tt> class which, as the name suggests, makes this node an alias for another node in the grammar tree. The node to alias is specified as either a relative or absolute path. In this case, the parent node, whose absolute path is <tt class="docutils literal"><span class="pre">/cat/files</span></tt>.</p> </div> <div class="section" id="step-six-completion"> <h1><a class="toc-backref" href="#id7">Step Six - Completion</a></h1> <p>Variables can have extra intelligence built into them to customise their behaviour. To make life a bit easier for the end developer a selection of variables are available in <tt class="docutils literal"><span class="pre">cly.builder</span></tt>. This includes a <tt class="docutils literal"><span class="pre">File</span></tt> class which we will use to provide file completion:</p> <div class="highlight"><pre><span class="kn">import</span> <span class="nn">os</span> <span class="kn">import</span> <span class="nn">sys</span> <span class="kn">from</span> <span class="nn">cly</span> <span class="kn">import</span> <span class="o">*</span> <span class="k">def</span> <span class="nf">do_quit</span><span class="p">():</span> <span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mf">0</span><span class="p">)</span> <span class="k">def</span> <span class="nf">do_cat</span><span class="p">(</span><span class="n">files</span><span class="p">):</span> <span class="k">for</span> <span class="nb">file</span> <span class="ow">in</span> <span class="n">files</span><span class="p">:</span> <span class="k">print</span> <span class="nb">open</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">expanduser</span><span class="p">(</span><span class="nb">file</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="p">(</span> <span class="n">quit</span><span class="o">=</span><span class="n">Node</span><span class="p">(</span><span class="s">'Quit'</span><span class="p">)(</span> <span class="n">Action</span><span class="p">(</span><span class="s">'Quit'</span><span class="p">,</span> <span class="n">do_quit</span><span class="p">),</span> <span class="p">),</span> <span class="n">cat</span><span class="o">=</span><span class="n">Node</span><span class="p">(</span><span class="s">'Concatenate files'</span><span class="p">)(</span> <span class="n">files</span><span class="o">=</span><span class="n">File</span><span class="p">(</span><span class="s">'File to concatenate'</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">Action</span><span class="p">(</span><span class="s">'Concatenate files'</span><span class="p">,</span> <span class="n">do_cat</span><span class="p">),</span> <span class="n">Alias</span><span class="p">(</span><span class="s">'..'</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> <p>Variables can be used to not only validate input, but parse it into a useful state. By overriding the <tt class="docutils literal"><span class="pre">parse()</span></tt> method of a <tt class="docutils literal"><span class="pre">Variable</span></tt> any type can be inserted into the context. An example of this is the IP variable, which returns the IP as a tuple of integers.</p> </div> <div class="section" id="conclusion"> <h1><a class="toc-backref" href="#id8">Conclusion</a></h1> <p>Hopefully this will have given you a taste of what CLY is capable of. There are quite a number of <a class="reference external" href="http://swapoff.org/cly/docs">other features</a> which allow you to extensively customise the behaviour of your command line applications.</p> </div> </div> </body> </html>