use std::sync::Arc;
use serde_json::Value;
use textlane_core::{Allocator, Error, File, Lines, Tree, TreeKind};
use crate::actions::allocator::reuse::with_allocator;
use crate::models::pipeline::{Api, read_tree, write_tree};
use crate::models::processor::{Advance, Finish, FrozenProcessor};
pub enum Progress {
Finished(Finished),
Paused(Pause),
}
pub struct Finished {
pub output: Outcome,
pub file: File,
}
pub enum Outcome {
Tree(Value),
Text(String),
}
pub struct Pause {
pub(crate) processor: Arc<FrozenProcessor>,
pub(crate) hosts: Vec<u32>,
pub(crate) tree: Value,
pub(crate) kind: TreeKind,
pub(crate) next: usize,
pub(crate) file: File,
pub(crate) api: Api,
pub(crate) compile: bool,
}
impl Pause {
pub fn hosts(&self) -> &[u32] {
&self.hosts
}
pub fn tree(&self) -> &Value {
&self.tree
}
pub fn file(&self) -> &File {
&self.file
}
pub fn file_mut(&mut self) -> &mut File {
&mut self.file
}
pub fn resume(self, tree: &Value) -> Result<Progress, Error> {
let Pause {
processor,
kind,
next,
mut file,
api,
compile,
..
} = self;
with_allocator(|alloc| {
let tree = read_tree(tree, Some(kind), alloc)?;
let run = Run {
processor: &processor,
api,
compile,
};
let step = run.advance(tree, &mut file, alloc, next)?;
Ok(run.progress(step, file))
})
}
}
pub(crate) enum Step {
Finished(Outcome),
Paused {
hosts: Vec<u32>,
tree: Value,
kind: TreeKind,
next: usize,
},
}
pub(crate) struct Run<'p> {
pub(crate) processor: &'p Arc<FrozenProcessor>,
pub(crate) api: Api,
pub(crate) compile: bool,
}
impl Run<'_> {
pub(crate) fn advance<'a>(
&self,
tree: Tree<'a>,
file: &mut File,
alloc: &'a Allocator,
from: usize,
) -> Result<Step, Error> {
if self.compile {
let finished = self
.processor
.finish(tree, file, alloc, from, self.api)
.map_err(|error| self.rename_async(error))?;
return match finished {
Finish::Text(text) => Ok(Step::Finished(Outcome::Text(text))),
Finish::Host { hosts, next, tree } => {
self.pause(hosts, next, &tree, file)
}
};
}
let advanced = self
.processor
.advance(tree, file, alloc, from, self.api)
.map_err(|error| self.rename_async(error))?;
match advanced {
Advance::Done(tree) => {
let lines = file.value.as_deref().map(Lines::new);
write_tree(&tree, lines.as_ref())
.map(|tree| Step::Finished(Outcome::Tree(tree)))
}
Advance::Host { hosts, next, tree } => self.pause(hosts, next, &tree, file),
}
}
fn pause(
&self,
hosts: Vec<u32>,
next: usize,
tree: &Tree<'_>,
file: &File,
) -> Result<Step, Error> {
let lines = file.value.as_deref().map(Lines::new);
Ok(Step::Paused {
hosts,
kind: tree.kind(),
tree: write_tree(tree, lines.as_ref())?,
next,
})
}
pub(crate) fn progress(&self, step: Step, file: File) -> Progress {
match step {
Step::Finished(output) => Progress::Finished(Finished { output, file }),
Step::Paused {
hosts,
tree,
kind,
next,
} => Progress::Paused(Pause {
processor: Arc::clone(self.processor),
hosts,
tree,
kind,
next,
file,
api: self.api,
compile: self.compile,
}),
}
}
fn rename_async(&self, error: Error) -> Error {
if error.code != "async-in-sync" {
return error;
}
let call = if self.compile { "process" } else { "run" };
let message = format!("`{call}Sync` finished async. Use `{call}` instead");
Error::new(error.code, message)
}
}
#[cfg(all(test, feature = "md", feature = "html"))]
#[path = "run_unit.rs"]
mod unit;