Parsing and then executing - a better way?

16 views
Skip to first unread message

Federico Mestrone

unread,
May 11, 2022, 11:38:35 PM5/11/22
to picocli
Hello!

I would like to be able to parse the command line arguments, pick up credentials if available, so that I can authenticate my users, and then execute the commands using the execute method of Picocli.

I can see that execute calls parse and then acts on the ParseResult. Isn't there a way to invoke the execute logic once I have already parsed the arguments. This is how I am doing now, but it seems redundant and I'd like to know how you would do this.

override fun run(vararg args: String?) {
  exitCode = try {
    val commandLine = CommandLine(ShlinkCommand(), factory)
    val result = commandLine.parseArgs(*args)

    val username = result.matchedOption("--usr")?.getValue<String>()
    val password = result.matchedOption("--pwd")?.getValue<String>()
    if (!username.isNullOrBlank() && !password.isNullOrBlank()) {
       oauth2Flow(username, password)
    } else {
      var context: SecurityContext = SecurityContextHolder.getContext()
      val authorities = HashSet<GrantedAuthority>()
      authorities.add(SimpleGrantedAuthority("ROLE_Owner"))
      context.authentication = AnonymousAuthenticationToken("nekoshlink", "anonymous", authorities)
    }

   // TODO must be able to pass parseResult so I don't have to parse again!
   exitProcess(commandLine.execute(*args))
} catch (e: CommandLine.PicocliException) {
  System.err.println(e.message)
  -1
} catch (e: Exception) {
  System.err.println("An internal application error has occurred!")
  System.err.println("${e.javaClass.name}: ${e.message}")
  -9
}

}



picocli

unread,
May 18, 2022, 4:34:47 PM5/18/22
to picocli
Hi, sorry for the late response.
I am not receiving notifications for messages posted to this list for some reason.
(It may be better to create a GitHub ticket instead.)

Question: why are you using ParseResult? Why not just use the annotations?
For example (sorry to use a mixture of Java and Kotlin but you get the idea):

class ShlinkCommand implements Runnable {

  @Option(names = "--usr")
  String username;

  @Option(names = "--pwd")
  String password;

  public void run() {
    // no need to use ParseResult here because picocli populated the annotated fields

    if (!username.isNullOrBlank() && !password.isNullOrBlank()) {
      oauth2Flow(username, password)
    } else {
      var context: SecurityContext = SecurityContextHolder.getContext()
      val authorities = HashSet<GrantedAuthority>()
      authorities.add(SimpleGrantedAuthority("ROLE_Owner"))
      context.authentication = AnonymousAuthenticationToken("nekoshlink", "anonymous", authorities)
    }
  }

  public static void main(String[] args) {
    IFactory factory = createFactory();
    int exitCode = new CommandLine(new ShlinkCommand(), factory).execute(args);
    exitProcess(exitCode);
  }
}


Alternatively, you don't need to call parseArgs again to get the ParseResult.
Just use execute().
You can get the ParseResult from the CommandLine object, which you can get from the CommandSpec, which you can get using the @Spec annotation:

class ShlinkCommand implements Runnable {

@Option(names = "--usr")
String username;

@Option(names = "--pwd")
String password;

@Spec
CommandSpec spec;

public void run() {
  CommandLine.ParseResult parseResult = spec.commandLine().getParseResult();
  // ...
}

public static void main(String[] args) {
  IFactory factory = createFactory();
  int exitCode = new CommandLine(new ShlinkCommand(), factory).execute(args);
  exitProcess(exitCode);
}
}

Federico Mestrone

unread,
May 26, 2022, 1:34:34 PM5/26/22
to picocli
Thank you so much!

That makes a lot of sense. I did not realize I'd be able to annotate the command itself to have some common options, and had not found the Spec annotation in my search. I will try both options out straightaway and let you know in case I have any issues, if you don't mind.

I haven't mentioned it before, but just wanted to say I love your work - it is a beautiful easy-to-use framework you have created there. Thanks!

Federico

Federico Mestrone

unread,
May 27, 2022, 9:26:28 AM5/27/22
to picocli
So, I tried it and I remembered why it was not working.

The authentication code (the run method you have added to the Shlink command) is only executed when I issue the command with no subcommands.

The moment I try and run a subcommand, the Runner is skipped and only the subcommand is executed.

I would like a way to execute my authentication logic for ALL the subcommands in my CLI application. Does that make sense?

Federico

Federico Mestrone

unread,
May 27, 2022, 9:34:19 AM5/27/22
to picocli
I think I found what I need


What do you think?

Fed

Reply all
Reply to author
Forward
0 new messages