aboutsummaryrefslogtreecommitdiffstats
path: root/crates/turtle/src/command/client/config.rs
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2026-07-09 21:43:23 +0200
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2026-07-09 21:43:23 +0200
commit3223d93cb3c77ab02aa0a35f2a8314e447cee9a4 (patch)
tree3971a1f37f5fe3cadb747c5229a0d54e868e45e3 /crates/turtle/src/command/client/config.rs
parentfix(client/sync): Pass through precise error on `SyncError::WrongKey` (diff)
downloadatuin-3223d93cb3c77ab02aa0a35f2a8314e447cee9a4.zip
chore: Separate daemon, client, server, and lib into crates
Diffstat (limited to 'crates/turtle/src/command/client/config.rs')
-rw-r--r--crates/turtle/src/command/client/config.rs352
1 files changed, 0 insertions, 352 deletions
diff --git a/crates/turtle/src/command/client/config.rs b/crates/turtle/src/command/client/config.rs
deleted file mode 100644
index 73d1c35e..00000000
--- a/crates/turtle/src/command/client/config.rs
+++ /dev/null
@@ -1,352 +0,0 @@
-use crate::atuin_client::settings::Settings;
-use clap::{Args, Subcommand, ValueEnum};
-use eyre::Result;
-use toml_edit::{Document, DocumentMut, Item, Table, TableLike, Value};
-
-#[derive(Subcommand, Debug)]
-#[command(infer_subcommands = true)]
-pub(crate) enum Cmd {
- /// Get a configuration value from your config.toml file
- /// or after defaults and overrides are applied
- #[command()]
- Get(GetCmd),
-
- /// Set a configuration value in your config.toml file
- #[command()]
- Set(SetCmd),
-
- /// Print all configuration values from your config.toml file
- /// in TOML format
- ///
- /// If a key is provided, only print the value of that key and all its children
- #[command()]
- Print(PrintCmd),
-}
-
-impl Cmd {
- pub(crate) async fn run(self, settings: &Settings) -> Result<()> {
- match self {
- Self::Get(get) => get.run(settings).await,
- Self::Set(set) => set.run(settings).await,
- Self::Print(print) => print.run(settings).await,
- }
- }
-}
-
-/// Get a configuration value from your config.toml file,
-/// or optionally the effective value after defaults and overrides are applied.
-#[derive(Args, Debug)]
-pub(crate) struct GetCmd {
- /// The configuration key to get
- pub(crate) key: String,
-
- /// Print the value after defaults and overrides are applied
- #[arg(long, short)]
- pub(crate) resolved: bool,
-
- /// Print both the config file value and the resolved value
- #[arg(long, short)]
- pub(crate) verbose: bool,
-}
-
-impl GetCmd {
- pub(crate) async fn run(&self, _settings: &Settings) -> Result<()> {
- let key = self.key.trim();
- if key.is_empty() || key.contains(char::is_whitespace) {
- eyre::bail!("Config key must be non-empty and must not contain whitespace");
- }
-
- if self.verbose {
- println!("Config file:");
- self.print_current_value(key, " ").await?;
- println!("\nResolved:");
- Self::print_effective_value(key, " ");
- return Ok(());
- }
-
- if self.resolved {
- Self::print_effective_value(key, "");
- } else {
- self.print_current_value(key, "").await?;
- }
-
- Ok(())
- }
-
- async fn print_current_value(&self, key: &str, prefix: &str) -> Result<()> {
- let config_file = Settings::get_config_path()?;
- let config_str = tokio::fs::read_to_string(&config_file).await?;
- let doc = config_str.parse::<Document<_>>()?;
-
- let current = get_deep_key(&doc, key);
-
- match current {
- Some(item) if item.is_table() || item.is_inline_table() => {
- let table = item
- .as_table_like()
- .expect("is_table()/is_inline_table() but no table");
- println!("{prefix}[{key}]");
- dump_table(table, prefix, &mut vec![key.to_string()])?;
- }
- Some(item) => {
- let val = item.to_string();
- let val = val.trim().trim_matches('"');
- println!("{prefix}{val}");
- }
- None => {
- println!("{prefix}(not set in config file)");
- }
- }
-
- Ok(())
- }
-
- fn print_effective_value(key: &str, prefix: &str) {
- match Settings::get_config_value(key) {
- Ok(value) => {
- for line in value.lines() {
- println!("{prefix}{line}");
- }
- }
- Err(_) => {
- println!("{prefix}(unknown key)");
- }
- }
- }
-}
-
-#[derive(Args, Debug)]
-pub(crate) struct SetCmd {
- /// The configuration key to set
- pub(crate) key: String,
-
- /// The value to set
- pub(crate) value: String,
-
- /// Store value as an explicit type
- #[arg(long = "type", short, value_enum, default_value_t = ValueType::Auto, value_name = "TYPE")]
- pub(crate) the_type: ValueType,
-}
-
-#[derive(ValueEnum, Debug, Clone, PartialEq, Eq)]
-pub(crate) enum ValueType {
- /// Automatically determine the type of the value
- Auto,
- /// Store value as a string
- String,
- /// Store value as a boolean
- Boolean,
- /// Store value as an integer
- Integer,
- /// Store the value as a float
- Float,
-}
-
-impl SetCmd {
- pub(crate) async fn run(self, _settings: &Settings) -> Result<()> {
- let key = self.key.trim();
- if key.is_empty() || key.contains(char::is_whitespace) {
- eyre::bail!("Config key must be non-empty and must not contain whitespace");
- }
-
- let config_file = Settings::get_config_path()?;
- let config_str = tokio::fs::read_to_string(&config_file).await?;
- let mut doc: DocumentMut = config_str.parse()?;
-
- // When using auto type detection, try to match the existing value's type
- // so we don't accidentally change e.g. "300" (string) to 300 (integer)
- let existing_type = detect_existing_type(&doc, key);
- let value = self.parse_value(existing_type.as_ref())?;
- set_deep_key(&mut doc, key, value)?;
-
- tokio::fs::write(&config_file, doc.to_string()).await?;
-
- Ok(())
- }
-
- fn parse_value(&self, existing_type: Option<&ValueType>) -> Result<Value> {
- let raw = &self.value;
-
- // Explicit --type takes priority, then existing value type, then auto-detect
- let effective_type = if self.the_type != ValueType::Auto {
- &self.the_type
- } else if let Some(existing) = existing_type {
- existing
- } else {
- &ValueType::Auto
- };
-
- match effective_type {
- ValueType::String => Ok(Value::from(raw.as_str())),
- ValueType::Boolean => {
- let b: bool = raw
- .parse()
- .map_err(|_| eyre::eyre!("invalid boolean value: {raw}"))?;
- Ok(Value::from(b))
- }
- ValueType::Integer => {
- let i: i64 = raw
- .parse()
- .map_err(|_| eyre::eyre!("invalid integer value: {raw}"))?;
- Ok(Value::from(i))
- }
- ValueType::Float => {
- let f: f64 = raw
- .parse()
- .map_err(|_| eyre::eyre!("invalid float value: {raw}"))?;
- Ok(Value::from(f))
- }
- ValueType::Auto => {
- if raw == "true" || raw == "false" {
- return Ok(Value::from(raw == "true"));
- }
- if let Ok(i) = raw.parse::<i64>() {
- return Ok(Value::from(i));
- }
- if let Ok(f) = raw.parse::<f64>() {
- return Ok(Value::from(f));
- }
- Ok(Value::from(raw.as_str()))
- }
- }
- }
-}
-
-#[derive(Args, Debug)]
-pub(crate) struct PrintCmd {
- /// Print the value of a specific key and all its children
- pub(crate) key: Option<String>,
-}
-
-impl PrintCmd {
- pub(crate) async fn run(&self, _settings: &Settings) -> Result<()> {
- let config_file = Settings::get_config_path()?;
- let config_str = tokio::fs::read_to_string(&config_file).await?;
- let doc = config_str.parse::<Document<_>>()?;
-
- if let Some(key) = &self.key {
- let current = get_deep_key(&doc, key);
-
- if let Some(current) = current {
- if current.is_table() || current.is_inline_table() {
- println!("[{key}]");
- dump_table(
- current
- .as_table_like()
- .expect("is_table()/is_inline_table() but no table"),
- "",
- &mut vec![key.clone()],
- )?;
- } else {
- println!("{}", current.to_string().trim().trim_matches('"'));
- }
- } else {
- println!("key not found");
- }
- } else {
- dump_table(doc.as_table(), "", &mut Vec::new())?;
- }
-
- Ok(())
- }
-}
-
-fn dump_table(table: &dyn TableLike, prefix: &str, stack: &mut Vec<String>) -> Result<()> {
- for (key, value) in table.iter() {
- if value.is_table() || value.is_inline_table() {
- stack.push(key.to_string());
-
- let table = value
- .as_table_like()
- .expect("is_table()/is_inline_table() but no table");
-
- println!("\n{}[{}]", prefix, stack.join("."));
-
- dump_table(table, prefix, stack)?;
-
- stack.pop();
- } else {
- println!("{prefix}{key} = {value}");
- }
- }
-
- Ok(())
-}
-
-fn get_deep_key<'doc>(doc: &'doc Document<String>, key: &str) -> Option<&'doc Item> {
- let parts = key.split('.');
- let mut current: Option<&Item> = Some(doc.as_item());
-
- for part in parts {
- current = current
- .and_then(|item| item.as_table_like())
- .and_then(|table| table.get(part));
- }
-
- current
-}
-
-/// Detect the TOML type of an existing key in the document, so `set` with auto
-/// type detection preserves the original type rather than guessing from the value string.
-fn detect_existing_type(doc: &DocumentMut, key: &str) -> Option<ValueType> {
- let parts: Vec<&str> = key.split('.').collect();
- let mut current: &dyn TableLike = doc.as_table();
-
- for &part in &parts[..parts.len().saturating_sub(1)] {
- current = current.get(part)?.as_table_like()?;
- }
-
- let last = parts.last()?;
- let v = current.get(last)?.as_value()?;
-
- if v.is_str() {
- Some(ValueType::String)
- } else if v.is_bool() {
- Some(ValueType::Boolean)
- } else if v.is_integer() {
- Some(ValueType::Integer)
- } else if v.is_float() {
- Some(ValueType::Float)
- } else {
- None
- }
-}
-
-fn set_deep_key(doc: &mut DocumentMut, key: &str, value: Value) -> Result<()> {
- let parts: Vec<&str> = key.split('.').collect();
-
- if parts.is_empty() {
- eyre::bail!("empty config key");
- }
-
- let mut current: &mut dyn TableLike = doc.as_table_mut();
-
- // Navigate/create intermediate tables
- for &part in &parts[..parts.len() - 1] {
- if !current.contains_key(part) {
- current.insert(part, Item::Table(Table::new()));
- }
- current = current
- .get_mut(part)
- .expect("just inserted or already exists")
- .as_table_like_mut()
- .ok_or_else(|| eyre::eyre!("'{}' exists but is not a table", part))?;
- }
-
- let last = *parts.last().unwrap();
-
- // Don't silently overwrite a table with a scalar value
- if let Some(existing) = current.get(last)
- && (existing.is_table() || existing.is_inline_table())
- {
- eyre::bail!(
- "'{}' is a table; use a dotted key like '{}.key' to set a value within it",
- key,
- key
- );
- }
-
- current.insert(last, Item::Value(value));
-
- Ok(())
-}