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.
This commit is contained in:
Ellie Huxtable 2024-04-25 07:52:23 +01:00 committed by GitHub
parent 38ea7706a0
commit d020c815c1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 742 additions and 32 deletions

View File

@ -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();

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -18,6 +18,9 @@ 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.
mod alias;
pub mod var;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AliasRecord {
Create(Alias), // create a full record

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,365 @@
/// Store for shell vars
/// I should abstract this and reuse code between the alias/env stores
/// This is easier for now
/// Once I have two implementations, building a common base is much easier.
use std::collections::BTreeMap;
use atuin_client::record::sqlite_store::SqliteStore;
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::Var;
const DOTFILES_VAR_VERSION: &str = "v0";
const DOTFILES_VAR_TAG: &str = "dotfiles-var";
const DOTFILES_VAR_LEN: usize = 20000; // 20kb max total len, way more than should be needed.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum VarRecord {
Create(Var), // create a full record
Delete(String), // delete by name
}
impl VarRecord {
pub fn serialize(&self) -> Result<DecryptedData> {
use rmp::encode;
let mut output = vec![];
match self {
VarRecord::Create(env) => {
encode::write_u8(&mut output, 0)?; // create
env.serialize(&mut output)?;
}
VarRecord::Delete(env) => {
encode::write_u8(&mut output, 1)?; // delete
encode::write_array_len(&mut output, 1)?; // 1 field
encode::write_str(&mut output, env.as_str())?;
}
}
Ok(DecryptedData(output))
}
pub fn deserialize(data: &DecryptedData, version: &str) -> Result<Self> {
use rmp::decode;
fn error_report<E: std::fmt::Debug>(err: E) -> eyre::Report {
eyre!("{err:?}")
}
match version {
DOTFILES_VAR_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 env = Var::deserialize(&mut bytes)?;
Ok(VarRecord::Create(env))
}
// delete
1 => {
let nfields = decode::read_array_len(&mut bytes).map_err(error_report)?;
ensure!(
nfields == 1,
"too many entries in v0 dotfiles var 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 dotfiles var record. malformed")
}
Ok(VarRecord::Delete(key.to_owned()))
}
n => {
bail!("unknown Dotfiles var record type {n}")
}
}
}
_ => {
bail!("unknown version {version:?}")
}
}
}
}
#[derive(Debug, Clone)]
pub struct VarStore {
pub store: SqliteStore,
pub host_id: HostId,
pub encryption_key: [u8; 32],
}
impl VarStore {
// will want to init the actual kv store when that is done
pub fn new(store: SqliteStore, host_id: HostId, encryption_key: [u8; 32]) -> VarStore {
VarStore {
store,
host_id,
encryption_key,
}
}
pub async fn xonsh(&self) -> Result<String> {
let env = self.vars().await?;
let mut config = String::new();
for env in env {
config.push_str(&format!("${}={}\n", env.name, env.value));
}
Ok(config)
}
pub async fn fish(&self) -> Result<String> {
let env = self.vars().await?;
let mut config = String::new();
for env in env {
config.push_str(&format!("set -gx {} {}\n", env.name, env.value));
}
Ok(config)
}
pub async fn posix(&self) -> Result<String> {
let env = self.vars().await?;
let mut config = String::new();
for env in env {
if env.export {
config.push_str(&format!("export {}={}\n", env.name, env.value));
} else {
config.push_str(&format!("{}={}\n", env.name, env.value));
}
}
Ok(config)
}
pub async fn build(&self) -> Result<()> {
let dir = atuin_common::utils::dotfiles_cache_dir();
tokio::fs::create_dir_all(dir.clone()).await?;
// Build for all supported shells
let posix = self.posix().await?;
let xonsh = self.xonsh().await?;
let fsh = self.fish().await?;
// All the same contents, maybe optimize in the future or perhaps there will be quirks
// per-shell
// I'd prefer separation atm
let zsh = dir.join("vars.zsh");
let bash = dir.join("vars.bash");
let fish = dir.join("vars.fish");
let xsh = dir.join("vars.xsh");
tokio::fs::write(zsh, &posix).await?;
tokio::fs::write(bash, &posix).await?;
tokio::fs::write(fish, &fsh).await?;
tokio::fs::write(xsh, &xonsh).await?;
Ok(())
}
pub async fn set(&self, name: &str, value: &str, export: bool) -> Result<()> {
if name.len() + value.len() > DOTFILES_VAR_LEN {
return Err(eyre!(
"var record too large: max len {} bytes",
DOTFILES_VAR_LEN
));
}
let record = VarRecord::Create(Var {
name: name.to_string(),
value: value.to_string(),
export,
});
let bytes = record.serialize()?;
let idx = self
.store
.last(self.host_id, DOTFILES_VAR_TAG)
.await?
.map_or(0, |entry| entry.idx + 1);
let record = atuin_common::record::Record::builder()
.host(Host::new(self.host_id))
.version(DOTFILES_VAR_VERSION.to_string())
.tag(DOTFILES_VAR_TAG.to_string())
.idx(idx)
.data(bytes)
.build();
self.store
.push(&record.encrypt::<PASETO_V4>(&self.encryption_key))
.await?;
// set mutates shell config, so build again
self.build().await?;
Ok(())
}
pub async fn delete(&self, name: &str) -> Result<()> {
if name.len() > DOTFILES_VAR_LEN {
return Err(eyre!(
"var record too large: max len {} bytes",
DOTFILES_VAR_LEN,
));
}
let record = VarRecord::Delete(name.to_string());
let bytes = record.serialize()?;
let idx = self
.store
.last(self.host_id, DOTFILES_VAR_TAG)
.await?
.map_or(0, |entry| entry.idx + 1);
let record = atuin_common::record::Record::builder()
.host(Host::new(self.host_id))
.version(DOTFILES_VAR_VERSION.to_string())
.tag(DOTFILES_VAR_TAG.to_string())
.idx(idx)
.data(bytes)
.build();
self.store
.push(&record.encrypt::<PASETO_V4>(&self.encryption_key))
.await?;
// delete mutates shell config, so build again
self.build().await?;
Ok(())
}
pub async fn vars(&self) -> Result<Vec<Var>> {
let mut build = BTreeMap::new();
// this is sorted, oldest to newest
let tagged = self.store.all_tagged(DOTFILES_VAR_TAG).await?;
for record in tagged {
let version = record.version.clone();
let decrypted = match version.as_str() {
DOTFILES_VAR_VERSION => record.decrypt::<PASETO_V4>(&self.encryption_key)?,
version => bail!("unknown version {version:?}"),
};
let ar = VarRecord::deserialize(&decrypted.data, version.as_str())?;
match ar {
VarRecord::Create(a) => {
build.insert(a.name.clone(), a);
}
VarRecord::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::Var;
use super::{test_sqlite_store_timeout, VarRecord, VarStore, DOTFILES_VAR_VERSION};
use crypto_secretbox::{KeyInit, XSalsa20Poly1305};
#[test]
fn encode_decode() {
let record = Var {
name: "BEEP".to_owned(),
value: "boop".to_owned(),
export: false,
};
let record = VarRecord::Create(record);
let snapshot = [
204, 0, 147, 164, 66, 69, 69, 80, 164, 98, 111, 111, 112, 194,
];
let encoded = record.serialize().unwrap();
let decoded = VarRecord::deserialize(&encoded, DOTFILES_VAR_VERSION).unwrap();
assert_eq!(encoded.0, &snapshot);
assert_eq!(decoded, record);
}
#[tokio::test]
async fn build_vars() {
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 env = VarStore::new(store, host_id, key);
env.set("BEEP", "boop", false).await.unwrap();
env.set("HOMEBREW_NO_AUTO_UPDATE", "1", true).await.unwrap();
let mut env_vars = env.vars().await.unwrap();
env_vars.sort_by_key(|a| a.name.clone());
assert_eq!(env_vars.len(), 2);
assert_eq!(
env_vars[0],
Var {
name: String::from("BEEP"),
value: String::from("boop"),
export: false,
}
);
assert_eq!(
env_vars[1],
Var {
name: String::from("HOMEBREW_NO_AUTO_UPDATE"),
value: String::from("1"),
export: true,
}
);
}
}

View File

@ -4,6 +4,7 @@ use eyre::Result;
use atuin_client::{record::sqlite_store::SqliteStore, settings::Settings};
mod alias;
mod var;
#[derive(Subcommand, Debug)]
#[command(infer_subcommands = true)]
@ -11,12 +12,17 @@ pub enum Cmd {
/// Manage shell aliases with Atuin
#[command(subcommand)]
Alias(alias::Cmd),
/// Manage shell and environment variables with Atuin
#[command(subcommand)]
Var(var::Cmd),
}
impl Cmd {
pub async fn run(self, settings: &Settings, store: SqliteStore) -> Result<()> {
match self {
Self::Alias(cmd) => cmd.run(settings, store).await,
Self::Var(cmd) => cmd.run(settings, store).await,
}
}
}

View File

@ -0,0 +1,101 @@
use clap::Subcommand;
use eyre::{Context, Result};
use atuin_client::{encryption, record::sqlite_store::SqliteStore, settings::Settings};
use atuin_dotfiles::{shell::Var, store::var::VarStore};
#[derive(Subcommand, Debug)]
#[command(infer_subcommands = true)]
pub enum Cmd {
/// Set a variable
Set {
name: String,
value: String,
#[clap(long, short, action)]
no_export: bool,
},
/// Delete a variable
Delete { name: String },
/// List all variables
List,
}
impl Cmd {
async fn set(&self, store: VarStore, name: String, value: String, export: bool) -> Result<()> {
let vars = store.vars().await?;
let found: Vec<Var> = vars.into_iter().filter(|a| a.name == name).collect();
let show_export = if export { "export " } else { "" };
if found.is_empty() {
println!("Setting '{show_export}{name}={value}'.");
} else {
println!(
"Overwriting alias '{show_export}{name}={}' with '{name}={value}'.",
found[0].value
);
}
store.set(&name, &value, export).await?;
Ok(())
}
async fn list(&self, store: VarStore) -> Result<()> {
let vars = store.vars().await?;
for i in vars.iter().filter(|v| !v.export) {
println!("{}={}", i.name, i.value);
}
for i in vars.iter().filter(|v| v.export) {
println!("export {}={}", i.name, i.value);
}
Ok(())
}
async fn delete(&self, store: VarStore, name: String) -> Result<()> {
let mut vars = store.vars().await?.into_iter();
if let Some(var) = vars.find(|var| var.name == name) {
println!("Deleting '{name}={}'.", var.value);
store.delete(&name).await?;
} else {
eprintln!("Cannot delete '{name}': Var not set.");
};
Ok(())
}
pub async fn run(&self, settings: &Settings, store: SqliteStore) -> Result<()> {
if !settings.dotfiles.enabled {
eprintln!("Dotfiles are not enabled. Add\n\n[dotfiles]\nenabled = true\n\nto your configuration file to enable them.\n");
eprintln!("The default configuration file is located at ~/.config/atuin/config.toml.");
return Ok(());
}
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 var_store = VarStore::new(store, host_id, encryption_key);
match self {
Self::Set {
name,
value,
no_export,
} => {
self.set(var_store, name.clone(), value.clone(), !no_export)
.await
}
Self::Delete { name } => self.delete(var_store, name.clone()).await,
Self::List => self.list(var_store).await,
}
}
}

View File

@ -1,7 +1,7 @@
use std::path::PathBuf;
use atuin_client::{encryption, record::sqlite_store::SqliteStore, settings::Settings};
use atuin_dotfiles::store::AliasStore;
use atuin_dotfiles::store::{var::VarStore, AliasStore};
use clap::{Parser, ValueEnum};
use eyre::{Result, WrapErr};
@ -112,21 +112,46 @@ $env.config = (
.into();
let host_id = Settings::host_id().expect("failed to get host_id");
let alias_store = AliasStore::new(sqlite_store, host_id, encryption_key);
let alias_store = AliasStore::new(sqlite_store.clone(), host_id, encryption_key);
let var_store = VarStore::new(sqlite_store.clone(), host_id, encryption_key);
match self.shell {
Shell::Zsh => {
zsh::init(alias_store, self.disable_up_arrow, self.disable_ctrl_r).await?;
zsh::init(
alias_store,
var_store,
self.disable_up_arrow,
self.disable_ctrl_r,
)
.await?;
}
Shell::Bash => {
bash::init(alias_store, self.disable_up_arrow, self.disable_ctrl_r).await?;
bash::init(
alias_store,
var_store,
self.disable_up_arrow,
self.disable_ctrl_r,
)
.await?;
}
Shell::Fish => {
fish::init(alias_store, self.disable_up_arrow, self.disable_ctrl_r).await?;
fish::init(
alias_store,
var_store,
self.disable_up_arrow,
self.disable_ctrl_r,
)
.await?;
}
Shell::Nu => self.init_nu(),
Shell::Xonsh => {
xonsh::init(alias_store, self.disable_up_arrow, self.disable_ctrl_r).await?;
xonsh::init(
alias_store,
var_store,
self.disable_up_arrow,
self.disable_ctrl_r,
)
.await?;
}
}

View File

@ -1,4 +1,4 @@
use atuin_dotfiles::store::AliasStore;
use atuin_dotfiles::store::{var::VarStore, AliasStore};
use eyre::Result;
pub fn init_static(disable_up_arrow: bool, disable_ctrl_r: bool) {
@ -15,12 +15,19 @@ pub fn init_static(disable_up_arrow: bool, disable_ctrl_r: bool) {
println!("{base}");
}
pub async fn init(store: AliasStore, disable_up_arrow: bool, disable_ctrl_r: bool) -> Result<()> {
pub async fn init(
aliases: AliasStore,
vars: VarStore,
disable_up_arrow: bool,
disable_ctrl_r: bool,
) -> Result<()> {
init_static(disable_up_arrow, disable_ctrl_r);
let aliases = atuin_dotfiles::shell::bash::config(&store).await;
let aliases = atuin_dotfiles::shell::bash::alias_config(&aliases).await;
let vars = atuin_dotfiles::shell::bash::var_config(&vars).await;
println!("{aliases}");
println!("{vars}");
Ok(())
}

View File

@ -1,4 +1,4 @@
use atuin_dotfiles::store::AliasStore;
use atuin_dotfiles::store::{var::VarStore, AliasStore};
use eyre::Result;
pub fn init_static(disable_up_arrow: bool, disable_ctrl_r: bool) {
@ -34,12 +34,19 @@ bind -M insert \e\[A _atuin_bind_up";
}
}
pub async fn init(store: AliasStore, disable_up_arrow: bool, disable_ctrl_r: bool) -> Result<()> {
pub async fn init(
aliases: AliasStore,
vars: VarStore,
disable_up_arrow: bool,
disable_ctrl_r: bool,
) -> Result<()> {
init_static(disable_up_arrow, disable_ctrl_r);
let aliases = atuin_dotfiles::shell::fish::config(&store).await;
let aliases = atuin_dotfiles::shell::fish::alias_config(&aliases).await;
let vars = atuin_dotfiles::shell::fish::var_config(&vars).await;
println!("{aliases}");
println!("{vars}");
Ok(())
}

View File

@ -1,4 +1,4 @@
use atuin_dotfiles::store::AliasStore;
use atuin_dotfiles::store::{var::VarStore, AliasStore};
use eyre::Result;
pub fn init_static(disable_up_arrow: bool, disable_ctrl_r: bool) {
@ -20,12 +20,19 @@ pub fn init_static(disable_up_arrow: bool, disable_ctrl_r: bool) {
println!("{base}");
}
pub async fn init(store: AliasStore, disable_up_arrow: bool, disable_ctrl_r: bool) -> Result<()> {
pub async fn init(
aliases: AliasStore,
vars: VarStore,
disable_up_arrow: bool,
disable_ctrl_r: bool,
) -> Result<()> {
init_static(disable_up_arrow, disable_ctrl_r);
let aliases = atuin_dotfiles::shell::xonsh::config(&store).await;
let aliases = atuin_dotfiles::shell::xonsh::alias_config(&aliases).await;
let vars = atuin_dotfiles::shell::xonsh::var_config(&vars).await;
println!("{aliases}");
println!("{vars}");
Ok(())
}

View File

@ -1,4 +1,4 @@
use atuin_dotfiles::store::AliasStore;
use atuin_dotfiles::store::{var::VarStore, AliasStore};
use eyre::Result;
pub fn init_static(disable_up_arrow: bool, disable_ctrl_r: bool) {
@ -28,12 +28,19 @@ bindkey -M vicmd 'k' atuin-up-search-vicmd";
}
}
pub async fn init(store: AliasStore, disable_up_arrow: bool, disable_ctrl_r: bool) -> Result<()> {
pub async fn init(
aliases: AliasStore,
vars: VarStore,
disable_up_arrow: bool,
disable_ctrl_r: bool,
) -> Result<()> {
init_static(disable_up_arrow, disable_ctrl_r);
let aliases = atuin_dotfiles::shell::zsh::config(&store).await;
let aliases = atuin_dotfiles::shell::zsh::alias_config(&aliases).await;
let vars = atuin_dotfiles::shell::zsh::var_config(&vars).await;
println!("{aliases}");
println!("{vars}");
Ok(())
}

View File

@ -1,4 +1,4 @@
use atuin_dotfiles::store::AliasStore;
use atuin_dotfiles::store::{var::VarStore, AliasStore};
use clap::Args;
use eyre::{bail, Result};
@ -59,9 +59,12 @@ impl Rebuild {
let encryption_key: [u8; 32] = encryption::load_key(settings)?.into();
let host_id = Settings::host_id().expect("failed to get host_id");
let alias_store = AliasStore::new(store, host_id, encryption_key);
let alias_store = AliasStore::new(store.clone(), host_id, encryption_key);
let var_store = VarStore::new(store.clone(), host_id, encryption_key);
alias_store.build().await?;
var_store.build().await?;
Ok(())
}

View File

@ -1,4 +1,4 @@
use atuin_dotfiles::store::AliasStore;
use atuin_dotfiles::store::{var::VarStore, AliasStore};
use eyre::{Context, Result};
use atuin_client::{
@ -29,9 +29,12 @@ pub async fn build(
let history_store = HistoryStore::new(store.clone(), host_id, encryption_key);
let alias_store = AliasStore::new(store.clone(), host_id, encryption_key);
let var_store = VarStore::new(store.clone(), host_id, encryption_key);
history_store.incremental_build(db, downloaded).await?;
alias_store.build().await?;
var_store.build().await?;
Ok(())
}