use crate::{
ebi_framework::{
ebi_command::EbiCommand,
ebi_input::{EbiInput, attempt_parse},
ebi_output::{self, EbiOutputType},
},
multiple_reader::MultipleReader,
};
use ebi_objects::anyhow::{Context, Error, Result, anyhow};
use itertools::Itertools;
use js_sys::Uint8Array;
use std::path::PathBuf;
use wasm_bindgen::prelude::*;
#[cfg(not(test))]
#[wasm_bindgen]
extern "C" {
pub fn ebi_error(s: &str, command_name: &str);
pub fn ebi_output(s: &str, command_name: &str, file_extension: &str);
pub fn ebi_log(s: &str, command_name: &str);
}
#[cfg(test)]
pub fn ebi_error(s: &str, command_name: &str) {
use std::backtrace::Backtrace;
println!("Custom backtrace: {}", Backtrace::force_capture());
panic!("{command_name}: {s}");
}
#[cfg(test)]
pub fn ebi_output(_s: &str, _command_name: &str, _file_extension: &str) {}
#[cfg(test)]
pub fn ebi_log(_s: &str, _command_name: &str) {}
#[wasm_bindgen(getter_with_clone)]
pub struct JavascriptInput {
pub textual: Option<String>,
pub binary: Option<Uint8Array>,
}
#[wasm_bindgen]
impl JavascriptInput {
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
Self {
textual: None,
binary: None,
}
}
pub fn set_textual(&mut self, string: String) {
self.textual = Some(string);
self.binary = None;
}
pub fn set_binary(&mut self, binary: Uint8Array) {
self.textual = None;
self.binary = Some(binary);
}
pub fn clone(&self) -> Self {
Self {
textual: self.textual.clone(),
binary: self.binary.clone(),
}
}
}
impl From<String> for JavascriptInput {
fn from(value: String) -> Self {
Self {
textual: Some(value),
binary: None,
}
}
}
pub fn read_inputs(
javascript_inputs: Vec<JavascriptInput>,
command: &EbiCommand,
) -> Result<(Vec<EbiInput>, &EbiOutputType)> {
if let EbiCommand::Command {
input_types: input_typess,
input_names,
output_type,
..
} = command
{
let mut inputs = vec![];
for ((input_types, input_name), javascript_input) in input_typess
.iter()
.zip(input_names.iter())
.zip(javascript_inputs.into_iter())
{
let reader = match (javascript_input.textual, javascript_input.binary) {
(Some(string_input), None) => MultipleReader::String(string_input),
(None, Some(array)) => MultipleReader::Bytes(array.to_vec()),
_ => {
return Err(anyhow!(
"No inputs or both textual and binary inputs given."
));
}
};
inputs.push(
attempt_parse(input_types, reader)
.with_context(|| format!("Reading parameter {}.", input_name))?,
);
}
Ok((inputs, output_type))
} else {
Err(anyhow!("Command expected."))
}
}
pub(crate) fn execute_javascript_command(
command: &EbiCommand,
javascript_inputs: Vec<JavascriptInput>,
command_name: &str,
exporter_file_extension: &str,
) {
let (inputs, output_type) =
match read_inputs(javascript_inputs, command).with_context(|| anyhow!("Reading inputs.")) {
Ok(t) => t,
Err(e) => return ebi_error(&print_error(e), command_name),
};
let result = match command
.execute_with_inputs(inputs)
.with_context(|| anyhow!("Executing command {}", command.long_name()))
{
Ok(result) => result,
Err(e) => return ebi_error(&print_error(e), command_name),
};
let mut p = PathBuf::new();
p.set_file_name("file");
p.set_extension(exporter_file_extension);
let exporter = EbiCommand::select_exporter(output_type, Some(&p), None).unwrap();
match ebi_output::export_to_string(result, &exporter) {
Ok(string) => return ebi_output(&string, command_name, exporter.get_extension()),
Err(e) => return ebi_error(&print_error(e), command_name),
}
}
pub fn print_error(e: Error) -> String {
format!(
"Error: {e}<br>Caused by:<br>{}",
e.chain()
.enumerate()
.skip(1)
.map(|(i, cause)| format!("{i}: {cause}"))
.join("<br>")
)
}