willdo 0.0.1

Task manager with DAG
Documentation
//! # essential job models

mod display;

/// Job ID unique within the graph
#[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct JobId {
    /// Project path within the graph
    pub namespace: Vec<Box<str>>,
    /// Job name in a project namespace
    pub name: Box<str>,
}

/// A job in the WillDo graph
#[derive(Debug, Default)]
pub struct Job {
    /// Job ID unique within the graph
    pub id: JobId,
    /// Configuration source
    pub source: Box<str>,
    /// Which interpretter should be used
    pub interpretter: Option<Box<str>>,
    /// Script command sequence
    pub script: Vec<Box<str>>,
}

/// Inter-job relationship
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Relation {
    /// Job inbound rule
    When(Rule),
    /// Job outbound trigger
    Trigger {
        /// Job id to start
        target: Box<str>,
        /// Completion code constraints combined with OR logic
        codes: Vec<Code>,
    },
}
/// When to start a job
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Rule {
    /// All child rules must be satisfied
    All {
        /// Rule name
        name: Box<str>,
        /// Child rules
        rules: Vec<Rule>,
    },
    /// Any child rule must be satisfied
    Any {
        /// Rule name
        name: Box<str>,
        /// Child rules
        rules: Vec<Rule>,
    },
    /// Depndency on another job's outcome
    After {
        /// Job id we depend on
        target: Box<str>,
        /// Completion code constraints combined with OR logic
        codes: Vec<Code>,
    },
}

/// Represents expected job completion exit code
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Code {
    /// Single value
    Explicit(usize),
    /// Value matching the constraints if set.
    /// The constraints apply independently and combine with AND logic
    Range {
        /// code >= min
        min: usize,
        /// code <= max
        max: usize,
        /// code & mask !=0 (if mask != 0)
        mask: usize,
    },
}
impl Code {
    pub(crate) fn matches(&self, code: usize) -> bool {
        match self {
            Code::Explicit(explicit) => *explicit == code,
            Code::Range { min, max, mask } => {
                code >= *min && code <= *max && (*mask == 0 || (code & mask != 0))
            }
        }
    }
}

impl From<usize> for Code {
    fn from(value: usize) -> Self {
        Code::Explicit(value)
    }
}
impl From<Option<usize>> for Code {
    fn from(value: Option<usize>) -> Self {
        Code::Explicit(value.unwrap_or_default())
    }
}
impl<Min, Max, Mask> From<(Min, Max, Mask)> for Code
where
    Min: Into<Option<usize>>,
    Max: Into<Option<usize>>,
    Mask: Into<Option<usize>>,
{
    fn from(value: (Min, Max, Mask)) -> Self {
        Code::Range {
            min: Into::<Option<usize>>::into(value.0).unwrap_or_default(),
            max: Into::<Option<usize>>::into(value.1).unwrap_or(usize::MAX),
            mask: Into::<Option<usize>>::into(value.2).unwrap_or_default(),
        }
    }
}

#[test]
fn code_0_in_0_3() {
    assert!(Code::Range {
        min: 0,
        max: 3,
        mask: 0
    }
    .matches(0))
}