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:
parent
a8fc500c5a
commit
fa0f908db8
11 changed files with 171 additions and 107 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -1,4 +1,5 @@
|
|||
/target
|
||||
|
||||
# testing artifact
|
||||
/parsers
|
||||
# testing artifacts
|
||||
*.so
|
||||
|
||||
|
|
20
Cargo.lock
generated
20
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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
7
chrysopoeia.toml
Normal 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
2
share/languages.toml
Normal 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"] }
|
|
@ -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};
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 {
|
||||
|
|
30
src/main.rs
30
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<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
|
||||
})
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue