Skip to content

Repeating options with sub-options/arguments #635

@hanslovsky

Description

@hanslovsky

I would like to add an option multiple times and add sub-options/parameters to it. In my scenario, I have a viewer application and, among other options and one positional argument, I would like to add datasets, somewhat like this:

viewer <positional-argument> \
    --add-dataset --container=c1 --dataset=d1 \
    --add-dataset --dataset=<d2 --type=label \
    --fallback-container=fbc \ # use this as fallback, if no container is specified 
    [other options]

For this call, I would add two datasets to my container:

  • d1 in container c1 (type is auto-detected from meta data)
  • d2 in container fbc as label dataset

As far as I can tell, this is not (yet) supported in picocli (please correct me if I am wrong), and my - kind of hackish way - to achieve that, is to add an Option

@CommandLine.Option(names = arrayOf("--add-dataset"), arity = "+")
var addDatasetStrings: List<String> = mutableListOf()

that collects greedily all Strings that follow that option into a list. Users need to append a split-char after the last option of each --add-dataset (I chose _ here). I then split the list at _, and parse each of the sublists with a separate parser. This works well for me but it has a few downsides, as far as I can tell:

  • I do not think it is possible to have overlapping arguments between the main arguments/options and the options/arguments for --add-dataset.
  • Need delimiter string (I used _)
  • help message for sub option (--add-dataset) not included automatically

I added my (kotlin) code and an invocation example at the very end of this comment for reference.

I think that this would be a useful addition. There are several issues that found that are related but not the same in my understanding:

Out of these, #454 seems to be the most closely related issues. For my real-world use case (viewer application with one positional argument, and more options), I could turn the one positional argument into an option and #454 could be a working solution for me. For anything that requires positional arguments, I would be concerned that the positional arguments might clash with the subcommands.

import org.apache.commons.lang3.builder.ToStringBuilder
import picocli.CommandLine
import java.util.concurrent.Callable

@CommandLine.Command

class AddDataset(val fallbackContainer: String?) : Callable<AddDataset> {

	@CommandLine.Option(names = arrayOf("--container"))
	var container: String? = null

	@CommandLine.Option(names = arrayOf("--dataset"), required=true)
	var dataset: String? = null

	@CommandLine.Option(names = arrayOf("--help", "-h"),  usageHelp = true)
	var helpRequested = false

	override fun call(): AddDataset {
		return this
	}

	override fun toString(): String {
		return ToStringBuilder(this)
				.append("container", container?: fallbackContainer)
				.append("dataset", dataset)
				.toString()
	}

}


class Args : Callable<List<AddDataset>> {

	@CommandLine.Option(names = arrayOf("--default-container"))
	var defaultContainer: String? = null

	@CommandLine.Option(names = arrayOf("--add-dataset"), arity = "+")
	var addDatasetStrings: List<String> = mutableListOf()

	@CommandLine.Option(names = arrayOf("--help", "-h"),  usageHelp = true)
	var helpRequested = false

	override fun call(): List<AddDataset> {

		val indices = addDatasetStrings
				.withIndex()
				.filter { it.value.equals("_", ignoreCase = true) }
				.map { it.index }

		val subLists = mutableListOf<List<String>>()
		var nextStartingIndex = 0
		for (i in 0 until indices.size) {
			subLists.add(addDatasetStrings.subList(nextStartingIndex, indices[i]))
			nextStartingIndex = indices[i] + 1
		}

		return subLists.map { CommandLine.call(AddDataset(defaultContainer), *it.toTypedArray()) }
	}

}

fun main(argv: Array<String>) {

	val args = Args()
	val datasets = CommandLine.call(args, *argv)
	println(datasets)

}
$ Command --add-dataset --container=123 --dataset=456 _ --default-container=abracadabra --add-dataset --dataset=789 _
[AddDataset@5b87ed94[container=123,dataset=456], AddDataset@6e0e048a[container=abracadabra,dataset=789]]

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions