From 757020689c0d406f6bab9c8786fa0e6f9d47ede9 Mon Sep 17 00:00:00 2001 From: atagen Date: Thu, 2 Oct 2025 08:52:15 +1000 Subject: [PATCH] state --- .cade | 2 + Cargo.lock | 51 ++++++- Cargo.toml | 2 + src/actions.rs | 270 ++++++++++++++++++++++++++++++++++++ src/cli/clap.rs | 14 ++ src/cli/mod.rs | 2 + src/cli/parse.rs | 98 +++++++++++++ src/core.rs | 44 ++++++ src/main.rs | 351 ++--------------------------------------------- src/types.rs | 47 +++++++ 10 files changed, 537 insertions(+), 344 deletions(-) create mode 100644 .cade create mode 100644 src/actions.rs create mode 100644 src/cli/clap.rs create mode 100644 src/cli/mod.rs create mode 100644 src/cli/parse.rs create mode 100644 src/core.rs create mode 100644 src/types.rs diff --git a/.cade b/.cade new file mode 100644 index 0000000..43e7ff4 --- /dev/null +++ b/.cade @@ -0,0 +1,2 @@ +pure +load flake diff --git a/Cargo.lock b/Cargo.lock index f68619d..61b1bed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -104,6 +104,8 @@ dependencies = [ "bincode", "clap", "microxdg", + "serde", + "serde_json", "sled", ] @@ -223,6 +225,12 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + [[package]] name = "libc" version = "0.2.175" @@ -245,6 +253,12 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + [[package]] name = "microxdg" version = "0.2.0" @@ -309,6 +323,12 @@ dependencies = [ "bitflags", ] +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + [[package]] name = "scopeguard" version = "1.2.0" @@ -317,24 +337,47 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.226" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.226" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.226" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + [[package]] name = "sled" version = "0.34.7" diff --git a/Cargo.toml b/Cargo.toml index 01000ff..b032084 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,4 +15,6 @@ anyhow = "1.0.99" bincode = "2.0.1" clap = { version = "4.5.45", features = ["derive"] } microxdg = "0.2.0" +serde = { version = "1.0.226", features = ["derive"] } +serde_json = "1.0.145" sled = "0.34.7" diff --git a/src/actions.rs b/src/actions.rs new file mode 100644 index 0000000..6c4b5e2 --- /dev/null +++ b/src/actions.rs @@ -0,0 +1,270 @@ +use crate::types::{CadeActions, Env}; +use anyhow::{Context, Result, anyhow, bail}; +use serde_json::Value; +use std::{ + collections::HashMap, + ffi::OsString, + fmt::{Debug, Display}, + io::{BufRead, Read}, + path::{Path, PathBuf}, +}; +type RawEnv = Vec<(String, Vec)>; + +#[repr(u8)] +#[derive(bincode::Encode, bincode::Decode, PartialEq, Debug)] +pub enum Permission { + Allowed, + Disallowed, +} + +impl Display for Permission { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Debug::fmt(self, f) + } +} + +pub fn load_flake(path: &Path, output: Option) -> Result { + let mut nix_cmd = String::from("nix print-dev-env --json"); + if let Some(flake_output) = output { + nix_cmd.push_str(&[" ", &flake_output, " "].concat()); + } + let mut proc = std::process::Command::new("sh"); + // proc.env_clear(); + proc.arg("-c"); + proc.arg(nix_cmd); + proc.current_dir(path); + let output = proc + .output() + .with_context(|| format!("loading flake at {}", path.display()))? + .stdout; + let json: Value = serde_json::from_slice(&output).context("parsing output as json")?; + parse_json(json) +} + +pub fn load_shell(path: &Path, filename: String) -> Result { + let mut nix_cmd = String::from("nix print-dev-env --json "); + // let mut nix_cmd = String::from("nix-shell "); + if !filename.is_empty() { + nix_cmd.push_str(&["-F ", &filename].concat()); + } else { + nix_cmd.push_str(&["-F ./shell.nix"].concat()); + } + // nix_cmd.push_str("--pure --command env --null"); + let mut proc = std::process::Command::new("sh"); + proc.env_clear(); + proc.arg("-c"); + proc.arg(nix_cmd); + proc.current_dir(path); + let output = proc + .output() + .with_context(|| format!("loading shell at {}", path.display()))? + .stdout; + let json: Value = serde_json::from_slice(&output).context("parsing output as json")?; + parse_json(json) +} + +pub fn load_env(path: &Path, filename: String) -> Result { + let mut p = path.to_path_buf(); + if filename.is_empty() { + p.push(".env"); + } else { + p.push(filename); + } + let mut file = std::fs::File::open(p) + .with_context(|| format!("opening env file at {}", path.display()))?; + let mut buf = String::new(); + file.read_to_string(&mut buf).context("reading env file")?; + parse_envs(buf) +} + +pub fn call(path: &Path, argv: Vec) -> Result { + let mut it = argv.iter(); + // safety: already checked at parsing + let mut process = std::process::Command::new(it.next().unwrap()); + process.current_dir(path); + process.args(it); + let output = process + .output() + .with_context(|| format!("running process {}", argv.concat()))? + .stdout; + + let text = String::from_utf8(output) + .with_context(|| format!("converting call {} output to text", argv.concat()))? + .replace(' ', "\0"); + parse_envs(text) +} + +fn parse_envs(text: String) -> Result { + let mut envs = Vec::new(); + for line in text.lines() { + let split: Vec<&str> = line.split('=').collect(); + let parse = match split.len() { + 3 => { + let key = split[0]; + let values = &split[2].split(":").to_owned().collect(); + } + 2 => { + // TODO + } + _ => { + return Err(anyhow!("parsing variable from {text}")); + } + // 1 => Clear(split[0].to_string()), + // _ => Add(split[0].to_string(), split[1..].concat().to_string()), + }; + envs.push(parse); + } + Ok(envs) +} + +fn parse_json(json: Value) -> Result { + if json.is_object() + && let Some(all_vars) = json.get("variables") + { + let vars = all_vars + .as_object() + .map(|inner| { + inner + .iter() + .filter(|(var, _)| match var.as_str() { + "NIX_BUILD_TOP" + | "NIX_BUILD_CORES" + | "NIX_STORE" + | "TEMP" + | "TEMPDIR" + | "TMP" + | "TMPDIR" + | "builder" + | "out" + | "stdenv" + | "system" + | "dontAddDisableDepTrack" + | "outputs" => false, + _ => true, + }) + .filter_map(|var| { + Some((var.0.to_string(), var.1.get("value")?.as_str()?.to_owned())) + }) + .map(|(name, value)| { + ( + name, + value + .split(':') + .map(|s| s.to_string()) + .collect::>(), + ) + }) + .collect() + }) + .context("collecting env vars")?; + Ok(vars) + } else { + Err(anyhow!("failed to parse PATH value from JSON output")) + } +} + +pub fn do_activation() -> Result<()> { + let dir = ensure_dir()?; + let db = sled::open(dir).context("open database")?; + let cwd = std::env::current_dir() + .context("determine CWD")? + .into_os_string(); + + if db + .get(cwd.into_encoded_bytes()) + .map(|perm| { + perm.map(|inner| { + let (p, _) = + bincode::decode_from_slice(inner.as_ref(), bincode::config::standard()) + .unwrap_or((Permission::Disallowed, 0)); + p + }) + .unwrap_or(Permission::Disallowed) + }) + .unwrap_or(Permission::Disallowed) + == Permission::Disallowed + { + bail!("cade is not permitted to operate here; use 'cade allow'."); + } + + let mut current_dir = std::env::current_dir().context("determine CWD")?; + let base_env = std::env::vars().collect::>(); + + use crate::core::{read_cade, realise}; + let mut cascade = HashMap::new(); + cascade.insert( + current_dir.clone(), + read_cade(¤t_dir.join(".cade")).context("reading cade file")?, + ); + + while let Some(parent) = current_dir.parent() + && std::fs::exists(parent.join(".cade")) + .context("check for .cade file in parent directory")? + { + current_dir = parent.to_path_buf(); + cascade.insert( + current_dir.clone(), + read_cade(¤t_dir.join(".cade")).context("reading cade file")?, + ); + } + + let mut env = HashMap::new(); + let adjustments = realise(cascade)?; + use CadeActions::*; + for action in adjustments { + match action { + Environ(env_actions) => { + for action in env_actions { + env.entry(action.name) + .and_modify(|iv: &mut Vec| { + iv.extend(action.value); + }) + .or_insert(action.value); + } + } + Purify => { + for (bk, bv) in base_env.iter() { + env.entry(bk).and_modify(|inner| inner) + if env.get(bk).is_some_and(|inner| inner.contains(bv)) { + env.remove(bk); + } + } + } + Hook(hook) => {} + }; + } + for (k, v) in env { + println!("{k}={v}"); + } + Ok(()) +} + +pub fn set_permission(permission: Permission) -> Result<()> { + let dir = ensure_dir()?; + let db = sled::open(dir).context("open database")?; + let cwd = std::env::current_dir() + .context("determine CWD")? + .into_os_string(); + let encoded_permission = bincode::encode_to_vec(&permission, bincode::config::standard()) + .context("encode permission value")?; + + let _ = db + .insert(cwd.into_encoded_bytes(), encoded_permission) + .context("update permissions database")?; + eprintln!( + "cade is now {} here.", + permission.to_string().to_lowercase() + ); + Ok(()) +} + +fn ensure_dir() -> Result { + let xdg = microxdg::Xdg::new().context("establish XDG paths")?; + let mut path = xdg.state().context("find xdg state dir")?; + path.push("cade"); + if !std::fs::exists(&path).is_ok_and(|v| v) { + std::fs::create_dir(&path).context("create cade's state path")?; + } + path.push("permissions.db"); + Ok(path) +} diff --git a/src/cli/clap.rs b/src/cli/clap.rs new file mode 100644 index 0000000..fef8320 --- /dev/null +++ b/src/cli/clap.rs @@ -0,0 +1,14 @@ +use clap::{Parser, Subcommand}; +#[derive(Subcommand)] +pub enum CliAction { + Enter, + Allow, + Disallow, + Edit, +} + +#[derive(Parser)] +pub struct Cli { + #[command(subcommand)] + pub action: CliAction, +} diff --git a/src/cli/mod.rs b/src/cli/mod.rs new file mode 100644 index 0000000..0e2b037 --- /dev/null +++ b/src/cli/mod.rs @@ -0,0 +1,2 @@ +pub mod clap; +pub mod parse; diff --git a/src/cli/parse.rs b/src/cli/parse.rs new file mode 100644 index 0000000..9b93e40 --- /dev/null +++ b/src/cli/parse.rs @@ -0,0 +1,98 @@ +use crate::Debug; +use crate::types::*; +use std::{fmt::Display, str::FromStr}; + +#[derive(Debug)] +enum ParseError { + InvalidKeyword, + UnknownLoadable, + TooManyOptions, + TooFewOptions, +} + +impl Display for ParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Debug::fmt(self, f) + } +} + +impl FromStr for Keyword { + type Err = ParseError; + fn from_str(s: &str) -> std::result::Result { + let lower = s.to_lowercase(); + use crate::types::Keyword::*; + if s.len() >= 4 { + let res = match &lower.get(..4) { + Some("pure") => Pure, + Some("call") => { + let target: Vec = lower[4..] + .split_whitespace() + .map(|s| s.to_owned()) + .collect(); + if target.is_empty() { + return Err(ParseError::TooFewOptions); + } + Call(target) + } + Some("load") => { + let mut words = lower[4..].split_whitespace(); + if words.clone().count() > 2 { + return Err(ParseError::TooManyOptions); + } else { + match words.next() { + None => Load(Loadable::Default), + Some("shell") => { + Load(Loadable::Shell(s.get(11..).unwrap_or("").to_string())) + } + Some("flake") => { + Load(Loadable::Flake(s.get(11..).unwrap_or("").to_string())) + } + Some("env") => { + Load(Loadable::Env(s.get(9..).unwrap_or("").to_string())) + } + Some(_) => return Err(ParseError::UnknownLoadable), + } + } + } + Some("hook") => { + let mut words = lower[4..].split_whitespace(); + let mut p = words.clone().peekable(); + use crate::types::HookType::*; + Hook(crate::types::Hook { + kind: match p.peek() { + None => return Err(ParseError::TooFewOptions), + Some(&"preload") => { + words.next(); + LoadPre + } + Some(&"load") => { + words.next(); + LoadPost + } + Some(&"preunload") => { + words.next(); + UnloadPre + } + Some(&"unload") => { + words.next(); + UnloadPost + } + Some(_) => LoadPost, + }, + content: words.map(|s| s.to_owned()).collect(), + }) + } + Some(n) => { + eprintln!("found invalid command: {n}"); + return Err(ParseError::InvalidKeyword); + } + None => { + return Err(ParseError::InvalidKeyword); + } + }; + Ok(res) + } else { + Err(ParseError::InvalidKeyword) + } + } +} diff --git a/src/core.rs b/src/core.rs new file mode 100644 index 0000000..351ea4b --- /dev/null +++ b/src/core.rs @@ -0,0 +1,44 @@ +use crate::types::{CadeActions, Keyword, Loadable}; +use anyhow::{Context, Result, anyhow}; +use std::{ + collections::HashMap, + io::BufRead, + path::{Path, PathBuf}, +}; + +pub fn read_cade(path: &Path) -> Result> { + let contents = std::fs::read(path).context("reading cade file")?; + let mut accum = Vec::new(); + let mut lines = contents.lines(); + while let Some(Ok(line)) = lines.next() { + let instruction: Keyword = line + .parse() + .map_err(|e| anyhow!("while parsing cade file at {}: {e:?}", path.display()))?; + accum.push(instruction); + } + Ok(accum) +} + +pub fn realise(cascade: HashMap>) -> Result> { + let mut actions: Vec = Vec::new(); + use crate::actions::*; + for (path, layer) in cascade { + for keyword in layer { + use Keyword::*; + use Loadable::*; + let acts = match keyword { + Pure => Ok(CadeActions::Purify), + Call(argv) => call(&path, argv).context("calling process"), + Load(loadable) => match loadable { + Default => load_flake(&path, None).context("loading flake"), + Flake(output) => load_flake(&path, Some(output)), + Shell(filename) => load_shell(&path, filename).context("loading shell"), + Env(filename) => load_env(&path, filename).context("loading env file"), + }, + Hook(hook) => Ok(CadeActions::Hook(hook)), + }?; + actions.push(acts); + } + } + Ok(actions) +} diff --git a/src/main.rs b/src/main.rs index c4a78ec..08bf644 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,355 +1,26 @@ +mod actions; +mod cli; +mod core; +mod types; + use std::{ collections::HashMap, ffi::OsString, fmt::{Debug, Display}, io::{BufRead, Read}, - os::unix::process::CommandExt, path::{Path, PathBuf}, - str::{FromStr, SplitWhitespace}, }; use anyhow::{Context, Result, anyhow, bail}; -use clap::{Parser, Subcommand}; - -#[repr(u8)] -#[derive(bincode::Encode, bincode::Decode, PartialEq, Debug)] -enum Permission { - Allowed, - Disallowed, -} -impl Display for Permission { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Debug::fmt(self, f) - } -} - -#[derive(Subcommand)] -enum Action { - Activate, - Allow, - Disallow, - Edit, -} - -#[derive(Parser)] -struct Cli { - #[command(subcommand)] - action: Action, -} - -#[derive(Debug)] -enum Loadable { - Default, - Flake(String), - Shell(String), - Env(String), -} - -#[derive(Debug)] -enum Keyword { - Pure, - Call(Vec), - Load(Loadable), -} - -#[derive(Debug)] -enum ParseError { - InvalidKeyword, - UnknownLoadable, - TooManyOptions, - TooFewOptions, -} -impl Display for ParseError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Debug::fmt(self, f) - } -} - -impl FromStr for Keyword { - type Err = ParseError; - fn from_str(s: &str) -> std::result::Result { - let lower = s.to_lowercase(); - use Keyword::*; - if s.len() >= 4 { - let res = match &lower.get(..4) { - Some("pure") => Pure, - Some("call") => { - let target: Vec = lower[4..] - .split_whitespace() - .map(|s| s.to_owned()) - .collect(); - if target.is_empty() { - return Err(ParseError::TooFewOptions); - } - Call(target) - } - Some("load") => { - let mut words = lower[4..].split_whitespace(); - if words.clone().count() > 2 { - return Err(ParseError::TooManyOptions); - } else { - match words.next() { - None => Load(Loadable::Default), - Some("shell") => { - Load(Loadable::Shell(s.get(11..).unwrap_or("").to_string())) - } - Some("flake") => { - Load(Loadable::Flake(s.get(11..).unwrap_or("").to_string())) - } - Some("env") => { - Load(Loadable::Env(s.get(9..).unwrap_or("").to_string())) - } - Some(_) => return Err(ParseError::UnknownLoadable), - } - } - } - Some(n) => { - eprintln!("found invalid command {n}, dying.."); - return Err(ParseError::InvalidKeyword); - } - None => { - return Err(ParseError::InvalidKeyword); - } - }; - Ok(res) - } else { - Err(ParseError::InvalidKeyword) - } - } -} - -fn read_cade(path: &Path) -> Result> { - let contents = std::fs::read(path).context("reading cade file")?; - let mut accum = Vec::new(); - let mut lines = contents.lines(); - while let Some(Ok(line)) = lines.next() { - let instruction: Keyword = line - .parse() - .map_err(|e| anyhow!("while parsing cade file at {}: {e:?}", path.display()))?; - accum.push(instruction); - } - Ok(accum) -} - -#[derive(Debug)] -enum EnvAction { - Add(String, String), - Clear(String), - Purify, -} - -fn load_flake(path: &Path, output: Option) -> Result> { - let mut nix_cmd = String::from("nix develop "); - if let Some(flake_output) = output { - nix_cmd.push_str(&[&flake_output, " "].concat()); - } - nix_cmd.push_str("-ic env --null"); - let mut proc = std::process::Command::new("sh"); - // proc.env_clear(); - proc.arg("-c"); - proc.arg(nix_cmd); - proc.current_dir(path); - let output = proc - .output() - .with_context(|| format!("loading flake at {}", path.display()))? - .stdout; - let text = String::from_utf8(output).context("converting output to text")?; - parse_envs(text) -} - -fn load_shell(path: &Path, filename: String) -> Result> { - let mut nix_cmd = String::from("nix-shell "); - if filename.is_empty() { - nix_cmd.push_str(&[&filename, " "].concat()); - } - nix_cmd.push_str("--pure --command env --null"); - let mut proc = std::process::Command::new("sh"); - proc.env_clear(); - proc.arg("-c"); - proc.arg(nix_cmd); - proc.current_dir(path); - let output = proc - .output() - .with_context(|| format!("loading shell at {}", path.display()))? - .stdout; - let text = String::from_utf8(output).context("converting output to text")?; - parse_envs(text) -} - -fn load_env(path: &Path, filename: String) -> Result> { - let mut p = path.to_path_buf(); - if filename.is_empty() { - p.push(".env"); - } else { - p.push(filename); - } - let mut file = std::fs::File::open(p) - .with_context(|| format!("opening env file at {}", path.display()))?; - let mut buf = String::new(); - file.read_to_string(&mut buf).context("reading env file")?; - parse_envs(buf) -} - -fn call(path: &Path, argv: Vec) -> Result> { - let mut it = argv.iter(); - // safety: already checked at parsing - let mut process = std::process::Command::new(it.next().unwrap()); - process.current_dir(path); - process.args(it); - let output = process - .output() - .with_context(|| format!("running process {}", argv.concat()))? - .stdout; - - let text = String::from_utf8(output) - .with_context(|| format!("converting call {} output to text", argv.concat()))? - .replace(' ', "\0"); - parse_envs(text) -} - -fn parse_envs(text: String) -> Result> { - let mut envs = Vec::new(); - use EnvAction::*; - for line in text.split('\0') { - let split: Vec<&str> = line.split('=').collect(); - let parse = match split.len() { - 0 => { - return Err(anyhow!("processing returned env var {text}")); - } - 1 => Clear(split[0].to_string()), - _ => Add(split[0].to_string(), split[1..].concat().to_string()), - }; - envs.push(parse); - } - Ok(envs) -} - -fn interpret(cascade: HashMap>) -> Result> { - let mut actions = Vec::new(); - for (path, layer) in cascade { - for keyword in layer { - use EnvAction::*; - use Keyword::*; - use Loadable::*; - let acts = match keyword { - Pure => Ok(Vec::from([Purify])), - Call(argv) => call(&path, argv).context("calling process"), - Load(loadable) => match loadable { - Default => load_flake(&path, None).context("loading flake"), - Flake(output) => load_flake(&path, Some(output)), - Shell(filename) => load_shell(&path, filename).context("loading shell"), - Env(filename) => load_env(&path, filename).context("loading env file"), - }, - }?; - actions.extend(acts); - } - } - Ok(actions) -} - -fn do_activation() -> Result<()> { - let dir = ensure_dir()?; - let db = sled::open(dir).context("open database")?; - let cwd = std::env::current_dir() - .context("determine CWD")? - .into_os_string(); - - if db - .get(cwd.into_encoded_bytes()) - .map(|perm| { - perm.map(|inner| { - let (p, _) = - bincode::decode_from_slice(inner.as_ref(), bincode::config::standard()) - .unwrap_or((Permission::Disallowed, 0)); - p - }) - .unwrap_or(Permission::Disallowed) - }) - .unwrap_or(Permission::Disallowed) - == Permission::Disallowed - { - bail!("cade is not permitted to operate here; use 'cade allow'."); - } - - let mut current_dir = std::env::current_dir().context("determine CWD")?; - let base_env = std::env::vars().collect::>(); - - let mut cascade = HashMap::new(); - cascade.insert( - current_dir.clone(), - read_cade(¤t_dir.join(".cade")).context("reading cade file")?, - ); - - while let Some(parent) = current_dir.parent() - && std::fs::exists(parent.join(".cade")) - .context("check for .cade file in parent directory")? - { - current_dir = parent.to_path_buf(); - cascade.insert( - current_dir.clone(), - read_cade(¤t_dir.join(".cade")).context("reading cade file")?, - ); - } - - let mut env = HashMap::new(); - let adjustments = interpret(cascade)?; - use EnvAction::*; - for action in adjustments { - let _ = match action { - Add(k, v) => env.insert(k, v), - Clear(k) => env.remove(&k), - Purify => { - for (bk, bv) in base_env.iter() { - if env.get(bk).is_some_and(|inner| inner == bv) { - env.remove(bk); - } - } - None - } - }; - } - for (k, v) in env { - println!("{k}={v}"); - } - Ok(()) -} - -fn set_permission(permission: Permission) -> Result<()> { - let dir = ensure_dir()?; - let db = sled::open(dir).context("open database")?; - let cwd = std::env::current_dir() - .context("determine CWD")? - .into_os_string(); - let encoded_permission = bincode::encode_to_vec(&permission, bincode::config::standard()) - .context("encode permission value")?; - - let _ = db - .insert(cwd.into_encoded_bytes(), encoded_permission) - .context("update permissions database")?; - eprintln!( - "cade is now {} here.", - permission.to_string().to_lowercase() - ); - Ok(()) -} - -fn ensure_dir() -> Result { - let xdg = microxdg::Xdg::new().context("establish XDG paths")?; - let mut path = xdg.state().context("find xdg state dir")?; - path.push("cade"); - if !std::fs::exists(&path).is_ok_and(|v| v) { - std::fs::create_dir(&path).context("create cade's state path")?; - } - path.push("permissions.db"); - Ok(path) -} +use clap::Parser; fn try_main() -> Result<()> { - let args = Cli::parse(); - use Action::*; + let args = cli::clap::Cli::parse(); + use crate::actions::*; + use cli::clap::CliAction::*; match args.action { - // TODO recursively ascend until we hit a top level .cade, then activate downwards - Activate => do_activation().context("activate cade environment")?, + // TODO recursively ascend until we hit a top level .cade, then activate downwards (ie. in reverse) + Enter => do_activation().context("activate cade environment")?, Allow => set_permission(Permission::Allowed)?, Disallow => set_permission(Permission::Disallowed)?, Edit => { diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 0000000..004ce4b --- /dev/null +++ b/src/types.rs @@ -0,0 +1,47 @@ +use std::path::PathBuf; + +#[derive(Debug)] +pub enum CadeActions { + Purify, + Environ(Vec), + Hook(Hook), +} + +#[derive(Debug)] +pub struct Env { + pub name: String, + pub value: Vec, + pub origin: PathBuf, + pub kind: Loadable, + pub layer: u8, +} + +#[derive(Debug)] +pub enum Keyword { + Pure, + Call(Vec), + Load(Loadable), + Hook(Hook), +} + +#[derive(Debug)] +pub enum Loadable { + Default, + Flake(String), + Shell(String), + Env(String), +} + +#[derive(Debug)] +pub enum HookType { + LoadPre, + LoadPost, + UnloadPre, + UnloadPost, +} + +#[derive(Debug)] +pub struct Hook { + pub content: Vec, + pub kind: HookType, +}