From f5ebdb450628c853e4865af6ae577cf6e9f21823 Mon Sep 17 00:00:00 2001 From: Chris Pryer Date: Tue, 7 Nov 2023 15:17:54 -0500 Subject: [PATCH] Use settings.toml with toolchains --- crates/huak-package-manager/src/metadata.rs | 4 + .../huak-package-manager/src/ops/toolchain.rs | 35 ++++---- crates/huak-package-manager/src/workspace.rs | 17 ++-- crates/huak-toolchain/src/error.rs | 4 +- crates/huak-toolchain/src/settings.rs | 87 ++++++++++++++++++- dev-resources/planning.md | 5 +- 6 files changed, 114 insertions(+), 38 deletions(-) diff --git a/crates/huak-package-manager/src/metadata.rs b/crates/huak-package-manager/src/metadata.rs index c02953c2..ef1d1548 100644 --- a/crates/huak-package-manager/src/metadata.rs +++ b/crates/huak-package-manager/src/metadata.rs @@ -252,6 +252,10 @@ impl Metadata { pub fn tool(&self) -> Option<&Table> { self.tool.as_ref() } + + pub fn tool_mut(&mut self) -> Option<&mut Table> { + self.tool.as_mut() + } } impl Default for Metadata { diff --git a/crates/huak-package-manager/src/ops/toolchain.rs b/crates/huak-package-manager/src/ops/toolchain.rs index 9554c44b..14282cd2 100644 --- a/crates/huak-package-manager/src/ops/toolchain.rs +++ b/crates/huak-package-manager/src/ops/toolchain.rs @@ -13,7 +13,6 @@ use std::{ str::FromStr, }; use termcolor::Color; -use toml_edit::value; /// Resolve the target toolchain if a user provides one, otherwise get the current toolchain /// for the current workspace. If no toolchain is found then emit "error: no toolchain found". @@ -125,19 +124,6 @@ pub fn install_toolchain( teardown(parent.join(&channel_string), config)?; Err(e) } else { - let settings = parent.join("settings.toml"); - let Some(mut db) = SettingsDb::try_from(settings) - .ok() - .or(Some(SettingsDb::default())) - else { - return Err(Error::InternalError( - "failed to create settings db".to_string(), - )); - }; - let table = db.doc_mut().as_table_mut(); - let key = format!("{}", config.cwd.display()); - table["toolchains"][key] = value(format!("{}", path.display())); - Ok(()) } } @@ -524,10 +510,23 @@ pub fn update_toolchain( terminal.print_custom("Success", "finished updating", Color::Green, true) } -pub fn use_toolchain(_channel: &Channel, _config: &Config) -> HuakResult<()> { - // Resolve the target toolchain if a user provides one, otherwise get the current toolchain - // for the current workspace. If none can be found then install and use the default toolchain. - todo!() +// Resolve the target toolchain if a user provides one, otherwise get the current toolchain +// for the current workspace. If none can be found then install and use the default toolchain. +// Update the settings.toml with the scope that should *use* the resolved toolchain. +pub fn use_toolchain(channel: &Channel, config: &Config) -> HuakResult<()> { + let ws = config.workspace(); + + let Some(home) = config.home.as_ref() else { + return Err(Error::HuakHomeNotFound); + }; + + let toolchain = ws.resolve_local_toolchain(Some(channel))?; + let settings = home.join("toolchains").join("settings.toml"); + let mut db = SettingsDb::try_from(&settings).unwrap_or_default(); + + db.insert_scope(ws.root(), toolchain.root()); + + Ok(db.save(settings)?) } fn resolve_installed_toolchains(config: &Config) -> Option> { diff --git a/crates/huak-package-manager/src/workspace.rs b/crates/huak-package-manager/src/workspace.rs index cb6d1658..b735915f 100644 --- a/crates/huak-package-manager/src/workspace.rs +++ b/crates/huak-package-manager/src/workspace.rs @@ -9,7 +9,6 @@ use crate::{ use huak_toolchain::{Channel, LocalToolchain, LocalToolchainResolver, SettingsDb}; use huak_workspace::{resolve_first, PathMarker}; use std::{path::PathBuf, process::Command}; -use toml_edit::{Item, Value}; /// The `Workspace` is a struct for resolving things like the current `Package` /// or the current `PythonEnvironment`. It can also provide a snapshot of the `Environment`, @@ -221,15 +220,13 @@ fn resolve_local_toolchain( }; }; - // Attempt to retrieve the toolchain for the current workspace scope. - if let Some(table) = SettingsDb::try_from(settings) - .ok() - .as_ref() - .and_then(|db| db.doc().as_table()["scopes"].as_table()) - { - if let Some(Item::Value(Value::String(s))) = table.get(&format!("{}", config.cwd.display())) - { - return Some(LocalToolchain::new(PathBuf::from(s.to_string()))); + // Attempt to retrieve the toolchain for the current workspace scope by resolving for + // the first matching path from cwd. + if let Some(db) = SettingsDb::try_from(settings).ok().as_ref() { + for p in config.cwd.ancestors() { + if let Some((_, value)) = db.get_scope_entry(p) { + return Some(LocalToolchain::new(PathBuf::from(value.to_string()))); + } } } diff --git a/crates/huak-toolchain/src/error.rs b/crates/huak-toolchain/src/error.rs index 0180c021..19e066b0 100644 --- a/crates/huak-toolchain/src/error.rs +++ b/crates/huak-toolchain/src/error.rs @@ -21,8 +21,8 @@ pub enum Error { LocalToolNotFound(PathBuf), #[error("a toolchain already exists: {0}")] LocalToolchainExistsError(PathBuf), - #[error("a problem with utf-8 parsing occurred: {0}")] - Utf8Error(#[from] std::str::Utf8Error), #[error("{0}")] TOMLEditError(#[from] toml_edit::TomlError), + #[error("a problem with utf-8 parsing occurred: {0}")] + Utf8Error(#[from] std::str::Utf8Error), } diff --git a/crates/huak-toolchain/src/settings.rs b/crates/huak-toolchain/src/settings.rs index acaf7d11..1f87b0ce 100644 --- a/crates/huak-toolchain/src/settings.rs +++ b/crates/huak-toolchain/src/settings.rs @@ -5,13 +5,15 @@ use toml_edit::Document; #[derive(Default)] pub struct SettingsDb { - doc: Document, + doc: Document, // TODO(cnpryer): Decouple from toml_edit here } impl SettingsDb { #[must_use] - pub fn new(doc: Document) -> Self { - Self { doc } + pub fn new() -> Self { + Self { + doc: Document::new(), + } } #[must_use] @@ -24,7 +26,52 @@ impl SettingsDb { } pub fn try_from>(path: T) -> Result { - Ok(SettingsDb::new(read_settings_file(path)?)) + let mut db = Self::new(); + db.doc = read_settings_file(path)?; + Ok(db) + } + + /// Insert a scope entry. + /// + /// ```rust + /// use huak_toolchain::SettingsDb; + /// use std::path::PathBuf; + /// + /// let mut db = SettingsDb::new(); + /// let cwd = PathBuf::new(); + /// let channel = "3.12"; + /// + /// db.insert_scope(cwd, channel); + /// ``` + pub fn insert_scope>(&mut self, key: T, value: T) { + let key = format!("{}", key.as_ref().display()); + let value = format!("{}", value.as_ref().display()); + + self.doc_mut()["scopes"][key] = toml_edit::value(value); + } + + pub fn remove_scope>(&mut self, key: T) { + let key = format!("{}", key.as_ref().display()); + + self.doc_mut() + .get_mut("scopes") + .and_then(|it| it.as_inline_table_mut()) // TODO(cnpryer): Don't inline + .and_then(|it| it.remove(&key)); + } + + // TODO(cnpryer): Potentially use `ScopeEntry`. + #[must_use] + pub fn get_scope_entry>(&self, key: T) -> Option<(T, String)> { + let k = format!("{}", key.as_ref().display()); + + // TODO(cnpryer): Smarter escape + self.doc() + .get("scopes") + .and_then(|it| it.get(k).map(|v| (key, escape_string(&v.to_string())))) + } + + pub fn save>(&self, to: T) -> Result<(), Error> { + write_settings_file(self.doc(), to) } } @@ -34,3 +81,35 @@ pub(crate) fn read_settings_file>(path: T) -> Result>(doc: &Document, path: T) -> Result<(), Error> { + Ok(std::fs::write(path, doc.to_string())?) +} + +pub fn escape_string(s: &str) -> String { + s.trim().replace(['\\', '"'], "") +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_scopes() { + let mut db = SettingsDb::new(); + + db.insert_scope("/", "default"); + + let (_, value) = db.get_scope_entry("/").unwrap(); + + assert_eq!(value.to_string(), toml_edit::value("default").to_string()); + + db.remove_scope("/"); + + let table = db.doc().get("scopes").unwrap(); + + assert!(table + .as_inline_table() + .map_or(false, toml_edit::InlineTable::is_empty)); + } +} diff --git a/dev-resources/planning.md b/dev-resources/planning.md index c6f5a708..4655476f 100644 --- a/dev-resources/planning.md +++ b/dev-resources/planning.md @@ -24,12 +24,9 @@ Huak's toolchain management system can allow for target directories to contain s ```toml [scopes] "/" = "default" # Resolve to "default" for any paths under the root (sets "default" as default) -"some/project/path" = "channel or path" +"some/project/path" = "toolchain path" ``` -Keyed channels and paths would resolve in the directory the settings.toml is located. - - # Huak Workspaces