In some use cases it can be useful to add or configure structured objects from the command line. For example, to add a dataset to a visualization tool at start-up, multiple parameters need to be specified: the location of the dataset, a contrast range, what kind of data it is, etc. The amazing Java CLI parser picocli added the ArgGroup
annotation in version 4.0.0
that we can use to configure/add structured objects from the command line.
In the following, I will use the Kotlin programming language to demonstrate how to parse structured objects with picocli.
First, we define a structured object:
data class StructuredObject(val text: String, val floatingPoint: Double?, val integer: Int?)
Next, we create a class with annotated options for each of the members of StructuredObject
. In this case, we require the --text
option:
class StructuredObjectOptions {
@CommandLine.Option(names = ["--text"], required = true)
lateinit var text: String
private set
@CommandLine.Option(names = ["--float"])
var floatingPoint: Double? = null
private set
@CommandLine.Option(names = ["--int"])
var integer: Int? = null
private set
}
The StructuredObjectOptions
are then grouped with a dummy option to "trigger" the configuration of a StructuredObject
in the class StructuredObjectGroup
:
class StructuredObjectGroup {
@CommandLine.Option(names = ["--structured-object", "-s"], required = true)
private var dummy: Boolean = false
@CommandLine.ArgGroup(multiplicity = "1", exclusive = false)
private lateinit var options: StructuredObjectOptions
val structuredObject: StructuredObject
get() = StructuredObject(options.text, options.floatingPoint, options.integer)
}
The CommandLine.ArgGroup
annotation with multiplicity = "1"
ensures that the StructuredObject
is configured properly for each --structured-object, -s
that is passed as argument. If StructuredObjectOptions
does not have any mandatory parameters (required = false
), it is probably best to set multiplicity = "0..1"
, here. The exclusive = false
option is necessary to configure multiple options at the same time.
Finally, the CommandLineArgs
class
@CommandLine.Command(name = "picocli-structured-objects")
class CommandLineArgs : Callable<Unit> {
@CommandLine.ArgGroup(multiplicity = "0..*", exclusive = false)
private lateinit var _options: Array<StructuredObjectGroup>
@CommandLine.Option(names = ["--help", "-h"], usageHelp = true)
var helpRequested: Boolean = false
private set
val options: Array<StructuredObjectGroup>
get() = if(this::_options.isInitialized) _options else arrayOf()
override fun call() = options.forEach { println(it.structuredObject) }
}
adds the StructuredObjectGroup
as an ArgGroup
with multiplicity = "0..*"
to allow for an arbitrary number of structured objects to be be added/configured through the CLI, for example:
CommandLine(CommandLineArgs()).execute("-s", "--text", "abc", "-s", "--text", "def", "--float=1.3", "-s", "--text", "xyz", "--int=-1", "-s", "--text", "kotlin", "--float=3.14159265359", "--int=7")
StructuredObject(text=abc, floatingPoint=null, integer=null)
StructuredObject(text=def, floatingPoint=1.3, integer=null)
StructuredObject(text=xyz, floatingPoint=null, integer=-1)
StructuredObject(text=kotlin, floatingPoint=3.14159265359, integer=7)
I added a fully-functional kscript example and example invocations with the expected outputs. For further reading, please see the picocli docs.
As far as I know, there are two major caveats with this approach that uses ArgGroup
annotations in a somewhat hacky way to allow for configuration of (multiple) structured objects through the command line:
- picocli does not enforce any order in which the options are passed, in particular passing
--text def --float=1.3 -s
and-s --text def --float=1.3
are the same. This is counter-intuitive for this use case, where the-s
option "triggers" configuration of a structured object but is probably only a minor issue because users will usually start with the-s
option. - Multiple use of the same option flag is not permitted. For example, adding a
"--text"
option to theCommandLineArgs
class results in this runtime error:
Option name '--text' is used by both field String Picocli_structured_objects$CommandLineArgs.text and field String Picocli_structured_objects$StructuredObjectOptions.text