autorun 0.1.0

A simple tool to manage autorun entries on Windows
use clap::{Args, Parser, Subcommand};

use autorun::{StartupEntry, StartupScope};

// ---------------------------------------------------------------------------
// Top-level CLI
// ---------------------------------------------------------------------------

/// Manage Windows startup entries via the registry Run key.
#[derive(Parser, Debug)]
#[command(name = "autorun", version, about, long_about = None)]
pub struct Cli {
    #[command(subcommand)]
    pub command: Command,
}

#[derive(Subcommand, Debug)]
pub enum Command {
    /// List startup entries.
    List(ListArgs),
    /// Add a new startup entry.
    Add(AddArgs),
    /// Remove an existing startup entry.
    Remove(RemoveArgs),
    /// Check whether a startup entry exists.
    Check(CheckArgs),
}

// ---------------------------------------------------------------------------
// Shared scope argument
// ---------------------------------------------------------------------------

/// Argument for choosing the registry scope.
#[derive(Args, Debug, Clone)]
pub struct ScopeArg {
    /// Target the local-machine scope (HKLM) instead of current user.
    /// Requires administrator privileges for write operations.
    #[arg(short, long)]
    pub system: bool,

    /// List entries from all scopes (HKCU + HKLM).
    /// Mutually exclusive with --system.
    #[arg(short, long, conflicts_with = "system")]
    pub all: bool,
}

impl ScopeArg {
    /// Resolve the scope from the CLI flags.
    pub fn to_scope(&self) -> Option<StartupScope> {
        if self.system {
            Some(StartupScope::LocalMachine)
        } else {
            Some(StartupScope::CurrentUser)
        }
    }
}

// ---------------------------------------------------------------------------
// Subcommand argument groups
// ---------------------------------------------------------------------------

/// Arguments for `autorun list`.
#[derive(Args, Debug)]
pub struct ListArgs {
    #[command(flatten)]
    pub scope: ScopeArg,
}

/// Arguments for `autorun add`.
#[derive(Args, Debug)]
pub struct AddArgs {
    /// Name of the startup entry (used as the registry value name).
    pub name: String,

    /// Command or program path to execute at startup.
    pub command: String,

    /// Target the local-machine scope (HKLM). Requires administrator privileges.
    #[arg(short, long)]
    pub system: bool,
}

/// Arguments for `autorun remove`.
#[derive(Args, Debug)]
pub struct RemoveArgs {
    /// Name of the startup entry to remove.
    pub name: String,

    /// Target the local-machine scope (HKLM). Requires administrator privileges.
    #[arg(short, long)]
    pub system: bool,
}

/// Arguments for `autorun check`.
#[derive(Args, Debug)]
pub struct CheckArgs {
    /// Name of the startup entry to check.
    pub name: String,

    /// Check in the local-machine scope (HKLM) instead of current user.
    #[arg(short, long)]
    pub system: bool,
}

// ---------------------------------------------------------------------------
// Run dispatch
// ---------------------------------------------------------------------------

/// Execute the parsed command against the autorun library.
pub fn run(cli: Cli) -> Result<(), String> {
    match cli.command {
        Command::List(args) => cmd_list(args),
        Command::Add(args) => cmd_add(args),
        Command::Remove(args) => cmd_remove(args),
        Command::Check(args) => cmd_check(args),
    }
}

// ---------------------------------------------------------------------------
// Command implementations
// ---------------------------------------------------------------------------

fn cmd_list(args: ListArgs) -> Result<(), String> {
    if args.scope.all {
        let entries = autorun::list_all()?;
        print_entries(&entries);
    } else {
        let scope = args.scope.to_scope().unwrap();
        let entries = autorun::list(scope)?;
        println!("Startup entries [{}] ({} total):", scope, entries.len());
        print_entries(&entries);
    }
    Ok(())
}

fn cmd_add(args: AddArgs) -> Result<(), String> {
    let scope = if args.system {
        StartupScope::LocalMachine
    } else {
        StartupScope::CurrentUser
    };
    autorun::add(&args.name, &args.command, scope)?;
    println!("Added startup entry '{}' [{}]", args.name, scope);
    Ok(())
}

fn cmd_remove(args: RemoveArgs) -> Result<(), String> {
    let scope = if args.system {
        StartupScope::LocalMachine
    } else {
        StartupScope::CurrentUser
    };
    autorun::remove(&args.name, scope)?;
    println!("Removed startup entry '{}' [{}]", args.name, scope);
    Ok(())
}

fn cmd_check(args: CheckArgs) -> Result<(), String> {
    let scope = if args.system {
        StartupScope::LocalMachine
    } else {
        StartupScope::CurrentUser
    };
    let found = autorun::exists(&args.name, scope)?;
    if found {
        println!("Startup entry '{}' exists [{}]", args.name, scope);
    } else {
        println!("Startup entry '{}' does not exist [{}]", args.name, scope);
    }
    Ok(())
}

// ---------------------------------------------------------------------------
// Output formatting
// ---------------------------------------------------------------------------

/// Pretty-print a list of startup entries.
fn print_entries(entries: &[StartupEntry]) {
    if entries.is_empty() {
        println!("(none)");
        return;
    }
    let max_name = entries.iter().map(|e| e.name.len()).max().unwrap_or(0);
    for entry in entries {
        println!(
            "  [{scope}] {name:<width$}  {cmd}",
            scope = entry.scope,
            name = entry.name,
            width = max_name,
            cmd = entry.command,
        );
    }
}