diff --git a/Cargo.lock b/Cargo.lock index fa7cdcb..35672a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,7 +38,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -49,7 +49,7 @@ checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", "once_cell", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -58,6 +58,12 @@ version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + [[package]] name = "bitflags" version = "2.9.0" @@ -91,6 +97,7 @@ dependencies = [ "clap", "clap-cargo", "config", + "crossterm", "git2", "lazy_static", "phf", @@ -168,6 +175,31 @@ dependencies = [ "winnow", ] +[[package]] +name = "crossterm" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +dependencies = [ + "bitflags", + "crossterm_winapi", + "mio", + "parking_lot", + "rustix 0.38.44", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -192,7 +224,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -219,7 +251,7 @@ dependencies = [ "cfg-if", "libc", "r-efi", - "wasi", + "wasi 0.14.2+wasi-0.2.4", ] [[package]] @@ -465,6 +497,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + [[package]] name = "linux-raw-sys" version = "0.9.3" @@ -477,6 +515,16 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.26" @@ -489,6 +537,18 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", +] + [[package]] name = "once_cell" version = "1.21.1" @@ -513,6 +573,29 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + [[package]] name = "pathdiff" version = "0.2.3" @@ -612,6 +695,28 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +[[package]] +name = "redox_syscall" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + [[package]] name = "rustix" version = "1.0.3" @@ -621,10 +726,16 @@ dependencies = [ "bitflags", "errno", "libc", - "linux-raw-sys", - "windows-sys", + "linux-raw-sys 0.9.3", + "windows-sys 0.59.0", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "serde" version = "1.0.219" @@ -660,6 +771,36 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + [[package]] name = "siphasher" version = "1.0.1" @@ -715,8 +856,8 @@ dependencies = [ "fastrand", "getrandom", "once_cell", - "rustix", - "windows-sys", + "rustix 1.0.3", + "windows-sys 0.59.0", ] [[package]] @@ -824,6 +965,12 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "wasi" version = "0.14.2+wasi-0.2.4" @@ -833,6 +980,37 @@ dependencies = [ "wit-bindgen-rt", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.59.0" diff --git a/Cargo.toml b/Cargo.toml index 160d217..45297b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ anyhow = "1.0.97" clap = { version = "4.5.32", features = ["derive"] } clap-cargo = "0.15.2" config = { version = "0.15.11", default-features = false, features = ["toml"] } +crossterm = "0.28.1" git2 = "0.20.1" lazy_static = "1.5.0" phf = { version = "0.11.3", features = ["macros"] } diff --git a/src/cc.rs b/src/cc.rs index e02df87..cec030c 100644 --- a/src/cc.rs +++ b/src/cc.rs @@ -23,6 +23,15 @@ pub enum Error { ExecFailed { status: ExitStatus, stderr: Vec }, } +impl Error { + pub fn fatal(&self) -> bool { + matches!( + self, + Self::NoCompiler | Self::BadCompiler { .. } | Self::EmptyCompiler + ) + } +} + fn to_res(Output { status, stderr, .. }: Output) -> Result<(), Error> { status .success() diff --git a/src/console.rs b/src/console.rs index ddf45fd..bc45ff4 100644 --- a/src/console.rs +++ b/src/console.rs @@ -7,8 +7,10 @@ use std::{ use anstream::AutoStream; use anstyle::{AnsiColor, Effects, Style}; use clap::ValueEnum; +use crossterm::{ExecutableCommand, cursor::MoveToPreviousLine}; pub const ERROR: Style = AnsiColor::Red.on_default().effects(Effects::BOLD); +pub const GOOD: Style = AnsiColor::Green.on_default().effects(Effects::BOLD); #[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)] pub enum Color { @@ -36,9 +38,20 @@ impl Console { } pub fn print(&self, status: &str, style: &Style, msg: impl Display) { - let mut stream = AutoStream::new(stderr(), self.0).lock(); + let mut stream = AutoStream::new(stderr(), self.0); let _ = writeln!(stream, "{style}{status}{style:#} {msg}"); } + + pub fn print_right(&self, status: &str, style: &Style, msg: impl Display) { + let mut stream = AutoStream::new(stderr(), self.0); + let _ = writeln!(stream, "{style}{status:>12}{style:#} {msg}"); + } + + pub fn update_right(&self, status: &str, style: &Style, msg: impl Display) { + let mut stream = AutoStream::new(stderr(), self.0).lock(); + let _ = stream.execute(MoveToPreviousLine(1)); + let _ = writeln!(stream, "{style}{status:>12}{style:#} {msg}"); + } } static CONSOLE: OnceLock = OnceLock::new(); @@ -54,10 +67,6 @@ pub fn init(color: Color) { } macro_rules! error { - ($arg:expr) => ( - crate::console::console().print("error:", &crate::console::ERROR, ($arg)) - ); - ($($arg:tt)+) => ( crate::console::console().print( "error:", @@ -67,4 +76,34 @@ macro_rules! error { ); } +macro_rules! info { + ($status:expr, $($arg:tt)+) => ( + crate::console::console().print_right( + $status, + &crate::console::GOOD, + format_args!($($arg)+), + ) + ); +} + +macro_rules! fail { + ($($arg:tt)+) => { + crate::console::console().update_right("Failed", &crate::console::ERROR, ""); + error!($($arg)+); + } +} + +macro_rules! update { + ($status:expr, $($arg:tt)+) => ( + crate::console::console().update_right( + $status, + &crate::console::GOOD, + format_args!($($arg)+), + ) + ); +} + pub(crate) use error; +pub(crate) use fail; +pub(crate) use info; +pub(crate) use update; diff --git a/src/languages.rs b/src/languages.rs index e1dd591..4388cb1 100644 --- a/src/languages.rs +++ b/src/languages.rs @@ -1,6 +1,6 @@ use phf::phf_map; -#[derive(Debug, Clone, Copy)] +#[derive(Debug)] pub struct InstallInfo { pub url: &'static str, pub files: &'static [&'static str], @@ -29,6 +29,6 @@ static LANGUAGES: phf::Map<&'static str, InstallInfo> = phf_map! { }, }; -pub fn get_install_info(language: &str) -> Option { - LANGUAGES.get(language).copied() +pub fn get_install_info(language: &str) -> Option<&InstallInfo> { + LANGUAGES.get(language) } diff --git a/src/main.rs b/src/main.rs index f04bde6..b767142 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,7 @@ mod console; mod languages; mod settings; -use console::{Color, error}; +use console::{Color, error, fail, info, update}; use languages::{InstallInfo, get_install_info}; use settings::{Settings, get_settings}; @@ -21,7 +21,7 @@ struct Cli { #[arg(short, long, value_enum, default_value_t = Color::Auto)] color: Color, - language: String, + languages: Vec, } fn main() -> ExitCode { @@ -29,15 +29,16 @@ fn main() -> ExitCode { console::init(cli.color); - if let Err(e) = main_inner(cli) { - error!(e); - ExitCode::FAILURE - } else { - ExitCode::SUCCESS + match install_all(cli.languages) { + Err(e) => { + error!("{e}"); + ExitCode::FAILURE + } + Ok(code) => code, } } -fn main_inner(Cli { language, .. }: Cli) -> anyhow::Result<()> { +fn install_all(mut languages: Vec) -> anyhow::Result { let Settings { parser_dir, compiler, @@ -50,37 +51,78 @@ fn main_inner(Cli { language, .. }: Cli) -> anyhow::Result<()> { // have to use a canonical path bc compiler runs elsewhere let parser_dir = parser_dir.canonicalize()?; - Ok(install( - &language, - &parser_dir, - src_dir.as_ref(), - compiler.as_deref(), - )?) + let mut errors = 0; + let mut installed = 0; + + languages.sort(); + languages.dedup(); + + for language in &languages { + if let Some(info) = get_install_info(language) { + if let Err(e) = install( + language, + info, + &parser_dir, + src_dir.as_ref(), + compiler.as_deref(), + ) { + fail!("{e}"); + if e.fatal() { + return Ok(ExitCode::FAILURE); + } else { + errors += 1; + } + } else { + installed += 1; + } + } else { + error!("unknown language `{language}`"); + } + } + + info!("Finished", "installed {installed} parsers"); + + Ok(if errors > 0 { + ExitCode::FAILURE + } else { + ExitCode::SUCCESS + }) } #[derive(Debug, thiserror::Error)] enum InstallError { - #[error("unknown language `{0}`")] - UnknownLanguage(String), - #[error("couldn't clone `{0}`")] Clone(&'static str), + #[error(transparent)] + Git(#[from] git2::Error), + #[error(transparent)] Compile(#[from] cc::Error), } +impl InstallError { + fn fatal(&self) -> bool { + match self { + Self::Compile(e) => e.fatal(), + _ => false, + } + } +} + fn install( language: &str, + InstallInfo { url, files }: &InstallInfo, parser_dir: &Path, src_dir: &Path, compiler: Option<&str>, ) -> Result<(), InstallError> { - let InstallInfo { url, files } = - get_install_info(language).ok_or(InstallError::UnknownLanguage(language.to_owned()))?; + info!("Installing", "{language}"); let repo_path = src_dir.join(language); - Repository::clone(url, &repo_path).map_err(|_| InstallError::Clone(url))?; + let repo = Repository::clone(url, &repo_path).map_err(|_| InstallError::Clone(url))?; + + let version = repo.head()?.peel_to_commit()?.id(); let output_path = parser_dir.join(language).with_extension(DLL_EXTENSION); @@ -88,5 +130,7 @@ fn install( build.compiler(compiler).dir(&repo_path).input_files(files); build.compile(&output_path)?; + update!("Installed", "{language} {version:.7}"); + Ok(()) }