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

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.)

Related

PicoCLI : Options order in Usage section

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.

Java8 map Object property to another property in List Stream

I need to map the 'count' property of the Source objects Stream to Target object's regCount & otherCount properties based on the indicator field by grouping them with PoNum property [ Ex : if (indicator.equals("Y") ? regCount = 15: otherCount = 400)]
Currently I'm doing something like the below
Map<Integer, List<SourceBean>> srcMap = srcBeanList.stream()
.collect(Collectors.groupingBy(SourceBean::getPoNum, Collectors.toList()));
for (Entry<Integer, List<SourceBean>> entry : srcMap.entrySet()) {
TargetBean tgtBean = new TargetBean();
for(SourceBean srcBean : entry.getValue()) {
tgtBean.setPoNum(srcBean.getPoNum());
tgtBean.setVendor(srcBean.getVendor());
if(srcBean.getIndicator().equals("N")) {
tgtBean.setRegCount(srcBean.getLblCnt());
}
else if(srcBean.getIndicator().equals("Y")) {
tgtBean.setOtherCount(srcBean.getLblCnt());
}
}
tgtBeanBeanList.add(tgtBean);
}
Please suggest how to achieve this mapping in Java 8 stream mapping possibly using flatmap
public class SourceBean {
private Integer poNum;
private String vendor;
private String indicator (Y/N);
private Integer count;
}
Source Json
[
{
"poNum" : 2490,
"count" : 15,
"vendor" : "Vend A",
"indicator " : "Y"
}, {
"poNum" : 2490,
"count" : 400,
"vendor" : "Vend A",
"indicator " : "N"
}
]
public class TargetBean {
private Integer poNum;
private String vendor;
private String regCount;
private String otherCount;
}
Target Json
[
{
"poNum" : 2490,
"regCount" : 15,
"otherCount" : 400,
"vendor" : "Vend A"}
]
I define a transform method named List2Bean(): it help to realize your forloop logic about combine SourceBeanList into one TargetBean. However it returns a List to apply for stream operation.
And then, focus on how I use .values().stream().map() operation. It is the key to realize your logic.
public class Test {
public TargetBean List2Bean(List<SourceBean> sourceBeanList){
TargetBean tgtBean = new TargetBean();
for(SourceBean srcBean : sourceBeanList) {
tgtBean.setPoNum(srcBean.getPoNum());
tgtBean.setVendor(srcBean.getVendor());
if(srcBean.getIndicator().equals("N")) {
tgtBean.setRegCount(String.valueOf(srcBean.getCount()));
}
else if(srcBean.getIndicator().equals("Y")) {
tgtBean.setOtherCount(String.valueOf(srcBean.getCount()));
}
}
return tgtBean;
}
public static void main(String[] args){
Test test = new Test();
List<SourceBean> srcBeanList = new ArrayList<>();
srcBeanList.add(new SourceBean(2490, "Vend A", "Y", 15));
srcBeanList.add(new SourceBean(2490, "Vend A", "N", 400));
Map<Integer, List<SourceBean>> srcMap = srcBeanList.stream()
.collect(Collectors.groupingBy(SourceBean::getPoNum, Collectors.toList()));
//List<TargetBean> targetBeanList = (List<TargetBean>)
List<TargetBean> tgtMaplist =
srcMap.values().stream().map( e -> {return test.List2Bean(e.getValue());})
.collect(Collectors.toList());
tgtMaplist.stream().forEach(t -> {
System.out.println(t.getPoNum() + " " + t.getVendor() + " " + t.getRegCount() + " " + t.getOtherCount() );
});
}
}

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.

How to pass value from dependency service to shared code

I am trying to get the last call duration on my xamarin.forms app. On android part I am using dependency service.I can get the call duration. How to pass the duration to shared code back?
My Implementation on Android
class Dialer : ICallerDialer
{
public void GetCallLogs()
{
string queryFilter = String.Format("{0}={1}", CallLog.Calls.Type, (int)CallType.Outgoing);
string querySorter = String.Format("{0} desc ", CallLog.Calls.Date);
ICursor queryData1 = Android.App.Application.Context.ContentResolver.Query(CallLog.Calls.ContentUri, null, queryFilter ,null, querySorter);
int number = queryData1.GetColumnIndex(CallLog.Calls.Number);
int duration1 = queryData1.GetColumnIndex(CallLog.Calls.Duration);
if (queryData1.MoveToFirst() == true)
{
String phNumber = queryData1.GetString(number);
String callDuration = queryData1.GetString(duration1);
How to pass this to Shared code back?
}
return;
}
}
My Interface
public interface ICallerDialer
{
void GetCallLogs();
}
Dependency call when button click
async void btnCall_Clicked(object sender, System.EventArgs e)
{
DependencyService.Get<ICallerDialer>().GetCallLogs();
//How to get duration here?
}
Any help is appreciated.
Just change the return type of your method to string type.
class Dialer : ICallerDialer
{
public string GetCallLogs()
{
string queryFilter = String.Format("{0}={1}", CallLog.Calls.Type, (int)CallType.Outgoing);
string querySorter = String.Format("{0} desc ", CallLog.Calls.Date);
ICursor queryData1 = Android.App.Application.Context.ContentResolver.Query(CallLog.Calls.ContentUri, null, queryFilter ,null, querySorter);
int number = queryData1.GetColumnIndex(CallLog.Calls.Number);
int duration1 = queryData1.GetColumnIndex(CallLog.Calls.Duration);
if (queryData1.MoveToFirst() == true)
{
String phNumber = queryData1.GetString(number);
String callDuration = queryData1.GetString(duration1);
return callDuration;
}
return string.Empty;
}
}
Interface
public interface ICallerDialer
{
string GetCallLogs();
}
Dependency call when button click
async void btnCall_Clicked(object sender, System.EventArgs e)
{
var duration = DependencyService.Get<ICallerDialer>().GetCallLogs();
}

Can picocli subcommands have options with the same name?

I have a program that executes different types of statistical analysis. I would like to define a subcommand for each type of analysis. The parent command would be the main entry point into the program. I get an error message that says "option should be specified only once" when my subcommands have options with the same name. The problem seems to be how I am calling the subcommands. In the example below, input1 and input2 work correctly. When I try to use both subcommands simultaneously (input3), I get an error message.
The code below demonstrates the problem. If the input contains both subcommands (i.e. input3), I get the error message "option '-id' at index 0 () should be specified only once".
How can I call both subcommands simultaneously as in input3?
import picocli.CommandLine;
import java.util.concurrent.Callable;
#CommandLine.Command(name = "myprogram", subcommands = {TestCase.FrequencyCommand.class, TestCase.HistogramCommand.class})
public class TestCase implements Callable<Void> {
public TestCase(){
}
public Void call() {
System.out.println("Main program called");
return null;
}
public static void main(String[] args){
String[] input1 = {"frequency", "-id", "1001", "-table", "ex1"};
String[] input2 = {"histogram", "-id", "1002", "-table", "ex5" };
String[] input3 = {"frequency", "-id", "1001", "-table", "ex1", "histogram", "-id", "1002", "-table", "ex5" };
CommandLine commandLine = new CommandLine(new TestCase());
System.out.println("==Test1==");
commandLine.execute(input1);
System.out.println();
System.out.println("==Test2==");
commandLine.execute(input2);
System.out.println();
System.out.println("==Test3==");
commandLine.execute(input3);
System.out.println();
}
#CommandLine.Command(name = "frequency", description = "Frequency analysis.")
static class FrequencyCommand implements Callable<Void> {
#CommandLine.Option(names = {"-id"}, arity = "1..*", description = "Unique case identifier")
public String id;
#CommandLine.Option(names = "-table", arity = "1..*", description = "Database table")
public String table;
public FrequencyCommand(){
}
public Void call() {
System.out.println("Frequency");
System.out.println("ID = " + id);
System.out.println("Table = " + table);
return null;
}
}
#CommandLine.Command(name = "histogram", description = "Histogram plot.")
static class HistogramCommand implements Callable<Void> {
#CommandLine.Option(names = {"-id"}, arity = "1..*", description = "Unique case identifier")
public String id;
#CommandLine.Option(names = "-table", arity = "1..*", description = "Database table")
public String table;
public HistogramCommand(){
}
public Void call() {
System.out.println("Histogram");
System.out.println("ID = " + id);
System.out.println("Table = " + table);
return null;
}
}
}
The output I expect to see is:
==Test1==
Frequency
ID = 1001
Table = ex1
==Test2==
Histogram
ID = 1002
Table = ex5
==Test3==
Frequency
ID = 1001
Table = ex1
Histogram
ID = 1002
Table = ex5
The last example invokes two subcommands, frequency and histogram, which are siblings (they have the same parent command).
This is not supported yet as of picocli 4.0.0-alpha-3: currently picocli expects subcommands to be a hierarchy.
However, support for this is on the todo list, see GitHub tickets #454 and #319.
Pull requests are welcome if you want to help speed things up. :-)

Resources