1use nu_ansi_term::{Color, ansi::RESET};
45use proc_macro::TokenStream;
46use proc_macro2::TokenStream as TokenStream2;
47use quote::{ToTokens, format_ident, quote, quote_spanned};
48use std::sync::{Once, OnceLock};
49use syn::{
50 Attribute, Error, FnArg, Ident, ImplItem, ImplItemFn, Item, ItemFn, ItemImpl, ItemMod, LitStr,
51 Result, Token, parse, parse_macro_input, parse_quote, spanned::Spanned,
52};
53
54#[allow(unused)]
55#[cfg(doctest)]
56mod helper;
57
58static IS_HELPER_MODULE_ADDED: Once = Once::new();
59static PRINT_RUN_DEFAULTS: OnceLock<PrintRunArgs> = OnceLock::new();
60
61#[derive(Clone, Debug, Default)]
63struct PrintRunArgs {
64 colored: Option<bool>,
65 duration: Option<bool>,
66 indent: Option<bool>,
67 skip: Option<bool>,
68 supress_labels: Option<bool>,
69 timestamps: Option<bool>,
70 __struct_prefix: Option<String>,
71}
72
73impl PrintRunArgs {
74 fn merge(&mut self, override_args: &PrintRunArgs) {
75 self.colored = override_args.colored.or(self.colored);
76 self.duration = override_args.duration.or(self.duration);
77 self.indent = override_args.indent.or(self.indent);
78 self.skip = override_args.skip.or(self.skip);
79 self.supress_labels = override_args.supress_labels.or(self.supress_labels);
80 self.timestamps = override_args.timestamps.or(self.timestamps);
81 self.__struct_prefix = override_args
82 .__struct_prefix
83 .clone()
84 .or_else(|| self.__struct_prefix.clone());
85 }
86
87 fn add_globals(&mut self) {
88 if let Some(glob) = get_print_run_defaults() {
89 if let Some(v) = glob.colored {
90 self.colored = Some(v);
91 }
92 if let Some(v) = glob.duration {
93 self.duration = Some(v);
94 }
95 if let Some(v) = glob.indent {
96 self.indent = Some(v);
97 }
98 if let Some(v) = glob.skip {
99 self.skip = Some(v);
100 }
101 if let Some(v) = glob.supress_labels {
102 self.supress_labels = Some(v);
103 }
104 if let Some(v) = glob.timestamps {
105 self.timestamps = Some(v);
106 }
107 }
108 }
109
110 fn to_attribute(&self) -> Attribute {
111 let arg_idents = self.to_idents();
112 let pre = self.__struct_prefix.as_ref().and_then(|p| Some(p.as_str()));
113
114 match (arg_idents.is_empty(), pre) {
115 (true, None) => parse_quote! {
116 #[print_run::print_run]
117 },
118 (true, Some(pre_val)) => parse_quote! {
119 #[print_run::print_run(__struct_prefix = #pre_val)]
120 },
121 (false, None) => parse_quote! {
122 #[print_run::print_run( #(#arg_idents),* )]
123 },
124 (false, Some(pre_val)) => parse_quote! {
125 #[print_run::print_run( #(#arg_idents),*, __struct_prefix = #pre_val )]
126 },
127 }
128 }
129
130 fn to_idents(&self) -> Vec<Ident> {
131 let mut result = Vec::new();
132
133 if self.colored == Some(true) {
134 result.push(format_ident!("colored"));
135 }
136 if self.duration == Some(true) {
137 result.push(format_ident!("duration"));
138 }
139 if self.indent == Some(true) {
140 result.push(format_ident!("indent"));
141 }
142 if self.skip == Some(true) {
143 result.push(format_ident!("skip"));
144 }
145 if self.supress_labels == Some(true) {
146 result.push(format_ident!("supress_labels"));
147 }
148 if self.timestamps == Some(true) {
149 result.push(format_ident!("timestamps"));
150 }
151
152 result
153 }
154}
155
156impl parse::Parse for PrintRunArgs {
157 fn parse(input: parse::ParseStream) -> Result<Self> {
158 let mut args = PrintRunArgs::default();
159
160 while !input.is_empty() {
161 let ident: Ident = input.parse()?;
162 let _ = input.parse::<Option<Token![,]>>(); match ident.to_string().as_str() {
165 "colored" => args.colored = Some(true),
166 "duration" => args.duration = Some(true),
167 "indent" => args.indent = Some(true),
168 "skip" => args.skip = Some(true),
169 "supress_labels" => args.supress_labels = Some(true),
170 "timestamps" => args.timestamps = Some(true),
171 "__struct_prefix" => {
172 let _ = input.parse::<Option<Token![=]>>()?;
173 let lit: LitStr = input.parse()?;
174 args.__struct_prefix = Some(lit.value().to_string());
175 }
176 other => {
177 return Err(Error::new(
178 ident.span(),
179 format!("Unknown attribute '{}'", other),
180 ));
181 }
182 }
183 }
184
185 Ok(args)
186 }
187}
188
189macro_rules! or_else {
190 ($cond:expr, $true_:expr, $false_:expr) => {
191 if $cond { $true_ } else { $false_ }
192 };
193}
194
195macro_rules! or_nothing {
196 ($cond:expr, $true_:expr) => {
197 or_else!($cond, $true_, quote! {})
198 };
199}
200
201macro_rules! or_empty_str {
202 ($cond:expr, $true_:expr) => {
203 or_else!($cond, $true_, quote! { || "".to_string() })
204 };
205}
206
207macro_rules! colorize {
208 ($txt:expr, $col_name: ident) => {
209 format!("{}{}{}", Color::$col_name.prefix().to_string(), $txt, RESET)
210 };
211}
212
213macro_rules! colorize_fn {
214 ($color_name: ident) => {{
215 let color = Color::$color_name.prefix().to_string();
216 quote! {
217 |txt: String| format!("{}{}{}", #color, txt, #RESET)
218 }
219 }};
220 ($color_name: ident, "bold") => {{
221 let color = Color::$color_name.bold().prefix().to_string();
222 quote! {
223 |txt: String| format!("{}{}{}", #color, txt, #RESET)
224 }
225 }};
226}
227
228macro_rules! create_timestamp {
229 ($colored:expr) => {{
230 let colorize = or_else!(
231 $colored,
232 colorize_fn!(DarkGray),
233 quote! { |txt: String| txt }
234 );
235 quote! {
236 || {
237 let now = std::time::SystemTime::now();
238 let epoch = now
239 .duration_since(std::time::UNIX_EPOCH)
240 .expect("Time went backwards");
241
242 let total_secs = epoch.as_secs();
243 let millis = epoch.subsec_millis();
244
245 let hours = (total_secs / 3600) % 24;
246 let minutes = (total_secs / 60) % 60;
247 let seconds = total_secs % 60;
248 let ts = format!("{:02}:{:02}:{:02}.{:03}", hours, minutes, seconds, millis);
249 let ts = {#colorize}(ts);
250 format!("{} ", ts) }
252 }
253 }};
254}
255
256macro_rules! create_duration {
257 ($colored:expr, $supress_labels:expr) => {{
258 let colorize = or_else!($colored, colorize_fn!(Green), quote! { |txt: String| txt });
259 let supress_labels = $supress_labels;
260 quote! {
261 |start: std::time::Instant| {
262 let elapsed = start.elapsed().as_nanos();
263 let dur =
264 if elapsed < 1_000 {
265 format!("{}ns", elapsed)
266 } else if elapsed < 1_000_000 {
267 format!("{:.2}µs", elapsed as f64 / 1_000.0)
268 } else if elapsed < 1_000_000_000 {
269 format!("{:.2}ms", elapsed as f64 / 1_000_000.0)
270 } else {
271 format!("{:.2}s", elapsed as f64 / 1_000_000_000.0)
272 }
273 ;
274 let dur = {#colorize}(dur); if #supress_labels {
276 format!("[{}]", dur)
277 } else {
278 format!(" in {}", dur)
279 }
280 }
281 }
282 }};
283}
284
285macro_rules! create_indent {
286 ($val:expr, $ch:expr) => {{
287 let val = $val;
288 let ch = $ch;
289
290 let depth_path: syn::Expr = if cfg!(doctest) {
292 syn::parse_str("crate::helper::DEPTH").unwrap()
293 } else {
294 syn::parse_str("crate::__print_run_helper::DEPTH").unwrap()
295 };
296
297 quote! {
298 || {#depth_path}.with(|depth| {
299 let depth_val = *depth.borrow();
300 *depth.borrow_mut() = depth_val.saturating_add_signed(#val);
301 let depth_val= depth_val.saturating_add_signed((#val-1) / 2);
302 let spaces = "┆ ".repeat(depth_val);
303 format!("{}{} ", spaces, #ch)
304 })
305 }
306 }};
307}
308
309fn get_print_run_defaults() -> Option<&'static PrintRunArgs> {
310 PRINT_RUN_DEFAULTS.get()
311}
312
313#[proc_macro_attribute]
363pub fn print_run_defaults(attr: TokenStream, input: TokenStream) -> TokenStream {
364 let attr_clone = attr.clone();
365 let args = parse_macro_input!(attr as PrintRunArgs);
366
367 if PRINT_RUN_DEFAULTS.set(args).is_err() {
368 return Error::new_spanned(
369 TokenStream2::from(attr_clone),
370 "print run defaults already set",
371 )
372 .to_compile_error()
373 .into();
374 }
375
376 input
377}
378
379#[proc_macro_attribute]
479pub fn print_run(attr: TokenStream, item: TokenStream) -> TokenStream {
480 let args = parse_macro_input!(attr as PrintRunArgs);
481
482 if let Ok(mut func) = parse::<ItemFn>(item.clone()) {
484 let new_args = extract_and_flatten_print_args(&args, &mut func.attrs);
485 return print_run_fn(new_args, func);
486 }
487
488 if let Ok(mut module) = parse::<ItemMod>(item.clone()) {
490 let new_args = extract_and_flatten_print_args(&args, &mut module.attrs);
491 return print_run_mod(new_args, module);
492 }
493
494 if let Ok(mut implementation) = parse::<ItemImpl>(item.clone()) {
496 let new_args = extract_and_flatten_print_args(&args, &mut implementation.attrs);
497 return print_run_impl(new_args, implementation);
498 }
499
500 Error::new_spanned(
502 TokenStream2::from(item),
503 "#[print_run] can only be used on functions, implementations or inline modules",
504 )
505 .to_compile_error()
506 .into()
507}
508
509fn print_run_fn(mut args: PrintRunArgs, mut fn_item: ItemFn) -> TokenStream {
510 args.add_globals();
512
513 let PrintRunArgs {
515 colored,
516 duration,
517 indent,
518 skip,
519 supress_labels,
520 timestamps,
521 __struct_prefix,
522 } = args;
523 let colored = colored == Some(true);
524 let duration = duration == Some(true);
525 let indent = indent == Some(true);
526 let skip = skip == Some(true);
527 let supress_labels = supress_labels == Some(true);
528 let timestamps = timestamps == Some(true);
529
530 if skip {
531 let use_msg = parse_quote! { use std::println as msg; };
533 fn_item.block.stmts.insert(0, use_msg);
534 return fn_item.to_token_stream().into();
535 }
536
537 let ItemFn {
538 attrs,
539 vis,
540 sig,
541 block,
542 } = fn_item;
543
544 let fn_name = sig.ident.to_string();
546 let prefix = __struct_prefix.unwrap_or("".into());
547 let fn_name = format!("{prefix}{fn_name}");
548
549 let start_label = or_else!(!supress_labels, "starting", "");
551 let end_label = or_else!(!supress_labels, "ended", "");
552
553 let start = or_else!(colored, colorize!(fn_name.clone(), Yellow), fn_name.clone());
555 let end = or_else!(colored, colorize!(fn_name.clone(), Blue), fn_name.clone());
556
557 let create_timestamp_fn = or_empty_str!(timestamps, create_timestamp!(colored));
559
560 let duration_fn = or_else!(
562 duration,
563 create_duration!(colored, supress_labels),
564 quote! { |_| "".to_string() }
565 );
566
567 let indent_top = or_empty_str!(indent, create_indent!(1isize, "┌"));
569 let indent_bottom = or_empty_str!(indent, create_indent!(-1isize, "└"));
570 let indent_body = or_empty_str!(indent, create_indent!(0isize, ""));
571
572 let colorize_msg = if colored {
574 colorize_fn!(White, "bold")
575 } else {
576 colorize_fn!(Default, "bold")
577 };
578 let msg_macro = quote! {
579 #[allow(unused)]
580 macro_rules! msg {
581 ($($arg:tt)*) => {{
582 let ts = {#create_timestamp_fn}();
583 let indent = {#indent_body}();
584 let msg = format!($($arg)*);
585 let msg = {#colorize_msg}(msg);
586 println!("{}{} {}", ts, indent, msg);
587 }};
588 }
589 };
590
591 let new_block = quote_spanned! (block.to_token_stream().span() =>
593 {
594 let ts = {#create_timestamp_fn}();
595 let start = std::time::Instant::now();
596 let indent = {#indent_top}();
597 println!("{}{}{} {}", ts, indent, #start, #start_label);
598 #msg_macro
599
600 let result = {
601 #block
602 };
603
604 let dur = {#duration_fn}(start);
605 let ts = {#create_timestamp_fn}();
606 let indent = {#indent_bottom}();
607 println!("{}{}{} {}{}", ts, indent, #end, #end_label, dur);
608 result
609 }
610 );
611
612 let helper_module = define_helper_module();
614
615 quote! {
617 #(#attrs)*
618 #vis #sig #new_block
619 #helper_module
620 }
621 .into()
622}
623
624fn print_run_mod(args: PrintRunArgs, mut module_item: ItemMod) -> TokenStream {
625 let content = match module_item.content {
627 Some((_, ref mut items)) => items,
628 _ => {
629 return Error::new_spanned(
630 module_item.mod_token,
631 "`#[print_run]` only supports inline modules",
632 )
633 .to_compile_error()
634 .into();
635 }
636 };
637
638 for item in content {
640 match item {
641 Item::Fn(func) => {
643 let new_args = extract_and_flatten_print_args(&args, &mut func.attrs);
644 func.attrs.push(new_args.to_attribute());
645 }
646 Item::Impl(item_impl) => {
648 let new_args = extract_and_flatten_print_args(&args, &mut item_impl.attrs);
649 item_impl.attrs.push(new_args.to_attribute());
650 }
651 _ => {}
652 }
653 }
654
655 let helper_module = define_helper_module();
657 let module_tokens = module_item.into_token_stream();
658
659 quote! { #module_tokens #helper_module }.into()
660}
661
662fn print_run_impl(args: PrintRunArgs, mut impl_item: ItemImpl) -> TokenStream {
663 let ty_str = (&impl_item.self_ty).into_token_stream().to_string();
665
666 for impl_item in &mut impl_item.items {
668 if let ImplItem::Fn(method) = impl_item {
669 let mut new_args = extract_and_flatten_print_args(&args, &mut method.attrs);
670 let is_static = is_static_method(&method);
671 let ty_str = ty_str.clone() + if is_static { "::" } else { "." };
672 new_args.__struct_prefix = Some(ty_str);
673 method.attrs.push(new_args.to_attribute());
674 }
675 }
676
677 let helper_module = define_helper_module();
679 let impl_tokens = impl_item.into_token_stream();
680
681 quote! { #impl_tokens #helper_module }.into()
682}
683
684fn extract_and_flatten_print_args(
685 parent: &PrintRunArgs,
686 attrs: &mut Vec<Attribute>,
687) -> PrintRunArgs {
688 let mut merged_args = get_print_run_defaults()
690 .as_deref()
691 .and_then(|a| Some(a.clone()))
692 .unwrap_or(PrintRunArgs::default());
693
694 attrs.retain(|attr| {
696 if attr.path().is_ident("print_run") {
697 if let Ok(parsed) = attr.parse_args::<PrintRunArgs>() {
698 merged_args.merge(&parsed);
699 }
700 false } else {
702 true }
704 });
705 merged_args.merge(parent);
706 merged_args.add_globals();
707
708 merged_args
709}
710
711fn define_helper_module() -> TokenStream2 {
712 let mut define = false;
714 IS_HELPER_MODULE_ADDED.call_once(|| define = true);
715 or_nothing!(
716 define,
717 quote! {
718 #[doc(hidden)]
719 #[allow(unused)]
720 pub(crate) mod __print_run_helper {
721 use std::cell::RefCell;
722 thread_local! {
723 pub static DEPTH: RefCell<usize> = RefCell::new(0);
724 }
725 }
726 }
727 )
728}
729
730fn is_static_method(method: &ImplItemFn) -> bool {
731 match method.sig.inputs.first() {
732 Some(FnArg::Receiver(_)) => false, Some(FnArg::Typed(_)) => true, None => true, }
736}