Bootstrap UCXL Core: Implementation of UCXLAddress and TemporalAxis in Rust

This commit is contained in:
anthonyrawlins
2026-03-03 14:18:16 +11:00
parent 68a489b64d
commit 68df0bddc7
38 changed files with 238 additions and 0 deletions

192
UCXL/src/lib.rs Normal file
View File

@@ -0,0 +1,192 @@
// UCXL Core Data Structures
use std::collections::HashMap;
use std::fmt;
use std::str::FromStr;
/// Represents the temporal axis in a UCXL address.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum TemporalAxis {
/// Present ("#")
Present,
/// Past ("~~")
Past,
/// Future ("^^")
Future,
}
impl FromStr for TemporalAxis {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"#" => Ok(TemporalAxis::Present),
"~~" => Ok(TemporalAxis::Past),
"^^" => Ok(TemporalAxis::Future),
_ => Err(format!("Invalid temporal axis: {}", s)),
}
}
}
impl fmt::Display for TemporalAxis {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
TemporalAxis::Present => "#",
TemporalAxis::Past => "~~",
TemporalAxis::Future => "^^",
};
write!(f, "{}", s)
}
}
/// Represents a parsed UCXL address.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct UCXLAddress {
pub agent: String,
pub role: Option<String>,
pub project: String,
pub task: String,
pub temporal: TemporalAxis,
pub path: String,
}
impl FromStr for UCXLAddress {
type Err = String;
fn from_str(address: &str) -> Result<Self, Self::Err> {
// Ensure the scheme is correct
let scheme_split: Vec<&str> = address.splitn(2, "://").collect();
if scheme_split.len() != 2 || scheme_split[0] != "ucxl" {
return Err("Address must start with 'ucxl://'".into());
}
let remainder = scheme_split[1];
// Split at the first '@' to separate agent/role from project/task
let parts: Vec<&str> = remainder.splitn(2, '@').collect();
if parts.len() != 2 {
return Err("Missing '@' separating agent and project".into());
}
// Agent and optional role
let agent_part = parts[0];
let mut agent_iter = agent_part.splitn(2, ':');
let agent = agent_iter.next().unwrap().to_string();
let role = agent_iter.next().map(|s| s.to_string());
// Project and task
let project_task_part = parts[1];
// Find the first '/' that starts the temporal segment and path
let slash_idx = project_task_part
.find('/')
.ok_or("Missing '/' before temporal segment and path")?;
let (proj_task, after_slash) = project_task_part.split_at(slash_idx);
let mut proj_task_iter = proj_task.splitn(2, ':');
let project = proj_task_iter.next().ok_or("Missing project")?.to_string();
let task = proj_task_iter.next().ok_or("Missing task")?.to_string();
// after_slash starts with '/', remove it
let after = &after_slash[1..];
// Temporal segment is up to the next '/' if present
let temporal_end = after
.find('/')
.ok_or("Missing '/' after temporal segment")?;
let temporal_str = &after[..temporal_end];
let temporal = TemporalAxis::from_str(temporal_str)?;
// The rest is the resource path
let path = after[temporal_end + 1..].to_string();
Ok(UCXLAddress {
agent,
role,
project,
task,
temporal,
path,
})
}
}
impl fmt::Display for UCXLAddress {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let role_part = if let Some(r) = &self.role {
format!(":{}", r)
} else {
"".to_string()
};
write!(
f,
"ucxl://{}{}@{}:{}/{}{}",
self.agent,
role_part,
self.project,
self.task,
self.temporal,
if self.path.is_empty() {
"".to_string()
} else {
format!("/{}", self.path)
}
)
}
}
/// Simple inmemory metadata store mapping a file path to a metadata string.
pub trait MetadataStore {
fn get(&self, path: &str) -> Option<&String>;
fn set(&mut self, path: &str, metadata: String);
fn remove(&mut self, path: &str) -> Option<String> {
None
}
}
/// A concrete inmemory implementation using a HashMap.
pub struct InMemoryMetadataStore {
map: HashMap<String, String>,
}
impl InMemoryMetadataStore {
pub fn new() -> Self {
InMemoryMetadataStore {
map: HashMap::new(),
}
}
}
impl MetadataStore for InMemoryMetadataStore {
fn get(&self, path: &str) -> Option<&String> {
self.map.get(path)
}
fn set(&mut self, path: &str, metadata: String) {
self.map.insert(path.to_string(), metadata);
}
fn remove(&mut self, path: &str) -> Option<String> {
self.map.remove(path)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_temporal_from_str() {
assert_eq!(TemporalAxis::from_str("#").unwrap(), TemporalAxis::Present);
assert_eq!(TemporalAxis::from_str("~~").unwrap(), TemporalAxis::Past);
assert_eq!(TemporalAxis::from_str("^^").unwrap(), TemporalAxis::Future);
}
#[test]
fn test_ucxl_address_parsing() {
let addr_str = "ucxl://alice:admin@myproj:task1/#/docs/readme.md";
let addr = UCXLAddress::from_str(addr_str).unwrap();
assert_eq!(addr.agent, "alice");
assert_eq!(addr.role, Some("admin".to_string()));
assert_eq!(addr.project, "myproj");
assert_eq!(addr.task, "task1");
assert_eq!(addr.temporal, TemporalAxis::Present);
assert_eq!(addr.path, "docs/readme.md");
assert_eq!(addr.to_string(), addr_str);
}
#[test]
fn test_metadata_store() {
let mut store = InMemoryMetadataStore::new();
store.set("/foo.txt", "meta".into());
assert_eq!(store.get("/foo.txt"), Some(&"meta".to_string()));
store.remove("/foo.txt");
assert!(store.get("/foo.txt").is_none());
}
}