Expand description
§ical-rs
A single, version-agnostic iCalendar library: one decoded model and one
byte-faithful syntax tree that read and write vCalendar 1.0 (versit) and
iCalendar 2.0 (RFC 5545, extended by 6638, 7529, 7953, 7986, 9073, 9074 and
9253) alike. The
calendar version is a decoded indicator, never a type parameter or a
separate dialect: the syntax tree ignores it, and only the codec and the
per-property spec branch on it where escaping or a value’s shape genuinely
differ. Unlike a flat address book, a calendar is a tree of components
(events, to-dos, journals, free/busy, time zones, alarms), and the whole
tree is parsed, walked and round-tripped. The crate is no_std (with
alloc); its core is dependency-free, and the only dependencies are the
small no_std crates behind the opt-in content-decoding features
(Cargo features).
§Postel’s law
The library is liberal in what it accepts and strict in what it sends.
Parsing is maximally liberal: any real calendar, including components,
properties, parameters and value types that no version officially defines,
is accepted and round-trips byte for byte. The decoded model keeps that
openness, with an Unknown arm on every open vocabulary. Strictness lives
only on the way out, as two runtime steps: the builder, which refuses to
construct a property the spec forbids, and
validate, which checks a decoded calendar against
its version’s RFC contract.
§The two layers
The decoded model (ical, component, version, prop,
param, value) is pure data with no dependency on the syntax side, so
it can be depended on alone. Component names, property names, parameter
names and value types are closed identity enums
(IcalComponentKind,
IcalPropKind,
IcalParamKind,
IcalValueKind) whose wire spelling is reached
through FromStr and Deref. A calendar is an Ical: a
version, the calendar-level properties, and a list of nested
IcalComponents (themselves recursive). A
property is a IcalProp of a name, parameters and one
value; its parameters and value are open payload enums
(IcalParam, IcalValue) with an
Unknown variant, so anything outside the model survives.
The syntax tree (tree, gated behind the parser feature, on by default)
is everything byte-faithful. Its hub is IcalCst, a
recursive tree of generic nodes that reproduces the wire bytes exactly.
Exactly means exactly: the tokeniser resolves a line’s wire layout (its RFC
5545 3.1 folds, the blank lines before it, its QUOTED-PRINTABLE soft
breaks) so every layer above sees one logical line, and records it on
IcalWire so serialization lays it back out. Only
an edit that changes a line’s length drops its layout, since the recorded
fold points no longer index the bytes they were taken against.
parse accepts bytes or a string and reads one
calendar; parse_many iterates a
multi-calendar file, and is what round-trips a whole file rather than its
first calendar. Both are strict, and refuse a calendar they cannot
structure; parse_recovering keeps
what it cannot structure as opaque bytes, carries on, and reports what it
worked around, for the calendars in the wild that a strict reading throws
away whole. decode projects a CST onto the
decoded Ical; encode (and From<Ical>) projects the model
back to a canonical CST. Per-property lens markers
(IcalPropLens) and per-component lens markers
(IcalComponentLens) read or edit a
single line or subtree through the byte-preserving
cursors, so editing one property leaves
every other byte intact.
§The spec layer
Each property carries a IcalPropSpec on its
lens marker (the versions it lives in, its cardinality, the value types and
parameters it may take per version), and each component carries a
IcalComponentSpec (the children it
may nest and the properties it requires). A single
vtable dispatch bridges the open kinds back to those static specs, so the
decoder, validate and the builder all consult one
source of truth. A calendar that passes earns a
IcalValid proof, and both Ical and
IcalValid<Ical> convert back into a IcalCst.
§Recurrence and time zones
recur answers what a rule denotes, and what a whole component denotes:
IcalRecurExpand walks one RRULE, and
IcalRecurSet walks the set an event actually
happens on, RDATEs, EXDATEs, EXRULEs and RECURRENCE-ID overrides
included. Both are lazy, and both are civil: RFC 5545 expands on the local
wall-clock time of DTSTART, so no offset is ever needed and none is ever
resolved. timezone is the step after, turning a civil occurrence into a
UTC offset from the VTIMEZONE the calendar carries, and reporting the
spring-forward gap and the fall-back fold rather than guessing.
§Reconciling two replicas
merge is the syntax layer’s answer to two divergent edits
of one calendar: IcalMerge diffs each against
their common base, reports what each did and where they collided, and
builds the merged calendar out of the left side’s own bytes. It lives under
tree rather than over the model because keeping the bytes of every line
neither side touched is the point.
§The JSON representations
jcal is the RFC 7265 spelling of this model in JSON, member for member.
jscalendar is the RFC 8984 data model, which is a different model: a
VCALENDAR is a Group of Events and Tasks, a DTEND is a duration, an
ATTENDEE line is a Participant object, and an overriding VEVENT is a
patch inside the series it overrides. Both are lossless, each through an
escape hatch of its own, and both take a raw serde_json::Value at the
boundary rather than a serde implementation, since one model with two JSON
spellings is exactly what serde cannot key.
§Cargo features
parser(default): the byte-faithfultreeand its codec. Everything undertreeis gated on it; the decoded model is always available.quoted-printable(default): decodeQUOTED-PRINTABLEvalue octets, via thequoted_printablecrate.base64(default): decode inlineBASE64binary values, via thebase64crate.encoding(default): transcode a foreignCHARSETvalue to text, via theencoding_rscrate (the WHATWG Encoding Standard).jcal: the RFC 7265 JSON representation of a calendar, via theserde_jsoncrate.jscalendar: the RFC 8984 JSON data model of a calendar, built onjcal.