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

Derive a form (multiple prompts) from struct #65

Open
IniterWorker opened this issue Aug 12, 2022 · 16 comments
Open

Derive a form (multiple prompts) from struct #65

IniterWorker opened this issue Aug 12, 2022 · 16 comments
Assignees
Milestone

Comments

@IniterWorker
Copy link
Collaborator

IniterWorker commented Aug 12, 2022

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

  • Auto-generate prompt-inputs derived from a structure.
  • Inspiration could be find into Keats/validator.

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
Copy link
Owner

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?

@mikaelmello mikaelmello changed the title Feature - Prompt based on struct fields Derive a form (multiple prompts) from struct Aug 19, 2022
@mikaelmello mikaelmello added this to the v1.0.0 milestone Aug 19, 2022
@IniterWorker
Copy link
Collaborator Author

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
Copy link
Collaborator Author

IniterWorker commented Oct 23, 2022

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
Copy link
Collaborator Author

IniterWorker commented Oct 24, 2022

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
Copy link
Owner

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
Copy link
Owner

And again, this is awesome.

@IniterWorker
Copy link
Collaborator Author

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:

  • Should we create a dedicated "Trait" in the "inquire" library? (e.g. impl ToInquire for T)
  • Should we share context to create interactive struct edit (conditional error, response, validation)

Project

  • Do you envisage merging inquire and inquire_derive? Or do you prefer to keep things separated?
    • If yes, are you willing to rework the current repository to create a cargo workspace on your side?

@mikaelmello
Copy link
Owner

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
Copy link
Collaborator Author

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
Copy link
Owner

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

@mikaelmello
Copy link
Owner

Created: #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 IniterWorker self-assigned this Apr 4, 2023
IniterWorker added a commit that referenced this issue Apr 11, 2023
@IniterWorker
Copy link
Collaborator Author

Hi @mikaelmello, aafad17 is our starting point.

State of the art of inquire-derive:

  • The current implementation uses a lot of unwrap calls that provide poor error output.
  • The current design can't self-generate its fields' implementation, such as serde derive, because of a missing inquire/default trait.

Foreknew RoadMap:

  • Refactoring the inquire-derive to generate user-friendly compiler errors.
  • Complete the README.md with examples' links.
  • ...

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
Copy link
Owner

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.

@ewoolsey
Copy link

ewoolsey commented Apr 17, 2023

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.

IniterWorker added a commit that referenced this issue Apr 23, 2023
@simonsan
Copy link

simonsan commented Jan 3, 2024

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
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants