diff --git a/AGENTS.md b/AGENTS.md index 51eb64d..bb8b4cb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -37,3 +37,5 @@ - After an explicit squash or rewrite request on a pull request, subsequent pull-request updates still use follow-up commits unless the user explicitly asks for another rewrite. - Starlark top-level rule and extension declarations cannot reference implementation functions before those functions are assigned. Keep executable public interface declarations after their implementation symbols, or validate before committing a top-down rewrite. +- `rules_rust` `rust_test` targets cannot set both `crate` and `srcs`. + Use one input style per test target. diff --git a/configs/nixos/modules/shell/fish/default.nix b/configs/nixos/modules/shell/fish/default.nix index c5063b4..4ee0279 100644 --- a/configs/nixos/modules/shell/fish/default.nix +++ b/configs/nixos/modules/shell/fish/default.nix @@ -9,7 +9,11 @@ let cfg = config.modules.shell.fish; user = config.user.name; shortcuts = config.registry.shell-shortcut.${config.networking.hostName}; + envVars = config.registry.env-var.${config.networking.hostName}; commandsOf = shortcuts: lib.mapAttrs (_name: shortcut: shortcut.command) shortcuts; + exportEnvVar = name: value: '' + set -gx ${name} ${lib.escapeShellArg value} + ''; in { options.modules.shell.fish = { @@ -99,7 +103,10 @@ in }; # Rendered by home-manager into ~/.config/fish/config.fish. - interactiveShellInit = builtins.readFile ./config.fish; + interactiveShellInit = lib.concatStringsSep "\n" ( + (lib.mapAttrsToList exportEnvVar envVars) + ++ [ (builtins.readFile ./config.fish) ] + ); }; }; }; diff --git a/configs/nixos/modules/shell/rust.nix b/configs/nixos/modules/shell/rust.nix index 050b508..36c9c8d 100644 --- a/configs/nixos/modules/shell/rust.nix +++ b/configs/nixos/modules/shell/rust.nix @@ -23,13 +23,13 @@ in options.modules.shell.rust.enable = lib.mkEnableOption "Rust tools for the primary user's shell environment"; config = lib.mkIf cfg.enable { + registry.env-var.${config.networking.hostName}.RUST_TOOLCHAIN_ROOT = "${toolchain}"; + home-manager.users.${user} = { home.packages = [ toolchain pkgs.rust-analyzer ]; - - home.sessionVariables.RUST_TOOLCHAIN_ROOT = "${toolchain}"; }; }; } diff --git a/configs/nixos/registry/env-var.nix b/configs/nixos/registry/env-var.nix new file mode 100644 index 0000000..8a6fd91 --- /dev/null +++ b/configs/nixos/registry/env-var.nix @@ -0,0 +1,4 @@ +{ lib, ... }: +{ + type = lib.types.str; +} diff --git a/docs/02 notes/falinks public surfaces.md b/docs/02 notes/falinks public surfaces.md index 94b2be9..c75947b 100644 --- a/docs/02 notes/falinks public surfaces.md +++ b/docs/02 notes/falinks public surfaces.md @@ -4,7 +4,7 @@ date-created: 2026-08-31 21:32 # Falinks Public Surfaces -The project has three components: +The project has two components: - `Falinks`: the library to resolve short links to fully qualified domain names. - `falinks` cli: - Non-interactive (use for one off requests or by agents) diff --git a/docs/02 notes/machoke public surface.md b/docs/02 notes/machoke public surface.md new file mode 100644 index 0000000..ec7f72b --- /dev/null +++ b/docs/02 notes/machoke public surface.md @@ -0,0 +1,46 @@ +--- +date-created: 2026-09-01 20:17 +--- + +# Machoke Public Surface + +Machoke's primary goal is to be a common storage interface for different types of storage backends. + +The first Machoke user is [[falinks|Falinks]]. Its storage needs are simple. +It just manages a flat list of keyed entries. + +The first backend for Machoke is just going to be a simple in-memory map that gets serialized on shutdown and loaded on startup. + +## Design + +### `Machoke` public interface + +| Method | Parameters | Returns | Description | +| --------------- | ----------------------- | ---------------- | ---------------------------------------------------- | +| `` | `machoke::Options` | `Result>` | Creates the `Machoke` object. | +| `get` | `key: String` | `Result` | Returns the stored value for the `key` if it exists. | +| `set` | `key: String, value: T` | `Result<()>` | Stores the value using `key`. | +| `delete` | `key: String` | `Result` | Deletes the value if it exists for `key`. | +| `list` | | `Result>>` | Retrieves all stored, raw values. | + + +#### machoke::Options + +| Key | Type | Default | Description | +| --- | --- | --- | --- | +| `backend` | `Backend` | `Backend::Simple` | The storage backend to use. | +| `path` | `std::path::PathBuf` | tbd | The path to the file/directory for the backend to use. | + + +#### machoke::Entry + +| Field | Type | Description | +| --- | --- | --- | +| `key` | `String` | The entry key. | +| `value` | `T` | The stored value. | + +#### machoke::Error + +- `NotFound`: no value exists for the provided key. +- `InvalidKey`: the provided key cannot be used. +- `Backend`: the storage backend failed. diff --git a/docs/03 wrk/machoke.md b/docs/03 wrk/machoke.md new file mode 100644 index 0000000..3f8ad39 --- /dev/null +++ b/docs/03 wrk/machoke.md @@ -0,0 +1,14 @@ +--- +date-created: 2026-09-01 20:17 +--- + +# Machoke + +## Current focus + +Implement the simple backend. + +## References + +- [[machoke public surface]] +- [[falinks public surfaces]] diff --git a/src/machoke/BUILD b/src/machoke/BUILD index 5d0806d..1f4a86d 100644 --- a/src/machoke/BUILD +++ b/src/machoke/BUILD @@ -1,4 +1,4 @@ -load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library") +load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") rust_library( name = "machoke_lib", @@ -6,8 +6,8 @@ rust_library( srcs = ["lib.rs"], ) -rust_binary( - name = "machoke", - srcs = ["main.rs"], +rust_test( + name = "machoke_test", + srcs = ["lib_test.rs"], deps = [":machoke_lib"], ) diff --git a/src/machoke/Cargo.toml b/src/machoke/Cargo.toml index 1beb8f9..c121f08 100644 --- a/src/machoke/Cargo.toml +++ b/src/machoke/Cargo.toml @@ -6,8 +6,8 @@ edition = "2024" [lib] path = "lib.rs" -[[bin]] -name = "machoke" -path = "main.rs" +[[test]] +name = "lib_test" +path = "lib_test.rs" [dependencies] diff --git a/src/machoke/lib.rs b/src/machoke/lib.rs index 33a6292..419072d 100644 --- a/src/machoke/lib.rs +++ b/src/machoke/lib.rs @@ -1,3 +1,86 @@ -pub fn greeting() -> &'static str { - "Hello from machoke" +use std::collections::HashMap; +use std::path::PathBuf; + +pub type Result = std::result::Result; + +#[derive(Debug, Default)] +pub struct Machoke { + entries: HashMap, + _options: Options, +} + +#[derive(Debug, Default)] +pub struct Options { + pub backend: Backend, + pub path: PathBuf, +} + +#[derive(Debug, Default)] +pub enum Backend { + #[default] + Simple, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Entry<'a, T> { + pub key: &'a str, + pub value: &'a T, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum Error { + NotFound { key: String }, + InvalidKey { key: String }, + Backend { message: String }, +} + +impl Machoke { + pub fn new(options: Options) -> Self { + Self { + entries: HashMap::new(), + _options: options, + } + } + + pub fn get(&self, key: &str) -> Result<&T> { + validate_key(key)?; + + self.entries.get(key).ok_or_else(|| Error::NotFound { + key: key.to_string(), + }) + } + + pub fn set(&mut self, key: String, value: T) -> Result<()> { + validate_key(&key)?; + + self.entries.insert(key, value); + Ok(()) + } + + pub fn delete(&mut self, key: &str) -> Result { + validate_key(key)?; + + Ok(self.entries.remove(key).is_some()) + } + + pub fn list(&self) -> Result>> { + Ok(self + .entries + .iter() + .map(|(key, value)| Entry { + key: key.as_str(), + value, + }) + .collect()) + } +} + +fn validate_key(key: &str) -> Result<()> { + if key.is_empty() { + return Err(Error::InvalidKey { + key: key.to_string(), + }); + } + + Ok(()) } diff --git a/src/machoke/lib_test.rs b/src/machoke/lib_test.rs new file mode 100644 index 0000000..5d67581 --- /dev/null +++ b/src/machoke/lib_test.rs @@ -0,0 +1,147 @@ +use machoke::*; + +#[test] +fn get_existing_key_returns_stored_value() { + let mut machoke = Machoke::new(Options::default()); + machoke.set("home".to_string(), "plex".to_string()).unwrap(); + + let value = machoke.get("home").unwrap(); + + assert_eq!(value, "plex"); +} + +#[test] +fn get_missing_key_returns_not_found() { + let machoke: Machoke = Machoke::new(Options::default()); + + let result = machoke.get("home"); + + assert_eq!( + result, + Err(Error::NotFound { + key: "home".to_string(), + }) + ); +} + +#[test] +fn get_empty_key_returns_invalid_key() { + let machoke: Machoke = Machoke::new(Options::default()); + + let result = machoke.get(""); + + assert_eq!( + result, + Err(Error::InvalidKey { + key: "".to_string(), + }) + ); +} + +#[test] +fn set_new_key_stores_value() { + let mut machoke = Machoke::new(Options::default()); + + machoke.set("home".to_string(), "plex".to_string()).unwrap(); + + assert_eq!(machoke.get("home").unwrap(), "plex"); +} + +#[test] +fn set_existing_key_overwrites_value() { + let mut machoke = Machoke::new(Options::default()); + machoke + .set("home".to_string(), "jellyfin".to_string()) + .unwrap(); + + machoke.set("home".to_string(), "plex".to_string()).unwrap(); + + assert_eq!(machoke.get("home").unwrap(), "plex"); +} + +#[test] +fn set_empty_key_returns_invalid_key() { + let mut machoke = Machoke::new(Options::default()); + + let result = machoke.set("".to_string(), "plex".to_string()); + + assert_eq!( + result, + Err(Error::InvalidKey { + key: "".to_string(), + }) + ); +} + +#[test] +fn delete_existing_key_removes_value() { + let mut machoke = Machoke::new(Options::default()); + machoke.set("home".to_string(), "plex".to_string()).unwrap(); + + let deleted = machoke.delete("home").unwrap(); + + assert!(deleted); + assert_eq!( + machoke.get("home"), + Err(Error::NotFound { + key: "home".to_string(), + }) + ); +} + +#[test] +fn delete_missing_key_returns_false() { + let mut machoke: Machoke = Machoke::new(Options::default()); + + let deleted = machoke.delete("home").unwrap(); + + assert!(!deleted); +} + +#[test] +fn delete_empty_key_returns_invalid_key() { + let mut machoke: Machoke = Machoke::new(Options::default()); + + let result = machoke.delete(""); + + assert_eq!( + result, + Err(Error::InvalidKey { + key: "".to_string(), + }) + ); +} + +#[test] +fn list_populated_store_returns_all_entries() { + let mut machoke = Machoke::new(Options::default()); + machoke.set("home".to_string(), "plex".to_string()).unwrap(); + machoke + .set("docs".to_string(), "readme".to_string()) + .unwrap(); + + let mut entries = machoke + .list() + .unwrap() + .into_iter() + .map(|entry| (entry.key.to_string(), entry.value.clone())) + .collect::>(); + entries.sort(); + + assert_eq!( + entries, + vec![ + ("docs".to_string(), "readme".to_string()), + ("home".to_string(), "plex".to_string()), + ] + ); +} + +#[test] +fn list_empty_store_returns_empty_entries() { + let machoke: Machoke = Machoke::new(Options::default()); + + let entries = machoke.list().unwrap(); + + assert!(entries.is_empty()); +} diff --git a/src/machoke/main.rs b/src/machoke/main.rs deleted file mode 100644 index ddac719..0000000 --- a/src/machoke/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("{}", machoke::greeting()); -}