From 2b513603b7b77197e2a63da3004fd991f7f6cb41 Mon Sep 17 00:00:00 2001 From: wires Date: Sat, 22 Mar 2025 23:32:04 -0400 Subject: [PATCH] reorganization, add subcommand --- src/console.rs | 6 +- src/install.rs | 133 +++++++++++++++++++++++++++++++ src/{ => install}/cc.rs | 9 --- src/{ => install}/languages.rs | 2 + src/main.rs | 140 +++++++-------------------------- 5 files changed, 166 insertions(+), 124 deletions(-) create mode 100644 src/install.rs rename src/{ => install}/cc.rs (94%) rename src/{ => install}/languages.rs (96%) diff --git a/src/console.rs b/src/console.rs index bc45ff4..8c73453 100644 --- a/src/console.rs +++ b/src/console.rs @@ -88,8 +88,10 @@ macro_rules! info { macro_rules! fail { ($($arg:tt)+) => { - crate::console::console().update_right("Failed", &crate::console::ERROR, ""); - error!($($arg)+); + { + crate::console::console().update_right("Failed", &crate::console::ERROR, ""); + error!($($arg)+) + } } } diff --git a/src/install.rs b/src/install.rs new file mode 100644 index 0000000..966667d --- /dev/null +++ b/src/install.rs @@ -0,0 +1,133 @@ +use std::{env::consts::DLL_EXTENSION, fs::create_dir_all, path::Path, process::ExitCode}; + +use clap::Args; +use git2::Repository; +use tempfile::tempdir; + +use crate::console::{error, fail, info, update}; +use crate::settings::Settings; + +mod cc; +mod languages; + +use languages::{InstallInfo, get_install_info}; + +#[derive(Debug, Args)] +pub struct Install { + languages: Vec, +} + +impl Install { + pub fn run( + self, + Settings { + parser_dir, + compiler, + }: Settings, + ) -> anyhow::Result { + let src_dir = tempdir()?; + + create_dir_all(&parser_dir)?; + + // have to use a canonical path bc compiler runs elsewhere + let parser_dir = parser_dir.canonicalize()?; + + let mut languages = self.languages; + + languages.sort(); + languages.dedup(); + + let mut errors = 0; + + for language in &languages { + if let Some(info) = get_install_info(language) { + if let Err(Error::Fatal(_)) = install_with_status( + language, + info, + &parser_dir, + src_dir.as_ref(), + compiler.as_deref(), + ) { + return Ok(ExitCode::FAILURE); + } else { + errors += 1; + } + } else { + error!("unknown language {language}"); + errors += 1; + } + } + + Ok(if errors > 0 { + ExitCode::FAILURE + } else { + ExitCode::SUCCESS + }) + } +} + +fn install_with_status( + language: &str, + install_info: &InstallInfo, + parser_dir: &Path, + src_dir: &Path, + compiler: Option<&str>, +) -> Result { + info!("Installing", "{language}"); + + let res = install(language, install_info, parser_dir, src_dir, compiler); + + match &res { + Err(e) => fail!("{e}"), + Ok(revision) => update!("Installed", "{language} {revision:.7}"), + } + + res +} + +#[derive(Debug, thiserror::Error)] +enum Error { + #[error("couldn't clone `{0}`")] + Clone(&'static str), + + #[error(transparent)] + Git(#[from] git2::Error), + + #[error(transparent)] + Compile(cc::Error), + + #[error(transparent)] + Fatal(cc::Error), +} + +impl From for Error { + fn from(e: cc::Error) -> Self { + use cc::Error as E; + + match e { + E::NoCompiler | E::BadCompiler { .. } | E::EmptyCompiler => Self::Fatal(e), + _ => Self::Compile(e), + } + } +} + +fn install( + language: &str, + InstallInfo { url, files }: &InstallInfo, + parser_dir: &Path, + src_dir: &Path, + compiler: Option<&str>, +) -> Result { + let repo_path = src_dir.join(language); + let repo = Repository::clone(url, &repo_path).map_err(|_| Error::Clone(url))?; + + let version = repo.head()?.peel_to_commit()?.id(); + + let output_path = parser_dir.join(language).with_extension(DLL_EXTENSION); + + let mut build = cc::Build::new(); + build.compiler(compiler).dir(&repo_path).input_files(files); + build.compile(&output_path)?; + + Ok(version) +} diff --git a/src/cc.rs b/src/install/cc.rs similarity index 94% rename from src/cc.rs rename to src/install/cc.rs index cec030c..e02df87 100644 --- a/src/cc.rs +++ b/src/install/cc.rs @@ -23,15 +23,6 @@ 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/languages.rs b/src/install/languages.rs similarity index 96% rename from src/languages.rs rename to src/install/languages.rs index 4388cb1..f68d009 100644 --- a/src/languages.rs +++ b/src/install/languages.rs @@ -1,5 +1,7 @@ use phf::phf_map; +pub(super) type Revision = git2::Oid; + #[derive(Debug)] pub struct InstallInfo { pub url: &'static str, diff --git a/src/main.rs b/src/main.rs index b767142..fab0ae9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,15 @@ -use std::{env::consts::DLL_EXTENSION, fs::create_dir_all, path::Path, process::ExitCode}; +use std::process::ExitCode; -use clap::Parser; +use clap::{Parser, Subcommand}; use clap_cargo::style::CLAP_STYLING; -use git2::Repository; -mod cc; mod console; -mod languages; +mod install; mod settings; -use console::{Color, error, fail, info, update}; -use languages::{InstallInfo, get_install_info}; -use settings::{Settings, get_settings}; +use console::{Color, error}; +use install::Install; +use settings::get_settings; #[derive(Debug, Parser)] #[command(version, about, long_about = None)] @@ -21,116 +19,32 @@ struct Cli { #[arg(short, long, value_enum, default_value_t = Color::Auto)] color: Color, - languages: Vec, + #[command(subcommand)] + command: Command, +} + +#[derive(Debug, Subcommand)] +enum Command { + Install(Install), +} + +impl Command { + fn run(self) -> anyhow::Result { + let settings = get_settings()?; + + match self { + Self::Install(args) => args.run(settings), + } + } } fn main() -> ExitCode { - let cli = Cli::parse(); + let Cli { color, command } = Cli::parse(); - console::init(cli.color); + console::init(color); - match install_all(cli.languages) { - Err(e) => { - error!("{e}"); - ExitCode::FAILURE - } - Ok(code) => code, - } -} - -fn install_all(mut languages: Vec) -> anyhow::Result { - let Settings { - parser_dir, - compiler, - } = get_settings()?; - - let src_dir = tempfile::tempdir()?; - - create_dir_all(&parser_dir)?; - - // have to use a canonical path bc compiler runs elsewhere - let parser_dir = parser_dir.canonicalize()?; - - 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 { + command.run().unwrap_or_else(|e| { + error!("{e}"); ExitCode::FAILURE - } else { - ExitCode::SUCCESS }) } - -#[derive(Debug, thiserror::Error)] -enum InstallError { - #[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> { - info!("Installing", "{language}"); - - let repo_path = src_dir.join(language); - 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); - - let mut build = cc::Build::new(); - build.compiler(compiler).dir(&repo_path).input_files(files); - build.compile(&output_path)?; - - update!("Installed", "{language} {version:.7}"); - - Ok(()) -}