PicoCLI : Options order in Usage section - picocli

I have set sortOptions to false for command but still, I am seeing command's options in sorted order in the Usage section.
I want to see them in their declaration order.
Sample code
#Command(name = "application", subcommands = { AddSubCommand.class })
public class Sample implements Runnable {
#Spec CommandSpec spec;
#Override
public void run() {
throw new ParameterException(spec.commandLine(), "Specify a subcommand");
}
public static void main(String... args) {
new CommandLine(new Sample()).execute("add", "-h");
}
}
#Command(name = "add", sortOptions = false)
class AddSubCommand implements Runnable {
#Option(names = { "-h" }, usageHelp = true, hidden = true)
boolean helpFlag;
#Option(names = { "-i" }, required = true, description = "id")
int id;
#Option(names = { "-t" }, required = true, description = "type")
String type;
#Option(names = { "-c" }, required = true, description = "config")
String config;
public void run() {
// logic
}
}
Output
Usage: application add -c=<config> -i=<id> -t=<type>
-i=<id> id
-t=<type> type
-c=<config> configuration
Expectation
Usage: application add -i=<id> -c=<config> -t=<type>
-i=<id> id
-t=<type> type
-c=<config> configuration

You are correct that options are sorted in the synopsis line, regardless of whether sortOptions = false is specified. (The sortOptions annotation attribute only affects the order of the options list section of the usage help message, not the synopsis.)
It is possible to customize the synopsis, but not with the annotations API; you will need to use the programmatic API.
Change the Sample class main method to the following:
public static void main(String... args) {
new CommandLine(new Sample())
.setHelpFactory(new UnsortedSynopsisHelpFactory())
.execute("add", "-h");
}
And add the following UnsortedSynopsisHelpFactory class:
class UnsortedSynopsisHelpFactory implements CommandLine.IHelpFactory {
#Override
public CommandLine.Help create(CommandSpec commandSpec, ColorScheme colorScheme) {
return new CommandLine.Help(commandSpec, colorScheme) {
#Override
protected Ansi.Text createDetailedSynopsisOptionsText(
Collection<ArgSpec> done,
Comparator<OptionSpec> optionSort,
boolean clusterBooleanOptions) {
return super.createDetailedSynopsisOptionsText(
done,
null, // do not sort options in synopsis
clusterBooleanOptions);
}
};
}
}
This will give the following output:
Usage: application add -i=<id> -t=<type> -c=<config>
-i=<id> id
-t=<type> type
-c=<config> config
Thanks for raising this! I added an example Unsorted.java to the picocli-examples module.

Related

Force Spring Kafka not to create topics automatically, but to use already created ones

There is a quite simple case I would like to implement:
I have a base and DLT topics:
MessageBus:
Topic: my_topic
DltTopic: my_dlt_topic
Broker: event-serv:9092
So, those topics are already predefined, I don't need to create them automatically.
The only I need to handle broken messages automatically without retries, because they don't make any sense, so I have something like this:
#KafkaListener(topics = ["#{config.messageBus.topic}"], groupId = "group_id")
#RetryableTopic(
dltStrategy = DltStrategy.FAIL_ON_ERROR,
autoCreateTopics = "false",
attempts = "1"
)
#Throws(IOException::class)
fun consume(rawMessage: String?) {
...
}
#DltHandler
fun processMessage(rawMessage: String?) {
kafkaTemplate.send(config.messageBus.dltTopic, rawMessage)
}
That of course doesn't work properly.
I also tried to specify a kafkaTemplate
#Bean
fun kafkaTemplate(
config: Config,
producerFactory: ProducerFactory<String, String>
): KafkaTemplate<String, String> {
val template = KafkaTemplate(producerFactory)
template.defaultTopic = config.messageBus.dltTopic
return template
}
however, that does not change the situation.
In the end, I believe there is an obvious solution, so I please give me a hint about it.
See the documenation.
#SpringBootApplication
public class So69317126Application {
public static void main(String[] args) {
SpringApplication.run(So69317126Application.class, args);
}
#RetryableTopic(attempts = "1", autoCreateTopics = "false", dltStrategy = DltStrategy.FAIL_ON_ERROR)
#KafkaListener(id = "so69317126", topics = "so69317126")
void listen(String in) {
System.out.println(in);
throw new RuntimeException();
}
#DltHandler
void handler(String in) {
System.out.println("DLT: " + in);
}
#Bean
RetryTopicNamesProviderFactory namer() {
return new RetryTopicNamesProviderFactory() {
#Override
public RetryTopicNamesProvider createRetryTopicNamesProvider(Properties properties) {
if (properties.isMainEndpoint()) {
return new SuffixingRetryTopicNamesProviderFactory.SuffixingRetryTopicNamesProvider(properties) {
#Override
public String getTopicName(String topic) {
return "so69317126";
}
};
}
else if(properties.isDltTopic()) {
return new SuffixingRetryTopicNamesProviderFactory.SuffixingRetryTopicNamesProvider(properties) {
#Override
public String getTopicName(String topic) {
return "so69317126.DLT";
}
};
}
else {
throw new IllegalStateException("Shouldn't get here - attempts is only 1");
}
}
};
}
}
so69317126: partitions assigned: [so69317126-0]
so69317126-dlt: partitions assigned: [so69317126.DLT-0]
foo
DLT: foo
This is a Kafka server configuration so you must set it on the server. The relevant property is:
auto.create.topics.enable (true by default)

PicoCLI : How can I use #ArgGroup for a method?

I want to have mutually exclusive command options for the below snippet :
#Command(description = "test command")
public void test(
#Option(names = { "-a"}, required = true, arity = "0", description = "print A") boolean a,
#Option(names = { "-b"}, required = true, description = "pint B") boolean b)
//
}
If I use #ArgGroup for a class field then It works but I want to achieve the same for methods.
class TestClass{
#ArgGroup(exclusive = true, multiplicity = "1")
private Sample sample = new Sample();
public static class Sample {
#Option(names = { "-a"}, required = true, arity = "0", description = "print A") boolean a ;
#Option(names = { "-b"}, required = true, description = "pint B") boolean b ;
}
}
You should be able to use an #ArgGroup-annotated method, just like an #ArgGroup-annotated field.
For example:
class SomeCommand implements Runnable {
private Sample sample;
#ArgGroup(exclusive = true, multiplicity = "1")
void setGroup(Sample sample) {
System.out.printf("setGroup was called with %s%n", sample);
this.sample = sample;
}
static class Sample {
#Option(names = "-a", required = true, arity = "0", description = "print A") boolean a ;
#Option(names = "-b", required = true, description = "print B") boolean b ;
public String toString() {
return String.format("Sample[a=%s, b=%s]#%x", a, b, hashCode());
}
}
public void run() {
System.out.printf("In run, sample=%s%n", this.sample);
}
public static void main(String... args) {
//System.setProperty("picocli.trace", "DEBUG");
new CommandLine(new SomeCommand()).execute("-a");
}
}
When I run this, I see the following output:
setGroup was called with Sample[a=false, b=false]#7de62196
In run, sample=Sample[a=true, b=false]#7de62196
So, you can use an #ArgGroup-annotated method; it will initially be invoked with a new instance, and this instance will be modified after the setter method was called.
(We can get more insight into what is happening under the hood by enabling picocli tracing.)

picocli : parse arguments without boiler plate

I generally store away all the command line options to a different class, say, CliArguments. This avoids the noise in the main class. This is what I have with picocli:
public final class MyApp {
private static final CliArguments cliArgs = new CliArguments();
private MyApp() {}
public static void main (String[] args) {
if (parseArgs (args)) {
new MyApp().execute();
}
}
/* want to avoid this boiler plate */
private static boolean parseArgs(String[] args) {
CommandLine cmd = new CommandLine ( cliArgs );
try {
cmd.parseArgs( args );
if (cmd.isUsageHelpRequested()) {
cmd.usage( cmd.getOut() );
return false;
}
else if ( cmd.isVersionHelpRequested() ) {
cmd.printVersionHelp (cmd.getOut());
return false;
}
logger.info("{}", cliArgs);
return true;
}
catch ( ParameterException ex ) {
logger.error ("Failure to parse : {}", ex);
return false;
}
}
private void execute() {
// execution logic
}
}
How do I avoid the boiler plate method, pargeArgs(String[])? The CliArguments class, technically, should not implement a Callable or Runnable. I can make MyApp be a Callable or Runnable. but to CommandLine, new MyApp() is not a command, new CliArguments() is.
If I want to do something like this:
final int exitCode = new CommandLine(new MyApp()).execute(args);
if (0 != exitCode) {
logger.error("Failed to parse");
System.exit(exitCode);
}
how do I push off all the #Option specification to a different class, CliArguments while still having the execution control in MyApp?
I am sure I am missing something straight forward.
The simplest way to achieve this is by making CliArguments a mixin in MyApp. We can then put the business logic in MyApp, and make it a Runnable or Callable so we can bootstrap the application with new CommandLine(new MyApp()).execute(args).
For example:
#Command(mixinStandardHelpOptions = true, version = "1.0.0")
public class CliArgs {
#Option(names = "-x") boolean x;
#Option(names = "-y") boolean y;
}
#Command(name = "myapp", description = "...")
public class MyApp implements Runnable {
// options defined in the mixin are added to this command
// also, #Command attributes from the mixin are applied to this command
#Mixin
CliArgs cliArgs;
public void run() {
System.out.printf("-x=%s%n", cliArgs.x);
System.out.printf("-y=%s%n", cliArgs.y);
}
public void main(String... args) {
System.exit(new CommandLine(new MyApp()).execute(args));
}
}
The options defined in the CliArgs mixin become part of the MyApp mixee.
Also, any #Command attributes defined in CliArgs become part of the MyApp command.
You can now run:
java MyApp -x
and this will print
-x=true
-y=false
Since the mixin has #Command(mixinStandardHelpOptions = true), the MyApp command also has --help and --version options that work as you would expect.

picocli example showing the usage of multiple commands

ive got some code that works very well with picocli:
#Command(name = "parse", sortOptions = false, description = "parse input files and write to database")
class CommandLineArgumentParser {
#Option(names = { "-h", "--help" }, usageHelp = true, description = "display this message")
private boolean helpRequested = false;
#Option(names = { "-s", "--startDate"}, description = "First day at which to parse data",
converter = GermanDateConverter.class, paramLabel = "dd.MM.yyyy")
public LocalDate start;
#Option(names = { "-e", "--endDate"}, description = "Last day (inclusive) at which to stop parsing",
converter = GermanDateConverter.class, paramLabel = "dd.MM.yyyy")
public LocalDate end;
private static class GermanDateConverter implements ITypeConverter<LocalDate> {
#Override
public LocalDate convert(String value) throws Exception {
LocalDate result = null;
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy");
result = LocalDate.parse(value, formatter);
if (result.getYear() < 1900) {
throw new IllegalArgumentException("year should be after 1900");
}
return result;
}
}
#SpringBootApplication
public class Application implements CommandLineRunner {
public void run(String... args) throws Exception {
CommandLineArgumentParser commandlineparser = new CommandLineArgumentParser();
CommandLine commandLine = new CommandLine(commandlineparser);
try {
commandLine.parseArgs(args);
} catch (MissingParameterException e) {
System.out.println(e);
System.out.println();
CommandLine.usage(CommandLineArgumentParser.class, System.out);
System.exit(1);
} catch (ParameterException e) {
System.out.println(e);
System.out.println();
CommandLine.usage(CommandLineArgumentParser.class, System.out);
System.exit(1);
}
if (commandLine.isUsageHelpRequested()) {
commandLine.usage(System.out);
return;
} else if (commandLine.isVersionHelpRequested()) {
commandLine.printVersionHelp(System.out);
return;
}
if (commandlineparser.start == null) {
log.warn("no start date specified, using: 01.01.2005");
commandlineparser.start = LocalDate.of(2005, 01, 01);
}
if (commandlineparser.end == null) {
LocalDate timePoint = LocalDate.now();
log.warn("no end date specified, using today: " + timePoint.toString());
commandlineparser.end = timePoint;
}
}
but i did not find a simple example that shows multiple commands used, for example this one:
https://github.com/remkop/picocli/blob/master/src/test/java/picocli/Demo.java
does not compile:
int exitCode = new CommandLine(new Demo()).execute(args);
The method execute(CommandLine, List<Object>) in the type CommandLine is not applicable for the arguments (String[])
could somebody please post a example on howto use multiple commands?
I suspect you’re using an older version of the library. The execute(String []) : int method was introduced in picocli 4.0.
Upgrading and using the execute method instead of the parseArgs method will allow you to remove a lot of boilerplate code from the application: handling requests for usage/version help and dealing with invalid input is done automatically with the execute method.
Your commands should implement Runnable or Callable, and this is where the business logic of each command lives.
Modifying your example and giving it a subcommand:
#Command(name = "parse", sortOptions = false,
mixinStandardHelpOptions = true, version = “1.0”,
description = "parse input files and write to database",
subcommands = MySubcommand.class)
class CommandLineArgumentParser implements Callable<Integer> {
#Option(names = { "-s", "--startDate"}, description = "First day at which to parse data",
converter = GermanDateConverter.class, paramLabel = "dd.MM.yyyy")
public LocalDate start;
#Option(names = { "-e", "--endDate"}, description = "Last day (inclusive) at which to stop parsing",
converter = GermanDateConverter.class, paramLabel = "dd.MM.yyyy")
public LocalDate end;
private static class GermanDateConverter implements ITypeConverter<LocalDate> {
#Override
public LocalDate convert(String value) throws Exception {
LocalDate result = null;
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy");
result = LocalDate.parse(value, formatter);
if (result.getYear() < 1900) {
throw new IllegalArgumentException("year should be after 1900");
}
return result;
}
}
#Override
public Integer call() {
if (start == null) {
log.warn("no start date specified, using: 01.01.2005");
start = LocalDate.of(2005, 01, 01);
}
if (end == null) {
LocalDate timePoint = LocalDate.now();
log.warn("no end date specified, using today: " + timePoint.toString());
end = timePoint;
}
// more business logic here ...
// add finally return an exit code
int exitCode = ok ? 0 : 1;
return exitCode;
}
}
#Command(name = "foo")
class MySubcommand implements Callable<Integer> {
#Override
public Integer call() {
System.out.println("hi");
return 0;
}
}
#SpringBootApplication
public class Application implements CommandLineRunner {
public void run(String... args) throws Exception {
int exitCode = new CommandLine(
CommandLineArgumentParser.class,
new picocli.spring.PicocliSpringFactory())
.execute(args);
System.exit(exitCode);
}
}
Note that when using Spring in combination with picocli subcommands, you need to call the CommandLine constructor with a picocli.spring.PicocliSpringFactory.
For a more complete Spring example, see the picocli-spring-boot-starter README.

Accessing OutArgument value of Receive implementation child activity within custom WF4 activity

Using VS2012/.NET 4.5 I am creating a custom activity which implements a Receive child activity (as an implementation child). The parameters are in the example below fixed to just one: OutValue of type Guid.
I really would love to access the value of incoming parameter value in ReceiveDone, because I need to work with it and transform it before returning it from the activity. Please ignore that I am currently using a Guid, it still fails to access the value with and InvalidOperationException:
An Activity can only get the location of arguments which it owns. Activity 'TestActivity' is trying to get the location of argument 'OutValue' which is owned by activity 'Wait for
workflow start request [Internal for TestActivity]'
I have tried everything I could think of, but am stupefied. There must be a way to do this very simple thing?
public class TestActivity : NativeActivity<Guid>
{
protected override void CacheMetadata(NativeActivityMetadata metadata)
{
var content = ReceiveParametersContent.Create(new Dictionary<string, OutArgument>()
{
// How to access the runtime value of this inside TestActivity?
{"OutValue", new OutArgument<Guid>()}
});
startReceiver = new Receive()
{
DisplayName = string.Format("Wait for workflow start request [Internal for {0}]", this.DisplayName),
CanCreateInstance = true,
ServiceContractName = XName.Get("IStartService", Namespace),
OperationName = "Start",
Content = content
};
foreach (KeyValuePair<string, OutArgument> keyValuePair in content.Parameters)
{
metadata.AddImportedChild(keyValuePair.Value.Expression);
}
metadata.AddImplementationChild(startReceiver);
}
protected override void Execute(NativeActivityContext context)
{
context.ScheduleActivity(startReceiver, ReceiveDone);
}
private void ReceiveDone(NativeActivityContext context, ActivityInstance completedInstance)
{
var receive = completedInstance.Activity as Receive;
ReceiveParametersContent content = receive.Content as ReceiveParametersContent;
try
{
// This causes InvalidOperationException.
// An Activity can only get the location of arguments which it owns.
// Activity 'TestActivity' is trying to get the location of argument 'OutValue'
// which is owned by activity 'Wait for workflow start request [Internal for TestActivity]'
var parmValue = content.Parameters["OutValue"].Get(context);
}
catch (Exception)
{ }
}
private Receive startReceiver;
private const string Namespace = "http://company.namespace";
}
Use internal variables to pass values between internal activities.
Although not directly related to your code, see the example below which should give you the idea:
public sealed class CustomNativeActivity : NativeActivity<int>
{
private Variable<int> internalVar;
private Assign<int> internalAssign;
protected override void CacheMetadata(NativeActivityMetadata metadata)
{
base.CacheMetadata(metadata);
internalVar = new Variable<int>("intInternalVar", 10);
metadata.AddImplementationVariable(internalVar);
internalAssign = new Assign<int>
{
To = internalVar,
Value = 12345
};
metadata.AddImplementationChild(internalAssign);
}
protected override void Execute(NativeActivityContext context)
{
context.ScheduleActivity(internalAssign, (activityContext, instance) =>
{
// Use internalVar value, which was seted by previous activity
var value = internalVar.Get(activityContext);
Result.Set(activityContext, value);
});
}
}
Calling the above activity:
WorkflowInvoker.Invoke<int>(new CustomNativeActivity());
Will output:
12345
Edit:
In your case your OutArgument will be the internalVar
new OutArgument<int>(internalVar);
You need to use OutArgument and them to variables. See the code example with the documentation.
I may have tried everything I thought of, but I am stubborn and refuse to give up, so I kept on thinking ;)
I here have changed my example to use a Data class as a parameter instead (it does not change anything in itself, but I needed that in my real world example).
This code below is now a working example on how to access the incoming data. The use of an implementation Variable is the key:
runtimeVariable = new Variable<Data>();
metadata.AddImplementationVariable(runtimeVariable);
And the OutArgument:
new OutArgument<Data>(runtimeVariable)
I can then access the value with:
// Here dataValue will get the incoming value.
var dataValue = runtimeVariable.Get(context);
I haven't seen an example elsewhere, which does exactly this. Hope it will be of use to any one but me.
The code:
[DataContract]
public class Data
{
[DataMember]
Guid Property1 { get; set; }
[DataMember]
int Property2 { get; set; }
}
public class TestActivity : NativeActivity<Guid>
{
public ReceiveContent Content { get; set; }
protected override void CacheMetadata(NativeActivityMetadata metadata)
{
runtimeVariable = new Variable<Data>();
metadata.AddImplementationVariable(runtimeVariable);
Content = ReceiveParametersContent.Create(new Dictionary<string, OutArgument>()
{
{"OutValue", new OutArgument<Data> (runtimeVariable)}
});
startReceiver = new Receive()
{
DisplayName = string.Format("Wait for workflow start request [Internal for {0}]", this.DisplayName),
CanCreateInstance = true,
ServiceContractName = XName.Get("IStartService", Namespace),
OperationName = "Start",
Content = Content
};
metadata.AddImplementationChild(startReceiver);
}
protected override void Execute(NativeActivityContext context)
{
context.ScheduleActivity(startReceiver, ReceiveDone);
}
private void ReceiveDone(NativeActivityContext context, ActivityInstance completedInstance)
{
// Here dataValue will get the incoming value.
var dataValue = runtimeVariable.Get(context);
}
private Receive startReceiver;
private Variable<Data> runtimeVariable;
private const string Namespace = "http://company.namespace";
}

Resources