use std::path::PathBuf; use std::num::NonZeroUsize; use std::{fmt,error}; use once_cell::sync::OnceCell; static GLOBAL: OnceCell = OnceCell::new(); /// Get the global config instance, if it has been set. #[inline] pub fn try_get_global() -> Option<&'static Config> { GLOBAL.get() } /// Get the global config instance. /// # Panics /// If one has not been set yet. pub fn get_global() -> &'static Config { try_get_global().expect("Tried to access global config when it had not been initialised") } /// Try to set the global config instance, if it hasn't been already. #[inline] pub fn try_set_global(cfg: Config) -> Result<(), Config> { GLOBAL.set(cfg) } /// Set the global config instance. /// # Panics /// If one has already been set. pub fn set_global(cfg: Config) { try_set_global(cfg).expect("Tried to set global config more than once") } #[derive(Debug, Clone, PartialEq, Eq, Copy, PartialOrd, Ord)] #[repr(u32)] pub enum OutputLevel { Silent = 0, Quiet = 1, Noisy = 2, Verbose = 3, } impl Default for OutputLevel { #[inline] fn default() -> Self { Self::Noisy } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum Recursion { None, Limited(NonZeroUsize), Unlimited, } impl Default for Recursion { #[inline] fn default() -> Self { Self::None } } impl Recursion { /// Can we run at this depth? pub fn can_run(&self, depth: usize) -> bool { debug_assert!(depth > 0, "Depth of 0 is invalid"); match self { Self::None => depth == 1, Self::Limited(limit) => depth <= limit.get(), Self::Unlimited => true } } } impl From for Recursion { fn from(from: usize) -> Self { match from { 0 => Self::Unlimited, 1 => Self::None, x => Self::Limited(unsafe {NonZeroUsize::new_unchecked(x)}), } } } #[cfg(feature="inspect")] #[derive(Debug, Clone, PartialEq, Eq)] #[non_exhaustive] pub enum OutputSerialisationMode { Stdout, File(PathBuf), RawFile(PathBuf), RawStdout, #[cfg(feature="prealloc")] PreallocFile(PathBuf), } #[cfg(feature="inspect")] impl OutputSerialisationMode { /// Should this serialisation mode be compressed? #[inline] pub fn should_compress(&self) -> bool { match self { Self::File(_) | Self::Stdout => true, _ => false, } } } /// What to do with the graph afterwards? #[derive(Debug, Clone, PartialEq, Eq)] pub struct Inspection { pub treemap: Option<(u64, u64)>, //w and h } impl Default for Inspection { #[inline] fn default() -> Self { Self { treemap: None } } } /// Configuration for this run #[derive(Debug, Clone, PartialEq, Eq)] pub struct Config { pub paths: Vec, pub recursive: Recursion, pub max_tasks: Option, pub output_level: OutputLevel, #[cfg(feature="inspect")] pub serialise_output: Option, pub inspection: Inspection, } impl Config { /// Try to make this the global config instance, if one does not already exist. #[inline] pub fn try_make_global(self) -> Result<&'static Self, Self> { try_set_global(self).map(|_| get_global()) } /// Make this the global config instance. /// # Panics /// If a global config instance has already been set. #[inline] pub fn make_global(self) -> &'static Self { set_global(self); get_global() //GLOBAL.get_or_init(move || self) // This isn't okay to do here. As it would silently fail if there was already an instance, when we want it to panic in that case. } /// Are we expected to dump data to `stdout`? #[inline] pub fn is_using_stdout(&self) -> bool { #[cfg(feature="inspect")] { return match self.serialise_output { Some(OutputSerialisationMode::Stdout) | Some(OutputSerialisationMode::RawStdout) => true, _ => false, } } #[cfg(not(feature="inspect"))] { false } } } /// The default `max_tasks` #[inline(always)] pub fn max_tasks_cpus() -> Option { lazy_static! { static ref CPUS: usize = num_cpus::get(); } NonZeroUsize::new(*CPUS) } impl Default for Config { #[inline] fn default() -> Self { Self { paths: Vec::new(), recursive: Default::default(), max_tasks: None, //max_tasks_cpus(), output_level: Default::default(), #[cfg(feature="inspect")] serialise_output: None, inspection: Default::default(), } } } impl Config { /// Validate this configuration instance. pub fn validate(self) -> Result { if self.paths.len() < 1 { return Err(InvalidConfigError::NoPaths); } let paths: Result, _> = self.paths.into_iter() .map(|path| if !path.exists() { Err(InvalidConfigError::PathNotFound(path)) } else { Ok(path) }) .collect(); Ok(Self{ paths: paths?, ..self }) } } /// Error type for an invalid instance of `Config`. #[derive(Debug)] #[non_exhaustive] pub enum InvalidConfigError { /// No paths were given. NoPaths, /// Non-existant path was given. PathNotFound(PathBuf), /// Unknown error Other, } impl error::Error for InvalidConfigError{} impl fmt::Display for InvalidConfigError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::NoPaths => write!(f, "No input paths were given. Cannot do anything"), Self::PathNotFound(path) => write!(f, "Root path {:?} not found", path), _ => write!(f, "Unknown error"), } } } /// Print an error line in accordance with `Config`'s output directives. #[macro_export] macro_rules! cfg_eprintln { ($cfg:expr, $fmt:literal $($tt:tt)*) => { { if $cfg.output_level > $crate::config::OutputLevel::Silent { eprintln!($fmt $($tt)*); } } }; ($level:ident; $cfg:expr, $fmt:literal $($tt:tt)*) => { { if $cfg.output_level >= $crate::config::OutputLevel::$level { eprintln!($fmt $($tt)*); } } } } /// Print a line in accordance with `Config`'s output directives. #[macro_export] macro_rules! cfg_println { ($cfg:expr, $fmt:literal $($tt:tt)*) => { { let cfg = &$cfg; if cfg.output_level > $crate::config::OutputLevel::Quiet { if cfg.is_using_stdout() { eprintln!($fmt $($tt)*); } else { println!($fmt $($tt)*); } } } }; ($level:ident; $cfg:expr, $fmt:literal $($tt:tt)*) => { { let cfg = &$cfg; if cfg.output_level >= $crate::config::OutputLevel::$level { if cfg.is_using_stdout() { eprintln!($fmt $($tt)*); } else { println!($fmt $($tt)*); } } } }; }