// nixos-config - My current NixOS configuration // // Copyright (C) 2025 Benedikt Peetz // SPDX-License-Identifier: GPL-3.0-or-later // // This file is part of my nixos-config. // // You should have received a copy of the License along with this program. // If not, see . use std::fmt::Write; use anyhow::bail; use log::debug; #[derive(Clone, Debug)] pub struct MapKey { pub key: char, pub(crate) resolution: usize, /// Part of the path, used to derive the key pub(crate) part_path: String, } impl std::hash::Hash for MapKey { fn hash(&self, state: &mut H) { self.key.hash(state) } } impl Eq for MapKey {} impl PartialEq for MapKey { fn eq(&self, other: &Self) -> bool { self.key == other.key } } impl Ord for MapKey { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.key.cmp(&other.key) } } impl PartialOrd for MapKey { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl MapKey { pub fn new_from_part_path(part_path: &str, resolution: usize) -> Vec { let key = Self::part_path_to_key(part_path, resolution); key.chars() .map(|ch| Self { key: ch, resolution, part_path: part_path.to_owned(), }) .collect() } pub fn new_ones_from_path(path: &str, number_of_chars: usize) -> Vec { let key: Vec = path .split('/') .flat_map(|part| Self::new_from_part_path(part, number_of_chars)) .collect(); debug!( "Generated full MapKeys: '{}' -> '{}'", path, MapKey::display(&key) ); key } pub fn increment(&self, target_resolution: usize) -> Vec { let new_resolution = target_resolution; // debug!("Incrementing: '{}' ('{}')", &self, &self.part_path); let added_chars = if new_resolution < self.part_path.len() { MapKey::part_path_to_key(&self.part_path, new_resolution) } else { let mut generated_chars = MapKey::part_path_to_key(&self.part_path, self.part_path.len()); generated_chars.extend( (0..(new_resolution - self.part_path.len())) .into_iter() .map(|_| self.part_path.chars().last().expect("This will exists")), ); generated_chars }; let part_path = self.part_path.clone(); let output: Vec = added_chars .chars() .enumerate() .map(|(res, ch)| MapKey { key: ch, resolution: res + 1, part_path: part_path.clone(), }) .collect(); // debug!("Finished increment: '{}' ('{}')", MapKey::display(&output), output[0].part_path); output } pub fn display(values: &[Self]) -> String { values.iter().map(|value| value.key.clone()).collect() } fn part_path_to_key(part: &str, number_of_chars: usize) -> String { fn make(pat: char, part: &str, number_of_chars: usize) -> String { let mut acc = String::new(); if !part.split(pat).all(|part| part.len() > 0) { panic!( "\ Can't turn this path '{}' to a mapping. This should not happen, please report the bug!", part ) } let mut last_working = None; for i in 0..number_of_chars { for str in part.split(pat) { if acc.len() != number_of_chars { acc.push(match str.chars().nth(i) { Some(ch) => ch, None => { if let Some(last) = last_working { str.chars().nth(last).expect("This should always exist") } else { last_working = Some(i - 1); str.chars().nth(i - 1).expect("This should always exist") } } }) } } } acc } let value = if part.contains('_') && !part.starts_with('_') && !part.ends_with('_') { make('_', part, number_of_chars) } else if part.contains('-') && !part.starts_with('-') && !part.ends_with('-') { make('-', part, number_of_chars) } else { part.chars().take(number_of_chars).collect::() }; assert_eq!( value.len(), number_of_chars, "'{}' does not have expected length of: {}", value, number_of_chars ); value } /// Checks whether a tiebreak via the [`Self::increment`] function can result in unique keys. pub fn can_tiebreak_with(&self, other: &Self) -> anyhow::Result<()> { /// Check whether the `input` &str is composed of only one character. /// If so, returns this character, otherwise returns None. fn reduce_string(input: &str) -> Option { let first_char = input .chars() .take(1) .last() .expect("Should contain one char"); if input.chars().all(|ch| ch == first_char) { Some(first_char) } else { None } } /// Check whether `a` is a subset of `b` or `b` is a subset of `a`. fn is_subset_either(a: &str, b: &str) -> bool { /// Checks if `subset` is a subset of `set`. /// /// # Examples /// ``` /// let a = "a"; /// let b = "aa"; /// assert!(is_subset(a, b)) /// ``` /// /// ``` /// let a = "abc"; /// let b = "def"; /// assert!(!is_subset(a, b)) /// ``` fn is_subset(subset: &str, set: &str) -> bool { let prefix: String = set.chars().take(subset.len()).collect(); let suffix: String = set.chars().skip(subset.len()).collect(); if prefix == subset { let clean_suffix = reduce_string(&suffix); if let Some(ch) = clean_suffix { ch == subset.chars().last().expect("Will exists") } else { false } } else { false } } match a.len().cmp(&b.len()) { std::cmp::Ordering::Less => { // `b` is the longer string. As such we need to check if `a` is a subset of `b`. is_subset(a, b) } std::cmp::Ordering::Greater => { // `a` is the longer string. As such we need to check if `b` is a subset of `a`. is_subset(b, a) } std::cmp::Ordering::Equal => a == b, } } if reduce_string(&other.part_path) .is_some_and(|a| Some(a) == reduce_string(&self.part_path)) { bail!( "\ The foreign_key ('{}', path_part: '{}' -> '{}') and our_key ('{}', path_part: '{}' -> '{}') \ have an identical path_part (when duplicated chars are removed)! I cannot extended them via incrementation. Please rename the paths to fix this. ", other, &other.part_path, reduce_string(&other.part_path).expect("Is some here"), self, &self.part_path, reduce_string(&self.part_path).expect("Is some here"), ); } if is_subset_either(&other.part_path, &self.part_path) { bail!( "\ The foreign_key ('{}', path_part: '{}') and our_key ('{}', path_part: '{}') \ are subsets of one another! A discrimination through incrementation will not work! Please rename the paths to fix this. ", other, &other.part_path, self, &self.part_path, ); } Ok(()) } } impl std::fmt::Display for MapKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_char(self.key) } }