use execra::{Context, ExitCode, Finding, Interpreter, InterpreterEvent as Event, Line, Progress};
use once_cell::sync::Lazy;
use regex::Regex;
static RE_INSTALLING: Lazy<Regex> = Lazy::new(|| Regex::new(r"^Installing '([^']+)'").unwrap());
static RE_DOWNLOADING: Lazy<Regex> = Lazy::new(|| Regex::new(r"Downloading '([^']+)'").unwrap());
static RE_BYTES_MB: Lazy<Regex> = Lazy::new(|| Regex::new(r"([\d.]+) MB / ([\d.]+) MB").unwrap());
static RE_CHECKING_HASH: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?i)checking hash").unwrap());
static RE_HASH_PASS: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^Hash check (?:passed|successful)").unwrap());
static RE_HASH_FAIL: Lazy<Regex> = Lazy::new(|| Regex::new(r"Hash check failed").unwrap());
static RE_EXTRACTING: Lazy<Regex> = Lazy::new(|| Regex::new(r"Extracting (.+)\.\.\.").unwrap());
static RE_LINKING: Lazy<Regex> = Lazy::new(|| Regex::new(r"^Linking ~").unwrap());
static RE_SHIMMING: Lazy<Regex> = Lazy::new(|| Regex::new(r"^Creating shim for").unwrap());
static RE_INSTALLED_OK: Lazy<Regex> =
Lazy::new(|| Regex::new(r"'([^']+)' \(([^)]+)\) was installed successfully").unwrap());
static RE_NO_MANIFEST: Lazy<Regex> =
Lazy::new(|| Regex::new(r"Couldn't find manifest for '([^']+)'").unwrap());
static RE_ALREADY_INSTALLED: Lazy<Regex> =
Lazy::new(|| Regex::new(r"is already installed").unwrap());
pub struct ScoopInstall {
in_notes: bool,
notes_buf: Vec<String>,
}
impl ScoopInstall {
pub fn new() -> Self {
Self {
in_notes: false,
notes_buf: Vec::new(),
}
}
fn flush_notes(&mut self) -> Vec<Event> {
if self.notes_buf.is_empty() {
return vec![];
}
let msg = std::mem::take(&mut self.notes_buf).join("\n");
self.in_notes = false;
vec![Event::Finding {
finding: Finding::info("scoop.notes", msg),
}]
}
}
impl Interpreter for ScoopInstall {
fn on_line(&mut self, _ctx: &Context, line: &Line) -> Vec<Event> {
let l = &line.text;
if self.in_notes {
if l.is_empty() {
return self.flush_notes();
}
if !l.trim_start().starts_with("---") {
self.notes_buf.push(l.clone());
}
return vec![];
}
if l == "Notes" {
self.in_notes = true;
return vec![];
}
if let Some(c) = RE_INSTALLING.captures(l) {
return vec![Event::Label {
text: format!("Installing {}", &c[1]),
}];
}
if let Some(c) = RE_DOWNLOADING.captures(l) {
return vec![Event::EnterPhase {
name: "download".into(),
label: Some(format!("Downloading {}", &c[1])),
}];
}
if let Some(c) = RE_BYTES_MB.captures(l) {
let done: f64 = c[1].parse().unwrap_or(0.0);
let total: f64 = c[2].parse().unwrap_or(0.0);
return vec![Event::Progress {
progress: Progress::bytes_mb(done, total),
}];
}
if RE_CHECKING_HASH.is_match(l) {
return vec![
Event::UpdatePhase {
label: "Verifying download".into(),
},
Event::Progress {
progress: Progress::indeterminate("verifying"),
},
];
}
if RE_HASH_PASS.is_match(l) {
return vec![Event::ExitPhase];
}
if let Some(c) = RE_EXTRACTING.captures(l) {
return vec![
Event::EnterPhase {
name: "extract".into(),
label: Some(format!("Extracting {}", &c[1])),
},
Event::Progress {
progress: Progress::indeterminate("extracting"),
},
];
}
if RE_LINKING.is_match(l) {
return vec![Event::EnterPhase {
name: "link".into(),
label: Some("Linking".into()),
}];
}
if RE_SHIMMING.is_match(l) {
return vec![Event::EnterPhase {
name: "link".into(),
label: Some("Creating shims".into()),
}];
}
if RE_HASH_FAIL.is_match(l) {
return vec![Event::KnownError {
code: "scoop.hash_mismatch".into(),
message: "Downloaded file hash did not match manifest".into(),
}];
}
if let Some(c) = RE_NO_MANIFEST.captures(l) {
return vec![Event::KnownError {
code: "scoop.unknown_package".into(),
message: format!("No manifest for {}", &c[1]),
}];
}
if let Some(c) = RE_INSTALLED_OK.captures(l) {
return vec![Event::Summary {
text: format!("Installed {} {}", &c[1], &c[2]),
}];
}
if RE_ALREADY_INSTALLED.is_match(l) {
return vec![Event::Summary {
text: "Already installed (no changes)".into(),
}];
}
vec![]
}
fn on_exit(&mut self, _ctx: &Context, _exit: &ExitCode) -> Vec<Event> {
self.flush_notes()
}
}