-
Notifications
You must be signed in to change notification settings - Fork 425
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
Add support for easily creating ANSI messages from Commands #376
Comments
Interesting idea. There are some existing classes that are potentially candidates to be injected to support user commands during their execution, I'm thinking of I need to think about this some more but I like this idea. |
Cool :) And as far as injection is concerned, I was also thinking this would be very handy at the highest levels, i.e. in the entry point. For example, this is what I'm currently doing: public final class Main {
public static void main(String[] args) {
System.exit(new Main().run(args));
}
public int run(String... args) {
try {
Integer code = CommandLine.call(new AllCommand(), args);
if (code == null) {
return 1;
}
return code;
} catch (ExecutionException e) {
printError(e.getMessage());
return 2;
} catch (Throwable t) {
printError(t);
return 3;
}
}
private static void printError(String message) {
System.err.println(Ansi.ON.apply("Error running command: " + message, ImmutableList.of(Style.fg_red)).toString());
}
private void printError(Throwable t) {
printError(Throwables.getStackTraceAsString(t));
}
} It would be nice to have something to aid at this level as well. |
At that level, the caller is already in control of the Ansi instance and the streams. In your example, the following line:
is the short form of:
So, to expand your example: public final class Main {
public static void main(String[] args) {
System.exit(new Main().run(args));
}
public int run(String... args) {
PrintStream out = System.out;
PrintStream err = System.err;
Ansi ansi = Ansi.AUTO;
try {
Integer code = CommandLine.call(new AllCommand(), out, err, ansi, args);
if (code == null) {
return 1;
}
return code;
} catch (ExecutionException e) {
printError(err, ansi, e.getMessage());
return 2;
} catch (Throwable t) {
printError(err, ansi, t);
return 3;
}
}
private static void printError(PrintStream stream, Ansi ansi, String message) {
stream.println(ansi.new Text("@|red " + "Error running command: " + message + "|@").toString());
}
private void printError(PrintStream stream, Ansi ansi, Throwable t) {
printError(stream, ansi, Throwables.getStackTraceAsString(t));
}
} (Note I used So at this level you are already okay, there is not much more that the picocli API could offer. |
Correct, the only thing I was thinking is that things could be a bit more fluent at this level as well. But maybe this is sufficient as you point out. Thanks for the explanation! Btw, why was the decision to not throw |
Both the
So unless you really need to handle |
I guess my question is, as a user I would expect to have the following semantics by default: private static class DefaultExceptionHandler2 extends DefaultExceptionHandler {
@Override
public Object handleParseException(ParameterException ex, String[] args) {
err().println(ex.getMessage());
ex.getCommandLine().usage(err(), ansi());
throw ex;
}
} so that this would be possible: public int run(String... args) {
try {
return CommandLine.call(new AllCommand(), args);
} catch (ParameterException e) {
// Or use e to determine return code
return 1;
} catch (ExecutionException e) {
printError(e.getMessage());
// Or use e with instanceof checking
return 2;
} catch (MyServiceException e) {
printError(t);
return 3;
}
} The reason is that it makes handling the return codes uniform by default. It is very common to want to return a non-zero return code in this case. Right now, it it seems unnatural to detect that for both the I also wish that Somewhat related is that I strongly believe that calling I guess what I'm getting at is that having the defaults aligned with testability and principle of least surprise would be nice. |
The objective of the The semantics you are looking for are available with the You can still easily run the last subcommand by creating a
|
Ok, I think I'm better understanding the intent of these objects now. Thanks for clarifying with these examples. One thought I had is that the "convenience" methods remind me a lot of how Spring Boot expects users to create their entry points. For example, here's the analog of public static void main(String[] args) {
SpringApplication.run(Application.class, args);
} However, they also have a builder that is somewhat more verbose, yet still declarative, and allows customization: public static void main(String[] args) {
new SpringApplicationBuilder()
.sources(Parent.class)
.child(Application.class)
.bannerMode(Banner.Mode.OFF)
.run(args);
} Perhaps it is this sweet spot that I'm after. Somewhere between implementing interfaces on one end and a single line on the other, that I feel that Picocli could benefit from. |
Interesting, thanks for the reference! |
One thing that may be interesting for testing: I've started to use Stephan Birkner's SystemErrAndOutRule and ExpectedSystemExit rule. This works very well for JUnit 4. If you using JUnit 5, and you need this urgently, you can offer Stefan to help with his work in progress on a JUnit 5 extension. |
I'm thinking to add the following methods to the /**
* Returns a new Text object for this Ansi mode, encapsulating the specified string
* which may contain markup like {@code @|bg(red),white,underline some text|@}.
* <p>
* Calling {@code toString()} on the returned Text will either include ANSI escape codes
* (if this Ansi mode is ON), or suppress ANSI escape codes (if this Ansi mode is OFF).
* <p>
* Equivalent to {@code this.new Text(stringWithMarkup)}.
*/
Text text(String stringWithMarkup);
/**
* Returns a String where any markup like
* {@code @|bg(red),white,underline some text|@} is converted to ANSI escape codes
* if this Ansi is ON, or suppressed if this Ansi is OFF.
* <p>
* Equivalent to {@code this.new Text(stringWithMarkup).toString()}.
*/
String string(String stringWithMarkup);
/** Returns Ansi.ON if enabled is true, Ansi.OFF otherwise. */
static Ansi valueOf(boolean enabled); // convenience method |
Something that would help ease the pain making awesome command lines is an abstraction over the console to help with creating formatted output within commands. There is great support for this for usage help. Why not extend this to the commands themselves? Think status reporting, warning and error messages.
I was thinking something like a
Console
,Terminal
orCommandContext
, etc. might be appropriate. This interface / implementation could encapsulate the state of theAnsi
enum, the stdin / stout / stderr streams, and expose methods to write formatted tables, etc. Currently, such abstractions exist in the project, but are undocumented and not designed to be fluently consumed by CLI developers.The text was updated successfully, but these errors were encountered: