This commit is contained in:
atagen 2025-10-02 08:52:15 +10:00
parent b2e0936d4a
commit 757020689c
10 changed files with 537 additions and 344 deletions

2
.cade Normal file
View file

@ -0,0 +1,2 @@
pure
load flake

51
Cargo.lock generated
View file

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

View file

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

270
src/actions.rs Normal file
View file

@ -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<String>)>;
#[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<String>) -> Result<RawEnv> {
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<CadeActions> {
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<CadeActions> {
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<String>) -> Result<CadeActions> {
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<RawEnv> {
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<RawEnv> {
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::<Vec<String>>(),
)
})
.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::<HashMap<String, String>>();
use crate::core::{read_cade, realise};
let mut cascade = HashMap::new();
cascade.insert(
current_dir.clone(),
read_cade(&current_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(&current_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<String>| {
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<PathBuf> {
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)
}

14
src/cli/clap.rs Normal file
View file

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

2
src/cli/mod.rs Normal file
View file

@ -0,0 +1,2 @@
pub mod clap;
pub mod parse;

98
src/cli/parse.rs Normal file
View file

@ -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<Self, Self::Err> {
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<String> = 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)
}
}
}

44
src/core.rs Normal file
View file

@ -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<Vec<Keyword>> {
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<PathBuf, Vec<Keyword>>) -> Result<Vec<CadeActions>> {
let mut actions: Vec<CadeActions> = 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)
}

View file

@ -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<String>),
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<Self, Self::Err> {
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<String> = 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<Vec<Keyword>> {
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<String>) -> Result<Vec<EnvAction>> {
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<Vec<EnvAction>> {
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<Vec<EnvAction>> {
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<String>) -> Result<Vec<EnvAction>> {
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<Vec<EnvAction>> {
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<PathBuf, Vec<Keyword>>) -> Result<Vec<EnvAction>> {
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::<HashMap<String, String>>();
let mut cascade = HashMap::new();
cascade.insert(
current_dir.clone(),
read_cade(&current_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(&current_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<PathBuf> {
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 => {

47
src/types.rs Normal file
View file

@ -0,0 +1,47 @@
use std::path::PathBuf;
#[derive(Debug)]
pub enum CadeActions {
Purify,
Environ(Vec<Env>),
Hook(Hook),
}
#[derive(Debug)]
pub struct Env {
pub name: String,
pub value: Vec<String>,
pub origin: PathBuf,
pub kind: Loadable,
pub layer: u8,
}
#[derive(Debug)]
pub enum Keyword {
Pure,
Call(Vec<String>),
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<String>,
pub kind: HookType,
}