fun_time_derive-0.3.4/.cargo_vcs_info.json0000644000000001550000000000100142050ustar { "git": { "sha1": "ec07669d8a9e5effb27ab48f38dece62be0643ba" }, "path_in_vcs": "fun_time_derive" }fun_time_derive-0.3.4/Cargo.toml0000644000000023120000000000100122000ustar # 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 = "fun_time_derive" version = "0.3.4" authors = ["Steven Liebregt "] description = "The macro implementations of fun_time" readme = "README.md" keywords = [ "function", "timing", "execution_time", "measure", "time", ] categories = ["development-tools::profiling"] license = "MIT" repository = "https://github.com/stevenliebregt/fun_time" [lib] proc-macro = true [dependencies.darling] version = "0.14.2" [dependencies.log] version = "0.4.17" optional = true [dependencies.proc-macro2] version = "1.0.47" [dependencies.quote] version = "1.0.21" [dependencies.syn] version = "1.0.103" features = [ "full", "extra-traits", ] [features] default = [] log = ["dep:log"] fun_time_derive-0.3.4/Cargo.toml.orig000064400000000000000000000011651046102023000156660ustar 00000000000000[package] name = "fun_time_derive" description = "The macro implementations of fun_time" authors = ["Steven Liebregt "] version = "0.3.4" edition = "2021" license = "MIT" repository = "https://github.com/stevenliebregt/fun_time" keywords = ["function", "timing", "execution_time", "measure", "time"] categories = ["development-tools::profiling"] [lib] proc-macro = true [dependencies] darling = "0.14.2" proc-macro2 = "1.0.47" quote = "1.0.21" syn = { version = "1.0.103", features = ["full", "extra-traits"] } log = { version = "0.4.17", optional = true } [features] default = [] log = ["dep:log"]fun_time_derive-0.3.4/LICENSE000064400000000000000000000020601046102023000137770ustar 00000000000000MIT License Copyright (c) 2022 Steven Liebregt Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. fun_time_derive-0.3.4/README.md000064400000000000000000000044761046102023000142660ustar 00000000000000# fun_time [![Crates.io](https://img.shields.io/crates/v/fun_time)](https://crates.io/crates/fun_time) [![docs.rs](https://img.shields.io/docsrs/fun_time)](https://docs.rs/fun_time/0.3.3/fun_time/) fun_time is a simple Rust library that allows you to easily time your function calls with a simple attribute! ### Basic example ```rust #[fun_time(message = "Heavy calculations on: {a_value}")] fn some_cool_function(a_value: String) -> usize { a_value.len() } fn main() { let my_value_length = some_cool_function(String::from("Hello, world.")); } ``` The above will print `Starting: Heavy calculations on: Hello, world.` when the function starts, and `Heavy calculations on: Hello, world.: Done in ` on completion. ### Configuration There are various attributes that allow you to configure the behavior of the `fun_time` attribute. - `message` allows you to set a message that will be printed when starting, and when done, the message is passed directly to the `format!` macro, so the arguments to the function can be used in the message (provided they have `Debug` or `Display`). - `when` allows you to configure when the timing should be collected. The possible values for this are: `"always"` which as the name might suggest will always collect timing information, and `"debug"` which will only collect when `cfg!(debug_assertions)` evaluates to `true`. - `give_back` is a flag that makes it so the wrapped function will now return the elapsed time instead of printing it. For this it modifies the return type from for example: `-> &'a str` to `-> (&'a str, std::time::Duration)`. This allows you to handle printing or storing the timing information. - `reporting` (_can not be used in combination with give_back_) determines how the reporting is done. The possible options are: `"println"` which will print to stdout using `println!`. The `"log"` option is only available when the `log` feature is used. This will use the [log](https://crates.io/crates/log) crate with `info!` level logs by default, this can be affected by the `level` option. - `level` Set the level for the log messages, can by any option that can be parsed by the `log::Level` enum. #### Reporting The reported messages are formatted as follows: **Start message**: "Starting: YOUR_MESSAGE_HERE" **Done message**: "YOUR_MESSAGE_HERE: Done in DURATION" fun_time_derive-0.3.4/src/lib.rs000064400000000000000000000257141046102023000147100ustar 00000000000000use darling::FromMeta; use quote::quote; use syn::{parse_macro_input, ReturnType}; macro_rules! make_darling_error { ($($arg:tt)*) => { Err(darling::Error::custom(format!($($arg)*))) }; } macro_rules! make_compile_error { ($($arg:tt)*) => { quote! { compile_error!($($arg)*) }.into() }; } /// Determines when to collect execution time information. #[derive(FromMeta)] enum When { /// Always collect timing information. Always, /// Only collect timing information if `cfg!(debug_assertions)` evaluates to `true`. Debug, } /// By default we always collect timing information. impl Default for When { fn default() -> Self { Self::Always } } impl When { /// Parse the [`When`] argument from a given string literal. fn from_lit(literal: syn::LitStr) -> Result { match literal.value().as_str() { "always" => Ok(Self::Always), "debug" => Ok(Self::Debug), unsupported => make_darling_error!( "Unsupported value for `when` attribute: {unsupported}. Use one of: always, debug" ), } } } /// Determines how to report the captured execution time information. /// /// It will print both a start and done message. /// The format of the start message is: "Starting: YOUR_MESSAGE_HERE" /// The format of the done message is: "YOUR_MESSAGE_HERE: Done in ELAPSED_TIME" /// /// The `ELAPSED_TIME` is the debug format of [`std::time::Duration`]. #[derive(FromMeta)] enum Reporting { /// Use a simple `println!` statement to print the information to the `stdout`. Println, /// Use the [log](https://crates.io/crates/log) crate to print the information using the /// provided `info!` macro. #[cfg(feature = "log")] Log, } /// By default we use the simple `println!` to write the reporting info to the `stdout`. impl Default for Reporting { #[cfg(not(feature = "log"))] fn default() -> Self { Self::Println } #[cfg(feature = "log")] fn default() -> Self { Self::Log } } impl Reporting { /// Parse the [`Reporting`] argument from a given string literal. fn from_lit(literal: syn::LitStr) -> Result { match literal.value().as_str() { "println" => Ok(Self::Println), #[cfg(feature = "log")] "log" => Ok(Self::Log), unsupported => make_darling_error!("Unsupported value for `reporting` attribute: {unsupported}. Use one of: println, (only with log feature) log") } } } #[cfg(feature = "log")] mod log_level { use super::*; use std::str::FromStr; pub struct Level(pub log::Level); impl FromMeta for Level {} impl Default for Level { fn default() -> Self { Self(log::Level::Info) } } impl Level { pub fn from_lit(literal: syn::LitStr) -> Result { Ok(Self(log::Level::from_str(&literal.value()).map_err(|_| { darling::Error::custom(format!( "Unsupported value for `level` attribute: {unsupported}. Use one of: trace, debug, info, warn, error", unsupported = literal.value() )) })?)) } } } #[derive(FromMeta)] struct FunTimeArgs { #[darling(default)] message: Option, /// Determines when we should perform the timing. #[darling(default)] #[darling(and_then = "When::from_lit")] when: When, /// Determines whether the elapsed time should be returned or that we log it immediately /// to stdout. #[darling(default)] give_back: bool, #[darling(default)] #[darling(and_then = "Reporting::from_lit")] reporting: Reporting, #[cfg(feature = "log")] #[darling(default)] #[darling(and_then = "log_level::Level::from_lit")] level: log_level::Level, } /// Measure the execution times of the function under the attribute. /// /// It does this by wrapping the function in a new block surrounded by a [`std::time::Instant::now()`] /// call and a [`std::time::Instant::elapsed()`] call. It then either logs the duration directly or /// returns it with the original functions return value, depending on how you configured this /// attribute. /// /// # Attributes /// /// ## when /// /// The `when` attribute can be used to configure when the timing information is collected. For /// example, with `"always"` the timing information is always collected, but with `"debug"` the /// timing information is only collected if the `cfg!(debug_assertions)` statement evaluates to /// `true`. /// /// ## give_back /// /// The `give_back` attribute can be used to switch the macro from printing mode to returning the /// captured elapsed time together with the original return value of the function. It will modify /// the original return value to be a tuple, where the first value is the original return value /// and the second value is the elapsed time as a [`std::time::Duration`] struct. /// /// ## message /// /// The `message` attribute allows you to set a message that will be displayed in the case you /// chose to let the macro report the elapsed time directly. This message will be shown both in /// the start and done messages. /// /// ## reporting /// /// The `reporting` attribute determines how the message and elapsed time will be displayed /// directly when you have chosen not to let the macro return the elapsed time to you. By default /// it uses a simple `println!` statement, but with the optional `log` feature it will use the /// [log](https://crates.io/crates/log) crate to log it using the `info!` macro. /// /// # Example /// /// ``` /// // Replace this in your code with `use fun_time::fun_time;` /// use fun_time_derive::fun_time; /// /// #[fun_time(give_back)] /// fn function_with_heavy_calculations(some_data: Vec) -> bool { /// // Big brain calculations... /// true /// } /// /// fn main() { /// let my_data = vec![1, 2, 3]; /// /// // Run the function and receive timing information /// let (my_original_return_value, how_long_did_it_take) = function_with_heavy_calculations(my_data); /// } /// ``` #[proc_macro_attribute] pub fn fun_time( args: proc_macro::TokenStream, item: proc_macro::TokenStream, ) -> proc_macro::TokenStream { let raw_args: syn::AttributeArgs = parse_macro_input!(args as syn::AttributeArgs); let args: FunTimeArgs = match FunTimeArgs::from_list(&raw_args) { Ok(args) => args, Err(error) => return error.write_errors().into(), }; if args.message.is_some() && args.give_back { return make_compile_error!( "the `message` and `give_back` attributes can not be used together!" ); } let item_fn: syn::ItemFn = parse_macro_input!(item as syn::ItemFn); // Check if we should time the function match args.when { When::Debug if args.give_back => return make_compile_error!("the `give_back` and `when` attribute with `\"debug\"` can not be used together! It would result in different return types"), When::Debug if !cfg!(debug_assertions) => return quote! { #item_fn }.into(), _ => {} // No restrictions, go ahead! } let visibility = item_fn.vis; let signature = item_fn.sig.clone(); let output = item_fn.sig.output; // Contains the original logic of the function let block = item_fn.block; // Create wrapped function block let wrapped_block = quote! { let super_secret_variable_that_does_not_clash_start = std::time::Instant::now(); // Immediately invoked closure so a `return` statement in the original function does not // break the logging. This also works with self-mutating structs. // We also put the original return type as return type for the closure otherwise things like // -> Box can not be correctly inferred by the compiler. let return_value = (|| #output #block)(); let elapsed = super_secret_variable_that_does_not_clash_start.elapsed(); }; // Create tokens for the `log` call if it is enabled #[cfg(feature = "log")] let log_tokens = match args.level.0 { log::Level::Error => quote! { log::error! }, log::Level::Warn => quote! { log::warn! }, log::Level::Info => quote! { log::info! }, log::Level::Debug => quote! { log::debug! }, log::Level::Trace => quote! { log::trace! }, }; // Depending on our `give_back` attibute we either return the elapsed time or not let tokens = if args.give_back { // Deconstruct the signature because we need to edit the return type let syn::Signature { ident, generics, inputs, output, .. } = signature; let where_clause = &generics.where_clause; // Modify our output type to also return a std::time::Duration (our elapsed time) // In case of an empty return type we can simply return the std::time::Duration, otherwise // we have to wrap it in a tuple. let output_with_duration = match output { ReturnType::Default => syn::parse_str::("-> std::time::Duration").unwrap(), ReturnType::Type(_, ty) => syn::parse_str::(&format!( "-> ({}, std::time::Duration)", quote! { #ty } )) .unwrap(), }; quote! { #visibility fn #ident #generics (#inputs) #output_with_duration #where_clause { #wrapped_block (return_value, elapsed) } } } else { let message = args.message.unwrap_or_default(); // Store the message at the top of the function because if the function were to take // ownership of the argument it would be gone by the time we want to print the done message. let message_statement = quote! { let super_secret_variable_that_does_not_clash_message = format!(#message); }; let starting_statement = match args.reporting { Reporting::Println => quote! { println!("{}", super_secret_variable_that_does_not_clash_message); }, #[cfg(feature = "log")] Reporting::Log => quote! { #log_tokens("{}", super_secret_variable_that_does_not_clash_message); }, }; let reporting_statement = match args.reporting { Reporting::Println => quote! { println!("{}: Done in {:.2?}", super_secret_variable_that_does_not_clash_message, elapsed); }, #[cfg(feature = "log")] Reporting::Log => quote! { #log_tokens("{}: Done in {:.2?}", super_secret_variable_that_does_not_clash_message, elapsed); }, }; quote! { #visibility #signature { #message_statement #starting_statement #wrapped_block #reporting_statement return_value } } }; tokens.into() }