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