tusks-lib 3.0.0

Declarative CLI framework built on top of clap
Documentation
use syn::{Item, ItemFn, ItemMod, parse_quote};
use syn::{Attribute, Meta};
use quote::quote;

use crate::{AttributeValue, attribute::models::TasksConfig};

pub fn add_use_statements(module: &mut ItemMod) {
    let use_statement: Item = parse_quote! {
        use ::tusks::clap::{CommandFactory, Parser};
    };

    if let Some((_, ref mut items)) = module.content {
        items.insert(0, use_statement);
    }
}

pub fn set_allow_external_subcommands(module: &mut ItemMod) {
    if module.get_attribute_bool("command", "allow_external_subcommands") {
        return;
    }

    // Find existing #[command(...)] attribute
    if let Some(attr) = module.attrs.iter_mut().find(|a| a.path().is_ident("command")) {
        match &mut attr.meta {
            Meta::List(list) => {
                let tokens_str = list.tokens.to_string();
                let has_trailing_comma = tokens_str.trim_end().ends_with(',');

                let tokens = &list.tokens;
                if tokens.is_empty() {
                    list.tokens = parse_quote! { allow_external_subcommands = true };
                } else if has_trailing_comma {
                    list.tokens = parse_quote! { #tokens allow_external_subcommands = true };
                } else {
                    list.tokens = parse_quote! { #tokens, allow_external_subcommands = true };
                }
            }
            Meta::Path(_) => {
                *attr = parse_quote! { #[command(allow_external_subcommands = true)] };
            }
            Meta::NameValue(_) => {
                *attr = parse_quote! { #[command(allow_external_subcommands = true)] };
            }
        }
    } else {
        // No command attribute present — add a new one
        let new_attr: Attribute = parse_quote! { #[command(allow_external_subcommands = true)] };
        module.attrs.push(new_attr);
    }
}

pub fn add_execute_task_function(module: &mut ItemMod, config: &TasksConfig) {
    let separator = &config.separator;
    let max_groupsize = &config.max_groupsize;
    let max_depth = &config.max_depth;
    let use_colors = &config.use_colors;

    let (maybe_async, maybe_await) = if cfg!(feature = "async") {
        (quote! { async }, quote! { .await })
    } else {
        (quote! {}, quote! {})
    };

    let tokens = quote! {
        #[command(about = "Execute a task", hide=true)]
        #[default]
        pub #maybe_async fn _execute_task(external_args: Vec<String>) -> Option<u8> {
            let command = __internal_tusks_module::cli::Cli::command();
            if let Some(first) = external_args.first() {
                let mut transformed_arguments = vec![command.get_name().to_string()];
                transformed_arguments.extend(first.split(#separator).map(|s| s.to_string()));
                transformed_arguments.extend_from_slice(&external_args[1..]);
                let cli = __internal_tusks_module::cli::Cli::parse_from(transformed_arguments);
                return __internal_tusks_module::handle_matches(&cli)#maybe_await;
            }

            let task_list = ::tusks::tasks::task_list::models::TaskList::from_command(
                &command,
                #separator.to_string(),
                #max_groupsize,
                #max_depth
            );
            let mut render_config = ::tusks::tasks::list::models::RenderConfig::default();
            render_config.use_colors = #use_colors;
            task_list.to_list().print(&render_config);
            Some(0)
        }
    };

    let function: ItemFn = syn::parse2(tokens).expect("failed to parse _execute_task");

    if let Some((_, ref mut items)) = module.content {
        items.push(Item::Fn(function));
    }
}

pub fn add_show_help_for_task(module: &mut ItemMod, config: &TasksConfig) {
    let separator = &config.separator;
    let max_groupsize = &config.max_groupsize;
    let max_depth = &config.max_depth;

    let (maybe_async, maybe_await) = if cfg!(feature = "async") {
        (quote! { async }, quote! { .await })
    } else {
        (quote! {}, quote! {})
    };

    let tokens = quote! {
        #[command(about = "Show the help for a task", name="h", hide=true)]
        pub #maybe_async fn _show_help_for_task(#[arg()] task: Option<String>) {
            if let Some(task) = task {
                let command = __internal_tusks_module::cli::Cli::command();
                let parts: Vec<&str> = task.split(#separator).collect();

                let args: Vec<&str> = std::iter::once(command.get_name())
                    .chain(parts.iter().copied())
                    .chain(std::iter::once("--help"))
                    .collect();

                let cli = __internal_tusks_module::cli::Cli::parse_from(args);
                __internal_tusks_module::handle_matches(&cli)#maybe_await;
            }
            else {
                let command = __internal_tusks_module::cli::Cli::command();
                let task_list = ::tusks::tasks::task_list::models::TaskList::from_command(
                    &command,
                    #separator.to_string(),
                    #max_groupsize,
                    #max_depth
                );
                task_list.to_list().print(&::tusks::tasks::list::models::RenderConfig::default());
            }
        }
    };

    let function: ItemFn = syn::parse2(tokens).expect("failed to parse _show_help_for_task");

    if let Some((_, ref mut items)) = module.content {
        items.push(Item::Fn(function));
    }
}