mkdir 0.1.0

A cross-platform mkdir command with enhanced features
use anyhow::{Context, Result};
use clap::{Arg, ArgAction, Command};
use colored::*;
use std::fs;
use std::io::{self, Write};
use std::path::Path;

#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;

fn main() -> Result<()> {
    let matches = Command::new("mkdir")
        .version(env!("CARGO_PKG_VERSION"))
        .author(env!("CARGO_PKG_AUTHORS"))
        .about(env!("CARGO_PKG_DESCRIPTION"))
        .arg(
            Arg::new("directories")
                .help("Directories to create")
                .required(true)
                .num_args(1..)
                .value_name("DIRECTORY"),
        )
        .arg(
            Arg::new("parents")
                .short('p')
                .long("parents")
                .help("Create parent directories as needed")
                .action(ArgAction::SetTrue),
        )
        .arg(
            Arg::new("mode")
                .short('m')
                .long("mode")
                .help("Set file mode (permissions) for created directories")
                .value_name("MODE"),
        )
        .arg(
            Arg::new("verbose")
                .short('v')
                .long("verbose")
                .help("Print a message for each created directory")
                .action(ArgAction::SetTrue),
        )
        .arg(
            Arg::new("interactive")
                .short('i')
                .long("interactive")
                .help("Prompt before creating each directory")
                .action(ArgAction::SetTrue),
        )

        .get_matches();

    let directories: Vec<&str> = matches.get_many::<String>("directories")
        .unwrap()
        .map(|s| s.as_str())
        .collect();

    let parents = matches.get_flag("parents");
    let verbose = matches.get_flag("verbose");
    let interactive = matches.get_flag("interactive");
    let mode = matches.get_one::<String>("mode");
    for dir in directories {
        if let Err(e) = create_directory(dir, parents, verbose, interactive, mode) {
            eprintln!("{}: {}", "Error".red().bold(), e);
            std::process::exit(1);
        }
    }

    Ok(())
}

fn create_directory(
    path: &str,
    parents: bool,
    verbose: bool,
    interactive: bool,
    mode: Option<&String>,
) -> Result<()> {
    let path = Path::new(path);

    // Check if directory already exists
    if path.exists() {
        if verbose {
            println!("{}: directory '{}' already exists",
                "Info".yellow().bold(),
                path.display()
            );
        }
        return Ok(());
    }

    // Interactive confirmation
    if interactive {
        print!("Create directory '{}'? [y/N]: ", path.display());
        io::stdout().flush()?;

        let mut input = String::new();
        io::stdin().read_line(&mut input)?;

        if !input.trim().to_lowercase().starts_with('y') {
            if verbose {
                println!("{}: skipped '{}'", "Info".yellow().bold(), path.display());
            }
            return Ok(());
        }
    }

    // Create directory
    if parents {
        fs::create_dir_all(path)
            .with_context(|| format!("Failed to create directory '{}'", path.display()))?;
    } else {
        fs::create_dir(path)
            .with_context(|| format!("Failed to create directory '{}'", path.display()))?;
    }

    // Set permissions if specified
    if let Some(mode_str) = mode {
        #[cfg(unix)]
        {
            set_permissions(path, mode_str)?;
        }
        #[cfg(not(unix))]
        {
            let _ = mode_str; // Suppress unused variable warning
            if verbose {
                println!("{}: permission setting not supported on this platform",
                    "Warning".yellow().bold());
            }
        }
    }

    // Verbose output
    if verbose {
        println!("{}: created directory '{}'",
            "Success".green().bold(),
            path.display()
        );
    }

    Ok(())
}

#[cfg(unix)]
fn set_permissions(path: &Path, mode_str: &str) -> Result<()> {
    let mode = parse_mode(mode_str)
        .with_context(|| format!("Invalid mode: {}", mode_str))?;

    let metadata = fs::metadata(path)?;
    let mut permissions = metadata.permissions();
    permissions.set_mode(mode);
    fs::set_permissions(path, permissions)?;

    Ok(())
}

#[cfg(unix)]
fn parse_mode(mode_str: &str) -> Result<u32> {
    if mode_str.len() == 3 && mode_str.chars().all(|c| c.is_ascii_digit()) {
        // Octal mode like "755"
        u32::from_str_radix(mode_str, 8)
            .with_context(|| "Invalid octal mode")
    } else {
        anyhow::bail!("Mode must be a 3-digit octal number (e.g., 755)")
    }
}