From fa0f908db8ca30a74caa3cba1f4586927fb84d94 Mon Sep 17 00:00:00 2001 From: wires Date: Sun, 23 Mar 2025 15:56:17 -0400 Subject: [PATCH] totally redo config system + install info install info is now stored in data dir and can be overridden from config file. removed dependency on config crate --- .gitignore | 5 +-- Cargo.lock | 20 +---------- Cargo.toml | 2 +- chrysopoeia.toml | 7 ++++ share/languages.toml | 2 ++ src/console.rs | 21 +++++++---- src/install.rs | 46 +++++++++++++----------- src/install/cc.rs | 10 +++--- src/install/languages.rs | 51 ++++++++++++-------------- src/main.rs | 36 +++++++++++++------ src/settings.rs | 78 +++++++++++++++++++++++++++++++++------- 11 files changed, 171 insertions(+), 107 deletions(-) create mode 100644 chrysopoeia.toml create mode 100644 share/languages.toml diff --git a/.gitignore b/.gitignore index 7ed5330..880cbda 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /target -# testing artifact -/parsers +# testing artifacts +*.so + diff --git a/Cargo.lock b/Cargo.lock index 35672a9..52385bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -96,7 +96,6 @@ dependencies = [ "anyhow", "clap", "clap-cargo", - "config", "crossterm", "git2", "lazy_static", @@ -104,6 +103,7 @@ dependencies = [ "serde", "tempfile", "thiserror", + "toml", "xdg", ] @@ -163,18 +163,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" -[[package]] -name = "config" -version = "0.15.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595aae20e65c3be792d05818e8c63025294ac3cb7e200f11459063a352a6ef80" -dependencies = [ - "pathdiff", - "serde", - "toml", - "winnow", -] - [[package]] name = "crossterm" version = "0.28.1" @@ -596,12 +584,6 @@ dependencies = [ "windows-targets", ] -[[package]] -name = "pathdiff" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" - [[package]] name = "percent-encoding" version = "2.3.1" diff --git a/Cargo.toml b/Cargo.toml index 45297b0..cc4aadb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,6 @@ 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"] } crossterm = "0.28.1" git2 = "0.20.1" lazy_static = "1.5.0" @@ -18,4 +17,5 @@ phf = { version = "0.11.3", features = ["macros"] } serde = { version = "1.0.219", features = ["derive"] } tempfile = "3.19.1" thiserror = "2.0.12" +toml = { version = "0.8.20", default-features = false, features = ["parse"] } xdg = "2.5.2" diff --git a/chrysopoeia.toml b/chrysopoeia.toml new file mode 100644 index 0000000..1ee6b73 --- /dev/null +++ b/chrysopoeia.toml @@ -0,0 +1,7 @@ +data_dir="share" + +# [install] +# cc="meoww" + +# [languages] +# zig = { url = "https://github.com/tree-sitter-grammars/tree-sitter-ziggggg", files = ["src/parser.c"] } diff --git a/share/languages.toml b/share/languages.toml new file mode 100644 index 0000000..bdcad98 --- /dev/null +++ b/share/languages.toml @@ -0,0 +1,2 @@ +zig = { url = "https://github.com/tree-sitter-grammars/tree-sitter-zig", files = ["src/parser.c"] } +tcl = { url = "https://github.com/tree-sitter-grammars/tree-sitter-tcl", files = ["src/parser.c", "src/scanner.c"] } diff --git a/src/console.rs b/src/console.rs index fd0bf7c..be847de 100644 --- a/src/console.rs +++ b/src/console.rs @@ -10,6 +10,7 @@ use clap::ValueEnum; use crossterm::{ExecutableCommand, cursor::MoveToPreviousLine}; pub const ERROR: Style = AnsiColor::Red.on_default().effects(Effects::BOLD); +pub const WARN: Style = AnsiColor::Yellow.on_default().effects(Effects::BOLD); pub const GOOD: Style = AnsiColor::Green.on_default().effects(Effects::BOLD); pub const NOTE: Style = AnsiColor::Cyan.on_default().effects(Effects::BOLD); @@ -77,10 +78,20 @@ macro_rules! error { ); } -macro_rules! note { +macro_rules! warning { ($($arg:tt)+) => ( crate::console::console().print( - " tip:", + "warning:", + &crate::console::WARN, + format_args!($($arg)+), + ) + ); +} + +macro_rules! tip { + ($($arg:tt)+) => ( + crate::console::console().print( + "tip:", &crate::console::NOTE, format_args!($($arg)+), ) @@ -116,8 +127,4 @@ macro_rules! update { ); } -pub(crate) use error; -pub(crate) use fail; -pub(crate) use note; -pub(crate) use status; -pub(crate) use update; +pub(crate) use {error, fail, status, tip, update, warning}; diff --git a/src/install.rs b/src/install.rs index ced745d..a0de8bd 100644 --- a/src/install.rs +++ b/src/install.rs @@ -4,17 +4,17 @@ use clap::Args; use git2::Repository; use tempfile::tempdir; -use crate::console::{GOOD, NOTE, error, fail, note, status, update}; -use crate::settings::Settings; +use crate::console::{GOOD, NOTE, error, fail, status, tip, update, warning}; +use crate::settings::{InstallSettings, Settings}; mod cc; -mod languages; +pub(crate) mod languages; -use languages::{InstallInfo, get_install_info, is_installed}; +use languages::{InstallInfo, Revision, is_installed}; #[derive(Debug, Args)] pub struct Install { - /// reinstall already present parsers + /// Reinstall already present parsers #[arg(short, long)] force: bool, @@ -22,15 +22,13 @@ pub struct Install { } impl Install { - pub fn run( - self, - Settings { - parser_dir, - compiler, - }: Settings, - ) -> anyhow::Result { + pub fn run(self, mut settings: Settings) -> anyhow::Result { let src_dir = tempdir()?; + let parser_dir = settings.parser_dir(); + let language_defs = settings.language_defs()?; + let InstallSettings { compiler } = settings.install; + create_dir_all(&parser_dir)?; // have to use a canonical path bc compiler runs elsewhere @@ -51,7 +49,7 @@ impl Install { if !force && is_installed(language, &parser_dir) { status!("Skipping", &NOTE, "{language} (already installed)"); skips += 1; - } else if let Some(info) = get_install_info(language) { + } else if let Some(info) = language_defs.get(language) { if let Err(Error::Fatal(_)) = install_with_status( language, info, @@ -70,7 +68,7 @@ impl Install { } if skips > 0 { - note!("try '{NOTE}--force{NOTE:#}' to reinstall parsers"); + tip!("try '{NOTE}--force{NOTE:#}' to reinstall parsers"); } Ok(if errors > 0 { @@ -87,14 +85,18 @@ fn install_with_status( parser_dir: &Path, src_dir: &Path, compiler: Option<&str>, -) -> Result { +) -> Result, Error> { status!("Installing", &GOOD, "{language}"); let res = install(language, install_info, parser_dir, src_dir, compiler); match &res { Err(e) => fail!("{e}"), - Ok(revision) => update!("Installed", &GOOD, "{language} {revision:.7}"), + Ok(Some(revision)) => update!("Installed", &GOOD, "{language} {revision:.7}"), + Ok(None) => { + update!("Installed", &GOOD, "{language}"); + warning!("couldn't get version info for {language}") + } } res @@ -103,7 +105,7 @@ fn install_with_status( #[derive(Debug, thiserror::Error)] enum Error { #[error("couldn't clone `{0}`")] - Clone(&'static str), + Clone(String), #[error(transparent)] Git(#[from] git2::Error), @@ -132,11 +134,15 @@ fn install( parser_dir: &Path, src_dir: &Path, compiler: Option<&str>, -) -> Result { +) -> Result, Error> { let repo_path = src_dir.join(language); - let repo = Repository::clone(url, &repo_path).map_err(|_| Error::Clone(url))?; + let repo = Repository::clone(url, &repo_path).map_err(|_| Error::Clone(url.to_owned()))?; - let version = repo.head()?.peel_to_commit()?.id(); + // it's not fatal if we just can't get the revision info, tho it'd definitely be weird + let version = repo + .head() + .and_then(|head| head.peel_to_commit().map(|c| c.id())) + .ok(); let output_path = parser_dir.join(language).with_extension(DLL_EXTENSION); diff --git a/src/install/cc.rs b/src/install/cc.rs index e02df87..6626245 100644 --- a/src/install/cc.rs +++ b/src/install/cc.rs @@ -36,7 +36,7 @@ const DEFAULT_COMPILERS: [&str; 3] = ["cc", "gcc", "clang"]; pub struct Build<'a> { compiler: Option<&'a str>, dir: Option<&'a Path>, - inputs: Vec<&'a str>, + inputs: Vec<&'a [String]>, } impl<'a> Build<'a> { @@ -84,10 +84,8 @@ impl<'a> Build<'a> { self } - pub fn input_files(&mut self, files: &[&'a str]) -> &mut Self { - for file in files { - self.inputs.push(*file); - } + pub fn input_files(&mut self, files: &'a [String]) -> &mut Self { + self.inputs.push(files); self } @@ -98,7 +96,7 @@ impl<'a> Build<'a> { cmd.current_dir(path); } - cmd.args(&self.inputs); + cmd.args(self.inputs.iter().flat_map(|s| *s)); cmd.arg("-o").arg(output_path); cmd.output() diff --git a/src/install/languages.rs b/src/install/languages.rs index a36b963..b3f654e 100644 --- a/src/install/languages.rs +++ b/src/install/languages.rs @@ -1,40 +1,33 @@ -use std::{env::consts::DLL_EXTENSION, path::Path}; +use std::{collections::HashMap, env::consts::DLL_EXTENSION, path::Path}; -use phf::phf_map; +use serde::Deserialize; pub(super) type Revision = git2::Oid; -#[derive(Debug)] +#[derive(Debug, Deserialize)] pub struct InstallInfo { - pub url: &'static str, - pub files: &'static [&'static str], + pub url: String, + + pub files: Vec, } -static LANGUAGES: phf::Map<&'static str, InstallInfo> = phf_map! { - "dockerfile" => InstallInfo{ - url: "https://github.com/camdencheek/tree-sitter-dockerfile", - files: &["src/parser.c", "src/scanner.c"], - }, - "ini" => InstallInfo{ - url: "https://github.com/justinmk/tree-sitter-ini", - files: &["src/parser.c"], - }, - "tcl" => InstallInfo{ - url: "https://github.com/tree-sitter-grammars/tree-sitter-tcl", - files: &["src/parser.c", "src/scanner.c"], - }, - "yaml" => InstallInfo{ - url: "https://github.com/tree-sitter-grammars/tree-sitter-yaml", - files: &["src/parser.c", "src/scanner.c"], - }, - "zig" => InstallInfo{ - url: "https://github.com/tree-sitter-grammars/tree-sitter-zig", - files: &["src/parser.c"], - }, -}; +pub type LanguageDefs = HashMap; -pub fn get_install_info(language: &str) -> Option<&InstallInfo> { - LANGUAGES.get(language) +pub struct LanguageDb(Vec); + +impl LanguageDb { + pub fn get(&self, language: &str) -> Option<&InstallInfo> { + for source in &self.0 { + if let Some(info) = source.get(language) { + return Some(info); + } + } + None + } + + pub fn new(sources: Vec>) -> Self { + Self(sources) + } } pub fn is_installed(language: &str, parser_dir: &Path) -> bool { diff --git a/src/main.rs b/src/main.rs index fab0ae9..8efee53 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,7 @@ -use std::process::ExitCode; +use std::{ + path::{Path, PathBuf}, + process::ExitCode, +}; use clap::{Parser, Subcommand}; use clap_cargo::style::CLAP_STYLING; @@ -9,14 +12,18 @@ mod settings; use console::{Color, error}; use install::Install; -use settings::get_settings; +use settings::{default_config, 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)] + /// Path to config file + #[arg(short, long, global = true, value_name = "PATH")] + config: Option, + + /// When to use terminal colors + #[arg(long, value_enum, global = true, default_value_t = Color::Auto, value_name="WHEN")] color: Color, #[command(subcommand)] @@ -25,12 +32,13 @@ struct Cli { #[derive(Debug, Subcommand)] enum Command { + /// Install parsers Install(Install), } impl Command { - fn run(self) -> anyhow::Result { - let settings = get_settings()?; + fn run(self, config_path: &Path) -> anyhow::Result { + let settings = get_settings(config_path)?; match self { Self::Install(args) => args.run(settings), @@ -39,12 +47,18 @@ impl Command { } fn main() -> ExitCode { - let Cli { color, command } = Cli::parse(); + let Cli { + config, + color, + command, + } = Cli::parse(); console::init(color); - command.run().unwrap_or_else(|e| { - error!("{e}"); - ExitCode::FAILURE - }) + command + .run(&config.unwrap_or_else(default_config)) + .unwrap_or_else(|e| { + error!("{e}"); + ExitCode::FAILURE + }) } diff --git a/src/settings.rs b/src/settings.rs index acc8834..a259bf1 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -1,9 +1,17 @@ -use std::path::PathBuf; +use std::{ + fs::read_to_string, + io, + path::{Path, PathBuf}, +}; -use config::{Config, ConfigError, File}; use lazy_static::lazy_static; use serde::Deserialize; +use crate::{ + console::warning, + install::languages::{LanguageDb, LanguageDefs}, +}; + lazy_static! { static ref XDG: xdg::BaseDirectories = xdg::BaseDirectories::with_prefix("chrysopoeia").unwrap(); @@ -11,21 +19,67 @@ lazy_static! { #[derive(Debug, Default, Deserialize)] pub struct Settings { - #[serde(default = "default_parser_dir")] - pub parser_dir: PathBuf, + #[serde(default = "default_data_dir")] + pub data_dir: PathBuf, + #[serde(default)] + pub install: InstallSettings, + + #[serde(rename = "languages")] + pub user_language_defs: Option, +} + +#[derive(Debug, Default, Deserialize)] +pub struct InstallSettings { #[serde(alias = "cc")] pub compiler: Option, } -fn default_parser_dir() -> PathBuf { - XDG.get_data_file("parsers") +fn default_data_dir() -> PathBuf { + if cfg!(debug_assertions) { + "share".into() + } else { + XDG.get_data_home() + } } -pub fn get_settings() -> Result { - Config::builder() - .add_source(File::from(XDG.get_config_file("config")).required(false)) - .add_source(File::with_name("chrysopoeia").required(false)) - .build()? - .try_deserialize::() +pub fn default_config() -> PathBuf { + if cfg!(debug_assertions) { + "chrysopoeia.toml".into() + } else { + XDG.get_config_file("config.toml") + } +} + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("unable to read config file: {0}")] + Io(#[from] io::Error), + + #[error(transparent)] + Toml(#[from] toml::de::Error), +} + +pub fn get_settings(path: &Path) -> Result { + Ok(toml::from_str(&read_to_string(path)?)?) +} + +impl Settings { + pub fn parser_dir(&self) -> PathBuf { + self.data_dir.join("parsers") + } + + /// note: this method moves out of self.user_language_defs + pub fn language_defs(&mut self) -> Result { + let mut sources: Vec = self.user_language_defs.take().into_iter().collect(); + + let default_path = self.data_dir.join("languages.toml"); + + match read_to_string(&default_path) { + Ok(default_defs) => sources.push(toml::from_str(&default_defs)?), + Err(e) => warning!("couldn't read language install info from {default_path:?}: {e}"), + } + + Ok(LanguageDb::new(sources)) + } }