ninja-files-data 0.1.0

Core data structures for ninja files
Documentation
use camino::Utf8Path;

use crate::{
    Build, BuildBuilder, BuildBuilderError, File, FileName, InvalidFileName, InvalidRuleId,
    InvalidVariable, InvalidVariableId, Rule, RuleBuilder, RuleBuilderError, RuleId, Variable,
    VariableId,
};

#[derive(Clone, Debug, Default)]
pub struct FileBuilder {
    file: File,
    errors: Vec<FileBuilderError>,
}

#[derive(Clone, Debug)]
pub enum FileBuilderError {
    InvalidVariableId(InvalidVariableId),
    InvalidVariable(InvalidVariable),
    InvaidRuleId(InvalidRuleId),
    InvalidFileName(InvalidFileName),
    InvalidRule {
        id: RuleId,
        errors: Vec<RuleBuilderError>,
    },
    InvalidBuild {
        id: FileName,
        errors: Vec<BuildBuilderError>,
    },
    RuleAlreadyExists {
        id: RuleId,
        previous: Rule,
        rule: Rule,
    },
    BuildAlreadyExists {
        id: FileName,
        previous: Build,
        build: Build,
    },
    VariableAlreadyExists(VariableId),
}

impl FileBuilder {
    pub fn new() -> FileBuilder {
        let file = File::default();
        let errors = vec![];
        FileBuilder { file, errors }
    }

    pub fn build(&self) -> Result<File, Vec<FileBuilderError>> {
        if self.errors.is_empty() {
            Ok(self.file.clone())
        } else {
            Err(self.errors.clone())
        }
    }

    pub fn variable<Id, Var>(mut self, id: Id, var: Var) -> FileBuilder
    where
        Id: AsRef<str>,
        Var: AsRef<str>,
    {
        let id = VariableId::try_create(id);
        let var = Variable::try_create(var);

        match (id, var) {
            (Ok(id), Ok(var)) => match self.file.variables.insert(id.clone(), var.clone()) {
                Some(exist) if exist == var => (),
                Some(_) => self
                    .errors
                    .push(FileBuilderError::VariableAlreadyExists(id)),
                None => (),
            },
            (Ok(_), Err(e)) => self.errors.push(FileBuilderError::InvalidVariable(e)),
            (Err(e), Ok(_)) => self.errors.push(FileBuilderError::InvalidVariableId(e)),
            (Err(e1), Err(e2)) => {
                self.errors.push(FileBuilderError::InvalidVariableId(e1));
                self.errors.push(FileBuilderError::InvalidVariable(e2));
            }
        }
        self
    }

    pub fn rule<Id, Rule>(mut self, id: Id, rule: Rule) -> FileBuilder
    where
        Id: AsRef<str>,
        Rule: Into<RuleBuilder>,
    {
        let id = RuleId::try_create(id);
        let rule = rule.into().build();

        if let Some(err) = id.clone().err() {
            self.errors.push(FileBuilderError::InvaidRuleId(err));
            return self;
        }
        let id = id.unwrap();
        if let Some(err) = rule.clone().err() {
            self.errors
                .push(FileBuilderError::InvalidRule { id, errors: err });
            return self;
        }
        let rule = rule.unwrap();

        match self.file.rules.get(&id) {
            Some(previous) if previous == &rule => (),
            Some(previous) => self.errors.push(FileBuilderError::RuleAlreadyExists {
                id,
                previous: previous.clone(),
                rule,
            }),
            None => {
                let _ = self.file.rules.insert(id, rule);
            }
        }

        self
    }

    pub fn file<File, Build>(mut self, filename: File, build: Build) -> Self
    where
        File: AsRef<Utf8Path>,
        Build: Into<BuildBuilder>,
    {
        let filename = FileName::try_create(filename);
        let build = build.into().build();

        if let Some(err) = filename.clone().err() {
            self.errors.push(FileBuilderError::InvalidFileName(err));
            return self;
        }
        let filename = filename.unwrap();

        if let Some(errors) = build.clone().err() {
            self.errors.push(FileBuilderError::InvalidBuild {
                id: filename.clone(),
                errors,
            })
        }
        let build = build.unwrap();

        match self.file.statements.get(&filename) {
            Some(previous) if previous == &build => (),
            Some(previous) => self.errors.push(FileBuilderError::BuildAlreadyExists {
                id: filename,
                previous: previous.clone(),
                build,
            }),
            None => {
                let _ = self.file.statements.insert(filename, build);
            }
        }
        self
    }

    pub fn merge(mut self, other: &Self) -> Self {
        // combine all the errors
        self.errors.extend(other.errors.clone());

        let mut file = self;
        for (id, var) in other.file.variables.iter() {
            file = file.variable(id.clone(), var.clone());
        }

        for (id, rule) in other.file.rules.iter() {
            file = file.rule(id.clone(), rule.clone());
        }

        for (id, rule) in other.file.statements.iter() {
            file = file.file(id.clone(), rule.clone());
        }

        file
    }
}