diff --git a/src/cli/clap.rs b/src/cli/clap.rs index fef8320..726fe31 100644 --- a/src/cli/clap.rs +++ b/src/cli/clap.rs @@ -2,6 +2,7 @@ use clap::{Parser, Subcommand}; #[derive(Subcommand)] pub enum CliAction { Enter, + Exit, Allow, Disallow, Edit, diff --git a/src/core.rs b/src/core.rs index 86ab313..1bfab74 100644 --- a/src/core.rs +++ b/src/core.rs @@ -64,109 +64,121 @@ impl Cade { .map_err(|e| e.into()) } - // TODO break this function up pub fn do_activation(&mut self) -> Result<()> { - let mut working_dir = self.cwd.clone(); - if !self.get_permission(&working_dir)? { - bail!("cade is not permitted to operate here; use 'cade allow'."); - } - - let base_env = std::env::vars() - .map(|(k, v)| { - ( - k, - v.split(':') - .map(|i| i.to_string()) - .collect::>(), - ) - }) - .collect::>>(); - - use crate::core::{read_cade, realise}; - let mut cade_files = HashMap::new(); - cade_files.insert( - working_dir.clone(), - read_cade(&working_dir.join(".cade")).context("reading cade file")?, - ); - - // recurse into parent dirs - while let Some(parent) = working_dir.parent() - && std::fs::exists(parent.join(".cade")) - .context("check for .cade file in parent directory")? - { - working_dir = parent.to_path_buf(); + fn collect_cade_files(mut working_dir: PathBuf) -> Result>> { + use crate::core::read_cade; + let mut cade_files = HashMap::new(); cade_files.insert( working_dir.clone(), read_cade(&working_dir.join(".cade")).context("reading cade file")?, ); + + // recurse into parent dirs + while let Some(parent) = working_dir.parent() + && std::fs::exists(parent.join(".cade")) + .context("check for .cade file in parent directory")? + { + working_dir = parent.to_path_buf(); + cade_files.insert( + working_dir.clone(), + read_cade(&working_dir.join(".cade")).context("reading cade file")?, + ); + } + Ok(cade_files) + } + // TODO use faststr + + fn rollup_envs(cade_layers: Vec) -> (HashMap>, bool) { + let mut purified = false; + let mut env = HashMap::new(); + let mut hooks = HashMap::new(); + for layer in cade_layers { + for (k, v) in layer.envs.0 { + env.entry(k) + .and_modify(|iv: &mut HashSet| { + iv.extend(v.clone()); + }) + .or_insert(v); + } + if !purified && layer.purify { + purified = true; + } + // FIXME not really happy with this impl of hooks + // should each line just be a `call` ? + for hook in layer.hooks { + hooks + .entry(hook.kind) + .and_modify(|h: &mut Vec| h.extend(hook.content.clone())) + .or_insert(hook.content); + } + } + + (env, purified) } - let mut env = HashMap::new(); - let cade_layers = realise(cade_files)?; - let mut purified = false; - - for layer in cade_layers { - for (k, v) in layer.envs.0 { - env.entry(k) - .and_modify(|iv: &mut HashSet| { - iv.extend(v.clone()); + fn output_changes(env: HashMap>, purified: bool) { + if purified { + let base_env = std::env::vars() + .map(|(k, v)| { + ( + k, + v.split(':') + .map(|i| i.to_string()) + .collect::>(), + ) }) - .or_insert(v); + .collect::>>(); + for (k, _) in base_env { + print!("set -u {k};") + } } - if layer.purify { - // && !purified { - // for (bk, bv) in base_env.iter() { - // // FIXME is this even worth doing ? - // // no, just unset them in the export.. - // env.entry(bk.clone()).and_modify(|v| { - // *v = v - // .iter() - // .filter(|iv| !bv.contains(*iv)) - // .cloned() - // .collect::>(); - // }); - // } - purified = true; + for (k, v) in env { + let len = v.len(); + let value: String = v + .into_iter() + .enumerate() + .map(|(i, s)| { + if i < len.saturating_sub(1) { + [&s, ":"].concat() + } else { + s + } + }) + .collect(); + print!("set -x -g '{k}' '{value}';"); } - // TODO hooks don't exist yet + println!(); } - // if !purified { - // for (bk, bv) in base_env { - // env.entry(bk) - // .and_modify(|v| { - // v.extend(bv.clone()); - // }) - // .or_insert(bv); - // } - // } + + let working_dir = self.cwd.clone(); + if !self.get_permission(&working_dir)? { + bail!("cade is not permitted to operate here; use 'cade allow'."); + } + + let cade_files = collect_cade_files(working_dir)?; + let cade_layers = load_envs(cade_files)?; + let (env, purified) = rollup_envs(cade_layers); + output_changes(env, purified); // TODO save these env sets for the unexport hook // base_envs will have to be fully restored // while envs must be carefully subtraced - if purified { - for (k, _) in base_env { - print!("set -u {k};") - } - } - for (k, v) in env { - let len = v.len(); - let value: String = v - .into_iter() - .enumerate() - .map(|(i, s)| { - if i < len.saturating_sub(1) { - [&s, ":"].concat() - } else { - s - } - }) - .collect(); - print!("set -x -g '{k}' '{value}';"); - } - println!(); Ok(()) } + // this is more complex than it seems - + // we need to understand what layers are in place, where our root is, and determine + // which ones should remain (in case we are leaving only an inner layer) + // then diff those against the current environment and output the appropriate set commands + // + // this will play into the caching story also, naturally + // perhaps we could store related stateful information into our own env vars + // + // alternatively, we just traverse to root and reinit up to our current dir.. + pub fn do_restore(&mut self) -> Result<()> { + todo!() + } + fn ensure_dir() -> Result { let mut path = if let Ok(xdg) = microxdg::Xdg::new() && let Ok(state_dir) = xdg.state() @@ -235,7 +247,7 @@ impl CadeLayer { } } -pub fn realise(cascade: HashMap>) -> Result> { +pub fn load_envs(cascade: HashMap>) -> Result> { let mut actions = Vec::new(); use crate::loaders::*; for (layer_count, (path, keywords)) in cascade.into_iter().enumerate() { @@ -260,6 +272,8 @@ pub fn realise(cascade: HashMap>) -> Result }?; layer.push_action(act); } + layer.origin = path; + layer.layer = layer_count; actions.push(layer); } Ok(actions) diff --git a/src/main.rs b/src/main.rs index 2a773b9..02de32c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ mod cli; mod core; mod envs; mod loaders; +mod shells; mod types; use std::ffi::OsString; @@ -17,6 +18,8 @@ fn try_main() -> Result<()> { let mut cade = Cade::init()?; match args.action { Enter => cade.do_activation().context("activate cade environment")?, + // TODO + Exit => cade.do_restore().context("deactivate cade environment")?, Allow => cade.set_permission(true)?, Disallow => cade.set_permission(false)?, Edit => { diff --git a/src/shells.rs b/src/shells.rs new file mode 100644 index 0000000..fb1f0a4 --- /dev/null +++ b/src/shells.rs @@ -0,0 +1 @@ +// TODO write an Output trait and impls for each shell diff --git a/src/types.rs b/src/types.rs index 17ece0f..798d8fa 100644 --- a/src/types.rs +++ b/src/types.rs @@ -36,7 +36,7 @@ pub enum Loadable { Cass(String), } -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq, Hash)] pub enum HookType { LoadPre, LoadPost,