diff --git a/src/install.rs b/src/install.rs index a4fe58c..7a36c61 100644 --- a/src/install.rs +++ b/src/install.rs @@ -5,12 +5,27 @@ use git2::Repository; use tempfile::tempdir; use crate::console::{GOOD, NOTE, error, fail, status, tip, update, warning}; -use crate::settings::{InstallSettings, Settings}; +use crate::settings::Settings; +use crate::util::load_toml; mod cc; pub mod languages; +mod lock; -use languages::{InstallInfo, Revision, is_installed}; +use languages::{InstallInfo, LanguageDefs, Revision, is_installed}; + +struct LanguageDb { + user: LanguageDefs, + default: LanguageDefs, +} + +impl LanguageDb { + pub fn get(&self, language: &str) -> Option<&InstallInfo> { + self.user + .get(language) + .or_else(|| self.default.get(language)) + } +} #[derive(Debug, Args)] pub struct Install { @@ -18,36 +33,22 @@ pub struct Install { #[arg(short, long)] force: bool, - /// List installable parsers - #[arg(short, long)] - list: bool, - languages: Vec, } impl Install { - pub fn run(self, mut settings: Settings) -> anyhow::Result { - let language_defs = settings.language_defs()?; - - if self.list { - let mut available = language_defs.into_keys().collect::>(); - available.sort(); - available.dedup(); - - for language in available { - println!("{language}"); - } - - return Ok(ExitCode::SUCCESS); - } - + pub fn run(self, settings: Settings) -> anyhow::Result { let src_dir = tempdir()?; let parser_dir = settings.parser_dir(); - let InstallSettings { compiler } = settings.install; create_dir_all(&parser_dir)?; + let db = LanguageDb { + default: load_toml(&settings.language_file())?, + user: settings.user_defs, + }; + // have to use a canonical path bc compiler runs elsewhere let parser_dir = parser_dir.canonicalize()?; @@ -67,13 +68,13 @@ impl Install { if !force && is_installed(language, &parser_dir) { status!("Skipping", &NOTE, "{language} (already installed)"); skips += 1; - } else if let Some(info) = language_defs.get(language) { + } else if let Some(info) = db.get(language) { if let Err(Error::Fatal(_)) = install_with_status( language, info, &parser_dir, src_dir.as_ref(), - compiler.as_deref(), + settings.install.compiler.as_deref(), ) { return Ok(ExitCode::FAILURE); } else { @@ -97,6 +98,21 @@ impl Install { } } +#[derive(Debug, thiserror::Error)] +enum Error { + #[error("couldn't clone `{0}`")] + Clone(String), + + #[error(transparent)] + Git(#[from] git2::Error), + + #[error(transparent)] + Compile(cc::Error), + + #[error(transparent)] + Fatal(cc::Error), +} + fn install_with_status( language: &str, install_info: &InstallInfo, @@ -120,21 +136,6 @@ fn install_with_status( res } -#[derive(Debug, thiserror::Error)] -enum Error { - #[error("couldn't clone `{0}`")] - Clone(String), - - #[error(transparent)] - Git(#[from] git2::Error), - - #[error(transparent)] - Compile(cc::Error), - - #[error(transparent)] - Fatal(cc::Error), -} - impl From for Error { fn from(e: cc::Error) -> Self { use cc::Error as E; diff --git a/src/install/languages.rs b/src/install/languages.rs index 8e7ed30..a0a6851 100644 --- a/src/install/languages.rs +++ b/src/install/languages.rs @@ -13,27 +13,6 @@ pub struct InstallInfo { pub type LanguageDefs = HashMap; -pub struct LanguageDb(Vec); - -impl LanguageDb { - pub fn new(sources: Vec>) -> Self { - Self(sources) - } - - 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 into_keys(self) -> impl Iterator { - self.0.into_iter().flat_map(HashMap::into_keys) - } -} - pub fn is_installed(language: &str, parser_dir: &Path) -> bool { parser_dir .join(language) diff --git a/src/main.rs b/src/main.rs index 8efee53..a345652 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,10 +9,12 @@ use clap_cargo::style::CLAP_STYLING; mod console; mod install; mod settings; +mod util; use console::{Color, error}; -use install::Install; -use settings::{default_config, get_settings}; +use install::{Install, languages::LanguageDefs}; +use settings::{Settings, default_config}; +use util::load_toml; #[derive(Debug, Parser)] #[command(version, about, long_about = None)] @@ -34,13 +36,30 @@ struct Cli { enum Command { /// Install parsers Install(Install), + + /// List available parsers + Available, } impl Command { fn run(self, config_path: &Path) -> anyhow::Result { - let settings = get_settings(config_path)?; + let settings: Settings = load_toml(config_path)?; match self { + Self::Available => { + let default_defs: LanguageDefs = load_toml(&settings.language_file())?; + + let mut languages = settings.user_defs.into_keys().collect::>(); + languages.extend(default_defs.into_keys()); + languages.sort(); + languages.dedup(); + + for language in languages { + println!("{language}"); + } + + Ok(ExitCode::SUCCESS) + } Self::Install(args) => args.run(settings), } } @@ -55,10 +74,10 @@ fn main() -> ExitCode { console::init(color); - command - .run(&config.unwrap_or_else(default_config)) - .unwrap_or_else(|e| { - error!("{e}"); - ExitCode::FAILURE - }) + let config_path = config.unwrap_or_else(default_config); + + command.run(&config_path).unwrap_or_else(|e| { + error!("{e}"); + ExitCode::FAILURE + }) } diff --git a/src/settings.rs b/src/settings.rs index 8dddc9c..a0ff417 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -1,16 +1,9 @@ -use std::{ - fs::read_to_string, - io, - path::{Path, PathBuf}, -}; +use std::path::PathBuf; use lazy_static::lazy_static; use serde::Deserialize; -use crate::{ - console::warning, - install::languages::{LanguageDb, LanguageDefs}, -}; +use crate::install::languages::LanguageDefs; lazy_static! { static ref XDG: xdg::BaseDirectories = @@ -26,7 +19,8 @@ pub struct Settings { pub install: InstallSettings, #[serde(rename = "languages")] - pub user_language_defs: Option, + #[serde(default)] + pub user_defs: LanguageDefs, } #[derive(Debug, Default, Deserialize)] @@ -51,43 +45,12 @@ pub fn default_config() -> PathBuf { } } -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("unable to read config file: {0}")] - Io(#[from] io::Error), - - #[error("{file:?}: {e}")] - Toml { file: PathBuf, e: toml::de::Error }, -} - -pub fn get_settings(path: &Path) -> Result { - toml::from_str(&read_to_string(path)?).map_err(|e| Error::Toml { - file: path.to_owned(), - e, - }) -} - 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).map_err(|e| Error::Toml { - file: default_path, - e, - })?) - } - Err(e) => warning!("couldn't read language install info from {default_path:?}: {e}"), - } - - Ok(LanguageDb::new(sources)) + pub fn language_file(&self) -> PathBuf { + self.data_dir.join("languages.toml") } } diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..eb030fb --- /dev/null +++ b/src/util.rs @@ -0,0 +1,38 @@ +use std::{ + fs::read_to_string, + io, + path::{Path, PathBuf}, +}; + +#[derive(Debug, thiserror::Error)] +#[error("{path:?}: {e}")] +pub struct TomlError { + path: PathBuf, + e: TomlErrorInner, +} + +#[derive(Debug, thiserror::Error)] +enum TomlErrorInner { + #[error(transparent)] + Io(#[from] io::Error), + + #[error(transparent)] + Deserialize(#[from] toml::de::Error), +} + +pub fn load_toml(path: &Path) -> Result +where + T: serde::de::DeserializeOwned, +{ + load_toml_inner(path).map_err(|e| TomlError { + path: path.to_owned(), + e, + }) +} + +fn load_toml_inner(path: &Path) -> Result +where + T: serde::de::DeserializeOwned, +{ + Ok(toml::from_str(&read_to_string(path)?)?) +}