Open simonsan opened 3 months ago
@simonsan Thank you for your proposal!
I would be interested if something like this is already existing, e.g. based on inquire, promkit, or if there is any interest to add this?
Currently, we do not offer such a feature (this is simply because I am not yet well-versed in Derive/macro). However, setting aside the specifics of the interface, I understand what you are looking to achieve and I am very interested in it.
Nice! In one issue, there is an example/demo of how such a thing could work: https://github.com/IniterWorker/inquire_derive
Maybe it helps to get started. Also, there is the The Little Book of Rust Macros and there is the proc-macro workshop by dtolnay. I'm saying this, because I'm trying to support and hope that such a thing will exist at one point in time, as I think it would be really valuable to the ecosystem. (:
@simonsan I've started by creating a prototype. I believe it needs to be refined further, but what do you think?
use promkit::{crossterm::style::Color, style::StyleBuilder, Result};
use promkit_derive::Promkit;
#[derive(Default, Debug, Promkit)]
struct MyStruct {
#[ask(
prefix = "What is your name?",
prefix_style = StyleBuilder::new().fgc(Color::DarkCyan).build(),
)]
name: String,
#[ask(prefix = "How old are you?", ignore_invalid_attr = "nothing")]
age: usize,
}
fn main() -> Result {
let ret = MyStruct::default().ask_name()?.ask_age()?;
dbg!(ret);
Ok(())
}
Looks really nice! 👍🏽 I need to try it out, to see if the API is ergonomic for me. But the way you instantiate and do it, is exactly how I would imagine it. In the end, it's like a builder pattern, that is being exposed directly to the user. For example, if you would like to ask for each field in the struct it would be pleasant to have a short calling method like MyStruct::ask()
.
Which would internally call Default::default()
on all fields, if #[ask(default)]
is set on this field.
returning a result, so we can return early with the try operator after each step
validate
could be implemented on top of it at later stage, there is a validator
crate, which API is ergonomic and could be inspiring. T.validate_args((77, 555)).is_ok()
sound like a good pattern to me as well
for attributes to annotate TypedBuilder
is interesting to look at for inspiration I think
Great work! 🌷
@simonsan Thank you for your comment! 👍 Please feel free to continue posting your feedback after you have had a chance to try it out. As a favor, could I ask for the opportunity to have you take another review the macro after I've refined it based on your feedback?
@simonsan Thank you for your comment! 👍 Please feel free to continue post your feedback after you have had a chance to try it out. As a favor, could I ask for the opportunity to have you take another look at the macro after I've refined it based on your feedback?
For sure! :) 👍🏽
@simonsan I refined it in PR #17. Here are the changes:
FromStr
). This is because it incorporates parse().The concerns are as follows, and I would appreciate your opinions:
ask
to readline
Example:
use promkit::{crossterm::style::Color, style::StyleBuilder, Result};
use promkit_derive::Promkit;
#[derive(Default, Debug, Promkit)]
struct Profile {
#[readline(
prefix = "What is your name?",
prefix_style = StyleBuilder::new().fgc(Color::DarkCyan).build(),
)]
name: String,
#[readline(default)]
hobby: Option<String>,
#[readline(prefix = "How old are you?", ignore_invalid_attr = "nothing")]
age: usize,
}
fn main() -> Result {
let mut ret = Profile::default();
ret.readline_name()?;
ret.readline_hobby()?;
ret.readline_age()?;
dbg!(ret);
Ok(())
}
Thanks 👍
I'm on the way to going out, so only tiny feedback:
I don't mind, if it's called readline
, or ask, or something. I think it should make clear the intent, of what is happening, and readline
might be sufficient for that. Could also be input
for example.
prefix
I personally find a bit confusing, I would rather call it prompt
as this would make it clear what it will do
for the supported fields I think something like a Vec
and a HashSet
(although we can build that from a Vec
might be good as well, like clap does it:
/// Tags for the activity
#[cfg_attr(
feature = "clap",
clap(
short,
long,
group = "adjust",
name = "Tags",
value_name = "Tags",
visible_alias = "tag",
value_delimiter = ','
)
)]
tags: Option<Vec<String>>,
Some naming ideas:
For the readline
attribute:
user_prompt
input_prompt
question
user_input
For the prefix
attribute:
prompt_text
question_text
message
query
@simonsan Thanks for your comments! 👍
Indeed, the terminology needs to be clearer about what can be done. It felt like it could also be offered as something separate from presets.
Accepting collections with delimiters is a good idea. On the other hand, incorporating various parser logics might make the macro complex (it already seemed quite complex when allowing for Option
). Therefore, what do you think about accepting parser functions in the attribute for types beyond primitives, like str -> Vec<T>
, str -> Option<T>
(I believe this was also in clap)?
Some idea:
use promkit::{crossterm::style::Color, style::StyleBuilder, Result};
use promkit_derive::Form;
#[derive(Default, Debug, Form)]
struct Profile {
#[input(
prompt = "What is your name?",
prompt_style = StyleBuilder::new().fgc(Color::DarkCyan).build(),
)]
name: String,
#[input(default)]
hobby: Option<String>,
#[input(prompt = "How old are you?", ignore_invalid_attr = "nothing")]
age: usize,
}
fn main() -> Result {
let mut ret = Profile::default();
ret.prompt_name()?;
ret.prompt_hobby()?;
ret.prompt_age()?;
dbg!(ret);
Ok(())
}
FormBuilder
could be also a viable option, as in:
use promkit_derive::FormBuilder;
#[derive(Default, Debug, FormBuilder)]
A form could be even layouted like this:
Please fill out the form:
------------------------
What is your name?: John Doe
What is your hobby? (Leave empty if none):
How old are you?: 25
Use Up/Down to navigate through the inputs
Press Enter if you are ready to continue
I don't know if that is easily possible, though 😅 It's not a dealbreaker, would just double down on the Form
Accepting collections with delimiters is a good idea. On the other hand, incorporating various parser logics might make the macro complex (it already seemed quite complex when allowing for
Option
). Therefore, what do you think about accepting parser functions in the attribute for types beyond primitives, likestr -> Vec<T>
,str -> Option<T>
(I believe this was also in clap)?
I agree completely. We don't want to make it too complex. I think clap
has https://docs.rs/clap/latest/clap/builder/struct.ValueParser.html for this.
I would like to easily annotate a configuration struct, to ask the user for input for various fields and give a hint on validation:
I would be interested if something like this is already existing, e.g. based on inquire, promkit, or if there is any interest to add this?
There are some ideas already in
inquire
, but it feels a bit out of scope.CC: https://github.com/mikaelmello/inquire/issues/212 CC: https://github.com/mikaelmello/inquire/issues/65
EDIT: It would basically be the
clap
way of doing interactive prompts. Annotate something and give it to the user to let it be filled out. I think it comes with its own advantages and disadvantages, and excels a lot in a workflow where you suddenly want the user to fill out some data you already have bundled in a struct.I think it could be a game changing feature, though. Because it would make dealing with interactive prompts much easier for a lot of users. Also, it would be excellent to have for people that don't want the full-fledged control of a
ratatui
TUI, but still a bit more ergonomic when it comes to interactivity and developer experience creating such.