Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question: Multiple invocations of execute method on single CommandLine instance #2066

Open
rsenden opened this issue Jul 16, 2023 · 3 comments

Comments

@rsenden
Copy link
Contributor

rsenden commented Jul 16, 2023

Is a single CommandLine instance supposed to be reusable for multiple invocations of the execute method? I.e. should something like the following be supported?

CommandLine cl = new CommandLine(FCLIRootCommands.class);
cl.execute("cmd", "--opt", "value1");
cl.execute("cmd");

In our application, it looks like the second invocation of cmd also has value1 set for --opt, causing unexpected behavior. In case it matters, --opt is defined in a mixin.

@remkop
Copy link
Owner

remkop commented Jul 16, 2023

Could you provide a small program that reproduces this issue?

@remkop
Copy link
Owner

remkop commented Jul 16, 2023

Yes CommandLine instances are meant to be reusable across execute invocations.
Prior to parsing, picocli resets the options and parameters to their default value (or to their initial value if no default was assigned).

With the example to reproduce it, please also provide the output of running it with tracing set to DEBUG.

I suspect the workaround will be to use an explicit defaultValue of Option.NULL_VALUE for that option. But I’d like to understand what’s happening first.

@rsenden
Copy link
Contributor Author

rsenden commented Jul 17, 2023

@remkop It looks like ArgGroups are causing this issue, and setting defaultValue to Option.NULL_VALUE indeed helps to remediate this issue.

Tested with picocli 4.7.4, the code below results in the following output, showing that options 3 & 4 haven't been reset to null values:

o1: v1, o2: v2, o3: v3, o4: v4, o5: v5
o1: null, o2: null, o3: v3, o4: v4, o5: null
import picocli.CommandLine;
import picocli.CommandLine.ArgGroup;
import picocli.CommandLine.Command;
import picocli.CommandLine.Mixin;
import picocli.CommandLine.Option;

public class PicoTest {
    public static void main(String[] args) {
        CommandLine cl = new CommandLine(MyCmd.class);
        cl.execute("--o1", "v1", "--o2", "v2", "--o3", "v3", "--o4", "v4", "--o5", "v5");
        cl.execute();
    }
    
    @Command(name="test")
    public static final class MyCmd implements Runnable {
        @Option(names = "--o1") private String o1;
        @Mixin private MyMixin mixin;
        @ArgGroup(exclusive=false) private MyArgGroup2 argGroup;
        @Override
        public void run() {
            System.out.println(String.format("o1: %s, o2: %s, o3: %s, o4: %s, o5: %s",
                    o1, mixin.o2, mixin.argGroup.o3, argGroup==null?null:argGroup.o4, argGroup==null?null:argGroup.o5));
        }
    }
    
    public static final class MyMixin {
        @Option(names = "--o2") private String o2;
        @ArgGroup(exclusive=false) MyArgGroup1 argGroup = new MyArgGroup1();
    }
    
    public static final class MyArgGroup1 {
        @Option(names = "--o3") private String o3;
    }
	
	public static final class MyArgGroup2 {
        @Option(names = "--o4") private String o4;
        @Option(names = "--o5", defaultValue = Option.NULL_VALUE) private String o5;
    }
}

Debug trace:

[picocli DEBUG] Creating CommandSpec for class PicoTest$MyCmd with factory picocli.CommandLine$DefaultFactory
[picocli DEBUG] Getting a PicoTest$MyCmd instance from factory picocli.CommandLine$DefaultFactory@6956de9
[picocli DEBUG] Factory returned a PicoTest$MyCmd instance (769c9116)
[picocli DEBUG] Creating CommandSpec for PicoTest$MyMixin@1ef7fe8e with factory picocli.CommandLine$DefaultFactory
[picocli INFO] Picocli version: 4.7.4, JVM: 17.0.7 (Private Build OpenJDK 64-Bit Server VM 17.0.7+7-Ubuntu-0ubuntu120.04), OS: Linux 5.15.90.1-microsoft-standard-WSL2 amd64
[picocli INFO] Parsing 10 command line args [--o1, v1, --o2, v2, --o3, v3, --o4, v4, --o5, v5]
[picocli DEBUG] Parser configuration: optionsCaseInsensitive=false, subcommandsCaseInsensitive=false, abbreviatedOptionsAllowed=false, abbreviatedSubcommandsAllowed=false, allowOptionsAsOptionParameters=false, allowSubcommandsAsOptionParameters=false, aritySatisfiedByAttachedOptionParam=false, atFileCommentChar=#, caseInsensitiveEnumValuesAllowed=false, collectErrors=false, endOfOptionsDelimiter=--, expandAtFiles=true, limitSplit=false, overwrittenOptionsAllowed=false, posixClusteredShortOptionsAllowed=true, separator=null, splitQuotedStrings=false, stopAtPositional=false, stopAtUnmatched=false, toggleBooleanFlags=false, trimQuotes=false, unmatchedArgumentsAllowed=false, unmatchedOptionsAllowedAsOptionParameters=true, unmatchedOptionsArePositionalParams=false, useSimplifiedAtFiles=false
[picocli DEBUG] (ANSI is enabled by default: systemproperty[picocli.ansi]=null, isatty=true, TERM=xterm-256color, OSTYPE=null, isWindows=false, JansiConsoleInstalled=false, ANSICON=null, ConEmuANSI=null, NO_COLOR=null, CLICOLOR=null, CLICOLOR_FORCE=null)
[picocli DEBUG] Initializing command 'test' (user object: PicoTest$MyCmd@769c9116): 5 options, 0 positional parameters, 0 required, 2 groups, 0 subcommands.
[picocli DEBUG] Set initial value for field String PicoTest$MyCmd.o1 of type class java.lang.String to null.
[picocli DEBUG] Set initial value for field String PicoTest$MyMixin.o2 of type class java.lang.String to null.
[picocli DEBUG] [0] Processing argument '--o1'. Remainder=[v1, --o2, v2, --o3, v3, --o4, v4, --o5, v5]
[picocli DEBUG] '--o1' cannot be separated into <option>=<option-parameter>
[picocli DEBUG] Found option named '--o1': field String PicoTest$MyCmd.o1, arity=1
[picocli DEBUG] 'v1' doesn't resemble an option: 0 matching prefix chars out of 5 option names
[picocli INFO] Setting field String PicoTest$MyCmd.o1 to 'v1' (was 'null') for option --o1 on MyCmd@769c9116
[picocli DEBUG] [2] Processing argument '--o2'. Remainder=[v2, --o3, v3, --o4, v4, --o5, v5]
[picocli DEBUG] '--o2' cannot be separated into <option>=<option-parameter>
[picocli DEBUG] Found option named '--o2': field String PicoTest$MyMixin.o2, arity=1
[picocli DEBUG] 'v2' doesn't resemble an option: 0 matching prefix chars out of 5 option names
[picocli INFO] Setting field String PicoTest$MyMixin.o2 to 'v2' (was 'null') for option --o2 on MyMixin@1ef7fe8e
[picocli DEBUG] [4] Processing argument '--o3'. Remainder=[v3, --o4, v4, --o5, v5]
[picocli DEBUG] '--o3' cannot be separated into <option>=<option-parameter>
[picocli DEBUG] Found option named '--o3': field String PicoTest$MyArgGroup1.o3, arity=1
[picocli INFO] Adding match to GroupMatchContainer [[--o3=<o3>]]={} (group=1 [[--o3=<o3>]]).
[picocli DEBUG] Creating new user object of type class PicoTest$MyArgGroup1 for group [[--o3=<o3>]]
[picocli DEBUG] Created PicoTest$MyArgGroup1@6833ce2c, invoking setter FieldBinding(PicoTest$MyArgGroup1 PicoTest$MyMixin.argGroup) with scope PicoTest$MyMixin@1ef7fe8e
[picocli DEBUG] Initializing --o3=<o3> in group [[--o3=<o3>]]: setting scope to user object PicoTest$MyArgGroup1@6833ce2c and initializing initial and default values
[picocli DEBUG] Set initial value for field String PicoTest$MyArgGroup1.o3 of type class java.lang.String to null.
[picocli DEBUG] defaultValue not defined for field String PicoTest$MyArgGroup1.o3
[picocli DEBUG] Initialization complete for group [[--o3=<o3>]]
[picocli DEBUG] 'v3' doesn't resemble an option: 0 matching prefix chars out of 5 option names
[picocli INFO] Setting field String PicoTest$MyArgGroup1.o3 to 'v3' (was 'null') for option --o3 on MyArgGroup1@6833ce2c
[picocli DEBUG] [6] Processing argument '--o4'. Remainder=[v4, --o5, v5]
[picocli DEBUG] '--o4' cannot be separated into <option>=<option-parameter>
[picocli DEBUG] Found option named '--o4': field String PicoTest$MyArgGroup2.o4, arity=1
[picocli INFO] Adding match to GroupMatchContainer [[--o4=<o4>] [--o5=<o5>]]={} (group=1 [[--o4=<o4>] [--o5=<o5>]]).
[picocli DEBUG] Creating new user object of type class PicoTest$MyArgGroup2 for group [[--o4=<o4>] [--o5=<o5>]]
[picocli DEBUG] Created PicoTest$MyArgGroup2@35851384, invoking setter FieldBinding(PicoTest$MyArgGroup2 PicoTest$MyCmd.argGroup) with scope PicoTest$MyCmd@769c9116
[picocli DEBUG] Initializing --o4=<o4> in group [[--o4=<o4>] [--o5=<o5>]]: setting scope to user object PicoTest$MyArgGroup2@35851384 and initializing initial and default values
[picocli DEBUG] Set initial value for field String PicoTest$MyArgGroup2.o4 of type class java.lang.String to null.
[picocli DEBUG] defaultValue not defined for field String PicoTest$MyArgGroup2.o4
[picocli DEBUG] Initializing --o5=<o5> in group [[--o4=<o4>] [--o5=<o5>]]: setting scope to user object PicoTest$MyArgGroup2@35851384 and initializing initial and default values
[picocli DEBUG] Set initial value for field String PicoTest$MyArgGroup2.o5 of type class java.lang.String to null.
[picocli DEBUG] Applying defaultValue (null) to field String PicoTest$MyArgGroup2.o5 on MyArgGroup2@35851384
[picocli DEBUG] Initialization complete for group [[--o4=<o4>] [--o5=<o5>]]
[picocli DEBUG] 'v4' doesn't resemble an option: 0 matching prefix chars out of 5 option names
[picocli INFO] Setting field String PicoTest$MyArgGroup2.o4 to 'v4' (was 'null') for option --o4 on MyArgGroup2@35851384
[picocli DEBUG] [8] Processing argument '--o5'. Remainder=[v5]
[picocli DEBUG] '--o5' cannot be separated into <option>=<option-parameter>
[picocli DEBUG] Found option named '--o5': field String PicoTest$MyArgGroup2.o5, arity=1
[picocli DEBUG] 'v5' doesn't resemble an option: 0 matching prefix chars out of 5 option names
[picocli INFO] Setting field String PicoTest$MyArgGroup2.o5 to 'v5' (was 'null') for option --o5 on MyArgGroup2@35851384
[picocli DEBUG] Applying default values for command 'test'
[picocli DEBUG] Applying default values for group '[[--o3=<o3>]]'
[picocli DEBUG] Applying default values for group '[[--o4=<o4>] [--o5=<o5>]]'
[picocli DEBUG] Help was not requested. Continuing to process ParseResult...
[picocli DEBUG] RunLast: handling ParseResult...
[picocli DEBUG] RunLast: executing user object for 'test'...
[picocli DEBUG] Invoking Runnable::run on user object PicoTest$MyCmd@769c9116...
o1: v1, o2: v2, o3: v3, o4: v4, o5: v5
[picocli DEBUG] RunLast: ParseResult has 0 exit code generators
[picocli DEBUG] resolveExitCode: exit code generators resulted in exit code=0
[picocli DEBUG] resolveExitCode: execution results resulted in exit code=0
[picocli DEBUG] resolveExitCode: returning exit code=0
[picocli INFO] Picocli version: 4.7.4, JVM: 17.0.7 (Private Build OpenJDK 64-Bit Server VM 17.0.7+7-Ubuntu-0ubuntu120.04), OS: Linux 5.15.90.1-microsoft-standard-WSL2 amd64
[picocli INFO] Parsing 0 command line args []
[picocli DEBUG] Parser configuration: optionsCaseInsensitive=false, subcommandsCaseInsensitive=false, abbreviatedOptionsAllowed=false, abbreviatedSubcommandsAllowed=false, allowOptionsAsOptionParameters=false, allowSubcommandsAsOptionParameters=false, aritySatisfiedByAttachedOptionParam=false, atFileCommentChar=#, caseInsensitiveEnumValuesAllowed=false, collectErrors=false, endOfOptionsDelimiter=--, expandAtFiles=true, limitSplit=false, overwrittenOptionsAllowed=false, posixClusteredShortOptionsAllowed=true, separator=null, splitQuotedStrings=false, stopAtPositional=false, stopAtUnmatched=false, toggleBooleanFlags=false, trimQuotes=false, unmatchedArgumentsAllowed=false, unmatchedOptionsAllowedAsOptionParameters=true, unmatchedOptionsArePositionalParams=false, useSimplifiedAtFiles=false
[picocli DEBUG] (ANSI is enabled by default: systemproperty[picocli.ansi]=null, isatty=true, TERM=xterm-256color, OSTYPE=null, isWindows=false, JansiConsoleInstalled=false, ANSICON=null, ConEmuANSI=null, NO_COLOR=null, CLICOLOR=null, CLICOLOR_FORCE=null)
[picocli DEBUG] Initializing command 'test' (user object: PicoTest$MyCmd@769c9116): 5 options, 0 positional parameters, 0 required, 2 groups, 0 subcommands.
[picocli DEBUG] Set initial value for field String PicoTest$MyCmd.o1 of type class java.lang.String to null.
[picocli DEBUG] Set initial value for field String PicoTest$MyMixin.o2 of type class java.lang.String to null.
[picocli DEBUG] Applying default values for command 'test'
[picocli DEBUG] defaultValue not defined for field String PicoTest$MyCmd.o1
[picocli DEBUG] defaultValue not defined for field String PicoTest$MyMixin.o2
[picocli DEBUG] Applying default values for group '[[--o3=<o3>]]'
[picocli DEBUG] defaultValue not defined for field String PicoTest$MyArgGroup1.o3
[picocli DEBUG] Applying default values for group '[[--o4=<o4>] [--o5=<o5>]]'
[picocli DEBUG] defaultValue not defined for field String PicoTest$MyArgGroup2.o4
[picocli DEBUG] Applying defaultValue (null) to field String PicoTest$MyArgGroup2.o5 on MyArgGroup2@35851384
[picocli DEBUG] Help was not requested. Continuing to process ParseResult...
[picocli DEBUG] RunLast: handling ParseResult...
[picocli DEBUG] RunLast: executing user object for 'test'...
[picocli DEBUG] Invoking Runnable::run on user object PicoTest$MyCmd@769c9116...
o1: null, o2: null, o3: v3, o4: v4, o5: null
[picocli DEBUG] RunLast: ParseResult has 0 exit code generators
[picocli DEBUG] resolveExitCode: exit code generators resulted in exit code=0
[picocli DEBUG] resolveExitCode: execution results resulted in exit code=0
[picocli DEBUG] resolveExitCode: returning exit code=0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants