From ecd4d6587c24f42d96735205d4d2cc4a23c8a18f Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Wed, 5 Nov 2025 15:50:39 -0700 Subject: [PATCH] refactor: Factored out the macros structs from the large config module --- src/config/macros.rs | 109 +++++++++++++++++++++++++++++++++++++++++++ src/config/mod.rs | 105 ++--------------------------------------- 2 files changed, 112 insertions(+), 102 deletions(-) create mode 100644 src/config/macros.rs diff --git a/src/config/macros.rs b/src/config/macros.rs new file mode 100644 index 0000000..7fa6d60 --- /dev/null +++ b/src/config/macros.rs @@ -0,0 +1,109 @@ +use crate::config::{Config, GlobalConfig, RoleLike}; +use crate::repl::{run_repl_command, split_args_text}; +use crate::utils::{multiline_text, AbortSignal}; +use anyhow::anyhow; +use indexmap::IndexMap; +use parking_lot::RwLock; +use serde::Deserialize; +use std::sync::Arc; + +#[async_recursion::async_recursion] +pub async fn macro_execute( + config: &GlobalConfig, + name: &str, + args: Option<&str>, + abort_signal: AbortSignal, +) -> anyhow::Result<()> { + let macro_value = Config::load_macro(name)?; + let (mut new_args, text) = split_args_text(args.unwrap_or_default(), cfg!(windows)); + if !text.is_empty() { + new_args.push(text.to_string()); + } + let variables = macro_value + .resolve_variables(&new_args) + .map_err(|err| anyhow!("{err}. Usage: {}", macro_value.usage(name)))?; + let role = config.read().extract_role(); + let mut config = config.read().clone(); + config.temperature = role.temperature(); + config.top_p = role.top_p(); + config.enabled_tools = role.enabled_tools().clone(); + config.enabled_mcp_servers = role.enabled_mcp_servers().clone(); + config.macro_flag = true; + config.model = role.model().clone(); + config.role = None; + config.session = None; + config.rag = None; + config.agent = None; + config.discontinuous_last_message(); + let config = Arc::new(RwLock::new(config)); + config.write().macro_flag = true; + for step in ¯o_value.steps { + let command = Macro::interpolate_command(step, &variables); + println!(">> {}", multiline_text(&command)); + run_repl_command(&config, abort_signal.clone(), &command).await?; + } + Ok(()) +} + +#[derive(Debug, Clone, Deserialize)] +pub struct Macro { + #[serde(default)] + pub variables: Vec, + pub steps: Vec, +} + +impl Macro { + pub fn resolve_variables(&self, args: &[String]) -> anyhow::Result> { + let mut output = IndexMap::new(); + for (i, variable) in self.variables.iter().enumerate() { + let value = if variable.rest && i == self.variables.len() - 1 { + if args.len() > i { + Some(args[i..].join(" ")) + } else { + variable.default.clone() + } + } else { + args.get(i) + .map(|v| v.to_string()) + .or_else(|| variable.default.clone()) + }; + let value = + value.ok_or_else(|| anyhow!("Missing value for variable '{}'", variable.name))?; + output.insert(variable.name.clone(), value); + } + Ok(output) + } + + pub fn usage(&self, name: &str) -> String { + let mut parts = vec![name.to_string()]; + for (i, variable) in self.variables.iter().enumerate() { + let part = match ( + variable.rest && i == self.variables.len() - 1, + variable.default.is_some(), + ) { + (true, true) => format!("[{}]...", variable.name), + (true, false) => format!("<{}>...", variable.name), + (false, true) => format!("[{}]", variable.name), + (false, false) => format!("<{}>", variable.name), + }; + parts.push(part); + } + parts.join(" ") + } + + pub fn interpolate_command(command: &str, variables: &IndexMap) -> String { + let mut output = command.to_string(); + for (key, value) in variables { + output = output.replace(&format!("{{{{{key}}}}}"), value); + } + output + } +} + +#[derive(Debug, Clone, Deserialize)] +pub struct MacroVariable { + pub name: String, + #[serde(default)] + pub rest: bool, + pub default: Option, +} diff --git a/src/config/mod.rs b/src/config/mod.rs index 241acdb..5f19664 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,5 +1,6 @@ mod agent; mod input; +mod macros; mod role; mod session; @@ -9,6 +10,7 @@ pub use self::role::{ Role, RoleLike, CODE_ROLE, CREATE_TITLE_ROLE, EXPLAIN_SHELL_ROLE, SHELL_ROLE, }; use self::session::Session; +pub use macros::macro_execute; use mem::take; use crate::client::{ @@ -18,9 +20,9 @@ use crate::client::{ use crate::function::{FunctionDeclaration, Functions, ToolResult}; use crate::rag::Rag; use crate::render::{MarkdownRender, RenderOptions}; -use crate::repl::{run_repl_command, split_args_text}; use crate::utils::*; +use crate::config::macros::Macro; use crate::mcp::{ McpRegistry, MCP_INVOKE_META_FUNCTION_NAME_PREFIX, MCP_LIST_META_FUNCTION_NAME_PREFIX, }; @@ -2948,107 +2950,6 @@ impl WorkingMode { } } -#[async_recursion::async_recursion] -pub async fn macro_execute( - config: &GlobalConfig, - name: &str, - args: Option<&str>, - abort_signal: AbortSignal, -) -> Result<()> { - let macro_value = Config::load_macro(name)?; - let (mut new_args, text) = split_args_text(args.unwrap_or_default(), cfg!(windows)); - if !text.is_empty() { - new_args.push(text.to_string()); - } - let variables = macro_value - .resolve_variables(&new_args) - .map_err(|err| anyhow!("{err}. Usage: {}", macro_value.usage(name)))?; - let role = config.read().extract_role(); - let mut config = config.read().clone(); - config.temperature = role.temperature(); - config.top_p = role.top_p(); - config.enabled_tools = role.enabled_tools().clone(); - config.enabled_mcp_servers = role.enabled_mcp_servers().clone(); - config.macro_flag = true; - config.model = role.model().clone(); - config.role = None; - config.session = None; - config.rag = None; - config.agent = None; - config.discontinuous_last_message(); - let config = Arc::new(RwLock::new(config)); - config.write().macro_flag = true; - for step in ¯o_value.steps { - let command = Macro::interpolate_command(step, &variables); - println!(">> {}", multiline_text(&command)); - run_repl_command(&config, abort_signal.clone(), &command).await?; - } - Ok(()) -} - -#[derive(Debug, Clone, Deserialize)] -pub struct Macro { - #[serde(default)] - pub variables: Vec, - pub steps: Vec, -} - -impl Macro { - pub fn resolve_variables(&self, args: &[String]) -> Result> { - let mut output = IndexMap::new(); - for (i, variable) in self.variables.iter().enumerate() { - let value = if variable.rest && i == self.variables.len() - 1 { - if args.len() > i { - Some(args[i..].join(" ")) - } else { - variable.default.clone() - } - } else { - args.get(i) - .map(|v| v.to_string()) - .or_else(|| variable.default.clone()) - }; - let value = - value.ok_or_else(|| anyhow!("Missing value for variable '{}'", variable.name))?; - output.insert(variable.name.clone(), value); - } - Ok(output) - } - - pub fn usage(&self, name: &str) -> String { - let mut parts = vec![name.to_string()]; - for (i, variable) in self.variables.iter().enumerate() { - let part = match ( - variable.rest && i == self.variables.len() - 1, - variable.default.is_some(), - ) { - (true, true) => format!("[{}]...", variable.name), - (true, false) => format!("<{}>...", variable.name), - (false, true) => format!("[{}]", variable.name), - (false, false) => format!("<{}>", variable.name), - }; - parts.push(part); - } - parts.join(" ") - } - - pub fn interpolate_command(command: &str, variables: &IndexMap) -> String { - let mut output = command.to_string(); - for (key, value) in variables { - output = output.replace(&format!("{{{{{key}}}}}"), value); - } - output - } -} - -#[derive(Debug, Clone, Deserialize)] -pub struct MacroVariable { - pub name: String, - #[serde(default)] - pub rest: bool, - pub default: Option, -} - #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ModelsOverride { pub version: String,