work on exit commands

This commit is contained in:
atagen 2025-11-27 09:54:57 +11:00
parent 870ab86ba4
commit f8083521e1
5 changed files with 106 additions and 87 deletions

View file

@ -2,6 +2,7 @@ use clap::{Parser, Subcommand};
#[derive(Subcommand)]
pub enum CliAction {
Enter,
Exit,
Allow,
Disallow,
Edit,

View file

@ -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::<HashSet<String>>(),
)
})
.collect::<HashMap<String, HashSet<String>>>();
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<HashMap<PathBuf, Vec<Keyword>>> {
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<CadeLayer>) -> (HashMap<String, HashSet<String>>, 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<String>| {
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<String>| 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<String>| {
iv.extend(v.clone());
fn output_changes(env: HashMap<String, HashSet<String>>, purified: bool) {
if purified {
let base_env = std::env::vars()
.map(|(k, v)| {
(
k,
v.split(':')
.map(|i| i.to_string())
.collect::<HashSet<String>>(),
)
})
.or_insert(v);
.collect::<HashMap<String, HashSet<String>>>();
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::<HashSet<String>>();
// });
// }
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<PathBuf> {
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<PathBuf, Vec<Keyword>>) -> Result<Vec<CadeLayer>> {
pub fn load_envs(cascade: HashMap<PathBuf, Vec<Keyword>>) -> Result<Vec<CadeLayer>> {
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<PathBuf, Vec<Keyword>>) -> Result<Vec<CadeLayer>
}?;
layer.push_action(act);
}
layer.origin = path;
layer.layer = layer_count;
actions.push(layer);
}
Ok(actions)

View file

@ -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 => {

1
src/shells.rs Normal file
View file

@ -0,0 +1 @@
// TODO write an Output trait and impls for each shell

View file

@ -36,7 +36,7 @@ pub enum Loadable {
Cass(String),
}
#[derive(Debug)]
#[derive(Debug, PartialEq, Eq, Hash)]
pub enum HookType {
LoadPre,
LoadPost,