Skip to main content

ical/
lib.rs

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