Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
// GNU Affero General Public License for more details.

use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};

Expand Down Expand Up @@ -63,6 +64,8 @@ pub struct Config {
pub quiet: Option<bool>,
/// Update configuration section
pub update: Option<UpdateConfig>,
/// Custom commands overrides
pub commands: Option<HashMap<String, String>>,
}

impl Config {
Expand Down Expand Up @@ -129,6 +132,15 @@ impl Config {
(Some(base), None) => Some(base),
(None, None) => None,
},
commands: match (self.commands, other.commands) {
(Some(mut base), Some(over)) => {
base.extend(over);
Some(base)
}
(None, Some(over)) => Some(over),
(Some(base), None) => Some(base),
(None, None) => None,
},
}
}

Expand Down Expand Up @@ -200,6 +212,7 @@ mod tests {
verbose: None,
quiet: None,
update: None,
commands: None,
};

let override_config = Config {
Expand All @@ -209,6 +222,7 @@ mod tests {
verbose: Some(true),
quiet: None,
update: None,
commands: None,
};

let merged = base.merge(override_config);
Expand All @@ -218,6 +232,34 @@ mod tests {
assert!(merged.get_verbose());
}

#[test]
fn test_merge_commands() {
let mut base_cmds = HashMap::new();
base_cmds.insert("base".to_string(), "echo base".to_string());
base_cmds.insert("both".to_string(), "echo base_both".to_string());

let base = Config {
commands: Some(base_cmds),
..Default::default()
};

let mut override_cmds = HashMap::new();
override_cmds.insert("over".to_string(), "echo over".to_string());
override_cmds.insert("both".to_string(), "echo over_both".to_string());

let override_config = Config {
commands: Some(override_cmds),
..Default::default()
};

let merged = base.merge(override_config);
let cmds = merged.commands.unwrap();

assert_eq!(cmds.get("base").unwrap(), "echo base");
assert_eq!(cmds.get("over").unwrap(), "echo over");
assert_eq!(cmds.get("both").unwrap(), "echo over_both");
}

#[test]
fn test_load_from_file() {
let dir = tempdir().unwrap();
Expand Down
6 changes: 6 additions & 0 deletions src/detectors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,12 @@ impl DetectedRunner {

/// Check if this runner supports the given command.
pub fn supports_command(&self, command: &str, working_dir: &Path) -> CommandSupport {
// First check if this is a custom command
if let Some(commands) = &self.custom_commands {
if commands.contains_key(command) {
return CommandSupport::Supported;
}
}
self.validator.supports_command(working_dir, command)
}

Expand Down
72 changes: 67 additions & 5 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ use clap::{CommandFactory, Parser};
use clap_complete::generate;
use run_cli::cli::{Cli, Commands};
use run_cli::config::Config;
use run_cli::detectors::{DetectedRunner, Ecosystem, UnknownValidator};
use run_cli::error::exit_codes;
use run_cli::output;
use run_cli::runner::{check_conflicts, execute, search_runners, select_runner};
use run_cli::update;
use std::env;
use std::io;
use std::process;
use std::sync::Arc;

fn main() {
// Check for internal update flag (used by background updater)
Expand Down Expand Up @@ -94,20 +96,80 @@ fn main() {
};

// Search for runners
let (runners, working_dir) = match search_runners(
let search_result = search_runners(
&current_dir,
max_levels,
&ignore_list,
verbose,
) {
);

// Prepare to inject custom commands
// Filter empty commands
let valid_config_commands: Option<std::collections::HashMap<String, String>> =
config.commands.as_ref().map(|cmds| {
cmds.iter()
.filter(|(_, cmd)| !cmd.trim().is_empty())
.map(|(k, v)| (k.clone(), v.clone()))
.collect()
});

let has_valid_commands = valid_config_commands
.as_ref()
.map_or(false, |c| !c.is_empty());

let (mut runners, working_dir) = match search_result {
Ok(result) => result,
Err(e) => {
output::error(&e.to_string());
eprintln!("Hint: Use --levels=N to increase search depth or check if you're in the right directory.");
process::exit(e.exit_code());
if has_valid_commands {
// If we have custom commands, we can proceed even without detected runners
(Vec::new(), current_dir.clone())
} else {
output::error(&e.to_string());
eprintln!("Hint: Use --levels=N to increase search depth or check if you're in the right directory.");
process::exit(e.exit_code());
}
}
};

// Inject custom commands from config
if let Some(valid_config_commands) = valid_config_commands {
if !valid_config_commands.is_empty() {
// Check if we already have a custom runner
if let Some(idx) = runners.iter().position(|r| r.ecosystem == Ecosystem::Custom) {
// Merge config commands into existing runner (local overrides global)
let mut merged_commands = valid_config_commands.clone();
if let Some(existing_cmds) = &runners[idx].custom_commands {
merged_commands.extend(existing_cmds.clone());
}

// Update the runner
let old_runner = &runners[idx];
let new_runner = DetectedRunner::with_custom_commands(
&old_runner.name,
&old_runner.detected_file,
old_runner.ecosystem,
old_runner.priority,
Arc::new(UnknownValidator),
merged_commands,
);
runners[idx] = new_runner;
} else {
// Create new runner
let new_runner = DetectedRunner::with_custom_commands(
"custom",
"config.toml",
Ecosystem::Custom,
0,
Arc::new(UnknownValidator),
valid_config_commands,
);
runners.push(new_runner);
// Sort by priority (0 first)
runners.sort_by_key(|r| r.priority);
}
}
}

// Check for conflicts and select runner based on command support
let runner = match check_conflicts(&runners, &working_dir, verbose) {
Ok(_) => match select_runner(&runners, &command, &working_dir, verbose) {
Expand Down
Loading