(** One calendar layer: a rite's base sanctoral, or a data file of the same shape. Entries are canonically sorted by slug. *) type 'r entry = { date : Date_spec.t; cel : 'r Celebration.t } [@@deriving sexp] type 'r t = { id : string; name : string; entries : 'r entry list } [@@deriving sexp] val empty : id:string -> name:string -> 'r t val of_entries : id:string -> name:string -> 'r entry list -> 'r t val find : 'r t -> Slug.t -> 'r entry option val mem : 'r t -> Slug.t -> bool (** Replaces any entry with the same slug. *) val set : 'r t -> 'r entry -> 'r t val remove : 'r t -> Slug.t -> 'r t (** Date index. Built once per layer, not per year: [Date_spec] dates are year-independent. *) type 'r index (** [index t ~easter ~years] builds the per-date lookup for [t]. Fixed entries are keyed (month, day), year-independently, as they always were. Movable entries ({!Date_spec.Easter_offset}, {!Date_spec.Nth_weekday}) have no year-independent key, so they are resolved once for each civil year in [years] and keyed by rata die. [easter] supplies that year's Easter as the RITE reckons it -- see {!Rite.t}'s own [easter] field. [years] must list every civil year the caller's span touches. A liturgical year is Advent-anchored and straddles two, so {!Calendar.year} and {!Validate.run} both pass [[y; y + 1]]. A movable entry whose year is omitted is simply absent from the index -- silently, so getting [years] wrong loses celebrations rather than erroring. NOTE the index is therefore no longer built once per layer independent of year: for fixed entries it still is, for movable ones it is per-span. *) val index : 'r t -> easter:(int -> Date.t) -> years:int list -> 'r index (** Entries falling on [date], fixed and movable together, in the layer's canonical slug order. [fixed_key] answers "which (month, day) does the FIXED half look up for this civil date", defaulting to the identity [Some (Date.month date, Date.day date)] -- exactly today's behaviour, for a caller that supplies nothing. A rite's own kalends-reckoning convention (see {!Rite.t}'s [fixed_key] field) is threaded through here rather than applied to [date] itself, so it touches ONLY the fixed table: the MOVABLE half (keyed by rata die, via {!Date_spec.Easter_offset}/{!Date_spec. Nth_weekday}) always uses [date] unchanged. *) val on_date : ?fixed_key:(Date.t -> (int * int) option) -> 'r index -> Date.t -> 'r entry list (** Loads a layer from a sexp file. Parse and validation failures come back as [Error], never as an exception. *) val load : (Sexplib0.Sexp.t -> 'r) -> string -> ('r t, string) result