From 55e078718d6dec1e1ac67042e116a4ae1e39c33f Mon Sep 17 00:00:00 2001 From: Alexander Gil Date: Thu, 15 Aug 2024 17:32:09 +0200 Subject: [PATCH] feat(lookup): Add find reusing module logic Resolves #22. Signed-off-by: Alexander Gil --- mdbook_rash/src/lib.rs | 8 +-- rash_core/src/jinja/lookup/find.rs | 35 ++++++++++ rash_core/src/jinja/lookup/mod.rs | 5 +- rash_core/src/jinja/lookup/passwordstore.rs | 14 ++-- rash_core/src/jinja/lookup/utils.rs | 5 ++ rash_core/src/modules/find.rs | 2 +- rash_core/src/modules/mod.rs | 2 +- rash_derive/src/lib.rs | 73 +++++++++++++++------ 8 files changed, 109 insertions(+), 35 deletions(-) create mode 100644 rash_core/src/jinja/lookup/find.rs create mode 100644 rash_core/src/jinja/lookup/utils.rs diff --git a/mdbook_rash/src/lib.rs b/mdbook_rash/src/lib.rs index fd270dc9..78cc32dc 100644 --- a/mdbook_rash/src/lib.rs +++ b/mdbook_rash/src/lib.rs @@ -183,7 +183,7 @@ fn replace_matches(captures: Vec<(Match, Option, String)>, ch: &mut Chap if capture.2 == "include_module_index" { let mut indexes_vec = MODULES .iter() - .map(|(name, _)| format!("- [{name}](./{name}.html)")) + .map(|(name, _)| format!("- [{name}](./module_{name}.html)")) .collect::>(); indexes_vec.sort(); let indexes_body = indexes_vec.join("\n"); @@ -224,7 +224,7 @@ indent: true let mut new_ch = Chapter::new( name, content_header, - format!("{}.md", &name), + format!("module_{}.md", &name), vec![ch.name.clone()], ); new_ch.number = Some(new_section_number); @@ -235,7 +235,7 @@ indent: true } else if capture.2 == "include_lookup_index" { let mut indexes_vec = LOOKUPS .iter() - .map(|name| format!("- [{name}](./{name}.html)")) + .map(|name| format!("- [{name}](./lookup_{name}.html)")) .collect::>(); indexes_vec.sort(); let indexes_body = indexes_vec.join("\n"); @@ -268,7 +268,7 @@ indent: true let mut new_ch = Chapter::new( lookup_name, content_header, - format!("{}.md", &lookup_name), + format!("lookup_{}.md", &lookup_name), vec![ch.name.clone()], ); new_ch.number = Some(new_section_number); diff --git a/rash_core/src/jinja/lookup/find.rs b/rash_core/src/jinja/lookup/find.rs new file mode 100644 index 00000000..6b2d8f8f --- /dev/null +++ b/rash_core/src/jinja/lookup/find.rs @@ -0,0 +1,35 @@ +/// ANCHOR: lookup +/// # find +/// +/// Use [find module](./module_find.html) as a lookup. Returns the extra field of the module result. +/// +/// ANCHOR_END: lookup +/// ANCHOR: examples +/// ## Example +/// +/// ```yaml +/// - debug: +/// msg: "{{ find(paths='/') }}" +/// ``` +/// ANCHOR_END: examples +use crate::jinja::lookup::utils::to_minijinja_error; +use crate::modules::find::find; +use crate::modules::parse_params; + +use std::result::Result as StdResult; + +use minijinja::{Error as MinijinjaError, Value}; + +pub fn function(config: Value) -> StdResult { + parse_params(serde_yaml::to_value(config).map_err(to_minijinja_error)?) + .map_err(to_minijinja_error) + .and_then(|params| { + Ok(find(params) + .map_err(to_minijinja_error) + .map(|x| serde_yaml::to_value(x.get_extra())) + .map_err(to_minijinja_error)? + .map(Value::from_serialize)) + }) + .map_err(to_minijinja_error)? + .map_err(to_minijinja_error) +} diff --git a/rash_core/src/jinja/lookup/mod.rs b/rash_core/src/jinja/lookup/mod.rs index 34621af1..9b8623af 100644 --- a/rash_core/src/jinja/lookup/mod.rs +++ b/rash_core/src/jinja/lookup/mod.rs @@ -1,6 +1,9 @@ +mod find; #[cfg(feature = "passwordstore")] mod passwordstore; +mod utils; + use rash_derive::generate_lookup_functions; -generate_lookup_functions!(passwordstore); +generate_lookup_functions!((find, false), (passwordstore, true),); diff --git a/rash_core/src/jinja/lookup/passwordstore.rs b/rash_core/src/jinja/lookup/passwordstore.rs index 44b4b196..5b1b09e2 100644 --- a/rash_core/src/jinja/lookup/passwordstore.rs +++ b/rash_core/src/jinja/lookup/passwordstore.rs @@ -12,6 +12,8 @@ /// msg: "{{ passwordstore('foo/boo') }}" /// ``` /// ANCHOR_END: examples +use crate::jinja::lookup::utils::to_minijinja_error; + use std::env; use std::result::Result as StdResult; @@ -34,15 +36,11 @@ pub fn function(path: String) -> StdResult { let config = Config::from(Proto::Gpg); let plaintext = crypto::context(&config) - .map_err(|e| MinijinjaError::new(MinijinjaErrorKind::InvalidOperation, e.to_string()))? + .map_err(to_minijinja_error)? .decrypt_file(&secret.path) - .map_err(|e| MinijinjaError::new(MinijinjaErrorKind::InvalidOperation, e.to_string()))?; - let first_line = plaintext - .first_line() - .map_err(|e| MinijinjaError::new(MinijinjaErrorKind::InvalidOperation, e.to_string()))?; + .map_err(to_minijinja_error)?; + let first_line = plaintext.first_line().map_err(to_minijinja_error)?; - let password = first_line - .unsecure_to_str() - .map_err(|e| MinijinjaError::new(MinijinjaErrorKind::InvalidOperation, e.to_string()))?; + let password = first_line.unsecure_to_str().map_err(to_minijinja_error)?; Ok(Value::from(password)) } diff --git a/rash_core/src/jinja/lookup/utils.rs b/rash_core/src/jinja/lookup/utils.rs new file mode 100644 index 00000000..e75cc23d --- /dev/null +++ b/rash_core/src/jinja/lookup/utils.rs @@ -0,0 +1,5 @@ +use minijinja::{Error as MinijinjaError, ErrorKind as MinijinjaErrorKind}; + +pub fn to_minijinja_error(err: E) -> MinijinjaError { + MinijinjaError::new(MinijinjaErrorKind::InvalidOperation, err.to_string()) +} diff --git a/rash_core/src/modules/find.rs b/rash_core/src/modules/find.rs index e454d47e..5bc3f750 100644 --- a/rash_core/src/modules/find.rs +++ b/rash_core/src/modules/find.rs @@ -139,7 +139,7 @@ fn get_regex_set(v: Option>) -> Result> { } } -fn find(params: Params) -> Result { +pub fn find(params: Params) -> Result { let paths = parse_if_json(params.paths); if paths.iter().map(Path::new).any(|x| x.is_relative()) { return Err(Error::new( diff --git a/rash_core/src/modules/mod.rs b/rash_core/src/modules/mod.rs index 1c468ac0..e1964cd6 100644 --- a/rash_core/src/modules/mod.rs +++ b/rash_core/src/modules/mod.rs @@ -3,7 +3,7 @@ mod command; mod copy; mod debug; mod file; -mod find; +pub mod find; mod pacman; mod set_vars; mod template; diff --git a/rash_derive/src/lib.rs b/rash_derive/src/lib.rs index 0f55c94a..2232fc5d 100644 --- a/rash_derive/src/lib.rs +++ b/rash_derive/src/lib.rs @@ -5,7 +5,7 @@ extern crate syn; use proc_macro::TokenStream; use quote::quote; -use syn::{parse_macro_input, punctuated::Punctuated, Ident, Token}; +use syn::{parse_macro_input, punctuated::Punctuated, Expr, ExprLit, ExprTuple, Lit, Token}; /// Implementation of the `#[derive(FieldNames)]` derive macro. /// @@ -76,22 +76,25 @@ pub fn derive_doc_json_schema(input: TokenStream) -> TokenStream { /// Macro to generate a function that adds lookup functions to a `minijinja::Environment`. /// /// This macro generates an `add_lookup_functions` function that registers multiple lookup -/// functions into a `minijinja::Environment`, with each function being conditionally compiled -/// based on the presence of a corresponding feature flag. +/// functions into a `minijinja::Environment`. Each function can be conditionally compiled +/// based on the presence of a corresponding feature flag if specified in the tuple. +/// +/// Additionally, when the `docs` feature is enabled, it will generate a `LOOKUPS` constant that +/// lists all the lookup function names. /// /// # Example /// /// Assuming you have three modules `lookup1`, `lookup2`, and `lookup3`, each with a `function` /// that you want to add to the environment, you would use the macro like this: /// -/// ``` +/// ```rust /// mod lookup1; /// mod lookup2; /// mod lookup3; /// -/// use rash_derive::generate_lookup_functions; +/// use my_macro::generate_lookup_functions; /// -/// generate_lookup_functions!(lookup1, lookup2, lookup3); +/// generate_lookup_functions!((lookup1, true), (lookup2, false), (lookup3, true)); /// ``` /// /// This will generate the following function: @@ -101,6 +104,9 @@ pub fn derive_doc_json_schema(input: TokenStream) -> TokenStream { /// #[cfg(feature = "lookup1")] /// env.add_function("lookup1", lookup1::function); /// +/// +/// #[cfg(feature = "lookup2")] +/// /// #[cfg(feature = "lookup2")] /// env.add_function("lookup2", lookup2::function); /// @@ -109,7 +115,18 @@ pub fn derive_doc_json_schema(input: TokenStream) -> TokenStream { /// } /// ``` /// -/// You can then control which functions are included by specifying the corresponding features +/// When the `docs` feature is enabled, it will also generate the following constant: +/// +/// ```rust +/// #[cfg(feature = "docs")] +/// const LOOKUPS: &[&str] = &[ +/// "lookup1", +/// "lookup2", +/// "lookup3", +/// ]; +/// ``` +/// +/// You can control which functions are included by specifying the corresponding features /// in your `Cargo.toml`: /// /// ```toml @@ -117,31 +134,47 @@ pub fn derive_doc_json_schema(input: TokenStream) -> TokenStream { /// lookup1 = [] /// lookup2 = [] /// lookup3 = [] +/// docs = [] /// ``` /// -/// When building your crate, you can enable the desired features: +/// When building your crate with the `docs` feature, the `LOOKUPS` constant will be included: /// /// ```sh -/// cargo build --features "lookup1 lookup2" +/// cargo build --features "docs" /// ``` #[proc_macro] pub fn generate_lookup_functions(input: TokenStream) -> TokenStream { - // Parse the input tokens into a punctuated list of identifiers separated by commas - let function_names = - parse_macro_input!(input with Punctuated::::parse_terminated); + // Parse the input tokens into a punctuated list of tuples separated by commas + let tuples = + parse_macro_input!(input with Punctuated::::parse_terminated); let mut add_functions = Vec::new(); let mut lookup_names = Vec::new(); - // Iterate through each identifier and generate the corresponding function call with #[cfg] - for func in function_names.iter() { - let func_name_str = func.to_string(); // Convert the identifier to a string - add_functions.push(quote! { - #[cfg(feature = #func_name_str)] - env.add_function(#func_name_str, #func::function); - }); + // Iterate through each tuple and generate the corresponding function call with optional #[cfg] + for tuple in tuples.iter() { + if let ( + Some(Expr::Path(path)), + Some(Expr::Lit(ExprLit { + lit: Lit::Bool(lit_bool), + .. + })), + ) = (tuple.elems.first(), tuple.elems.last()) + { + let func_name = path.path.segments.first().unwrap().ident.to_string(); // Extract function name + lookup_names.push(func_name.clone()); - lookup_names.push(func_name_str); + if lit_bool.value { + add_functions.push(quote! { + #[cfg(feature = #func_name)] + env.add_function(#func_name, #path::function); + }); + } else { + add_functions.push(quote! { + env.add_function(#func_name, #path::function); + }); + } + } } // Generate the output function code