willdo 0.0.1

Task manager with DAG
Documentation
use super::{
    AnyNumberOf, Code, Command, Configuration, ConfigurationEntry, Interpretter2, Job, Reference,
    Rule, Trigger,
};
use crate::job::Relation;
use std::{ffi::OsStr, path::PathBuf};
use url::Url;

pub struct Entry {
    // the path of module names
    namespace: Vec<Box<str>>,
    source: Url,
    definition: Definition,
}

impl core::fmt::Debug for Entry {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.debug_struct("Entry")
            .field("namespace", &self.namespace)
            .field("source", &self.source.to_string())
            .field("definition", &self.definition)
            .finish()
    }
}
#[derive(Debug)]
enum Definition {
    Job(Job),
    Interpretter(Interpretter2),
    Project { name: Box<str> },
}
impl ConfigurationEntry for Entry {
    fn configure<C: Configuration>(self, config: &mut C) -> Result<(), C::Error> {
        match self.definition {
            Definition::Job(Job {
                job: name,
                runs,
                script,
                when,
                then,
            }) => {
                let name = Self::normalize_name(&self.source, name);
                let mut relations = Self::configure_rules(when);
                relations.extend(Self::apply_triggers(then));
                config.configure_job(
                    &self.namespace,
                    name,
                    self.source.as_str().into(),
                    Self::normalize_script(script),
                    runs,
                    relations,
                )?;
                Ok(())
            }
            Definition::Project { name } => {
                config.configure_project(&self.namespace, name, self.source)
            }
            Definition::Interpretter(Interpretter2 {
                interpretter,
                provider,
                spec,
            }) => config.configure_interpretter(
                &self.namespace,
                interpretter,
                self.source.as_str().into(),
                provider,
                spec,
            ),
        }
    }
}
impl Entry {
    pub fn project(namespace: &[Box<str>], source: &Url, name: Box<str>) -> Entry {
        Entry {
            namespace: namespace.to_vec(),
            source: source.clone(),
            definition: Definition::Project { name },
        }
    }
    pub fn job(namespace: &[Box<str>], source: &Url, job: Job) -> Entry {
        Entry {
            namespace: namespace.to_vec(),
            source: source.clone(),
            definition: Definition::Job(job),
        }
    }
    pub fn interpretter(
        namespace: &[Box<str>],
        source: &Url,
        interpretter: Interpretter2,
    ) -> Entry {
        Entry {
            namespace: namespace.to_vec(),
            source: source.clone(),
            definition: Definition::Interpretter(interpretter),
        }
    }
    fn normalize_script(script: AnyNumberOf<Command>) -> Vec<Box<str>> {
        match script {
            AnyNumberOf::None => vec![],
            AnyNumberOf::Single(one) => vec![one],
            AnyNumberOf::Multiple(vec) => vec,
        }
        .into_iter()
        .map(|c| match c {
            Command::Float(v) => v.to_string().into_boxed_str(),
            Command::Integer(v) => v.to_string().into_boxed_str(),
            Command::SignedInteger(v) => v.to_string().into_boxed_str(),
            Command::Bool(v) => v.to_string().into_boxed_str(),
            Command::String(v) => v,
        })
        .collect()
    }
    fn apply_triggers(then: AnyNumberOf<Trigger>) -> Vec<Relation> {
        let mut trigs = vec![];
        for trigger in then {
            match trigger {
                // Trigger::Enable { enable, code: _ } => todo!(),
                Trigger::Start { start, code } => {
                    let codes = Self::normalize_codes(code);
                    for target in Self::normalize_reference(start) {
                        trigs.push(Relation::Trigger {
                            target,
                            codes: codes.clone(),
                        });
                    }
                }
            }
        }
        trigs
    }
    fn normalize_codes(code: AnyNumberOf<Code>) -> Vec<crate::job::Code> {
        let code_fix = |code| match code {
            Code::Explicit(code) => code.into(),
            Code::Range { min, max, mask } => (min, max, mask).into(),
        };
        code.into_iter().map(code_fix).collect()
    }
    fn normalize_reference(reference: Reference) -> Vec<Box<str>> {
        match reference {
            Reference::Single(r) => vec![r],
            Reference::Multiple(vec) => vec,
        }
    }
    fn configure_rules(when: AnyNumberOf<Rule>) -> Vec<Relation> {
        when.into_iter()
            .enumerate()
            .map(|(o, r)| r.to_job_rule(o))
            .map(Relation::When)
            .collect::<Vec<_>>()
    }
    fn normalize_name(source: &Url, name: Option<Box<str>>) -> Box<str> {
        if let Some(name) = name {
            return name;
        }

        let mut path = source.path();

        if let Some(size) = PathBuf::from(path).file_name().map(OsStr::len) {
            path = &path[path.len() - size..];
        }
        if path.to_ascii_lowercase().ends_with(".yaml")
            || path.to_ascii_lowercase().ends_with(".yml")
        {
            path = &path[0..PathBuf::from(path)
                .file_stem()
                .map(OsStr::len)
                .unwrap_or(path.len())];
        }
        if path.to_ascii_lowercase().ends_with(".willdo") {
            path = &path[0..PathBuf::from(path)
                .file_stem()
                .map(OsStr::len)
                .unwrap_or(path.len())];
        }

        path.into()
    }
}