spring-projects / spring-boot

Spring Boot helps you to create Spring-powered, production-grade applications and services with absolute minimum fuss.
https://spring.io/projects/spring-boot
Apache License 2.0
75.4k stars 40.74k forks source link

A functional command line runner #25945

Open xenoterracide opened 3 years ago

xenoterracide commented 3 years ago

So in order to do a command line runner with an exit code you have to do mutable state (at least from what I can find documented), here's picocli's example

import picocli.CommandLine;
import picocli.CommandLine.IFactory;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.ExitCodeGenerator;
import org.springframework.stereotype.Component;

@Component
public class MyApplicationRunner implements CommandLineRunner, ExitCodeGenerator {

    private final MyCommand myCommand;

    private final IFactory factory; // auto-configured to inject PicocliSpringFactory

    private int exitCode;

    public MyApplicationRunner(MyCommand myCommand, IFactory factory) {
        this.myCommand = myCommand;
        this.factory = factory;
    }

    @Override
    public void run(String... args) throws Exception {
        exitCode = new CommandLine(myCommand, factory).execute(args);
    }

    @Override
    public int getExitCode() {
        return exitCode;
    }
}

and from what little is in the reference documentation this s true, I think we need an interface that can work without mutability.

interface ExitingCommandLineRunner {

   int run( String... args );
}
philwebb commented 3 years ago

The exit code logic could certainly do with a refresh. We have another issue (#25100) related to the SpringApplication.exit method.

One trick that might work in the meantime is to put ExitCodeGenerator on an exception:

@Component
public class MyApplicationRunner implements CommandLineRunner {

    private final MyCommand myCommand;

    private final IFactory factory; // auto-configured to inject PicocliSpringFactory

    public MyApplicationRunner(MyCommand myCommand, IFactory factory) {
        this.myCommand = myCommand;
        this.factory = factory;
    }

    @Override
    public void run(String... args) throws Exception {
        int exitCode = new CommandLine(myCommand, factory).execute(args);
        if (exitCode != 0) {
            throw new MyExitException(exitCode);
        }
    }

    static class ExitException implements ExitCodeGenerator {

        private final int code;

        ExitException(int code) {
            this.code = code;
        }

        @Override
        public int getExitCode() {
            return this.code;
        }

    }

}
raqueru commented 2 years ago

Hello, i would like to solve this issue

raqueru commented 2 years ago

Should this interface be implemented only by classes that use exit code or in general? I can't seem to find any example of run codes that use exit code in general