facti 0.2.3

Factorio mod tool
use indoc::indoc;

#[derive(clap::Args, Debug, Clone)]
pub struct Verbosity<L: LogLevel = ErrorLevel> {
    #[arg(
        long,
        short = 'v',
        action = clap::ArgAction::Count,
        global = true,
        help = L::verbose_help(),
        long_help = L::verbose_long_help()
    )]
    verbose: u8,

    #[arg(
        long,
        short = 'q',
        action = clap::ArgAction::Count,
        global = true,
        help = L::quiet_help(),
        long_help = L::quiet_long_help(),
        conflicts_with = "verbose"
    )]
    quiet: u8,

    #[arg(skip)]
    _phantom: std::marker::PhantomData<L>,
}

impl<L: LogLevel> Verbosity<L> {
    #[allow(dead_code)]
    pub fn new(verbose: u8, quiet: u8) -> Self {
        Verbosity {
            verbose,
            quiet,
            _phantom: std::marker::PhantomData,
        }
    }

    #[allow(dead_code)]
    pub fn log_level(&self) -> Option<tracing::Level> {
        level_enum(self.verbosity())
    }

    pub fn log_level_filter(&self) -> tracing::level_filters::LevelFilter {
        level_enum(self.verbosity())
            .map(|l| l.into())
            .unwrap_or(tracing::level_filters::LevelFilter::OFF)
    }

    pub fn is_given(&self) -> bool {
        self.verbose > 0 || self.quiet > 0
    }

    #[allow(dead_code)]
    pub fn is_silent(&self) -> bool {
        self.log_level().is_none()
    }

    fn verbosity(&self) -> i8 {
        level_value(L::default()) - (self.quiet as i8) + (self.verbose as i8)
    }
}

fn level_value(level: Option<tracing::Level>) -> i8 {
    match level {
        None => -1,
        Some(tracing::Level::ERROR) => 0,
        Some(tracing::Level::WARN) => 1,
        Some(tracing::Level::INFO) => 2,
        Some(tracing::Level::DEBUG) => 3,
        Some(tracing::Level::TRACE) => 4,
    }
}

fn level_enum(verbosity: i8) -> Option<tracing::Level> {
    match verbosity {
        i8::MIN..=-1 => None,
        0 => Some(tracing::Level::ERROR),
        1 => Some(tracing::Level::WARN),
        2 => Some(tracing::Level::INFO),
        3 => Some(tracing::Level::DEBUG),
        4..=i8::MAX => Some(tracing::Level::TRACE),
    }
}

pub trait LogLevel {
    fn default() -> Option<tracing::Level>;

    fn verbose_help() -> Option<&'static str> {
        Some("More output per occurrence")
    }

    fn verbose_long_help() -> Option<&'static str> {
        Some(indoc! {"
            Specifies the amount of verbosity desired.

            The more times this option is specified, the more verbose the output
            will become.

            Specifically, they relate as follows:
                -v       Show warnings
                -vv      Show info
                -vvv     Show debug
                -vvvv    Show trace

            Specifying this option more than four times has no further effect.
        "})
    }

    fn quiet_help() -> Option<&'static str> {
        Some("Silences output")
    }

    fn quiet_long_help() -> Option<&'static str> {
        Some(indoc! {"
            Silences all (logging) output.

            Specifying this option more than once has no effect.
        "})
    }
}

#[derive(Default, Copy, Clone, Debug)]
pub struct ErrorLevel;

impl LogLevel for ErrorLevel {
    fn default() -> Option<tracing::Level> {
        Some(tracing::Level::ERROR)
    }
}

#[derive(Default, Copy, Clone, Debug)]
pub struct WarnLevel;

impl LogLevel for WarnLevel {
    fn default() -> Option<tracing::Level> {
        Some(tracing::Level::WARN)
    }
}

#[derive(Default, Copy, Clone, Debug)]
pub struct InfoLevel;

impl LogLevel for InfoLevel {
    fn default() -> Option<tracing::Level> {
        Some(tracing::Level::INFO)
    }
}

#[derive(Default, Copy, Clone, Debug)]
pub struct DebugLevel;

impl LogLevel for DebugLevel {
    fn default() -> Option<tracing::Level> {
        Some(tracing::Level::DEBUG)
    }
}

#[derive(Default, Copy, Clone, Debug)]
pub struct TraceLevel;

impl LogLevel for TraceLevel {
    fn default() -> Option<tracing::Level> {
        Some(tracing::Level::TRACE)
    }
}