├── notes ├── 0.1.markdown ├── 0.4.markdown ├── 0.3.8.markdown ├── 1.0.3.markdown ├── 0.3.7.markdown ├── 0.3.4.markdown ├── 0.3.5.markdown ├── 1.0.2.md ├── 0.3.2.markdown ├── 0.3.3.markdown ├── 0.2.markdown ├── 0.3.6.markdown ├── 1.0.1.markdown ├── 1.0.0.markdown ├── 0.3.1.markdown ├── 0.3.markdown └── about.markdown ├── project ├── build.properties └── plugins.sbt ├── .gitignore ├── .ensime ├── src ├── main │ ├── ls │ │ ├── 0.3.6.json │ │ ├── 0.3.7.json │ │ ├── 0.3.8.json │ │ ├── 1.0.2.json │ │ ├── 1.0.1.json │ │ └── 1.0.0.json │ └── scala │ │ └── org │ │ └── clapper │ │ └── argot │ │ ├── ArgotTest.scala │ │ ├── exception.scala │ │ └── Argot.scala └── test │ └── scala │ └── org │ └── clapper │ └── argot │ └── ArgotParser │ ├── multioption.scala │ ├── option.scala │ ├── flag.scala │ └── param.scala ├── README.md ├── LICENSE.md └── CHANGELOG.md /notes/0.1.markdown: -------------------------------------------------------------------------------- 1 | * Initial version. 2 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.8 2 | -------------------------------------------------------------------------------- /notes/0.4.markdown: -------------------------------------------------------------------------------- 1 | * Added Scala 2.9.2 to the set of cross-built versions. 2 | -------------------------------------------------------------------------------- /notes/0.3.8.markdown: -------------------------------------------------------------------------------- 1 | * Added Scala 2.9.1-1 to the set of cross-built versions. 2 | -------------------------------------------------------------------------------- /notes/1.0.3.markdown: -------------------------------------------------------------------------------- 1 | Various tweaks to try to get Bintray to shove this thing into Maven Central. 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("me.lessis" % "bintray-sbt" % "0.3.0") 2 | addSbtPlugin("com.typesafe.sbt" % "sbt-pgp" % "0.8.1") 3 | -------------------------------------------------------------------------------- /notes/0.3.7.markdown: -------------------------------------------------------------------------------- 1 | * Built for Scala 2.8.2, in addition to 2.8.0, 2.8.1, 2.9.0, 2.9.0-1 and 2.9.1 2 | * Updated Grizzled Scala version. 3 | -------------------------------------------------------------------------------- /notes/0.3.4.markdown: -------------------------------------------------------------------------------- 1 | * Converted code to conform with standard Scala coding style. 2 | 3 | [SBT]: http://code.google.com/p/simple-build-tool/ 4 | -------------------------------------------------------------------------------- /notes/0.3.5.markdown: -------------------------------------------------------------------------------- 1 | * Now builds for [Scala][] 2.9.1, as well as 2.9.0-1, 2.9.0, 2.8.1, and 2.8.0. 2 | 3 | [Scala]: http://www.scala-lang.org/ 4 | -------------------------------------------------------------------------------- /notes/1.0.2.md: -------------------------------------------------------------------------------- 1 | - Added cross-compilation for Scala 2.11, courtesy of 2 | Martin Grotzke (martin.grotzke _at_ googlemail.com) 3 | - Publish to Bintray. 4 | -------------------------------------------------------------------------------- /notes/0.3.2.markdown: -------------------------------------------------------------------------------- 1 | * Updated [posterous-sbt][] to version 0.1.7. 2 | 3 | [posterous-sbt]: https://github.com/n8han/posterous-sbt 4 | [SBT]: http://code.google.com/p/simple-build-tool/ 5 | -------------------------------------------------------------------------------- /notes/0.3.3.markdown: -------------------------------------------------------------------------------- 1 | * Now builds against Scala 2.9.0.1, as well as Scala 2.9.0, 2.8.1 and 2.8.0. 2 | * Converted to build with [SBT][] 0.10.1 3 | 4 | [SBT]: http://code.google.com/p/simple-build-tool/ 5 | -------------------------------------------------------------------------------- /notes/0.2.markdown: -------------------------------------------------------------------------------- 1 | * Upgraded [Grizzled Scala][] dependency to version 1.0.3. 2 | * Now compiles against [Scala][] 2.8.1, as well as 2.8.0. 3 | 4 | [Grizzled Scala]: http://bmc.github.com/grizzled-scala/ 5 | [Scala]: http://www.scala-lang.org/ 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib_managed 2 | local_lib 3 | project/boot 4 | project/build/target 5 | project/plugins/lib_managed 6 | project/plugins/project 7 | project/plugins/src_managed 8 | project/plugins/target 9 | target 10 | *.sublime-* 11 | .idea* 12 | -------------------------------------------------------------------------------- /notes/0.3.6.markdown: -------------------------------------------------------------------------------- 1 | * Converted to use SBT 0.11.2. 2 | * Updated ScalaTest versions. 3 | * Added _ls.implicit.ly_ metadata. 4 | * Now publishes to `oss.sonatype.org` (and, thence, to the Maven central repo). 5 | 6 | [Scala]: http://www.scala-lang.org/ 7 | -------------------------------------------------------------------------------- /notes/1.0.1.markdown: -------------------------------------------------------------------------------- 1 | * Built for the Scala 2.10.0 and 2.10.1. 2 | 3 | **This version, and later versions, are 2.10-only.** 2.9.x and earlier will be 4 | supported via the `pre-scala-2.10` GitHub branch. This is due to changes 5 | in the Scala library between 2.9 and 2.10. 6 | -------------------------------------------------------------------------------- /.ensime: -------------------------------------------------------------------------------- 1 | ;; This config was generated using ensime-config-gen. Feel free to customize its contents manually. 2 | 3 | ( 4 | 5 | :server-root "~/lib/scala/ensime" 6 | 7 | :project-package "org.clapper.argot" 8 | 9 | :project-name "Argot" 10 | 11 | :use-sbt t 12 | 13 | :classpath "lib_managed/scala_2.8.0/compile/grizzled-scala_2.8.0-1.0.2.jar" 14 | 15 | ) 16 | -------------------------------------------------------------------------------- /notes/1.0.0.markdown: -------------------------------------------------------------------------------- 1 | * Built for the Scala 2.10.0 series _only_ (2.10.0-RC1 and -RC3, initially). 2 | * Added `-feature` to `scalac` options, and removed all feature warnings. 3 | * Upgraded build to SBT 0.12. 4 | 5 | **This version, and later versions, are 2.10-only.** 2.9.x and earlier will be 6 | supported via the `pre-scala-2.10` GitHub branch. This is due to changes 7 | in the Scala library between 2.9 and 2.10. 8 | -------------------------------------------------------------------------------- /notes/0.3.1.markdown: -------------------------------------------------------------------------------- 1 | * Now builds against Scala 2.9.0, as well as Scala 2.8.0 and 2.8.1. 2 | * Updated to version 1.4.1 of [ScalaTest][] for Scala 2.9.0. (Still uses 3 | ScalaTest 1.3, for Scala 2.8). 4 | * Updated to use [SBT][] 0.7.7. 5 | * Updated to version 1.0.6 of the [Grizzled Scala][] library. 6 | 7 | [ScalaTest]: http://www.scalatest.org/ 8 | [SBT]: http://code.google.com/p/simple-build-tool/ 9 | [Grizzled Scala]: http://software.clapper.org/grizzled-scala/ 10 | -------------------------------------------------------------------------------- /notes/0.3.markdown: -------------------------------------------------------------------------------- 1 | * Renamed to *clap* (**C**ommand **L**ine **A**rgument **P**arser) 2 | 3 | * Addressed [Issue #1][]: 4 | 5 | 1. In the usage display, options are now sorted so that the POSIX-style 6 | single-character option names always precede any longer GNU-style 7 | synonyms. 8 | 2. The `Argot` constructor now supports an `sortOptions` parameter, which 9 | defaults to `true`. If set to `true`, the options in the usage output 10 | are sorted lexically. If set to `false`, the options are displayed in 11 | the order they were specified in the code. 12 | 13 | [Issue #1]: https://github.com/bmc/argot/issues#issue/1 14 | -------------------------------------------------------------------------------- /src/main/ls/0.3.6.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "organization":"org.clapper", 4 | "name":"argot", 5 | "version":"0.3.6", 6 | "description":"A command-line option and parameter parser", 7 | "site":"http://software.clapper.org/argot/", 8 | "tags":["argument parser","command line","parameters"], 9 | "docs":"", 10 | "licenses": [{ 11 | "name": "BSD", 12 | "url": "http://software.clapper.org/argot/license.html" 13 | }], 14 | "resolvers": ["http://scala-tools.org/repo-releases"], 15 | "dependencies": [{ 16 | "organization":"org.clapper", 17 | "name": "grizzled-scala", 18 | "version": "1.0.11" 19 | }], 20 | "scalas": ["2.9.1","2.9.0","2.8.1","2.8.0"], 21 | "sbt": false 22 | } -------------------------------------------------------------------------------- /src/main/ls/0.3.7.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "organization":"org.clapper", 4 | "name":"argot", 5 | "version":"0.3.7", 6 | "description":"A command-line option and parameter parser", 7 | "site":"http://software.clapper.org/argot/", 8 | "tags":["argument parser","command line","parameters"], 9 | "docs":"", 10 | "licenses": [{ 11 | "name": "BSD", 12 | "url": "http://software.clapper.org/argot/license.html" 13 | }], 14 | "resolvers": ["http://scala-tools.org/repo-releases"], 15 | "dependencies": [{ 16 | "organization":"org.clapper", 17 | "name": "grizzled-scala", 18 | "version": "1.0.11.1" 19 | }], 20 | "scalas": ["2.9.1","2.9.0","2.9.0-1","2.8.2","2.8.1","2.8.0"], 21 | "sbt": false 22 | } -------------------------------------------------------------------------------- /src/main/ls/0.3.8.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "organization":"org.clapper", 4 | "name":"argot", 5 | "version":"0.3.8", 6 | "description":"A command-line option and parameter parser", 7 | "site":"http://software.clapper.org/argot/", 8 | "tags":["argument parser","command line","parameters"], 9 | "docs":"", 10 | "licenses": [{ 11 | "name": "BSD", 12 | "url": "http://software.clapper.org/argot/license.html" 13 | }], 14 | "resolvers": ["http://scala-tools.org/repo-releases"], 15 | "dependencies": [{ 16 | "organization":"org.clapper", 17 | "name": "grizzled-scala", 18 | "version": "1.0.12" 19 | }], 20 | "scalas": ["2.9.1-1","2.9.1","2.9.0","2.9.0-1","2.8.2","2.8.1","2.8.0"], 21 | "sbt": false 22 | } -------------------------------------------------------------------------------- /src/main/ls/1.0.2.json: -------------------------------------------------------------------------------- 1 | { 2 | "organization" : "org.clapper", 3 | "name" : "argot", 4 | "version" : "1.0.2", 5 | "description" : "A command-line option and parameter parser", 6 | "site" : "http://software.clapper.org/argot/", 7 | "tags" : [ "argument parser", "command line", "parameters" ], 8 | "docs" : "", 9 | "resolvers" : [ "https://oss.sonatype.org/content/repositories/releases" ], 10 | "dependencies" : [ { 11 | "organization" : "org.clapper", 12 | "name" : "grizzled-scala", 13 | "version" : "1.2" 14 | } ], 15 | "scalas" : [ "2.10.4", "2.11.1" ], 16 | "licenses" : [ { 17 | "name" : "BSD", 18 | "url" : "http://software.clapper.org/argot/license.html" 19 | } ], 20 | "sbt" : false 21 | } -------------------------------------------------------------------------------- /src/main/ls/1.0.1.json: -------------------------------------------------------------------------------- 1 | { 2 | "organization" : "org.clapper", 3 | "name" : "argot", 4 | "version" : "1.0.1", 5 | "description" : "A command-line option and parameter parser", 6 | "site" : "http://software.clapper.org/argot/", 7 | "tags" : [ "argument parser", "command line", "parameters" ], 8 | "docs" : "", 9 | "resolvers" : [ "https://oss.sonatype.org/content/repositories/releases" ], 10 | "dependencies" : [ { 11 | "organization" : "org.clapper", 12 | "name" : "grizzled-scala", 13 | "version" : "1.1.2" 14 | } ], 15 | "scalas" : [ "2.10.0", "2.10.1" ], 16 | "licenses" : [ { 17 | "name" : "BSD", 18 | "url" : "http://software.clapper.org/argot/license.html" 19 | } ], 20 | "sbt" : false 21 | } -------------------------------------------------------------------------------- /src/main/ls/1.0.0.json: -------------------------------------------------------------------------------- 1 | { 2 | "organization" : "org.clapper", 3 | "name" : "argot", 4 | "version" : "1.0.0", 5 | "description" : "A command-line option and parameter parser", 6 | "site" : "http://software.clapper.org/argot/", 7 | "tags" : [ "argument parser", "command line", "parameters" ], 8 | "docs" : "", 9 | "resolvers" : [ "https://oss.sonatype.org/content/repositories/releases" ], 10 | "dependencies" : [ { 11 | "organization" : "org.clapper", 12 | "name" : "grizzled-scala", 13 | "version" : "1.1.2" 14 | } ], 15 | "scalas" : [ "2.10.0-RC3", "2.10.0-RC1" ], 16 | "licenses" : [ { 17 | "name" : "BSD", 18 | "url" : "http://software.clapper.org/argot/license.html" 19 | } ], 20 | "sbt" : false 21 | } -------------------------------------------------------------------------------- /notes/about.markdown: -------------------------------------------------------------------------------- 1 | [Argot][] is a command-line parser library for [Scala][], supporting: 2 | 3 | * single-value and multi-value options 4 | * single-value and multi-value parameters 5 | * flag and non-flag options 6 | * GNU-style long options, (i.e., "--option") 7 | * POSIX-style short options, (i.e., single "-" lead-in), with option 8 | grouping (e.g., "`tar -xcf foo.tgz`") 9 | * automatic parameter conversion (i.e., values with non-string types, 10 | with automatic conversion) 11 | * the ability to supply your own conversion functions 12 | 13 | You can find complete details, including usage information, on the 14 | [Argot home page][]. 15 | 16 | [Argot]: http://bmc.github.com/argot/ 17 | [Argot home page]: http://software.clapper.org/argot/ 18 | [Scala]: http://www.scala-lang.org/ 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Abandonware Alert! 2 | 3 | **THIS PROJECT IS DEAD. I will no longer be maintaining it. 4 | [scopt](https://github.com/scopt/scopt) does everything you'd need 5 | in a command-line options parser, and it's what I'll be using going 6 | forward.** 7 | 8 | ## Old Description 9 | 10 | Argot is a command-line parser library for [Scala][], supporting: 11 | 12 | * single-value and multi-value options 13 | * single-value and multi-value parameters 14 | * flag and non-flag options 15 | * GNU-style long options, i.e., "--option") 16 | * POSIX-style short options, i.e., single "-" lead-in, with option 17 | grouping (e.g., "`tar -xcf foo.tgz`") 18 | * automatic parameter conversion (i.e., values with non-string types, 19 | with automatic conversion) 20 | * the ability to supply your own conversion functions 21 | 22 | For more information, see the [Argot home page][]. 23 | 24 | **NOTE**: The `master` branch supports only Scala 2.10 and later. See 25 | the [`pre-scala-2.10`](https://github.com/bmc/argot/tree/pre-scala-2.10) 26 | branch for versions of Scala prior to 2.10. 27 | 28 | [Scala]: http://www.scala-lang.org/ 29 | [Argot home page]: http://software.clapper.org/argot/ 30 | 31 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | This software is released under a BSD license, adapted from 2 | 3 | 4 | Copyright © 2010 Brian M. Clapper. 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, 11 | this list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | * Neither the name "clapper.org", "Argot", nor the names of its 18 | contributors may be used to endorse or promote products derived from this 19 | software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 25 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 26 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 27 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 28 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 29 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 30 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 31 | POSSIBILITY OF SUCH DAMAGE. 32 | -------------------------------------------------------------------------------- /src/main/scala/org/clapper/argot/ArgotTest.scala: -------------------------------------------------------------------------------- 1 | package org.clapper.argot 2 | import java.io.File 3 | import scala.math 4 | 5 | object ArgotTest { 6 | import ArgotConverters._ 7 | 8 | val parser = new ArgotParser( 9 | "test", preUsage=Some("ArgotTest: Version 0.1. Copyright (c) " + 10 | "2010, Brian M. Clapper. Pithy quotes go here.") 11 | ) 12 | 13 | val iterations = parser.option[Int](List("i", "iterations"), "n", 14 | "Total iterations") 15 | val verbose = parser.flag[Int](List("v", "verbose"), 16 | List("q", "quiet"), 17 | "Increment (-v, --verbose) or " + 18 | "decrement (-q, --quiet) the " + 19 | "verbosity level.") { 20 | (onOff, opt) => 21 | 22 | import scala.math 23 | 24 | val currentValue = opt.value.getOrElse(0) 25 | val newValue = if (onOff) currentValue + 1 else currentValue - 1 26 | math.max(0, newValue) 27 | } 28 | 29 | val noError = parser.flag[Boolean](List("n", "noerror"), 30 | "Do not abort on error.") 31 | val users = parser.multiOption[String](List("u", "user"), "username", 32 | "User to receive email. Email " + 33 | "address is queried from " + 34 | "database.") 35 | 36 | val emails = parser.multiOption[String](List("e", "email"), "emailaddr", 37 | "Address to receive emailed " + 38 | "results.") { 39 | (s, opt) => 40 | 41 | val ValidAddress = """^[^@]+@[^@]+\.[a-zA-Z]+$""".r 42 | ValidAddress.findFirstIn(s) match { 43 | case None => parser.usage("Bad email address \"" + s + 44 | "\" for " + opt.name + " option.") 45 | case Some(_) => s 46 | } 47 | } 48 | 49 | val output = parser.parameter[String]("outputfile", 50 | "Output file to which to write.", 51 | false) 52 | 53 | val input = parser.multiParameter[File]("input", 54 | "Input files to read. If not " + 55 | "specified, use stdin.", 56 | true) { 57 | (s, opt) => 58 | 59 | val file = new File(s) 60 | if (! file.exists) 61 | parser.usage("Input file \"" + s + "\" does not exist.") 62 | 63 | file 64 | } 65 | 66 | def main(args: Array[String]) { 67 | 68 | try { 69 | parser.parse(args) 70 | println("----------") 71 | println("iterations=" + iterations.value) 72 | println("verbose=" + verbose.value) 73 | println("users=" + users.value) 74 | println("emails=" + emails.value) 75 | println("output=" + output.value) 76 | println("input=" + input.value) 77 | } 78 | 79 | catch { 80 | case e: ArgotUsageException => println(e.message) 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Version 1.0.4 2 | 3 | Merged in support for GNU-style "--long=value" option syntax, from 4 | Matthew Neeley (maffoo _at_ google.com) 5 | 6 | Version 1.0.3 7 | 8 | Fixed Issue #8. 9 | 10 | Various tweaks to try to get Bintray to shove this thing into Maven 11 | Central. 12 | 13 | Version 1.0.2 14 | 15 | * Added cross-compilation for Scala 2.11, courtesy of 16 | Martin Grotzke (martin.grotzke _at_ googlemail.com) 17 | * Publish to Bintray. 18 | 19 | Version 1.0.1 20 | 21 | * Built for Scala 2.10.0 and 2.10.1 22 | 23 | Version 1.0.0 24 | 25 | * Supports Scala 2.10-RC2 26 | 27 | Version 0.4: 28 | 29 | * Added Scala 2.9.2 to the set of cross-built versions. 30 | 31 | Version 0.3.8: 32 | 33 | * Added Scala 2.9.1-1 to the set of cross-built versions. 34 | 35 | Version 0.3.7: 36 | 37 | * Built for Scala 2.8.2, in addition to 2.8.0, 2.8.1, 2.9.0, 2.9.0-1 and 2.9.1 38 | * Updated Grizzled Scala version. 39 | 40 | Version 0.3.6: 41 | 42 | * Converted to use SBT 0.11.2. 43 | * Updated ScalaTest versions. 44 | * Added _ls.implicit.ly_ metadata. 45 | * Now publishes to `oss.sonatype.org` (and, thence, to the Maven central repo). 46 | 47 | [Scala]: http://www.scala-lang.org/ 48 | 49 | Version 0.3.5: 50 | 51 | * Now builds for [Scala][] 2.9.1, as well as 2.9.0-1, 2.9.0, 2.8.1, and 2.8.0. 52 | 53 | [Scala]: http://www.scala-lang.org/ 54 | 55 | Version 0.3.4: 56 | 57 | * Converted code to conform with standard Scala coding style. 58 | 59 | [SBT]: http://code.google.com/p/simple-build-tool/ 60 | 61 | Version 0.3.3: 62 | 63 | * Now builds against Scala 2.9.0.1, as well as Scala 2.9.0, 2.8.1 and 2.8.0. 64 | * Converted to build with [SBT][] 0.10.1 65 | 66 | Version 0.3.1: 67 | 68 | * Now builds against Scala 2.9.0, as well as Scala 2.8.0 and 2.8.1. 69 | * Updated to version 1.4.1 of [ScalaTest][] for Scala 2.9.0. (Still uses 70 | ScalaTest 1.3, for Scala 2.8). 71 | * Updated to use [SBT][] 0.7.7. 72 | * Updated to version 1.0.6 of the [Grizzled Scala][] library. 73 | 74 | [ScalaTest]: http://www.scalatest.org/ 75 | [SBT]: http://code.google.com/p/simple-build-tool/ 76 | [Grizzled Scala]: http://software.clapper.org/grizzled-scala/ 77 | 78 | Version 0.3: 79 | 80 | * Renamed to *clap* (**C**ommand **L**ine **A**rgument **P**arser) 81 | 82 | * Addressed [Issue #1][]: 83 | 84 | 1. In the usage display, options are now sorted so that the POSIX-style 85 | single-character option names always precede any longer GNU-style 86 | synonyms. 87 | 2. The `Argot` constructor now supports an `sortOptions` parameter, which 88 | defaults to `true`. If set to `true`, the options in the usage output 89 | are sorted lexically. If set to `false`, the options are displayed in 90 | the order they were specified in the code. 91 | 92 | [Issue #1]: https://github.com/bmc/argot/issues#issue/1 93 | 94 | Version 0.2: 95 | 96 | * Upgraded [Grizzled Scala][] dependency to version 1.0.3. 97 | * Now compiles against [Scala][] 2.8.1, as well as 2.8.0. 98 | 99 | [Grizzled Scala]: http://bmc.github.com/grizzled-scala/ 100 | [Scala]: http://www.scala-lang.org/ 101 | 102 | Version 0.1: 103 | 104 | * Initial version. 105 | -------------------------------------------------------------------------------- /src/main/scala/org/clapper/argot/exception.scala: -------------------------------------------------------------------------------- 1 | /* 2 | --------------------------------------------------------------------------- 3 | This software is released under a BSD license, adapted from 4 | http://opensource.org/licenses/bsd-license.php 5 | 6 | Copyright (c) 2010, Brian M. Clapper 7 | All rights reserved. 8 | 9 | Redistribution and use in source and binary forms, with or without 10 | modification, are permitted provided that the following conditions are 11 | met: 12 | 13 | * Redistributions of source code must retain the above copyright notice, 14 | this list of conditions and the following disclaimer. 15 | 16 | * Redistributions in binary form must reproduce the above copyright 17 | notice, this list of conditions and the following disclaimer in the 18 | documentation and/or other materials provided with the distribution. 19 | 20 | * Neither the names "clapper.org", "Argot", nor the names of its 21 | contributors may be used to endorse or promote products derived from 22 | this software without specific prior written permission. 23 | 24 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 25 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 26 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 27 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 28 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 29 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 30 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 31 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 32 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 33 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 34 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 35 | --------------------------------------------------------------------------- 36 | */ 37 | 38 | /** Argot is a command-line argument parsing API for Scala. 39 | */ 40 | package org.clapper.argot 41 | 42 | /** Base Argot exception class. 43 | * 44 | * @param message exception message 45 | * @param cause optional wrapped, or nested, exception 46 | */ 47 | class ArgotException(val message: String, val cause: Option[Throwable]) 48 | extends Exception(message) { 49 | if (cause != None) 50 | initCause(cause.get) 51 | 52 | /** Alternate constructor. 53 | * 54 | * @param message exception message 55 | */ 56 | def this(msg: String) = this(msg, None) 57 | 58 | /** Alternate constructor. 59 | * 60 | * @param message exception message 61 | * @param cause wrapped, or nested, exception 62 | */ 63 | def this(msg: String, cause: Throwable) = this(msg, Some(cause)) 64 | } 65 | 66 | /** Thrown to indicate usage errors. The calling application can catch this 67 | * exception and print the message, which will be a fully fleshed-out usage 68 | * string. For instance: 69 | * 70 | * {{{ 71 | * import org.clapper.argot._ 72 | * 73 | * ... 74 | * 75 | * val p = new Argot("MyProgram") 76 | * ... 77 | * try { 78 | * p.parse(args) 79 | * } 80 | * catch { 81 | * case e: ArgotUsageException => 82 | * println(e.message) 83 | * System.exit(1) 84 | * } 85 | * }}} 86 | * 87 | * @param message exception message 88 | */ 89 | class ArgotUsageException(message: String) 90 | extends ArgotException(message, None) 91 | 92 | /** Thrown to indicate that Argot could not convert a command line parameter 93 | * to the desired type. 94 | * 95 | * @param message exception message 96 | */ 97 | class ArgotConversionException(message: String) 98 | extends ArgotException(message, None) 99 | 100 | /** Thrown to indicate that Argot encountered a problem in the caller's 101 | * argument specification. This exception can be interpreted as a bug in 102 | * the caller's program. 103 | * 104 | * @param message exception message 105 | */ 106 | class ArgotSpecificationError(message: String) 107 | extends ArgotException("(BUG) " + message, None) 108 | -------------------------------------------------------------------------------- /src/test/scala/org/clapper/argot/ArgotParser/multioption.scala: -------------------------------------------------------------------------------- 1 | /* 2 | --------------------------------------------------------------------------- 3 | This software is released under a BSD license, adapted from 4 | http://opensource.org/licenses/bsd-license.php 5 | 6 | Copyright (c) 2010 Brian M. Clapper. All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without 9 | modification, are permitted provided that the following conditions are 10 | met: 11 | 12 | * Redistributions of source code must retain the above copyright notice, 13 | this list of conditions and the following disclaimer. 14 | 15 | * Redistributions in binary form must reproduce the above copyright 16 | notice, this list of conditions and the following disclaimer in the 17 | documentation and/or other materials provided with the distribution. 18 | 19 | * Neither the names "clapper.org", "Scalasti", nor the names of its 20 | contributors may be used to endorse or promote products derived from 21 | this software without specific prior written permission. 22 | 23 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 24 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 25 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 26 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 27 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 28 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 29 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 30 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 31 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 32 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 33 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | --------------------------------------------------------------------------- 35 | */ 36 | 37 | import org.scalatest.FunSuite 38 | import org.clapper.argot._ 39 | 40 | /** Tests the grizzled.io functions. 41 | */ 42 | class ArgotMultiOptionTest extends FunSuite { 43 | import ArgotConverters._ 44 | 45 | test("multi-value option success") { 46 | val parser = new ArgotParser("test") 47 | val opt = parser.multiOption[String]( 48 | List("s", "something"), "something", "Some value" 49 | ) 50 | 51 | val data = List( 52 | (Seq("something"), Array("-s", "something")), 53 | (Seq("foo"), Array("--something", "foo")), 54 | (Nil, Array.empty[String]), 55 | (Seq("foo", "bar"), Array("-s", "foo", "-s", "bar")) 56 | ) 57 | 58 | for ((expected, args) <- data) { 59 | parser.reset() 60 | parser.parse(args) 61 | assertResult(expected, args.mkString("[", ", ", "]") + " -> " + expected) { 62 | opt.value 63 | } 64 | } 65 | } 66 | 67 | test("multi-value option failure") { 68 | val parser = new ArgotParser("test") 69 | val opt = parser.multiOption[String]( 70 | List("s", "something"), "something", "Some value" 71 | ) 72 | 73 | val data = List(Array("-f"), 74 | Array("-s")) 75 | 76 | for (args <- data) { 77 | intercept[ArgotUsageException] { 78 | parser.parse(args) 79 | } 80 | } 81 | } 82 | 83 | test("integer multi-option") { 84 | val parser = new ArgotParser("test") 85 | val opt = parser.multiOption[Int]("i", "someint", "integer") 86 | 87 | val data = List( 88 | (Seq(3), Array("-i", "3")), 89 | (Nil, Array.empty[String]), 90 | (Seq(3, 0), Array("-i", "3", "-i", "0")), 91 | (Seq(1, 10, 1), Array("-i", "1", "-i", "10", "-i", "1")) 92 | ) 93 | 94 | for ((expected, args) <- data) { 95 | parser.reset() 96 | parser.parse(args) 97 | assertResult(expected, args.mkString("[", ", ", "]") + " -> " + expected) { 98 | opt.value 99 | } 100 | } 101 | } 102 | 103 | test("custom type multi-option") { 104 | class Foo(val i: Int) 105 | 106 | val parser = new ArgotParser("test") 107 | val opt = parser.multiOption[Foo]("i", "n", "some number") { 108 | (s, opt) => 109 | 110 | new Foo(s.toInt) 111 | } 112 | 113 | val data = List( 114 | (List(3), Array("-i", "3")), 115 | (Nil, Array.empty[String]), 116 | (List(3, 0), Array("-i", "3", "-i", "0")), 117 | (List(1, 10, 1), Array("-i", "1", "-i", "10", "-i", "1")) 118 | ) 119 | 120 | for ((expected, args) <- data) { 121 | parser.reset() 122 | parser.parse(args) 123 | assertResult(expected, args.mkString("[", ", ", "]") + " -> " + expected) { 124 | opt.value.map(_.i) 125 | } 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/test/scala/org/clapper/argot/ArgotParser/option.scala: -------------------------------------------------------------------------------- 1 | /* 2 | --------------------------------------------------------------------------- 3 | This software is released under a BSD license, adapted from 4 | http://opensource.org/licenses/bsd-license.php 5 | 6 | Copyright (c) 2010 Brian M. Clapper. All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without 9 | modification, are permitted provided that the following conditions are 10 | met: 11 | 12 | * Redistributions of source code must retain the above copyright notice, 13 | this list of conditions and the following disclaimer. 14 | 15 | * Redistributions in binary form must reproduce the above copyright 16 | notice, this list of conditions and the following disclaimer in the 17 | documentation and/or other materials provided with the distribution. 18 | 19 | * Neither the names "clapper.org", "Scalasti", nor the names of its 20 | contributors may be used to endorse or promote products derived from 21 | this software without specific prior written permission. 22 | 23 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 24 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 25 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 26 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 27 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 28 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 29 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 30 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 31 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 32 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 33 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | --------------------------------------------------------------------------- 35 | */ 36 | 37 | import org.scalatest.FunSuite 38 | import org.clapper.argot._ 39 | 40 | /** Tests the grizzled.io functions. 41 | */ 42 | class ArgotOptionTest extends FunSuite { 43 | import ArgotConverters._ 44 | 45 | test("single-value option success") { 46 | val parser = new ArgotParser("test") 47 | val opt = parser.option[String](List("s", "something"), "something", 48 | "Some value") 49 | 50 | val data = List( 51 | (Some("something"), Array("-s", "something")), 52 | (Some("foo"), Array("--something", "foo")), 53 | (None, Array.empty[String]), 54 | (Some("bar"), Array("-s", "foo", "-s", "bar")), 55 | (Some("baz"), Array("--something=baz")) 56 | ) 57 | 58 | for ((expected, args) <- data) { 59 | parser.reset() 60 | parser.parse(args) 61 | assertResult(expected, args.mkString("[", ", ", "]") + " -> " + expected) { 62 | opt.value 63 | } 64 | } 65 | } 66 | 67 | test("single-value option failure") { 68 | val parser = new ArgotParser("test") 69 | val opt = parser.option[String](List("s", "something"), "something", 70 | "Some value") 71 | 72 | val data = List(Array("-f"), 73 | Array("-s")) 74 | 75 | for (args <- data) { 76 | intercept[ArgotUsageException] { 77 | parser.parse(args) 78 | } 79 | } 80 | } 81 | 82 | test("integer option") { 83 | val parser = new ArgotParser("test") 84 | val opt = parser.option[Int]("i", "someint", "integer") 85 | 86 | val data = List( 87 | (Some(3), Array("-i", "3")), 88 | (None, Array.empty[String]), 89 | (Some(0), Array("-i", "3", "-i", "0")), 90 | (Some(1), Array("-i", "1", "-i", "10", "-i", "1")) 91 | ) 92 | 93 | for ((expected, args) <- data) { 94 | parser.reset() 95 | parser.parse(args) 96 | assertResult(expected, args.mkString("[", ", ", "]") + " -> " + expected) { 97 | opt.value 98 | } 99 | } 100 | } 101 | 102 | test("custom type option") { 103 | class Foo(val i: Int) 104 | 105 | val parser = new ArgotParser("test") 106 | val opt = parser.option[Foo]("i", "n", "some number") { 107 | (s, opt) => 108 | 109 | new Foo(s.toInt) 110 | } 111 | 112 | val data = List( 113 | (Some(3), Array("-i", "3")), 114 | (None, Array.empty[String]), 115 | (Some(0), Array("-i", "3", "-i", "0")), 116 | (Some(1), Array("-i", "1", "-i", "10", "-i", "1")) 117 | ) 118 | 119 | for ((expected, args) <- data) { 120 | parser.reset() 121 | parser.parse(args) 122 | assertResult(expected, args.mkString("[", ", ", "]") + " -> " + expected) { 123 | opt.value.map(_.i) 124 | } 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/test/scala/org/clapper/argot/ArgotParser/flag.scala: -------------------------------------------------------------------------------- 1 | /* 2 | --------------------------------------------------------------------------- 3 | This software is released under a BSD license, adapted from 4 | http://opensource.org/licenses/bsd-license.php 5 | 6 | Copyright (c) 2010 Brian M. Clapper. All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without 9 | modification, are permitted provided that the following conditions are 10 | met: 11 | 12 | * Redistributions of source code must retain the above copyright notice, 13 | this list of conditions and the following disclaimer. 14 | 15 | * Redistributions in binary form must reproduce the above copyright 16 | notice, this list of conditions and the following disclaimer in the 17 | documentation and/or other materials provided with the distribution. 18 | 19 | * Neither the names "clapper.org", "Scalasti", nor the names of its 20 | contributors may be used to endorse or promote products derived from 21 | this software without specific prior written permission. 22 | 23 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 24 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 25 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 26 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 27 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 28 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 29 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 30 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 31 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 32 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 33 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | --------------------------------------------------------------------------- 35 | */ 36 | 37 | import org.scalatest.FunSuite 38 | import org.clapper.argot._ 39 | 40 | /** Tests the grizzled.io functions. 41 | */ 42 | class ArgotFlagTest extends FunSuite { 43 | import ArgotConverters._ 44 | 45 | test("flag option success") { 46 | val parser = new ArgotParser("test") 47 | val flag = parser.flag[Boolean](List("y", "on"), List("n", "off"), 48 | "Do something") 49 | 50 | val data = List( 51 | (true, Array("-y")), 52 | (true, Array("--on")), 53 | (false, Array("-n")), 54 | (false, Array("--off")), 55 | (true, Array("-y", "-n", "--on")) 56 | ) 57 | 58 | for ((expected, args) <- data) { 59 | parser.reset() 60 | parser.parse(args) 61 | assertResult(expected, args.mkString("[", ", ", "]") + " -> " + expected) { 62 | flag.value.get 63 | } 64 | } 65 | } 66 | 67 | test("flag option failure") { 68 | val parser = new ArgotParser("test") 69 | val flag = parser.flag[Boolean](List("y", "on"), List("n", "off"), 70 | "Do something") 71 | 72 | intercept[ArgotUsageException] { 73 | parser.parse(Array("-f")) 74 | } 75 | } 76 | 77 | test("integer flag") { 78 | val parser = new ArgotParser("test") 79 | val flag = parser.flag[Int](List("y", "on"), List("n", "off"), "toggle") { 80 | (onOff, opt) => 81 | 82 | import scala.math 83 | 84 | val currentValue = opt.value.getOrElse(0) 85 | val newValue = if (onOff) currentValue + 1 else currentValue - 1 86 | math.max(0, newValue) 87 | } 88 | 89 | val data = List( 90 | (Some(3), Array("--on", "--on", "--on")), 91 | (None, Array.empty[String]), 92 | (Some(0), Array("-y", "-y", "-n", "-n", "-n", "-n")), 93 | (Some(1), Array("-y", "-y", "-y", "-n", "-n")) 94 | ) 95 | 96 | for ((expected, args) <- data) { 97 | parser.reset() 98 | parser.parse(args) 99 | assertResult(expected, args.mkString("[", ", ", "]") + " -> " + expected) { 100 | flag.value 101 | } 102 | } 103 | } 104 | 105 | test("custom type flag") { 106 | class MyFlag(val counter: Int) 107 | 108 | val parser = new ArgotParser("test") 109 | val flag = parser.flag[MyFlag](List("y"), List("n"), "a toggle") { 110 | (onOff, opt) => 111 | 112 | import scala.math 113 | 114 | val currentValue = opt.value.getOrElse(new MyFlag(0)) 115 | val newValue = if (onOff) currentValue.counter + 1 116 | else currentValue.counter - 1 117 | 118 | new MyFlag(math.max(0, newValue)) 119 | } 120 | 121 | val data = List( 122 | (Some(3), Array("-y", "-y", "-y")), 123 | (None, Array.empty[String]), 124 | (Some(0), Array("-y", "-y", "-n", "-n", "-n", "-n")) 125 | ) 126 | 127 | for ((expected, args) <- data) { 128 | parser.reset() 129 | parser.parse(args) 130 | assertResult(expected, args.mkString("[", ", ", "]") + " -> " + expected) { 131 | flag.value.map(_.counter) 132 | } 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/test/scala/org/clapper/argot/ArgotParser/param.scala: -------------------------------------------------------------------------------- 1 | /* 2 | --------------------------------------------------------------------------- 3 | This software is released under a BSD license, adapted from 4 | http://opensource.org/licenses/bsd-license.php 5 | 6 | Copyright (c) 2010 Brian M. Clapper. All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without 9 | modification, are permitted provided that the following conditions are 10 | met: 11 | 12 | * Redistributions of source code must retain the above copyright notice, 13 | this list of conditions and the following disclaimer. 14 | 15 | * Redistributions in binary form must reproduce the above copyright 16 | notice, this list of conditions and the following disclaimer in the 17 | documentation and/or other materials provided with the distribution. 18 | 19 | * Neither the names "clapper.org", "Scalasti", nor the names of its 20 | contributors may be used to endorse or promote products derived from 21 | this software without specific prior written permission. 22 | 23 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 24 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 25 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 26 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 27 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 28 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 29 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 30 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 31 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 32 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 33 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | --------------------------------------------------------------------------- 35 | */ 36 | 37 | import org.scalatest.FunSuite 38 | import org.clapper.argot._ 39 | 40 | /** Tests the grizzled.io functions. 41 | */ 42 | class ArgotParameterTest extends FunSuite { 43 | import ArgotConverters._ 44 | 45 | test("one required argument") { 46 | val parser = new ArgotParser("test") 47 | val opt = parser.option[String](List("s", "something"), 48 | "something", "Some value") 49 | val req = parser.parameter[String]("foo", "some param", optional=false) 50 | 51 | val data = List( 52 | (Some("something"), Array("-s", "something", "something")), 53 | (Some("param"), Array("--something", "foo", "param")), 54 | (Some("foo"), Array("-s", "foo", "-s", "bar", "foo")), 55 | (Some("foo"), Array("foo")), 56 | (Some("--foo"), Array("-s", "something", "--", "--foo")) 57 | ) 58 | 59 | for ((expected, args) <- data) { 60 | parser.reset() 61 | parser.parse(args) 62 | assertResult(expected, args.mkString("[", ", ", "]") + " -> " + expected) { 63 | req.value 64 | } 65 | } 66 | } 67 | 68 | test("required argument failure") { 69 | val parser = new ArgotParser("test") 70 | val req = parser.parameter[String]("foo", "some param", optional=false) 71 | 72 | val data = List(Array("-f"), 73 | Array("-s"), 74 | Array("-s", "something"), 75 | Array.empty[String]) 76 | 77 | for (args <- data) { 78 | intercept[ArgotUsageException] { 79 | parser.parse(args) 80 | } 81 | } 82 | 83 | val opt = parser.option[String]( 84 | List("s", "something"), "something", "Some value" 85 | ) 86 | 87 | for (args <- data) { 88 | intercept[ArgotUsageException] { 89 | parser.parse(args) 90 | } 91 | } 92 | } 93 | 94 | test("optional argument") { 95 | val parser = new ArgotParser("test") 96 | val opt = parser.option[String]( 97 | List("s", "something"), "something", "Some value" 98 | ) 99 | val req = parser.parameter[String]("foo", "some param", optional=true) 100 | 101 | val data = List( 102 | (Some("something"), Array("-s", "something", "something")), 103 | (Some("param"), Array("--something", "foo", "param")), 104 | (Some("foo"), Array("-s", "foo", "-s", "bar", "foo")), 105 | (Some("foo"), Array("foo")), 106 | (Some("--foo"), Array("-s", "something", "--", "--foo")), 107 | (None, Array("-s", "something")), 108 | (None, Array("-s", "something", "--")) 109 | ) 110 | 111 | for ((expected, args) <- data) { 112 | parser.reset() 113 | parser.parse(args) 114 | assertResult(expected, args.mkString("[", ", ", "]") + " -> " + expected) { 115 | req.value 116 | } 117 | } 118 | } 119 | 120 | test("required + optional argument") { 121 | val parser = new ArgotParser("test") 122 | val opt = parser.option[String]( 123 | List("s", "something"), "something", "Some value" 124 | ) 125 | val foo = parser.parameter[String]("foo", "some param", optional=false) 126 | val bar = parser.parameter[String]("bar", "some param", optional=true) 127 | 128 | val data = List( 129 | (Some("abc"), None, Array("-s", "s", "abc")), 130 | (Some("foo"), Some("bar"), Array("-s", "foo", "foo", "bar")), 131 | (Some("foo"), None, Array("foo")) 132 | ) 133 | 134 | for ((expected_foo, expected_bar, args) <- data) { 135 | parser.reset() 136 | parser.parse(args) 137 | val prefix = 138 | assertResult((expected_foo, expected_bar), 139 | 140 | args.mkString("[", ", ", "]") + " -> " + 141 | "(" + expected_foo + ", " + expected_bar + ")") { 142 | (foo.value, bar.value) 143 | } 144 | } 145 | } 146 | 147 | test("specification error 1") { 148 | val parser = new ArgotParser("test") 149 | 150 | intercept[ArgotSpecificationError] { 151 | // Optional parameter, followed by required parameter. 152 | 153 | parser.parameter[String]("foo", "some param", optional=true) 154 | parser.parameter[String]("bar", "some param", optional=false) 155 | } 156 | } 157 | 158 | test("specification error 2") { 159 | val parser = new ArgotParser("test") 160 | 161 | intercept[ArgotSpecificationError] { 162 | // Multi-parameter, not last. 163 | 164 | parser.multiParameter[String]("foo", "some param", optional=true) 165 | parser.parameter[String]("bar", "some param", optional=true) 166 | } 167 | } 168 | 169 | test("multi-valued parameter") { 170 | val parser = new ArgotParser("test") 171 | parser.option[String]("s", "string", "some string") 172 | val param = parser.multiParameter[Int]("count", "some count", 173 | optional=true) 174 | 175 | val data = List((Seq(1), Array("1")), 176 | (Seq(1, 2), Array("1", "2")), 177 | (Seq(3), Array("-s", "s", "3")), 178 | (Nil, Array("-s", "foo")), 179 | (Nil, Array.empty[String])) 180 | 181 | for ((expected, args) <- data) { 182 | parser.reset() 183 | parser.parse(args) 184 | assertResult(expected, args.mkString("[", ", ", "]") + " -> " + expected) { 185 | param.value 186 | } 187 | } 188 | } 189 | 190 | test("custom type parameter") { 191 | class MyParam(val i: Int) 192 | 193 | val parser = new ArgotParser("test") 194 | parser.option[String]("s", "string", "some string") 195 | 196 | val param = parser.multiParameter[MyParam]("count", "some count", 197 | optional=true) { 198 | (s, opt) => 199 | 200 | new MyParam(s.toInt) 201 | } 202 | 203 | val data = List((Seq(1), Array("1")), 204 | (Seq(1, 2), Array("1", "2")), 205 | (Seq(3), Array("-s", "s", "3")), 206 | (Nil, Array("-s", "foo")), 207 | (Nil, Array.empty[String])) 208 | 209 | for ((expected, args) <- data) { 210 | parser.reset() 211 | parser.parse(args) 212 | assertResult(expected, args.mkString("[", ", ", "]") + " -> " + expected) { 213 | param.value.map(_.i) 214 | } 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/main/scala/org/clapper/argot/Argot.scala: -------------------------------------------------------------------------------- 1 | /* 2 | --------------------------------------------------------------------------- 3 | This software is released under a BSD license, adapted from 4 | http://opensource.org/licenses/bsd-license.php 5 | 6 | Copyright (c) 2010, Brian M. Clapper 7 | All rights reserved. 8 | 9 | Redistribution and use in source and binary forms, with or without 10 | modification, are permitted provided that the following conditions are 11 | met: 12 | 13 | * Redistributions of source code must retain the above copyright notice, 14 | this list of conditions and the following disclaimer. 15 | 16 | * Redistributions in binary form must reproduce the above copyright 17 | notice, this list of conditions and the following disclaimer in the 18 | documentation and/or other materials provided with the distribution. 19 | 20 | * Neither the names "clapper.org", "Argot", nor the names of its 21 | contributors may be used to endorse or promote products derived from 22 | this software without specific prior written permission. 23 | 24 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 25 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 26 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 27 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 28 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 29 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 30 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 31 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 32 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 33 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 34 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 35 | --------------------------------------------------------------------------- 36 | */ 37 | 38 | /** Argot is a command-line argument parsing API for Scala. 39 | */ 40 | package org.clapper.argot 41 | 42 | import scala.reflect.Manifest 43 | import scala.collection.mutable.{Map => MutableMap, 44 | LinkedHashMap, 45 | LinkedHashSet} 46 | import scala.util.matching.Regex 47 | import scala.util.Try 48 | import scala.annotation.tailrec 49 | 50 | /** Base trait for all option and parameter classes, `CommandLineArgument` 51 | * contains comment methods and values. 52 | * 53 | * @tparam T the type associated with the argument 54 | */ 55 | trait CommandLineArgument[T] { 56 | 57 | /** The `ArgotParser` instance that owns this object. 58 | */ 59 | val parent: ArgotParser 60 | 61 | /** The argument's description, displayed in the usage message. 62 | */ 63 | val description: String 64 | 65 | /** Whether or not the argument has an associated value. For instance, 66 | * parameters have values, and non-flag options have values. Flag options, 67 | * however, do not. 68 | */ 69 | val hasValue: Boolean 70 | 71 | /** Displayable name for the argument, used in the usage message. 72 | * 73 | * @return the name 74 | */ 75 | def name: String 76 | 77 | /** Resets the internal state of the argument to what it was right 78 | * after construction, undoing the effects of any parse operation. 79 | */ 80 | def reset(): Unit 81 | 82 | /** The standard `equals()` method. 83 | * 84 | * @param o some other object 85 | * 86 | * @return `true` if the other object is the same class and is equivalent, 87 | * `false` if not. 88 | */ 89 | override def equals(o: Any): Boolean = { 90 | o match { 91 | case that: CommandLineArgument[_] => 92 | (this.getClass == that.getClass) && (this.key == that.key) 93 | case _ => 94 | false 95 | } 96 | } 97 | 98 | /** Calculate the hash code for the object. The default implementation 99 | * returns the hash code of the key. 100 | * 101 | * @return the hash code 102 | * 103 | * @see #key 104 | */ 105 | override def hashCode = key.hashCode 106 | 107 | /** Return an object that represents the key for this parameter, suitable 108 | * for hashing, sorting, etc. 109 | * 110 | * @return the key 111 | */ 112 | protected def key: Any 113 | } 114 | 115 | /** 116 | * The `HasValue` trait is mixed into option and parameter classes that 117 | * support one or mor associated values of type `T`. 118 | * 119 | * @tparam T the value type 120 | * 121 | * @see SingleValueArg 122 | * @see MultiValueArg 123 | */ 124 | trait HasValue[T] extends CommandLineArgument[T] { 125 | /** Always `true`, indicating that `HasValue` classes always have an 126 | * associated value. 127 | */ 128 | val hasValue: Boolean = true 129 | 130 | /** All options and values with parameters must have a placeholder name 131 | * for the value, used in generating the usage message. 132 | */ 133 | val valueName: String 134 | 135 | /** Whether or not the class supports multiple values (e.g., a sequence) 136 | * or just one. 137 | */ 138 | val supportsMultipleValues: Boolean 139 | 140 | /** Method that converts a string value to type `T`. Should throw 141 | * `ArgotConversionException` on error. 142 | * 143 | * @param s the string to convert 144 | * 145 | * @return the converted result 146 | * 147 | * @throws ArgotConversionException conversion error 148 | */ 149 | def convertString(s: String): T 150 | 151 | /** Store the value. 152 | * 153 | * @param v the value, of type `T` 154 | */ 155 | private[argot] def storeValue(v: T): Unit 156 | 157 | /** Given a string value, convert the value to type `T` by calling 158 | * `convert()`, then store it by calling `storeValue()`. 159 | * 160 | * @param s the string to convert 161 | * 162 | * @throws ArgotConversionException conversion error 163 | */ 164 | def setFromString(s: String) = storeValue(convertString(s)) 165 | } 166 | 167 | /** 168 | * `SingleValueArg` is a refinement of the `HasValue` trait, specifically 169 | * for arguments (options or parameters) that take only a single value. 170 | * This trait exists primarily as a place for shared logic and values 171 | * for the option- and parameter-specific subclasses. 172 | * 173 | * @tparam T the value type 174 | * 175 | * @see SingleValueOption 176 | * @see SingleValueParameter 177 | */ 178 | trait SingleValueArg[T] extends HasValue[T] { 179 | private var optValue: Option[T] = None 180 | 181 | val supportsMultipleValues = false 182 | 183 | def reset() = optValue = None 184 | 185 | /** Get the option's value. 186 | * 187 | * @return `Some(value)` if the value is set; `None` if not. 188 | */ 189 | def value: Option[T] = optValue 190 | 191 | private[argot] def storeValue(v: T) = optValue = Some(v) 192 | } 193 | 194 | /** 195 | * `MultiValueArg` is a refinement of the `HasValue` trait, specifically 196 | * for arguments (options or parameters) that take multiple values of type 197 | * `T`. Each instance of the parameter on the command line adds to the 198 | * sequence of values in associated `MultiValueArg` object. 199 | * 200 | * This trait exists primarily as a place for shared logic and values 201 | * for the option- and parameter-specific subclasses. 202 | * 203 | * @tparam T the value type 204 | * 205 | * @see MultiValueOption 206 | * @see MultiValueParameter 207 | */ 208 | trait MultiValueArg[T] extends HasValue[T] { 209 | val supportsMultipleValues = true 210 | var optValue: Seq[T] = Seq.empty[T] 211 | 212 | def reset() = optValue = Seq.empty[T] 213 | 214 | /** Get the option's value(s). 215 | * 216 | * @return a sequence of option values, or `Nil` if none were present. 217 | */ 218 | def value: Seq[T] = optValue 219 | 220 | private[argot] def storeValue(v: T) = optValue = optValue.toList ::: List(v) 221 | } 222 | 223 | /** 224 | * `CommandLineOption` is the base trait for all option classes. 225 | * 226 | * @tparam T the value type 227 | * 228 | * @see SingleValueOption 229 | * @see MultiValueOption 230 | */ 231 | trait CommandLineOption[T] extends CommandLineArgument[T] { 232 | /** List of option names, both long (multi-character) and short 233 | * (single-character). 234 | */ 235 | val names: List[String] 236 | 237 | /** Return a suitable name for the option. The returned name will 238 | * have a "-" or "--" prefix, depending on whether it's long or short. 239 | * It will be based on the first option in the list of option names. 240 | * 241 | * @return the option name 242 | */ 243 | def name = names(0) match { 244 | case s: String if s.length > 1 => "--" + s 245 | case s: String if s.length == 1 => "-" + s 246 | } 247 | 248 | /** Get a printable name for this object. 249 | * 250 | * @return the printable name 251 | */ 252 | override def toString = "option " + name 253 | 254 | /** Return an object that represents the key for this parameter, suitable 255 | * for hashing, sorting, etc. They key for a command line option is the 256 | * result of calling `name()`. 257 | * 258 | * @return the key 259 | */ 260 | protected def key = name 261 | } 262 | 263 | /** 264 | * Class for an option that takes a single value. 265 | * 266 | * @tparam the type of the converted option value 267 | * 268 | * @param parent the parent parser instance that owns the option 269 | * @param names the list of names the option is known by 270 | * @param valueName the placeholder name for the option's value, for the 271 | * usage message 272 | * @param description textual description of the option 273 | * @param convert a function that will convert a string value for the 274 | * option to an appropriate value of type `T`. 275 | */ 276 | class SingleValueOption[T](val parent: ArgotParser, 277 | val names: List[String], 278 | val valueName: String, 279 | val description: String, 280 | val convert: (String, SingleValueOption[T]) => T) 281 | extends CommandLineOption[T] with SingleValueArg[T] { 282 | require ((names != Nil) && (! names.exists(_.length == 0))) 283 | 284 | def convertString(s: String): T = convert(s, this) 285 | } 286 | 287 | /** 288 | * Class for an option that takes a multiple values. Each instance of the 289 | * option on the command line adds to the sequence of values associated 290 | * with the option. 291 | * 292 | * @tparam the type of the converted option value 293 | * 294 | * @param parent the parent parser instance that owns the option 295 | * @param names the list of names the option is known by 296 | * @param valueName the placeholder name for the option's value, for the 297 | * usage message 298 | * @param description textual description of the option 299 | * @param convert a function that will convert a string value for the 300 | * option to an appropriate value of type `T`. 301 | */ 302 | class MultiValueOption[T](val parent: ArgotParser, 303 | val names: List[String], 304 | val valueName: String, 305 | val description: String, 306 | val convert: (String, MultiValueOption[T]) => T) 307 | extends CommandLineOption[T] with MultiValueArg[T] { 308 | require ((names != Nil) && (! names.exists(_.length == 0))) 309 | 310 | def convertString(s: String): T = convert(s, this) 311 | } 312 | 313 | /** 314 | * Class for a flag. A flag option consists of a set of names that enable 315 | * the flag (e.g.,, set it to true) if present on the command line, and a set 316 | * of names that disable the flag (e.g., set it to false) if present on the 317 | * command line. The type of flag option can be anything, but is generally 318 | * boolean. 319 | * 320 | * @tparam the underlying value type 321 | * 322 | * @param parent the parent parser instance that owns the option 323 | * @param namesOn list of names (short or long) that toggle the value on 324 | * @param namesOff list of names (short or long) that toggle the value off 325 | * @param description textual description of the option 326 | * @param convert a function that takes a boolean value and maps it to 327 | * the appropriate value to store as the option's value. 328 | */ 329 | class FlagOption[T](val parent: ArgotParser, 330 | namesOn: List[String], 331 | namesOff: List[String], 332 | val description: String, 333 | val convert: (Boolean, FlagOption[T]) => T) 334 | extends CommandLineOption[T] { 335 | val supportsMultipleValues = false 336 | val hasValue: Boolean = true 337 | 338 | private var flagValue: Option[T] = None 339 | private val shortNamesOnSet = namesOn.filter(_.length == 1).toSet 340 | private val shortNamesOffSet = namesOff.filter(_.length == 1).toSet 341 | private val longNamesOnSet = namesOn.filter(_.length > 1).toSet 342 | private val longNamesOffSet = namesOff.filter(_.length > 1).toSet 343 | 344 | require (wellDefined) 345 | 346 | val names = namesOn ::: namesOff 347 | 348 | def reset() = flagValue = None 349 | 350 | def value = flagValue 351 | 352 | private[argot] def value_(v: Option[T]): Unit = flagValue = v 353 | 354 | /** Called when the option is set (i.e., when one of the "on" names is 355 | * seen on the command line). Subclasses may override this method. 356 | * The default version calls `convert()` with a `true`, and stores the 357 | * result in `value`. 358 | */ 359 | def set: Unit = flagValue = Some(convert(true, this)) 360 | 361 | /** Called when the option to unset (i.e., when one of the "off" names is 362 | * seen on the command line). Subclasses may override this method. 363 | * The default version calls `convert()` with a `false` and stores the 364 | * result in `value`. 365 | */ 366 | def clear: Unit = flagValue = Some(convert(false, this)) 367 | 368 | /** Set the value, based on whether the specified option name is an 369 | * "on" or an "off" name. 370 | * 371 | * @param name the name, without any leading "-" or "--" 372 | */ 373 | def setByName(name: String): Unit = { 374 | assert(name.length > 0) 375 | 376 | checkValidity(name) 377 | 378 | name.length match { 379 | case 1 => if (shortNamesOnSet contains name) set else clear 380 | case _ => if (longNamesOnSet contains name) set else clear 381 | } 382 | } 383 | 384 | /** Displayable name for the argument, used in the usage message. 385 | * 386 | * @return the name 387 | */ 388 | override def name = namesOn match { 389 | case c :: tail => "-" + c.toString 390 | case Nil => "--" + namesOff(0) 391 | } 392 | 393 | /** Return an object that represents the key for this parameter, suitable 394 | * for hashing, sorting, etc. They key for a command line option is the 395 | * result of calling `name()`. 396 | * 397 | * @return the key 398 | */ 399 | override protected def key = 400 | namesOn.mkString("|") + "!" + namesOff.mkString("|") 401 | 402 | private def wellDefined: Boolean = { 403 | def inBoth(s: String) = 404 | (((shortNamesOnSet | longNamesOnSet) contains s) && 405 | ((shortNamesOffSet | longNamesOffSet) contains s)) 406 | 407 | val l = namesOn ::: namesOff 408 | (l != Nil) && (! l.exists(_.length == 0)) && (! l.exists(inBoth _)) 409 | } 410 | 411 | private def checkValidity(optName: String) = { 412 | if (! ((shortNamesOnSet contains optName) || 413 | (shortNamesOffSet contains optName) || 414 | (longNamesOnSet contains optName) || 415 | (longNamesOffSet contains optName)) ) 416 | throw new ArgotException("(BUG) Flag name \"" + optName + 417 | "\" is neither a short nor a long name " + 418 | "for option \"" + this.name + "\"") 419 | } 420 | } 421 | 422 | /** 423 | * Base trait for parameter classes 424 | */ 425 | private[argot] trait Parameter[T] 426 | extends CommandLineArgument[T] with HasValue[T] { 427 | val convert: (String, Parameter[T]) => T 428 | val description: String 429 | val optional: Boolean 430 | 431 | require (valueName.length > 0) 432 | 433 | def name = valueName 434 | def convertString(s: String): T = convert(s, this) 435 | override def toString = "parameter " + valueName 436 | protected def key = valueName 437 | } 438 | 439 | /** 440 | * Class for a non-option parameter that takes a single value. 441 | * 442 | * @tparam the type of the converted parameter value 443 | * 444 | * @param parent the parent parser instance that owns the parameter 445 | * @param valueName the placeholder name for the parameter's value, 446 | * for the usage message 447 | * @param description textual description of the parameter 448 | * @param optional whether or not the parameter is optional. Only one 449 | * parameter may be optional, and it must be last one 450 | * @param convert a function that will convert a string value for 451 | * the parameter to an appropriate value of type `T`. 452 | */ 453 | class SingleValueParameter[T]( 454 | val parent: ArgotParser, 455 | val valueName: String, 456 | val description: String, 457 | val optional: Boolean, 458 | val convert: (String, Parameter[T]) => T) 459 | extends Parameter[T] with SingleValueArg[T] 460 | 461 | /** 462 | * Class for a non-option parameter that takes a multiple values. Each 463 | * instance of the parameter on the command line adds to the sequence of 464 | * values associated with the parameter. 465 | * 466 | * @tparam the type of the converted parameter value 467 | * 468 | * @param parent the parent parser instance that owns the parameter 469 | * @param valueName the placeholder name for the parameter's value, 470 | * for the usage message 471 | * @param description textual description of the parameter 472 | * @param optional whether or not the parameter is optional. Only one 473 | * parameter may be optional, and it must be the last one. 474 | * @param convert a function that will convert a string value for 475 | * the parameter to an appropriate value of type `T`. 476 | */ 477 | class MultiValueParameter[T]( 478 | val parent: ArgotParser, 479 | val valueName: String, 480 | val description: String, 481 | val optional: Boolean, 482 | val convert: (String, Parameter[T]) => T) 483 | extends Parameter[T] with MultiValueArg[T] 484 | 485 | /** 486 | * Internally used common conversion functions 487 | */ 488 | private object Conversions { 489 | implicit def parseInt(s: String, opt: String): Int = { 490 | parseNum[Int](s, s.toInt) 491 | } 492 | 493 | implicit def parseLong(s: String, opt: String): Long = { 494 | parseNum[Long](s, s.toLong) 495 | } 496 | 497 | implicit def parseShort(s: String, opt: String): Short = { 498 | parseNum[Short](s, s.toShort) 499 | } 500 | 501 | implicit def parseFloat(s: String, opt: String): Float = { 502 | parseNum[Float](s, s.toFloat) 503 | } 504 | 505 | implicit def parseDouble(s: String, opt: String): Double = { 506 | parseNum[Double](s, s.toDouble) 507 | } 508 | 509 | implicit def parseChar(s: String, opt: String): Char = { 510 | if (s.length != 1) 511 | throw new ArgotConversionException( 512 | "Option \"" + opt + "\": " + 513 | "Cannot parse \"" + s + "\" to a character." 514 | ) 515 | s(0) 516 | } 517 | 518 | implicit def parseByte(s: String, opt: String): Byte = { 519 | val num = s.toInt 520 | if ((num < 0) || (num > 255)) 521 | throw new ArgotConversionException( 522 | "Option \"" + opt + "\": " + "\"" + s + 523 | "\" results in a number that is too large for a byte." 524 | ) 525 | 526 | num.toByte 527 | } 528 | 529 | implicit def parseString(s: String, opt: String): String = { 530 | s 531 | } 532 | 533 | private def parseNum[T](s: String, parse: => T): T = { 534 | try { 535 | parse 536 | } 537 | 538 | catch { 539 | case e: NumberFormatException => 540 | throw new ArgotConversionException( 541 | "Cannot convert argument \"" + s + "\" to a number." 542 | ) 543 | } 544 | } 545 | } 546 | 547 | /** 548 | * Conversion functions that can be used to satisfy the implicit conversions 549 | * specified to the various specification functions in the `ArgotParser` class. 550 | * If you import this namespace, you'll get a bunch of implicit conversion 551 | * functions that the Scala compiler will automatically use, for the 552 | * various definition functions in `ArgotParser`. 553 | * 554 | * The conversion functions all take the `CommandLineArgument` for which the 555 | * value applies. This serves two purposes. First, it provides more information 556 | * for error messages. Second, it makes the conversion functions less ambiguous. 557 | */ 558 | object ArgotConverters { 559 | /** Convert a string value into an integer. A non-numeric string value 560 | * will cause an error. 561 | * 562 | * @param s the string value to convert 563 | * @param opt the command line argument to which the value applies 564 | * 565 | * @return the integer 566 | * 567 | * @throws ArgotConversionException conversion error 568 | */ 569 | implicit def convertInt(s: String, opt: CommandLineArgument[Int]): Int = { 570 | Conversions.parseInt(s, opt.name) 571 | } 572 | 573 | /** Convert a string value into a long. A non-numeric string value 574 | * will cause an error. 575 | * 576 | * @param s the string value to convert 577 | * @param opt the command line argument to which the value applies 578 | * 579 | * @return the long integer 580 | * 581 | * @throws ArgotConversionException conversion error 582 | */ 583 | implicit def convertLong(s: String, opt: CommandLineArgument[Long]): Long = { 584 | Conversions.parseLong(s, opt.name) 585 | } 586 | 587 | /** Convert a string value into a short. A non-numeric string value 588 | * will cause an error. 589 | * 590 | * @param s the string value to convert 591 | * @param opt the command line argument to which the value applies 592 | * 593 | * @return the short 594 | * 595 | * @throws ArgotConversionException conversion error 596 | */ 597 | implicit def convertShort(s: String, opt: CommandLineArgument[Short]): 598 | Short = { 599 | Conversions.parseShort(s, opt.name) 600 | } 601 | 602 | /** Convert a string value into a float. A non-numeric string value 603 | * will cause an error. 604 | * 605 | * @param s the string value to convert 606 | * @param opt the command line argument to which the value applies 607 | * 608 | * @return the float. 609 | * 610 | * @throws ArgotConversionException conversion error 611 | */ 612 | implicit def convertFloat(s: String, opt: CommandLineArgument[Float]): 613 | Float = { 614 | Conversions.parseFloat(s, opt.name) 615 | } 616 | 617 | /** Convert a string value into an double. A non-numeric string value 618 | * will cause an error. 619 | * 620 | * @param s the string value to convert 621 | * @param opt the command line argument to which the value applies 622 | * 623 | * @return the double. 624 | * 625 | * @throws ArgotConversionException conversion error 626 | */ 627 | implicit def convertDouble(s: String, opt: CommandLineArgument[Double]): 628 | Double = { 629 | Conversions.parseDouble(s, opt.name) 630 | } 631 | 632 | /** Convert a string value into a character. A string that is empty or 633 | * is longer than one character in length will cause an error. 634 | * 635 | * @param s the string value to convert 636 | * @param opt the command line argument to which the value applies 637 | * 638 | * @return the character 639 | * 640 | * @throws ArgotConversionException conversion error 641 | */ 642 | implicit def convertChar(s: String, opt: CommandLineArgument[Char]): Char = { 643 | Conversions.parseChar(s, opt.name) 644 | } 645 | 646 | /** Convert a string value into a byte value. A non-numeric string value 647 | * will cause an error, as will a value that is outside the range 648 | * [0, 255]. 649 | * 650 | * @param s the string value to convert 651 | * @param opt the command line argument to which the value applies 652 | * 653 | * @return the integer 654 | * 655 | * @throws ArgotConversionException conversion error 656 | */ 657 | implicit def convertByte(s: String, opt: CommandLineArgument[Byte]): Byte = { 658 | Conversions.parseByte(s, opt.name) 659 | } 660 | 661 | /** Convert a string value into a string. This function is a no-op. 662 | * 663 | * @param s the string value to convert 664 | * @param opt the command line argument to which the value applies 665 | * 666 | * @return the integer 667 | * 668 | * @throws ArgotConversionException conversion error 669 | */ 670 | implicit def convertString(s: String, opt: CommandLineArgument[String]): 671 | String = { 672 | s 673 | } 674 | 675 | /** Convert a value for a flag option. This function is primarily a 676 | * no-op that exists to satisfy the implicit parameter for the 677 | * `ArgotParser.flag()` methods. 678 | * 679 | * @param onOff the value to be returned 680 | * @param opt the command line argument to which the value applies 681 | * 682 | * @return the value of `onOff` 683 | */ 684 | implicit def convertFlag[Boolean](onOff: Boolean, 685 | opt: FlagOption[Boolean]): Boolean = { 686 | onOff 687 | } 688 | 689 | /** Convert a string value into a sequence, adding the result to the 690 | * supplied `MultiValueOption` object's `value` field. The string is 691 | * split into multiple strings via the supplied `parse()` function 692 | * parameter; the parameter is marked implicit, so that it can be 693 | * satisfied automatically. 694 | * 695 | * If the `ArgotConverters` name space is in scope, then the default 696 | * implicit function that satisfies the parameter simply returns the 697 | * string, unparsed, thus resulting in the argument string being 698 | * concatenated, as is, to the `MultiValueOption`. This behavior is 699 | * generally the one most often used; however, it's possible to substitute 700 | * other parsing functions that (for instance) split the string based on 701 | * a delimiter. 702 | * 703 | * @param s the string value to convert 704 | * @param opt the command line argument to which the value applies 705 | * 706 | * @return the integer 707 | * 708 | * @throws ArgotConversionException conversion error 709 | */ 710 | implicit def convertSeq[T](s: String, opt: MultiValueOption[T]) 711 | (implicit parse: (String, String) => T): Seq[T] = { 712 | opt.value :+ parse(s, opt.name).asInstanceOf[T] 713 | } 714 | } 715 | 716 | /** 717 | * `ArgotParser` is a command-line parser, with support for single-value and 718 | * multi-value options, single-value and multi-value parameters, typed value, 719 | * custom conversions (with suitable defaults), and extensibility. 720 | * 721 | * An `ArgotParser` embodies a representation of the command line: its 722 | * expected options and their value types, the expected positional 723 | * parameters and their value types, the name of the program, and other 724 | * values. 725 | * 726 | * For complete details, see the Argot web site (linked below). 727 | * 728 | * @param programName the name of the program, for the usage message 729 | * @param compactUsage force a more compact usage message 730 | * @param outputWidth width of the output; used when wrapping the usage 731 | * message 732 | * @param preUsage optional message to issue before the usage message 733 | * (e.g., a copyright and/or version string) 734 | * @param postUsage optional message to issue after the usage message 735 | * @param sortUsage If `true` (the default), the options are sorted 736 | * alphabetically in the usage output. If `false`, they 737 | * are displayed in the order they were created. 738 | * 739 | * @see the Argot web site 740 | */ 741 | class ArgotParser(programName: String, 742 | compactUsage: Boolean = false, 743 | outputWidth: Int = 79, 744 | preUsage: Option[String] = None, 745 | postUsage: Option[String] = None, 746 | sortUsage: Boolean = true) { 747 | require(outputWidth > 0) 748 | 749 | protected val shortNameMap = MutableMap.empty[Char, CommandLineOption[_]] 750 | protected val longNameMap = MutableMap.empty[String, CommandLineOption[_]] 751 | protected val allOptions = new LinkedHashMap[String, CommandLineOption[_]] 752 | protected val nonFlags = new LinkedHashSet[HasValue[_]] 753 | protected val flags = new LinkedHashSet[FlagOption[_]] 754 | protected val parameters = new LinkedHashSet[Parameter[_]] 755 | 756 | /** Resets the internal state of all contained options and parameters, 757 | * undoing the effects of any parse operation. 758 | */ 759 | def reset() = { 760 | allOptions.values.foreach(_.reset()) 761 | parameters.foreach(_.reset()) 762 | } 763 | 764 | /** Define an option that takes a single value of type `T`. 765 | * 766 | * @tparam T the type of the option's value, which will be stored in 767 | * the `SingleValueOption` object's `value` field. 768 | * 769 | * @param names the list of names for the option. Each name can be 770 | * a single character (thus, "v" corresponds to "-v") or 771 | * multiple characters ("verbose" for "--verbose"). 772 | * @param valueName a name to use for the associated value in the 773 | * generated usage message 774 | * @param description a description for the option, for the usage message 775 | * @param convert a function that will convert a string value into 776 | * type `T`. The function should throw 777 | * `ArgotConversionException` on conversion error. 778 | * For common types, the implicit functions in the 779 | * `ArgotConverters` module are often suitable. 780 | * 781 | * @return the `SingleValueOption` object that will contain the 782 | * parsed value, as an `Option[T]` in the `value` field. 783 | */ 784 | def option[T](names: List[String], valueName: String, description: String) 785 | (implicit convert: (String, SingleValueOption[T]) => T): 786 | SingleValueOption[T] = { 787 | names.foreach(checkOptionName) 788 | 789 | val opt = new SingleValueOption[T](this, names, valueName, description, 790 | convert) 791 | replaceOption(opt) 792 | nonFlags += opt 793 | opt 794 | } 795 | 796 | /** Define an option that takes a single value of type `T`. This short-hand 797 | * method provides only one option name, as opposed to a list of option 798 | * names. 799 | * 800 | * @tparam T the type of the option's value, which will be stored in 801 | * the `SingleValueOption` object's `value` field. 802 | * 803 | * @param name the name for the option. The name can be a single 804 | * character (thus, "v" corresponds to "-v") or 805 | * multiple characters ("verbose" for "--verbose"). 806 | * @param valueName a name to use for the associated value in the 807 | * generated usage message 808 | * @param description a description for the option, for the usage message 809 | * @param convert a function that will convert a string value into 810 | * type `T`. The function should throw 811 | * `ArgotConversionException` on conversion error. 812 | * For common types, the implicit functions in the 813 | * `ArgotConverters` module are often suitable. 814 | * 815 | * @return the `SingleValueOption` object that will contain the 816 | * parsed value, as an `Option[T]` in the `value` field. 817 | */ 818 | def option[T](name: String, valueName: String, description: String) 819 | (implicit convert: (String, SingleValueOption[T]) => T): 820 | SingleValueOption[T] = { 821 | option[T](List(name), valueName, description)(convert) 822 | } 823 | 824 | /** Define an option that takes a sequence of values of type `T`. Each 825 | * invocation of the option on the command line contributes a new value 826 | * to the sequence. 827 | * 828 | * @tparam T the type of the option's value, which added to the 829 | * the `MultiValueOption` object's `value` sequence field. 830 | * 831 | * @param names the list of names for the option. Each name can be 832 | * a single character (thus, "v" corresponds to "-v") or 833 | * multiple characters ("verbose" for "--verbose"). 834 | * @param valueName a name to use for the associated value in the 835 | * generated usage message 836 | * @param description a description for the option, for the usage message 837 | * @param convert a function that will convert a string value into 838 | * type `T`. The function should throw 839 | * `ArgotConversionException` on conversion error. 840 | * For common types, the implicit functions in the 841 | * `ArgotConverters` module are often suitable. 842 | * 843 | * @return the `MultiValueOption` object that will contain the 844 | * parsed value(s), as a `Seq[T]` in the `value` field. 845 | */ 846 | def multiOption[T](names: List[String], 847 | valueName: String, 848 | description: String) 849 | (implicit convert: (String, MultiValueOption[T]) => T): 850 | MultiValueOption[T] = { 851 | names.foreach(checkOptionName) 852 | 853 | val opt = new MultiValueOption[T](this, names, 854 | valueName, description, convert) 855 | replaceOption(opt) 856 | nonFlags += opt 857 | opt 858 | } 859 | 860 | /** Define an option that takes a sequence of values of type `T`. Each 861 | * invocation of the option on the command line contributes a new value 862 | * to the sequence. This short-hand method provides only one option 863 | * name, as opposed to a list of option names. 864 | * 865 | * @tparam T the type of the option's value, which added to the 866 | * the `MultiValueOption` object's `value` sequence field. 867 | * 868 | * @param name the name for the option. The name can be a single 869 | * character (thus, "v" corresponds to "-v") or 870 | * multiple characters ("verbose" for "--verbose"). 871 | * @param valueName a name to use for the associated value in the 872 | * generated usage message 873 | * @param description a description for the option, for the usage message 874 | * @param convert a function that will convert a string value into 875 | * type `T`. The function should throw 876 | * `ArgotConversionException` on conversion error. 877 | * For common types, the implicit functions in the 878 | * `ArgotConverters` module are often suitable. 879 | * 880 | * @return the `MultiValueOption` object that will contain the 881 | * parsed value(s), as a `Seq[T]` in the `value` field. 882 | */ 883 | def multiOption[T](name: String, valueName: String, description: String) 884 | (implicit convert: (String, MultiValueOption[T]) => T): 885 | MultiValueOption[T] = { 886 | multiOption[T](List(name), valueName, description)(convert) 887 | } 888 | 889 | /** Define a flag option. Flag options take no value parameters. 890 | * Instead, a flag option is simply present or absent. Typically, flag 891 | * options are associated with boolean value, though Argot will permit 892 | * you to associate them with any type you choose. 893 | * 894 | * Flag options permit you to segregate the option names into ''on'' 895 | * names and ''off'' names. With boolean flag options, the ''on'' names 896 | * set the value to `true`, and the ''off'' names set the value to 897 | * values. With typed flag options, what happens depends on the 898 | * conversion function. Whatever the conversion function returns gets 899 | * stored as the option's value. 900 | * 901 | * Flag conversion functions receive a boolean parameter, indicating 902 | * whether an ''on'' option was seen (`true`) or an ''off'' option was 903 | * seen (`false`). The built-in conversion functions for boolean flags 904 | * simply return the value of the boolean, which is then stored in the 905 | * (Boolean) flag option's value. However, it's perfectly reasonable 906 | * to have flag options with other types. For instance, one could easily 907 | * define a "Verbosity" flag option of type `Int`, where each ''on'' 908 | * option increments the verbosity level and each ''off'' option decrements 909 | * the value. 910 | * 911 | * @tparam T the type of the option's value, which will be stored in 912 | * the `FlagOption` object's `value` field. 913 | * 914 | * @param namesOn the names for the option that enable (turn on) the 915 | * option. Each name can be a single 916 | * character (thus, "v" corresponds to "-v") or 917 | * multiple characters ("verbose" for "--verbose"). 918 | * @param namesOff the names for the option that disable (turn off) the 919 | * option. Each name can be a single 920 | * character (thus, "v" corresponds to "-v") or 921 | * multiple characters ("verbose" for "--verbose"). 922 | * @param description a description for the option, for the usage message 923 | * @param convert a function that will convert a boolean on/off value 924 | * to type `T`. The function should throw 925 | * `ArgotConversionException` on conversion error. 926 | * For a boolean flag option , the implicit functions in 927 | * the `ArgotConverters` module are often suitable. 928 | * 929 | * @return the `FlagOption` object that will contain the 930 | * parsed value, as an `Option[T]` in the `value` field. 931 | */ 932 | def flag[T](namesOn: List[String], 933 | namesOff: List[String], 934 | description: String) 935 | (implicit convert: (Boolean, FlagOption[T]) => T): 936 | FlagOption[T] = { 937 | namesOn.foreach(checkOptionName) 938 | namesOff.foreach(checkOptionName) 939 | 940 | val opt = new FlagOption[T](this, namesOn, namesOff, 941 | description, convert) 942 | replaceOption(opt) 943 | flags += opt 944 | opt 945 | } 946 | 947 | /** Define a flag option. Flag options take no value parameters. 948 | * Instead, a flag option is simply present or absent. Typically, flag 949 | * options are associated with boolean value, though Argot will permit 950 | * you to associate them with any type you choose. 951 | * 952 | * Flag options permit you to segregate the option names into ''on'' 953 | * names and ''off'' names. With boolean flag options, the ''on'' names 954 | * set the value to `true`, and the ''off'' names set the value to 955 | * values. With typed flag options, what happens depends on the 956 | * conversion function. Whatever the conversion function returns gets 957 | * stored as the option's value. 958 | * 959 | * Flag conversion functions receive a boolean parameter, indicating 960 | * whether an ''on'' option was seen (`true`) or an ''off'' option was 961 | * seen (`false`). The built-in conversion functions for boolean flags 962 | * simply return the value of the boolean, which is then stored in the 963 | * (Boolean) flag option's value. However, it's perfectly reasonable 964 | * to have flag options with other types. For instance, one could easily 965 | * define a "Verbosity" flag option of type `Int`, where each ''on'' 966 | * option increments the verbosity level and each ''off'' option decrements 967 | * the value. 968 | * 969 | * @tparam T the type of the option's value, which will be stored in 970 | * the `FlagOption` object's `value` field. 971 | * 972 | * @param namesOn the names for the option that enable (turn on) the 973 | * option. Each name can be a single character (thus, 974 | * "v" corresponds to "-v") or multiple characters 975 | * ("verbose" for "--verbose"). 976 | * @param description a description for the option, for the usage message 977 | * @param convert a function that will convert a boolean on/off value 978 | * to type `T`. The function should throw 979 | * `ArgotConversionException` on conversion error. 980 | * For a boolean flag option , the implicit functions in 981 | * the `ArgotConverters` module are often suitable. 982 | * 983 | * @return the `FlagOption` object that will contain the 984 | * parsed value, as an `Option[T]` in the `value` field. 985 | */ 986 | def flag[T](namesOn: List[String], description: String) 987 | (implicit convert: (Boolean, FlagOption[T]) => T): 988 | FlagOption[T] = { 989 | flag(namesOn, Nil, description)(convert) 990 | } 991 | 992 | /** Define a flag option. Flag options take no value parameters. 993 | * Instead, a flag option is simply present or absent. Typically, flag 994 | * options are associated with boolean value, though Argot will permit 995 | * you to associate them with any type you choose. 996 | * 997 | * Flag options permit you to segregate the option names into ''on'' 998 | * names and ''off'' names. With boolean flag options, the ''on'' names 999 | * set the value to `true`, and the ''off'' names set the value to 1000 | * values. With typed flag options, what happens depends on the 1001 | * conversion function. Whatever the conversion function returns gets 1002 | * stored as the option's value. 1003 | * 1004 | * Flag conversion functions receive a boolean parameter, indicating 1005 | * whether an ''on'' option was seen (`true`) or an ''off'' option was 1006 | * seen (`false`). The built-in conversion functions for boolean flags 1007 | * simply return the value of the boolean, which is then stored in the 1008 | * (Boolean) flag option's value. However, it's perfectly reasonable 1009 | * to have flag options with other types. For instance, one could easily 1010 | * define a "Verbosity" flag option of type `Int`, where each ''on'' 1011 | * option increments the verbosity level and each ''off'' option decrements 1012 | * the value. 1013 | * 1014 | * @tparam T the type of the option's value, which will be stored in 1015 | * the `FlagOption` object's `value` field. 1016 | * 1017 | * @param name the name for the option that enables (turns on) the 1018 | * option. The name can be a single character (thus, 1019 | * "v" corresponds to "-v") or multiple characters 1020 | * ("verbose" for "--verbose"). 1021 | * @param description a description for the option, for the usage message 1022 | * @param convert a function that will convert a boolean on/off value 1023 | * to type `T`. The function should throw 1024 | * `ArgotConversionException` on conversion error. 1025 | * For a boolean flag option , the implicit functions in 1026 | * the `ArgotConverters` module are often suitable. 1027 | * 1028 | * @return the `FlagOption` object that will contain the 1029 | * parsed value, as an `Option[T]` in the `value` field. 1030 | */ 1031 | def flag[T](name: String, default: T, description: String) 1032 | (implicit convert: (Boolean, FlagOption[T]) => T): 1033 | FlagOption[T] = { 1034 | flag[T](List(name), description)(convert) 1035 | } 1036 | 1037 | /** Define a positional parameter that has a single value. Positional 1038 | * parameters are parsed from the command line in the order they are 1039 | * added to the `ArgotParser`. See the class documentation for complete 1040 | * details. 1041 | * 1042 | * @tparam the type of the converted parameter value 1043 | * 1044 | * @param valueName the placeholder name for the parameter's value, 1045 | * for the usage message 1046 | * @param description textual description of the parameter 1047 | * @param optional whether or not the parameter is optional. Only one 1048 | * parameter may be optional, and it must be the last 1049 | * one. 1050 | * @param convert a function that will convert a string value for 1051 | * the parameter to an appropriate value of type `T`. 1052 | * The function should throw `ArgotConversionException` 1053 | * on conversion error. 1054 | * 1055 | * @return the `SingleValueParameter` object will contain the parsed 1056 | * value (as an `Option[T]` in the `value` field). 1057 | */ 1058 | def parameter[T](valueName: String, description: String, optional: Boolean) 1059 | (implicit convert: (String, Parameter[T]) => T): 1060 | SingleValueParameter[T] = { 1061 | val param = new SingleValueParameter[T](this, 1062 | valueName, 1063 | description, 1064 | optional, 1065 | convert) 1066 | checkOptionalStatus(param, optional) 1067 | checkForMultiParam(param) 1068 | replaceParameter(param) 1069 | param 1070 | } 1071 | 1072 | /** Define a positional parameter that can occur multiple times. Only 1073 | * one such parameter can exist, and it must be the last parameter 1074 | * in the command line. See the class documentation for complete 1075 | * details. 1076 | * 1077 | * @tparam the type of the converted parameter value 1078 | * 1079 | * @param valueName the placeholder name for the parameter's value, 1080 | * for the usage message 1081 | * @param description textual description of the parameter 1082 | * @param optional whether or not the parameter is optional. Only one 1083 | * parameter may be optional, and it must be the last 1084 | * one. 1085 | * @param convert a function that will convert a string value for 1086 | * the parameter to an appropriate value of type `T`. 1087 | * The function should throw `ArgotConversionException` 1088 | * on conversion error. 1089 | * 1090 | * @return the `MultiValueParameter` object will contain the parsed 1091 | * values (as a `Seq[T]` in the `value` field). 1092 | */ 1093 | def multiParameter[T](valueName: String, 1094 | description: String, 1095 | optional: Boolean) 1096 | (implicit convert: (String, Parameter[T]) => T): 1097 | MultiValueParameter[T] = { 1098 | val param = new MultiValueParameter[T](this, 1099 | valueName, 1100 | description, 1101 | optional, 1102 | convert) 1103 | checkOptionalStatus(param, optional) 1104 | checkForMultiParam(param) 1105 | replaceParameter(param) 1106 | param 1107 | } 1108 | 1109 | /** Parse the specified array of command-line arguments, according to the 1110 | * parser's specification. A successful parse sets the various value 1111 | * objects returned by the specification methods. 1112 | * 1113 | * @param args the command line parameters 1114 | * 1115 | * @throws ArgotUsageException user error on the command line; the 1116 | * exception contains the usage message 1117 | * @throws ArgotException some other kind of fatal error 1118 | */ 1119 | def parse(args: Array[String]): Unit = parse(args.toList) 1120 | 1121 | /** Parse the specified array of command-line arguments, according to the 1122 | * parser's specification. A successful parse sets the various value 1123 | * objects returned by the specification methods. 1124 | * 1125 | * @param args the command line parameters 1126 | * 1127 | * @throws ArgotUsageException user error on the command line; the 1128 | * exception contains the usage message 1129 | * @throws ArgotException some other kind of fatal error 1130 | */ 1131 | def parse(args: List[String]): Unit = { 1132 | try { 1133 | parseArgList(args) 1134 | } 1135 | 1136 | catch { 1137 | case e: ArgotConversionException => usage(e.message) 1138 | } 1139 | } 1140 | 1141 | /** Generate the usage string. This string is automatically included in 1142 | * any thrown `ArgotUsageException`. 1143 | * 1144 | * @param message optional message to prefix the usage string. 1145 | * 1146 | * @return the usage string, wrapped appropriately. 1147 | */ 1148 | def usageString(message: Option[String] = None): String = { 1149 | import grizzled.math.{util => MathUtil} 1150 | import grizzled.string.WordWrapper 1151 | 1152 | val SPACES_BETWEEN = 2 // spaces between arg name and description 1153 | 1154 | def paramString(p: Parameter[_]): String = 1155 | if (p.optional) "[" + p.name + "]" else p.name 1156 | 1157 | def optString(name: String, opt: CommandLineOption[_]) = { 1158 | val hyphen = if (name.length == 1) "-" else "--" 1159 | 1160 | opt match { 1161 | case ov: HasValue[_] => hyphen + name + " " + ov.valueName 1162 | case _ => hyphen + name 1163 | } 1164 | } 1165 | 1166 | val mmax = MathUtil.max _ 1167 | 1168 | // Calculate the maximum length of all the option strings. 1169 | 1170 | val lengths: Iterable[Int] = for {opt <- allOptions.values 1171 | name <- opt.names} 1172 | yield optString(name, opt).length 1173 | val maxOptLen = Try(lengths.max).getOrElse(0) 1174 | 1175 | // Create the output buffer. 1176 | 1177 | val buf = new StringBuilder 1178 | 1179 | // Build the usage line. 1180 | val wrapper = new WordWrapper(wrapWidth=outputWidth) 1181 | 1182 | message.foreach(s => buf.append(wrapper.wrap(s) + "\n\n")) 1183 | preUsage.foreach(s => buf.append(wrapper.wrap(s) + "\n\n")) 1184 | buf.append("Usage: " + programName) 1185 | if (allOptions.size > 0) 1186 | buf.append(" [OPTIONS]") 1187 | 1188 | for (p <- parameters) { 1189 | buf.append(" ") 1190 | buf.append( 1191 | p match { 1192 | case _: SingleValueParameter[_] => paramString(p) 1193 | case _: MultiValueParameter[_] => paramString(p) + " ..." 1194 | } 1195 | ) 1196 | } 1197 | 1198 | buf.append('\n') 1199 | 1200 | // Build the option summary. 1201 | 1202 | def handleOneOption(key: String) = { 1203 | if (! compactUsage) 1204 | buf.append("\n") 1205 | 1206 | val opt = allOptions(key) 1207 | 1208 | // Ensure that the short names always appear before the long 1209 | // names. 1210 | 1211 | val sorted = opt.names.filter(_.length == 1).sortWith(_ < _) ::: 1212 | opt.names.filter(_.length > 1).sortWith(_ < _) 1213 | 1214 | for (name <- sorted.take(sorted.length - 1)) 1215 | buf.append(optString(name, opt) + "\n") 1216 | 1217 | val name = sorted.takeRight(1)(0) 1218 | val os = optString(name, opt) 1219 | val padding = (maxOptLen - os.length) + SPACES_BETWEEN 1220 | val prefix = os + (" " * padding) 1221 | val wrapper = new WordWrapper(prefix=prefix, 1222 | wrapWidth=outputWidth) 1223 | val desc = opt match { 1224 | case o: HasValue[_] => 1225 | if (o.supportsMultipleValues) 1226 | o.description + " (May be specified multiple times.)" 1227 | else 1228 | o.description 1229 | 1230 | case _ => 1231 | opt.description 1232 | 1233 | } 1234 | 1235 | buf.append(wrapper.wrap(desc)) 1236 | buf.append("\n") 1237 | } 1238 | 1239 | def handleOneParameter(p: Parameter[_], maxNameLen: Int) = { 1240 | if (! compactUsage) 1241 | buf.append('\n') 1242 | 1243 | val padding = (maxNameLen - p.name.length) + SPACES_BETWEEN 1244 | val prefix = p.name + (" " * padding) 1245 | val wrapper = new WordWrapper(prefix=prefix, wrapWidth=outputWidth) 1246 | val desc = p match { 1247 | case o: HasValue[_] => 1248 | if (o.supportsMultipleValues) 1249 | o.description + " (May be specified multiple times.)" 1250 | else 1251 | o.description 1252 | 1253 | case _ => 1254 | p.description 1255 | } 1256 | 1257 | buf.append(wrapper.wrap(desc)) 1258 | buf.append('\n') 1259 | } 1260 | 1261 | if (allOptions.size > 0) { 1262 | buf.append("\nOPTIONS\n") 1263 | val optionKeys = 1264 | if (sortUsage) 1265 | allOptions.keySet.toList.sortWith(_ < _) 1266 | else 1267 | allOptions.keySet.toList 1268 | 1269 | optionKeys.foreach(handleOneOption) 1270 | } 1271 | 1272 | if (parameters.size > 0) { 1273 | buf.append("\nPARAMETERS\n") 1274 | val maxNameLen = parameters.map(_.name.length).max 1275 | parameters.toList.foreach(handleOneParameter(_, maxNameLen)) 1276 | } 1277 | 1278 | postUsage.foreach(s => buf.append(wrapper.wrap(s) + "\n")) 1279 | buf.toString 1280 | } 1281 | 1282 | /** Throws an `ArgotUsageException` containing the usage string. 1283 | * 1284 | * @throws ArgotUsageException unconditionally 1285 | */ 1286 | def usage() = throw new ArgotUsageException(usageString()) 1287 | 1288 | /** Throws an `ArgotUsageException` containing the usage string. 1289 | * 1290 | * @param message optional message to prefix the usage string. 1291 | * 1292 | * @throws ArgotUsageException unconditionally 1293 | */ 1294 | def usage(message: String) = 1295 | throw new ArgotUsageException(usageString(Some(message))) 1296 | 1297 | /* 1298 | ----------------------------------------------------------------------- 1299 | Private Methods 1300 | ----------------------------------------------------------------------- 1301 | */ 1302 | 1303 | private def replaceOption(opt: CommandLineOption[_]) { 1304 | opt.names.filter(_.length == 1). 1305 | foreach(s => shortNameMap += s(0) -> opt) 1306 | opt.names.filter(_.length > 1).foreach(s => longNameMap += s -> opt) 1307 | allOptions += opt.name -> opt 1308 | } 1309 | 1310 | private def replaceParameter(param: Parameter[_]) { 1311 | parameters += param 1312 | } 1313 | 1314 | private def parseParams(a: List[String]): Unit = { 1315 | def parseNext(a: List[String], paramSpecs: List[Parameter[_]]): 1316 | List[String] = { 1317 | def checkMissing(paramSpecs: List[Parameter[_]]): List[String] = { 1318 | if (paramSpecs.count(! _.optional) > 0) 1319 | usage("Missing parameter(s): " + 1320 | paramSpecs.filter(! _.optional). 1321 | map(_.name). 1322 | mkString(", ") 1323 | ) 1324 | Nil 1325 | } 1326 | 1327 | paramSpecs match { 1328 | case Nil if (a.length > 0) => 1329 | usage("Too many parameters.") 1330 | Nil 1331 | 1332 | case Nil => 1333 | Nil 1334 | 1335 | case (p: MultiValueParameter[_]) :: tail => { 1336 | if (a.length == 0) 1337 | checkMissing(paramSpecs) 1338 | else 1339 | a.foreach(s => p.setFromString(s)) 1340 | 1341 | parseNext(Nil, tail) 1342 | } 1343 | 1344 | case (p: SingleValueParameter[_]) :: tail => { 1345 | if (a.length == 0) 1346 | checkMissing(paramSpecs) 1347 | else 1348 | p.setFromString(a.take(1)(0)) 1349 | 1350 | parseNext(a drop 1, tail) 1351 | } 1352 | } 1353 | } 1354 | 1355 | parseNext(a, parameters.toList) 1356 | } 1357 | 1358 | private def paddedList(l: List[String], total: Int): List[String] = { 1359 | if (l.length >= total) 1360 | l 1361 | else 1362 | l ::: (1 to (total - l.length)).map(i => "").toList 1363 | } 1364 | 1365 | private def parseCompressedShortOpt(optString: String, 1366 | optName: String, 1367 | a: List[String]): 1368 | List[String] = { 1369 | assert(optName.length > 1) 1370 | val (name, rest) = (optName take 1, optName drop 1) 1371 | assert(rest.length > 0) 1372 | val opt = shortNameMap.getOrElse( 1373 | name(0), usage("Unknown option: " + optString) 1374 | ) 1375 | 1376 | val result = opt match { 1377 | case o: HasValue[_] => 1378 | if (rest.length == 0) 1379 | usage("Option -" + name + " requires a value.") 1380 | o.setFromString(rest) 1381 | a drop 1 1382 | 1383 | case o: FlagOption[_] => 1384 | // It's a flag. thus, the remainder of the option string 1385 | // consists of the next set of options (e.g., -cvf) 1386 | o.setByName(name) 1387 | List("-" + rest) ::: (a drop 1) 1388 | 1389 | case _ => 1390 | throw new ArgotException("(BUG) Found " + opt.getClass + 1391 | " in shortNameMap") 1392 | } 1393 | 1394 | result 1395 | } 1396 | 1397 | private def parseRegularShortOpt(optString: String, 1398 | optName: String, 1399 | a: List[String]): 1400 | List[String] = { 1401 | assert(optName.length == 1) 1402 | val opt = shortNameMap.getOrElse( 1403 | optName(0), usage("Unknown option: " + optString) 1404 | ) 1405 | 1406 | val a2 = a drop 1 1407 | 1408 | val result = opt match { 1409 | case o: HasValue[_] => 1410 | if (a2.length == 0) 1411 | usage("Option " + optString + " requires a value.") 1412 | o.setFromString(a2(0)) 1413 | a2 drop 1 1414 | 1415 | case o: FlagOption[_] => 1416 | o.setByName(optName) 1417 | a2 1418 | 1419 | case _ => 1420 | throw new ArgotException("(BUG) Found " + opt.getClass + 1421 | " in longNameMap") 1422 | } 1423 | 1424 | result 1425 | } 1426 | 1427 | private def parseShortOpt(a: List[String]): List[String] = { 1428 | val optString = a.take(1)(0) 1429 | assert(optString startsWith "-") 1430 | val optName = optString drop 1 1431 | 1432 | optName.length match { 1433 | case 0 => usage("Missing option name in \"" + optString + "\"") 1434 | case 1 => parseRegularShortOpt(optString, optName, a) 1435 | case _ => parseCompressedShortOpt(optString, optName, a) 1436 | } 1437 | } 1438 | 1439 | def parseLongOpt(a: List[String]): List[String] = { 1440 | val optString :: rest = a 1441 | assert(optString startsWith "--") 1442 | val optName = optString drop 2 takeWhile (_ != '=') 1443 | val opt = longNameMap.getOrElse( 1444 | optName, usage("Unknown option: " + optString) 1445 | ) 1446 | 1447 | val gnuPrefix = "--" + optName + "=" 1448 | 1449 | def fail() = { 1450 | throw new ArgotException("(BUG) Found " + opt.getClass + 1451 | " in longNameMap") 1452 | } 1453 | 1454 | if (optString.startsWith(gnuPrefix)) { 1455 | opt match { 1456 | case o: HasValue[_] => 1457 | o.setFromString(optString.stripPrefix(gnuPrefix)) 1458 | rest 1459 | 1460 | case o: FlagOption[_] => 1461 | usage("Option " + optString + " does not take a value.") 1462 | 1463 | case _ => 1464 | fail() 1465 | } 1466 | 1467 | } else { 1468 | opt match { 1469 | case o: HasValue[_] => 1470 | if (rest.length == 0) 1471 | usage("Option " + optString + " requires a value.") 1472 | o.setFromString(rest(0)) 1473 | rest drop 1 1474 | 1475 | case o: FlagOption[_] => 1476 | o.setByName(optName) 1477 | rest 1478 | 1479 | case _ => 1480 | fail() 1481 | } 1482 | } 1483 | } 1484 | 1485 | @tailrec private def parseArgList(a: List[String]): Unit = { 1486 | a match { 1487 | case Nil => 1488 | parseParams(Nil) 1489 | 1490 | case "--" :: tail => 1491 | parseParams(tail) 1492 | 1493 | case opt :: tail if (opt.startsWith("--")) => 1494 | parseArgList(parseLongOpt(a)) 1495 | 1496 | case opt :: tail if (opt(0) == '-') => 1497 | parseArgList(parseShortOpt(a)) 1498 | 1499 | case _ => 1500 | parseParams(a) 1501 | } 1502 | } 1503 | 1504 | private def checkOptionName(name: String) = { 1505 | name.toList match { 1506 | case '-' :: tail => 1507 | throw new ArgotSpecificationError( 1508 | "Option name \"" + name + "\" must not start with \"-\"." 1509 | ) 1510 | case Nil => 1511 | throw new ArgotSpecificationError("Empty option name.") 1512 | 1513 | case _ => 1514 | } 1515 | } 1516 | 1517 | private def checkForMultiParam(param: Parameter[_]) = { 1518 | if (parameters.size > 0) { 1519 | parameters.last match { 1520 | case p: MultiValueParameter[_] => 1521 | throw new ArgotSpecificationError( 1522 | "Multi-parameter \"" + p.name + "\" must be the last " + 1523 | "parameter in the specification." 1524 | ) 1525 | 1526 | case _ => 1527 | } 1528 | } 1529 | } 1530 | 1531 | private def checkOptionalStatus(param: Parameter[_], 1532 | optionalSpec: Boolean) = { 1533 | if (parameters.size > 0) { 1534 | if (parameters.last.optional && (! optionalSpec)) 1535 | throw new ArgotSpecificationError( 1536 | "Optional parameter \"" + parameters.last.name + 1537 | "\" cannot be followed by required parameter \"" + 1538 | param.valueName + "\"") 1539 | } 1540 | } 1541 | } 1542 | --------------------------------------------------------------------------------