Skip to main content

ical/
lib.rs

1#![no_std]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3
4//! # ical-rs
5//!
6//! One version-agnostic iCalendar library: a decoded model and a
7//! byte-faithful syntax tree that read and write vCalendar 1.0 (versit) and
8//! iCalendar 2.0 (RFC 5545, extended by 6638, 7529, 7953, 7986, 9073, 9074
9//! and 9253) alike.
10//!
11//! The version is a decoded indicator, never a type parameter: the tree
12//! ignores it, and only the codec and the per-property spec branch on it,
13//! where escaping or a value's shape genuinely differ.
14//!
15//! Unlike a flat address book, a calendar is a tree of components (events,
16//! to-dos, journals, free/busy, time zones, alarms), and the whole tree is
17//! parsed, walked and round-tripped.
18//!
19//! The crate is `no_std` (with `alloc`), its core is dependency-free, and
20//! every dependency sits behind a [cargo feature](#cargo-features).
21//!
22//! This header is the architecture; the behaviour behind it is specified
23//! capability by capability in the repository's cairn/spec folder.
24//!
25//! ## Postel's law
26//!
27//! Parsing is maximally liberal: any real calendar round-trips byte for byte,
28//! components, properties, parameters and value types no version defines
29//! included, and an `Unknown` arm on every open vocabulary carries that
30//! openness into the model.
31//!
32//! Strictness lives on the way out: the [`builder`] refuses to construct a
33//! property the spec forbids, and the [`validator`] checks a decoded calendar
34//! against its version's RFC contract. Neither needs the parser.
35//!
36//! ## The two layers
37//!
38//! The decoded model ([`ical`], [`component`], [`version`], [`prop`],
39//! [`param`], [`value`]) is pure data with no dependency on the syntax side,
40//! so it can be depended on alone, and so can everything reading it: the
41//! [`builder`], the [`validator`], [`recur`], [`tz`] and both JSON
42//! representations.
43//!
44//! Component, property and parameter names and value types are closed
45//! identity enums ([`IcalComponentKind`], [`IcalPropKind`],
46//! [`IcalParamKind`], [`IcalValueKind`]) whose wire spelling is reached
47//! through `FromStr` and `Deref`.
48//!
49//! A calendar is an [`Ical`]: a version, the calendar-level properties, and a
50//! list of nested [`IcalComponent`]s, themselves recursive.
51//!
52//! A property is an [`IcalProp`] of a name, parameters and one value, the
53//! last two open payload enums ([`IcalParam`], [`IcalValue`]) with an
54//! `Unknown` arm, so anything outside the model survives.
55//!
56//! The syntax tree ([`tree`], behind the default `parser` feature) is
57//! everything byte-faithful. Its hub is [`IcalCst`], a recursive tree of
58//! generic nodes reproducing the wire bytes exactly.
59//!
60//! Exactly means exactly: the tokeniser resolves a line's wire layout (its
61//! RFC 5545 3.1 folds, the blank lines before it, its `QUOTED-PRINTABLE` soft
62//! breaks) so every layer above sees one logical line, and records it on
63//! [`IcalWire`] so serialization lays it back out.
64//!
65//! Only an edit that changes a line's length drops that layout, since the
66//! recorded fold points no longer index the bytes they were taken against.
67//!
68//! [`parse`] reads one calendar and [`parse_many`] iterates a multi-calendar
69//! file, both strict, refusing a calendar they cannot structure.
70//!
71//! [`parse_recovering`] keeps what it cannot structure as opaque bytes,
72//! carries on, and reports what it worked around, for the calendars in the
73//! wild that a strict reading throws away whole.
74//!
75//! [`decode`] projects a CST onto the decoded [`Ical`], and `encode` (with
76//! `From<Ical>`) projects the model back to a canonical CST.
77//!
78//! A per-property lens ([`IcalPropLens`], implemented on the marker the
79//! property defines in [`prop`]) reads or edits one line through the
80//! byte-preserving [`cursor`]s, so editing one property leaves every other
81//! byte intact. A component marker keys the same access over a whole subtree.
82//!
83//! ## The spec layer
84//!
85//! Each property carries an [`IcalPropSpec`] on its marker (the versions it
86//! lives in, its cardinality, the value types and parameters it may take per
87//! version), and each component an [`IcalComponentSpec`] (the children it may
88//! nest and the properties it requires).
89//!
90//! A contract is what the RFC allows, so it is model rather than syntax: the
91//! markers live in [`prop`] and [`component`], and only their read-and-edit
92//! lens sits under [`tree`].
93//!
94//! One vtable dispatch bridges the open kinds back to those static specs, so
95//! the decoder, the [`validator`] and the [`builder`] all consult one source
96//! of truth.
97//!
98//! A calendar that passes earns an [`IcalValid`](validator::IcalValid) proof,
99//! and both `Ical` and `IcalValid<Ical>` convert back into an [`IcalCst`].
100//!
101//! ## Recurrence and time zones
102//!
103//! [`recur`] answers what a rule denotes, and what a whole component denotes:
104//! [`IcalRecurExpand`] walks one `RRULE`, and [`IcalRecurSet`] walks the set
105//! an event actually happens on, `RDATE`s, `EXDATE`s, `EXRULE`s and
106//! `RECURRENCE-ID` overrides included.
107//!
108//! Both are lazy, and both are civil: RFC 5545 expands on the local
109//! wall-clock time of `DTSTART`, so no offset is ever needed and none is ever
110//! resolved.
111//!
112//! [`tz`] is the step after, turning a civil occurrence into a UTC
113//! offset from the `VTIMEZONE` the calendar carries, and reporting the
114//! spring-forward gap and the fall-back fold rather than guessing.
115//!
116//! A zone crosses back into expansion for one purpose, and changes nothing
117//! about it: RFC 5545 3.3.10 forbids counting an instance a rule generates at
118//! a local time the clock jumped over, so an expansion given a zone drops
119//! those candidates before `COUNT` is spent.
120//!
121//! ## Reconciling two replicas
122//!
123//! [`merge`](tree::merge) is the syntax layer's answer to two divergent edits
124//! of one calendar: [`IcalMerge`] diffs each against their common base,
125//! reports what each did and where they collided, and builds the merged
126//! calendar out of the left side's own bytes.
127//!
128//! It lives under [`tree`] rather than over the model because keeping the
129//! bytes of every line neither side touched is the point.
130//!
131//! ## The JSON representations
132//!
133//! [`jcal`] is the RFC 7265 spelling of this model in JSON, member for
134//! member.
135//!
136//! [`jscalendar`] is the RFC 8984 data model, which is a different model: a
137//! `VCALENDAR` is a Group of Events and Tasks, a `DTEND` is a duration, an
138//! `ATTENDEE` line is a Participant object, and an overriding `VEVENT` is a
139//! patch inside the series it overrides.
140//!
141//! Both are lossless, each through an escape hatch of its own, and both take
142//! a raw [`serde_json::Value`] at the boundary rather than a serde
143//! implementation, since one model with two JSON spellings is exactly what
144//! serde cannot key.
145//!
146//! ## Cargo features
147//!
148//! `parser` (default) brings the byte-faithful [`tree`] and its codec, via
149//! the `memchr` crate. Everything under [`tree`] is gated on it; the decoded
150//! model, the builder, the validator, the recurrence layer, the time zones
151//! and both JSON representations are always available.
152//!
153//! Three content decoders are default too, one small crate each:
154//! `quoted-printable` decodes `QUOTED-PRINTABLE` value octets, `base64`
155//! decodes inline `BASE64` binary values, and `encoding` transcodes a foreign
156//! `CHARSET` to text through `encoding_rs` (the WHATWG Encoding Standard).
157//!
158//! `jcal` adds the RFC 7265 JSON representation, via the `serde_json` crate.
159//! `jscalendar` adds the RFC 8984 JSON data model, implies `jcal`, whose
160//! syntax carries the escape hatch, and pulls no crate of its own.
161//!
162//! [`IcalComponentKind`]: component::IcalComponentKind
163//! [`IcalPropKind`]: prop::IcalPropKind
164//! [`IcalParamKind`]: param::IcalParamKind
165//! [`IcalValueKind`]: value::IcalValueKind
166//! [`Ical`]: ical::Ical
167//! [`IcalComponent`]: component::IcalComponent
168//! [`IcalProp`]: prop::IcalProp
169//! [`IcalParam`]: param::IcalParam
170//! [`IcalValue`]: value::IcalValue
171//! [`IcalCst`]: tree::cst::IcalCst
172//! [`IcalWire`]: tree::wire::IcalWire
173//! [`parse`]: tree::cst::IcalCst::parse
174//! [`parse_many`]: tree::cst::IcalCst::parse_many
175//! [`parse_recovering`]: tree::cst::IcalCst::parse_recovering
176//! [`decode`]: tree::cst::IcalCst::decode
177//! [`IcalPropLens`]: tree::prop::lens::IcalPropLens
178//! [`cursor`]: tree::value::cursor::IcalValueCursor
179//! [`IcalPropSpec`]: prop::spec::IcalPropSpec
180//! [`IcalComponentSpec`]: component::spec::IcalComponentSpec
181//! [`IcalRecurExpand`]: recur::expand::IcalRecurExpand
182//! [`IcalRecurSet`]: recur::set::IcalRecurSet
183//! [`IcalMerge`]: tree::merge::IcalMerge
184
185extern crate alloc;
186
187pub mod builder;
188pub mod component;
189pub mod ical;
190pub mod param;
191pub mod prop;
192pub mod recur;
193pub mod tz;
194pub mod validator;
195pub mod value;
196pub mod version;
197
198#[cfg(feature = "jcal")]
199#[cfg_attr(docsrs, doc(cfg(feature = "jcal")))]
200pub mod jcal;
201
202#[cfg(feature = "jscalendar")]
203#[cfg_attr(docsrs, doc(cfg(feature = "jscalendar")))]
204pub mod jscalendar;
205
206#[cfg(feature = "parser")]
207pub mod tree;