✨ feat: integrate configurable rules path throughout CLI
- Make parse_config_root() public for reuse in main.rs - Add get_rules_path() helper to extract and resolve rules path from config - Update main CLI to use configured rules path for default rule execution - Add run_with_rules_path() method to RulesCli for custom path support - Update all unit tests to include rules_dir field - Rules path now supports h:, c:, r: prefixes throughout the application
This commit is contained in:
committed by
Jeremiah Russell
parent
20b36a00ed
commit
bcb93fd68f
@@ -60,7 +60,13 @@ use cull_gmail::{ClientConfig, Error, GmailClient, Result};
|
|||||||
use lazy_regex::{Lazy, Regex, lazy_regex};
|
use lazy_regex::{Lazy, Regex, lazy_regex};
|
||||||
|
|
||||||
/// Parse configuration root path with h:, c:, r: prefixes.
|
/// Parse configuration root path with h:, c:, r: prefixes.
|
||||||
fn parse_config_root(path_str: &str) -> PathBuf {
|
///
|
||||||
|
/// Supports:
|
||||||
|
/// - `h:path` - Relative to home directory
|
||||||
|
/// - `c:path` - Relative to current directory
|
||||||
|
/// - `r:path` - Relative to filesystem root
|
||||||
|
/// - `path` - Use path as-is
|
||||||
|
pub fn parse_config_root(path_str: &str) -> PathBuf {
|
||||||
static ROOT_CONFIG: Lazy<Regex> = lazy_regex!(r"^(?P<class>[hrc]):(?P<path>.+)$");
|
static ROOT_CONFIG: Lazy<Regex> = lazy_regex!(r"^(?P<class>[hrc]):(?P<path>.+)$");
|
||||||
|
|
||||||
if let Some(captures) = ROOT_CONFIG.captures(path_str) {
|
if let Some(captures) = ROOT_CONFIG.captures(path_str) {
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ mod unit_tests {
|
|||||||
create_mock_credential_file(temp_dir.path()).unwrap();
|
create_mock_credential_file(temp_dir.path()).unwrap();
|
||||||
|
|
||||||
let init_cli = InitCli {
|
let init_cli = InitCli {
|
||||||
|
rules_dir: None,
|
||||||
config_dir: "test".to_string(),
|
config_dir: "test".to_string(),
|
||||||
credential_file: None,
|
credential_file: None,
|
||||||
force: false,
|
force: false,
|
||||||
@@ -88,6 +89,7 @@ mod unit_tests {
|
|||||||
fn test_validate_credential_file_not_found() {
|
fn test_validate_credential_file_not_found() {
|
||||||
let temp_dir = TempDir::new().unwrap();
|
let temp_dir = TempDir::new().unwrap();
|
||||||
let init_cli = InitCli {
|
let init_cli = InitCli {
|
||||||
|
rules_dir: None,
|
||||||
config_dir: "test".to_string(),
|
config_dir: "test".to_string(),
|
||||||
credential_file: None,
|
credential_file: None,
|
||||||
force: false,
|
force: false,
|
||||||
@@ -108,6 +110,7 @@ mod unit_tests {
|
|||||||
fs::write(&credential_path, "invalid json content").unwrap();
|
fs::write(&credential_path, "invalid json content").unwrap();
|
||||||
|
|
||||||
let init_cli = InitCli {
|
let init_cli = InitCli {
|
||||||
|
rules_dir: None,
|
||||||
config_dir: "test".to_string(),
|
config_dir: "test".to_string(),
|
||||||
credential_file: None,
|
credential_file: None,
|
||||||
force: false,
|
force: false,
|
||||||
@@ -131,6 +134,7 @@ mod unit_tests {
|
|||||||
let config_path = temp_dir.path().join("new-config");
|
let config_path = temp_dir.path().join("new-config");
|
||||||
|
|
||||||
let init_cli = InitCli {
|
let init_cli = InitCli {
|
||||||
|
rules_dir: None,
|
||||||
config_dir: "test".to_string(),
|
config_dir: "test".to_string(),
|
||||||
credential_file: None,
|
credential_file: None,
|
||||||
force: false,
|
force: false,
|
||||||
@@ -183,6 +187,7 @@ mod unit_tests {
|
|||||||
fs::rename(temp_dir.path().join("credential.json"), &cred_path).unwrap();
|
fs::rename(temp_dir.path().join("credential.json"), &cred_path).unwrap();
|
||||||
|
|
||||||
let init_cli = InitCli {
|
let init_cli = InitCli {
|
||||||
|
rules_dir: None,
|
||||||
config_dir: "test".to_string(),
|
config_dir: "test".to_string(),
|
||||||
credential_file: None,
|
credential_file: None,
|
||||||
force: false,
|
force: false,
|
||||||
@@ -220,6 +225,7 @@ mod unit_tests {
|
|||||||
fs::write(config_path.join("cull-gmail.toml"), "existing config").unwrap();
|
fs::write(config_path.join("cull-gmail.toml"), "existing config").unwrap();
|
||||||
|
|
||||||
let init_cli = InitCli {
|
let init_cli = InitCli {
|
||||||
|
rules_dir: None,
|
||||||
config_dir: "test".to_string(),
|
config_dir: "test".to_string(),
|
||||||
credential_file: None,
|
credential_file: None,
|
||||||
force: false,
|
force: false,
|
||||||
@@ -243,6 +249,7 @@ mod unit_tests {
|
|||||||
fs::write(config_path.join("rules.toml"), "existing rules").unwrap();
|
fs::write(config_path.join("rules.toml"), "existing rules").unwrap();
|
||||||
|
|
||||||
let init_cli = InitCli {
|
let init_cli = InitCli {
|
||||||
|
rules_dir: None,
|
||||||
config_dir: "test".to_string(),
|
config_dir: "test".to_string(),
|
||||||
credential_file: None,
|
credential_file: None,
|
||||||
force: true,
|
force: true,
|
||||||
@@ -275,6 +282,7 @@ mod unit_tests {
|
|||||||
fs::write(&test_file, "test content").unwrap();
|
fs::write(&test_file, "test content").unwrap();
|
||||||
|
|
||||||
let init_cli = InitCli {
|
let init_cli = InitCli {
|
||||||
|
rules_dir: None,
|
||||||
config_dir: "test".to_string(),
|
config_dir: "test".to_string(),
|
||||||
credential_file: None,
|
credential_file: None,
|
||||||
force: false,
|
force: false,
|
||||||
@@ -317,6 +325,7 @@ mod unit_tests {
|
|||||||
fs::write(&test_file, "test content").unwrap();
|
fs::write(&test_file, "test content").unwrap();
|
||||||
|
|
||||||
let init_cli = InitCli {
|
let init_cli = InitCli {
|
||||||
|
rules_dir: None,
|
||||||
config_dir: "test".to_string(),
|
config_dir: "test".to_string(),
|
||||||
credential_file: None,
|
credential_file: None,
|
||||||
force: false,
|
force: false,
|
||||||
|
|||||||
@@ -127,6 +127,8 @@ use messages_cli::MessagesCli;
|
|||||||
use rules_cli::RulesCli;
|
use rules_cli::RulesCli;
|
||||||
use token_cli::{TokenCli, restore_tokens_from_string};
|
use token_cli::{TokenCli, restore_tokens_from_string};
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
/// Main CLI application structure defining global options and subcommands.
|
/// Main CLI application structure defining global options and subcommands.
|
||||||
///
|
///
|
||||||
/// This struct represents the root of the command-line interface, providing
|
/// This struct represents the root of the command-line interface, providing
|
||||||
@@ -304,8 +306,11 @@ async fn run(args: Cli) -> Result<()> {
|
|||||||
|
|
||||||
let mut client = GmailClient::new_with_config(client_config).await?;
|
let mut client = GmailClient::new_with_config(client_config).await?;
|
||||||
|
|
||||||
|
// Get configured rules path
|
||||||
|
let rules_path = get_rules_path(&config)?;
|
||||||
|
|
||||||
let Some(sub_command) = args.sub_command else {
|
let Some(sub_command) = args.sub_command else {
|
||||||
let rules = rules_cli::get_rules()?;
|
let rules = rules_cli::get_rules_from(rules_path.as_deref())?;
|
||||||
let execute = config.get_bool("execute").unwrap_or(false);
|
let execute = config.get_bool("execute").unwrap_or(false);
|
||||||
return run_rules(&mut client, rules, execute).await;
|
return run_rules(&mut client, rules, execute).await;
|
||||||
};
|
};
|
||||||
@@ -317,7 +322,11 @@ async fn run(args: Cli) -> Result<()> {
|
|||||||
}
|
}
|
||||||
SubCmds::Message(messages_cli) => messages_cli.run(&mut client).await,
|
SubCmds::Message(messages_cli) => messages_cli.run(&mut client).await,
|
||||||
SubCmds::Labels(labels_cli) => labels_cli.run(client).await,
|
SubCmds::Labels(labels_cli) => labels_cli.run(client).await,
|
||||||
SubCmds::Rules(rules_cli) => rules_cli.run(&mut client).await,
|
SubCmds::Rules(rules_cli) => {
|
||||||
|
rules_cli
|
||||||
|
.run_with_rules_path(&mut client, rules_path.as_deref())
|
||||||
|
.await
|
||||||
|
}
|
||||||
SubCmds::Token(token_cli) => {
|
SubCmds::Token(token_cli) => {
|
||||||
// Token commands don't need an initialized client, just the config
|
// Token commands don't need an initialized client, just the config
|
||||||
// We need to get a fresh client_config since the original was moved
|
// We need to get a fresh client_config since the original was moved
|
||||||
@@ -530,6 +539,33 @@ async fn run_rules(client: &mut GmailClient, rules: Rules, execute: bool) -> Res
|
|||||||
/// - Container deployments with injected token environment variables
|
/// - Container deployments with injected token environment variables
|
||||||
/// - CI/CD pipelines with stored token secrets
|
/// - CI/CD pipelines with stored token secrets
|
||||||
/// - Ephemeral compute environments requiring periodic Gmail access
|
/// - Ephemeral compute environments requiring periodic Gmail access
|
||||||
|
/// Gets the rules file path from configuration.
|
||||||
|
///
|
||||||
|
/// Reads the `rules` configuration value and resolves it using path prefixes.
|
||||||
|
/// Supports h:, c:, r: prefixes for home, current, and root directories.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `config` - Application configuration
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// Returns the resolved rules file path, or None if using default location.
|
||||||
|
fn get_rules_path(config: &Config) -> Result<Option<PathBuf>> {
|
||||||
|
let rules_config = config
|
||||||
|
.get_string("rules")
|
||||||
|
.unwrap_or_else(|_| "rules.toml".to_string());
|
||||||
|
|
||||||
|
// If it's just "rules.toml" (the default), return None to use default location
|
||||||
|
if rules_config == "rules.toml" {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, parse the path with prefix support
|
||||||
|
let path = init_cli::parse_config_root(&rules_config);
|
||||||
|
Ok(Some(path))
|
||||||
|
}
|
||||||
|
|
||||||
fn restore_tokens_if_available(config: &Config, client_config: &ClientConfig) -> Result<()> {
|
fn restore_tokens_if_available(config: &Config, client_config: &ClientConfig) -> Result<()> {
|
||||||
let token_env_var = config
|
let token_env_var = config
|
||||||
.get_string("token_cache_env")
|
.get_string("token_cache_env")
|
||||||
|
|||||||
@@ -263,7 +263,21 @@ impl RulesCli {
|
|||||||
/// - **Error isolation**: Subcommand errors don't affect rule loading
|
/// - **Error isolation**: Subcommand errors don't affect rule loading
|
||||||
/// - **State preservation**: Configuration errors don't corrupt existing rules
|
/// - **State preservation**: Configuration errors don't corrupt existing rules
|
||||||
pub async fn run(&self, client: &mut GmailClient) -> Result<()> {
|
pub async fn run(&self, client: &mut GmailClient) -> Result<()> {
|
||||||
let rules = get_rules()?;
|
self.run_with_rules_path(client, None).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Executes the rules command with an optional custom rules path.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `client` - Mutable Gmail client for API operations
|
||||||
|
/// * `rules_path` - Optional path to rules file
|
||||||
|
pub async fn run_with_rules_path(
|
||||||
|
&self,
|
||||||
|
client: &mut GmailClient,
|
||||||
|
rules_path: Option<&Path>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let rules = get_rules_from(rules_path)?;
|
||||||
|
|
||||||
match &self.sub_command {
|
match &self.sub_command {
|
||||||
SubCmds::Config(config_cli) => config_cli.run(rules),
|
SubCmds::Config(config_cli) => config_cli.run(rules),
|
||||||
|
|||||||
Reference in New Issue
Block a user