| <!DOCTYPE html> |
| <!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]--> |
| <!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]--> |
| <!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]--> |
| <!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]--><head> |
| <meta charset='utf-8'/><meta http-equiv='X-UA-Compatible' content='IE=edge'/><meta name='viewport' content='width=device-width, initial-scale=1'/><meta name='keywords' content='groovy, release, clibuilder, picocli, commons cli'/><meta name='description' content='This post looks at new CliBuilder features from Groovy 2.5 in particular the Picocli-based implementation.'/><title>The Apache Groovy programming language - Blogs - Apache Groovy 2.5 CliBuilder Renewal</title><link href='../img/favicon.ico' type='image/x-ico' rel='icon'/><link rel='stylesheet' type='text/css' href='../css/bootstrap.css'/><link rel='stylesheet' type='text/css' href='../css/font-awesome.min.css'/><link rel='stylesheet' type='text/css' href='../css/style.css'/><link rel='stylesheet' type='text/css' href='https://cdnjs.cloudflare.com/ajax/libs/prettify/r298/prettify.min.css'/> |
| </head><body> |
| <div id='fork-me'> |
| <a href='https://github.com/apache/groovy'> |
| <img style='position: fixed; top: 20px; right: -58px; border: 0; z-index: 100; transform: rotate(45deg);' src='/img/horizontal-github-ribbon.png'/> |
| </a> |
| </div><div id='st-container' class='st-container st-effect-9'> |
| <nav class='st-menu st-effect-9' id='menu-12'> |
| <h2 class='icon icon-lab'>Socialize</h2><ul> |
| <li> |
| <a href='https://groovy-lang.org/mailing-lists.html' class='icon'><span class='fa fa-envelope'></span> Discuss on the mailing-list</a> |
| </li><li> |
| <a href='https://twitter.com/ApacheGroovy' class='icon'><span class='fa fa-twitter'></span> Groovy on Twitter</a> |
| </li><li> |
| <a href='https://groovy-lang.org/events.html' class='icon'><span class='fa fa-calendar'></span> Events and conferences</a> |
| </li><li> |
| <a href='https://github.com/apache/groovy' class='icon'><span class='fa fa-github'></span> Source code on GitHub</a> |
| </li><li> |
| <a href='https://groovy-lang.org/reporting-issues.html' class='icon'><span class='fa fa-bug'></span> Report issues in Jira</a> |
| </li><li> |
| <a href='http://stackoverflow.com/questions/tagged/groovy' class='icon'><span class='fa fa-stack-overflow'></span> Stack Overflow questions</a> |
| </li><li> |
| <a href='http://groovycommunity.com/' class='icon'><span class='fa fa-slack'></span> Slack Community</a> |
| </li> |
| </ul> |
| </nav><div class='st-pusher'> |
| <div class='st-content'> |
| <div class='st-content-inner'> |
| <!--[if lt IE 7]> |
| <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p> |
| <![endif]--><div><div class='navbar navbar-default navbar-static-top' role='navigation'> |
| <div class='container'> |
| <div class='navbar-header'> |
| <button type='button' class='navbar-toggle' data-toggle='collapse' data-target='.navbar-collapse'> |
| <span class='sr-only'></span><span class='icon-bar'></span><span class='icon-bar'></span><span class='icon-bar'></span> |
| </button><a class='navbar-brand' href='../index.html'> |
| <i class='fa fa-star'></i> Apache Groovy |
| </a> |
| </div><div class='navbar-collapse collapse'> |
| <ul class='nav navbar-nav navbar-right'> |
| <li class=''><a href='https://groovy-lang.org/learn.html'>Learn</a></li><li class=''><a href='https://groovy-lang.org/documentation.html'>Documentation</a></li><li class=''><a href='/download.html'>Download</a></li><li class=''><a href='https://groovy-lang.org/support.html'>Support</a></li><li class=''><a href='/'>Contribute</a></li><li class=''><a href='https://groovy-lang.org/ecosystem.html'>Ecosystem</a></li><li class=''><a href='/blog'>Blog posts</a></li><li class=''><a href='https://groovy.apache.org/events.html'></a></li><li> |
| <a data-effect='st-effect-9' class='st-trigger' href='#'>Socialize</a> |
| </li><li class=''> |
| <a href='../search.html'> |
| <i class='fa fa-search'></i> |
| </a> |
| </li> |
| </ul> |
| </div> |
| </div> |
| </div><div id='content' class='page-1'><div class='row'><div class='row-fluid'><div class='col-lg-3'><ul class='nav-sidebar'><li><a href='./'>Blog index</a></li><li class='active'><a href='#doc'>Apache Groovy 2.5 CliBuilder Renewal</a></li><li><a href='#_the_code_groovy_util_clibuilder_code_class_is_deprecated' class='anchor-link'>The <code>groovy.util.CliBuilder</code> Class is Deprecated</a></li><li><a href='#_typed_options' class='anchor-link'>Typed Options</a></li><li><a href='#_annotations' class='anchor-link'>Annotations</a></li><li><a href='#_typed_positional_parameters' class='anchor-link'>Typed Positional Parameters</a></li><li><a href='#_apache_commons_cli_features' class='anchor-link'>Apache Commons CLI Features</a></li><li><a href='#_picocli_clibuilder_features' class='anchor-link'>Picocli CliBuilder Features</a></li><li><a href='#_gotchas_incompatibilities' class='anchor-link'>Gotchas/Incompatibilities</a></li><li><a href='#_conclusion' class='anchor-link'>Conclusion</a></li></ul><br/><ul class='nav-sidebar'><li style='padding: 0.35em 0.625em; background-color: #eee'><span>Related posts</span></li><li><a href='./groovy-2-5-7-released'>Groovy 2.5.7 Released</a></li><li><a href='./groovy-2-5-5-windows'>Groovy 2.5.5 Windows Installer Released (Community Artifact)</a></li><li><a href='./groovy-2-5-4-released'>Groovy 2.5.4 Released</a></li><li><a href='./groovy-2-5-0-released'>Groovy 2.5.0 released</a></li><li><a href='./groovy-2-5-5-released'>Groovy 2.5.5 released</a></li><li><a href='./groovy-2-5-6-released'>Groovy 2.5.6 Released</a></li><li><a href='./groovy-2-5-3-windows'>Groovy 2.5.3 Windows Installer Released (Community Artifact)</a></li><li><a href='./groovy-2-5-3-released'>Groovy 2.5.3 Released</a></li><li><a href='./groovy-2-5-1-released'>Groovy 2.5.1 released</a></li><li><a href='./groovy-2-5-2-windows'>Groovy 2.5.2 Windows Installer Released (Community Artifact)</a></li><li><a href='./groovy-2-5-2-released'>Groovy 2.5.2 released</a></li><li><a href='./groovy-2-5-7-and'>Groovy 2.5.7 and 3.0.0-beta-1 Windows Installers Released (Community Artifacts)</a></li><li><a href='./groovy-2-5-4-windows'>Groovy 2.5.4 Windows Installer Released (Community Artifact)</a></li><li><a href='./groovy-2-4-16-released'>Groovy 2.4.16 Released</a></li><li><a href='./groovy-2-4-16-windows'>Groovy 2.4.16 Windows Installer Released (Community Artifact)</a></li><li><a href='./groovy-3-0-0-beta'>Groovy 3.0.0-beta-1 Released</a></li><li><a href='./groovy-3-0-0-alpha'>Groovy 3.0.0-alpha-4 Released</a></li><li><a href='./groovy-release-train-4-0'>Groovy release train: 4.0.4, 3.0.12, 2.5.18</a></li><li><a href='./groovy-3-0-0-beta2'>Groovy 3.0.0-beta-2 Windows Installer Released (Community Release)</a></li><li><a href='./groovy-3-0-0-alpha1'>Groovy 3.0.0-alpha-4 Windows Installer Released (Community Artifact)</a></li><li><a href='./groovy-2-4-17-released'>Groovy 2.4.17 Released</a></li><li><a href='./gmavenplus-1-6-2-released'>GMavenPlus 1.6.2 Released (Community Artifact)</a></li><li><a href='./groovy-3-0-0-beta1'>Groovy 3.0.0-beta-2 Released</a></li><li><a href='./groovy-4-0-3-released'>Groovy 4.0.3 Released</a></li></ul></div><div class='col-lg-8 col-lg-pull-0'><a name='doc'></a><h1>Apache Groovy 2.5 CliBuilder Renewal</h1><p><span>Author: <i>Remko Popma</i></span><br/><span>Published: 2018-05-30 11:28AM</span></p><hr/><div id="preamble"> |
| <div class="sectionbody"> |
| <div class="paragraph"> |
| <p>The <code>CliBuilder</code> class for quickly and concisely building |
| command-line applications has been renewed in Apache Groovy 2.5. |
| This article highlights what is new.</p> |
| </div> |
| <div class="imageblock"> |
| <div class="content"> |
| <img src="http://picocli.info/images/CliBuilder2.5-cygwin.png" alt="CliBuilder2.5 cygwin"> |
| </div> |
| </div> |
| </div> |
| </div> |
| <div class="sect1"> |
| <h2 id="_the_code_groovy_util_clibuilder_code_class_is_deprecated">The <code>groovy.util.CliBuilder</code> Class is Deprecated</h2> |
| <div class="sectionbody"> |
| <div class="paragraph"> |
| <p>Previous versions of CliBuilder used Apache <a href="https://commons.apache.org/proper/commons-cli/index.html">Commons CLI</a> as the underlying parser library. |
| From Groovy 2.5, there is an alternative version of CliBuilder based on the <a href="https://github.com/remkop/picocli">picocli</a> parser.</p> |
| </div> |
| <div class="paragraph"> |
| <p>It is recommended that applications explicitly import either <code>groovy.cli.picocli.CliBuilder</code> or <code>groovy.cli.commons.CliBuilder</code>. The <code>groovy.util.CliBuilder</code> class is deprecated and delegates to the Commons CLI version for backwards compatibility.</p> |
| </div> |
| <div class="paragraph"> |
| <p>New features will likely only be added to the picocli version, and <code>groovy.util.CliBuilder</code> may be removed in a future version of Groovy. |
| The Commons CLI version is intended for applications that rely on the internals of the Commons CLI implementation of CliBuilder and cannot easily migrate to the picocli version.</p> |
| </div> |
| <div class="paragraph"> |
| <p>Next, let’s look at some new features in Groovy 2.5 CliBuilder.</p> |
| </div> |
| </div> |
| </div> |
| <div class="sect1"> |
| <h2 id="_typed_options">Typed Options</h2> |
| <div class="sectionbody"> |
| <div class="imageblock"> |
| <div class="content"> |
| <img src="http://picocli.info/images/Type.jpg" alt="Type"> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>Options can be boolean flags or they can take one or more option parameters. |
| In previous versions of CliBuilder, you would have to specify <code>args: 1</code> for options that need a parameter, or |
| <code>args: '+'</code> for options that accept multiple parameters.</p> |
| </div> |
| <div class="paragraph"> |
| <p>This version of CliBuilder adds support for typed options. This is convenient when processing parse results, |
| but additionally, the number of arguments is inferred from the type, |
| so if the <code>type</code> is specified, <code>args</code> can be omitted.</p> |
| </div> |
| <div class="paragraph"> |
| <p>For example:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="CodeRay highlight"><code lang="groovy">def cli = new CliBuilder() |
| cli.a(type: String, 'a-arg') |
| cli.b(type: boolean, 'b-arg') |
| cli.c(type: Boolean, 'c-arg') |
| cli.d(type: int, 'd-arg') |
| cli.e(type: Long, 'e-arg') |
| cli.f(type: Float, 'f-arg') |
| cli.g(type: BigDecimal, 'g-arg') |
| cli.h(type: File, 'h-arg') |
| cli.i(type: RoundingMode, 'i-arg') |
| |
| def argz = '''-a John -b -d 21 -e 1980 -f 3.5 -g 3.14159 |
| -h cv.txt -i DOWN and some more'''.split() |
| |
| def options = cli.parse(argz) |
| assert options.a == 'John' |
| assert options.b |
| assert !options.c |
| assert options.d == 21 |
| assert options.e == 1980L |
| assert options.f == 3.5f |
| assert options.g == 3.14159 |
| assert options.h == new File('cv.txt') |
| assert options.i == RoundingMode.DOWN |
| assert options.arguments() == ['and', 'some', 'more'] |
| </code></pre> |
| </div> |
| </div> |
| <div class="sect2"> |
| <h3 id="_supported_types">Supported Types</h3> |
| <div class="paragraph"> |
| <p>The Commons CLI-based CliBuilder supports primitives, numeric types, files, enums and arrays thereof |
| (using <a href="http://docs.groovy-lang.org/2.5.0-SNAPSHOT/html/gapi/index.html?org/codehaus/groovy/runtime/StringGroovyMethods.html#asType">StringGroovyMethods#asType(String, Class)</a>). |
| The picocli-based CliBuilder supports those <a href="http://picocli.info/#_built_in_types">and more</a>.</p> |
| </div> |
| </div> |
| <div class="sect2"> |
| <h3 id="_adding_more_types">Adding More Types</h3> |
| <div class="paragraph"> |
| <p>If the built-in types don’t meet your needs, it is easy to register a custom converter. Specify a <code>convert</code> Closure to convert the String argument to any other type. For example:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="CodeRay highlight"><code lang="groovy">import java.nio.file.Paths |
| import java.time.LocalTime |
| |
| def cli = new CliBuilder() |
| cli.a(convert: { it.toUpperCase() }, 'a-arg') // (1) |
| cli.p(convert: { Paths.get(it) }, 'p-arg') // (2) |
| cli.t(convert: { LocalTime.parse(it) }, 't-arg') // (3) |
| |
| def options = cli.parse('-a abc -p /usr/home -t 15:31:59'.split()) |
| assert options.a == 'ABC' |
| assert options.p.absolute && options.p.parent == Paths.get('/usr') |
| assert options.t.hour == 15 && options.t.minute == 31 |
| </code></pre> |
| </div> |
| </div> |
| <div class="colist arabic"> |
| <table> |
| <tr> |
| <td><i class="conum" value="1"></i><b>1</b></td> |
| <td>Convert one String to another</td> |
| </tr> |
| <tr> |
| <td><i class="conum" value="2"></i><b>2</b></td> |
| <td>Option value is converted to a <code>java.nio.file.Path</code></td> |
| </tr> |
| <tr> |
| <td><i class="conum" value="3"></i><b>3</b></td> |
| <td>Option value is converted to a <code>java.time.LocalTime</code></td> |
| </tr> |
| </table> |
| </div> |
| </div> |
| </div> |
| </div> |
| <div class="sect1"> |
| <h2 id="_annotations">Annotations</h2> |
| <div class="sectionbody"> |
| <div class="imageblock"> |
| <div class="content"> |
| <img src="http://picocli.info/images/a-annotations.png" alt="Annotations"> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>From this release, Groovy offers an annotation API for processing command line arguments.</p> |
| </div> |
| <div class="paragraph"> |
| <p>Applications can annotate fields or methods with <code>@groovy.cli.Option</code> for named options |
| or <code>@groovy.cli.Unparsed</code> for positional parameters. |
| When the parser matches a command line argument with an option name or positional parameter, the value is converted |
| to the correct type and injected into the field or method.</p> |
| </div> |
| <div class="sect2"> |
| <h3 id="_annotating_methods_of_an_interface">Annotating Methods of an Interface</h3> |
| <div class="paragraph"> |
| <p>One way to use the annotations is on "getter-like" methods (methods that return a value) of an interface. For example:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="CodeRay highlight"><code lang="groovy">import groovy.cli.* |
| |
| interface IHello { |
| @Option(shortName='h', description='display usage') Boolean help() // (1) |
| @Option(shortName='u', description='user name') String user() // (2) |
| @Unparsed(description = 'positional parameters') List remaining() // (3) |
| } |
| </code></pre> |
| </div> |
| </div> |
| <div class="colist arabic"> |
| <table> |
| <tr> |
| <td><i class="conum" value="1"></i><b>1</b></td> |
| <td>Method returns <code>true</code> if <code>-h</code> or <code>--help</code> was specified on the command line.</td> |
| </tr> |
| <tr> |
| <td><i class="conum" value="2"></i><b>2</b></td> |
| <td>Method returns the parameter value that was specified for the <code>-u</code> or <code>--user</code> option.</td> |
| </tr> |
| <tr> |
| <td><i class="conum" value="3"></i><b>3</b></td> |
| <td>Any remaining parameters will be returned as a list from this method.</td> |
| </tr> |
| </table> |
| </div> |
| <div class="paragraph"> |
| <p>How to use this interface (using the picocli version to demonstrate its usage help):</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="CodeRay highlight"><code lang="groovy">import groovy.cli.picocli.CliBuilder |
| |
| def cli = new CliBuilder(name: 'groovy Greeter') |
| def argz = '--user abc'.split() |
| IHello hello = cli.parseFromSpec(IHello, argz) |
| assert hello.user() == 'abc' |
| |
| hello = cli.parseFromSpec(GreeterI, ['--help', 'Some', 'Other', 'Args'] as String[]) |
| assert hello.help() |
| cli.usage() |
| assert hello.remaining() == ['Some', 'Other', 'Args'] |
| </code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>This prints the following usage help message:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="CodeRay highlight"><code>Usage: groovy Greeter [-h] [-u=<user>] [<remaining>...] |
| [<remaining>...] positional parameters |
| -u, --user=<user> user name |
| -h, --help display usage</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>When <code>parseFromSpec</code> is called, <code>CliBuilder</code> reads the annotations, parses the command line arguments |
| and returns an instance of the interface. |
| The interface methods return the option values matched on the command line.</p> |
| </div> |
| </div> |
| <div class="sect2"> |
| <h3 id="_annotating_properties_or_setter_methods_of_a_class">Annotating Properties or Setter Methods of a Class</h3> |
| <div class="paragraph"> |
| <p>Another way to use the annotations is on the properties or "setter-like" methods (<code>void</code> methods with a single parameter) of a class. For example:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="CodeRay highlight"><code lang="groovy">class Hello { |
| @Option(shortName='h', description='display usage') // (1) |
| Boolean help |
| |
| private String user |
| @Option(shortName='u', description='user name') // (2) |
| void setUser(String user) { |
| this.user = user |
| } |
| String getUser() { user } |
| |
| @Unparsed(description = 'positional parameters') // (3) |
| List remaining |
| } |
| </code></pre> |
| </div> |
| </div> |
| <div class="colist arabic"> |
| <table> |
| <tr> |
| <td><i class="conum" value="1"></i><b>1</b></td> |
| <td>The <code>help</code> Boolean property is set to <code>true</code> if <code>-h</code> or <code>--help</code> was specified on the command line.</td> |
| </tr> |
| <tr> |
| <td><i class="conum" value="2"></i><b>2</b></td> |
| <td>The <code>setUser</code> property setter method is invoked with the <code>-u</code> or <code>--user</code> option parameter value.</td> |
| </tr> |
| <tr> |
| <td><i class="conum" value="3"></i><b>3</b></td> |
| <td>The <code>remaining</code> property is set to a new <code>List</code> containing the remaining args, if any.</td> |
| </tr> |
| </table> |
| </div> |
| <div class="paragraph"> |
| <p>The annotated class can be used as follows:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="CodeRay highlight"><code lang="groovy">String[] argz = ['--user', 'abc', 'foo'] |
| |
| def cli = new CliBuilder(usage: 'groovy Greeter [option]') // (1) |
| Hello greeter = cli.parseFromInstance(new Hello(), argz) // (2) |
| assert greeter.user == 'abc' // (3) |
| assert greeter.remaining == ['foo'] // (4) |
| </code></pre> |
| </div> |
| </div> |
| <div class="colist arabic"> |
| <table> |
| <tr> |
| <td><i class="conum" value="1"></i><b>1</b></td> |
| <td>Create a <code>CliBuilder</code> instance.</td> |
| </tr> |
| <tr> |
| <td><i class="conum" value="2"></i><b>2</b></td> |
| <td>Extract options from the annotated instance, parse arguments, and populate and return the supplied instance.</td> |
| </tr> |
| <tr> |
| <td><i class="conum" value="3"></i><b>3</b></td> |
| <td>Verify that the String option value has been assigned to the property.</td> |
| </tr> |
| <tr> |
| <td><i class="conum" value="4"></i><b>4</b></td> |
| <td>Verify the remaining arguments property.</td> |
| </tr> |
| </table> |
| </div> |
| <div class="paragraph"> |
| <p>When <code>parseFromInstance</code> is called, <code>CliBuilder</code> again reads the annotations, parses the command line |
| arguments and finally returns the instance. The annotated fields and setter methods are initialized with the values |
| matched for the associated option.</p> |
| </div> |
| </div> |
| <div class="sect2"> |
| <h3 id="_script_annotations">Script Annotations</h3> |
| <div class="imageblock"> |
| <div class="content"> |
| <img src="http://picocli.info/images/GroovyScriptAnnotations.png" alt="Script"> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>Groovy 2.5 also offers new annotations for Groovy scripts.</p> |
| </div> |
| <div class="paragraph"> |
| <p><code>@OptionField</code> is equivalent to combining <code>@groovy.transform.Field</code> and <code>@Option</code>, whereas <code>@UnparsedField</code> is equivalent to combining <code>@Field</code> and <code>@Unparsed</code>.</p> |
| </div> |
| <div class="paragraph"> |
| <p>Use these annotations to turn script variables into fields so that the variables can be populated by CliBuilder. For example:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="CodeRay highlight"><code lang="groovy">import groovy.cli.OptionField |
| import groovy.cli.UnparsedField |
| |
| @OptionField String user |
| @OptionField Boolean help |
| @UnparsedField List remaining |
| |
| String[] argz = ['--user', 'abc', 'foo'] |
| |
| new CliBuilder().parseFromInstance(this, argz) |
| assert user == 'abc' |
| assert remaining == ['foo'] |
| </code></pre> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| <div class="sect1"> |
| <h2 id="_typed_positional_parameters">Typed Positional Parameters</h2> |
| <div class="sectionbody"> |
| <div class="paragraph"> |
| <p>This version of CliBuilder offers some limited support for strongly typed positional parameters.</p> |
| </div> |
| <div class="paragraph"> |
| <p>If all positional parameters have the same type, the <code>@Unparsed</code> annotation can be used with an array type other than <code>String[]</code>. |
| Again, the type conversion is done using <a href="http://docs.groovy-lang.org/2.5.0-SNAPSHOT/html/gapi/index.html?org/codehaus/groovy/runtime/StringGroovyMethods.html#asType">StringGroovyMethods#asType(String, Class)</a> |
| in the Commons CLI version, while the picocli version of CliBuilder supports a <a href="http://picocli.info/#_built_in_types">superset</a> of those types.</p> |
| </div> |
| <div class="paragraph"> |
| <p>This functionality is only available for the annotations API, not for the dynamic API. |
| Here is an example of an interface that can capture strongly typed positional parameters:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="CodeRay highlight"><code lang="groovy">interface TypedPositionals { |
| @Unparsed Integer[] nums() |
| } |
| </code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>The code below demonstrates the type conversion:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="CodeRay highlight"><code lang="groovy">def argz = '12 34 56'.split() |
| def cli = new CliBuilder() |
| def options = cli.parseFromSpec(TypedPositionals, argz) |
| assert options.nums() == [12, 34, 56] |
| </code></pre> |
| </div> |
| </div> |
| </div> |
| </div> |
| <div class="sect1"> |
| <h2 id="_apache_commons_cli_features">Apache Commons CLI Features</h2> |
| <div class="sectionbody"> |
| <div class="imageblock"> |
| <div class="content"> |
| <img src="http://picocli.info/images/FeatureIconAdvancedOptions.png" alt="FeatureIconAdvancedOptions"> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>Sometimes you may want to use advanced features of the underlying parsing library. |
| For example, you may have a command line application with mutually exclusive options. |
| The below code shows how to achieve this using the Apache Commons CLI <code>OptionGroup</code> API:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="CodeRay highlight"><code lang="groovy">import groovy.cli.commons.CliBuilder |
| import org.apache.commons.cli.* |
| |
| def cli = new CliBuilder() |
| def optionGroup = new OptionGroup() |
| optionGroup.with { |
| addOption cli.option('s', [longOpt: 'silent'], 's option') |
| addOption cli.option('v', [longOpt: 'verbose'], 'v option') |
| } |
| cli.options.addOptionGroup optionGroup |
| |
| assert !cli.parse('--silent --verbose'.split()) (1) |
| </code></pre> |
| </div> |
| </div> |
| <div class="colist arabic"> |
| <table> |
| <tr> |
| <td><i class="conum" value="1"></i><b>1</b></td> |
| <td>Parsing this input will fail because two mutually exclusive options were specified.</td> |
| </tr> |
| </table> |
| </div> |
| </div> |
| </div> |
| <div class="sect1"> |
| <h2 id="_picocli_clibuilder_features">Picocli CliBuilder Features</h2> |
| <div class="sectionbody"> |
| <div class="imageblock"> |
| <div class="content"> |
| <img src="http://picocli.info/images/FeatureIconAdvancedOptions.png" alt="FeatureIconAdvancedOptions"> |
| </div> |
| </div> |
| <div class="sect2"> |
| <h3 id="_strongly_typed_lists">Strongly Typed Lists</h3> |
| <div class="imageblock"> |
| <div class="content"> |
| <img src="http://picocli.info/images/list.png" alt="list"> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>Options with multiple values often use an array or a List to capture the values. |
| Arrays can be strongly typed, that is, contain elements other than String. |
| The picocli version of CliBuilder lets you do the same with Lists. |
| The <code>auxiliaryType</code> specifies the type that the elements should be converted to. |
| For example:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="CodeRay highlight"><code lang="groovy">import groovy.cli.picocli.CliBuilder |
| |
| def cli = new CliBuilder() |
| cli.T(type: List, auxiliaryTypes: Long, 'typed list') // (1) |
| |
| def options = cli.parse('-T 1 -T 2 -T 3'.split()) // (2) |
| assert options.Ts == [ 1L, 2L, 3L ] // (3) |
| </code></pre> |
| </div> |
| </div> |
| <div class="colist arabic"> |
| <table> |
| <tr> |
| <td><i class="conum" value="1"></i><b>1</b></td> |
| <td>Define an option that can have multiple integer values.</td> |
| </tr> |
| <tr> |
| <td><i class="conum" value="2"></i><b>2</b></td> |
| <td>An example command line.</td> |
| </tr> |
| <tr> |
| <td><i class="conum" value="3"></i><b>3</b></td> |
| <td>The option values as a <code>List<Integer></code>.</td> |
| </tr> |
| </table> |
| </div> |
| </div> |
| <div class="sect2"> |
| <h3 id="_strongly_typed_maps">Strongly Typed Maps</h3> |
| <div class="imageblock"> |
| <div class="content"> |
| <img src="http://picocli.info/images/map.png" alt="map"> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>The picocli version of CliBuilder offers native support for Map options. |
| This is as simple as specifying Map as the option type. |
| By default, both keys and values are stored as Strings in the Map, |
| but it’s possible to use <code>auxiliaryType</code> to specify the types that the keys and values should be converted to.</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="CodeRay highlight"><code lang="groovy">import groovy.cli.picocli.CliBuilder |
| |
| def cli = new CliBuilder() |
| cli.D(args: 2, valueSeparator: '=', 'Commons CLI style map') // (1) |
| cli.X(type: Map, 'picocli style map support') // (2) |
| cli.Z(type: Map, auxiliaryTypes: [TimeUnit, Integer].toArray(), 'typed map') // (3) |
| |
| def options = cli.parse('-Da=b -Dc=d -Xx=y -Xi=j -ZDAYS=2 -ZHOURS=23'.split()) // (4) |
| assert options.Ds == ['a', 'b', 'c', 'd'] // (5) |
| assert options.Xs == [ 'x':'y', 'i':'j' ] // (6) |
| assert options.Zs == [ (DAYS as TimeUnit):2, (HOURS as TimeUnit):23 ] // (7) |
| </code></pre> |
| </div> |
| </div> |
| <div class="colist arabic"> |
| <table> |
| <tr> |
| <td><i class="conum" value="1"></i><b>1</b></td> |
| <td>Commons CLI has map-like options by specifying that each option must have two parameters, with some separator.</td> |
| </tr> |
| <tr> |
| <td><i class="conum" value="2"></i><b>2</b></td> |
| <td>The picocli version of CliBuilder has native support for Map options.</td> |
| </tr> |
| <tr> |
| <td><i class="conum" value="3"></i><b>3</b></td> |
| <td>The key type and value type can be specified for strongly-typed maps.</td> |
| </tr> |
| <tr> |
| <td><i class="conum" value="4"></i><b>4</b></td> |
| <td>An example command line.</td> |
| </tr> |
| <tr> |
| <td><i class="conum" value="5"></i><b>5</b></td> |
| <td>The Commons CLI style option gives a list of [key, value, key, value, …​] objects.</td> |
| </tr> |
| <tr> |
| <td><i class="conum" value="6"></i><b>6</b></td> |
| <td>The picocli style option gives the result as a <code>Map<String, String></code>.</td> |
| </tr> |
| <tr> |
| <td><i class="conum" value="7"></i><b>7</b></td> |
| <td>When <code>auxiliaryTypes</code> are specified, the keys and values of the map are converted to the specified types, giving you a <code>Map<TimeUnit, Integer></code>.</td> |
| </tr> |
| </table> |
| </div> |
| </div> |
| <div class="sect2"> |
| <h3 id="_usage_help_with_detailed_synopsis">Usage Help with Detailed Synopsis</h3> |
| <div class="imageblock"> |
| <div class="content"> |
| <img src="http://picocli.info/images/iceberg.png" alt="iceberg"> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>CliBuilder has always supported a <code>usage</code> property to display the usage help synopsis of a command:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="CodeRay highlight"><code lang="groovy">// the old way |
| new CliBuilder(usage: 'myapp [options]').usage() |
| </code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>The above program prints:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre>Usage: myapp [options]</pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>This still works, but the picocli version has a better alternative with the <code>name</code> property. |
| If you specify <code>name</code> instead of <code>usage</code>, picocli will show all options in a succinct synopsis with square brackets <code>[</code> and <code>]</code> for optional elements and ellipsis <code>…​</code> for elements that can be repeated one or more times. For example:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="CodeRay highlight"><code lang="groovy">// the new way |
| def cli = new CliBuilder(name: 'myapp') // detailed synopsis |
| cli.a('option a description') |
| cli.b('option b description') |
| cli.c(type: List, 'option c description') |
| cli.usage() |
| </code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>The above program prints:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre>Usage: myapp [-ab] [-c=PARAM]... |
| -a option a description |
| -b option b description |
| -c= PARAM option c description</pre> |
| </div> |
| </div> |
| </div> |
| <div class="sect2"> |
| <h3 id="_use_any_option_names">Use Any Option Names</h3> |
| <div class="imageblock"> |
| <div class="content"> |
| <img src="http://picocli.info/images/freedom-c-PsychoShadow-www.bigstockphoto.com.jpg" alt="freedom c PsychoShadow www.bigstockphoto.com"> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p><em>Image credit: (c) PsychoShadow - www.bigstockphoto.com</em></p> |
| </div> |
| <div class="paragraph"> |
| <p>Before, if an option had multiple names with a single hyphen, you had no choice but to declare the option multiple times:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="CodeRay highlight"><code lang="groovy">// before: split -cp, -classpath into two options |
| def cli = new CliBuilder(usage: 'groovyConsole [options] [filename]') |
| cli.classpath('Where to find the class files') |
| cli.cp(longOpt: 'classpath', 'Aliases for '-classpath') |
| </code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>The picocli version of CliBuilder supports a <code>names</code> property that can have any number of option names that can take any prefix. For example:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="CodeRay highlight"><code lang="groovy">// after: an option can have many names with any prefix |
| def cli = new CliBuilder(usage: 'groovyConsole [options] [filename]') |
| cli._(names: ['-cp', '-classpath', '--classpath'], 'Where to find the class files') |
| </code></pre> |
| </div> |
| </div> |
| </div> |
| <div class="sect2"> |
| <h3 id="_fine_grained_usage_help_message">Fine-grained Usage Help Message</h3> |
| <div class="imageblock"> |
| <div class="content"> |
| <img src="http://picocli.info/images/sift.png" alt="sift"> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>Picocli offers fine-grained control over the usage help message format and this functionality is exposed via the <code>usageMessage</code> CliBuilder property.</p> |
| </div> |
| <div class="paragraph"> |
| <p>The usage message has a number of sections: header, synopsis, description, parameters, options and finally the footer. Each section has a heading, that precedes the first line of its section. For example:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="CodeRay highlight"><code lang="groovy">import groovy.cli.picocli.CliBuilder |
| |
| def cli = new CliBuilder() |
| cli.name = "groovy clidemo" |
| cli.usageMessage.with { // (1) |
| headerHeading("Header heading:%n") // (2) |
| header("header 1", "header 2") // (3) |
| synopsisHeading("%nUSAGE: ") |
| descriptionHeading("%nDescription heading:%n") |
| description("description 1", "description 2") |
| optionListHeading("%nOPTIONS:%n") |
| footerHeading("%nFooter heading:%n") |
| footer("footer 1", "footer 2") |
| } |
| cli.a(longOpt: 'aaa', 'a-arg') // (4) |
| cli.b(longOpt: 'bbb', 'b-arg') |
| cli.usage() |
| </code></pre> |
| </div> |
| </div> |
| <div class="colist arabic"> |
| <table> |
| <tr> |
| <td><i class="conum" value="1"></i><b>1</b></td> |
| <td>Use the <code>usageMessage</code> CliBuilder property to customize the usage help message.</td> |
| </tr> |
| <tr> |
| <td><i class="conum" value="2"></i><b>2</b></td> |
| <td>Headings can contain string format specifiers like the <code>%n</code> newline.</td> |
| </tr> |
| <tr> |
| <td><i class="conum" value="3"></i><b>3</b></td> |
| <td>Sections are multi-line: each string will be rendered on a separate line.</td> |
| </tr> |
| <tr> |
| <td><i class="conum" value="4"></i><b>4</b></td> |
| <td>Define some options.</td> |
| </tr> |
| </table> |
| </div> |
| <div class="paragraph"> |
| <p>This prints the following output:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre>Header heading: |
| header 1 |
| header 2 |
| |
| USAGE: groovy clidemo [-ab] |
| |
| Description heading: |
| description 1 |
| description 2 |
| |
| OPTIONS: |
| -a, --aaa a-arg |
| -b, --bbb b-arg |
| |
| Footer heading: |
| footer 1 |
| footer 2</pre> |
| </div> |
| </div> |
| </div> |
| <div class="sect2"> |
| <h3 id="_usage_help_with_ansi_colors">Usage Help with ANSI Colors</h3> |
| <div class="paragraph"> |
| <p>Out of the box, the command name, option names and parameter labels in the usage help message are rendered with <a href="http://picocli.info/#_ansi_colors_and_styles">ANSI styles and colors</a>. |
| The color scheme for these elements can be <a href="http://picocli.info/#_configuring_fixed_elements">configured</a> with system properties.</p> |
| </div> |
| <div class="paragraph"> |
| <p>Other than that, you can use colors and styles in the descriptions and other sections of the usage help message, |
| using a <a href="http://picocli.info/#_usage_help_with_styles_and_colors">simple markup notation</a>. The example below demonstrates:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="CodeRay highlight"><code lang="groovy">def cli = new groovy.cli.picocli.CliBuilder(name: 'myapp') |
| cli.usageMessage.with { |
| headerHeading("@|bold,red,underline Header heading|@:%n") |
| header($/@|bold,green \ |
| ___ _ _ ___ _ _ _ |
| / __| (_) _ )_ _(_) |__| |___ _ _ |
| | (__| | | _ \ || | | / _` / -_) '_| |
| \___|_|_|___/\_,_|_|_\__,_\___|_| |
| |@/$) |
| synopsisHeading("@|bold,underline Usage|@: ") |
| descriptionHeading("%n@|bold,underline Description heading|@:%n") |
| description("Description 1", "Description 2") // after the synopsis |
| optionListHeading("%n@|bold,underline Options heading|@:%n") |
| footerHeading("%n@|bold,underline Footer heading|@:%n") |
| footer($/@|bold,blue \ |
| ___ ___ ___ |
| / __|_ _ ___ _____ ___ _ |_ ) | __| |
| | (_ | '_/ _ \/ _ \ V / || | / / _|__ \ |
| \___|_| \___/\___/\_/ \_, | /___(_)___/ |
| |__/ |@/$) |
| } |
| cli.a('option a description') |
| cli.b('option b description') |
| cli.c(type: List, 'option c description') |
| cli.usage() |
| </code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>The code above gives the following output:</p> |
| </div> |
| <div class="imageblock"> |
| <div class="content"> |
| <img src="http://picocli.info/images/CliBuilder2.5-cygwin.png" alt="CliBuilder2.5 cygwin"> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>(Credit to <a href="http://patorjk.com/software/taag/">http://patorjk.com/software/taag/</a> for the ASCII art.)</p> |
| </div> |
| </div> |
| <div class="sect2"> |
| <h3 id="_new_code_errorwriter_code_property">New <code>errorWriter</code> Property</h3> |
| <div class="imageblock"> |
| <div class="content"> |
| <img src="http://picocli.info/images/error.png" alt="error"> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>When the user provided invalid input, the picocli version of CliBuilder writes an error message and the usage help message to the new <code>errorWriter</code> property (set to <code>System.err</code> by default). |
| When the user requests help, and the application calls <code>CliBuilder.usage()</code>, the usage help message is printed to the <code>writer</code> property (<code>System.out</code> by default).</p> |
| </div> |
| <div class="paragraph"> |
| <p>Previous versions of CliBuilder used the <code>writer</code> property for both invalid input and user-requested help.</p> |
| </div> |
| <div class="paragraph"> |
| <p>Why this change? This helps command line application authors to follow standard practice and separate diagnostic output from the program output: If the output of a Groovy program is piped to another program, |
| sending error messages to STDERR prevents the downstream program from inadvertently trying to parse error output. |
| On the other hand, when users request help with <code>--help</code> or <code>--version</code>, the output should be sent to STDOUT, |
| because the user may want to pipe the output to a utility like <code>less</code> or <code>grep</code>.</p> |
| </div> |
| <div class="paragraph"> |
| <p>For backwards compatibility, setting the <code>writer</code> property to another value will also set the <code>errorWriter</code> to the same value. |
| (You can still set the <code>errorWriter</code> to another value afterwards if desired.)</p> |
| </div> |
| </div> |
| </div> |
| </div> |
| <div class="sect1"> |
| <h2 id="_gotchas_incompatibilities">Gotchas/Incompatibilities</h2> |
| <div class="sectionbody"> |
| <div class="imageblock"> |
| <div class="content"> |
| <img src="http://picocli.info/images/incompatible.jpg" alt="incompatible"> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>There are a few areas where the new versions of <code>CliBuilder</code> are not compatible with previous versions or with each other.</p> |
| </div> |
| <div class="sect2"> |
| <h3 id="_properties_code_options_code_and_code_formatter_code_unavailable_in_picocli_version">Properties <code>options</code> and <code>formatter</code> Unavailable in Picocli Version</h3> |
| <div class="paragraph"> |
| <p>The Commons CLI version of CliBuilder, and previous versions of CliBuilder, expose an <code>options</code> property of type <code>org.apache.commons.cli.Options</code>, that can be used to configure the underlying Commons CLI parser without going through the CliBuilder API. This property is not available in the picocli version of CliBuilder. |
| Applications that read or write this property must import <code>groovy.cli.commons.CliBuilder</code> |
| or modify the application.</p> |
| </div> |
| <div class="paragraph"> |
| <p>Additionally, the <code>formatter</code> property of type <code>org.apache.commons.cli.HelpFormatter</code> is not available in the picocli version of CliBuilder. If your application uses this property, consider using the <code>usageMessage</code> property instead, or import <code>groovy.cli.commons.CliBuilder</code>.</p> |
| </div> |
| </div> |
| <div class="sect2"> |
| <h3 id="_property_code_parser_code_differs_in_picocli_and_commons_cli_versions">Property <code>parser</code> Differs in Picocli and Commons CLI Versions</h3> |
| <div class="paragraph"> |
| <p>The picocli version of CliBuilder has a <code>parser</code> property that exposes a <code>picocli.CommandLine.Model.ParserSpec</code> object |
| that can be used to configure the parser behavior.</p> |
| </div> |
| <div class="paragraph"> |
| <p>The Commons CLI version of CliBuilder, and previous versions of CliBuilder, expose a <code>parser</code> property of type <code>org.apache.commons.cli.CommandLineParser</code>. This functionality is not available in the picocli version of CliBuilder.</p> |
| </div> |
| <div class="paragraph"> |
| <p>If your application uses the <code>parser</code> property to set a different Commons CLI parser, consider using the <code>posix</code> property instead, or import <code>groovy.cli.commons.CliBuilder</code>.</p> |
| </div> |
| </div> |
| <div class="sect2"> |
| <h3 id="_different_parser_behavior_for_code_longoption_code">Different Parser Behavior for <code>longOption</code></h3> |
| <div class="paragraph"> |
| <p>The Commons CLI <code>DefaultParser</code> recognizes <code>longOption</code> option names prefixed with a single hypen (e.g., <code>-option</code>) |
| as well as options prefixed with a double hyphen (e.g., <code>--option</code>). |
| This is not always obvious since the usage help message only shows the double hyphen prefix for <code>longOption</code> option names.</p> |
| </div> |
| <div class="paragraph"> |
| <p>For backwards compatibility, the picocli version of CliBuilder has an <code>acceptLongOptionsWithSingleHyphen</code> property: |
| set this property to <code>true</code> if the parser should recognize long option names with both |
| a single hyphen and a double hyphen prefix. The default is <code>false</code>, |
| so only long option names with a double hypen prefix (<code>--option</code>) are recognized.</p> |
| </div> |
| </div> |
| </div> |
| </div> |
| <div class="sect1"> |
| <h2 id="_conclusion">Conclusion</h2> |
| <div class="sectionbody"> |
| <div class="paragraph"> |
| <p>Groovy 2.5 CliBuilder offers a host of exciting new features. |
| Try it out and let us know what you think!</p> |
| </div> |
| <div class="paragraph"> |
| <p>For reference: Groovy <a href="http://groovy-lang.org/">site</a> and |
| GitHub <a href="https://github.com/apache/groovy/">mirror</a>, |
| picocli <a href="http://picocli.info/">site</a> and |
| <a href="https://github.com/remkop/picocli">picocli GitHub project</a>. |
| Please star the projects if you like what you see!</p> |
| </div> |
| <div class="paragraph"> |
| <p>A copy of this article was previously published on the picocli website.<br> |
| <a href="http://picocli.info/groovy-2.5-clibuilder-renewal.html">See the original article here.</a></p> |
| </div> |
| </div> |
| </div></div></div></div></div><footer id='footer'> |
| <div class='row'> |
| <div class='colset-3-footer'> |
| <div class='col-1'> |
| <h1>Groovy</h1><ul> |
| <li><a href='https://groovy-lang.org/learn.html'>Learn</a></li><li><a href='https://groovy-lang.org/documentation.html'>Documentation</a></li><li><a href='/download.html'>Download</a></li><li><a href='https://groovy-lang.org/support.html'>Support</a></li><li><a href='/'>Contribute</a></li><li><a href='https://groovy-lang.org/ecosystem.html'>Ecosystem</a></li><li><a href='/blog'>Blog posts</a></li><li><a href='https://groovy.apache.org/events.html'></a></li> |
| </ul> |
| </div><div class='col-2'> |
| <h1>About</h1><ul> |
| <li><a href='https://github.com/apache/groovy'>Source code</a></li><li><a href='https://groovy-lang.org/security.html'>Security</a></li><li><a href='https://groovy-lang.org/learn.html#books'>Books</a></li><li><a href='https://groovy-lang.org/thanks.html'>Thanks</a></li><li><a href='http://www.apache.org/foundation/sponsorship.html'>Sponsorship</a></li><li><a href='https://groovy-lang.org/faq.html'>FAQ</a></li><li><a href='https://groovy-lang.org/search.html'>Search</a></li> |
| </ul> |
| </div><div class='col-3'> |
| <h1>Socialize</h1><ul> |
| <li><a href='https://groovy-lang.org/mailing-lists.html'>Discuss on the mailing-list</a></li><li><a href='https://twitter.com/ApacheGroovy'>Groovy on Twitter</a></li><li><a href='https://groovy-lang.org/events.html'>Events and conferences</a></li><li><a href='https://github.com/apache/groovy'>Source code on GitHub</a></li><li><a href='https://groovy-lang.org/reporting-issues.html'>Report issues in Jira</a></li><li><a href='http://stackoverflow.com/questions/tagged/groovy'>Stack Overflow questions</a></li><li><a href='http://groovycommunity.com/'>Slack Community</a></li> |
| </ul> |
| </div><div class='col-right'> |
| <p> |
| The Groovy programming language is supported by the <a href='http://www.apache.org'>Apache Software Foundation</a> and the Groovy community. |
| </p><div text-align='right'> |
| <img src='../img/asf_logo.png' title='The Apache Software Foundation' alt='The Apache Software Foundation' style='width:60%'/> |
| </div><p>Apache® and the Apache feather logo are either registered trademarks or trademarks of The Apache Software Foundation.</p> |
| </div> |
| </div><div class='clearfix'>© 2003-2024 the Apache Groovy project — Groovy is Open Source: <a href='http://www.apache.org/licenses/LICENSE-2.0.html' alt='Apache 2 License'>license</a>, <a href='https://privacy.apache.org/policies/privacy-policy-public.html'>privacy policy</a>.</div> |
| </div> |
| </footer></div> |
| </div> |
| </div> |
| </div> |
| </div><script src='../js/vendor/jquery-1.10.2.min.js' defer></script><script src='../js/vendor/classie.js' defer></script><script src='../js/vendor/bootstrap.js' defer></script><script src='../js/vendor/sidebarEffects.js' defer></script><script src='../js/vendor/modernizr-2.6.2.min.js' defer></script><script src='../js/plugins.js' defer></script><script src='https://cdnjs.cloudflare.com/ajax/libs/prettify/r298/prettify.min.js'></script><script>document.addEventListener('DOMContentLoaded',prettyPrint)</script><script> |
| (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ |
| (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), |
| m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) |
| })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); |
| |
| ga('create', 'UA-257558-10', 'auto'); |
| ga('send', 'pageview'); |
| </script> |
| </body></html> |