use napi::bindgen_prelude::{AsyncTask, Either, Latin1String};
use napi::{Env, Task};
use napi_derive::napi;
use serde_json::Value;
use textlane_core::File;
use crate::actions::json::read::read_json;
use crate::{Api, Outcome, Progress};
#[napi]
pub fn version() -> &'static str {
crate::read_version()
}
#[napi]
pub fn retained_memory() -> i64 {
i64::try_from(crate::read_retained_memory()).unwrap_or(i64::MAX)
}
#[napi]
pub fn set_retained_memory(bytes: i64) -> napi::Result<()> {
let bytes = usize::try_from(bytes).map_err(|_| {
napi::Error::new(napi::Status::InvalidArg, "bytes must not be negative")
})?;
crate::set_retained_memory(bytes);
Ok(())
}
#[napi]
pub fn plugin_names() -> Vec<String> {
crate::list_plugins()
.into_iter()
.map(str::to_string)
.collect()
}
#[cfg(feature = "testing")]
#[napi]
pub fn run_case(case: String) -> napi::Result<String> {
let case = serde_json::from_str(&case)
.map_err(|e| napi::Error::from_reason(e.to_string()))?;
Ok(crate::run_case(&case).to_string())
}
type Result<T> = napi::Result<T, String>;
#[napi]
pub struct Pipeline {
inner: crate::Pipeline,
}
#[napi]
impl Pipeline {
#[napi(constructor)]
pub fn new() -> Self {
Self {
inner: crate::Pipeline::new(),
}
}
#[napi(js_name = "use")]
pub fn use_value(&mut self, value: Value, options: Option<Value>) -> Result<()> {
self.inner.use_value(&value, options).map_err(throw)
}
#[napi]
pub fn data(&self, key: Option<String>) -> Value {
self.inner.data(key.as_deref())
}
#[napi]
pub fn set_data(&mut self, key: String, value: Value) -> Result<()> {
self.inner.set_data(&key, value).map_err(throw)
}
#[napi]
pub fn replace_data(&mut self, data: Value) -> Result<()> {
self.inner.replace_data(data).map_err(throw)
}
#[napi]
pub fn freeze(&mut self) -> Result<()> {
self.inner.freeze().map(|_| ()).map_err(throw)
}
#[napi]
pub fn parse(&mut self, text: String) -> Result<String> {
let tree = self.inner.parse(&text, &mut File::new()).map_err(throw)?;
Ok(tree.to_string())
}
#[napi]
pub fn run(&mut self, tree: String, value: Option<String>) -> Result<String> {
self.run_with(&tree, value, Api::Async)
}
#[napi]
pub fn run_sync(&mut self, tree: String, value: Option<String>) -> Result<String> {
self.run_with(&tree, value, Api::Sync)
}
#[napi]
pub fn stringify(&mut self, tree: String) -> Result<String> {
let tree = read_json(&tree).map_err(throw)?;
self.inner.stringify(&tree, &mut File::new()).map_err(throw)
}
#[napi]
pub fn hosts(&self) -> Vec<u32> {
self.inner.hosts()
}
#[napi]
pub fn start_run(
&mut self,
tree: String,
value: Option<String>,
sync: bool,
) -> Result<Run> {
let tree = read_json(&tree).map_err(throw)?;
let file = File {
value,
..File::new()
};
let progress = self.inner.start_run(&tree, file, read_api(sync));
progress.map(Run::new).map_err(throw)
}
#[napi]
pub fn start_process(
&mut self,
text: String,
sync: bool,
path: Option<String>,
) -> Result<Run> {
let file = File {
path,
..File::new()
};
let progress = self.inner.start_process(&text, file, read_api(sync));
progress.map(Run::new).map_err(throw)
}
#[napi(ts_return_type = "Promise<ProcessOutcome>")]
pub fn process(
&self,
text: String,
path: Option<String>,
) -> Result<AsyncTask<ProcessTask>> {
let job = self.inner.prepare_process(Api::Async).map_err(throw)?;
Ok(AsyncTask::new(ProcessTask { job, text, path }))
}
#[napi]
pub fn process_sync(
&mut self,
text: String,
path: Option<String>,
) -> Result<ProcessedFile> {
self.process_with(&text, path, Api::Sync)
}
fn run_with(
&mut self,
tree: &str,
value: Option<String>,
api: Api,
) -> Result<String> {
let tree = read_json(tree).map_err(throw)?;
let mut file = File {
value,
..File::new()
};
let tree = self.inner.run(&tree, &mut file, api).map_err(throw)?;
Ok(tree.to_string())
}
fn process_with(
&mut self,
text: &str,
path: Option<String>,
api: Api,
) -> Result<ProcessedFile> {
let mut file = File {
path,
..File::new()
};
let value = self.inner.process(text, &mut file, api).map_err(throw)?;
Ok(write_processed(value, file))
}
}
fn write_processed(value: String, file: File) -> ProcessedFile {
let value = match value.is_ascii() {
true => Either::B(Latin1String::from(value)),
false => Either::A(value),
};
ProcessedFile {
value,
messages: crate::write_messages(&file.messages),
data: Value::Object(file.data),
}
}
pub struct ProcessTask {
job: crate::ProcessJob,
text: String,
path: Option<String>,
}
impl Task for ProcessTask {
type Output = std::result::Result<ProcessedFile, crate::Error>;
type JsValue = ProcessOutcome;
fn compute(&mut self) -> napi::Result<Self::Output> {
let mut file = File {
path: self.path.take(),
..File::new()
};
let value = self.job.process(&self.text, &mut file);
Ok(value.map(|value| write_processed(value, file)))
}
fn resolve(&mut self, _: Env, output: Self::Output) -> napi::Result<ProcessOutcome> {
Ok(match output {
Ok(file) => ProcessOutcome {
file: Some(file),
error: None,
},
Err(error) => ProcessOutcome {
file: None,
error: Some(ProcessError {
code: error.code,
message: error.message,
}),
},
})
}
}
#[napi(object)]
pub struct ProcessOutcome {
pub file: Option<ProcessedFile>,
pub error: Option<ProcessError>,
}
#[napi(object)]
pub struct ProcessError {
pub code: String,
pub message: String,
}
#[napi(object)]
pub struct ProcessedFile {
pub value: Either<String, Latin1String>,
pub messages: Value,
pub data: Value,
}
#[napi]
pub struct Run {
progress: Option<Progress>,
}
#[napi]
impl Run {
fn new(progress: Progress) -> Self {
Self {
progress: Some(progress),
}
}
#[napi]
pub fn hosts(&self) -> Vec<u32> {
match &self.progress {
Some(Progress::Paused(pause)) => pause.hosts().to_vec(),
_ => Vec::new(),
}
}
#[napi]
pub fn tree(&self) -> String {
match &self.progress {
Some(Progress::Paused(pause)) => pause.tree().to_string(),
_ => Value::Null.to_string(),
}
}
#[napi]
pub fn value(&self) -> Option<String> {
match &self.progress {
Some(Progress::Paused(pause)) => pause.file().value.clone(),
_ => None,
}
}
#[napi]
pub fn messages(&self) -> Value {
match &self.progress {
Some(Progress::Paused(pause)) => {
crate::write_messages(&pause.file().messages)
}
_ => Value::Array(Vec::new()),
}
}
#[napi]
pub fn resume(&mut self, tree: String, messages: Value) -> Result<()> {
let tree = read_json(&tree).map_err(throw)?;
let Some(Progress::Paused(mut pause)) = self.progress.take() else {
return Err(napi::Error::new(
"finished-run".to_string(),
"Cannot resume a finished run",
));
};
let messages = crate::read_messages(&messages).map_err(throw)?;
pause.file_mut().messages.extend(messages);
self.progress = Some(pause.resume(&tree).map_err(throw)?);
Ok(())
}
#[napi]
pub fn result(&self) -> String {
match &self.progress {
Some(Progress::Finished(finished)) => match &finished.output {
Outcome::Text(text) => {
crate::write_file(&finished.file, text).to_string()
}
Outcome::Tree(tree) => tree.to_string(),
},
_ => Value::Null.to_string(),
}
}
}
fn read_api(sync: bool) -> Api {
if sync { Api::Sync } else { Api::Async }
}
fn throw(error: crate::Error) -> napi::Error<String> {
napi::Error::new(error.code, error.message)
}