lr2-oxytabler 0.10.2

Table manager for Lunatic Rave 2
Documentation
use std::path::PathBuf;

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct OutputFolderKey(pub String);

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct OutputFolder(pub OutputFolderKey, pub PathBuf);

impl std::fmt::Display for OutputFolderKey {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.0.fmt(f)
    }
}

impl OutputFolder {
    const SEPARATOR: char = ':';

    /// Returns: a pair of bad outputs if any
    #[must_use]
    pub fn is_any_parent_of_another(outputs: &[Self]) -> Option<(Self, Self)> {
        for (i, output1) in outputs.iter().enumerate() {
            for output2 in &outputs[i + 1..] {
                #[expect(clippy::suspicious_operation_groupings, reason = "false positive")]
                if output1.1 != output2.1
                    && (output1.1.starts_with(&output2.1) || output2.1.starts_with(&output1.1))
                {
                    return Some((output1.clone(), output2.clone()));
                }
            }
        }
        None
    }
}

impl std::str::FromStr for OutputFolder {
    type Err = anyhow::Error;
    fn from_str(value: &str) -> Result<Self, Self::Err> {
        use anyhow::Context as _;
        let (key, val) = value.split_once(Self::SEPARATOR).with_context(|| {
            format!(
                "separator '{}' not found in output folder string",
                Self::SEPARATOR
            )
        })?;
        Ok(Self(OutputFolderKey(key.to_string()), PathBuf::from(val)))
    }
}

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

    #[test]
    fn parse_output_path() {
        use super::{OutputFolder, OutputFolderKey};
        assert_eq!(
            "a".parse::<OutputFolder>().unwrap_err().to_string(),
            "separator ':' not found in output folder string"
        );
        assert_eq!(
            "a:D:\\b".parse::<OutputFolder>().unwrap(),
            OutputFolder(OutputFolderKey("a".into()), "D:\\b".into())
        );
    }

    #[test]
    fn is_any_parent_of_another() {
        use super::{OutputFolder, OutputFolderKey};
        let p = |p: &str| OutputFolder(OutputFolderKey(String::new()), p.into());
        let f = |pp: &[OutputFolder]| OutputFolder::is_any_parent_of_another(pp).is_some();

        assert!(!f(&[]));

        assert!(!f(&[p(""), p("")]));

        assert!(f(&[p("a"), p("a/b")]));
        assert!(f(&[p("a/b"), p("a")]));

        assert!(!f(&[p("/a"), p("/b")]));
        assert!(!f(&[p("/"), p("/")]));
        assert!(f(&[p("/a"), p("/a/b")]));
        assert!(f(&[p("/a/b"), p("/a")]));

        assert!(!f(&[p("C:/a"), p("C:/b")]));
        assert!(!f(&[p("C:/"), p("C:/")]));
        assert!(f(&[p("C:/a"), p("C:/a/b")]));
        assert!(f(&[p("C:/a"), p("C:/a/b")]));
    }
}