From d84b074dde0bb1abf6435448b0a7e249d93aab46 Mon Sep 17 00:00:00 2001 From: wires Date: Sat, 22 Mar 2025 15:21:01 -0400 Subject: [PATCH] prettier error reporting --- Cargo.lock | 13 ++++++++++ Cargo.toml | 3 +++ src/cc.rs | 6 ++--- src/console.rs | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 67 +++++++++++++++++++++++++++++++++++++---------- 5 files changed, 142 insertions(+), 17 deletions(-) create mode 100644 src/console.rs diff --git a/Cargo.lock b/Cargo.lock index acfe70d..fa7cdcb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -85,8 +85,11 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" name = "chrysopoeia" version = "0.1.0" dependencies = [ + "anstream", + "anstyle", "anyhow", "clap", + "clap-cargo", "config", "git2", "lazy_static", @@ -107,6 +110,16 @@ dependencies = [ "clap_derive", ] +[[package]] +name = "clap-cargo" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d546f0e84ff2bfa4da1ce9b54be42285767ba39c688572ca32412a09a73851e5" +dependencies = [ + "anstyle", + "clap", +] + [[package]] name = "clap_builder" version = "4.5.32" diff --git a/Cargo.toml b/Cargo.toml index 342df3a..160d217 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,8 +5,11 @@ edition = "2024" license = "GPL-3.0-only" [dependencies] +anstream = "0.6.18" +anstyle = "1.0.10" 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"] } git2 = "0.20.1" lazy_static = "1.5.0" diff --git a/src/cc.rs b/src/cc.rs index 066f2a8..e02df87 100644 --- a/src/cc.rs +++ b/src/cc.rs @@ -11,8 +11,8 @@ pub enum Error { NoCompiler, /// user specified a compiler and it didn't work - #[error("couldn't execute `{cmd}`: {src}")] - BadCompiler { cmd: String, src: io::Error }, + #[error("couldn't execute `{cmd}`: {e}")] + BadCompiler { cmd: String, e: io::Error }, /// user specified a compiler and it's nonsense #[error("compiler can't be empty")] @@ -57,7 +57,7 @@ impl<'a> Build<'a> { return match self.try_compile(cmd, output_path) { Err(e) => Err(Error::BadCompiler { cmd: compiler.to_owned(), - src: e, + e, }), Ok(output) => to_res(output), }; diff --git a/src/console.rs b/src/console.rs new file mode 100644 index 0000000..ddf45fd --- /dev/null +++ b/src/console.rs @@ -0,0 +1,70 @@ +use std::{ + fmt::Display, + io::{Write, stderr}, + sync::OnceLock, +}; + +use anstream::AutoStream; +use anstyle::{AnsiColor, Effects, Style}; +use clap::ValueEnum; + +pub const ERROR: Style = AnsiColor::Red.on_default().effects(Effects::BOLD); + +#[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)] +pub enum Color { + Auto, + Always, + Never, +} + +impl From for anstream::ColorChoice { + fn from(value: Color) -> Self { + match value { + Color::Always => Self::Always, + Color::Never => Self::Never, + Color::Auto => Self::Auto, + } + } +} + +#[derive(Debug)] +pub struct Console(anstream::ColorChoice); + +impl Console { + fn new(color: Color) -> Self { + Self(color.into()) + } + + pub fn print(&self, status: &str, style: &Style, msg: impl Display) { + let mut stream = AutoStream::new(stderr(), self.0).lock(); + let _ = writeln!(stream, "{style}{status}{style:#} {msg}"); + } +} + +static CONSOLE: OnceLock = OnceLock::new(); + +pub fn console() -> &'static Console { + CONSOLE.get().expect("console should be initialized") +} + +pub fn init(color: Color) { + CONSOLE + .set(Console::new(color)) + .expect("console should only be initialized once") +} + +macro_rules! error { + ($arg:expr) => ( + crate::console::console().print("error:", &crate::console::ERROR, ($arg)) + ); + + ($($arg:tt)+) => ( + crate::console::console().print( + "error:", + &crate::console::ERROR, + format_args!($($arg)+), + ) + ); +} + +pub(crate) use error; diff --git a/src/main.rs b/src/main.rs index eeef899..f04bde6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,24 +1,43 @@ -use std::{env::consts::DLL_EXTENSION, fs::create_dir_all}; +use std::{env::consts::DLL_EXTENSION, fs::create_dir_all, path::Path, process::ExitCode}; use clap::Parser; +use clap_cargo::style::CLAP_STYLING; use git2::Repository; mod cc; +mod console; mod languages; mod settings; +use console::{Color, error}; use languages::{InstallInfo, get_install_info}; use settings::{Settings, get_settings}; #[derive(Debug, Parser)] #[command(version, about, long_about = None)] +#[command(styles = CLAP_STYLING)] struct Cli { + /// when to use terminal colors + #[arg(short, long, value_enum, default_value_t = Color::Auto)] + color: Color, + language: String, } -fn main() -> anyhow::Result<()> { - let Cli { language } = Cli::parse(); +fn main() -> ExitCode { + let cli = Cli::parse(); + console::init(cli.color); + + if let Err(e) = main_inner(cli) { + error!(e); + ExitCode::FAILURE + } else { + ExitCode::SUCCESS + } +} + +fn main_inner(Cli { language, .. }: Cli) -> anyhow::Result<()> { let Settings { parser_dir, compiler, @@ -31,23 +50,43 @@ fn main() -> anyhow::Result<()> { // have to use a canonical path bc compiler runs elsewhere let parser_dir = parser_dir.canonicalize()?; - let InstallInfo { url, files } = - get_install_info(&language).ok_or(anyhow::anyhow!("unknown language `{language}`"))?; + Ok(install( + &language, + &parser_dir, + src_dir.as_ref(), + compiler.as_deref(), + )?) +} - let repo_path = src_dir.as_ref().join(&language); - Repository::clone(url, &repo_path)?; +#[derive(Debug, thiserror::Error)] +enum InstallError { + #[error("unknown language `{0}`")] + UnknownLanguage(String), + + #[error("couldn't clone `{0}`")] + Clone(&'static str), + + #[error(transparent)] + Compile(#[from] cc::Error), +} + +fn install( + language: &str, + 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()))?; + + let repo_path = src_dir.join(language); + Repository::clone(url, &repo_path).map_err(|_| InstallError::Clone(url))?; let output_path = parser_dir.join(language).with_extension(DLL_EXTENSION); let mut build = cc::Build::new(); - build - .compiler(compiler.as_deref()) - .dir(&repo_path) - .input_files(files); + build.compiler(compiler).dir(&repo_path).input_files(files); build.compile(&output_path)?; - // close explicitly to prevent premature drop - src_dir.close()?; - Ok(()) }