Skip to main content

Grain

Enum Grain 

Source
pub enum Grain {
    Year,
    Month,
    Day,
    Initial(usize),
}
Expand description

What a grouped arrangement sorts entries into groups by.

prov’s own, not a mirror of it. This used to be a redeclaration — the crate sits below the workspace layer and must stay portable to wasm32-unknown-unknown, so it kept its own DateGrain and Grouping with the spellings and prefix lengths copied across, on the reasoning that a site grouped “by year” must cut dates the same way the app’s lens does or the published archive reads differently from the vault it came from.

Since prov 0.5 the grouping engine is prov-views, which reaches nothing that can write and is already in this crate’s dependency graph. So the way to keep the two identical is to stop having two: the published site now groups through the same Grouping::keys_of the vault does, and “identical” is a fact rather than a promise two copies make to each other. A coarsening: how finely a value is cut into groups.

Not a date vocabulary. A grain is any many-to-one function from a value to group keys, and the calendar grains are one family of them — year is “the year this date names”, and Initial is “the first n characters” with no such condition. What makes something a grain is the two properties below, not what it is about.

§Two properties, and what each one licenses

  • cuts — value → keys. This is all by needs, because grouping is a reading operation with no invariant to keep. Usually one key; an interval (1918/1922) is under every year it spans, which is what makes it keys.
  • chain — the coarser grains this one refines, coarsest first. This is what nest needs, and it is a strictly stronger requirement: nesting builds a hierarchy of index documents, so each level’s key must be determined by the finer level’s (2026-07-242026-072026, AdaAdA). A coarsening with no such chain can group but cannot nest.

The second constraint is prov’s, not taste. nest files a record into the spanning relation, which is single-parent, so a nest chain must also be single-valued per document — see ViewSpec::nest_route, which returns None rather than guessing which of a multi-valued field’s values a document should be filed under.

§Adding a grain

The rule is the one crate::filter uses for predicates: a concrete lens that cannot otherwise be said, not a shape that seems likely to be wanted. initial earns its place as the A–Z index every list of names and places eventually wants. A numeric bucket (ratings by tens) is the obvious next one and is deliberately not here: nobody has asked for it, and it would arrive with a problem the calendar grains do not have — its keys sort lexically as 0, 10, 100, 20, so it needs group ordering to become grain-aware, which is really the deferred sort: axis wearing a disguise.

Variants§

§

Year

2026 — the default, and what a lifetime of entries wants.

§

Month

2026-07.

§

Day

2026-07-25.

§

Initial(usize)

The first n characters, upper-cased — the A–Z index.

Upper-casing is a deliberate normalization rather than a faithful cut: an alphabetical index that files ada apart from Ada is not an index. It is the same kind of choice a date cut makes when it reports 2026 for a value that says 2026-07-24; a group key describes a bucket, not a value that appears in the data.

Implementations§

Source§

impl Grain

Source

pub fn as_config_str(self) -> Option<&'static str>

The config spelling, when this grain has a bare-word one.

None for a parameterized grain that is not at its default — write to_value instead, which always round-trips.

Source

pub fn from_config_str(text: &str) -> Option<Grain>

Parse a bare-word config spelling. Unknown text is not silently defaulted — a by: yearr that quietly grouped by year would look applied and be wrong, which is the failure a config linter exists to prevent.

Source

pub fn parse(value: &Value) -> Option<Grain>

Read a by:/nest: value: a bare word, or a one-key mapping naming a parameterized grain ({ initial: 2 }).

A parameter of zero is rejected rather than clamped: { initial: 0 } would put every document in one group called “”, which is a view that has stopped being one.

Source

pub fn to_value(self) -> Value

The value this grain writes back as — a bare word where it has one, a one-key mapping otherwise.

Source

pub fn display(self) -> String

How this grain reads in a listing (month, initial 2).

Source

pub fn chain(self) -> Vec<Grain>

The grains to nest through to reach self, coarsest first.

Filing at month grain means a year index and then a month index inside it: a month index that is not inside its year is not where anyone looks for it. The alphabetical case is the same shape — filing at initial 2 means an A index holding an Ad index.

Each step must be determined by the one after it, which is what makes the hierarchy well defined. That is why this is a property of the grain rather than something a caller can assemble: an arbitrary sequence of coarsenings is not a nest.

Source

pub fn cuts(self, value: &str) -> Vec<String>

Every group key value falls under at this grain — empty when the value does not reach it.

The calendar grains validate rather than taking a blind prefix, which is what keeps by: usable on a view whose field is only usually a date: banana cut to a year would otherwise group under bana, a group key that looks like data. A value this rejects falls to the ungrouped bucket, where it is visible as something that did not sort.

What they validate as is EDTF, so 1913~ is the group 1913, 192X is a group of its own, 1918/1922 is five groups, and XXXX is none — the rules are in crate::date. An RFC 3339 instant (2026-07-24T07:32:00Z — what a machine-maintained updated field carries) cuts exactly like the plain date it starts with.

The group keys are spelled so that an ISO date’s lexical order is its calendar order, so the group order falls out of the string with no calendar arithmetic and no time zone to get wrong. (Years before 0000 sort backwards among themselves; nothing files there yet.)

Source

pub fn cut(self, value: &str) -> Option<String>

The one group key value falls under at this grain, or None when it falls under none — or under several.

The single-valued half of cuts, for the caller that needs one answer: filing. An interval has several homes at a grain it spans, and ViewSpec::nest_route must not pick one, for the reason it does not pick between two people.

Trait Implementations§

Source§

impl Clone for Grain

Source§

fn clone(&self) -> Grain

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Copy for Grain

Source§

impl Debug for Grain

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Default for Grain

Source§

fn default() -> Grain

Returns the “default value” for a type. Read more
Source§

impl Eq for Grain

Source§

impl PartialEq for Grain

Source§

fn eq(&self, other: &Grain) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for Grain

Auto Trait Implementations§

§

impl Freeze for Grain

§

impl RefUnwindSafe for Grain

§

impl Send for Grain

§

impl Sync for Grain

§

impl Unpin for Grain

§

impl UnsafeUnpin for Grain

§

impl UnwindSafe for Grain

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.