Expand description
§ical-rs
One version-agnostic iCalendar library: a decoded model and a 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 version is a decoded indicator, never a type parameter: the 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
every dependency sits behind a cargo feature.
This header is the architecture; the behaviour behind it is specified capability by capability in the repository’s cairn/spec folder.
§Postel’s law
Parsing is maximally liberal: any real calendar round-trips byte for byte,
components, properties, parameters and value types no version defines
included, and an Unknown arm on every open vocabulary carries that
openness into the model.
Strictness lives on the way out: the builder refuses to construct a
property the spec forbids, and the validator checks a decoded calendar
against its version’s RFC contract. Neither needs the parser.
§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, and so can everything reading it: the
builder, the validator, recur, tz and both JSON
representations.
Component, property and 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 an IcalProp of a name, parameters and one value, the
last two open payload enums (IcalParam, IcalValue) with an
Unknown arm, so anything outside the model survives.
The syntax tree (tree, behind the default parser feature) is
everything byte-faithful. Its hub is IcalCst, a recursive tree of
generic nodes reproducing 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 that layout, since the recorded fold points no longer index the bytes they were taken against.
parse reads one calendar and parse_many iterates a multi-calendar
file, both strict, refusing 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, and encode (with
From<Ical>) projects the model back to a canonical CST.
A per-property lens (IcalPropLens, implemented on the marker the
property defines in prop) reads or edits one line through the
byte-preserving cursors, so editing one property leaves every other
byte intact. A component marker keys the same access over a whole subtree.
§The spec layer
Each property carries an IcalPropSpec on its marker (the versions it
lives in, its cardinality, the value types and parameters it may take per
version), and each component an IcalComponentSpec (the children it may
nest and the properties it requires).
A contract is what the RFC allows, so it is model rather than syntax: the
markers live in prop and component, and only their read-and-edit
lens sits under tree.
One vtable dispatch bridges the open kinds back to those static specs, so
the decoder, the validator and the builder all consult one source
of truth.
A calendar that passes earns an IcalValid proof,
and both Ical and IcalValid<Ical> convert back into an 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.
tz 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.
A zone crosses back into expansion for one purpose, and changes nothing
about it: RFC 5545 3.3.10 forbids counting an instance a rule generates at
a local time the clock jumped over, so an expansion given a zone drops
those candidates before COUNT is spent.
§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) brings the byte-faithful tree and its codec, via
the memchr crate. Everything under tree is gated on it; the decoded
model, the builder, the validator, the recurrence layer, the time zones
and both JSON representations are always available.
Three content decoders are default too, one small crate each:
quoted-printable decodes QUOTED-PRINTABLE value octets, base64
decodes inline BASE64 binary values, and encoding transcodes a foreign
CHARSET to text through encoding_rs (the WHATWG Encoding Standard).
jcal adds the RFC 7265 JSON representation, via the serde_json crate.
jscalendar adds the RFC 8984 JSON data model, implies jcal, whose
syntax carries the escape hatch, and pulls no crate of its own.