Skip to main content

tusks_lib/codegen/preparse/tasks/
functions.rs

1use syn::{Item, ItemFn, ItemMod, parse_quote};
2use syn::{Attribute, Meta};
3use quote::quote;
4
5use crate::{AttributeValue, attribute::models::TasksConfig};
6
7pub fn add_use_statements(module: &mut ItemMod) {
8    let use_statement: Item = parse_quote! {
9        use ::tusks::clap::{CommandFactory, Parser};
10    };
11
12    if let Some((_, ref mut items)) = module.content {
13        items.insert(0, use_statement);
14    }
15}
16
17pub fn set_allow_external_subcommands(module: &mut ItemMod) {
18    if module.get_attribute_bool("command", "allow_external_subcommands") {
19        return;
20    }
21
22    // Find existing #[command(...)] attribute
23    if let Some(attr) = module.attrs.iter_mut().find(|a| a.path().is_ident("command")) {
24        match &mut attr.meta {
25            Meta::List(list) => {
26                let tokens_str = list.tokens.to_string();
27                let has_trailing_comma = tokens_str.trim_end().ends_with(',');
28
29                let tokens = &list.tokens;
30                if tokens.is_empty() {
31                    list.tokens = parse_quote! { allow_external_subcommands = true };
32                } else if has_trailing_comma {
33                    list.tokens = parse_quote! { #tokens allow_external_subcommands = true };
34                } else {
35                    list.tokens = parse_quote! { #tokens, allow_external_subcommands = true };
36                }
37            }
38            Meta::Path(_) => {
39                *attr = parse_quote! { #[command(allow_external_subcommands = true)] };
40            }
41            Meta::NameValue(_) => {
42                *attr = parse_quote! { #[command(allow_external_subcommands = true)] };
43            }
44        }
45    } else {
46        // No command attribute present — add a new one
47        let new_attr: Attribute = parse_quote! { #[command(allow_external_subcommands = true)] };
48        module.attrs.push(new_attr);
49    }
50}
51
52pub fn add_execute_task_function(module: &mut ItemMod, config: &TasksConfig) {
53    let separator = &config.separator;
54    let max_groupsize = &config.max_groupsize;
55    let max_depth = &config.max_depth;
56    let use_colors = &config.use_colors;
57
58    let (maybe_async, maybe_await) = if cfg!(feature = "async") {
59        (quote! { async }, quote! { .await })
60    } else {
61        (quote! {}, quote! {})
62    };
63
64    let tokens = quote! {
65        #[command(about = "Execute a task", hide=true)]
66        #[default]
67        pub #maybe_async fn _execute_task(external_args: Vec<String>) -> Option<u8> {
68            let command = __internal_tusks_module::cli::Cli::command();
69            if let Some(first) = external_args.first() {
70                let mut transformed_arguments = vec![command.get_name().to_string()];
71                transformed_arguments.extend(first.split(#separator).map(|s| s.to_string()));
72                transformed_arguments.extend_from_slice(&external_args[1..]);
73                let cli = __internal_tusks_module::cli::Cli::parse_from(transformed_arguments);
74                return __internal_tusks_module::handle_matches(&cli)#maybe_await;
75            }
76
77            let task_list = ::tusks::tasks::task_list::models::TaskList::from_command(
78                &command,
79                #separator.to_string(),
80                #max_groupsize,
81                #max_depth
82            );
83            let mut render_config = ::tusks::tasks::list::models::RenderConfig::default();
84            render_config.use_colors = #use_colors;
85            task_list.into_list().print(&render_config);
86            Some(0)
87        }
88    };
89
90    let function: ItemFn = syn::parse2(tokens).expect("failed to parse _execute_task");
91
92    if let Some((_, ref mut items)) = module.content {
93        items.push(Item::Fn(function));
94    }
95}
96
97pub fn add_show_help_for_task(module: &mut ItemMod, config: &TasksConfig) {
98    let separator = &config.separator;
99    let max_groupsize = &config.max_groupsize;
100    let max_depth = &config.max_depth;
101
102    let (maybe_async, maybe_await) = if cfg!(feature = "async") {
103        (quote! { async }, quote! { .await })
104    } else {
105        (quote! {}, quote! {})
106    };
107
108    let tokens = quote! {
109        #[command(about = "Show the help for a task", name="h", hide=true)]
110        pub #maybe_async fn _show_help_for_task(#[arg()] task: Option<String>) {
111            if let Some(task) = task {
112                let command = __internal_tusks_module::cli::Cli::command();
113                let parts: Vec<&str> = task.split(#separator).collect();
114
115                let args: Vec<&str> = std::iter::once(command.get_name())
116                    .chain(parts.iter().copied())
117                    .chain(std::iter::once("--help"))
118                    .collect();
119
120                let cli = __internal_tusks_module::cli::Cli::parse_from(args);
121                __internal_tusks_module::handle_matches(&cli)#maybe_await;
122            }
123            else {
124                let command = __internal_tusks_module::cli::Cli::command();
125                let task_list = ::tusks::tasks::task_list::models::TaskList::from_command(
126                    &command,
127                    #separator.to_string(),
128                    #max_groupsize,
129                    #max_depth
130                );
131                task_list.into_list().print(&::tusks::tasks::list::models::RenderConfig::default());
132            }
133        }
134    };
135
136    let function: ItemFn = syn::parse2(tokens).expect("failed to parse _show_help_for_task");
137
138    if let Some((_, ref mut items)) = module.content {
139        items.push(Item::Fn(function));
140    }
141}