feat(machoke): add in-memory store #4
@@ -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.
|
- 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.
|
- 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.
|
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.
|
||||||
|
|||||||
@@ -9,7 +9,11 @@ let
|
|||||||
cfg = config.modules.shell.fish;
|
cfg = config.modules.shell.fish;
|
||||||
user = config.user.name;
|
user = config.user.name;
|
||||||
shortcuts = config.registry.shell-shortcut.${config.networking.hostName};
|
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;
|
commandsOf = shortcuts: lib.mapAttrs (_name: shortcut: shortcut.command) shortcuts;
|
||||||
|
exportEnvVar = name: value: ''
|
||||||
|
set -gx ${name} ${lib.escapeShellArg value}
|
||||||
|
'';
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options.modules.shell.fish = {
|
options.modules.shell.fish = {
|
||||||
@@ -99,7 +103,10 @@ in
|
|||||||
};
|
};
|
||||||
|
|
||||||
# Rendered by home-manager into ~/.config/fish/config.fish.
|
# 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) ]
|
||||||
|
);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,13 +23,13 @@ in
|
|||||||
options.modules.shell.rust.enable = lib.mkEnableOption "Rust tools for the primary user's shell environment";
|
options.modules.shell.rust.enable = lib.mkEnableOption "Rust tools for the primary user's shell environment";
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
|
registry.env-var.${config.networking.hostName}.RUST_TOOLCHAIN_ROOT = "${toolchain}";
|
||||||
|
|
||||||
home-manager.users.${user} = {
|
home-manager.users.${user} = {
|
||||||
home.packages = [
|
home.packages = [
|
||||||
toolchain
|
toolchain
|
||||||
pkgs.rust-analyzer
|
pkgs.rust-analyzer
|
||||||
];
|
];
|
||||||
|
|
||||||
home.sessionVariables.RUST_TOOLCHAIN_ROOT = "${toolchain}";
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
4
configs/nixos/registry/env-var.nix
Normal file
4
configs/nixos/registry/env-var.nix
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{ lib, ... }:
|
||||||
|
{
|
||||||
|
type = lib.types.str;
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ date-created: 2026-08-31 21:32
|
|||||||
|
|
||||||
# Falinks Public Surfaces
|
# 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`: the library to resolve short links to fully qualified domain names.
|
||||||
- `falinks` cli:
|
- `falinks` cli:
|
||||||
- Non-interactive (use for one off requests or by agents)
|
- Non-interactive (use for one off requests or by agents)
|
||||||
|
|||||||
46
docs/02 notes/machoke public surface.md
Normal file
46
docs/02 notes/machoke public surface.md
Normal file
@@ -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 |
|
||||||
|
| --------------- | ----------------------- | ---------------- | ---------------------------------------------------- |
|
||||||
|
| `<constructor>` | `machoke::Options` | `Result<Machoke<T>>` | Creates the `Machoke` object. |
|
||||||
|
| `get` | `key: String` | `Result<T>` | 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<bool>` | Deletes the value if it exists for `key`. |
|
||||||
|
| `list` | | `Result<Vec<machoke::Entry<T>>>` | 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.
|
||||||
14
docs/03 wrk/machoke.md
Normal file
14
docs/03 wrk/machoke.md
Normal file
@@ -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]]
|
||||||
@@ -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(
|
rust_library(
|
||||||
name = "machoke_lib",
|
name = "machoke_lib",
|
||||||
@@ -6,8 +6,8 @@ rust_library(
|
|||||||
srcs = ["lib.rs"],
|
srcs = ["lib.rs"],
|
||||||
)
|
)
|
||||||
|
|
||||||
rust_binary(
|
rust_test(
|
||||||
name = "machoke",
|
name = "machoke_test",
|
||||||
srcs = ["main.rs"],
|
srcs = ["lib_test.rs"],
|
||||||
deps = [":machoke_lib"],
|
deps = [":machoke_lib"],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ edition = "2024"
|
|||||||
[lib]
|
[lib]
|
||||||
path = "lib.rs"
|
path = "lib.rs"
|
||||||
|
|
||||||
[[bin]]
|
[[test]]
|
||||||
name = "machoke"
|
name = "lib_test"
|
||||||
path = "main.rs"
|
path = "lib_test.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
@@ -1,3 +1,86 @@
|
|||||||
pub fn greeting() -> &'static str {
|
use std::collections::HashMap;
|
||||||
"Hello from machoke"
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct Machoke<T> {
|
||||||
|
entries: HashMap<String, T>,
|
||||||
|
_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<T> Machoke<T> {
|
||||||
|
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<bool> {
|
||||||
|
validate_key(key)?;
|
||||||
|
|
||||||
|
Ok(self.entries.remove(key).is_some())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list(&self) -> Result<Vec<Entry<'_, T>>> {
|
||||||
|
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(())
|
||||||
}
|
}
|
||||||
|
|||||||
147
src/machoke/lib_test.rs
Normal file
147
src/machoke/lib_test.rs
Normal file
@@ -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<String> = 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<String> = 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<String> = Machoke::new(Options::default());
|
||||||
|
|
||||||
|
let deleted = machoke.delete("home").unwrap();
|
||||||
|
|
||||||
|
assert!(!deleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn delete_empty_key_returns_invalid_key() {
|
||||||
|
let mut machoke: Machoke<String> = 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::<Vec<_>>();
|
||||||
|
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<String> = Machoke::new(Options::default());
|
||||||
|
|
||||||
|
let entries = machoke.list().unwrap();
|
||||||
|
|
||||||
|
assert!(entries.is_empty());
|
||||||
|
}
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
fn main() {
|
|
||||||
println!("{}", machoke::greeting());
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user