tnk 0.1.49

Zero-trust sandbox for local inference and secure AI coding agent runtimes.
use std::sync::atomic::{AtomicU8, Ordering};

use clap::error::ErrorKind;

const VERBOSITY_QUIET: u8 = 0;
const VERBOSITY_NORMAL: u8 = 1;
const VERBOSITY_VERBOSE: u8 = 2;

static GLOBAL_VERBOSITY: AtomicU8 = AtomicU8::new(VERBOSITY_NORMAL);

pub fn set_quiet() {
    GLOBAL_VERBOSITY.store(VERBOSITY_QUIET, Ordering::Relaxed);
}

pub fn set_verbose() {
    GLOBAL_VERBOSITY.store(VERBOSITY_VERBOSE, Ordering::Relaxed);
}

pub fn is_quiet() -> bool {
    GLOBAL_VERBOSITY.load(Ordering::Relaxed) == VERBOSITY_QUIET
}

pub fn is_verbose() -> bool {
    GLOBAL_VERBOSITY.load(Ordering::Relaxed) == VERBOSITY_VERBOSE
}

pub fn log_info(message: &str) {
    if GLOBAL_VERBOSITY.load(Ordering::Relaxed) >= VERBOSITY_NORMAL {
        eprintln!("info: {}", message);
    }
}

pub fn log_warn(message: &str) {
    if GLOBAL_VERBOSITY.load(Ordering::Relaxed) >= VERBOSITY_NORMAL {
        eprintln!("warning: {}", message);
    }
}

#[derive(Copy, Clone, PartialEq)]
pub enum ExitCode {
    Usage = 64,
    NotFound = 66,
    PermissionDenied = 77,
    Error = 1,
}

impl ExitCode {
    pub fn as_i32(&self) -> i32 {
        *self as i32
    }
}

pub fn exit_with(code: ExitCode, msg: &str) -> ! {
    eprintln!("error: {msg}");
    std::process::exit(code.as_i32());
}

pub fn exit_code_for_error(err: &color_eyre::Report) -> ExitCode {
    let msg = err.to_string().to_lowercase();
    if msg.contains("outside the configured workspace root") {
        ExitCode::Usage
    } else if msg.contains("permission denied") {
        ExitCode::PermissionDenied
    } else {
        ExitCode::Error
    }
}

pub fn clap_exit_code_for_kind(kind: &ErrorKind) -> i32 {
    match kind {
        ErrorKind::DisplayHelp
        | ErrorKind::DisplayVersion
        | ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand => 0,
        ErrorKind::UnknownArgument
        | ErrorKind::InvalidSubcommand
        | ErrorKind::MissingRequiredArgument
        | ErrorKind::TooManyValues
        | ErrorKind::TooFewValues
        | ErrorKind::WrongNumberOfValues
        | ErrorKind::InvalidValue
        | ErrorKind::ValueValidation
        | ErrorKind::ArgumentConflict
        | ErrorKind::NoEquals
        | ErrorKind::MissingSubcommand
        | ErrorKind::InvalidUtf8 => 64,
        _ => 1,
    }
}

#[cfg(test)]
mod clap_exit_tests {
    use super::*;

    #[test]
    fn help_exits_zero() {
        assert_eq!(clap_exit_code_for_kind(&ErrorKind::DisplayHelp), 0);
    }

    #[test]
    fn version_exits_zero() {
        assert_eq!(clap_exit_code_for_kind(&ErrorKind::DisplayVersion), 0);
    }

    #[test]
    fn unknown_argument_exits_sixty_four() {
        assert_eq!(clap_exit_code_for_kind(&ErrorKind::UnknownArgument), 64);
    }

    #[test]
    fn invalid_subcommand_exits_sixty_four() {
        assert_eq!(clap_exit_code_for_kind(&ErrorKind::InvalidSubcommand), 64);
    }

    #[test]
    fn missing_required_exits_sixty_four() {
        assert_eq!(
            clap_exit_code_for_kind(&ErrorKind::MissingRequiredArgument),
            64
        );
    }
}