use std::collections::BTreeMap;
use std::ffi::OsString;
use std::path::Path;
use pmpx_engine::store::InstalledPlugin;
use pmpx_engine::Event;
pub use pmpx_engine::{Backend, Options, Session};
use pmpx_plugin::Verb;
use crate::cli::Cli;
use crate::debug;
use crate::detect_types::{FamilyScore, Selection};
use crate::error::PmpxError;
pub fn options(args: &Cli) -> Options {
Options {
start_dir: args.dir.clone(),
no_walk_up: args.no_walk_up,
wanted_plugin: args.plugin.clone(),
quiet: args.quiet,
trace: debug::enabled(),
child_output_on_stderr: args.json,
contract_version: env!("CARGO_PKG_VERSION").to_string(),
}
}
pub fn session(args: &Cli) -> crate::error::Result<Session> {
let mut sink = Sink::new();
Session::open(options(args), &mut |event| sink.handle(event)).map_err(PmpxError::from)
}
pub struct Sink {
plugin: String,
}
impl Sink {
pub fn new() -> Self {
Self {
plugin: String::new(),
}
}
pub fn handle(&mut self, event: Event) {
if let Event::Notes { plugin, .. } = &event {
self.plugin.clone_from(plugin);
}
crate::runtime::render_event(&self.plugin, event);
}
}
impl Default for Sink {
fn default() -> Self {
Self::new()
}
}
pub fn select(
session: &Session,
root: &Path,
) -> std::result::Result<Selection, crate::error::PmpxError> {
let mut sink = Sink::new();
session
.select(root, &mut |event| sink.handle(event))
.map_err(|failure| PmpxError::from(pmpx_engine::EngineError::Detect(failure)))
}
pub fn select_from(
session: &Session,
root: &Path,
families: &BTreeMap<String, FamilyScore>,
) -> std::result::Result<Selection, crate::error::PmpxError> {
let mut sink = Sink::new();
session
.select_from(root, families, &mut |event| sink.handle(event))
.map_err(|failure| PmpxError::from(pmpx_engine::EngineError::Detect(failure)))
}
pub fn score_all(session: &Session, root: &Path) -> BTreeMap<String, FamilyScore> {
let mut sink = Sink::new();
pmpx_engine::detect::score_all(&session.plugins, root, &session.project, &mut |event| {
sink.handle(event)
})
}
pub fn load_backend(session: &Session, selection: &Selection) -> crate::error::Result<Backend> {
let mut sink = Sink::new();
session
.load_backend(selection, &mut |event| sink.handle(event))
.map_err(PmpxError::from)
}
pub fn load_plugin(session: &Session, plugin: &InstalledPlugin) -> crate::error::Result<Backend> {
let mut sink = Sink::new();
session
.load_plugin(plugin, &mut |event| sink.handle(event))
.map_err(PmpxError::from)
}
pub fn emit_notes(session: &Session, selection: &Selection) {
let mut sink = Sink::new();
sink.handle(Event::Notes {
plugin: selection.name.clone(),
notes: selection.notes.clone(),
});
let _ = session;
}
pub fn run_verb(
session: &Session,
verb: Verb,
args: &[OsString],
allow_exec_fallback: bool,
) -> crate::error::Result<u8> {
let mut sink = Sink::new();
pmpx_engine::run_verb(session, verb, args, allow_exec_fallback, &mut |event| {
sink.handle(event)
})
.map_err(PmpxError::from)
}
pub fn run_script(
session: &Session,
name: &str,
extra: &[OsString],
) -> Option<crate::error::Result<u8>> {
let mut sink = Sink::new();
pmpx_engine::run_script(session, name, extra, &mut |event| sink.handle(event))
.map(|result| result.map_err(PmpxError::from))
}
pub fn explain(args: &Cli) -> crate::error::Result<u8> {
let trace = debug::enabled();
let mut sink = Sink::new();
let session = Session::open(options(args), &mut |event| {
if trace {
sink.handle(event);
}
})
.map_err(PmpxError::from)?;
let root = session
.project_root()
.map(Path::to_path_buf)
.unwrap_or_else(|| session.start_dir.clone());
let families =
pmpx_engine::detect::score_all(&session.plugins, &root, &session.project, &mut |event| {
if trace {
sink.handle(event);
}
});
let outcome = session.project_root().map(|_| {
session
.select_from(&root, &families, &mut |event| {
if trace {
sink.handle(event);
}
})
.map_err(|failure| PmpxError::from(pmpx_engine::EngineError::Detect(failure)))
});
if crate::runtime::is_json() {
let installed: Vec<serde_json::Value> = session
.plugins
.plugins
.iter()
.map(|plugin| {
serde_json::json!({
"name": plugin.name(),
"crate_name": plugin.crate_name(),
"version": plugin.version(),
"family": plugin.family.as_ref().map(|f| f.as_str().to_string()),
"abi": plugin.abi(),
"problem": plugin.problem(),
"markers": plugin.detect_names().collect::<Vec<_>>(),
})
})
.collect();
let scores: Vec<serde_json::Value> = families
.values()
.map(|family| {
serde_json::json!({
"family": family.family,
"score": family.score,
"pinned": family.pinned,
"plugins": family.plugins.len(),
})
})
.collect();
let chosen = match &outcome {
Some(Ok(selection)) => serde_json::json!({
"plugin": selection.name,
"crate_name": selection.crate_name,
"family": selection.family,
"score": selection.score,
"reason": format!("{:?}", selection.reason),
}),
Some(Err(error)) => serde_json::json!({ "refused": error.to_string() }),
None => serde_json::Value::Null,
};
anstream::println!(
"{}",
serde_json::json!({
"start_dir": session.start_dir.to_string_lossy(),
"project_root": session.project_root().map(|p| p.to_string_lossy().into_owned()),
"walked": session.walk.dirs.len(),
"installed": installed,
"scores": scores,
"chosen": chosen,
})
);
return Ok(crate::error::EXIT_OK);
}
anstream::println!(
"{}",
crate::style::paint(crate::style::DIM, "explain: nothing was run")
);
anstream::println!("{:<18}{}", "start directory", session.start_dir.display());
anstream::println!(
"{:<18}{}",
"project root",
session
.project_root()
.map(|p| p.display().to_string())
.unwrap_or_else(|| "(none)".to_string())
);
anstream::println!("{:<18}{} directories", "walk-up", session.walk.dirs.len());
anstream::println!("\ninstalled");
if session.plugins.plugins.is_empty() {
anstream::println!(" (none)");
}
for plugin in &session.plugins.plugins {
anstream::println!(
" {} {} family={} abi={}{}",
plugin.name(),
crate::style::paint(crate::style::DIM, format!("v{}", plugin.version())),
plugin.family.as_ref().map(|f| f.as_str()).unwrap_or("-"),
plugin
.abi()
.map(|abi| abi.to_string())
.unwrap_or_else(|| "-".to_string()),
match plugin.problem() {
Some(problem) => format!(" [{problem}]"),
None => String::new(),
}
);
let markers: Vec<&str> = plugin.detect_names().collect();
if !markers.is_empty() {
anstream::println!(" markers: {}", markers.join(", "));
}
}
anstream::println!("\nscores");
if families.is_empty() {
anstream::println!(" (nothing scored)");
}
for family in families.values() {
anstream::println!(
" {:<12}{}{}",
family.family,
family.score,
if family.pinned { " (pinned)" } else { "" }
);
}
match &outcome {
Some(Ok(selection)) => anstream::println!(
"\nchosen {} ({}) score {} -- {:?}",
selection.name,
selection.family,
selection.score,
selection.reason
),
Some(Err(error)) => anstream::println!("\nchosen (none) {error}"),
None => anstream::println!("\nchosen (none) no project root"),
}
Ok(crate::error::EXIT_OK)
}