Coder Social home page Coder Social logo

xenomachina / kotlin-argparser Goto Github PK

View Code? Open in Web Editor NEW
478.0 11.0 33.0 516 KB

Easy to use and concise yet powerful and robust command line argument parsing for Kotlin

License: GNU Lesser General Public License v2.1

Kotlin 72.50% Shell 27.50%
kotlin kotlin-library command-line-parser argument-parser option-parser

kotlin-argparser's Introduction

Kotlin --argparser

Maven Central Build Status codebeat badge Awesome Kotlin Badge Javadocs License: LGPL 2.1

This is a library for parsing command-line arguments. It can parse both options and positional arguments. It aims to be easy to use and concise yet powerful and robust.

Overview

Defining options and positional arguments is as simple as:

import com.xenomachina.argparser.ArgParser

class MyArgs(parser: ArgParser) {
    val v by parser.flagging("enable verbose mode")

    val name by parser.storing("name of the user")

    val count by parser.storing("number of the widgets") { toInt() }

    val source by parser.positional("source filename")

    val destination by parser.positional("destination filename")
}

An instance of MyArgs will represent the set of parsed arguments. Each option and positional argument is declared as a property that delegates through a delegate factory method on an instance of ArgParser.

The name of an option is inferred from the name of the property it is bound to. The options above are named -v, --name and --count, respectively. There are also two positional arguments.

Direct control over an option's name is also possible, and for most types of options it is also possible to have multiple names, like a short and long name:

class MyArgs(parser: ArgParser) {
    val verbose by parser.flagging(
        "-v", "--verbose",
        help = "enable verbose mode")

    val name by parser.storing(
        "-N", "--name",
        help = "name of the user")

    val count by parser.storing(
        "-c", "--count",
        help = "number of widgets") { toInt() }

    val source by parser.positional(
        "SOURCE",
        help = "source filename")

    val destination by parser.positional(
        "DEST",
        help = "destination filename")
}

The unparsed command-line arguments are passed to the ArgParser instance at construction:

fun main(args: Array<String>) = mainBody {
    ArgParser(args).parseInto(::MyArgs).run {
        println("Hello, ${name}!")
        println("I'm going to move ${count} widgets from ${source} to ${destination}.")
        // TODO: move widgets
    }
}

See kotlin-argparser-example for a complete example project.

Nomenclature

Options, arguments, flags... what's the difference?

An application's main function is passed an array of strings. These are the unparsed command-line arguments, or unparsed arguments for short.

The unparsed arguments can then be parsed into options, which start with a hyphen ("-"), and positional arguments. For example, in the command ls -l /tmp/, the unparsed arguments would be "-l", "/tmp" where -l is an option, while /tmp/ is a positional argument.

Options can also have option arguments. In the command ls --time-style=iso, the option is --time-style and that options argument is iso. Note that in parsing a single unparsed argument can be split into an option and an option argument, or even into multiple options in some cases.

A flag is a boolean option which has no arguments and which is false if not provided, but true if provided. The -l option of ls is a flag.

Option Types

Boolean Flags

Boolean flags are created by asking the parser for a flagging delegate. One or more option names, may be provided:

val verbose by parser.flagging("-v", "--verbose",
                               help = "enable verbose mode")

Here the presence of either -v or --verbose options in the arguments will cause the Boolean property verbose to be true, otherwise it will be false.

Storing a Single Argument

Single argument options are created by asking the parser for a storing delegate.

val name by parser.storing("-N", "--name",
                           help = "name of the user")

Here either -N or --name with an argument will cause the name property to have that argument as its value.

A function can also be supplied to transform the argument into the desired type. Here the size property will be an Int rather than a String:

val size by parser.storing("-c", "--count",
                           help = "number of widgets") { toInt() }

Adding to a Collection

Options that add to a Collection each time they appear in the arguments are created with using the adding delegate. Just like storing delegates, a transform function may optionally be supplied:

val includeDirs by parser.adding(
        "-I", help = "directory to search for header files") { File(this) }

Now each time the -I option appears, its transformed argument is appended to includeDirs.

Mapping from an option to a fixed value

For choosing between a fixed set of values (typically, but not necessarily, from an enum), a mapping delegate can be used:

val mode by parser.mapping(
        "--fast" to Mode.FAST,
        "--small" to Mode.SMALL,
        "--quiet" to Mode.QUIET,
        help = "mode of operation")

Here the mode property will be set to the corresponding ArgParser.Mode value depending on which of --fast, --small, and --quiet appears (last) in the arguments.

mapping is one of the few cases where it is not possible to infer the option name from the property name.

More advanced options

For all other types of options, the option method should be used. The methods mentioned above are, in fact, convenience methods built on top of the option method.

For example, it is possible to create an option that has multiple arguments:

  fun ArgParser.putting(vararg names: String, help: String) =
          option<MutableMap<String, String>>(*names,
                  argNames = listOf("KEY", "VALUE"),
                  help = help) {
              value.orElse { mutableMapOf<String, String>() }.apply {
                  put(arguments.first(), arguments.last()) }
          }

Note that the option method does not have an auto-naming overload. If you need this capability, create a DelegateProvider that creates your Delegate:

  fun ArgParser.putting(help: String) =
          ArgParser.DelegateProvider { identifier ->
              putting(identifierToOptionName(identifier), help = help) }

Positional Arguments

Positional arguments are collected by using the positional and positionalList methods.

For a single positional argument:

val destination by parser.positional("destination filename")

An explicit name may also be specified:

val destination by parser.positional("DEST",
                                     help = "destination filename")

The name ("DEST", here) is used in error handling and help text.

For a list of positional arguments:

val sources by parser.positionalList("SOURCE", 1..Int.MAX_VALUE,
                                     help = "source filename")

The range indicates how many arguments should be collected, and defaults to the value shown in this example. As the name suggests, the resulting property will be a List.

Both of these methods accept an optional transform function for converting arguments from String to whatever type is actually desired:

val destination by parser.positional("DEST",
                                     help = "...") { File(this) }

val sources by parser.positionalList("SOURCE", 1..Int.MAX_VALUE,
                                     help = "...") { File(this) }

Modifying Delegates

The delegates returned by any of these methods also have a few methods for setting optional attributes:

Adding a Default Value

Certain types of delegates (notably storing, mapping, and positional) have no default value, and hence will be required options unless a default value is provided. This is done with the default method:

val name by parser.storing("-N", "--name", help = "...").default("John Doe")

Note that it is possible to use null for the default, though this may require specifying the type parameter for default explicitly:

val name by parser.storing("-N", "--name", help = "...").default<String?>(null)

The type of the resulting property be nullable (a String? in this case).

Adding a Validator

Sometimes it's easier to validate an option at the end of parsing, in which case the addValidator method can be used.

val percentages by parser.adding("--percentages", help = "...") { toInt() }
        .addValidator {
              if (value.sum() != 100)
                  throw InvalidArgumentException(
                          "Percentages must add up to 100%")
        }

Error Handling

If the parser determines that execution should not continue it will throw a SystemExitException which has a status code appropriate for passing to exitProcess as well as a message for the user.

These exceptions can be caused by user error, or even if the user requests help (eg: via the --help option).

It is recommended that transform functions (given to storing, positionalList, etc.) and post-parsing validation, including that performed via, addValidator also throw a SystemExitException on failure.

As a convenience, these exceptions can be handled by using the mainBody function:

class ParsedArgs(parser: ArgParser) {
    val name by positional("The user's name").default("world")
}

fun main(args: Array<String>) = mainBody {
    ArgParser(args).parseInto(::ParsedArgs).run {
        println("Hello, {name}!")
    }
}

Parsing

Parsing of command-line arguments is performed sequentially. So long as option-processing is enabled, each not-yet-processed command-line argument that starts with a hyphen (-) is treated as an option.

Short Options

Short options start with a single hyphen. If the option takes an argument, the argument can either be appended:

# "-o" with argument "ARGUMENT"
my_program -oARGUMENT

or can be the following command-line argument:

# "-o" with argument "ARGUMENT"
my_program -o ARGUMENT

Zero argument short options can also be appended to each other without intermediate hyphens:

# "-x", "-y" and "-z" options
my_program -xyz

An option that accepts arguments is also allowed at the end of such a chain:

# "-x", "-y" and "-z" options, with argument for "-z"
my_program -xyzARGUMENT

Long Options

Long options start with a double hyphen (--). An argument to a long option can either be delimited with an equal sign (=):

# "--foo" with argument "ARGUMENT"
my_program --foo=ARGUMENT

or can be the following command-line argument:

# "--foo" with argument "ARGUMENT"
my_program --foo ARGUMENT

Multi-argument Options

Multi-argument options are supported, though currently not by any of the convenience methods. Option-arguments after the first must be separate command-line arguments, for both an long and short forms of an option.

Positional Arguments

In GNU mode (the default), options can be interspersed with positional arguments, but in POSIX mode the first positional argument that is encountered disables option processing for the remaining arguments. In either mode, if the argument "--" is encountered while option processing is enabled, then option processing is disabled for the rest of the command-line. Once the options and option-arguments have been eliminated, what remains are considered to be positional arguments.

Each positional argument delegate can specify a minimum and maximum number of arguments it is willing to collect.

The positional arguments are distributed to the delegates by allocating each positional delegate at least as many arguments as it requires. If more than the minimum number of positional arguments have been supplied then additional arguments will be allocated to the first delegate up to its maximum, then the second, and so on, until all arguments have been allocated to a delegate.

This makes it easy to create a program that behaves like grep:

class Args(parser: ArgParser) {
    // accept 1 regex followed by n filenames
    val regex by parser.positional("REGEX",
            help = "regular expression to search for")
    val files by parser.positionalList("FILE",
            help = "file to search in")
}

And equally easy to create a program that behaves like cp:

class Args(parser: ArgParser) {
    // accept n source files followed by 1 destination
    val sources by parser.positionalList("SOURCE",
            help = "source file")
    val destination by parser.positional("DEST",
            help = "destination file")
}

Forcing Parsing

Parsing normally does not begin until a delegate's value is accessed. Sometimes this is not desirable, so it is possible to enforce the parsing of arguments into a class of values. This ensures that all arguments that are required are provided, and all arguments provided are consumed.

Forcing can be done in a separate step using the force method:

val parser = ArgParser(args)
val parsedArgs = ParsedArgs(parser)
parser.force()
// now you can use parsedArgs

Alternatively, forcing can be done inline via the parseInto method:

val parsedArgs = ArgParser(args).parseInto(::ParsedArgs)
// now you can use parsedArgs

In both cases exceptions will be thrown where parsing or validation errors are found.

Help Formatting

By default, ArgParser will add a --help option (short name -h) for displaying usage information. If this option is present a ShowHelpException will be thrown. If the default exception handling is being used (see Error Handling) the program will halt and print a help message like the one below, based on the ArgParser configuration:

usage: program_name [-h] [-n] [-I INCLUDE]... -o OUTPUT
                    [-v]... SOURCE... DEST


This is the prologue. Lorem ipsum dolor sit amet, consectetur
adipiscing elit. Aliquam malesuada maximus eros. Fusce
luctus risus eget quam consectetur, eu auctor est
ullamcorper. Maecenas eget suscipit dui, sed sodales erat.
Phasellus.


required arguments:
  -o OUTPUT,          directory in which all output should
  --output OUTPUT     be generated


optional arguments:
  -h, --help          show this help message and exit

  -n, --dry-run       don't do anything

  -I INCLUDE,         search in this directory for header
  --include INCLUDE   files

  -v, --verbose       increase verbosity


positional arguments:
  SOURCE              source file

  DEST                destination file


This is the epilogue. Lorem ipsum dolor sit amet,
consectetur adipiscing elit. Donec vel tortor nunc. Sed eu
massa sed turpis auctor faucibus. Donec vel pellentesque
tortor. Ut ultrices tempus lectus fermentum vestibulum.
Phasellus.

The creation of the --help option can be disabled by passing null as the helpFormatter when constructing the ArgParser, or configured by manually constructing a HelpFormatter instance. In the above example a DefaultHelpFormatter was created with the prologue and epilogue.

Caveats

  • This library should be considered to be very beta. While there are no plans to make any breaking changes to the API, it's possible that there may be some until it is mature.

  • Upon reading the value any of the delegated properties created by an ArgParser, the arguments used to construct that ArgParser will be parsed. This means it's important that you don't attempt to create delegates on an ArgParser after any of its existing delegated properties have been read. Attempting to do so will cause an IllegalStateException. It would be nice if Kotlin had facilities for doing some of the work of ArgParser at compile time rather than run time, but so far the run time errors seem to be reasonably easy to avoid.

Configuring Your Build

Kotlin-argparser binaries are hosted on Maven Central and also Bintray's JCenter.

In Gradle, add something like this in your build.gradle:

// you probably already have this part
buildscript {
    repositories {
        mavenCentral() // or jcenter()
    }
}

dependencies {
    compile "com.xenomachina:kotlin-argparser:$kotlin_argparser_version"
}

In Maven add something like this to your pom.xml:

<dependency>
    <groupId>com.xenomachina</groupId>
    <artifactId>kotlin-argparser</artifactId>
    <version>VERSION</version>
</dependency>

Information on setting up other build systems, as well as the current version number, can be found on MVN Repository's page for Kotlin-argparser.

Thanks

Thanks to the creators of Python's argparse module, which provided the initial inspiration for this library.

Thanks also to the team behind Kotlin.

Finally, thanks to all of the people who have contributed code and/or issues.

kotlin-argparser's People

Contributors

colinhebert avatar konfilios avatar shanethehat avatar tgockel avatar wasabi375 avatar xenomachina avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

kotlin-argparser's Issues

Document package name and import

The docs and examples do not include any reference to what imports or package is used.
they should have a reference to

import com.xenomachina.argparser.* ;

( no its not entirely obvious without scraping the code )

Add a combination of adding/mapping arguments

Not sure whether it's something that should be part of the core of argparser but I came across a usecase which consists of multiple arguments (flags) being collected to obtain a set of switches (and allowing this set of switches to be validated).

I monkey patched it as this:

fun <E, T : MutableCollection<E>> ArgParser.addMapping(
        map: Map<String, E>,
        help: String,
        initialValue: T): ArgParser.Delegate<T> {
    val names = map.keys.toTypedArray()
    return option<T>(*names,
            errorName = map.keys.joinToString("|"),
            help = help,
            isRepeating = true) {
        val result = value.orElse { initialValue }
        result.add(map.getValue(optionName))
        result
    }.default(initialValue)
}

fun <E> ArgParser.addMapping(map: Map<String, E>, help: String): ArgParser.Delegate<MutableCollection<E>> =
        addMapping(map, help, mutableSetOf())

fun <E, T : MutableCollection<E>> ArgParser.addMapping(vararg pairs: Pair<String, E>, help: String, initialValue: T): ArgParser.Delegate<T> =
        addMapping(mapOf(*pairs), help, initialValue)

fun <E> ArgParser.addMapping(vararg pairs: Pair<String, E>, help: String): ArgParser.Delegate<MutableCollection<E>> =
        addMapping(mapOf(*pairs), help, mutableSetOf())

Here's an example of the usage:

    private val dumps: MutableCollection<DumpType> by commandParser.addMapping(
            "--heap" to DumpType.HEAP_DUMP,
            "--thread" to DumpType.THREAD_DUMP,
            "--database" to DumpType.THREAD_DUMP, help = "Type of dump to process (more than one type is allowed)")
            .addValidator { if (value.isEmpty()) throw InvalidArgumentException("At least one dump type must be selected") }

What do you think?

Incorrect line breaking for line with many words

I have issue in displaying prologue for --help command. Function wrapText breaks my lines in wrong way.

    fun testPrologue2() {
        val actual = """
            |Line with many words. Many many many words. More words. More and more.
        |AA BB  .. CC
        |DD EE  .. FF
        |...........
        |F  G  .. H
        """.trimMargin().wrapText(80)

        val expected = "Line with many words. Many many many words. More words. More and more.\n" +
                "AA BB  .. CC\n" +
                "DD EE  .. FF\n" +
                "...........\n" +
                "F  G  .. H"

        assertEquals(expected, actual)
    }```

It returns 
```Line with many words. Many many many words. More words. More and more.
AA BB ..
CC
DD EE .. FF
...........
F G .. H```

Use provideDelegate operator to automatically pick long option name if no names are provided

Kotlin 1.1 introduces the provideDelegate operator which makes it possible to discover the property name once the delegate is bound.

Instead of disallowing delegates with no name, this check could be performed in provideDelegate, and if no names were set at construction, use the property name to construct a long option name.

This would make it possible to do something like:

val exclude by storing(help="thing to exclude")

as an equivalent to:

val exclude by storing("--exclude", help="thing to exclude")

.addValidator after .default removes default setting

The following code

private val x by parser.storing(
		"-x",
		help = "",
		transform = String::toInt
).addValidator {
	// Validate here
}.default(0)

receives the default value of 0 and gets labeled as optional, while

private val x by parser.storing(
		"-x",
		help = "",
		transform = String::toInt
).default(0).addValidator {
	// Validate here
}

does not, and gets labeled as required parameter.

Cross referencing arguments cause StackOverflowError

When cross referencing arguments, it will cause a StackOverflowError.

I have the following Args class:

class Args(parser: ArgParser) {
    val caseInsensitive by parser.flagging("-c", "--case_insensitive",
            help = "Help")

    val includedExtensions by parser.adding("-e", "--include_ext",
            help = "Help")
        { extensionCheckCaseInsensitive() }.default(Collections.emptyList())

    private fun String.extensionCheckCaseInsensitive() =
            if (caseInsensitive) this.toLowerCase() else this
}

The includedExtensions needs to check the other argument caseInsensitive in order to format the extensions. This could be done after the parsing is done, but it is more convenient to do as part of the parser's formatting.

The parser is called like this in the main function:

val parsedArgs: Args
try {
    parsedArgs = ArgParser(args).parseInto(::Args)
} catch (e: SystemExitException) {
    e.printAndExit()
}

When running the above code, it will produce a StackOverflowError, which I did not expect:

Exception in thread "main" java.lang.StackOverflowError
	at java.lang.String.<init>(String.java:207)
	at java.lang.StringBuilder.toString(StringBuilder.java:407)
	at com.xenomachina.argparser.ArgParser.parseShortOpts(ArgParser.kt:579)
	at com.xenomachina.argparser.ArgParser.access$parseShortOpts(ArgParser.kt:38)
	at com.xenomachina.argparser.ArgParser$parseOptions$2.invoke(ArgParser.kt:492)
	at com.xenomachina.argparser.ArgParser$parseOptions$2.invoke(ArgParser.kt:38)
	at kotlin.SynchronizedLazyImpl.getValue(Lazy.kt:131)
	at com.xenomachina.argparser.ArgParser.getParseOptions(ArgParser.kt)
	at com.xenomachina.argparser.ArgParser.force(ArgParser.kt:446)
	at com.xenomachina.argparser.DefaultKt$default$3.getValue(Default.kt:73)
	at com.xenomachina.argparser.ArgParser$Delegate.getValue(ArgParser.kt:340)
	at com.myPackage.Args.getCaseInsensitive(Args.kt)
	at com.myPackage.Args.extensionCheckCaseInsensitive(Args.kt:55)
	at com.myPackage.Args.access$extensionCheckCaseInsensitive(Args.kt:46)
	at com.myPackage.Args$includedExtensions$2.invoke(Args.kt:52)
	at com.myPackage.Args$includedExtensions$2.invoke(Args.kt:46)
	at com.xenomachina.argparser.ArgParser$adding$1.invoke(ArgParser.kt:135)
	at com.xenomachina.argparser.ArgParser$adding$1.invoke(ArgParser.kt:38)
	at com.xenomachina.argparser.OptionDelegate.parseOption(OptionDelegate.kt:60)
	at com.xenomachina.argparser.ArgParser.parseShortOpts(ArgParser.kt:587)
	at com.xenomachina.argparser.ArgParser.access$parseShortOpts(ArgParser.kt:38)
	at com.xenomachina.argparser.ArgParser$parseOptions$2.invoke(ArgParser.kt:492)
	at com.xenomachina.argparser.ArgParser$parseOptions$2.invoke(ArgParser.kt:38)
	at kotlin.SynchronizedLazyImpl.getValue(Lazy.kt:131)
	at com.xenomachina.argparser.ArgParser.getParseOptions(ArgParser.kt)
	at com.xenomachina.argparser.ArgParser.force(ArgParser.kt:446)
	at com.xenomachina.argparser.DefaultKt$default$3.getValue(Default.kt:73)
	at com.xenomachina.argparser.ArgParser$Delegate.getValue(ArgParser.kt:340)
	at com.myPackage.Args.getCaseInsensitive(Args.kt)
	at com.myPackage.Args.extensionCheckCaseInsensitive(Args.kt:55)
	at com.myPackage.Args.access$extensionCheckCaseInsensitive(Args.kt:46)
	at com.myPackage.Args$includedExtensions$2.invoke(Args.kt:52)
	at com.myPackage.Args$includedExtensions$2.invoke(Args.kt:46)
...

Am I doing something wrong, or is this a bug?

Allow omitting mandatory parameters

If you have:
val configFile by parser.storing("--config", "-c", argName = "CONFIGFILE", help = "Configuration file") { asPath }
val version by parser.flagging("--version", help = "print version and exit")

You cannot specify only "--version" in command line - it will complain about "missing CONFIGFILE"

We need an option for allowing printing version.

Alternatively allow multiple configurations:
usage: --config CONFIGFILE
usage: --version

Maven not downloading .jar from bintray

I've added the Bintray maven dependency but the argparse libraries are not in my path... only the xenocom ones are (xenomachina.text and .common)

I'm not a Maven expert, but I had a look at the .pom on Bintray -- I see the Xenocom dependency in there -- maybe there's something misconfigured or not pointing to the argparse jar?

Add support for lazy default

Add way to pass lambda to default for lazy initialization of default. like:

val myOpt by parser.storing("--foo", help = "bar")
    .default { "Do $crazy stuff here, only if there is no myOpt" }

Add support for inlining arguments, possibly from a file

It would be nice to be able to be able to enable the processing of arguments from files. For example/ something like:

myProgram --foo -@bar --quux

might read arguments from the file bar, so that if bar contained:

--hello world
--so-long "thanks for all the fish"

this would behave as if the arguments were:

myProgram --foo --hello world --so-long "thanks for all the fish" --quux

(with the exception that an option or positional argument list cannot be partly in the file and partly outside of it)

Allow '.' as Separator in Options and Arguments

The paradigm used for "advanced" options at my company uses namespace-qualified option names on the command line. A la:

java -jar my-program.jar --com.foo.bar=100 --com.foo.baz=taco

Specifying these as options will always throw IllegalArgumentException, as this does not pass the OPTION_NAME_RE pattern in ArgParser.kt. Is there any reason to prevent using '.' as part of an option or argument (sibling to the '_' and '-' characters)?

Add support for subparsers

Python's argparse has the useful ability to specify subparsers. Would you be willing to add similar functionality here?

The API might look something like this:

fun <T> ArgParser.subparser(help: String, factory: (ArgParser) -> T): ArgParser.Delegate<T?> {
    ...
}

class SubparserOne(parser: ArgParser) {
    val argOne by parser.flagging(help = "help")
}

class SubparserTwo(parser: ArgParser) {
    val argTwo by parser.flagging(help = "help")
}

class MyArgParser(parser: ArgParser) {
    val foo: Boolean by parser.flagging(help = "foo")
    val commandOne: SubparserOne? by parser.subparser(help = "sub command") { SubparserOne(it) }
    val commandTwo: SubparserTwo? by parser.subparser(help = "sub command") { SubparserTwo(it) }
}

If the sub command for a subparser is given on the command line, then the factory would be called. The delegates on the subparser would then handle the rest of the command line arguments. If a subparser isn't called, then it's delegate would be null.

This doesn't enforce that the object returned by the factory actually declares and delegates. I don't think that's a big deal, because you can create subparsers without arguments in python as well.

Default values with null not working

When I try to define an argument like this:
val myVal by parser.storing(help = "Help").default(null)
I get an error:
Null can not be a value of a non-null type () -> String even if I explicitly define it as String?

I use version 2.0.4

Lazy parsing

Hi!
I want to parse my args lazily so when i access arg via delegate, parser should parse only this one exact arg. Currently parser run force() method on first delegate access, so all args are being read. Is it possible to disable this behavior and parse 1 arg per delegate access?

Spaces should not be removed for --help prologue message

I want display table like message with several nested spaces for indentaion, e.g.

AA BB  .. CC
DD EE  .. FF
...........
F  G   .. H

But multiple spaces are removed - only one is printed. I get ugly table like this

AA BB .. CC
DD EE .. FF
...........
F G .. H
 @Test
    fun testPrologue() {
        val actual = "D  F".trimMargin().wrapText(80)
        assertEquals("D  F", actual)
    }

Add support for sub-commands

Sub commands (as used by git or p4, for example) would be nice:

myCommand --foo --bar subcommand --baz --quux

The arguments after the subcommand should be disjoint from those of the main command.

Can someone provide an example of an optional argument?

How would one go about adding optional arguments.

package platform.args

import com.xenomachina.argparser.ArgParser

class ArgsProcess(parser: ArgParser) {

    val data by parser.storing("-s", "--settings",
            help = "path to the settings file")

    val version by parser.flagging("-v", "--version",
            help="Prints out the version of the program")
}

My program is currently crashing out if I don't specify the settings file. Sorry, I have read the read me but I might have missed this or misunderstood. It looks as though you do support optional arguments, but I don't understand how they are declared as such.

add usage on readme

i am want to use but no any where can found the maven address
i am read the publish task of build.gradle so turn to search jcenter

lucky ...
compile 'com.xenomachina:kotlin-argparser:1.1.0'

0 columns does weird wrapping

When not setting the columns value, the help output is weirdly formatted, since it behaves as if you try to wrap with a very small value, instead of no wrap as the javadoc indicates.

Sample code

class Configuration(parser: ArgParser) {
    val test by parser.storing(help = "lol test")
    init {
        parser.force()
    }
}
fun main(args: Array<String>) = mainBody(columns = 0) {
    Configuration(ArgParser(args))
}

Output:

usage: 
        
        [
        -
        h
        ]
        
        -
        -
        t
        e
        s
        t
        
        T
        E
        S
        T

required arguments:
     
  -  l
  -  o
  t  l
  e  
  s  t
  t  e
     s
  T  t
  E
  S
  T


optional arguments:
     
  -  s
  h  h
  ,  o
     w
  -  
  -  t
  h  h
  e  i
  l  s
  p  
     h
     e
     l
     p
     
     m
     e
     s
     s
     a
     g
     e
     
     a
     n
     d
     
     e
     x
     i
     t

This has been obtained with kotlin 1.1.2-2 on jre 8.

Usable example?

Can you add a full, usable example to either the docs or the repo?

ie: something that is a complete program that can be run.

Thanks

Cannot create non-greedy option arguments, like `--color` option of GNU ls

The --help for GNU ls looks like:

      --color[=WHEN]         colorize the output; WHEN can be 'always' (default
                               if omitted), 'auto', or 'never'; more info below

You can say:

  • ls --color=never /tmp ← color has argument "never"
  • ls --color /tmp ← color has no argument, defaults to "auto"
  • ls --color never /tmp ← color has no argument, defaults to "auto" and "never" is treated as a positional argument

So the --color option has an optional argument, which it will only consume if it is in the same command-line argument.

With OptionArgumentIterator there is no way distinguish between --color=never and --color never, so it is currently impossible to simulate the behavior of this option.

Suggested fix: add a property to OptionArgumentIterator to indicate whether the first argument is "mandatory". A "storingWithDefault" convenience method could then be created that would consume an argument iff it is mandatory.

Hierarchical arguments

Hierarchical arguments would make it possible to plug together different
modules on the command-line.

Usage might look something like:

    // Arguments for some module, defined just like program args are defined
    // now:
    class WidgetArgs(parser: ArgParser) {
        val name by storing("name of the widget")
        val fast by flagging("makes widget faster")
    }

    // Arguments for your program:
    class Args(parser: ArgParser) {
        ...

        val widget by compositing(::WidgetArgs, "widget")
    }

This would effectively create two options:

    --widget.name=WIDGET.NAME    name of the widget
    --widget.fast                makes widget faster

It would also be possivle to use the same args constructor multiple times:

    // Arguments for a different program:
    class Args(parser: ArgParser) {
        ...

        val source by compositing(::WidgetArgs, "source widget")

        val destination by compositing(::WidgetArgs, "destination widget")
    }

This would effectively create four options:

    --source.name=WIDGET.NAME         name of the widget
    --source.fast                     makes widget faster
    --destination.name=WIDGET.NAME    name of the widget
    --destination.fast                makes widget faster

Some things to figure out:

  • How should help formatting look? Maybe just refer to "[source widget options]" in
    summary, and then have a section for them?

  • Is "compositing" the best name for this? Maybe "including" is more
    straigtforward?

  • The sub-constructor should take an OptionParser, not an ArgParser, which
    doesn't exist yet. (I currently have an implementation of this in an
    experimental branch, as part of the sub-command implementation.)

  • Should there be some sort of "array" mechanism, where a variable number of
    widgets could be specified somehow? This can probably wait for a separate
    feature, but don't want to paint ourselves into a corner.

Make mainBody inline

If the mainBody function was inline, it would be possible to return from the body function and then end the program execution.

ArgParser throws exceptions on --help and missing arguments

As the title says, ArgParser throws exceptions when requesting help and also when arguments are missing.
I'm running the compiled jar with the help command as follows:
java -cp /path/to/myproject.jar org.company.project.Main --help

And I'm getting the following exception:

Exception in thread "main" com.xenomachina.argparser.ShowHelpException: Help was requested
        at com.xenomachina.argparser.ArgParser$1.invoke(ArgParser.kt:590)
        at com.xenomachina.argparser.ArgParser$1.invoke(ArgParser.kt:38)
        at com.xenomachina.argparser.OptionDelegate.parseOption(OptionDelegate.kt:60)
        at com.xenomachina.argparser.ArgParser.parseLongOpt(ArgParser.kt:549)
        at com.xenomachina.argparser.ArgParser.access$parseLongOpt(ArgParser.kt:38)
        at com.xenomachina.argparser.ArgParser$parseOptions$2.invoke(ArgParser.kt:473)
        at com.xenomachina.argparser.ArgParser$parseOptions$2.invoke(ArgParser.kt:38)
        at kotlin.SynchronizedLazyImpl.getValue(Lazy.kt:130)
        at com.xenomachina.argparser.ArgParser.getParseOptions(ArgParser.kt)
        at com.xenomachina.argparser.ArgParser.force(ArgParser.kt:447)
        at com.xenomachina.argparser.ParsingDelegate.getValue(ParsingDelegate.kt:39)
        at com.xenomachina.argparser.ArgParser$Delegate.getValue(ArgParser.kt:340)
        at org.company.project.util.CommandLineArguments.getName(CommandLineArguments.kt)
        at org.company.project.Main.main(Main.kt:20)

This is my project structure:

.
└── org
    └── company
        ├── project
            ├── Main.kt
            └── util
                └── CommandLineArguments.kt

Is this expected behavior? I expect that it would show the help instructions, not an exception.

Bind truly-optional nullable properties

I've been using sentinel values and default() to accomplish this, but it's gets awkward when using custom types. I would really like something like optional() which turned the property into a having a nullable type.

Help message bug

Assume you have following configuration:

val configFile by parser.storing("--config", "-c", argName = "CONFIGFILE", help = "Configuration file") { asPath }
val version by parser.flagging("--version", help = "print version and exit")

Invoking command like
script --config filename --help
will print:

usage: script [-h] [--config CONFIGFILE] [--version]

optional arguments:
  -h, --help             show this help message and exit

  --config CONFIGFILE,   Excel file with MLM configuration
  -c CONFIGFILE

  --version              print version and exit

As you can see "--config" is under optional arguments. It must be under mandatory ones.

If you execute:
script --help
The result is correct (config is under required):

usage: script [-h] --config CONFIGFILE [--version]

required arguments:
  --config CONFIGFILE,   Excel file with MLM configuration
  -c CONFIGFILE


optional arguments:
  -h, --help             show this help message and exit

  --version              print version and exit

parser.adding(...).default(...) does not work

As parser.adding(...) creates default delegate by itself (ArgParser.kt:137), nested default delegates are confused as to whose default value should be used, and it does not work.

Mechanism which combines 'adding' and 'positionalList' behavior

I want to parse a command which takes a series of key/value pairs. From my experiments, adding gives me the ability to accumulate multiple arguments and positionalList gives me the ability to consume the next two arguments after a flag. There doesn't appear to be a way to combine these so that I can accomplish something like the following:

./whatever --kv hey there --kv these are --kv multiple values

Which would somehow allow combining the positionalList and adding behavior into a List<List<String>>.

ShowHelpException not caught

Apologies if I'm misunderstanding something here, but from looking at the implementation it doesn't seem like anything is catching com.xenomachina.argparser.ShowHelpException: Help was requested and if I start my application with -h or --help, I see the exception stacktrace rather than the help text:

Exception in thread "main" com.xenomachina.argparser.ShowHelpException: Help was requested
        at com.xenomachina.argparser.ArgParser$1.invoke(ArgParser.kt:590)
        at com.xenomachina.argparser.ArgParser$1.invoke(ArgParser.kt:38)
        at com.xenomachina.argparser.OptionDelegate.parseOption(OptionDelegate.kt:60)
        at com.xenomachina.argparser.ArgParser.parseShortOpts(ArgParser.kt:576)
        at com.xenomachina.argparser.ArgParser.access$parseShortOpts(ArgParser.kt:38)
        at com.xenomachina.argparser.ArgParser$parseOptions$2.invoke(ArgParser.kt:473)
        at com.xenomachina.argparser.ArgParser$parseOptions$2.invoke(ArgParser.kt:38)
        at kotlin.SynchronizedLazyImpl.getValue(Lazy.kt:130)
        at com.xenomachina.argparser.ArgParser.getParseOptions(ArgParser.kt)
        at com.xenomachina.argparser.ArgParser.force(ArgParser.kt:447)
        at com.xenomachina.argparser.ParsingDelegate.getValue(ParsingDelegate.kt:39)
        at com.xenomachina.argparser.ArgParser$Delegate.getValue(ArgParser.kt:340)
        at myapp.TestRunnerArgs.getTests(testRunner.kt)

Is this expected? Do I need to catch this myself and delegate to the help formatter?

Support to print usage explicitly or on error.

On any SystemExitException except for --help there is no obvious way I could find to print the help text.
In general on error I'd like to print the help text.

The best I could do was to recursively call main("--help") then some ugly code to prevent infinte recursion.

snippet:


catch (h: SystemExitException)
  {
    var w = StringWriter()
    w.use {
      h.printUserMessage(it, progName="maildb-cli", columns=120)
    }
    System.out.println( w.toString() )
    if( h is ShowHelpException ) System.exit(0)
    main(arrayOf("--help"))
  }

Other libraries like JCommander, joptsimple etc provide some way to call the usage() method and/or some way to trigger it being displayed on an error.

The above code is embarrassingly ugly.
I attempted something slightly less ugly - to create a ShowHelpMessageException() but the constructor is internal, plus I don't have access to the parsed help values.

Default for parser arguments should be able to take a lambda

I have an argument like:

    val resourceLoader by parser.storing("-l", "--resource-loader", help = "The package name of the resource loader to use - a default, no-arg constructor must be present") {
        val javaClass = Class.forName(this)
        val loaderClass = javaClass.asSubclass(ResourceLoader::class.java)
        val ctor = loaderClass.getDeclaredConstructor()
        ctor.newInstance()!!
    }.default(DefaultResourceLoader())

It would be nice if I didn't have to instantiate that class if it is not necessary, ie:

    val resourceLoader by parser.storing("-l", "--resource-loader", help = "The package name of the resource loader to use - a default, no-arg constructor must be present") {
        val javaClass = Class.forName(this)
        val loaderClass = javaClass.asSubclass(ResourceLoader::class.java)
        val ctor = loaderClass.getDeclaredConstructor()
        ctor.newInstance()!!
    }.default { DefaultResourceLoader() }

Make it easier to block on parsing

To force parsing, one currently needs to do something like:

    val parser = ArgParser(args)
    val parsedArgs = ParsedArgs(parser)
    parser.force()
    // now you can use parsedArgs

It would be nice if the intermediate ArgParser didn't need to be named, so this could become a one-liner. Perhaps one could write something like:

    val parsedArgs = ArgParser(args).force(ParsedArgs)

This could be done by adding an optional parameter to force, or by creating a new method, hopefully with a better name.

Argument optional or mandatory depend on another argument

I would like to write a CLI that accept two different type of argument, and one argument needs another attribute. Let me show an example

// This command is going to show the info of the program, doesn't need any extra argument to show it
myProg -i

// This command is going to show the info of a transaction and need the transaction id
myProg --showTransactionInfo --transactionId 3

transactionId is not needed for the argument -i but it is mandatory for the argument showTransactionInfo

How could I code it?

Make it possible to emulate the `[<tree-ish>] [--] [<paths>...]` behavior of git-checkout

Part of git-checkout's usage looks like this:

git checkout [-p|--patch] [<tree-ish>] [--] [<paths>...]

The <tree-ish> and <paths> are both positional arguments. One can disambiguate between a
<tree-ish> and a <paths> argument by using --.

It might be useful to be able to emulate this behavior.

Implementing this might be as simple as adding a flag to ArgParser.argumentList (and argument) that disables parsing of the positional argument once -- appears.

NPE in constructor of DefaultHelpFormatter

Following line:

    var hf = DefaultHelpFormatter()

leads to a NullPointerException:

Exception in thread "main" kotlin.KotlinNullPointerException
	at kotlin.coroutines.experimental.SequenceBuilderIterator.yield(SequenceBuilder.kt:163)
	at com.xenomachina.text.TextKt$codePointSequence$1.doResume(Text.kt:33)
	at kotlin.coroutines.experimental.jvm.internal.CoroutineImpl.resume(CoroutineImpl.kt:54)
	at kotlin.coroutines.experimental.SequenceBuilderIterator.hasNext(SequenceBuilder.kt:128)
	at com.xenomachina.text.term.TextTermKt.codePointWidth(TextTerm.kt:180)
	at com.xenomachina.argparser.DefaultHelpFormatter.<init>(DefaultHelpFormatter.kt:66)
	at edu.kit.iti.formal.automation.rvt.HelloKt.main(Hello.kt:16)
  • kotlin-argparser version: com.xenomachina : kotlin-argparser : 2.0.1
    • version 2.0.0 also effected
    • version 1.1.0 not effected
  • Kotlin version: 1.1.2-4

Add support for a Java like parser mode

It would be nice to have a parser mode which parses all arguments as long arguments regardless of the amount of dashes, so you could have a parser that parses like typical java applications (for example consider java -version).

Print help when catching SystemExitException

Or at least print something like "usage: see --help".
Even better would be an option to do either.

It would go a long way in helping people who are not familiar with the usage.

Readme example does not work

Currently I try to use kotlin-argparser in one of my projects. Unfortunately even a simple example throws an error.

class MyArgs(parser: ArgParser) { val test by parser.flagging("-t", "--test", help = "Test") }

fun main(args: Array<String>) = mainBody { ArgParser(args).parseInto(::MyArgs).run { println("Hello, ${test}!") } }

In IntelliJ I set the program argument to i but I get the error unrecognized option '-t'
After debugging I found that in the method parseShortOpt the shortOptionDelegates only contains the help delegate.

Does anybody have a solution for this?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.