Skip to content

Commit

Permalink
init promkit-derive crate
Browse files Browse the repository at this point in the history
  • Loading branch information
ynqa committed Mar 18, 2024
1 parent 3a4881d commit 74787df
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 27 deletions.
31 changes: 6 additions & 25 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,25 +1,6 @@
[package]
name = "promkit"
version = "0.4.0"
authors = ["ynqa <[email protected]>"]
edition = "2021"
description = "A toolkit for building your own interactive command-line tools"
repository = "https://github.com/ynqa/promkit"
license = "MIT"
readme = "README.md"

[lib]
name = "promkit"
path = "src/lib.rs"

[dependencies]
crossterm = { version = "0.27.0", features = ["use-dev-tty"] }
indexmap = "2.2.3"
radix_trie = "0.2.1"
serde = { version = "1.0.197" }
serde_json = { version = "1.0.114", features = ["preserve_order"] }
thiserror = "1.0.50"
unicode-width = "0.1.8"

[dev-dependencies]
strip-ansi-escapes = "0.2.0"
[workspace]
resolver = "2"
members = [
"promkit",
"promkit-derive",
]
18 changes: 18 additions & 0 deletions promkit-derive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "promkit-derive"
version = "0.1.0"
authors = ["ynqa <[email protected]>"]
edition = "2021"
description = "A derive macro for promkit"
repository = "https://github.com/ynqa/promkit"
license = "MIT"
readme = "README.md"

[lib]
proc-macro = true

[dependencies]
syn = { version = "2.0.52", features = ["full"] }
quote = "1.0"
proc-macro2 = "1.0"
promkit = { path = "../promkit", version = "0.4.0" }
20 changes: 20 additions & 0 deletions promkit-derive/examples/example.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
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(())
}
112 changes: 112 additions & 0 deletions promkit-derive/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
extern crate proc_macro;

use proc_macro::TokenStream;
use quote::quote;
use syn::{punctuated::Punctuated, DeriveInput, Meta, MetaNameValue, Token};

#[proc_macro_derive(Promkit, attributes(ask))]
pub fn promkit_derive(input: TokenStream) -> TokenStream {
let ast: DeriveInput = syn::parse(input).unwrap();

let name = &ast.ident;

let fields = match ast.data {
syn::Data::Struct(s) => s.fields,
_ => panic!("Promkit can only be derived for structs"),
};

let mut ask_fns = quote! {};
let mut run_fns = quote! {};

for field in fields.iter() {
for attr in field
.attrs
.iter()
.filter(|attr| attr.path().is_ident("ask"))
{
let field_ident = field.ident.as_ref().unwrap();
let field_type = &field.ty;
let ask_fn = syn::Ident::new(&format!("ask_{}", field_ident), field_ident.span());
let run_fn = syn::Ident::new(&format!("run_{}", field_ident), field_ident.span());

ask_fns = quote! {
#ask_fns

pub fn #ask_fn(mut self) -> promkit::Result<Self> {
let value_str = Self::#run_fn()?;
let parsed_value = value_str.parse::<#field_type>()
.map_err(|e| promkit::Error::ParseError(e.to_string()))?;
self.#field_ident = parsed_value;
Ok(self)
}
};

let mut prefix = quote! {};
let mut mask = quote! {};
let mut prefix_style = quote! {};
let mut active_char_style = quote! {};
let mut inactive_char_style = quote! {};

match &attr.meta {
Meta::List(list) => {
list.parse_args_with(Punctuated::<MetaNameValue, Token![,]>::parse_terminated)
.expect("`ask` attribute requires list of namevalue")
.into_iter()
.for_each(|entry| {
match entry.path.get_ident().unwrap().to_string().as_str() {
"prefix" => {
let expr = entry.value;
prefix = quote! { .prefix(format!("{} ", #expr)) };
}
"mask" => {
let expr = entry.value;
mask = quote! { .mask(#expr) };
}
"prefix_style" => {
let expr = entry.value;
prefix_style = quote! { .prefix_style(#expr) };
}
"active_char_style" => {
let expr = entry.value;
active_char_style = quote! { .active_char_style(#expr) };
}
"inactive_char_style" => {
let expr = entry.value;
inactive_char_style = quote! { .inactive_char_style(#expr) };
}
_ => (),
}
});
}
_ => {
panic!("`ask` attribute requires list of namevalue")
}
}

run_fns = quote! {
#run_fns

fn #run_fn() -> promkit::Result<String> {
promkit::preset::readline::Readline::default()
#prefix
#mask
#prefix_style
#active_char_style
#inactive_char_style
.prompt()?
.run()
}
};
}
}

let expanded = quote! {
impl #name {
#ask_fns

#run_fns
}
};

expanded.into()
}
3 changes: 1 addition & 2 deletions promkit/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "promkit"
version = "0.3.0"
version = "0.4.0"
authors = ["ynqa <[email protected]>"]
edition = "2021"
description = "A toolkit for building your own interactive command-line tools"
Expand All @@ -15,7 +15,6 @@ path = "src/lib.rs"
[dependencies]
crossterm = { version = "0.27.0", features = ["use-dev-tty"] }
indexmap = "2.2.3"
libc = "0.2.153"
radix_trie = "0.2.1"
serde = { version = "1.0.197" }
serde_json = { version = "1.0.114", features = ["preserve_order"] }
Expand Down

0 comments on commit 74787df

Please sign in to comment.