From 958d82af6395726ed740551b4d3374e7e23855a0 Mon Sep 17 00:00:00 2001 From: Andrey Cherkashin Date: Sun, 19 Apr 2020 17:10:49 -0700 Subject: [PATCH] feat(derive): Ability to add pre_sync_hook --- src/lib.rs | 4 ++ tests/derive.rs | 88 ++++++++++++++++++++++++++++++++ uclicious_derive/src/bindings.rs | 6 +++ uclicious_derive/src/builder.rs | 9 ++++ uclicious_derive/src/options.rs | 4 ++ uclicious_derive/src/parser.rs | 11 ++++ 6 files changed, 122 insertions(+) create mode 100644 tests/derive.rs diff --git a/src/lib.rs b/src/lib.rs index 9b0f7d4..c10bc9a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -256,6 +256,10 @@ //! - a string representation of filepath. //! - `expand` //! - (optional) if set, then variables would be expanded to absolute. +//! - `pre_source_hook(...)` +//! - Optional attribute to run a function before sources are added +//! - Can be used to register vars handler +//! - Must take `&mut Parser` as argument and return `Result<(), Into>` //! - `var(..)` //! - Optional attribute to register string variables with the parser. //! - Has following nested attributes: diff --git a/tests/derive.rs b/tests/derive.rs new file mode 100644 index 0000000..b60e733 --- /dev/null +++ b/tests/derive.rs @@ -0,0 +1,88 @@ +use std::ptr::slice_from_raw_parts; +use uclicious::Uclicious; +use uclicious::{variable_handlers, Priority, UclError, DEFAULT_DUPLICATE_STRATEGY}; + +#[test] +fn derive_with_hook() { + fn add_handlers(parser: &mut uclicious::Parser) -> Result<(), UclError> { + let www = |data: *const ::std::os::raw::c_uchar, + len: usize, + replace: *mut *mut ::std::os::raw::c_uchar, + replace_len: *mut usize, + need_free: *mut bool| { + let var = unsafe { + let slice = slice_from_raw_parts(data, len).as_ref().unwrap(); + std::str::from_utf8(slice).unwrap() + }; + if var.eq("WWW") { + let test = "asd"; + let size = test.as_bytes().len(); + unsafe { + *replace = libc::malloc(size).cast(); + *replace_len = size; + test.as_bytes() + .as_ptr() + .copy_to_nonoverlapping(*replace, size); + *need_free = true; + } + true + } else { + false + } + }; + + let zzz = |data: *const ::std::os::raw::c_uchar, + len: usize, + replace: *mut *mut ::std::os::raw::c_uchar, + replace_len: *mut usize, + need_free: *mut bool| { + let var = unsafe { + let slice = slice_from_raw_parts(data, len).as_ref().unwrap(); + std::str::from_utf8(slice).unwrap() + }; + if var.eq("ZZZ") { + let test = "dsa"; + let size = test.as_bytes().len(); + unsafe { + *replace = libc::malloc(size).cast(); + *replace_len = size; + test.as_bytes() + .as_ptr() + .copy_to_nonoverlapping(*replace, size); + *need_free = true; + } + true + } else { + false + } + }; + + let mut compound_handler = variable_handlers::compound::CompoundHandler::default(); + compound_handler.register_handler(Box::new(www)); + compound_handler.register_handler(Box::new(zzz)); + parser.set_variables_handler(Box::new(compound_handler)); + Ok(()) + } + + #[derive(Uclicious, Debug)] + #[ucl(pre_source_hook = "add_handlers")] + struct Test { + key_one: String, + key_two: String, + } + + let input = r#" + key_one = "${ZZZ}" + key_two = "${WWW}" + "#; + + let mut parser = Test::builder().unwrap(); + parser + .add_chunk_full(input, Priority::default(), DEFAULT_DUPLICATE_STRATEGY) + .unwrap(); + + let test = parser.build().unwrap(); + + assert_eq!("dsa", test.key_one); + assert_eq!("asd", test.key_two); +} diff --git a/uclicious_derive/src/bindings.rs b/uclicious_derive/src/bindings.rs index cb4f8d9..3a8d882 100644 --- a/uclicious_derive/src/bindings.rs +++ b/uclicious_derive/src/bindings.rs @@ -37,7 +37,13 @@ pub fn clone_trait() -> Path { pub fn into_trait() -> Path { syn::parse_str("::std::convert::Into").unwrap() } +pub fn box_ty() -> Path { + syn::parse_str("::std::boxed::Box").unwrap() +} +pub fn var_handler_trait() -> Path { + syn::parse_str("::uclicious::traits::VariableHandler").unwrap() +} /// TryInto trait. pub fn try_into_trait() -> Path { syn::parse_str("::std::convert::TryInto").unwrap() diff --git a/uclicious_derive/src/builder.rs b/uclicious_derive/src/builder.rs index 17cd5e1..94625b6 100644 --- a/uclicious_derive/src/builder.rs +++ b/uclicious_derive/src/builder.rs @@ -27,6 +27,7 @@ pub struct Builder<'a> { pub includes: Vec, pub parser: &'a Parser, pub vars: Vec, + pub pre_source_hook: Option, } impl<'a> Builder<'a> { @@ -198,6 +199,13 @@ impl<'a> ToTokens for Builder<'a> { let result_ty = bindings::result_ty(); let ucl_error_ty = bindings::ucl_parser_error(); let parser = self.parser; + let pre_source_hook = if let Some(ref hook) = self.pre_source_hook { + quote! { + #hook(&mut parser)?; + } + } else { + quote!() + }; tokens.append_all(quote!( #[derive(#derived_traits)] #builder_doc_comment @@ -212,6 +220,7 @@ impl<'a> ToTokens for Builder<'a> { #builder_vis fn new() -> #result_ty { #parser #(#vars)* + #pre_source_hook #(#includes)* Ok( Self { diff --git a/uclicious_derive/src/options.rs b/uclicious_derive/src/options.rs index feab2fa..93b8429 100644 --- a/uclicious_derive/src/options.rs +++ b/uclicious_derive/src/options.rs @@ -171,6 +171,9 @@ pub struct Options { #[darling(default, multiple, rename = "var")] vars: Vec, + + #[darling(default)] + pre_source_hook: Option, } /// Data extracted from the fields of the input struct. @@ -372,6 +375,7 @@ impl Options { includes: self.include.clone(), parser: &self.parser, vars: self.vars.clone(), + pre_source_hook: self.pre_source_hook.clone(), } } pub fn as_build_method(&self) -> BuildMethod { diff --git a/uclicious_derive/src/parser.rs b/uclicious_derive/src/parser.rs index dc208d3..4faaaea 100644 --- a/uclicious_derive/src/parser.rs +++ b/uclicious_derive/src/parser.rs @@ -36,6 +36,8 @@ impl ToTokens for ParserMethods { let result = bindings::result_ty(); let err = bindings::ucl_parser_error(); let path = bindings::path_ty(); + let box_ty = bindings::box_ty(); + let var_handler_trait = bindings::var_handler_trait(); tokens.append_all(quote! ( /// Add a chunk of text to the parser. String must: /// - not have `\0` character; @@ -60,6 +62,15 @@ impl ToTokens for ParserMethods { self.__parser.register_variable(var, value); self } + /// A safe counterpart of [`Parser::set_variable_handler_raw`](#method.set_variables_handler_raw). Unlike unsafe version this one takes ownership of a handler and ensures it stays alive as long as parser does. + /// + /// ### Caveats + /// + /// Parser can have only bar handler. In order to have multiple, please use [`CompoundHandler`](../../variable_handlers/compound/struct.CompoundHandler.html) to join multiple handlers into one. + #vis fn set_variables_handler(&mut self, handler: #box_ty) -> &mut Self { + self.__parser.set_variables_handler(handler); + self + } /// Add the standard file variables to the `parser` based on the `filename` specified: /// /// - `$FILENAME`- a filename of ucl input