Skip to content

Commit

Permalink
feat: never exit with nonzero code and respect existing env vars
Browse files Browse the repository at this point in the history
  • Loading branch information
mohsen1 committed Jan 17, 2025
1 parent 95a5ce5 commit ce762ca
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 89 deletions.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,22 @@ stop-nagging [options]
3. If more complex logic is needed, you can add or modify Rust code in `src/runner.rs`
4. Submit a Pull Request

## Behavior: Non-Failing

`stop-nagging` **never** exits with a nonzero code, even if it fails to disable certain nags. This ensures your CI/CD pipeline won't break due to a missing or optional tool. Instead, it prints **warnings** for:

1. Missing executables (not found in `PATH`).
2. Commands that fail.
3. Already-set environment variables (which we won't override).

You'll see these warnings in the console logs, but your process will exit **0** regardless.

## Skipping Already-Set Environment Variables

If an environment variable is **already set**, `stop-nagging` **does not override** it. This avoids unintentional conflicts with variables you may want set differently. If a var is already set, we print a warning like:

Warning: Env var 'KEY' is already set; skipping override for tool 'npm'.

```
```
10 changes: 3 additions & 7 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,16 @@ fn main() {
let args = StopNaggingArgs::parse();
let yaml_path = &args.yaml;

// Parse the YAML
let yaml_config = match YamlToolsConfig::from_yaml_file(yaml_path) {
Ok(config) => config,
Err(e) => {
eprintln!("Failed to read YAML config '{}': {}", yaml_path, e);
std::process::exit(1);
std::process::exit(0);
}
};

// Run the disable logic
if let Err(e) = disable_nags(&yaml_config, &args.ecosystems, &args.ignore_tools) {
eprintln!("Failed to disable nags: {}", e);
std::process::exit(1);
}
disable_nags(&yaml_config, &args.ecosystems, &args.ignore_tools);

println!("All applicable nags have been disabled (or attempts made).");
std::process::exit(0);
}
53 changes: 26 additions & 27 deletions src/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,24 @@ pub fn disable_nags(
yaml_config: &YamlToolsConfig,
selected_ecosystems: &[String],
ignore_list: &[String],
) -> Result<(), StopNaggingError> {
// Convert user's ecosystem list to lowercase for simpler matching
) {
let selected_ecosystems: HashSet<String> = selected_ecosystems
.iter()
.map(|s| s.to_lowercase())
.collect();

// Convert ignore list to lowercase
let ignore_list: HashSet<String> = ignore_list.iter().map(|s| s.to_lowercase()).collect();

// If user passed no ecosystems, we'll run them all
let run_all_ecosystems = selected_ecosystems.is_empty();

// Iterate over each ecosystem in the YAML
for (ecosystem_name, ecosystem_config) in &yaml_config.ecosystems {
let ecosystem_name_lower = ecosystem_name.to_lowercase();

// Skip if user specified ecosystems AND this one isn't in that list
if !run_all_ecosystems && !selected_ecosystems.contains(&ecosystem_name_lower) {
println!("Skipping ecosystem: {}", ecosystem_name);
continue;
}

// Process each tool
for tool in &ecosystem_config.tools {
// If tool is marked skip in YAML or in CLI ignore list, skip it
if tool.skip.unwrap_or(false) || ignore_list.contains(&tool.name.to_lowercase()) {
println!(
"Ignoring tool: {} (ecosystem: {})",
Expand All @@ -40,46 +32,53 @@ pub fn disable_nags(
continue;
}

if let Err(msg) = check_tool_executable(&tool.executable) {
eprintln!(
"Tool {} not found in ecosystem {}: {}",
tool.name, ecosystem_name, msg
);
continue;
match check_tool_executable(&tool.executable) {
Ok(()) => { /* tool found, continue */ }
Err(msg) => {
eprintln!(
"Warning: Tool '{}' not found in ecosystem '{}': {}",
tool.name, ecosystem_name, msg
);
continue;
}
}

// Set environment variables
if let Some(env_vars) = &tool.env {
for (key, val) in env_vars {
env::set_var(key, val);
println!(
"Set {}={} for tool {} in {}",
key, val, tool.name, ecosystem_name
);
if env::var_os(key).is_some() {
eprintln!(
"Warning: Env var '{}' is already set; skipping override for tool '{}'.",
key, tool.name
);
} else {
env::set_var(key, val);
println!(
"Set {}={} for tool {} in {}",
key, val, tool.name, ecosystem_name
);
}
}
}

// Run each command
if let Some(cmds) = &tool.commands {
for cmd_str in cmds {
println!(
"Running command for {} in {}: {}",
tool.name, ecosystem_name, cmd_str
);
match run_shell_command(cmd_str) {
Ok(_) => println!("Command succeeded."),
Err(e) => eprintln!("Failed to run command '{}': {}", cmd_str, e),
if let Err(e) = run_shell_command(cmd_str) {
eprintln!("Warning: Failed to run command '{}': {}", cmd_str, e);
} else {
println!("Command succeeded.");
}
}
}
}
}
Ok(())
}

pub fn check_tool_executable(executable: &str) -> Result<(), String> {
let which = Command::new("which").arg(executable).output();

match which {
Ok(output) => {
if !output.status.success() {
Expand Down
103 changes: 48 additions & 55 deletions tests/runner_test.rs
Original file line number Diff line number Diff line change
@@ -1,84 +1,77 @@
use std::collections::HashMap;
use stop_nagging::runner::{check_tool_executable, disable_nags, run_shell_command};
use stop_nagging::yaml_config::{EcosystemConfig, ToolEntry, YamlToolsConfig};

#[test]
fn test_check_tool_executable_missing() {
let result = check_tool_executable("some_nonexistent_tool_for_test123");
assert!(result.is_err(), "Expected error for missing executable");
}

#[test]
fn test_run_shell_command_success() {
let result = run_shell_command("echo Hello");
assert!(result.is_ok(), "Expected success running 'echo Hello'");
}

#[test]
fn test_disable_nags_empty_config() {
let cfg = YamlToolsConfig {
let config = YamlToolsConfig {
ecosystems: HashMap::new(),
};
let result = disable_nags(&cfg, &[], &[]);
assert!(
result.is_ok(),
"disable_nags with empty config should succeed"
);
stop_nagging::runner::disable_nags(&config, &[], &[]);
}

#[test]
fn test_disable_nags_one_tool_skip_true() {
let mut ecosystems = HashMap::new();
let tool = ToolEntry {
name: "TestTool".to_string(),
executable: "echo".to_string(),
let mut tools = Vec::new();
tools.push(ToolEntry {
name: "test-tool".to_string(),
executable: "test-executable".to_string(),
skip: Some(true),
env: None,
commands: None,
skip: Some(true),
};
let ecosystem = EcosystemConfig { tools: vec![tool] };
ecosystems.insert("test".to_string(), ecosystem);
let cfg = YamlToolsConfig { ecosystems };
});

let mut ecosystems = HashMap::new();
ecosystems.insert("test-ecosystem".to_string(), EcosystemConfig { tools });

let result = disable_nags(&cfg, &[], &[]);
assert!(result.is_ok(), "Skipping a tool should not fail");
let config = YamlToolsConfig { ecosystems };
stop_nagging::runner::disable_nags(&config, &[], &[]);
}

#[test]
fn test_disable_nags_with_ignore_list() {
let mut ecosystems = HashMap::new();
let tool = ToolEntry {
name: "TestTool".to_string(),
executable: "echo".to_string(),
let mut tools = Vec::new();
tools.push(ToolEntry {
name: "test-tool".to_string(),
executable: "test-executable".to_string(),
skip: None,
env: None,
commands: None,
skip: None,
};
let ecosystem = EcosystemConfig { tools: vec![tool] };
ecosystems.insert("test".to_string(), ecosystem);
let cfg = YamlToolsConfig { ecosystems };
});

let mut ecosystems = HashMap::new();
ecosystems.insert("test-ecosystem".to_string(), EcosystemConfig { tools });

let result = disable_nags(&cfg, &[], &["TestTool".to_string()]);
assert!(result.is_ok(), "Ignoring a tool via CLI should not fail");
let config = YamlToolsConfig { ecosystems };
stop_nagging::runner::disable_nags(&config, &[], &["test-tool".to_string()]);
}

#[test]
fn test_disable_nags_with_ecosystem_filter() {
let mut ecosystems = HashMap::new();
let tool = ToolEntry {
name: "TestTool".to_string(),
executable: "echo".to_string(),
let mut tools = Vec::new();
tools.push(ToolEntry {
name: "test-tool".to_string(),
executable: "test-executable".to_string(),
skip: None,
env: None,
commands: None,
skip: None,
};
let ecosystem = EcosystemConfig { tools: vec![tool] };
ecosystems.insert("test".to_string(), ecosystem);
let cfg = YamlToolsConfig { ecosystems };
});

let mut ecosystems = HashMap::new();
ecosystems.insert("test-ecosystem".to_string(), EcosystemConfig { tools });

let result = disable_nags(&cfg, &["other".to_string()], &[]);
assert!(
result.is_ok(),
"Filtering ecosystems via CLI should not fail"
);
let config = YamlToolsConfig { ecosystems };
stop_nagging::runner::disable_nags(&config, &["other-ecosystem".to_string()], &[]);
}

#[test]
fn test_check_tool_executable_missing() {
let result = stop_nagging::runner::check_tool_executable("non-existent-tool-12345");
assert!(result.is_err());
}

#[test]
fn test_run_shell_command_success() {
println!("Hello");
let result = stop_nagging::runner::run_shell_command("echo test");
assert!(result.is_ok());
}

0 comments on commit ce762ca

Please sign in to comment.