Skip to content

Commit

Permalink
Merge pull request #2 from bodo-run/yaml-is-optional
Browse files Browse the repository at this point in the history
feat: use embedded tools.yaml by default and make custom yaml optional
  • Loading branch information
mohsen1 authored Jan 17, 2025
2 parents 84f5ad7 + 8f381d9 commit 070f5e3
Show file tree
Hide file tree
Showing 9 changed files with 330 additions and 696 deletions.
35 changes: 29 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# stop-nagging

stop-nagging is a Rust-based CLI tool that silences or disables upgrade/advertising nags and other unnecessary warnings from various CLI tools and development tools.
`stop-nagging` is a Rust-based CLI tool that silences or disables upgrade/advertising nags and other unnecessary warnings from various CLI tools and development tools. It also disables telemetry and other tracking mechanisms.

It uses a YAML file (`tools.yaml`) to list each tool's name, environment variables, and commands to run, making it easy for new contributors to update the logic without writing Rust code.

## Features
Expand Down Expand Up @@ -49,11 +50,33 @@ Then add `~/.local/bin` to your PATH if not already.
stop-nagging [options]
```

- Without arguments: Runs with default settings, reading `tools.yaml` in the project directory (or local directory)
- Example:
```bash
stop-nagging
```
### Options

- `-y, --yaml <FILE>`: Optional path to a custom YAML configuration file
- If not provided, the default built-in configuration will be used
- If the custom file fails to load, falls back to the default configuration
- See [`tools.yaml`](tools.yaml) for the default configuration
- `--ignore-tools <TOOLS>`: Comma-separated list of tool names to ignore (e.g., `npm,yarn`)
- `--ecosystems <ECOSYSTEMS>`: Comma-separated list of ecosystems to run (leave empty to run all)

### Examples

```bash
# Run with default built-in configuration
stop-nagging

# Use a custom YAML file
stop-nagging --yaml custom-tools.yaml

# Ignore specific tools (using default configuration)
stop-nagging --ignore-tools npm,yarn,pnpm

# Only run for specific ecosystems (using default configuration)
stop-nagging --ecosystems nodejs,python

# Combine multiple options with custom configuration
stop-nagging --yaml custom.yaml --ignore-tools npm --ecosystems nodejs
```

## Contributing

Expand Down
12 changes: 7 additions & 5 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
use clap::{ArgAction, Parser};
use clap::Parser;

#[derive(Parser, Debug)]
#[command(name = "stop-nagging")]
#[command(version = "0.1.0")]
#[command(about = "Silence or disable nags from various CLI tools using tools.yaml")]
#[command(
about = "A Rust-based CLI tool that silences or disables upgrade/advertising nags and other unnecessary warnings from various CLI tools and development tools.\n\nIt uses a YAML file (tools.yaml) to list each tool's name, environment variables, and commands to run, making it easy for new contributors to update the logic without writing Rust code."
)]
pub struct StopNaggingArgs {
/// Path to the YAML configuration file
#[arg(short, long, action = ArgAction::Set, default_value = "tools.yaml", value_name = "FILE")]
pub yaml: String,
/// Optional path to a custom YAML configuration file. If not provided, the default configuration will be used.
#[arg(short, long, value_name = "FILE")]
pub yaml: Option<String>,

/// A comma-separated list of tool names to ignore
#[arg(long = "ignore-tools", num_args=0.., value_delimiter=',', default_value = "")]
Expand Down
13 changes: 6 additions & 7 deletions src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
use thiserror::Error;

#[derive(Error, Debug)]
#[allow(clippy::enum_variant_names)]
pub enum StopNaggingError {
#[error("I/O Error: {0}")]
IoError(#[from] std::io::Error),
#[error("YAML error: {0}")]
Yaml(String),

#[error("YAML Parsing Error: {0}")]
YamlError(#[from] serde_yaml::Error),
#[error("File error: {0}")]
File(String),

#[error("Failed to run command: {0}")]
CommandError(String),
#[error("Command error: {0}")]
Command(String),
}
20 changes: 14 additions & 6 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,22 @@ use clap::Parser;

fn main() {
let args = StopNaggingArgs::parse();
let yaml_path = &args.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(0);
let yaml_config = if let Some(yaml_path) = args.yaml {
// User provided a custom YAML file
match YamlToolsConfig::from_yaml_file(&yaml_path) {
Ok(config) => config,
Err(e) => {
eprintln!("Failed to read custom YAML config '{}': {}", yaml_path, e);
eprintln!("Falling back to default configuration");
YamlToolsConfig::from_yaml_str(include_str!("../tools.yaml"))
.expect("Default tools.yaml should be valid")
}
}
} else {
// Use the embedded tools.yaml
YamlToolsConfig::from_yaml_str(include_str!("../tools.yaml"))
.expect("Default tools.yaml should be valid")
};

disable_nags(&yaml_config, &args.ecosystems, &args.ignore_tools);
Expand Down
73 changes: 47 additions & 26 deletions src/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ use crate::errors::StopNaggingError;
use crate::yaml_config::YamlToolsConfig;
use std::{collections::HashSet, env, process::Command};

struct EnvVarBackup {
key: String,
original_value: Option<String>,
}

pub fn disable_nags(
yaml_config: &YamlToolsConfig,
selected_ecosystems: &[String],
Expand All @@ -15,6 +20,8 @@ pub fn disable_nags(

let run_all_ecosystems = selected_ecosystems.is_empty();

let mut env_backups: Vec<EnvVarBackup> = Vec::new();

for (ecosystem_name, ecosystem_config) in &yaml_config.ecosystems {
let ecosystem_name_lower = ecosystem_name.to_lowercase();

Expand Down Expand Up @@ -45,18 +52,17 @@ pub fn disable_nags(

if let Some(env_vars) = &tool.env {
for (key, val) in env_vars {
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
);
}
let original = env::var(key).ok();
env_backups.push(EnvVarBackup {
key: key.clone(),
original_value: original,
});

env::set_var(key, val);
println!(
"Set {}={} for tool {} in {}",
key, val, tool.name, ecosystem_name
);
}
}

Expand All @@ -75,32 +81,47 @@ pub fn disable_nags(
}
}
}

// Restore environment variables
for backup in env_backups {
match backup.original_value {
Some(val) => env::set_var(&backup.key, val),
None => env::remove_var(&backup.key),
}
}
}

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() {
return Err(format!("Executable '{}' not found in PATH", executable));
}
}
Err(e) => {
return Err(format!("Error running 'which {}': {}", executable, e));
}
#[cfg(windows)]
let (cmd, arg) = ("where", executable);
#[cfg(not(windows))]
let (cmd, arg) = ("which", executable);

let output = Command::new(cmd)
.arg(arg)
.output()
.map_err(|e| format!("Error running '{}': {}", cmd, e))?;

if !output.status.success() {
return Err(format!("Executable '{}' not found in PATH", executable));
}
Ok(())
}

pub fn run_shell_command(cmd_str: &str) -> Result<(), StopNaggingError> {
let status = Command::new("sh")
.arg("-c")
#[cfg(windows)]
let (shell, shell_arg) = ("cmd", "/C");
#[cfg(not(windows))]
let (shell, shell_arg) = ("sh", "-c");

let status = Command::new(shell)
.arg(shell_arg)
.arg(cmd_str)
.status()
.map_err(|e| StopNaggingError::CommandError(e.to_string()))?;
.map_err(|e| StopNaggingError::Command(e.to_string()))?;

if !status.success() {
return Err(StopNaggingError::CommandError(format!(
return Err(StopNaggingError::Command(format!(
"Command '{}' exited with status: {}",
cmd_str, status
)));
Expand Down
10 changes: 7 additions & 3 deletions src/yaml_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,13 @@ pub struct ToolEntry {
}

impl YamlToolsConfig {
pub fn from_yaml_str(yaml_str: &str) -> Result<Self, StopNaggingError> {
serde_yaml::from_str(yaml_str).map_err(|e| StopNaggingError::Yaml(e.to_string()))
}

pub fn from_yaml_file(path: &str) -> Result<Self, StopNaggingError> {
let content = fs::read_to_string(path)?;
let config: Self = serde_yaml::from_str(&content)?;
Ok(config)
let contents = fs::read_to_string(path)
.map_err(|e| StopNaggingError::File(format!("Failed to read file: {}", e)))?;
Self::from_yaml_str(&contents)
}
}
2 changes: 1 addition & 1 deletion tests/integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ fn test_stop_nagging_cli_help() {
cmd.arg("--help");

cmd.assert().success().stdout(predicate::str::contains(
"Silence or disable nags from various CLI tools",
"A Rust-based CLI tool that silences or disables upgrade/advertising nags",
));
}

Expand Down
39 changes: 14 additions & 25 deletions tests/yaml_config_test.rs
Original file line number Diff line number Diff line change
@@ -1,49 +1,38 @@
use std::fs::File;
use std::io::Write;
use stop_nagging::errors::StopNaggingError;
use stop_nagging::yaml_config::YamlToolsConfig;
use tempfile::tempdir;

#[test]
fn test_yaml_config_parsing_valid() -> Result<(), StopNaggingError> {
fn test_yaml_config_parsing_valid() -> Result<(), Box<dyn std::error::Error>> {
let dir = tempdir()?;
let file_path = dir.path().join("test.yaml");

let yaml_str = r#"
ecosystems:
node:
test:
tools:
- name: "dummy"
executable: "dummy_exe"
- name: "test-tool"
executable: "test-executable"
env:
KEY: "VALUE"
TEST_VAR: "test-value"
commands:
- "echo hello"
- "test-command"
skip: false
"#;

let dir = tempdir().unwrap();
let file_path = dir.path().join("valid_tools.yaml");
let mut file = File::create(&file_path)?;
file.write_all(yaml_str.as_bytes())?;

let config = YamlToolsConfig::from_yaml_file(file_path.to_str().unwrap())?;
assert_eq!(config.ecosystems.len(), 1);
let ecosystem = config.ecosystems.get("node").unwrap();
assert_eq!(ecosystem.tools.len(), 1);
let tool = &ecosystem.tools[0];
assert_eq!(tool.name, "dummy");
assert_eq!(tool.executable, "dummy_exe");
assert_eq!(tool.env.as_ref().unwrap().get("KEY").unwrap(), "VALUE");
assert_eq!(tool.commands.as_ref().unwrap()[0], "echo hello");
assert!(config.ecosystems.contains_key("test"));

Ok(())
}

#[test]
fn test_yaml_config_parsing_invalid() {
let yaml_str = r#" invalid yaml: [ "#;
let dir = tempdir().unwrap();
let file_path = dir.path().join("invalid_tools.yaml");
let mut file = File::create(&file_path).unwrap();
file.write_all(yaml_str.as_bytes()).unwrap();

let result = YamlToolsConfig::from_yaml_file(file_path.to_str().unwrap());
assert!(result.is_err(), "Should fail to parse invalid YAML");
let invalid_yaml = "invalid: - yaml: content";
let result = YamlToolsConfig::from_yaml_str(invalid_yaml);
assert!(result.is_err());
}
Loading

0 comments on commit 070f5e3

Please sign in to comment.