tagtree
tagtree is an in-memory collection for assigning items to hierarchical tag
paths. It fits bookmarks, documents, tasks, notes, or any records that may
belong to more than one branch, such as work/rust/async and favorite.
The core idea
An item is assigned to one or more complete paths. Querying a parent includes all descendants, but does not create extra assignments:
tags
|-- work -> item 1
| `-- rust -> item 2
| `-- async -> item 3
|-- favorite -> item 1
`-- (untagged) -> item 4
items_under("work") returns items 1, 2, and 3. The item assigned to
work/rust is not separately assigned to work; work is its ancestor for
queries. An item can also appear in another branch, as item 1 does above.
Installation
Add the published crate to your application:
[]
= "0.3"
The crate requires Rust 1.85 or newer.
Quick start
Use TagTree for
the normal item-centric workflow:
use TagTree;
set_tags trims, skips blank values, removes duplicates, validates paths, and
stores an empty tag list as an untagged item. Items are set-like, so a query
returns each matching item once. items_under returns values in descending
order; T therefore needs Eq + Hash + Clone + Ord for queries and subtree
mutations.
Updating tags
move_subtree moves every path and item below a path. If the destination
already exists, the two subtrees are merged. remove_subtree removes the
subtree while preserving unrelated tags on affected items:
use TagTree;
The returned ItemTags
values contain each affected item and its resulting tags, which is useful for
updating a database or UI. Removing an unknown item with remove_item is a
no-op; unknown paths return Error::PathNotFound. A rejected mutation leaves
the tree unchanged. remove_subtree("") returns Error::CannotRemoveRoot
because untagged items live at the root path; move_subtree uses "" as the
destination to strip the leading segment.
Paths
Paths use / as the separator. A valid path must not start or end with /,
contain //, or contain a blank segment. The empty path ("") is the root.
The path helpers are available at the crate root and in the [path] module:
use normalize_paths;
let paths = normalize_paths?;
assert_eq!;
# Ok::
normalize_path handles one value and returns None for blank input.
validate_path checks one path without changing it. is_within tests whether
one path is a descendant of another path, including equality; the empty path
is the root and contains every path.
API reference and license
Licensed under the MIT License.