max / tagtree
git clone https://makenot.work/git/max/tagtree.git
git clone git@ssh.makenot.work:max/tagtree.git
| Name | Size | |
|---|---|---|
| benches/ | ||
| docs/ | ||
| src/ | ||
| .gitignore | 19 B | |
| Cargo.lock | 14.1 KB | |
| Cargo.toml | 518 B | |
| LICENSE | 4.5 KB | |
| README.md | 3.0 KB |
README
tagtree
Hierarchical dot-notation tag standard for Rust. Validation, parsing, tree operations, SQL helpers, and autocomplete — with zero runtime dependencies.
Tag format
Tags are lowercase dot-separated strings representing a hierarchy:
genre.electronic.house
work.meeting.standup
news.tech.rust
Segments allow lowercase alphanumeric characters and hyphens ([a-z0-9-]). No empty segments, no leading/trailing dots, no consecutive dots.
Per-app configuration
Each consumer defines a TagConfig controlling depth, length, and semantic prefix rules:
use tagtree::TagConfig;
// audiofiles: deep hierarchy, namespace-driven
const AF_TAGS: TagConfig = TagConfig { max_depth: 5, max_length: 100, semantic_depth: 1 };
// goingson: shallow tags, no required prefix
const GO_TAGS: TagConfig = TagConfig { max_depth: 3, max_length: 60, semantic_depth: 0 };
max_depth— maximum number of segmentsmax_length— maximum character length of the entire tagsemantic_depth— number of leading segments that carry dispatch meaning (0 = free-form)
API
Validation
validate_with(tag, config)— validate against aTagConfigvalidate(tag)— validate with defaults (depth 5, length 100, no semantic prefix)
Parsing
parent(tag)—"a.b.c"->Some("a.b")leaf(tag)—"a.b.c"->"c"depth(tag)—"a.b.c"->3segment(tag, i)— extract segment by indexprefix_at_depth(tag, n)— firstnsegmentsancestors(tag)—"a.b.c"->["a", "a.b"]
Tree operations
is_ancestor_of(a, b)— true ifais a prefix ofbcommon_ancestor(a, b)— longest shared prefixchildren_at_prefix(prefix, tags)— direct children one level below prefixsubtree(prefix, tags)— all descendants of prefixrename_prefix(old, new, tag)— swap a tag’s prefix
Semantic splitting
semantic_prefix(tag, depth)— namespace portion ("genre.rock"with depth 1 ->"genre")free_suffix(tag, depth)— value portion ("genre.rock"with depth 1 ->"rock")
SQL helpers
escape_like(s)— escape%,_,\for safe embedding inLIKEpatternslike_descendant_pattern(prefix)— buildprefix.%pattern for hierarchy queries
Works identically on SQLite and PostgreSQL.
TagIndex (autocomplete)
In-memory sorted index with binary-search lookup and two-tier suggestion:
use tagtree::TagIndex;
let index = TagIndex::new(vec![
"genre.electronic.house".into(),
"genre.electronic.techno".into(),
"genre.rock.punk".into(),
]);
// Path prefix: "genre.electronic" matches descendants
let results = index.suggest("genre.electronic", 10);
assert_eq!(results, vec!["genre.electronic.house", "genre.electronic.techno"]);
// Segment prefix: "gen" matches by segment start
let results = index.suggest("gen", 10);
assert!(results.contains(&"genre.electronic.house"));
Dependencies
Zero runtime dependencies. std-only. Criterion is a dev-dependency for benchmarks.
License
PolyForm Noncommercial 1.0.0