clap-rs / clap

A full featured, fast Command Line Argument Parser for Rust
docs.rs/clap
Apache License 2.0
14.16k stars 1.04k forks source link

Allow more flexible configuration of the output steam (stdout vs. stderr) #1788

Closed tzakharko closed 2 years ago

tzakharko commented 4 years ago

Describe your use case

Some command line tools are used exclusively as components within larger configurations and their stdout might be piped into other tools. Clap is currently hard-coded in Error::use_stderr() to print certain messages (e.g. the help block) to stdout, which might lead to surprising behavior.

Describe the solution you'd like

It would be nice to have an option in configuring the behavior of Error:: use_stderr(). maybe a configuration option in App that would override the default behavior, e.g.:

App::new(...).
  ...
  .setting(AppSettings::PrintToStderr)
  .get_matches()

Alternatives, if applicable

I am currently intercepting all clap messages and printing them manually like this:

App::new(...).
  ...
 .get_matches_safe()
 .unwrap_or_else(|err| {
     eprintln!("{}", err.message);
     std::process::exit(1);
});

but it feels a bit hacky to me.

Dylan-DPC-zz commented 4 years ago

@tzakharko hi thanks for the issue, would you be willing to send us a pr that makes this change? thanks

tzakharko commented 4 years ago

@Dylan-DPC I am afraid I am neither familiar enough with the Clap codebase nor with Rust to figure out the best way of doing it at the moment. I suppose one would hav etc decouple the output settings from the Error struct, so it's probably not trivial.

I have opened the issue mainly because @pksunkara asked me to do so in a discussion thread. I understand this might be lower priority.

Dylan-DPC-zz commented 4 years ago

@tzakharko that's fine we will mentor you if needed. It's a good way to learn rust :D

tzakharko commented 4 years ago

@Dylan-DPC fair enough :) I will have a look, but I won't be able to allocate any time for this short term. I will put it on my agenda and let's keep the issue open for now.

olson-sean-k commented 3 years ago

I imagine this would be much trickier to implement, but how do folks feel about allowing arbitrary Write targets for this? Instead of naming bespoke outputs, users could instead provide an impl Write. This would still support selecting stdout or stderr via std::io::stdout and std::io::stderr, which both emit impl Write types.

I've been working on a CLI tool that incorporates paging by conditionally targeting a paging child process or the configured terminal. bat does something similar. AFAICT, it is not possible to use clap's generated help text, options, and subcommands with such a paging mechanism and the only alternative would be to define help options and subcommands manually and use App::print{_long}_help by hand. For example, you'll notice that both nym and bat automatically page their output except for help text, which is written to stdout by clap.

epage commented 2 years ago

@tzakharko I believe clap is only reporting --help and --version to stdout. Those are explicit user actions. What would be the use case of passing those flags but still programmatically processing the output so you don't want it on stdout?

@olson-sean-k some quick thoughts on this

epage commented 2 years ago

I am leaning towards rejecting this until we have a use case that explains why output going to stdout for explicit user interactions is interfering with an applications behavior (especially since there are cases where people want their -V to be parseable).

This isn't to make a statement of whether we want this or not but because we are specifically looking at finding ways to trim the API of clap which also raises the bar for changes that expand the API.

TheOnlyMrCat commented 2 years ago

An example use case is if a command is leveraging shell hooks to cd when certain subcommands are used. The shell hook is only capable of capturing stdout, due to limitations of the shell language, but the program otherwise tries to behave like a normal shell command with subcommands and flags.

epage commented 2 years ago

I've not dealt with shell hooks to cd. Could you elaborate more on the relationhip between the command and the hook and how a call to --help or --version wouldn't be processed correctly?

TheOnlyMrCat commented 2 years ago

The shell hook is the main interface to the program, so the user would be expected to run prog --help. The shell hook passes all arguments through to the program and captures the contents of stdout. (There is no way to swap stdout and stderr).

The automatic --help and --version flags print to stdout, which is incorrectly interpreted as a directory to attempt to change to, and as a result are not shown to the user in the intended way.

function prog() {
    result="$(prog_bin $@)" # Run `prog_bin` with the supplied arguments, capturing stdout but leaving stderr to the tty
    [[ -n $result ]] && cd $result # If anything was printed to stdout, `cd` to it.
}
tzakharko commented 2 years ago

@epage Sorry for replying late and thanks for following this through. I have to be quite honest that I do not remember what was the actual problem that made me open this issue, it was two years ago and couldn't find any relevant notes. I think it had to do with the fact that some outputs did not pipe correctly when uses as part of a complex unix command sequence, but we are not currently using these tools so I guess that's all I can say.

At any rate, I would be fine with this being closed, as it is not a critical issue for us at the moment. But I gather from the comments that some other folks are running into real-world issues, so maybe they will have a better motivating example.

olson-sean-k commented 2 years ago

It seems that version 4.0 of clap is on the horizon, so I wanted to bump this again in hopes that some of the dust has settled since this issue was first opened.

I am leaning towards rejecting this until we have a use case that explains why output going to stdout for explicit user interactions is interfering with an applications behavior

I believe the use case described in my previous comment has precedent and falls into this category. It is not possible to (comprehensively) implement cli --pager=target if clap cannot be configured to write to (more) arbitrary targets. Redirecting standard I/O streams does not completely solve I/O routing for a CLI application.

We wouldn't be able to detect coloring support. ... the user can do it and tell us what coloring to do

I feel that clap should probably allow users to configure styled (colored) output in all cases. Arbitrary outputs could interact with the styling APIs, with something like a RenderTarget that provides styling information as well as the impl Write target.

Regarding styling, perhaps detection and styles could be feature gated. When the styling feature is enabled, RenderTarget::default could try to detect styling support and use it where available while simply disabling it when the feature is not enabled. In both of these cases the default impl Write target could be std::io::stdout.

Any thoughts on something like this post-4.0? Thanks for taking a look!

epage commented 2 years ago

I believe the use case described in https://github.com/clap-rs/clap/issues/1788#issuecomment-845496010 has precedent and falls into this category. It is not possible to (comprehensively) implement cli --pager=target if clap cannot be configured to write to (more) arbitrary targets. Redirecting standard I/O streams does not completely solve I/O routing for a CLI application.

Requiring clap to take on lifetimes again to pass around the streams would be a no-go.

For you printing everything yourself, my hope is that with the new styling API I plan to focus on for 4.x, we'll be able to provide you a render_help function that returns a type that will output ANSI escape codes (currently, we only allow access to output stripped of escape codes)

Regarding styling, perhaps detection and styles could be feature gated.

Any thoughts on something like this post-4.0? Thanks for taking a look!

My priority is going to be on the issues currently targeting 4.x. People are free to come up with a design for additional APIs but the further from my priorities, the less attention it will get. There will also be a high bar for impact on all users.