crepuscularity-cli 0.9.13

crepus CLI — scaffolding and builds for Crepuscularity (UNSTABLE; in active development).
use clap::Args;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum BuildMode {
    Debug,
    Dev,
    Release,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum OptimizationLevel {
    None,
    Fast,
    Size,
    Aggressive,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Args)]
pub struct BuildOptionsArgs {
    #[arg(long, conflicts_with_all = ["dev", "release"])]
    pub debug: bool,
    #[arg(long, conflicts_with_all = ["debug", "release"])]
    pub dev: bool,
    #[arg(long, conflicts_with_all = ["debug", "dev"])]
    pub release: bool,
    #[arg(long = "opt-level", value_name = "LEVEL")]
    pub opt_level: Option<OptLevelArg>,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, clap::ValueEnum)]
pub enum OptLevelArg {
    None,
    Fast,
    Size,
    Aggressive,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct BuildOptions {
    pub(crate) mode: BuildMode,
    pub(crate) optimization: OptimizationLevel,
}

impl BuildOptionsArgs {
    pub(crate) fn into_options(self) -> Result<BuildOptions, String> {
        let mode = if self.release {
            BuildMode::Release
        } else if self.dev {
            BuildMode::Dev
        } else {
            BuildMode::Debug
        };
        let optimization = self
            .opt_level
            .map(OptLevelArg::into_level)
            .unwrap_or_else(|| mode.default_optimization());
        Ok(BuildOptions { mode, optimization })
    }

    pub(crate) fn into_options_or_exit(self) -> BuildOptions {
        self.into_options().unwrap_or_else(|e| crate::ui::error(&e))
    }
}

impl OptLevelArg {
    fn into_level(self) -> OptimizationLevel {
        match self {
            Self::None => OptimizationLevel::None,
            Self::Fast => OptimizationLevel::Fast,
            Self::Size => OptimizationLevel::Size,
            Self::Aggressive => OptimizationLevel::Aggressive,
        }
    }
}

impl BuildOptions {
    pub(crate) fn debug() -> Self {
        Self {
            mode: BuildMode::Debug,
            optimization: OptimizationLevel::None,
        }
    }

    pub(crate) fn release(self) -> bool {
        self.mode == BuildMode::Release
    }

    pub(crate) fn cargo_profile(self) -> &'static str {
        if self.release() {
            "release"
        } else {
            "debug"
        }
    }

    pub(crate) fn optimize_artifacts(self) -> bool {
        self.optimization != OptimizationLevel::None
    }
}

impl BuildMode {
    fn default_optimization(self) -> OptimizationLevel {
        match self {
            BuildMode::Debug | BuildMode::Dev => OptimizationLevel::None,
            BuildMode::Release => OptimizationLevel::Fast,
        }
    }
}

impl OptimizationLevel {
    pub(crate) fn wasm_opt_flag(self) -> Option<&'static str> {
        match self {
            Self::None => None,
            Self::Fast => Some("-O2"),
            Self::Size => Some("-Oz"),
            Self::Aggressive => Some("-O3"),
        }
    }
}

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

    #[test]
    fn defaults_to_debug_with_no_post_optimization() {
        let opts = BuildOptionsArgs::default().into_options().expect("parse");
        assert_eq!(opts.mode, BuildMode::Debug);
        assert_eq!(opts.optimization, OptimizationLevel::None);
        assert!(!opts.release());
        assert!(!opts.optimize_artifacts());
    }

    #[test]
    fn release_defaults_to_fast_optimization() {
        let opts = BuildOptionsArgs {
            release: true,
            ..Default::default()
        }
        .into_options()
        .expect("parse");
        assert_eq!(opts.mode, BuildMode::Release);
        assert_eq!(opts.optimization, OptimizationLevel::Fast);
        assert!(opts.release());
        assert!(opts.optimize_artifacts());
    }

    #[test]
    fn explicit_optimization_overrides_mode_default() {
        let opts = BuildOptionsArgs {
            release: true,
            opt_level: Some(OptLevelArg::Size),
            ..Default::default()
        }
        .into_options()
        .expect("parse");
        assert_eq!(opts.mode, BuildMode::Release);
        assert_eq!(opts.optimization, OptimizationLevel::Size);
    }
}