///|
/// Define a task that starts at an event in an EventContext.
pub fn BatchTask::from_event(
name~ : String,
event~ : String,
rule~ : DateRule,
start_rule~ : StartDateRule,
calendar~ : Calendar,
extension~ : ExtensionPolicy,
) -> BatchTask {
{
name,
event: Some(event),
dependency: None,
rule,
start_rule,
calendar,
extension,
}
}
///|
/// Define a task that starts at another task's adjusted result.
pub fn BatchTask::after(
name~ : String,
dependency~ : String,
rule~ : DateRule,
start_rule~ : StartDateRule,
calendar~ : Calendar,
extension~ : ExtensionPolicy,
) -> BatchTask {
{
name,
event: None,
dependency: Some(dependency),
rule,
start_rule,
calendar,
extension,
}
}
///|
/// Start an empty batch.
pub fn BatchDeadline::new() -> BatchDeadline {
{ tasks: [] }
}
///|
/// Add a uniquely named task without mutating the original batch.
pub fn BatchDeadline::add(
self : BatchDeadline,
task : BatchTask,
) -> Result[BatchDeadline, BatchError] {
if task.name.length() == 0 {
return Err(BatchDuplicate(task.name))
}
for current in self.tasks {
if current.name == task.name {
return Err(BatchDuplicate(task.name))
}
}
let tasks = self.tasks.copy()
tasks.push(task)
Ok({ tasks, })
}
///|
/// Calculate all tasks in dependency order.
pub fn BatchDeadline::calculate(
self : BatchDeadline,
context : EventContext,
) -> Result[BatchResult, BatchError] {
if self.tasks.is_empty() {
return Err(BatchEmpty)
}
let states = Array::make(self.tasks.length(), 0)
let results = Array::new()
let mut index = 0
while index < self.tasks.length() {
match calculate_task(self, context, index, states, results) {
Ok(_) => ()
Err(error) => return Err(error)
}
index = index + 1
}
Ok({ entries: results.copy() })
}
///|
fn task_index(tasks : Array[BatchTask], name : String) -> Int {
let mut index = 0
while index < tasks.length() {
if tasks[index].name == name {
return index
}
index = index + 1
}
-1
}
///|
fn result_index(results : Array[BatchResultEntry], name : String) -> Int {
let mut index = 0
while index < results.length() {
if results[index].name == name {
return index
}
index = index + 1
}
-1
}
///|
fn calculate_task(
batch : BatchDeadline,
context : EventContext,
index : Int,
states : Array[Int],
results : Array[BatchResultEntry],
) -> Result[Unit, BatchError] {
if states[index] == 2 {
return Ok(())
}
let task = batch.tasks[index]
if states[index] == 1 {
return Err(BatchCycle(task.name))
}
states[index] = 1
let start = match task.dependency {
Some(dependency) => {
let dependency_index = task_index(batch.tasks, dependency)
if dependency_index < 0 {
return Err(BatchMissingDependency(dependency))
}
match calculate_task(batch, context, dependency_index, states, results) {
Ok(_) => ()
Err(error) => return Err(error)
}
let result_index = result_index(results, dependency)
if result_index < 0 {
return Err(BatchMissingDependency(dependency))
}
results[result_index].result.adjusted_date()
}
None =>
match task.event {
Some(event) =>
match context.resolve(event) {
Ok(date) => date
Err(error) => return Err(BatchEventFailure(error))
}
None => return Err(BatchMissingDependency(task.name))
}
}
let deadline = Deadline::new(
start_date=start,
rule=task.rule,
start_rule=task.start_rule,
calendar=task.calendar,
extension=task.extension,
)
let result = match deadline.calculate() {
Ok(value) => value
Err(error) => return Err(BatchDeadlineFailure(error))
}
results.push({ name: task.name, result })
states[index] = 2
Ok(())
}
///|
/// Return task names in deterministic calculation order.
pub fn BatchResult::ordered(self : BatchResult) -> Array[String] {
[
for entry in self.entries => entry.name
]
}
///|
/// Look up one calculated task.
pub fn BatchResult::get(self : BatchResult, name : String) -> DeadlineResult? {
let index = result_index(self.entries, name)
if index < 0 {
None
} else {
Some(self.entries[index].result)
}
}
///|
/// Render a compact audit summary for the complete batch.
pub fn BatchResult::explain(self : BatchResult) -> String {
let mut output = ""
for entry in self.entries {
output = output +
entry.name +
": " +
entry.result.adjusted_date().to_string() +
"\n"
}
output
}