mikaelmello / inquire

A Rust library for building interactive prompts
https://docs.rs/inquire
MIT License
2.03k stars 75 forks source link

Derive a form (multiple prompts) from struct #65

Open IniterWorker opened 2 years ago

IniterWorker commented 2 years ago

Feature Proposal

It can be time-consuming to maintain or create an interactive configurable struct. I have a proposal to leverage Rust's features to reduce maintenance and implementation.

Mainlines

Demo

Draft

use inquire::ToInquire;
use strum::IntoEnumIterator; // usefull third-party lib/macros

#[derive(Debug, Display, EnumIter)]
pub enum MyEnum {
    EV_1 = 0,
    EV_2 = 1,
    EV_3 = 2,
}

#[derive(ToInquire, Default)]
#[inquire(default)] // general struct
pub struct Basic {
    #[inquire(default)]
    ev: MyEnum,
    #[inquire(default, validator = "custom::validator")] // custom validator
    num: u8,
    #[inquire(ignore)] // ignore field
    ignored: u8,
}

//
// # derive auto-gen
// fn inquire() -> Result<(), Error>  {
//  let list_evs = MyEnum::iter().collect::<Vec<_>>();
//  let ans = Select::new("What's your ev?", list_evs).prompt()?;
//  let num = Text::new("What's your num?").prompt()?;
// }
//
// # Example
// inquire::to_prompt(&T)
// - What's your ev? (Default::default)
// - What's your num? (Default::default)
//

Do you have any thoughts or derivative-features?

mikaelmello commented 2 years ago

That is a very good idea.

In my opinion this should be a separate package that builds prompts via the public API only. What do you think?

Also, would this be something you'd be willing to implement?

IniterWorker commented 2 years ago

Finally, I love the idea.

The pros of integrating it into the same lib, it's easier for the final user to use it. And, it's a handy feature to be ignored by a developer. It could stream a lot of things very quickly in a cli design. What do you think about it?

After thought is very nice to have a default value from the current in-memory structure data versus the Default::default.

PS: the holiday keeps me busy :-)

IniterWorker commented 2 years ago

Hi @mikaelmello,

Hope you're doing well,

I started a new crate to work on inquire's proc-macro derive, and I'd like your advise.

Trait InquireInvoke

pub trait InquireInvoke {
    fn inquire() -> InquireResult<()>;
}

Macro FieldType

pub enum FieldType {
    Text {
        prompt_message: Option<String>,
        help_message: Option<String>,
        default_value: Option<String>,
        initial_value: Option<String>,
        placeholder_value: Option<String>,
        validators: Option<Vec<String>>,
        formatter: Option<String>,
        suggester: Option<String>,
        autocompleter: Option<String>,
    },
    // ...
}

What do you think about both approaches?

Inline

#[derive(InquireInvoke)
pub struct MyStruct {
    #[inquire(field = "text", prompt_message = "What is your name?", default_value = "IniterWorker")]
    name: String,
}

Inner

#[derive(InquireInvoke)
pub struct MyStruct {
    #[inquire(text(prompt_message = "What is your name?", default_value = "IniterWorker"))]
    name: String,
}
IniterWorker commented 2 years ago

Finally, I implemented the inner approach. It seems the way to go after a lot of brainstorming in my head.

I started an experimental repository/playground. It is not perfect and not finished yet. But you might have an opinion on the proc-macro flow and testing the inquire::Text.

Experimental/Demo - inquire_derive

Thank you,

mikaelmello commented 2 years ago

That is so so awesome. Honestly I don't feel like I'm currently able to contribute a lot, never dove too deep on the proc-macro side of Rust although I always wanted to.

I skimmed through the repo and overall felt like everything was in place, but again I don't know much about it so I wouldn't be able to differentiate lol

My (irrelevant for now) suggestions would be to ask what you think about InquireForm as the trait name to be derived, feels more like the end product for me.

Next weekend I'll try to dive deeper on your repo to try and have some actual suggestions/contributions on it.

mikaelmello commented 2 years ago

And again, this is awesome.

IniterWorker commented 2 years ago

Improvements

My (irrelevant for now) suggestions would be to ask what you think about InquireForm as the trait name to be derived, feels more like the end product for me.

Done!

Design

During the implementation of inquire_derive two main subjects popped up:

Project

mikaelmello commented 1 year ago

Sorry for the delay! Completely lost the notification email after reading it.

Regarding merging both, I'd be more than glad to merge if that's something you're looking forward to. I'll be honest in that I haven't dedicated as much time as I wanted to inquire, so I also don't want to be a blocker for you (as I have kinda been...). Would you prefer to keep things separate? Or maybe merging and becoming a maintainer here as well?

Regarding the design questions, I'm still gonna take a dive on your repo and be able to have an opinion

IniterWorker commented 1 year ago

No worries about it. I want to find the best way to land in your repository. Should we rework it as a workspace like serde's crate? inquire_derive is setup likewise.

Regarding the design questions, I'm still gonna take a dive on your repo and be able to have an opinion

It would be great! You will find out one main change from your last dive: Parametrics macro variables are now Expr to mitigate the issue with a no-context approach and allow custom code. But, macro's Expr downside is that static strs are escaped.

Thanks,

mikaelmello commented 1 year ago

Yes, let's rework it as a workspace! I'll put it in the (small amount needed of) work tomorrow.

mikaelmello commented 1 year ago

Created: https://github.com/mikaelmello/inquire/pull/87

Want to take a look? I think that's all there is to it, so it's just a matter of porting it to this repo I think

IniterWorker commented 1 year ago

Hi @mikaelmello, https://github.com/mikaelmello/inquire/commit/aafad1767dab24d34733a4e81317f84cc2fa6dd2 is our starting point.

State of the art of inquire-derive:

Foreknew RoadMap:

inquire crate's trait strategy

Should we create a trait for all primitives that will be implemented enough to be used in inquire-derive to generate a default inquire prompt?

Example without trait from inquire

#[derive(Debug, InquireForm)]
pub struct TestStruct {
    #[inquire(editor(prompt_message = "\"What's your text?\"",))]
    pub text: String,
}

Example with trait form inquire

#[derive(Debug, InquireForm)]
pub struct TestStruct {
    pub text: String,
}
mikaelmello commented 1 year ago

Should we create a trait for all primitives that will be implemented enough to be used in inquire-derive to generate a default inquire prompt?

Providing plug-n-go features is certainly my goal, so it is a direction that makes sense to me.

0xForerunner commented 1 year ago

Hey there @mikaelmello @IniterWorker ! was reading through these issues and found this. I have a crate that is built on top of inquire and does exactly this, check it out! https://crates.io/crates/interactive-parse. It's still a very young project though is fully feature complete. It has the advantage of using a very popular preexisting derive, so you're much more likely to be able to use it with external types.

simonsan commented 10 months ago
use inquire::AskUser;

#[derive(Debug, AskUser)]
struct MyConf {
    #[ask(q="Please enter password:", hide_input=true, min_len=12)]
    password: Option<String>,

    #[ask(q="Please enter username:", min_len=3)]
    username: Option<String>,

    #[ask(q="Please enter age:", range=18..=85)]
    age: Option<u8>,
}

fn main() -> Result<()> {
    let mut conf = MyConf::default()
    conf.password()?.validate()?;
    conf.username()?.validate()?;
    conf.age()?.validate()?;

    println!("{conf#?}");
}

Copying over, this feature would be awesome!

rakshith-ravi commented 6 months ago

Hi! Author of the preprocess crate here. Is there any way I can help with integrating the validation / preprocessing layer with the preprocess crate?