From b2e0936d4ad4e055502b4380145b366fbf9534b5 Mon Sep 17 00:00:00 2001 From: atagen Date: Thu, 21 Aug 2025 23:51:24 +1000 Subject: [PATCH] init --- .envrc | 1 + .gitignore | 2 + Cargo.lock | 501 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 18 ++ flake.lock | 43 +++++ flake.nix | 44 +++++ src/main.rs | 373 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 982 insertions(+) create mode 100644 .envrc create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 src/main.rs diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2d5df85 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +.direnv diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..f68619d --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,501 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anstream" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bincode" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" +dependencies = [ + "bincode_derive", + "serde", + "unty", +] + +[[package]] +name = "bincode_derive" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09" +dependencies = [ + "virtue", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cade" +version = "0.1.0" +dependencies = [ + "anyhow", + "bincode", + "clap", + "microxdg", + "sled", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "clap" +version = "4.5.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc0e74a703892159f5ae7d3aac52c8e6c392f5ae5f359c70b5881d60aaac318" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "libc" +version = "0.2.175" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "microxdg" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0ba94daabea2c803df477d4dc1d37a6f15726e4bed53be7067022ddb89328e" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "proc-macro2" +version = "1.0.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d61789d7719defeb74ea5fe81f2fdfdbd28a803847077cecce2ff14e1472f6f1" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sled" +version = "0.34.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935" +dependencies = [ + "crc32fast", + "crossbeam-epoch", + "crossbeam-utils", + "fs2", + "fxhash", + "libc", + "log", + "parking_lot", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unty" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "virtue" +version = "0.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..01000ff --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "cade" +version = "0.1.0" +edition = "2024" + +[profile.release] +strip = true +opt-level = "z" +codegen-units = 1 +# lto = true +# panic = "abort" + +[dependencies] +anyhow = "1.0.99" +bincode = "2.0.1" +clap = { version = "4.5.45", features = ["derive"] } +microxdg = "0.2.0" +sled = "0.34.7" diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..79d4936 --- /dev/null +++ b/flake.lock @@ -0,0 +1,43 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1755175540, + "narHash": "sha256-V0j2S1r25QnbqBLzN2Rg/dKKil789bI3P3id7bDPVc4=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "a595dde4d0d31606e19dcec73db02279db59d201", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs", + "systems": "systems" + } + }, + "systems": { + "locked": { + "lastModified": 1689347949, + "narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=", + "owner": "nix-systems", + "repo": "default-linux", + "rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default-linux", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..ce9d71d --- /dev/null +++ b/flake.nix @@ -0,0 +1,44 @@ +{ + inputs.nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; + inputs.systems.url = "github:nix-systems/default-linux"; + + outputs = + { + self, + nixpkgs, + systems, + }: + let + forAllSystems = + function: nixpkgs.lib.genAttrs (import systems) (system: function nixpkgs.legacyPackages.${system}); + in + { + devShells = forAllSystems (pkgs: { + default = pkgs.mkShell { + RUSTFLAGS = "-C prefer-dynamic=yes"; + packages = builtins.attrValues { + inherit (pkgs) + rustc + cargo + rust-analyzer + rustfmt + clippy + ; + }; + }; + }); + packages = forAllSystems (pkgs: { + default = + let + project = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package; + in + pkgs.rustPlatform.buildRustPackage (finalAttrs: { + pname = project.name; + version = project.version; + src = ./.; + cargoLock.lockFile = ./Cargo.lock; + RUSTFLAGS = "-C prefer-dynamic=yes"; + }); + }); + }; +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..c4a78ec --- /dev/null +++ b/src/main.rs @@ -0,0 +1,373 @@ +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) +} + +fn try_main() -> Result<()> { + let args = Cli::parse(); + use Action::*; + match args.action { + // TODO recursively ascend until we hit a top level .cade, then activate downwards + Activate => do_activation().context("activate cade environment")?, + Allow => set_permission(Permission::Allowed)?, + Disallow => set_permission(Permission::Disallowed)?, + Edit => { + let editor = std::env::var("EDITOR").context("find EDITOR variable")?; + let mut session = std::process::Command::new(Into::::into(editor)) + .arg(Into::::into(".cade")) + .spawn() + .context("spawn editor process")?; + session.wait().context("wait for editor process")?; + set_permission(Permission::Allowed)?; + } + }; + Ok(()) +} + +fn main() { + if let Err(e) = try_main() { + eprintln!("failed to {e}\n{}", e.root_cause()); + std::process::exit(1); + } +}