From a5e1d252877af0d7c453a209156205ce6ce9739d Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Mon, 4 Mar 2024 15:49:28 +0000 Subject: refactor: rename atuin-config to atuin-dotfiles (#1817) --- Cargo.lock | 4 +- Cargo.toml | 2 +- atuin-config/Cargo.toml | 23 --- atuin-config/src/lib.rs | 2 - atuin-config/src/shell.rs | 10 - atuin-config/src/shell/bash.rs | 12 -- atuin-config/src/shell/fish.rs | 12 -- atuin-config/src/shell/xonsh.rs | 12 -- atuin-config/src/shell/zsh.rs | 12 -- atuin-config/src/store.rs | 310 ----------------------------- atuin-dotfiles/Cargo.toml | 23 +++ atuin-dotfiles/src/lib.rs | 2 + atuin-dotfiles/src/shell.rs | 10 + atuin-dotfiles/src/shell/bash.rs | 12 ++ atuin-dotfiles/src/shell/fish.rs | 12 ++ atuin-dotfiles/src/shell/xonsh.rs | 12 ++ atuin-dotfiles/src/shell/zsh.rs | 12 ++ atuin-dotfiles/src/store.rs | 310 +++++++++++++++++++++++++++++ atuin/Cargo.toml | 2 +- atuin/src/command/client.rs | 6 +- atuin/src/command/client/config.rs | 21 -- atuin/src/command/client/config/alias.rs | 70 ------- atuin/src/command/client/dotfiles.rs | 21 ++ atuin/src/command/client/dotfiles/alias.rs | 70 +++++++ atuin/src/command/client/init.rs | 2 +- atuin/src/command/client/init/bash.rs | 4 +- atuin/src/command/client/init/fish.rs | 4 +- atuin/src/command/client/init/xonsh.rs | 4 +- atuin/src/command/client/init/zsh.rs | 4 +- 29 files changed, 500 insertions(+), 500 deletions(-) delete mode 100644 atuin-config/Cargo.toml delete mode 100644 atuin-config/src/lib.rs delete mode 100644 atuin-config/src/shell.rs delete mode 100644 atuin-config/src/shell/bash.rs delete mode 100644 atuin-config/src/shell/fish.rs delete mode 100644 atuin-config/src/shell/xonsh.rs delete mode 100644 atuin-config/src/shell/zsh.rs delete mode 100644 atuin-config/src/store.rs create mode 100644 atuin-dotfiles/Cargo.toml create mode 100644 atuin-dotfiles/src/lib.rs create mode 100644 atuin-dotfiles/src/shell.rs create mode 100644 atuin-dotfiles/src/shell/bash.rs create mode 100644 atuin-dotfiles/src/shell/fish.rs create mode 100644 atuin-dotfiles/src/shell/xonsh.rs create mode 100644 atuin-dotfiles/src/shell/zsh.rs create mode 100644 atuin-dotfiles/src/store.rs delete mode 100644 atuin/src/command/client/config.rs delete mode 100644 atuin/src/command/client/config/alias.rs create mode 100644 atuin/src/command/client/dotfiles.rs create mode 100644 atuin/src/command/client/dotfiles/alias.rs diff --git a/Cargo.lock b/Cargo.lock index 50ffc518..a27eab14 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -185,7 +185,7 @@ dependencies = [ "async-trait", "atuin-client", "atuin-common", - "atuin-config", + "atuin-dotfiles", "atuin-server", "atuin-server-postgres", "base64 0.21.7", @@ -291,7 +291,7 @@ dependencies = [ ] [[package]] -name = "atuin-config" +name = "atuin-dotfiles" version = "0.1.0" dependencies = [ "atuin-client", diff --git a/Cargo.toml b/Cargo.toml index 29ff07dd..5090889c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ members = [ "atuin-server-postgres", "atuin-server-database", "atuin-common", - "atuin-config", + "atuin-dotfiles", ] resolver = "2" diff --git a/atuin-config/Cargo.toml b/atuin-config/Cargo.toml deleted file mode 100644 index b6fffbf4..00000000 --- a/atuin-config/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "atuin-config" -edition = "2021" -version = "0.1.0" # intentionally not the same as the rest - -authors.workspace = true -rust-version.workspace = true -license.workspace = true -homepage.workspace = true -repository.workspace = true -readme.workspace = true - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -atuin-common = { path = "../atuin-common", version = "18.0.1" } -atuin-client = { path = "../atuin-client", version = "18.0.1" } - -eyre = { workspace = true } -tokio = { workspace = true } -rmp = { version = "0.8.11" } -rand = { workspace = true } -crypto_secretbox = "0.1.1" diff --git a/atuin-config/src/lib.rs b/atuin-config/src/lib.rs deleted file mode 100644 index 74daf8ef..00000000 --- a/atuin-config/src/lib.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod shell; -pub mod store; diff --git a/atuin-config/src/shell.rs b/atuin-config/src/shell.rs deleted file mode 100644 index a69a2d6b..00000000 --- a/atuin-config/src/shell.rs +++ /dev/null @@ -1,10 +0,0 @@ -pub mod bash; -pub mod fish; -pub mod xonsh; -pub mod zsh; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Alias { - pub name: String, - pub value: String, -} diff --git a/atuin-config/src/shell/bash.rs b/atuin-config/src/shell/bash.rs deleted file mode 100644 index c5bd87b2..00000000 --- a/atuin-config/src/shell/bash.rs +++ /dev/null @@ -1,12 +0,0 @@ -use super::Alias; - -// Configuration for bash -pub fn build(aliases: &[Alias]) -> String { - let mut config = String::new(); - - for alias in aliases { - config.push_str(&format!("alias {}='{}'\n", alias.name, alias.value)); - } - - config -} diff --git a/atuin-config/src/shell/fish.rs b/atuin-config/src/shell/fish.rs deleted file mode 100644 index c6277f34..00000000 --- a/atuin-config/src/shell/fish.rs +++ /dev/null @@ -1,12 +0,0 @@ -use super::Alias; - -// Configuration for fish -pub fn build(aliases: &[Alias]) -> String { - let mut config = String::new(); - - for alias in aliases { - config.push_str(&format!("alias {}='{}'\n", alias.name, alias.value)); - } - - config -} diff --git a/atuin-config/src/shell/xonsh.rs b/atuin-config/src/shell/xonsh.rs deleted file mode 100644 index 8b61ff4c..00000000 --- a/atuin-config/src/shell/xonsh.rs +++ /dev/null @@ -1,12 +0,0 @@ -use super::Alias; - -// Configuration for xonsh -pub fn build(aliases: &[Alias]) -> String { - let mut config = String::new(); - - for alias in aliases { - config.push_str(&format!("aliases['{}'] ='{}'\n", alias.name, alias.value)); - } - - config -} diff --git a/atuin-config/src/shell/zsh.rs b/atuin-config/src/shell/zsh.rs deleted file mode 100644 index 6f81ed55..00000000 --- a/atuin-config/src/shell/zsh.rs +++ /dev/null @@ -1,12 +0,0 @@ -use super::Alias; - -// Configuration for zsh -pub fn build(aliases: &[Alias]) -> String { - let mut config = String::new(); - - for alias in aliases { - config.push_str(&format!("alias {}='{}'\n", alias.name, alias.value)); - } - - config -} diff --git a/atuin-config/src/store.rs b/atuin-config/src/store.rs deleted file mode 100644 index 96e0fb32..00000000 --- a/atuin-config/src/store.rs +++ /dev/null @@ -1,310 +0,0 @@ -use std::collections::BTreeMap; - -use atuin_client::record::sqlite_store::SqliteStore; -// Sync aliases -// This will be noticeable similar to the kv store, though I expect the two shall diverge -// While we will support a range of shell config, I'd rather have a larger number of small records -// + stores, rather than one mega config store. -use atuin_common::record::{DecryptedData, Host, HostId}; -use eyre::{bail, ensure, eyre, Result}; - -use atuin_client::record::encryption::PASETO_V4; -use atuin_client::record::store::Store; - -use crate::shell::Alias; - -const CONFIG_SHELL_ALIAS_VERSION: &str = "v0"; -const CONFIG_SHELL_ALIAS_TAG: &str = "config-shell-alias"; -const CONFIG_SHELL_ALIAS_FIELD_MAX_LEN: usize = 20000; // 20kb max total len, way more than should be needed. - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum AliasRecord { - Create(Alias), // create a full record - Delete(String), // delete by name -} - -impl AliasRecord { - pub fn serialize(&self) -> Result { - use rmp::encode; - - let mut output = vec![]; - - match self { - AliasRecord::Create(alias) => { - encode::write_u8(&mut output, 0)?; // create - encode::write_array_len(&mut output, 2)?; // 2 fields - - encode::write_str(&mut output, alias.name.as_str())?; - encode::write_str(&mut output, alias.value.as_str())?; - } - AliasRecord::Delete(name) => { - encode::write_u8(&mut output, 1)?; // delete - encode::write_array_len(&mut output, 1)?; // 1 field - - encode::write_str(&mut output, name.as_str())?; - } - } - - Ok(DecryptedData(output)) - } - - pub fn deserialize(data: &DecryptedData, version: &str) -> Result { - use rmp::decode; - - fn error_report(err: E) -> eyre::Report { - eyre!("{err:?}") - } - - match version { - CONFIG_SHELL_ALIAS_VERSION => { - let mut bytes = decode::Bytes::new(&data.0); - - let record_type = decode::read_u8(&mut bytes).map_err(error_report)?; - - match record_type { - // create - 0 => { - let nfields = decode::read_array_len(&mut bytes).map_err(error_report)?; - ensure!( - nfields == 2, - "too many entries in v0 shell alias create record" - ); - - let bytes = bytes.remaining_slice(); - - let (key, bytes) = - decode::read_str_from_slice(bytes).map_err(error_report)?; - let (value, bytes) = - decode::read_str_from_slice(bytes).map_err(error_report)?; - - if !bytes.is_empty() { - bail!("trailing bytes in encoded shell alias record. malformed") - } - - Ok(AliasRecord::Create(Alias { - name: key.to_owned(), - value: value.to_owned(), - })) - } - - // delete - 1 => { - let nfields = decode::read_array_len(&mut bytes).map_err(error_report)?; - ensure!( - nfields == 1, - "too many entries in v0 shell alias delete record" - ); - - let bytes = bytes.remaining_slice(); - - let (key, bytes) = - decode::read_str_from_slice(bytes).map_err(error_report)?; - - if !bytes.is_empty() { - bail!("trailing bytes in encoded shell alias record. malformed") - } - - Ok(AliasRecord::Delete(key.to_owned())) - } - - n => { - bail!("unknown AliasRecord type {n}") - } - } - } - _ => { - bail!("unknown version {version:?}") - } - } - } -} - -#[derive(Debug, Clone)] -pub struct AliasStore { - pub store: SqliteStore, - pub host_id: HostId, - pub encryption_key: [u8; 32], -} - -impl AliasStore { - // will want to init the actual kv store when that is done - pub fn new(store: SqliteStore, host_id: HostId, encryption_key: [u8; 32]) -> AliasStore { - AliasStore { - store, - host_id, - encryption_key, - } - } - - pub async fn set(&self, name: &str, value: &str) -> Result<()> { - if name.len() + value.len() > CONFIG_SHELL_ALIAS_FIELD_MAX_LEN { - return Err(eyre!( - "alias record too large: max len {} bytes", - CONFIG_SHELL_ALIAS_FIELD_MAX_LEN - )); - } - - let record = AliasRecord::Create(Alias { - name: name.to_string(), - value: value.to_string(), - }); - - let bytes = record.serialize()?; - - let idx = self - .store - .last(self.host_id, CONFIG_SHELL_ALIAS_TAG) - .await? - .map_or(0, |entry| entry.idx + 1); - - let record = atuin_common::record::Record::builder() - .host(Host::new(self.host_id)) - .version(CONFIG_SHELL_ALIAS_VERSION.to_string()) - .tag(CONFIG_SHELL_ALIAS_TAG.to_string()) - .idx(idx) - .data(bytes) - .build(); - - self.store - .push(&record.encrypt::(&self.encryption_key)) - .await?; - - Ok(()) - } - - pub async fn delete(&self, name: &str) -> Result<()> { - if name.len() > CONFIG_SHELL_ALIAS_FIELD_MAX_LEN { - return Err(eyre!( - "alias record too large: max len {} bytes", - CONFIG_SHELL_ALIAS_FIELD_MAX_LEN - )); - } - - let record = AliasRecord::Delete(name.to_string()); - - let bytes = record.serialize()?; - - let idx = self - .store - .last(self.host_id, CONFIG_SHELL_ALIAS_TAG) - .await? - .map_or(0, |entry| entry.idx + 1); - - let record = atuin_common::record::Record::builder() - .host(Host::new(self.host_id)) - .version(CONFIG_SHELL_ALIAS_VERSION.to_string()) - .tag(CONFIG_SHELL_ALIAS_TAG.to_string()) - .idx(idx) - .data(bytes) - .build(); - - self.store - .push(&record.encrypt::(&self.encryption_key)) - .await?; - - Ok(()) - } - - pub async fn aliases(&self) -> Result> { - let mut build = BTreeMap::new(); - - // this is sorted, oldest to newest - let tagged = self.store.all_tagged(CONFIG_SHELL_ALIAS_TAG).await?; - - for record in tagged { - let version = record.version.clone(); - - let decrypted = match version.as_str() { - CONFIG_SHELL_ALIAS_VERSION => record.decrypt::(&self.encryption_key)?, - version => bail!("unknown version {version:?}"), - }; - - let ar = AliasRecord::deserialize(&decrypted.data, version.as_str())?; - - match ar { - AliasRecord::Create(a) => { - build.insert(a.name.clone(), a); - } - AliasRecord::Delete(d) => { - build.remove(&d); - } - } - } - - Ok(build.into_values().collect()) - } -} - -#[cfg(test)] -pub(crate) fn test_sqlite_store_timeout() -> f64 { - std::env::var("ATUIN_TEST_SQLITE_STORE_TIMEOUT") - .ok() - .and_then(|x| x.parse().ok()) - .unwrap_or(0.1) -} - -#[cfg(test)] -mod tests { - use rand::rngs::OsRng; - - use atuin_client::record::sqlite_store::SqliteStore; - - use crate::shell::Alias; - - use super::{test_sqlite_store_timeout, AliasRecord, AliasStore, CONFIG_SHELL_ALIAS_VERSION}; - use crypto_secretbox::{KeyInit, XSalsa20Poly1305}; - - #[test] - fn encode_decode() { - let record = Alias { - name: "k".to_owned(), - value: "kubectl".to_owned(), - }; - let record = AliasRecord::Create(record); - - let snapshot = [204, 0, 146, 161, 107, 167, 107, 117, 98, 101, 99, 116, 108]; - - let encoded = record.serialize().unwrap(); - let decoded = AliasRecord::deserialize(&encoded, CONFIG_SHELL_ALIAS_VERSION).unwrap(); - - assert_eq!(encoded.0, &snapshot); - assert_eq!(decoded, record); - } - - #[tokio::test] - async fn build_aliases() { - let store = SqliteStore::new(":memory:", test_sqlite_store_timeout()) - .await - .unwrap(); - let key: [u8; 32] = XSalsa20Poly1305::generate_key(&mut OsRng).into(); - let host_id = atuin_common::record::HostId(atuin_common::utils::uuid_v7()); - - let alias = AliasStore::new(store, host_id, key); - - alias.set("k", "kubectl").await.unwrap(); - - alias.set("gp", "git push").await.unwrap(); - - let mut aliases = alias.aliases().await.unwrap(); - - aliases.sort_by_key(|a| a.name.clone()); - - assert_eq!(aliases.len(), 2); - - assert_eq!( - aliases[0], - Alias { - name: String::from("gp"), - value: String::from("git push") - } - ); - - assert_eq!( - aliases[1], - Alias { - name: String::from("k"), - value: String::from("kubectl") - } - ); - } -} diff --git a/atuin-dotfiles/Cargo.toml b/atuin-dotfiles/Cargo.toml new file mode 100644 index 00000000..1305f521 --- /dev/null +++ b/atuin-dotfiles/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "atuin-dotfiles" +edition = "2021" +version = "0.1.0" # intentionally not the same as the rest + +authors.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +readme.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +atuin-common = { path = "../atuin-common", version = "18.0.1" } +atuin-client = { path = "../atuin-client", version = "18.0.1" } + +eyre = { workspace = true } +tokio = { workspace = true } +rmp = { version = "0.8.11" } +rand = { workspace = true } +crypto_secretbox = "0.1.1" diff --git a/atuin-dotfiles/src/lib.rs b/atuin-dotfiles/src/lib.rs new file mode 100644 index 00000000..74daf8ef --- /dev/null +++ b/atuin-dotfiles/src/lib.rs @@ -0,0 +1,2 @@ +pub mod shell; +pub mod store; diff --git a/atuin-dotfiles/src/shell.rs b/atuin-dotfiles/src/shell.rs new file mode 100644 index 00000000..a69a2d6b --- /dev/null +++ b/atuin-dotfiles/src/shell.rs @@ -0,0 +1,10 @@ +pub mod bash; +pub mod fish; +pub mod xonsh; +pub mod zsh; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Alias { + pub name: String, + pub value: String, +} diff --git a/atuin-dotfiles/src/shell/bash.rs b/atuin-dotfiles/src/shell/bash.rs new file mode 100644 index 00000000..c5bd87b2 --- /dev/null +++ b/atuin-dotfiles/src/shell/bash.rs @@ -0,0 +1,12 @@ +use super::Alias; + +// Configuration for bash +pub fn build(aliases: &[Alias]) -> String { + let mut config = String::new(); + + for alias in aliases { + config.push_str(&format!("alias {}='{}'\n", alias.name, alias.value)); + } + + config +} diff --git a/atuin-dotfiles/src/shell/fish.rs b/atuin-dotfiles/src/shell/fish.rs new file mode 100644 index 00000000..c6277f34 --- /dev/null +++ b/atuin-dotfiles/src/shell/fish.rs @@ -0,0 +1,12 @@ +use super::Alias; + +// Configuration for fish +pub fn build(aliases: &[Alias]) -> String { + let mut config = String::new(); + + for alias in aliases { + config.push_str(&format!("alias {}='{}'\n", alias.name, alias.value)); + } + + config +} diff --git a/atuin-dotfiles/src/shell/xonsh.rs b/atuin-dotfiles/src/shell/xonsh.rs new file mode 100644 index 00000000..8b61ff4c --- /dev/null +++ b/atuin-dotfiles/src/shell/xonsh.rs @@ -0,0 +1,12 @@ +use super::Alias; + +// Configuration for xonsh +pub fn build(aliases: &[Alias]) -> String { + let mut config = String::new(); + + for alias in aliases { + config.push_str(&format!("aliases['{}'] ='{}'\n", alias.name, alias.value)); + } + + config +} diff --git a/atuin-dotfiles/src/shell/zsh.rs b/atuin-dotfiles/src/shell/zsh.rs new file mode 100644 index 00000000..6f81ed55 --- /dev/null +++ b/atuin-dotfiles/src/shell/zsh.rs @@ -0,0 +1,12 @@ +use super::Alias; + +// Configuration for zsh +pub fn build(aliases: &[Alias]) -> String { + let mut config = String::new(); + + for alias in aliases { + config.push_str(&format!("alias {}='{}'\n", alias.name, alias.value)); + } + + config +} diff --git a/atuin-dotfiles/src/store.rs b/atuin-dotfiles/src/store.rs new file mode 100644 index 00000000..96e0fb32 --- /dev/null +++ b/atuin-dotfiles/src/store.rs @@ -0,0 +1,310 @@ +use std::collections::BTreeMap; + +use atuin_client::record::sqlite_store::SqliteStore; +// Sync aliases +// This will be noticeable similar to the kv store, though I expect the two shall diverge +// While we will support a range of shell config, I'd rather have a larger number of small records +// + stores, rather than one mega config store. +use atuin_common::record::{DecryptedData, Host, HostId}; +use eyre::{bail, ensure, eyre, Result}; + +use atuin_client::record::encryption::PASETO_V4; +use atuin_client::record::store::Store; + +use crate::shell::Alias; + +const CONFIG_SHELL_ALIAS_VERSION: &str = "v0"; +const CONFIG_SHELL_ALIAS_TAG: &str = "config-shell-alias"; +const CONFIG_SHELL_ALIAS_FIELD_MAX_LEN: usize = 20000; // 20kb max total len, way more than should be needed. + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum AliasRecord { + Create(Alias), // create a full record + Delete(String), // delete by name +} + +impl AliasRecord { + pub fn serialize(&self) -> Result { + use rmp::encode; + + let mut output = vec![]; + + match self { + AliasRecord::Create(alias) => { + encode::write_u8(&mut output, 0)?; // create + encode::write_array_len(&mut output, 2)?; // 2 fields + + encode::write_str(&mut output, alias.name.as_str())?; + encode::write_str(&mut output, alias.value.as_str())?; + } + AliasRecord::Delete(name) => { + encode::write_u8(&mut output, 1)?; // delete + encode::write_array_len(&mut output, 1)?; // 1 field + + encode::write_str(&mut output, name.as_str())?; + } + } + + Ok(DecryptedData(output)) + } + + pub fn deserialize(data: &DecryptedData, version: &str) -> Result { + use rmp::decode; + + fn error_report(err: E) -> eyre::Report { + eyre!("{err:?}") + } + + match version { + CONFIG_SHELL_ALIAS_VERSION => { + let mut bytes = decode::Bytes::new(&data.0); + + let record_type = decode::read_u8(&mut bytes).map_err(error_report)?; + + match record_type { + // create + 0 => { + let nfields = decode::read_array_len(&mut bytes).map_err(error_report)?; + ensure!( + nfields == 2, + "too many entries in v0 shell alias create record" + ); + + let bytes = bytes.remaining_slice(); + + let (key, bytes) = + decode::read_str_from_slice(bytes).map_err(error_report)?; + let (value, bytes) = + decode::read_str_from_slice(bytes).map_err(error_report)?; + + if !bytes.is_empty() { + bail!("trailing bytes in encoded shell alias record. malformed") + } + + Ok(AliasRecord::Create(Alias { + name: key.to_owned(), + value: value.to_owned(), + })) + } + + // delete + 1 => { + let nfields = decode::read_array_len(&mut bytes).map_err(error_report)?; + ensure!( + nfields == 1, + "too many entries in v0 shell alias delete record" + ); + + let bytes = bytes.remaining_slice(); + + let (key, bytes) = + decode::read_str_from_slice(bytes).map_err(error_report)?; + + if !bytes.is_empty() { + bail!("trailing bytes in encoded shell alias record. malformed") + } + + Ok(AliasRecord::Delete(key.to_owned())) + } + + n => { + bail!("unknown AliasRecord type {n}") + } + } + } + _ => { + bail!("unknown version {version:?}") + } + } + } +} + +#[derive(Debug, Clone)] +pub struct AliasStore { + pub store: SqliteStore, + pub host_id: HostId, + pub encryption_key: [u8; 32], +} + +impl AliasStore { + // will want to init the actual kv store when that is done + pub fn new(store: SqliteStore, host_id: HostId, encryption_key: [u8; 32]) -> AliasStore { + AliasStore { + store, + host_id, + encryption_key, + } + } + + pub async fn set(&self, name: &str, value: &str) -> Result<()> { + if name.len() + value.len() > CONFIG_SHELL_ALIAS_FIELD_MAX_LEN { + return Err(eyre!( + "alias record too large: max len {} bytes", + CONFIG_SHELL_ALIAS_FIELD_MAX_LEN + )); + } + + let record = AliasRecord::Create(Alias { + name: name.to_string(), + value: value.to_string(), + }); + + let bytes = record.serialize()?; + + let idx = self + .store + .last(self.host_id, CONFIG_SHELL_ALIAS_TAG) + .await? + .map_or(0, |entry| entry.idx + 1); + + let record = atuin_common::record::Record::builder() + .host(Host::new(self.host_id)) + .version(CONFIG_SHELL_ALIAS_VERSION.to_string()) + .tag(CONFIG_SHELL_ALIAS_TAG.to_string()) + .idx(idx) + .data(bytes) + .build(); + + self.store + .push(&record.encrypt::(&self.encryption_key)) + .await?; + + Ok(()) + } + + pub async fn delete(&self, name: &str) -> Result<()> { + if name.len() > CONFIG_SHELL_ALIAS_FIELD_MAX_LEN { + return Err(eyre!( + "alias record too large: max len {} bytes", + CONFIG_SHELL_ALIAS_FIELD_MAX_LEN + )); + } + + let record = AliasRecord::Delete(name.to_string()); + + let bytes = record.serialize()?; + + let idx = self + .store + .last(self.host_id, CONFIG_SHELL_ALIAS_TAG) + .await? + .map_or(0, |entry| entry.idx + 1); + + let record = atuin_common::record::Record::builder() + .host(Host::new(self.host_id)) + .version(CONFIG_SHELL_ALIAS_VERSION.to_string()) + .tag(CONFIG_SHELL_ALIAS_TAG.to_string()) + .idx(idx) + .data(bytes) + .build(); + + self.store + .push(&record.encrypt::(&self.encryption_key)) + .await?; + + Ok(()) + } + + pub async fn aliases(&self) -> Result> { + let mut build = BTreeMap::new(); + + // this is sorted, oldest to newest + let tagged = self.store.all_tagged(CONFIG_SHELL_ALIAS_TAG).await?; + + for record in tagged { + let version = record.version.clone(); + + let decrypted = match version.as_str() { + CONFIG_SHELL_ALIAS_VERSION => record.decrypt::(&self.encryption_key)?, + version => bail!("unknown version {version:?}"), + }; + + let ar = AliasRecord::deserialize(&decrypted.data, version.as_str())?; + + match ar { + AliasRecord::Create(a) => { + build.insert(a.name.clone(), a); + } + AliasRecord::Delete(d) => { + build.remove(&d); + } + } + } + + Ok(build.into_values().collect()) + } +} + +#[cfg(test)] +pub(crate) fn test_sqlite_store_timeout() -> f64 { + std::env::var("ATUIN_TEST_SQLITE_STORE_TIMEOUT") + .ok() + .and_then(|x| x.parse().ok()) + .unwrap_or(0.1) +} + +#[cfg(test)] +mod tests { + use rand::rngs::OsRng; + + use atuin_client::record::sqlite_store::SqliteStore; + + use crate::shell::Alias; + + use super::{test_sqlite_store_timeout, AliasRecord, AliasStore, CONFIG_SHELL_ALIAS_VERSION}; + use crypto_secretbox::{KeyInit, XSalsa20Poly1305}; + + #[test] + fn encode_decode() { + let record = Alias { + name: "k".to_owned(), + value: "kubectl".to_owned(), + }; + let record = AliasRecord::Create(record); + + let snapshot = [204, 0, 146, 161, 107, 167, 107, 117, 98, 101, 99, 116, 108]; + + let encoded = record.serialize().unwrap(); + let decoded = AliasRecord::deserialize(&encoded, CONFIG_SHELL_ALIAS_VERSION).unwrap(); + + assert_eq!(encoded.0, &snapshot); + assert_eq!(decoded, record); + } + + #[tokio::test] + async fn build_aliases() { + let store = SqliteStore::new(":memory:", test_sqlite_store_timeout()) + .await + .unwrap(); + let key: [u8; 32] = XSalsa20Poly1305::generate_key(&mut OsRng).into(); + let host_id = atuin_common::record::HostId(atuin_common::utils::uuid_v7()); + + let alias = AliasStore::new(store, host_id, key); + + alias.set("k", "kubectl").await.unwrap(); + + alias.set("gp", "git push").await.unwrap(); + + let mut aliases = alias.aliases().await.unwrap(); + + aliases.sort_by_key(|a| a.name.clone()); + + assert_eq!(aliases.len(), 2); + + assert_eq!( + aliases[0], + Alias { + name: String::from("gp"), + value: String::from("git push") + } + ); + + assert_eq!( + aliases[1], + Alias { + name: String::from("k"), + value: String::from("kubectl") + } + ); + } +} diff --git a/atuin/Cargo.toml b/atuin/Cargo.toml index b53a8b5b..3eac0746 100644 --- a/atuin/Cargo.toml +++ b/atuin/Cargo.toml @@ -45,7 +45,7 @@ atuin-server-postgres = { path = "../atuin-server-postgres", version = "18.0.2", atuin-server = { path = "../atuin-server", version = "18.0.2", optional = true } atuin-client = { path = "../atuin-client", version = "18.0.2", optional = true, default-features = false } atuin-common = { path = "../atuin-common", version = "18.0.2" } -atuin-config = { path = "../atuin-config", version = "0.1.0" } +atuin-dotfiles = { path = "../atuin-dotfiles", version = "0.1.0" } log = { workspace = true } env_logger = "0.11.2" diff --git a/atuin/src/command/client.rs b/atuin/src/command/client.rs index ec2a03fb..732df7ff 100644 --- a/atuin/src/command/client.rs +++ b/atuin/src/command/client.rs @@ -12,9 +12,9 @@ mod sync; #[cfg(feature = "sync")] mod account; -mod config; mod default_config; mod doctor; +mod dotfiles; mod history; mod import; mod init; @@ -54,7 +54,7 @@ pub enum Cmd { Store(store::Cmd), #[command(subcommand)] - Config(config::Cmd), + Dotfiles(dotfiles::Cmd), #[command()] Init(init::Cmd), @@ -113,7 +113,7 @@ impl Cmd { Self::Store(store) => store.run(&settings, &db, sqlite_store).await, - Self::Config(config) => config.run(&settings, sqlite_store).await, + Self::Dotfiles(dotfiles) => dotfiles.run(&settings, sqlite_store).await, Self::Init(init) => init.run(&settings).await, diff --git a/atuin/src/command/client/config.rs b/atuin/src/command/client/config.rs deleted file mode 100644 index a3967719..00000000 --- a/atuin/src/command/client/config.rs +++ /dev/null @@ -1,21 +0,0 @@ -use clap::Subcommand; -use eyre::Result; - -use atuin_client::{record::sqlite_store::SqliteStore, settings::Settings}; - -mod alias; - -#[derive(Subcommand, Debug)] -#[command(infer_subcommands = true)] -pub enum Cmd { - #[command(subcommand)] - Alias(alias::Cmd), -} - -impl Cmd { - pub async fn run(self, settings: &Settings, store: SqliteStore) -> Result<()> { - match self { - Self::Alias(cmd) => cmd.run(settings, store).await, - } - } -} diff --git a/atuin/src/command/client/config/alias.rs b/atuin/src/command/client/config/alias.rs deleted file mode 100644 index 77dc8123..00000000 --- a/atuin/src/command/client/config/alias.rs +++ /dev/null @@ -1,70 +0,0 @@ -use clap::Subcommand; -use eyre::{Context, Result}; - -use atuin_client::{encryption, record::sqlite_store::SqliteStore, settings::Settings}; - -use atuin_config::{shell::Alias, store::AliasStore}; - -#[derive(Subcommand, Debug)] -#[command(infer_subcommands = true)] -pub enum Cmd { - Set { name: String, value: String }, - Delete { name: String }, - List, -} - -impl Cmd { - async fn set(&self, store: AliasStore, name: String, value: String) -> Result<()> { - let aliases = store.aliases().await?; - let found: Vec = aliases.into_iter().filter(|a| a.name == name).collect(); - - if found.is_empty() { - println!("Aliasing '{name}={value}'."); - } else { - println!( - "Overwriting alias '{name}={}' with '{name}={value}'.", - found[0].value - ); - } - - store.set(&name, &value).await?; - - Ok(()) - } - - async fn list(&self, store: AliasStore) -> Result<()> { - let aliases = store.aliases().await?; - - for i in aliases { - println!("{}={}", i.name, i.value); - } - - Ok(()) - } - - async fn delete(&self, store: AliasStore, name: String) -> Result<()> { - let mut aliases = store.aliases().await?.into_iter(); - if let Some(alias) = aliases.find(|alias| alias.name == name) { - println!("Deleting '{name}={}'.", alias.value); - store.delete(&name).await?; - } else { - eprintln!("Cannot delete '{name}': Alias not set."); - }; - Ok(()) - } - - pub async fn run(&self, settings: &Settings, store: SqliteStore) -> Result<()> { - let encryption_key: [u8; 32] = encryption::load_key(settings) - .context("could not load encryption key")? - .into(); - let host_id = Settings::host_id().expect("failed to get host_id"); - - let alias_store = AliasStore::new(store, host_id, encryption_key); - - match self { - Self::Set { name, value } => self.set(alias_store, name.clone(), value.clone()).await, - Self::Delete { name } => self.delete(alias_store, name.clone()).await, - Self::List => self.list(alias_store).await, - } - } -} diff --git a/atuin/src/command/client/dotfiles.rs b/atuin/src/command/client/dotfiles.rs new file mode 100644 index 00000000..a3967719 --- /dev/null +++ b/atuin/src/command/client/dotfiles.rs @@ -0,0 +1,21 @@ +use clap::Subcommand; +use eyre::Result; + +use atuin_client::{record::sqlite_store::SqliteStore, settings::Settings}; + +mod alias; + +#[derive(Subcommand, Debug)] +#[command(infer_subcommands = true)] +pub enum Cmd { + #[command(subcommand)] + Alias(alias::Cmd), +} + +impl Cmd { + pub async fn run(self, settings: &Settings, store: SqliteStore) -> Result<()> { + match self { + Self::Alias(cmd) => cmd.run(settings, store).await, + } + } +} diff --git a/atuin/src/command/client/dotfiles/alias.rs b/atuin/src/command/client/dotfiles/alias.rs new file mode 100644 index 00000000..5d067d47 --- /dev/null +++ b/atuin/src/command/client/dotfiles/alias.rs @@ -0,0 +1,70 @@ +use clap::Subcommand; +use eyre::{Context, Result}; + +use atuin_client::{encryption, record::sqlite_store::SqliteStore, settings::Settings}; + +use atuin_dotfiles::{shell::Alias, store::AliasStore}; + +#[derive(Subcommand, Debug)] +#[command(infer_subcommands = true)] +pub enum Cmd { + Set { name: String, value: String }, + Delete { name: String }, + List, +} + +impl Cmd { + async fn set(&self, store: AliasStore, name: String, value: String) -> Result<()> { + let aliases = store.aliases().await?; + let found: Vec = aliases.into_iter().filter(|a| a.name == name).collect(); + + if found.is_empty() { + println!("Aliasing '{name}={value}'."); + } else { + println!( + "Overwriting alias '{name}={}' with '{name}={value}'.", + found[0].value + ); + } + + store.set(&name, &value).await?; + + Ok(()) + } + + async fn list(&self, store: AliasStore) -> Result<()> { + let aliases = store.aliases().await?; + + for i in aliases { + println!("{}={}", i.name, i.value); + } + + Ok(()) + } + + async fn delete(&self, store: AliasStore, name: String) -> Result<()> { + let mut aliases = store.aliases().await?.into_iter(); + if let Some(alias) = aliases.find(|alias| alias.name == name) { + println!("Deleting '{name}={}'.", alias.value); + store.delete(&name).await?; + } else { + eprintln!("Cannot delete '{name}': Alias not set."); + }; + Ok(()) + } + + pub async fn run(&self, settings: &Settings, store: SqliteStore) -> Result<()> { + let encryption_key: [u8; 32] = encryption::load_key(settings) + .context("could not load encryption key")? + .into(); + let host_id = Settings::host_id().expect("failed to get host_id"); + + let alias_store = AliasStore::new(store, host_id, encryption_key); + + match self { + Self::Set { name, value } => self.set(alias_store, name.clone(), value.clone()).await, + Self::Delete { name } => self.delete(alias_store, name.clone()).await, + Self::List => self.list(alias_store).await, + } + } +} diff --git a/atuin/src/command/client/init.rs b/atuin/src/command/client/init.rs index d9bad148..ded6e712 100644 --- a/atuin/src/command/client/init.rs +++ b/atuin/src/command/client/init.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; use atuin_client::{encryption, record::sqlite_store::SqliteStore, settings::Settings}; -use atuin_config::store::AliasStore; +use atuin_dotfiles::store::AliasStore; use clap::{Parser, ValueEnum}; use eyre::{Result, WrapErr}; diff --git a/atuin/src/command/client/init/bash.rs b/atuin/src/command/client/init/bash.rs index ac2bfb00..40f8adc6 100644 --- a/atuin/src/command/client/init/bash.rs +++ b/atuin/src/command/client/init/bash.rs @@ -1,4 +1,4 @@ -use atuin_config::store::AliasStore; +use atuin_dotfiles::store::AliasStore; use eyre::Result; pub async fn init(store: AliasStore, disable_up_arrow: bool, disable_ctrl_r: bool) -> Result<()> { @@ -6,7 +6,7 @@ pub async fn init(store: AliasStore, disable_up_arrow: bool, disable_ctrl_r: boo let aliases = store.aliases().await?; - let aliases = atuin_config::shell::bash::build(&aliases[..]); + let aliases = atuin_dotfiles::shell::bash::build(&aliases[..]); let (bind_ctrl_r, bind_up_arrow) = if std::env::var("ATUIN_NOBIND").is_ok() { (false, false) diff --git a/atuin/src/command/client/init/fish.rs b/atuin/src/command/client/init/fish.rs index 9274b350..01f5d2bd 100644 --- a/atuin/src/command/client/init/fish.rs +++ b/atuin/src/command/client/init/fish.rs @@ -1,4 +1,4 @@ -use atuin_config::store::AliasStore; +use atuin_dotfiles::store::AliasStore; use eyre::Result; pub async fn init(store: AliasStore, disable_up_arrow: bool, disable_ctrl_r: bool) -> Result<()> { @@ -34,7 +34,7 @@ bind -M insert \e\[A _atuin_bind_up"; } let aliases = store.aliases().await?; - let aliases = atuin_config::shell::fish::build(&aliases[..]); + let aliases = atuin_dotfiles::shell::fish::build(&aliases[..]); println!("{aliases}"); diff --git a/atuin/src/command/client/init/xonsh.rs b/atuin/src/command/client/init/xonsh.rs index 4e5929ed..ffac7fec 100644 --- a/atuin/src/command/client/init/xonsh.rs +++ b/atuin/src/command/client/init/xonsh.rs @@ -1,4 +1,4 @@ -use atuin_config::store::AliasStore; +use atuin_dotfiles::store::AliasStore; use eyre::Result; pub async fn init(store: AliasStore, disable_up_arrow: bool, disable_ctrl_r: bool) -> Result<()> { @@ -20,7 +20,7 @@ pub async fn init(store: AliasStore, disable_up_arrow: bool, disable_ctrl_r: boo println!("{base}"); let aliases = store.aliases().await?; - let aliases = atuin_config::shell::xonsh::build(&aliases[..]); + let aliases = atuin_dotfiles::shell::xonsh::build(&aliases[..]); println!("{aliases}"); diff --git a/atuin/src/command/client/init/zsh.rs b/atuin/src/command/client/init/zsh.rs index 9044c835..9c4dbe43 100644 --- a/atuin/src/command/client/init/zsh.rs +++ b/atuin/src/command/client/init/zsh.rs @@ -1,4 +1,4 @@ -use atuin_config::store::AliasStore; +use atuin_dotfiles::store::AliasStore; use eyre::Result; pub async fn init(store: AliasStore, disable_up_arrow: bool, disable_ctrl_r: bool) -> Result<()> { @@ -28,7 +28,7 @@ bindkey -M vicmd 'k' atuin-up-search-vicmd"; } let aliases = store.aliases().await?; - let aliases = atuin_config::shell::zsh::build(&aliases[..]); + let aliases = atuin_dotfiles::shell::zsh::build(&aliases[..]); println!("{aliases}"); -- cgit v1.3.1