cursive-macros-0.1.0/.cargo_vcs_info.json0000644000000001540000000000100137740ustar { "git": { "sha1": "4269f524ba80aaca8d9b068d7fa32dd3a12bc7db" }, "path_in_vcs": "cursive-macros" }cursive-macros-0.1.0/Cargo.toml0000644000000025750000000000100120030ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "cursive-macros" version = "0.1.0" authors = ["Alexandre Bury "] build = false include = ["src/**/*"] autobins = false autoexamples = false autotests = false autobenches = false description = "Proc-macros for the cursive TUI library." documentation = "https://docs.rs/cursive-macros" readme = "Readme.md" keywords = [ "ncurses", "TUI", "UI", ] categories = [ "command-line-interface", "gui", ] license = "MIT" repository = "https://github.com/gyscos/cursive" [lib] name = "cursive_macros" path = "src/lib.rs" proc-macro = true [dependencies.find-crate] version = "0.6.3" optional = true [dependencies.proc-macro2] version = "1.0.47" [dependencies.quote] version = "1.0.21" optional = true [dependencies.syn] version = "2" features = [ "full", "extra-traits", ] optional = true [features] builder = [ "find-crate", "syn", "quote", ] cursive-macros-0.1.0/Cargo.toml.orig000064400000000000000000000014601046102023000154540ustar 00000000000000[package] authors = ["Alexandre Bury "] categories = ["command-line-interface", "gui"] description = "Proc-macros for the cursive TUI library." documentation = "https://docs.rs/cursive-macros" edition = "2021" keywords = ["ncurses", "TUI", "UI"] license = "MIT" name = "cursive-macros" readme = "Readme.md" repository = "https://github.com/gyscos/cursive" version = "0.1.0" include = ["src/**/*"] [lib] proc-macro = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] find-crate = { optional = true, version = "0.6.3" } proc-macro2 = "1.0.47" quote = {version = "1.0.21", optional = true } syn = { version = "2", features = ["full", "extra-traits"], optional = true } [features] builder = ["find-crate", "syn", "quote"] cursive-macros-0.1.0/Readme.md000064400000000000000000000003041046102023000143000ustar 00000000000000# cursive-macros This crate defines procedural macros for use with cursive. You probably don't need to use this directly; instead, look at the re-exported macros in `cursive` or `cursive-core`. cursive-macros-0.1.0/src/builder/blueprint.rs000064400000000000000000000510431046102023000173560ustar 00000000000000use proc_macro::TokenStream; use proc_macro2::Span; use quote::quote; use std::collections::HashSet; fn is_single_generic<'a>(path: &'a syn::Path, name: &str) -> Option<&'a syn::Type> { if path.segments.len() != 1 { return None; } let segment = &path.segments[0]; if segment.ident != name { return None; } if let syn::PathArguments::AngleBracketed(args) = &segment.arguments { if args.args.len() != 1 { return None; } let arg = &args.args[0]; if let syn::GenericArgument::Type(ty) = arg { Some(ty) } else { None } } else { None } } // fn is_vec(path: &syn::Path) -> Option<&syn::Type> { // // TODO: handle std::vec::Vec? // is_single_generic(path, "Vec") // } fn is_option(path: &syn::Path) -> Option<&syn::Type> { // TODO: handle std::option::Option? is_single_generic(path, "Option") } fn is_option_type(ty: &syn::Type) -> Option<&syn::Type> { match ty { syn::Type::Path(syn::TypePath { ref path, .. }) => is_option(path), _ => None, } } fn looks_inferred(ty: &syn::Type) -> bool { match ty { syn::Type::Infer(_) => true, syn::Type::Path(syn::TypePath { path, .. }) => { if let Some(ty) = is_option(path) { looks_inferred(ty) } else { false } } _ => false, } } fn parse_enum( item: &syn::ItemEnum, params: &HashSet, base: &syn::Expr, root: &proc_macro2::TokenStream, ) -> syn::parse::Result { // Try to find disjoint set of config targets let mut cases = Vec::new(); for variant in &item.variants { // Possible sources: // - Null (unit) // - String (single tuple) // - Number (single tuple with number type?) // - Array (tuple with >1 items or tuple with a single array) // - Object (struct) let variant_name = variant.ident.to_string(); match &variant.fields { syn::Fields::Unnamed(fields) => { if fields.unnamed.len() == 1 { // Direct value? // String! With name of variant as ident? // variant.ident // The match case let consumer = parse_struct(&variant.fields, params, &variant_name, base, root)?; cases.push(quote! { match || -> Result<_, crate::builder::Error> { Ok({#consumer}) }() { Ok(res) => return Ok(res), Err(err) => errors.push(err), } }); } else { // Array? unimplemented!("Non-singleton in tuple variant"); } } syn::Fields::Named(_) => { // An object. let consumer = parse_struct(&variant.fields, params, &variant_name, base, root)?; cases.push(quote! { match || -> Result<_, crate::builder::Error> { Ok({#consumer}) }() { Ok(res) => return Ok(res), Err(err) => errors.push(err), } }); } syn::Fields::Unit => { // Null? cases.push(quote! { if config.is_null() { return Ok({#base}); } else { } }); } } } Ok(quote! { || -> Result<_, crate::builder::Error> { let mut errors = Vec::new(); #(#cases)* Err(#root::builder::Error::AllVariantsFailed { config: config.clone(), errors }) }()? }) } // #[blueprint( // config = false, // setter = set_enabled, // config_name = "foo", // foreach = add_stuff, // callback = true, // default = Foo::default(), // )] struct VariableSpecs { no_config: bool, setter: Option, foreach: Option, callback: Option, config_name: Option, default: Option, } struct Variable { // How to load the variable from a config. loader: Loader, // How to call the variable between loading and consuming. // Mostly irrelevant, except for constructor. ident: syn::Ident, // How to consume the data (set some fields?) consumer: Consumer, } struct Loader { // When `true`, load it through a `cursive::builder::NoConfig`. no_config: bool, // How the value is called in the config. config_name: Option, // Type we want to load into the ident. ty: syn::Type, // Default constructor for the value. default: Option, } impl Loader { fn load( &self, ident: &syn::Ident, root: &proc_macro2::TokenStream, ) -> proc_macro2::TokenStream { let ty = &self.ty; let mut resolve_type = quote! { #ty }; let mut suffix = quote! {}; let config = if let Some(ref config_name) = self.config_name { quote! { &config[#config_name] } } else { quote! { &config } }; if let Some(ref default) = self.default { resolve_type = quote! { Option<#resolve_type> }; suffix = quote! { .unwrap_or_else(|| #default) #suffix }; } if self.no_config { resolve_type = quote! { #root::NoConfig<#ty> }; suffix = quote! { .into_inner() #suffix }; } // Some constructor require &mut access to the fields quote! { let mut #ident: #ty = context.resolve::<#resolve_type>( #config )? #suffix; } } } impl VariableSpecs { fn parse(field: &syn::Field) -> syn::parse::Result { let mut result = VariableSpecs { no_config: false, setter: None, foreach: None, callback: None, config_name: None, default: None, }; // Look for an explicit #[blueprint] for attr in &field.attrs { if !attr.path().is_ident("blueprint") { continue; } // eprintln!("Parsing {attr:?}"); attr.parse_nested_meta(|meta| { // eprintln!("Parsed nested meta: {:?} / {:?}", meta.path, meta.input); if meta.path.is_ident("config") { let value = meta.value()?; let config: syn::LitBool = value.parse()?; result.no_config = !config.value(); } else if meta.path.is_ident("foreach") { let value = meta.value()?; let foreach = value.parse()?; result.foreach = Some(foreach); } else if meta.path.is_ident("setter") { let value = meta.value()?; let setter = value.parse()?; result.setter = Some(setter); } else if meta.path.is_ident("callback") { let value = meta.value()?; let callback: syn::LitBool = value.parse()?; result.callback = Some(callback.value()); } else if meta.path.is_ident("default") { let value = meta.value()?; let default: syn::Expr = value.parse()?; result.default = Some(default); } else if meta.path.is_ident("config_name") { let value = meta.value()?; let name: syn::LitStr = value.parse()?; result.config_name = Some(name.value()); } else { panic!("Unrecognized ident: {:?}", meta.path); } Ok(()) })?; } Ok(result) } } impl Variable { fn load(&self, root: &proc_macro2::TokenStream) -> proc_macro2::TokenStream { self.loader.load(&self.ident, root) } fn consume(&self, base_ident: &syn::Ident) -> proc_macro2::TokenStream { self.consumer.consume(base_ident, &self.ident) } fn parse( field: &syn::Field, struct_name: &str, constructor_fields: &HashSet, ) -> syn::parse::Result { // First: is it one of the constructor fields? If so, skip the setter. let specs = VariableSpecs::parse(field)?; // An example from TextView: // // ``` // #[crate::blueprint(TextView::empty())] // enum Blueprint { // Empty, // // Content(String), // // Object { content: Option }, // } // ``` // // Here we should be able to parse both: // * `Object { content: Object }` as usual. // * `Content(String)` as `set_content(...)` // For this, we need to know the name of the "struct" (here `Content`), // since the field itself (`String`) is unnamed. let parameter_name = match field.ident { Some(ref ident) => ident.to_string(), None => struct_name.to_lowercase(), }; let ident = syn::Ident::new(¶meter_name, Span::call_site()); // Only one of setter/foreach/callback. let mut consumer = if constructor_fields.contains(¶meter_name) { Consumer::Noop } else { let inferred_type = looks_inferred(&field.ty); match (specs.setter, specs.foreach, specs.callback, inferred_type) { (None, None, None | Some(false), false) | (None, None, Some(false), true) => { // Default case: use a setter based on the ident. let setter = format!("set_{parameter_name}"); Consumer::Setter(Setter { method: syn::Ident::new(&setter, Span::call_site()), }) } (Some(setter), None, None | Some(false), _) => { // Explicit setter function Consumer::Setter(Setter { method: setter }) } (None, Some(foreach), None | Some(false), _) => { // Foreach function (like `add_item`) Consumer::ForEach(Box::new(Consumer::Setter(Setter { method: foreach }))) } (None, None, Some(true), _) | (None, None, _, true) => { // TODO: Check that the type is iterable? A Vec? // Callback flag means we use the callback_helper-generated `_cb` setter. let setter = format!("set_{parameter_name}_cb"); Consumer::Setter(Setter { method: syn::Ident::new(&setter, Span::call_site()), }) } _ => panic!("unsupported configuration"), } }; // Some types have special handling if is_option_type(&field.ty).is_some() { consumer = Consumer::Opt(Box::new(consumer)); } // Now, how to fetch the config? // Either specs.config_name, or the parameter name let loader = Loader { no_config: specs.no_config, config_name: specs .config_name .or_else(|| field.ident.as_ref().map(|i| i.to_string())), ty: field.ty.clone(), default: specs.default, }; Ok(Variable { consumer, loader, ident, }) } } struct Setter { method: syn::Ident, } impl Setter { fn consume( &self, base_ident: &syn::Ident, field_ident: &syn::Ident, ) -> proc_macro2::TokenStream { let function = &self.method; quote! { #base_ident.#function(#field_ident); } } } /// Defines how to consume/use a variable. /// /// For example: just include it in the constructor, or run a method on the item, ... enum Consumer { // Not individually consumed. // // Most likely, the value is used in the constructor, no need to set it here. Noop, // The value is a Vec and we need to call something on each item. ForEach(Box), Opt(Box), // We need to call this method to "set" the value. // // TODO: support more than just ident (expr? closure?) Setter(Setter), } impl Consumer { fn consume( &self, base_ident: &syn::Ident, field_ident: &syn::Ident, ) -> proc_macro2::TokenStream { match self { Consumer::Noop => quote! {}, Consumer::Setter(setter) => setter.consume(base_ident, field_ident), Consumer::Opt(consumer) => { let consumer = consumer.consume(base_ident, field_ident); quote! { if let Some(#field_ident) = #field_ident { #consumer } } } Consumer::ForEach(consumer) => { let consumer = consumer.consume(base_ident, field_ident); quote! { for #field_ident in #field_ident { #consumer } } } } } } // Returns the quote!d code to build the object using this struct. fn parse_struct( fields: &syn::Fields, parameter_names: &HashSet, struct_name: &str, base: &syn::Expr, root: &proc_macro2::TokenStream, ) -> syn::parse::Result { // Assert: no generic? let fields = match fields { syn::Fields::Named(fields) => &fields.named, syn::Fields::Unnamed(fields) => &fields.unnamed, syn::Fields::Unit => { return Ok(quote! { #base }); } // Nothing to do! }; // We'll build: // - A list of parameter loaders // - A list of setter loaders let base_ident = syn::Ident::new("_res", Span::call_site()); let vars: Vec = fields .iter() .map(|field| Variable::parse(field, struct_name, parameter_names)) .collect::>()?; let loaders: Vec<_> = vars.iter().map(|var| var.load(root)).collect(); let consumers: Vec<_> = vars.iter().map(|var| var.consume(&base_ident)).collect(); Ok(quote! { #(#loaders)* let mut #base_ident = #base ; #(#consumers)* #base_ident }) } // Direct parsing (with minimal processing) of the attributes from a blueprint. struct BlueprintAttributes { // Base expression to build. Ex: `TextView::new()`. // Might rely on variables in base_parameters. base: syn::Expr, // Set of parameter names we need for the constructor. // These might not need to be set separately. base_parameters: HashSet, // Name for the blueprint. name: String, } fn find_parameters(expr: &syn::Expr, parameters: &mut HashSet) { match expr { // Handle the main `View::new(...)` expression. syn::Expr::Call(syn::ExprCall { args, .. }) => { for arg in args { find_parameters(arg, parameters); } } // Handle individual variables given as parameters. syn::Expr::Path(syn::ExprPath { path, .. }) => { if path.segments.len() == 1 { parameters.insert(path.segments[0].ident.to_string()); } } // Handle method calls like `var1.to_lowercase()` syn::Expr::MethodCall(syn::ExprMethodCall { receiver, args, .. }) => { find_parameters(receiver, parameters); for arg in args { find_parameters(arg, parameters); } } // Handle `[var1, var2]` syn::Expr::Array(syn::ExprArray { elems, .. }) => { for elem in elems { find_parameters(elem, parameters); } } syn::Expr::Reference(syn::ExprReference { expr, .. }) => find_parameters(expr, parameters), _ => (), } } fn base_default_name(expr: &syn::Expr) -> Option { // From `TextView::new(content)`, return `TextView`. // If the expression is not such a method call, bail. let func = match expr { syn::Expr::Call(syn::ExprCall { func, .. }) => func, _ => return None, }; let path = match &**func { syn::Expr::Path(syn::ExprPath { path, .. }) => path, _ => return None, }; let struct_name_id = path.segments.len().checked_sub(2)?; let ident = &path.segments[struct_name_id].ident; Some(ident.to_string()) } impl syn::parse::Parse for BlueprintAttributes { // Parse attributes for a blueprint. Ex: // #[blueprint(TextView::new(content), name="Text")] fn parse(input: syn::parse::ParseStream<'_>) -> syn::parse::Result { let base: syn::Expr = input.parse()?; let mut base_parameters = HashSet::new(); find_parameters(&base, &mut base_parameters); // Compute name and parameters from the expression. let mut name = base_default_name(&base).unwrap_or_default(); // We can't parse this as a regular nested meta. // Parse it as a list of `key = value` items. // So far only `name = "Name"` is supported. while input.peek(syn::Token![,]) { let _comma: syn::Token![,] = input.parse()?; let path: syn::Path = input.parse()?; let _equal: syn::Token![=] = input.parse()?; if path.is_ident("name") { let value: syn::LitStr = input.parse()?; name = value.value(); } } Ok(BlueprintAttributes { base, base_parameters, name, }) } } pub fn blueprint(attrs: TokenStream, item: TokenStream) -> TokenStream { let input = syn::parse_macro_input!(item as syn::Item); // Parse the initial things given to the #[blueprint(...)] macro. // We expect: // Positional, first argument: an expression. // Optional, named arguments: // name = "BlueprintName" let attributes = syn::parse_macro_input!(attrs as BlueprintAttributes); // Either cursive or cursive_core are good roots. // If we can't find it, assume it's building cursive_core itself. let root = match find_crate::find_crate(|s| { s == "cursive" || s == "cursive-core" || s == "cursive_core" }) { Ok(cursive) => { let root = syn::Ident::new(&cursive.name, Span::call_site()); quote! { ::#root } } Err(_) => { quote! { crate } } }; // Then, from the body, we can get: // * A list of setter variables // * A way to check each setter variable and each parameter. let builder = match &input { syn::Item::Enum(item) => { if !item.generics.params.is_empty() { panic!("expected non-generic struct"); } // Option A.1: enums are named after a data type (eg: String, Array, Object) // Option A.2: enums are using disjoint data types // If we can, build a mapping of config types to consumers // if let Some(map) = make_map() { // // // let mut cases = Vec::new(); // quote! { // match config { // #(#cases),* // } // } // } else { // // Plan B: try enums one by one until one works // unimplemented!(); // } parse_enum(item, &attributes.base_parameters, &attributes.base, &root).unwrap() } syn::Item::Struct(item) => { if !item.generics.params.is_empty() { panic!("expected non-generic struct"); } let struct_name = item.ident.to_string(); parse_struct( &item.fields, &attributes.base_parameters, &struct_name, &attributes.base, &root, ) .unwrap() } _ => panic!("Expected enum or struct"), }; let ident = syn::Ident::new(&attributes.name, Span::call_site()); let result = quote! { #root::manual_blueprint!(#ident, |config, context| { Ok({ #builder }) }); }; // eprintln!("Res: {result}"); result.into() } cursive-macros-0.1.0/src/builder/callback_helper.rs000064400000000000000000000456061046102023000204550ustar 00000000000000use proc_macro::TokenStream; use proc_macro2::Span; use quote::quote; fn find_arg_name<'a>(signature: &'a syn::Signature, type_name: &'a syn::Ident) -> &'a syn::Ident { for arg in &signature.inputs { let arg = match arg { syn::FnArg::Typed(arg) => arg, _ => continue, }; let path = match arg.ty.as_ref() { syn::Type::Path(path) => &path.path, _ => continue, }; if path.is_ident(type_name) { match arg.pat.as_ref() { syn::Pat::Ident(ident) => return &ident.ident, _ => panic!("Argument is an unsupported pattern."), } } } panic!("Could not find argument with type {type_name}."); } // Return a stream of all generics involved in this type. // // We'll want to wrap the function, and we need to make it just as generic. fn find_dependent_generics( signature: &syn::Signature, bound: &syn::TraitBound, ) -> proc_macro2::TokenStream { use std::collections::HashMap; // Visit all idents in this path. fn visit_path_idents(p: &syn::Path, f: &mut impl FnMut(&syn::Ident)) { for segment in &p.segments { f(&segment.ident); match &segment.arguments { syn::PathArguments::AngleBracketed(arguments) => { for argument in &arguments.args { match argument { syn::GenericArgument::Type(t) => visit_type_idents(t, f), syn::GenericArgument::AssocType(t) => visit_type_idents(&t.ty, f), syn::GenericArgument::AssocConst(_c) => { // Visit c.expr ? } _ => (), } } } syn::PathArguments::Parenthesized(arguments) => { for t in &arguments.inputs { visit_type_idents(t, f); } if let syn::ReturnType::Type(_, t) = &arguments.output { visit_type_idents(t, f); } } _ => (), } } } // Visit all idents in this type. fn visit_type_idents(t: &syn::Type, f: &mut impl FnMut(&syn::Ident)) { match t { syn::Type::Paren(t) => visit_type_idents(&t.elem, f), syn::Type::Path(p) => { if let Some(qs) = &p.qself { visit_type_idents(&qs.ty, f); } visit_path_idents(&p.path, f) } syn::Type::Array(a) => visit_type_idents(&a.elem, f), syn::Type::Group(g) => visit_type_idents(&g.elem, f), syn::Type::Reference(r) => visit_type_idents(&r.elem, f), syn::Type::Slice(s) => visit_type_idents(&s.elem, f), syn::Type::Tuple(t) => { for t in &t.elems { visit_type_idents(t, f); } } _ => (), } } fn check_new_dependent( // signature: &syn::Signature, relevant: &mut HashMap, bound: &syn::TraitBound, ) { let mut new_idents = Vec::new(); visit_path_idents(&bound.path, &mut |ident| { if let Some(r) = relevant.get_mut(ident) { if !(*r) { *r = true; new_idents.push(ident.clone()); // Find the new bound check_new_dependent(/* signature, */ relevant, bound); } } }); } let mut relevant: HashMap = signature .generics .type_params() .map(|t| (t.ident.clone(), false)) .collect(); // Maps type ident to Vec that mention this type. // So we know if we need the type, we probably need to include the bound? let mut bounds = HashMap::new(); // Look for links in the generics from the function definition. for bound in signature .generics .type_params() .flat_map(|t| &t.bounds) .filter_map(|bounds| match bounds { syn::TypeParamBound::Trait(bound) => Some(bound), _ => None, }) { // Attach this bound to all relevant types visit_path_idents(&bound.path, &mut |ident| { // Register this bound as relevant to this event. bounds .entry(ident.clone()) .or_insert_with(Vec::new) .push(bound); }); } // Look for links in the where clause. // For example there could be `where T: Into`, in which case if we need T, we need `U`. // // if let Some(ref where_clause) = signature.generics.where_clause { // for pred in &where_clause.predicates { // match pred { // syn::WherePredicate::Type(t) => for bound in &t.bounds {}, // syn::WherePredicate::Lifetime(l) => (), // syn::WherePredicate::Eq(e) => (), // } // } // } check_new_dependent(/* signature, */ &mut relevant, bound); let generics: Vec<_> = signature .generics .type_params() .filter(|t| relevant[&t.ident]) .collect(); quote! { #(#generics),* } } /// Returns (Bounds, argument name, Optional) /// /// The type name is not available if the arg is impl Fn() fn find_fn_generic( signature: &syn::Signature, ) -> Option<(&syn::TraitBound, &syn::Ident, Option<&syn::Ident>)> { // Option A: fn foo(f: F) for param in signature.generics.type_params() { for bound in ¶m.bounds { let bound = match bound { syn::TypeParamBound::Trait(bound) => bound, _ => continue, }; let segment = match bound.path.segments.iter().last() { Some(segment) => segment, None => continue, }; if segment.ident == "Fn" || segment.ident == "FnMut" || segment.ident == "FnOnce" { let arg_name = find_arg_name(signature, ¶m.ident); // This is it! return Some((bound, arg_name, Some(¶m.ident))); } } } // Option B: fn foo(f: F) where F: Fn() for predicate in &signature.generics.where_clause.as_ref()?.predicates { let predicate = match predicate { syn::WherePredicate::Type(predicate) => predicate, _ => continue, }; for bound in &predicate.bounds { let bound = match bound { syn::TypeParamBound::Trait(bound) => bound, _ => continue, }; let segment = match bound.path.segments.iter().last() { Some(segment) => segment, None => continue, }; if segment.ident == "Fn" || segment.ident == "FnMut" || segment.ident == "FnOnce" { // This is it! let ident = match &predicate.bounded_ty { syn::Type::Path(path) => path .path .get_ident() .expect("expected single-ident for this type"), _ => panic!("expected generic type for this bound"), }; let arg_name = find_arg_name(signature, ident); return Some((bound, arg_name, Some(ident))); } } } // Option C: fn foo(f: impl Fn()) for arg in &signature.inputs { let arg = match arg { syn::FnArg::Typed(arg) => arg, _ => continue, }; let impl_trait = match arg.ty.as_ref() { syn::Type::ImplTrait(impl_trait) => impl_trait, _ => continue, }; for bound in &impl_trait.bounds { let bound = match bound { syn::TypeParamBound::Trait(bound) => bound, _ => continue, }; let segment = match bound.path.segments.iter().last() { Some(segment) => segment, None => continue, }; if segment.ident == "Fn" || segment.ident == "FnMut" || segment.ident == "FnOnce" { // Found it! let arg_name = match arg.pat.as_ref() { syn::Pat::Ident(ident) => &ident.ident, _ => panic!("Argument is an unsupported pattern."), }; return Some((bound, arg_name, None)); } } } None } fn bound_to_dyn(bound: &syn::TraitBound) -> proc_macro2::TokenStream { quote!(::std::sync::Arc) } fn get_arity(bound: &syn::TraitBound) -> usize { let segment = bound.path.segments.iter().last().unwrap(); let args = match &segment.arguments { syn::PathArguments::Parenthesized(args) => args, _ => panic!("Expected Fn trait arguments"), }; args.inputs.len() } /// Generate two helper functions to help working with callbacks in cursive blueprints. /// /// # Problem to solve /// /// When writing cursive blueprints, it is often necessary to load parameters or variables. /// /// Some of these have simple types like `u64` or `String`, but in some cases we need to load a /// callback. Most of the time, the existing setter function will take a generic ``. /// /// In this case, the blueprint loading the variable and the user storing the variable need /// to use the exact same type (otherwise, downcasting will not work). This is made complicated by Rust's /// closures, where each closure is a unique anonymous type: if the user directly stores a closure, /// there will be no way to identify its exact type to downcast it in the blueprint. /// /// Instead, both sides (blueprint and user) need to agree to use a fixed type, for example a trait /// object like `Arc` (we use `Arc` rather than `Box` because we want variables /// to be cloneable). /// /// It's a bit cumbersome having to write the exact type including the `Arc` whenever we want to /// store a callback for a blueprint. Similarly, it's a bit annoying when writing the blueprint to make /// sure the correct `Arc<...>` type is fetched and converted to a type directly usable as callback. /// Most importantly, it increases the chances of the two sides not using _exactly_ the same type, /// leading to failures when attempting to load the variable for the blueprint. /// /// # Solution /// /// This is where this macro comes into play: from an original function that requires a closure, it /// generates two helper functions: /// * A _maker_ function, to be used when storing variables. This function takes a generic type /// implementing the same `Fn` trait as the desired callback, and returns it wrapped in the /// correct trait object. It will be named `{name}_cb`, where `{name}` is the name of the /// original function this macro is attached to. /// * A _setter_ function, to be used when writing blueprints. This function wraps the original /// function, but takes a trait-object instead of a generic `Fn` type, and unwraps it /// internally. It will be named `{name}_with_cb`, where `{name}` is the name of the original /// function this macro is attached to. /// /// # Notes /// /// * The wrapped function doesn't even have to take `self`, it can be a "static" constructor /// method. /// * The `maker` function always takes a `Fn`, not a `FnMut` or `FnOnce`. Use the /// `cursive::immut1!` (and others) macros to wrap a `FnMut` into a `Fn` if you need it. /// /// # Examples /// /// ```rust,ignore /// struct Foo { /// callback: Box, /// } /// /// impl Foo { /// // This will generate 2 extra functions: /// // * `new_cb` to wrap a closure into the proper "shareable" type. /// // * `new_with_cb` that takes the "shareable" type instead of `F`, and internally calls /// // `new` itself. /// #[cursive::callback_helpers] /// pub fn new(callback: F) -> Self /// where /// F: Fn(&mut Cursive) + 'static, /// { /// let callback = Box::new(callback); /// Foo { callback } /// } /// } /// /// cursive::blueprint!(Foo, |config, context| { /// // In a blueprint, we use `new_with_cb` to resolve the proper callback type. /// let foo = /// Foo::new_with_cb(context.resolve(config["callback"])?); /// /// Ok(foo) /// }); /// /// // Elsewhere /// fn foo() { /// let mut context = cursive::builder::Context::new(); /// /// // When storing the callback, we use `new_cb` to wrap it into a shareable type. /// context.store("callback", Foo::new_cb(|s| s.quit())); /// } /// ``` pub fn callback_helpers(item: TokenStream) -> TokenStream { // Read the tokens. Should be a function. let input = syn::parse_macro_input!(item as syn::ImplItemFn); // TODO: use attrs to customize the setter/maker names // * Set the wrapper name // * Set the setter name // * Specify the generic parameter to wrap. // eprintln!("{:#?}", input.sig); // The wrapped function should have (at least) one generic type parameter. // This type parameter should include a function bound. // It could be specified in many ways: impl Fn, , where F: Fn... let (fn_bound, cb_arg_name, type_ident) = find_fn_generic(&input.sig).expect("Could not find function-like generic parameter."); // Fn-ify the function bound let mut fn_bound = fn_bound.clone(); fn_bound.path.segments.last_mut().unwrap().ident = syn::Ident::new("Fn", Span::call_site()); // We will deduce a dyn-able type from this bound (a Arc Output) let dyn_type = bound_to_dyn(&fn_bound); // set_on_foo | new let fn_ident = &input.sig.ident; let fn_name = format!("{}", fn_ident); // on_foo | new let (maker_name, setter_name) = match fn_name.strip_prefix("set_") { Some(base) => (format!("{base}_cb"), format!("{fn_name}_cb")), None => (format!("{fn_name}_cb"), format!("{fn_name}_with_cb")), }; // We will then append to the function 2 helper functions: // * A callback-maker function: takes the same function type, return the dyn-able type. // on_foo_cb | new_cb let maker_ident = syn::Ident::new(&maker_name, Span::call_site()); // TODO: There may be extra generics for F, such as a generic argument. // So find all the generics that are referenced by F (but not F itself, since we are getting // rid of it) let maker_generics = find_dependent_generics(&input.sig, &fn_bound); // And all bounds that apply to these generics // TODO: implement it let maker_bounds = quote!(); // find_dependent_bounds(&maker_generics); // And keep them in the maker. let maker_doc = format!( r#"Helper method to store a callback of the correct type for [`Self::{fn_ident}`]. This is mostly useful when using this view in a template."# ); let maker_fn = quote! { #[doc = #maker_doc] pub fn #maker_ident ( #cb_arg_name: F ) -> #dyn_type where #maker_bounds { ::std::sync::Arc::new(#cb_arg_name) } }; // eprintln!("{}", maker_fn); // * A callback-setter function: takes the dyn-able type (and maybe other vars), and call the // set_on_foo_cb | new_with_cb let setter_ident = syn::Ident::new(&setter_name, Span::call_site()); let return_type = &input.sig.output; let args_signature: Vec<_> = input .sig .inputs .iter() .map(|arg| { if let syn::FnArg::Typed(arg) = arg { if let syn::Pat::Ident(ident) = arg.pat.as_ref() { if &ident.ident == cb_arg_name { return quote! { #cb_arg_name: #dyn_type }; } } } quote! { #arg } }) .collect(); let args_signature = quote! { #(#args_signature),* }; // a,b,c... for as many arguments as the function takes. let n_args = get_arity(&fn_bound); let cb_args: Vec<_> = (0..n_args).map(|i| quote::format_ident!("a{i}")).collect(); let cb_args = quote! { #(#cb_args),* }; let args_call: Vec<_> = input .sig .inputs .iter() .map(|arg| match arg { syn::FnArg::Receiver(_) => { quote! { self } } syn::FnArg::Typed(arg) => { if let syn::Pat::Ident(ident) = arg.pat.as_ref() { if &ident.ident == cb_arg_name { return quote! { move |#cb_args| { (*#cb_arg_name)(#cb_args) } }; } } let pat = &arg.pat; quote! { #pat } } }) .collect(); let args_call = quote! { #(#args_call),* }; let generics: Vec<_> = input .sig .generics .params .iter() .filter(|param| { if let syn::GenericParam::Type(type_param) = param { Some(&type_param.ident) != type_ident } else { true } }) .collect(); let generics = quote! { < #(#generics),* > }; let where_clause: Vec<_> = input .sig .generics .where_clause .as_ref() .map(|where_clause| { where_clause .predicates .iter() .filter(|predicate| { if let syn::WherePredicate::Type(syn::PredicateType { bounded_ty: syn::Type::Path(path), .. }) = predicate { type_ident.map_or(false, |ident| !path.path.is_ident(ident)) } else { false } }) .collect() }) .unwrap_or_else(Vec::new); let where_clause = quote! { where #(#where_clause),* }; // TODO: omit the Function's trait bound when we forward it // TODO: decide: // - should we just take a Arc instead of impl Fn? // - or should we take (config, context) and parse there instead? And maybe do nothing on null? let setter_doc = format!( r#"Helper method to call [`Self::{fn_ident}`] with a variable from a config. This is mostly useful when writing a cursive blueprint for this view."# ); let setter_fn = quote! { #[doc = #setter_doc] pub fn #setter_ident #generics (#args_signature) #return_type #where_clause { Self::#fn_ident(#args_call) } }; // eprintln!("{}", setter_fn); // main wrapped function. TokenStream::from(quote! { #input #maker_fn #setter_fn }) } cursive-macros-0.1.0/src/builder/dummy_mod.rs000064400000000000000000000004711046102023000173430ustar 00000000000000use proc_macro::TokenStream; // When the builder feature is disabled, just remove the entire thing. pub fn blueprint(_: TokenStream, _: TokenStream) -> TokenStream { TokenStream::new() } // Just return the annotated function unchanged. pub fn callback_helpers(item: TokenStream) -> TokenStream { item } cursive-macros-0.1.0/src/builder/mod.rs000064400000000000000000000001611046102023000161240ustar 00000000000000#[cfg(feature = "builder")] include!("real_mod.rs"); #[cfg(not(feature = "builder"))] include!("dummy_mod.rs"); cursive-macros-0.1.0/src/builder/real_mod.rs000064400000000000000000000001561046102023000171330ustar 00000000000000mod blueprint; mod callback_helper; pub use blueprint::blueprint; pub use callback_helper::callback_helpers; cursive-macros-0.1.0/src/lib.rs000064400000000000000000000071411046102023000144720ustar 00000000000000use proc_macro::TokenStream; mod builder; /// Generate two helper functions to help working with cursive blueprints. /// /// # Problem to solve /// /// When writing cursive blueprints, it is often necessary to load parameters or variables. /// /// Some of these have simple types like `u64` or `String`, but in some cases we need to load a /// callback. /// /// In this case, the blueprint loading the variable and the user storing the variable need /// to use the exact same type, or downcasting will not work. This is made complicated by Rust's /// closures, where each closure is a unique anonymous type: if the user directly stores a closure, /// there will be no way to identify its exact type to downcast it in the blueprint. /// /// Instead, both sides (blueprint and user) need to agree to use a fixed type, for example a trait /// object like `Arc` (we use `Arc` rather than `Box` because we want variables /// to be cloneable). /// /// It's a bit cumbersome having to write the exact type including the `Arc` whenever we want to /// store a callback for a blueprint. Similarly, it's a bit annoying when writing the blueprint to make /// sure the correct `Arc<...>` type is fetched and converted to a type directly usable as callback. /// /// # Solution /// /// This is where this macro comes into play: from an original function that uses a callback, it /// generates two helper functions: /// /// * A _maker_ function, to be used when storing variables. This function takes a generic type /// implementing the same `Fn` trait as the desired callback, and returns it wrapped in the correct /// trait object. /// * A _setter_ function, to be used when writing blueprints. This function wraps the original /// function, but takes a trait-object instead of a generic `Fn` type, and unwraps it internally. /// /// # Notes /// /// * The wrapped function doesn't even have to take `self`, it can be a "static" /// constructor method. /// * The `maker` function always takes a `Fn`, not a `FnMut` or `FnOnce`. /// Use the `cursive::immut1!` (and others) macros to wrap a `FnMut` if you need it. /// /// # Examples /// /// ```rust,ignore /// struct Foo { /// callback: Box, /// } /// /// impl Foo { /// #[cursive::callback_helpers] /// pub fn new(callback: F) -> Self /// where /// F: Fn(&mut Cursive) + 'static, /// { /// let callback = Box::new(callback); /// Foo { callback } /// } /// } /// /// cursive::blueprint!(Foo, |config, context| { /// let foo = /// Foo::new_with_cb(context.resolve(config["callback"])?); /// /// Ok(foo) /// }); /// /// // Elsewhere /// fn foo() { /// let mut context = cursive::builder::Context::new(); /// /// context.store("callback", Foo::new_cb(|s| s.quit())); /// } /// ``` #[proc_macro_attribute] pub fn callback_helpers(_attrs: TokenStream, item: TokenStream) -> TokenStream { builder::callback_helpers(item) } /// Defines a blueprint for creating a view from config. /// /// It should be added to a type which defines how to build the view. /// /// # Examples /// /// ```rust,ignore /// #[cursive::blueprint(TextView::empty())] /// struct BlueprintForTextview { /// content: StyledString, /// } /// ``` /// /// This recipe will: /// * Create a base view with `TextView::empty()`. /// * Look for a `content` key in the given config. /// * Try to resolve the associated value to a `StyledString`. /// * Call `set_content` on the base with the resulting `StyledString`. #[proc_macro_attribute] pub fn blueprint(attrs: TokenStream, item: TokenStream) -> TokenStream { builder::blueprint(attrs, item) }