reorganization, add subcommand

This commit is contained in:
wires 2025-03-22 23:32:04 -04:00
parent e9b3f5073f
commit 2b513603b7
Signed by: wires
SSH key fingerprint: SHA256:9GtP+M3O2IivPDlw1UY872UPUuJH2gI0yG6ExBxaaiM
5 changed files with 166 additions and 124 deletions

View file

@ -88,8 +88,10 @@ macro_rules! info {
macro_rules! fail {
($($arg:tt)+) => {
{
crate::console::console().update_right("Failed", &crate::console::ERROR, "");
error!($($arg)+);
error!($($arg)+)
}
}
}

133
src/install.rs Normal file
View file

@ -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<String>,
}
impl Install {
pub fn run(
self,
Settings {
parser_dir,
compiler,
}: Settings,
) -> anyhow::Result<ExitCode> {
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<languages::Revision, Error> {
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<cc::Error> 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<git2::Oid, Error> {
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)
}

View file

@ -23,15 +23,6 @@ pub enum Error {
ExecFailed { status: ExitStatus, stderr: Vec<u8> },
}
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()

View file

@ -1,5 +1,7 @@
use phf::phf_map;
pub(super) type Revision = git2::Oid;
#[derive(Debug)]
pub struct InstallInfo {
pub url: &'static str,

View file

@ -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<String>,
#[command(subcommand)]
command: Command,
}
#[derive(Debug, Subcommand)]
enum Command {
Install(Install),
}
impl Command {
fn run(self) -> anyhow::Result<ExitCode> {
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) => {
command.run().unwrap_or_else(|e| {
error!("{e}");
ExitCode::FAILURE
}
Ok(code) => code,
}
}
fn install_all(mut languages: Vec<String>) -> anyhow::Result<ExitCode> {
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 {
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(())
}