Structured Objects
September 9, 2019 ยท View on GitHub
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.
Caveats
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 -sand-s --text def --float=1.3are the same. This is counter-intuitive for this use case, where the-soption "triggers" configuration of a structured object but is probably only a minor issue because users will usually start with the-soption. - Multiple use of the same option flag is not permitted. For example, adding a
"--text"option to theCommandLineArgsclass 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