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

Add simple prompts #1634

Open
mayabyte opened this issue Jan 10, 2020 · 24 comments
Open

Add simple prompts #1634

mayabyte opened this issue Jan 10, 2020 · 24 comments
Labels
A-parsing Area: Parser's logic and needs it changed somehow. C-enhancement Category: Raise on the bar on expectations S-blocked Status: Blocked on something else such as an RFC or other implementation work.

Comments

@mayabyte
Copy link

mayabyte commented Jan 10, 2020

Maintainer's notes


Why?

Let's say you want to write a CLI program that requires the user to log in with a username and password, e.g. a CLI API for some web service. This is perfectly doable with arguments, but it may be preferred to prompt the user to supply these when they start the program, as follows:

$ example_program
> username: beepboop
> password: 
Successfully logged in!

Or, let's say you have an option that can be dangerous to enable in certain circumstances, and you want to ask the user if they're sure (like rm -rf does):

$ example_program --danger
> Are you sure you want to do the dangerous thing? [y/N] 

It's not hard to write this logic on your own, but given that these are rather common use-cases, it would be convenient to include this functionality within Clap.

What?

I propose adding a few simple prompting functions for handling common prompting situations. Here's a rough list:

  • prompt_if_absent(prompt: &str): Asks the user to supply a value for this argument if they didn't at run time. Displays prompt on the line where they type; in the first example above, the prompts would have been "> username: " and "> password: " respectively.
  • suggest_if_absent(prompt: &str, default: Fn() -> Option<String>): Like prompt_if_absent, but takes a function that can try to find a sensible default to suggest to the user, which can then be chosen by pressing enter without typing. Useful when you're not sure the default makes sense (e.g. if it's found from an envar or something), so you want to run it by the user to make sure.
  • ensure_if(prompt: &str, arg_id: Key, val: &str, default: Yes/No/None) and similar ensure_ifs: Asks the user if they're sure when they've set val(s) with a [y/n] prompt. The user can select the default option by just pressing enter.
  • prompt_secret(prompt: &str): like prompt_if_absent but doesn't show what you're typing

These can all be gated behind a prompts feature or something to keep the core functionality simple.

I realise there's been a little pushback on stuff like this in the past (~a few years ago?), but I do genuinely think it would be a nice addition. A lot of great CLI building tools in other languages include prompting functionality, so adding a few convenience methods for it reduces the friction required to port existing things over to Rust.

I'm happy to implement this myself if there's interest.

(Note: I'm aware of #1471, but the changes they suggest are much more significant and have potentially quite wide implications, so I consider it a separate matter. I'd love to see that get added though :P)

@danieleades
Copy link
Contributor

danieleades commented Jan 11, 2020

actually i think this is proposing a much more significant change than #1471.
you could resolve #1471 simply by adding a hook that takes a closure-

Arg::default_with<F, S>(mut self, fun: F) -> Self
    where
    F: FnOnce -> S,
    S: AsRef<str>,

then parsing your prompt function into this method

@mayabyte
Copy link
Author

Ah, that's true I suppose. Their suggestion of adding a get_matches_from_fns is definitely larger though.

Something like default_with would be pretty nice to have as well. You can kinda already fudge something like that with default_value and a closure full of prompting (or whatever else) code, but default_with could be lazily-evaluated which makes it nicer imo.

@danieleades
Copy link
Contributor

if you're prompting for user input it must be lazily evaluated, right?

@mayabyte
Copy link
Author

Yeah, but default_with could be used for other things besides prompting. I was porting a tool the other day that set the default of a field with the output of another program, so in that case default_with would be noticeably better than default_value due to laziness

@pksunkara
Copy link
Member

pksunkara commented Jan 18, 2020

Copying my response from #1471

I have worked on https://github.com/termapps/enquirer this week. Either a hook fn or a matches fn, this library can easily provide them. IMO, the prompts shouldn't be in the core. Hooks? yes but not prompts.

Implementation plan

  • If a required option is missing, and a default_with exists, then prompt happens.
  • If a flag is marked confirm_with, then when the flag is used, the prompt can confirm

It should cover all the use cases @mayabyte wanted since the type of prompts are irrelevant when we abstract them out as hooks.

@CreepySkeleton
Copy link
Contributor

Also, should we decide to go on the way of hooks, they would be impossible to serialize/deserialize since... well, how do you serialize a function 😄 ?

@danieleades
Copy link
Contributor

Also, should we decide to go on the way of hooks, they would be impossible to serialize/deserialize since... well, how do you serialize a function ?

Yeah it's not the easiest...

This exists- https://docs.rs/serde_closure/0.2.9/serde_closure/
But I suppose in general you could only use these hooks with programmatic argument building.

@CreepySkeleton
Copy link
Contributor

This exists- https://docs.rs/serde_closure/0.2.9/serde_closure/

...which generally serializes/deserializes the code as plain text. It brings in all the problems of eval in some languages, let alone the fact it allows a malicious user/attacker inject arbitrary code and execute it!

I'm really, totally, absolutely not OK with this approach.

But I suppose in general you could only use these hooks with programmatic argument building.

This sounds good.

@pksunkara pksunkara added T: new feature A-parsing Area: Parser's logic and needs it changed somehow. labels Feb 1, 2020
@pksunkara pksunkara added this to the 3.1 milestone Feb 1, 2020
@Dylan-DPC-zz
Copy link

I think this is beyond the scope of clap. You could use enquirer or I'd tackled a similar problem in clim which I never completed 😄

@pksunkara
Copy link
Member

Definitely, I am just keeping this issue open as a placeholder for hooks feature.

@CreepySkeleton
Copy link
Contributor

I think this is beyond the scope of clap.

I wouldn't be so categorical. Maybe not hooks, but some sort of prompt support would be pretty useful.

@pksunkara
Copy link
Member

I would say prompts are beyond the scope of main clap.

@pksunkara
Copy link
Member

Wanted to note that I am now a maintainer of https://github.com/mitsuhiko/dialoguer which means it will be easy to add compatibility for clap if needed.

@kbknapp
Copy link
Member

kbknapp commented Apr 29, 2020

I'd second the prompts being too far out of scope for clap. A hook I think is OK.

As for the serialization problem, I'm fine with just saying, "This one feature can't be serialized" to avoid the eval type issues, just like validators.

@tech6hutch
Copy link

Is there currently any way to make prompts in Clap, manually if necessary? I just don't want to repeat myself with arg parsing and error messages.

@danieleades
Copy link
Contributor

danieleades commented May 20, 2020

Is there currently any way to make prompts in Clap, manually if necessary? I just don't want to repeat myself with arg parsing and error messages.

Negative. But it looks like there's a reasonable concensus as to how to fit the pieces together.

Clap can add hooks which allow you to populate fields using a function. Then crates like dialoguer can handle the prompt. This might involve some interior mutability to lazily populate the value.

But then you run into questions like

  • if interactive prompts, why not config files too?
  • what is the precedence of config values (and how can I customise it)?

The design probably needs to consider these extensions.

@danieleades
Copy link
Contributor

@tech6hutch if you absolutely must have interactive prompts, you can try an approach I used. (I used structopt).

  • define an Args struct
  • define a PartialArgs struct (derive StructOpt) which is the same as the Args struct, but with Optional fields
  • implement From<PartialArgs> for Args with a bunch of unwrap_or_else(|| //prompt user) calls

@pksunkara pksunkara mentioned this issue Jul 6, 2021
2 tasks
@pksunkara pksunkara removed the W: 3.x label Aug 13, 2021
@pksunkara
Copy link
Member

This can be made a plugin when #3008 is done.

@epage epage removed the C: args label Dec 8, 2021
@epage epage removed this from the 3.1 milestone Dec 8, 2021
@epage epage added C-enhancement Category: Raise on the bar on expectations S-blocked Status: Blocked on something else such as an RFC or other implementation work. and removed T: new feature labels Dec 8, 2021
@cbzehner
Copy link
Contributor

cbzehner commented Feb 28, 2022

I recently wanted this functionality in a CLI tool and built on top of the suggestion @danieleades made above.

Here's a greeting example that uses the default_value_t from clap_derive to prompt the user when a value is missing.

@epage
Copy link
Member

epage commented Feb 28, 2022

A recent discussion explores a potential API to allow designing prompt support.

Here's a greeting example that uses the default_value_t from clap_derive to prompt the user when a value is missing.

That works? We should be evaluating default_value_t independent of whether a default value is needed or not.

@cbzehner
Copy link
Contributor

cbzehner commented Feb 28, 2022

Nope 🙃 Thanks Ed, Ah, I didn't realize we were evaluating it regardless - you're right that it doesn't work. It seems like it does from a happy-path example but it just evaluates every time rather than taking into account the arguments actually passed into Clap.

Back to the drawing board!

Edit: Here's a an example of the workaround using exactly the pattern suggested above

@beautyfree
Copy link

I really miss this kind of functionality https://typer.tiangolo.com/tutorial/options/prompt/

@beautyfree
Copy link

beautyfree commented Oct 9, 2023

Also prompts it's a better way to ask credentials/passwords (which with clap will be saved in history now)

@gabrywu
Copy link

gabrywu commented Dec 19, 2023

any updates here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-parsing Area: Parser's logic and needs it changed somehow. C-enhancement Category: Raise on the bar on expectations S-blocked Status: Blocked on something else such as an RFC or other implementation work.
Projects
None yet
Development

No branches or pull requests