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
This commit is contained in:
wires 2025-03-23 15:56:17 -04:00
parent a8fc500c5a
commit fa0f908db8
Signed by: wires
SSH key fingerprint: SHA256:9GtP+M3O2IivPDlw1UY872UPUuJH2gI0yG6ExBxaaiM
11 changed files with 171 additions and 107 deletions

5
.gitignore vendored
View file

@ -1,4 +1,5 @@
/target
# testing artifact
/parsers
# testing artifacts
*.so

20
Cargo.lock generated
View file

@ -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"

View file

@ -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"

7
chrysopoeia.toml Normal file
View file

@ -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"] }

2
share/languages.toml Normal file
View file

@ -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"] }

View file

@ -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,7 +78,17 @@ macro_rules! error {
);
}
macro_rules! note {
macro_rules! warning {
($($arg:tt)+) => (
crate::console::console().print(
"warning:",
&crate::console::WARN,
format_args!($($arg)+),
)
);
}
macro_rules! tip {
($($arg:tt)+) => (
crate::console::console().print(
"tip:",
@ -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};

View file

@ -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<ExitCode> {
pub fn run(self, mut settings: Settings) -> anyhow::Result<ExitCode> {
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<languages::Revision, Error> {
) -> Result<Option<Revision>, 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<git2::Oid, Error> {
) -> Result<Option<Revision>, 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);

View file

@ -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()

View file

@ -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<String>,
}
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<String, InstallInfo>;
pub fn get_install_info(language: &str) -> Option<&InstallInfo> {
LANGUAGES.get(language)
pub struct LanguageDb(Vec<LanguageDefs>);
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<HashMap<String, InstallInfo>>) -> Self {
Self(sources)
}
}
pub fn is_installed(language: &str, parser_dir: &Path) -> bool {

View file

@ -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<PathBuf>,
/// 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<ExitCode> {
let settings = get_settings()?;
fn run(self, config_path: &Path) -> anyhow::Result<ExitCode> {
let settings = get_settings(config_path)?;
match self {
Self::Install(args) => args.run(settings),
@ -39,11 +47,17 @@ 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| {
command
.run(&config.unwrap_or_else(default_config))
.unwrap_or_else(|e| {
error!("{e}");
ExitCode::FAILURE
})

View file

@ -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<LanguageDefs>,
}
#[derive(Debug, Default, Deserialize)]
pub struct InstallSettings {
#[serde(alias = "cc")]
pub compiler: Option<String>,
}
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<Settings, ConfigError> {
Config::builder()
.add_source(File::from(XDG.get_config_file("config")).required(false))
.add_source(File::with_name("chrysopoeia").required(false))
.build()?
.try_deserialize::<Settings>()
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<Settings, Error> {
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<LanguageDb, Error> {
let mut sources: Vec<LanguageDefs> = 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))
}
}