use crate::core::{config_tier::ConfigTier, pattern::FilePattern};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConfigFileRule {
pub pattern: FilePattern,
pub tiers: TierSearchMode,
pub required: bool,
}
impl ConfigFileRule {
#[must_use]
pub fn toml(base: impl Into<String>) -> Self {
Self {
pattern: FilePattern::extensions(base.into(), &["toml"]),
tiers: TierSearchMode::default(),
required: false,
}
}
#[must_use]
pub fn extensions(base: impl Into<String>, extensions: &[&str]) -> Self {
Self {
pattern: FilePattern::extensions(base.into(), extensions),
tiers: TierSearchMode::default(),
required: false,
}
}
#[must_use]
pub fn exact(name: impl Into<String>) -> Self {
Self {
pattern: FilePattern::exact(name.into()),
tiers: TierSearchMode::default(),
required: false,
}
}
#[must_use]
pub fn glob(pattern: impl Into<String>) -> Self {
Self {
pattern: FilePattern::glob(pattern.into()),
tiers: TierSearchMode::default(),
required: false,
}
}
#[must_use]
pub const fn tiers(mut self, tiers: TierSearchMode) -> Self {
self.tiers = tiers;
self
}
#[must_use]
pub const fn required(mut self, required: bool) -> Self {
self.required = required;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum TierSearchMode {
UserOnly,
UserAndLocal,
#[default]
All,
FromTier(ConfigTier),
FromTierDownward(ConfigTier),
Custom {
user: bool,
local: bool,
system: bool,
},
}
impl TierSearchMode {
#[must_use]
pub const fn includes_user(&self) -> bool {
matches!(
self,
Self::UserOnly | Self::UserAndLocal | Self::All | Self::FromTierDownward(_)
) || matches!(self, Self::Custom { user: true, .. })
|| matches!(self, Self::FromTier(ConfigTier::User))
}
#[must_use]
pub const fn includes_local(&self) -> bool {
matches!(self, Self::All | Self::FromTier(ConfigTier::Local))
|| matches!(self, Self::Custom { local: true, .. })
|| matches!(self, Self::FromTier(ConfigTier::System | ConfigTier::User))
}
#[must_use]
pub const fn includes_system(&self) -> bool {
matches!(self, Self::All)
|| matches!(self, Self::Custom { system: true, .. })
|| matches!(self, Self::FromTier(ConfigTier::System))
}
#[must_use]
pub const fn tiers(&self) -> TierIterator {
TierIterator::new(*self)
}
#[must_use]
pub const fn default_tier(&self) -> ConfigTier {
match self {
Self::FromTier(tier) => *tier,
_ => ConfigTier::User,
}
}
}
#[derive(Debug, Clone)]
pub struct TierIterator {
mode: TierSearchMode,
state: u8,
}
impl TierIterator {
#[must_use]
pub const fn new(mode: TierSearchMode) -> Self {
Self { mode, state: 0 }
}
}
impl Iterator for TierIterator {
type Item = ConfigTier;
fn next(&mut self) -> Option<Self::Item> {
let mode = self.mode;
let state = self.state;
self.state = state.wrapping_add(1);
match mode {
TierSearchMode::All => match state {
0 => Some(ConfigTier::User),
1 => Some(ConfigTier::Local),
2 => Some(ConfigTier::System),
_ => None,
},
TierSearchMode::UserOnly => match state {
0 => Some(ConfigTier::User),
_ => None,
},
TierSearchMode::UserAndLocal => match state {
0 => Some(ConfigTier::User),
1 => Some(ConfigTier::Local),
_ => None,
},
TierSearchMode::FromTier(start) => match state {
0 => Some(start),
1 if start < ConfigTier::User => Some(ConfigTier::User),
1 if start < ConfigTier::Local => Some(ConfigTier::Local),
_ => None,
},
TierSearchMode::FromTierDownward(start) => match state {
0 => Some(start),
1 if start > ConfigTier::System => Some(ConfigTier::System),
1 if start > ConfigTier::Local => Some(ConfigTier::Local),
_ => None,
},
TierSearchMode::Custom {
user,
local,
system,
} => match state {
0 if user => Some(ConfigTier::User),
1 if local => Some(ConfigTier::Local),
2 if system => Some(ConfigTier::System),
_ => None,
},
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FragmentRule {
pub dir_name: String,
pub pattern: FilePattern,
pub tiers: TierSearchMode,
}
impl FragmentRule {
#[must_use]
pub fn new(dir_name: impl Into<String>, pattern: impl Into<String>) -> Self {
Self {
dir_name: dir_name.into(),
pattern: FilePattern::glob(pattern),
tiers: TierSearchMode::default(),
}
}
#[must_use]
pub const fn tiers(mut self, tiers: TierSearchMode) -> Self {
self.tiers = tiers;
self
}
}
#[derive(Debug, Clone, Default)]
pub struct ConfigRuleSet {
pub main_files: Vec<ConfigFileRule>,
pub fragments: Option<FragmentRule>,
}
impl ConfigRuleSet {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn builder() -> ConfigRuleSetBuilder {
ConfigRuleSetBuilder::new()
}
pub fn add_main_file(&mut self, rule: ConfigFileRule) {
self.main_files.push(rule);
}
pub fn set_fragments(&mut self, fragments: FragmentRule) {
self.fragments = Some(fragments);
}
#[must_use]
pub fn main_files(&self) -> &[ConfigFileRule] {
&self.main_files
}
#[must_use]
pub const fn fragments(&self) -> Option<&FragmentRule> {
self.fragments.as_ref()
}
}
#[derive(Debug, Clone, Default)]
pub struct ConfigRuleSetBuilder {
rules: ConfigRuleSet,
}
impl ConfigRuleSetBuilder {
#[must_use]
pub fn new() -> Self {
Self {
rules: ConfigRuleSet::new(),
}
}
#[must_use]
pub fn main_file(mut self, rule: ConfigFileRule) -> Self {
self.rules.add_main_file(rule);
self
}
#[must_use]
pub fn fragments(mut self, fragments: FragmentRule) -> Self {
self.rules.set_fragments(fragments);
self
}
#[must_use]
pub fn build(self) -> ConfigRuleSet {
self.rules
}
}
#[derive(Debug, Clone)]
pub struct RuleBasedDiscovery {
pub rules: ConfigRuleSet,
pub main_files: Vec<RuleMatchResult>,
pub fragments: Vec<RuleMatchResult>,
}
impl RuleBasedDiscovery {
#[must_use]
pub fn is_empty(&self) -> bool {
self.main_files.iter().all(|r| r.matches.is_empty())
&& self.fragments.iter().all(|r| r.matches.is_empty())
}
#[must_use]
pub fn file_count(&self) -> usize {
let main_count: usize = self.main_files.iter().map(|r| r.matches.len()).sum();
let fragment_count: usize = self.fragments.iter().map(|r| r.matches.len()).sum();
main_count + fragment_count
}
#[must_use]
pub fn main_paths(&self) -> Vec<std::path::PathBuf> {
let mut paths = Vec::new();
for result in &self.main_files {
for candidate in &result.matches {
paths.push(candidate.path.clone());
}
}
paths.sort_by_key(|p| {
self.main_files
.iter()
.flat_map(|r| &r.matches)
.find(|c| &c.path == p)
.map_or(std::cmp::Reverse(0), |c| {
std::cmp::Reverse(u8::from(c.tier))
})
});
paths
}
#[must_use]
pub fn fragment_paths(&self) -> Vec<std::path::PathBuf> {
let mut paths = Vec::new();
for result in &self.fragments {
for candidate in &result.matches {
paths.push(candidate.path.clone());
}
}
paths.sort_by_key(|p| {
self.fragments
.iter()
.flat_map(|r| &r.matches)
.find(|c| &c.path == p)
.map_or(std::cmp::Reverse(0), |c| {
std::cmp::Reverse(u8::from(c.tier))
})
});
paths
}
#[must_use]
pub fn all_paths(&self) -> Vec<std::path::PathBuf> {
let mut paths = self.main_paths();
paths.extend(self.fragment_paths());
paths
}
#[must_use]
pub fn main_candidates(&self) -> Vec<&ConfigCandidate> {
let mut candidates: Vec<&ConfigCandidate> = self
.main_files
.iter()
.flat_map(|r| r.matches.iter())
.collect();
candidates.sort_by_key(|c| std::cmp::Reverse(u8::from(c.tier)));
candidates
}
#[must_use]
pub fn fragment_candidates(&self) -> Vec<&ConfigCandidate> {
let mut candidates: Vec<&ConfigCandidate> = self
.fragments
.iter()
.flat_map(|r| r.matches.iter())
.collect();
candidates.sort_by_key(|c| std::cmp::Reverse(u8::from(c.tier)));
candidates
}
#[must_use]
pub fn missing_required(&self) -> Option<&ConfigFileRule> {
self.rules.main_files.iter().find(|rule| {
rule.required && {
!self
.main_files
.iter()
.any(|r| &r.rule == *rule && !r.matches.is_empty())
}
})
}
#[must_use]
pub fn existing_files(&self) -> Vec<&ConfigCandidate> {
self.main_candidates()
.into_iter()
.chain(self.fragment_candidates())
.filter(|c| c.status.exists())
.collect()
}
}
#[derive(Debug, Clone)]
pub struct RuleMatchResult {
pub rule: ConfigFileRule,
pub matches: Vec<ConfigCandidate>,
}
pub use crate::core::discovery::ConfigCandidate;