aboutsummaryrefslogtreecommitdiffstats
path: root/crates/atuin-dotfiles/src/shell
diff options
context:
space:
mode:
authorEllie Huxtable <ellie@elliehuxtable.com>2024-04-25 07:52:23 +0100
committerGitHub <noreply@github.com>2024-04-25 07:52:23 +0100
commitd020c815c121f7f28cfcf1419f94109851fdc422 (patch)
tree8b9476849f35b9614352132c9cc0835662f3d90f /crates/atuin-dotfiles/src/shell
parentfix(ci): release workflow (#1978) (diff)
downloadatuin-d020c815c121f7f28cfcf1419f94109851fdc422.zip
feat(dotfiles): support syncing shell/env vars (#1977)
There's a bunch of duplication here! I'd also like to support syncing shell "snippets", aka just bits of shell config that don't fit into the structure here. Potentially special handling for PATH too. Rather than come up with some abstraction in the beginning, which inevitably will not fit future uses, I'm duplicating code _for now_. Once all the functionality is there, I can tidy things up and sort a proper abstraction out. Something in atuin-client for map/list style synced structures would probably work best.
Diffstat (limited to '')
-rw-r--r--crates/atuin-dotfiles/src/shell.rs65
-rw-r--r--crates/atuin-dotfiles/src/shell/bash.rs33
-rw-r--r--crates/atuin-dotfiles/src/shell/fish.rs33
-rw-r--r--crates/atuin-dotfiles/src/shell/xonsh.rs33
-rw-r--r--crates/atuin-dotfiles/src/shell/zsh.rs33
5 files changed, 186 insertions, 11 deletions
diff --git a/crates/atuin-dotfiles/src/shell.rs b/crates/atuin-dotfiles/src/shell.rs
index a5cb0b7a..d4cacf8f 100644
--- a/crates/atuin-dotfiles/src/shell.rs
+++ b/crates/atuin-dotfiles/src/shell.rs
@@ -1,4 +1,5 @@
-use eyre::Result;
+use eyre::{ensure, eyre, Result};
+use rmp::{decode, encode};
use serde::Serialize;
use atuin_common::shell::{Shell, ShellError};
@@ -16,6 +17,64 @@ pub struct Alias {
pub value: String,
}
+#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
+pub struct Var {
+ pub name: String,
+ pub value: String,
+
+ // False? This is a _shell var_
+ // True? This is an _env var_
+ pub export: bool,
+}
+
+impl Var {
+ /// Serialize into the given vec
+ /// This is intended to be called by the store
+ pub fn serialize(&self, output: &mut Vec<u8>) -> Result<()> {
+ encode::write_array_len(output, 3)?; // 3 fields
+
+ encode::write_str(output, self.name.as_str())?;
+ encode::write_str(output, self.value.as_str())?;
+ encode::write_bool(output, self.export)?;
+
+ Ok(())
+ }
+
+ pub fn deserialize(bytes: &mut decode::Bytes) -> Result<Self> {
+ fn error_report<E: std::fmt::Debug>(err: E) -> eyre::Report {
+ eyre!("{err:?}")
+ }
+
+ let nfields = decode::read_array_len(bytes).map_err(error_report)?;
+
+ ensure!(
+ nfields == 3,
+ "too many entries in v0 dotfiles env create record, got {}, expected {}",
+ nfields,
+ 3
+ );
+
+ 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)?;
+
+ let mut bytes = decode::Bytes::new(bytes);
+ let export = decode::read_bool(&mut bytes).map_err(error_report)?;
+
+ ensure!(
+ bytes.remaining_slice().is_empty(),
+ "trailing bytes in encoded dotfiles env record, malformed"
+ );
+
+ Ok(Var {
+ name: key.to_owned(),
+ value: value.to_owned(),
+ export,
+ })
+ }
+}
+
pub fn parse_alias(line: &str) -> Option<Alias> {
// consider the fact we might be importing a fish alias
// 'alias' output
@@ -158,14 +217,14 @@ mod tests {
| inevitably two kinds of slaves: the |
| prisoners of addiction and the |
\\ prisoners of envy. /
- -------------------------------------
+ -------------------------------------
\\ ^__^
\\ (oo)\\_______
(__)\\ )\\/\\
||----w |
|| ||
emacs='TERM=xterm-24bits emacs -nw --foo=bar'
-k=kubectl
+k=kubectl
";
let aliases: Vec<Alias> = shell.lines().filter_map(parse_alias).collect();
diff --git a/crates/atuin-dotfiles/src/shell/bash.rs b/crates/atuin-dotfiles/src/shell/bash.rs
index 5bdd7dce..b4c87336 100644
--- a/crates/atuin-dotfiles/src/shell/bash.rs
+++ b/crates/atuin-dotfiles/src/shell/bash.rs
@@ -1,6 +1,6 @@
use std::path::PathBuf;
-use crate::store::AliasStore;
+use crate::store::{var::VarStore, AliasStore};
async fn cached_aliases(path: PathBuf, store: &AliasStore) -> String {
match tokio::fs::read_to_string(path).await {
@@ -16,6 +16,20 @@ async fn cached_aliases(path: PathBuf, store: &AliasStore) -> String {
}
}
+async fn cached_vars(path: PathBuf, store: &VarStore) -> String {
+ match tokio::fs::read_to_string(path).await {
+ Ok(vars) => vars,
+ Err(r) => {
+ // we failed to read the file for some reason, but the file does exist
+ // fallback to generating new vars on the fly
+
+ store.posix().await.unwrap_or_else(|e| {
+ format!("echo 'Atuin: failed to read and generate vars: \n{r}\n{e}'",)
+ })
+ }
+ }
+}
+
/// Return bash dotfile config
///
/// Do not return an error. We should not prevent the shell from starting.
@@ -23,7 +37,7 @@ async fn cached_aliases(path: PathBuf, store: &AliasStore) -> String {
/// In the worst case, Atuin should not function but the shell should start correctly.
///
/// While currently this only returns aliases, it will be extended to also return other synced dotfiles
-pub async fn config(store: &AliasStore) -> String {
+pub async fn alias_config(store: &AliasStore) -> String {
// First try to read the cached config
let aliases = atuin_common::utils::dotfiles_cache_dir().join("aliases.bash");
@@ -37,3 +51,18 @@ pub async fn config(store: &AliasStore) -> String {
cached_aliases(aliases, store).await
}
+
+pub async fn var_config(store: &VarStore) -> String {
+ // First try to read the cached config
+ let vars = atuin_common::utils::dotfiles_cache_dir().join("vars.bash");
+
+ if vars.exists() {
+ return cached_vars(vars, store).await;
+ }
+
+ if let Err(e) = store.build().await {
+ return format!("echo 'Atuin: failed to generate vars: {}'", e);
+ }
+
+ cached_vars(vars, store).await
+}
diff --git a/crates/atuin-dotfiles/src/shell/fish.rs b/crates/atuin-dotfiles/src/shell/fish.rs
index bf4e1a3b..fc1aeee5 100644
--- a/crates/atuin-dotfiles/src/shell/fish.rs
+++ b/crates/atuin-dotfiles/src/shell/fish.rs
@@ -1,7 +1,7 @@
// Configuration for fish
use std::path::PathBuf;
-use crate::store::AliasStore;
+use crate::store::{var::VarStore, AliasStore};
async fn cached_aliases(path: PathBuf, store: &AliasStore) -> String {
match tokio::fs::read_to_string(path).await {
@@ -17,6 +17,20 @@ async fn cached_aliases(path: PathBuf, store: &AliasStore) -> String {
}
}
+async fn cached_vars(path: PathBuf, store: &VarStore) -> String {
+ match tokio::fs::read_to_string(path).await {
+ Ok(vars) => vars,
+ Err(r) => {
+ // we failed to read the file for some reason, but the file does exist
+ // fallback to generating new vars on the fly
+
+ store.posix().await.unwrap_or_else(|e| {
+ format!("echo 'Atuin: failed to read and generate vars: \n{r}\n{e}'",)
+ })
+ }
+ }
+}
+
/// Return fish dotfile config
///
/// Do not return an error. We should not prevent the shell from starting.
@@ -24,7 +38,7 @@ async fn cached_aliases(path: PathBuf, store: &AliasStore) -> String {
/// In the worst case, Atuin should not function but the shell should start correctly.
///
/// While currently this only returns aliases, it will be extended to also return other synced dotfiles
-pub async fn config(store: &AliasStore) -> String {
+pub async fn alias_config(store: &AliasStore) -> String {
// First try to read the cached config
let aliases = atuin_common::utils::dotfiles_cache_dir().join("aliases.fish");
@@ -38,3 +52,18 @@ pub async fn config(store: &AliasStore) -> String {
cached_aliases(aliases, store).await
}
+
+pub async fn var_config(store: &VarStore) -> String {
+ // First try to read the cached config
+ let vars = atuin_common::utils::dotfiles_cache_dir().join("vars.fish");
+
+ if vars.exists() {
+ return cached_vars(vars, store).await;
+ }
+
+ if let Err(e) = store.build().await {
+ return format!("echo 'Atuin: failed to generate vars: {}'", e);
+ }
+
+ cached_vars(vars, store).await
+}
diff --git a/crates/atuin-dotfiles/src/shell/xonsh.rs b/crates/atuin-dotfiles/src/shell/xonsh.rs
index 383df4ec..a416ccb2 100644
--- a/crates/atuin-dotfiles/src/shell/xonsh.rs
+++ b/crates/atuin-dotfiles/src/shell/xonsh.rs
@@ -1,6 +1,6 @@
use std::path::PathBuf;
-use crate::store::AliasStore;
+use crate::store::{var::VarStore, AliasStore};
async fn cached_aliases(path: PathBuf, store: &AliasStore) -> String {
match tokio::fs::read_to_string(path).await {
@@ -16,6 +16,20 @@ async fn cached_aliases(path: PathBuf, store: &AliasStore) -> String {
}
}
+async fn cached_vars(path: PathBuf, store: &VarStore) -> String {
+ match tokio::fs::read_to_string(path).await {
+ Ok(vars) => vars,
+ Err(r) => {
+ // we failed to read the file for some reason, but the file does exist
+ // fallback to generating new vars on the fly
+
+ store.xonsh().await.unwrap_or_else(|e| {
+ format!("echo 'Atuin: failed to read and generate vars: \n{r}\n{e}'",)
+ })
+ }
+ }
+}
+
/// Return xonsh dotfile config
///
/// Do not return an error. We should not prevent the shell from starting.
@@ -23,7 +37,7 @@ async fn cached_aliases(path: PathBuf, store: &AliasStore) -> String {
/// In the worst case, Atuin should not function but the shell should start correctly.
///
/// While currently this only returns aliases, it will be extended to also return other synced dotfiles
-pub async fn config(store: &AliasStore) -> String {
+pub async fn alias_config(store: &AliasStore) -> String {
// First try to read the cached config
let aliases = atuin_common::utils::dotfiles_cache_dir().join("aliases.xsh");
@@ -37,3 +51,18 @@ pub async fn config(store: &AliasStore) -> String {
cached_aliases(aliases, store).await
}
+
+pub async fn var_config(store: &VarStore) -> String {
+ // First try to read the cached config
+ let vars = atuin_common::utils::dotfiles_cache_dir().join("vars.xsh");
+
+ if vars.exists() {
+ return cached_vars(vars, store).await;
+ }
+
+ if let Err(e) = store.build().await {
+ return format!("echo 'Atuin: failed to generate vars: {}'", e);
+ }
+
+ cached_vars(vars, store).await
+}
diff --git a/crates/atuin-dotfiles/src/shell/zsh.rs b/crates/atuin-dotfiles/src/shell/zsh.rs
index d863b261..efb83897 100644
--- a/crates/atuin-dotfiles/src/shell/zsh.rs
+++ b/crates/atuin-dotfiles/src/shell/zsh.rs
@@ -1,6 +1,6 @@
use std::path::PathBuf;
-use crate::store::AliasStore;
+use crate::store::{var::VarStore, AliasStore};
async fn cached_aliases(path: PathBuf, store: &AliasStore) -> String {
match tokio::fs::read_to_string(path).await {
@@ -16,6 +16,20 @@ async fn cached_aliases(path: PathBuf, store: &AliasStore) -> String {
}
}
+async fn cached_vars(path: PathBuf, store: &VarStore) -> String {
+ match tokio::fs::read_to_string(path).await {
+ Ok(aliases) => aliases,
+ Err(r) => {
+ // we failed to read the file for some reason, but the file does exist
+ // fallback to generating new vars on the fly
+
+ store.posix().await.unwrap_or_else(|e| {
+ format!("echo 'Atuin: failed to read and generate aliases: \n{r}\n{e}'",)
+ })
+ }
+ }
+}
+
/// Return zsh dotfile config
///
/// Do not return an error. We should not prevent the shell from starting.
@@ -23,7 +37,7 @@ async fn cached_aliases(path: PathBuf, store: &AliasStore) -> String {
/// In the worst case, Atuin should not function but the shell should start correctly.
///
/// While currently this only returns aliases, it will be extended to also return other synced dotfiles
-pub async fn config(store: &AliasStore) -> String {
+pub async fn alias_config(store: &AliasStore) -> String {
// First try to read the cached config
let aliases = atuin_common::utils::dotfiles_cache_dir().join("aliases.zsh");
@@ -37,3 +51,18 @@ pub async fn config(store: &AliasStore) -> String {
cached_aliases(aliases, store).await
}
+
+pub async fn var_config(store: &VarStore) -> String {
+ // First try to read the cached config
+ let vars = atuin_common::utils::dotfiles_cache_dir().join("vars.zsh");
+
+ if vars.exists() {
+ return cached_vars(vars, store).await;
+ }
+
+ if let Err(e) = store.build().await {
+ return format!("echo 'Atuin: failed to generate aliases: {}'", e);
+ }
+
+ cached_vars(vars, store).await
+}