use anyhow::{anyhow, Result};
use rhai::{Array, Engine, Scope, AST};
use crate::e_collect::collect_all_targets_silent;
use crate::e_processmanager::ProcessManager;
use crate::e_target::CargoTarget;
use crate::plugins::plugin_api::{CommandSpec, Plugin, Target};
use crate::Cli;
use serde_json;
use std::sync::Arc;
use std::{
fs,
path::{Path, PathBuf},
process::Command,
};
#[allow(dead_code)]
pub struct RhaiPlugin {
name: String,
engine: Engine,
ast: AST,
path: PathBuf,
cli: crate::Cli,
manager: Arc<ProcessManager>,
}
impl RhaiPlugin {
pub fn load(path: &Path, cli: &Cli, manager: Arc<ProcessManager>) -> Result<Self> {
log::trace!("RhaiPlugin::load: reading script from {:?}", path);
let code = fs::read_to_string(path)?;
log::trace!("RhaiPlugin::load: script length {} bytes", code.len());
let mut engine = Engine::new();
let script_path_str = path.to_string_lossy().into_owned();
engine.register_fn("script_path", move || script_path_str.clone());
fn cargo_e_collect_json() -> String {
let threads = std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(4);
let targets: Vec<CargoTarget> =
collect_all_targets_silent(true, threads).unwrap_or_default();
let plugin_targets: Vec<Target> = targets
.into_iter()
.map(|t| Target {
name: t.display_name,
metadata: Some(t.manifest_path.to_string_lossy().to_string()),
cargo_target: None,
})
.collect();
serde_json::to_string(&plugin_targets).unwrap_or_default()
}
engine.register_fn("cargo_e_collect", cargo_e_collect_json);
{
let mgr = manager.clone();
let cli_clone = cli.clone();
engine.register_fn("run_example", move |target_name: String| -> i64 {
let threads = std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(4);
let targets =
crate::e_collect::collect_all_targets_silent(cli_clone.workspace, threads)
.unwrap_or_default();
if let Some(ct) = targets.into_iter().find(|t| t.name == target_name) {
match crate::e_runner::run_example(mgr.clone(), &cli_clone, &ct) {
Ok(Some(status)) => status.code().unwrap_or(-1).into(),
Ok(None) => 0,
Err(_) => -1,
}
} else {
-1
}
});
}
log::trace!("RhaiPlugin::load: compiling AST");
let ast = engine.compile(&code)?;
log::trace!("RhaiPlugin::load: compiled AST successfully");
let mut scope = Scope::new();
log::trace!("RhaiPlugin::load: invoking name() in script");
let name: String = engine
.call_fn(&mut scope, &ast, "name", ())
.map_err(|e| anyhow!("Rhai error calling name: {:?}", e))?;
log::trace!("RhaiPlugin::load: plugin reports name = {}", name);
Ok(RhaiPlugin {
name,
engine,
ast,
path: path.to_path_buf(),
cli: cli.clone(),
manager,
})
}
}
impl Plugin for RhaiPlugin {
fn name(&self) -> &str {
&self.name
}
fn matches(&self, dir: &Path) -> bool {
let dir_str = dir.to_string_lossy().to_string();
log::trace!(
"RhaiPlugin '{}' checking matches on dir {:?}",
self.name,
dir
);
let mut scope = Scope::new();
let result = self
.engine
.call_fn::<bool>(&mut scope, &self.ast, "matches", (dir_str,))
.unwrap_or(false);
log::trace!("RhaiPlugin '{}' matches returned {}", self.name, result);
result
}
fn collect_targets(&self, dir: &Path) -> Result<Vec<Target>> {
let dir_str = dir.to_string_lossy().to_string();
log::trace!(
"RhaiPlugin '{}' collecting targets on dir {:?}",
self.name,
dir
);
let mut scope = Scope::new();
let json: String = self
.engine
.call_fn(&mut scope, &self.ast, "collect_targets", (dir_str,))
.map_err(|e| anyhow!("Rhai error calling collect_targets: {:?}", e))?;
let targets: Vec<Target> = serde_json::from_str(&json)?;
log::trace!(
"RhaiPlugin '{}' collect_targets returned {} targets",
self.name,
targets.len()
);
Ok(targets)
}
fn build_command(&self, dir: &Path, target: &Target) -> Result<Command> {
let dir_str = dir.to_string_lossy().to_string();
let target_str = target.name.clone();
log::trace!(
"RhaiPlugin '{}' building command for target {}",
self.name,
target.name
);
let mut scope = Scope::new();
let json: String = self
.engine
.call_fn(
&mut scope,
&self.ast,
"build_command",
(dir_str, target_str),
)
.map_err(|e| anyhow!("Rhai error calling build_command: {:?}", e))?;
let spec: CommandSpec = serde_json::from_str(&json)
.map_err(|e| anyhow!("Invalid JSON from Rhai: {:?}\nOriginal: {}", e, json))?;
log::trace!(
"RhaiPlugin '{}' build_command JSON spec: {}",
self.name,
json
);
Ok(spec.into_command(dir))
}
fn source(&self) -> Option<String> {
Some(self.path.to_string_lossy().into())
}
fn run(&self, dir: &Path, target: &Target) -> Result<Vec<String>> {
let mut scope = Scope::new();
let dir_str = dir.to_string_lossy().to_string();
let tgt_str = target.name.clone();
if let Ok(arr) = self.engine.call_fn::<Array>(
&mut scope,
&self.ast,
&target.name,
(dir_str.clone(), tgt_str.clone()),
) {
let mut result = Vec::new();
if !arr.is_empty() {
result.push(arr[0].to_string());
for v in arr.iter().skip(1) {
result.push(v.to_string());
}
}
return Ok(result);
}
if let Ok(arr) = self.engine.call_fn::<Array>(
&mut scope,
&self.ast,
"run",
(dir_str.clone(), tgt_str.clone()),
) {
let mut result = Vec::new();
if !arr.is_empty() {
result.push(arr[0].to_string());
for v in arr.iter().skip(1) {
result.push(v.to_string());
}
}
return Ok(result);
}
let spec_json = self
.engine
.call_fn::<String>(
&mut scope,
&self.ast,
"build_command",
(dir_str.clone(), tgt_str.clone()),
)
.map_err(|e| anyhow!("Rhai error calling build_command: {:?}", e))?;
let spec: CommandSpec = serde_json::from_str(&spec_json).map_err(|e| {
anyhow!(
"Invalid JSON from Rhai build_command: {:?}\nJSON: {}",
e,
spec_json
)
})?;
let mut cmd = spec.into_command(dir);
let output = cmd.output()?;
let mut result = Vec::new();
let code = output.status.code().unwrap_or(0);
result.push(code.to_string());
let stdout = String::from_utf8_lossy(&output.stdout);
for line in stdout.lines() {
result.push(line.to_string());
}
Ok(result)
}
}