···1+ISC License
2+3+Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>
4+5+Permission to use, copy, modify, and distribute this software for any
6+purpose with or without fee is hereby granted, provided that the above
7+copyright notice and this permission notice appear in all copies.
8+9+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
···1+(*---------------------------------------------------------------------------
2+ Copyright (c) 2025 Anil Madhavapeddy. All rights reserved.
3+ SPDX-License-Identifier: ISC
4+ ---------------------------------------------------------------------------*)
5+6+(** Apple Mail flag colors.
7+8+ This module implements the Apple Mail flag color encoding using the
9+ [$MailFlagBit0], [$MailFlagBit1], and [$MailFlagBit2] keywords.
10+11+ See {{:https://datatracker.ietf.org/doc/draft-ietf-mailmaint-messageflag-mailboxattribute#section-3}
12+ draft-ietf-mailmaint-messageflag-mailboxattribute Section 3}.
13+14+ Colors are encoded as a 3-bit pattern where each bit corresponds to
15+ a keyword:
16+ - Bit 0: [$MailFlagBit0]
17+ - Bit 1: [$MailFlagBit1]
18+ - Bit 2: [$MailFlagBit2]
19+20+ The bit patterns are:
21+ - Red: 000 (no bits set)
22+ - Orange: 100 (bit 0 only)
23+ - Yellow: 010 (bit 1 only)
24+ - Green: 110 (bits 0 and 1)
25+ - Blue: 001 (bit 2 only)
26+ - Purple: 101 (bits 0 and 2)
27+ - Gray: 011 (bits 1 and 2)
28+ - 111: undefined (all bits set) *)
29+30+type t = [
31+ | `Red (** Bit pattern: 000 *)
32+ | `Orange (** Bit pattern: 100 *)
33+ | `Yellow (** Bit pattern: 010 *)
34+ | `Green (** Bit pattern: 110 *)
35+ | `Blue (** Bit pattern: 001 *)
36+ | `Purple (** Bit pattern: 101 *)
37+ | `Gray (** Bit pattern: 011 *)
38+]
39+40+val to_bits : t -> bool * bool * bool
41+(** [to_bits color] converts [color] to a [(bit0, bit1, bit2)] tuple
42+ representing which [$MailFlagBit*] keywords should be set.
43+44+ Example: [to_bits Green] returns [(true, true, false)]. *)
45+46+val of_bits : bool * bool * bool -> t option
47+(** [of_bits (bit0, bit1, bit2)] converts a bit pattern to a color.
48+ Returns [None] for the undefined pattern [(true, true, true)] (111). *)
49+50+val to_keywords : t -> [ `MailFlagBit0 | `MailFlagBit1 | `MailFlagBit2 ] list
51+(** [to_keywords color] returns the list of keyword bits that should be
52+ set for [color]. Red returns an empty list since no bits are needed. *)
53+54+val of_keywords : [ `MailFlagBit0 | `MailFlagBit1 | `MailFlagBit2 ] list -> t option
55+(** [of_keywords keywords] extracts a color from a list of keyword bits.
56+ Returns [None] if the pattern is 111 (undefined) or if no bits are
57+ present in the list (which would indicate no flag color is set,
58+ rather than Red). Use {!of_keywords_default_red} if you want to
59+ treat an empty list as Red. *)
60+61+val of_keywords_default_red : [ `MailFlagBit0 | `MailFlagBit1 | `MailFlagBit2 ] list -> t option
62+(** [of_keywords_default_red keywords] is like {!of_keywords} but treats
63+ an empty keyword list as Red. Returns [None] only for pattern 111. *)
64+65+val pp : Format.formatter -> t -> unit
66+(** [pp ppf color] pretty-prints the color name to [ppf]. *)
67+68+val to_string : t -> string
69+(** [to_string color] returns the lowercase color name. *)
70+71+val of_string : string -> t option
72+(** [of_string s] parses a color name (case-insensitive).
73+ Returns [None] if [s] is not a valid color name. *)
···1+(*---------------------------------------------------------------------------
2+ Copyright (c) 2025 Anil Madhavapeddy. All rights reserved.
3+ SPDX-License-Identifier: ISC
4+ ---------------------------------------------------------------------------*)
5+6+(** Unified Message Keywords for IMAP and JMAP
7+8+ This module provides a unified representation of message keywords that
9+ works across both IMAP ({{:https://datatracker.ietf.org/doc/html/rfc9051}RFC 9051})
10+ and JMAP ({{:https://datatracker.ietf.org/doc/html/rfc8621}RFC 8621}) protocols.
11+12+ {2 Keyword Types}
13+14+ Keywords are organized into categories based on their specification:
15+ - {!standard}: Core flags from RFC 8621 Section 4.1.1 that map to IMAP system flags
16+ - {!spam}: Spam-related keywords for junk mail handling
17+ - {!extended}: Extended keywords from draft-ietf-mailmaint
18+ - {!flag_bit}: Apple Mail flag color bits
19+20+ {2 Protocol Mapping}
21+22+ IMAP system flags ([\Seen], [\Answered], etc.) map to JMAP keywords
23+ ([$seen], [$answered], etc.). The {!to_string} and {!to_imap_string}
24+ functions handle these conversions. *)
25+26+(** {1 Keyword Types} *)
27+28+(** Standard keywords per {{:https://datatracker.ietf.org/doc/html/rfc8621#section-4.1.1}RFC 8621 Section 4.1.1}.
29+30+ These keywords have direct mappings to IMAP system flags defined in
31+ {{:https://datatracker.ietf.org/doc/html/rfc9051#section-2.3.2}RFC 9051 Section 2.3.2}. *)
32+type standard = [
33+ | `Seen (** Message has been read. Maps to IMAP [\Seen]. *)
34+ | `Answered (** Message has been answered. Maps to IMAP [\Answered]. *)
35+ | `Flagged (** Message is flagged/starred. Maps to IMAP [\Flagged]. *)
36+ | `Draft (** Message is a draft. Maps to IMAP [\Draft]. *)
37+ | `Deleted (** Message marked for deletion. IMAP only, maps to [\Deleted]. *)
38+ | `Forwarded (** Message has been forwarded. JMAP [$forwarded] keyword. *)
39+]
40+41+(** Spam-related keywords for junk mail handling.
42+43+ These keywords help mail clients and servers coordinate spam filtering
44+ decisions across protocol boundaries. *)
45+type spam = [
46+ | `Phishing (** Message is a phishing attempt. JMAP [$phishing]. *)
47+ | `Junk (** Message is spam/junk. JMAP [$junk]. *)
48+ | `NotJunk (** Message explicitly marked as not junk. JMAP [$notjunk]. *)
49+]
50+51+(** Extended keywords per draft-ietf-mailmaint.
52+53+ These keywords provide additional metadata for enhanced mail client features
54+ beyond basic read/reply tracking. *)
55+type extended = [
56+ | `HasAttachment (** Message has attachments. *)
57+ | `HasNoAttachment (** Message has no attachments. Mutually exclusive with [`HasAttachment]. *)
58+ | `Memo (** Message is a memo. *)
59+ | `HasMemo (** Message has an associated memo. *)
60+ | `CanUnsubscribe (** Message has unsubscribe capability (List-Unsubscribe header). *)
61+ | `Unsubscribed (** User has unsubscribed from this sender. *)
62+ | `Muted (** Thread is muted. Mutually exclusive with [`Followed]. *)
63+ | `Followed (** Thread is followed. Mutually exclusive with [`Muted]. *)
64+ | `AutoSent (** Message was sent automatically. *)
65+ | `Imported (** Message was imported from another source. *)
66+ | `IsTrusted (** Sender is trusted. *)
67+ | `MaskedEmail (** Message was sent to a masked email address. *)
68+ | `New (** Message is new (not yet processed by client). *)
69+ | `Notify (** User should be notified about this message. *)
70+]
71+72+(** Apple Mail flag color bits.
73+74+ Apple Mail uses a 3-bit encoding for flag colors. The color is determined
75+ by the combination of bits set. See {!flag_color_of_keywords} for the
76+ mapping. *)
77+type flag_bit = [
78+ | `MailFlagBit0 (** Bit 0 of Apple Mail flag color encoding. *)
79+ | `MailFlagBit1 (** Bit 1 of Apple Mail flag color encoding. *)
80+ | `MailFlagBit2 (** Bit 2 of Apple Mail flag color encoding. *)
81+]
82+83+(** Unified keyword type combining all keyword categories.
84+85+ Use [`Custom s] for server-specific or application-specific keywords
86+ not covered by the standard categories. *)
87+type t = [ standard | spam | extended | flag_bit | `Custom of string ]
88+89+(** {1 Conversion Functions} *)
90+91+val of_string : string -> t
92+(** [of_string s] parses a keyword string.
93+94+ Handles both JMAP format ([$seen]) and bare format ([seen]).
95+ Parsing is case-insensitive for known keywords.
96+97+ Examples:
98+ - ["$seen"] -> [`Seen]
99+ - ["seen"] -> [`Seen]
100+ - ["SEEN"] -> [`Seen]
101+ - ["\\Seen"] -> [`Seen] (IMAP system flag format)
102+ - ["my-custom-flag"] -> [`Custom "my-custom-flag"] *)
103+104+val to_string : t -> string
105+(** [to_string k] converts a keyword to canonical JMAP format.
106+107+ Standard and extended keywords are returned with [$] prefix in lowercase.
108+ Apple Mail flag bits preserve their mixed case.
109+ Custom keywords are returned as-is.
110+111+ Examples:
112+ - [`Seen] -> ["$seen"]
113+ - [`MailFlagBit0] -> ["$MailFlagBit0"]
114+ - [`Custom "foo"] -> ["foo"] *)
115+116+val to_imap_string : t -> string
117+(** [to_imap_string k] converts a keyword to IMAP format.
118+119+ Standard keywords that map to IMAP system flags use backslash prefix.
120+ Other keywords use [$] prefix with appropriate casing.
121+122+ Examples:
123+ - [`Seen] -> ["\\Seen"]
124+ - [`Deleted] -> ["\\Deleted"]
125+ - [`Forwarded] -> ["$Forwarded"]
126+ - [`MailFlagBit0] -> ["$MailFlagBit0"] *)
127+128+(** {1 Predicates} *)
129+130+val is_standard : t -> bool
131+(** [is_standard k] returns [true] if [k] maps to an IMAP system flag.
132+133+ The standard keywords are: [`Seen], [`Answered], [`Flagged], [`Draft],
134+ and [`Deleted]. Note that [`Forwarded] is {i not} an IMAP system flag. *)
135+136+val is_mutually_exclusive : t -> t -> bool
137+(** [is_mutually_exclusive k1 k2] returns [true] if keywords [k1] and [k2]
138+ cannot both be set on the same message.
139+140+ Mutually exclusive pairs:
141+ - [`HasAttachment] and [`HasNoAttachment]
142+ - [`Junk] and [`NotJunk]
143+ - [`Muted] and [`Followed] *)
144+145+(** {1 Pretty Printing} *)
146+147+val pp : Format.formatter -> t -> unit
148+(** [pp ppf k] pretty-prints keyword [k] in JMAP format. *)
149+150+val equal : t -> t -> bool
151+(** [equal k1 k2] tests equality of keywords. *)
152+153+val compare : t -> t -> int
154+(** [compare k1 k2] provides total ordering on keywords. *)
155+156+(** {1 Apple Mail Flag Colors} *)
157+158+(** Apple Mail flag colors encoded as 3-bit values. *)
159+type flag_color = [
160+ | `Red (** No bits set *)
161+ | `Orange (** Bit 0 only *)
162+ | `Yellow (** Bit 1 only *)
163+ | `Green (** All bits set *)
164+ | `Blue (** Bit 2 only *)
165+ | `Purple (** Bits 0 and 2 *)
166+ | `Gray (** Bits 1 and 2 *)
167+]
168+169+val flag_color_of_keywords : t list -> flag_color option
170+(** [flag_color_of_keywords keywords] extracts the Apple Mail flag color
171+ from a list of keywords.
172+173+ Returns [None] if bits 0 and 1 are set but not bit 2 (invalid encoding). *)
174+175+val flag_color_to_keywords : flag_color -> t list
176+(** [flag_color_to_keywords color] returns the keyword bits needed to
177+ represent the given flag color. *)
+46
ocaml-mail-flag/lib/mail_flag.ml
···0000000000000000000000000000000000000000000000
···1+(*---------------------------------------------------------------------------
2+ Copyright (c) 2025 Anil Madhavapeddy. All rights reserved.
3+ SPDX-License-Identifier: ISC
4+ ---------------------------------------------------------------------------*)
5+6+(** Unified Mail Flags for IMAP and JMAP
7+8+ This library provides a unified representation of message flags and mailbox
9+ attributes that works across both IMAP (RFC 9051) and JMAP (RFC 8621) protocols.
10+11+ The core types use polymorphic variants for type safety and extensibility. *)
12+13+(** {1 Module Aliases} *)
14+15+module Keyword = Keyword
16+module Mailbox_attr = Mailbox_attr
17+module Flag_color = Flag_color
18+19+(** {1 Type Aliases} *)
20+21+(** Standard message keywords that map to IMAP system flags. *)
22+type standard = Keyword.standard
23+24+(** Spam-related keywords for junk mail handling. *)
25+type spam = Keyword.spam
26+27+(** Extended keywords from draft-ietf-mailmaint. *)
28+type extended = Keyword.extended
29+30+(** Apple Mail flag color bit keywords. *)
31+type flag_bit = Keyword.flag_bit
32+33+(** Unified message keyword type combining all categories. *)
34+type keyword = Keyword.t
35+36+(** IMAP LIST response attributes. *)
37+type list_attr = Mailbox_attr.list_attr
38+39+(** Special-use mailbox roles. *)
40+type special_use = Mailbox_attr.special_use
41+42+(** Unified mailbox attribute type. *)
43+type mailbox_attr = Mailbox_attr.t
44+45+(** Apple Mail flag colors. *)
46+type flag_color = Flag_color.t
···1+(*---------------------------------------------------------------------------
2+ Copyright (c) 2025 Anil Madhavapeddy. All rights reserved.
3+ SPDX-License-Identifier: ISC
4+ ---------------------------------------------------------------------------*)
5+6+(** Unified Mail Flags for IMAP and JMAP
7+8+ This library provides a unified representation of message flags and mailbox
9+ attributes that works across both IMAP
10+ ({{:https://datatracker.ietf.org/doc/html/rfc9051}RFC 9051}) and JMAP
11+ ({{:https://datatracker.ietf.org/doc/html/rfc8621}RFC 8621}) protocols.
12+13+ {2 Overview}
14+15+ The library defines three main concepts:
16+17+ - {!Keyword}: Message keywords/flags like [`Seen], [`Flagged], [`Junk]
18+ - {!Mailbox_attr}: Mailbox attributes and special-use roles like [`Drafts], [`Inbox]
19+ - {!Flag_color}: Apple Mail flag color encoding
20+21+ All types use polymorphic variants for:
22+ - Type safety: The compiler catches invalid flag combinations
23+ - Extensibility: Custom flags via [`Custom] and [`Extension] variants
24+ - Interoperability: Easy conversion between protocol representations
25+26+ {2 Protocol Mapping}
27+28+ {b IMAP system flags} ([\Seen], [\Answered], etc.) map to {!standard} keywords.
29+ Use {!Keyword.to_imap_string} for wire format conversion.
30+31+ {b JMAP keywords} ([$seen], [$answered], etc.) are the canonical form.
32+ Use {!Keyword.to_string} for JMAP format.
33+34+ {b Mailbox roles} work similarly with {!Mailbox_attr.to_string} for IMAP
35+ and {!Mailbox_attr.to_jmap_role} for JMAP.
36+37+ {2 References}
38+39+ - {{:https://www.rfc-editor.org/rfc/rfc9051}RFC 9051} - IMAP4rev2
40+ - {{:https://www.rfc-editor.org/rfc/rfc8621}RFC 8621} - JMAP for Mail
41+ - {{:https://www.rfc-editor.org/rfc/rfc6154}RFC 6154} - IMAP Special-Use Mailboxes
42+ - {{:https://datatracker.ietf.org/doc/draft-ietf-mailmaint-messageflag-mailboxattribute}
43+ draft-ietf-mailmaint} - Extended keywords and attributes *)
44+45+(** {1 Modules} *)
46+47+(** Message keywords for both IMAP and JMAP.
48+49+ See {!module:Keyword} for the full API. *)
50+module Keyword : sig
51+ (** {1 Keyword Types} *)
52+53+ (** Standard keywords per RFC 8621 Section 4.1.1 that map to IMAP system flags. *)
54+ type standard = [
55+ | `Seen (** Message has been read. Maps to IMAP [\Seen]. *)
56+ | `Answered (** Message has been answered. Maps to IMAP [\Answered]. *)
57+ | `Flagged (** Message is flagged/starred. Maps to IMAP [\Flagged]. *)
58+ | `Draft (** Message is a draft. Maps to IMAP [\Draft]. *)
59+ | `Deleted (** Message marked for deletion. Maps to IMAP [\Deleted]. *)
60+ | `Forwarded (** Message has been forwarded. JMAP [$forwarded]. *)
61+ ]
62+63+ (** Spam-related keywords for junk mail handling. *)
64+ type spam = [
65+ | `Phishing (** Message is a phishing attempt. *)
66+ | `Junk (** Message is spam/junk. *)
67+ | `NotJunk (** Message explicitly marked as not junk. *)
68+ ]
69+70+ (** Extended keywords per draft-ietf-mailmaint. *)
71+ type extended = [
72+ | `HasAttachment (** Message has attachments. *)
73+ | `HasNoAttachment (** Message has no attachments. *)
74+ | `Memo (** Message is a memo. *)
75+ | `HasMemo (** Message has an associated memo. *)
76+ | `CanUnsubscribe (** Message has unsubscribe capability. *)
77+ | `Unsubscribed (** User has unsubscribed from this sender. *)
78+ | `Muted (** Thread is muted. *)
79+ | `Followed (** Thread is followed. *)
80+ | `AutoSent (** Message was sent automatically. *)
81+ | `Imported (** Message was imported from another source. *)
82+ | `IsTrusted (** Sender is trusted. *)
83+ | `MaskedEmail (** Message was sent to a masked email address. *)
84+ | `New (** Message is new (not yet processed). *)
85+ | `Notify (** User should be notified about this message. *)
86+ ]
87+88+ (** Apple Mail flag color bits. *)
89+ type flag_bit = [
90+ | `MailFlagBit0 (** Bit 0 of Apple Mail flag color encoding. *)
91+ | `MailFlagBit1 (** Bit 1 of Apple Mail flag color encoding. *)
92+ | `MailFlagBit2 (** Bit 2 of Apple Mail flag color encoding. *)
93+ ]
94+95+ (** Unified keyword type combining all categories. *)
96+ type t = [ standard | spam | extended | flag_bit | `Custom of string ]
97+98+ (** {1 Conversion Functions} *)
99+100+ val of_string : string -> t
101+ (** [of_string s] parses a keyword string.
102+ Handles JMAP format ([$seen]), IMAP format ([\Seen]), and bare format ([seen]).
103+ Unknown keywords become [`Custom]. *)
104+105+ val to_string : t -> string
106+ (** [to_string k] converts a keyword to canonical JMAP format (e.g., ["$seen"]). *)
107+108+ val to_imap_string : t -> string
109+ (** [to_imap_string k] converts a keyword to IMAP wire format.
110+ Standard keywords use backslash ([\Seen]), others use dollar ([$Forwarded]). *)
111+112+ (** {1 Predicates} *)
113+114+ val is_standard : t -> bool
115+ (** [is_standard k] returns [true] if [k] maps to an IMAP system flag. *)
116+117+ val is_mutually_exclusive : t -> t -> bool
118+ (** [is_mutually_exclusive k1 k2] returns [true] if keywords cannot both be set.
119+ Mutually exclusive pairs: HasAttachment/HasNoAttachment, Junk/NotJunk, Muted/Followed. *)
120+121+ (** {1 Comparison and Pretty Printing} *)
122+123+ val equal : t -> t -> bool
124+ val compare : t -> t -> int
125+ val pp : Format.formatter -> t -> unit
126+127+ (** {1 Apple Mail Flag Colors} *)
128+129+ type flag_color = [
130+ | `Red | `Orange | `Yellow | `Green | `Blue | `Purple | `Gray
131+ ]
132+133+ val flag_color_of_keywords : t list -> flag_color option
134+ (** Extract Apple Mail flag color from keywords. Returns [None] for invalid encoding. *)
135+136+ val flag_color_to_keywords : flag_color -> t list
137+ (** Convert flag color to the keyword bits needed to represent it. *)
138+end
139+140+(** Mailbox attributes and special-use roles.
141+142+ See {!module:Mailbox_attr} for the full API. *)
143+module Mailbox_attr : sig
144+ (** {1 Attribute Types} *)
145+146+ (** IMAP LIST response attributes per RFC 9051 Section 7.2.2. *)
147+ type list_attr = [
148+ | `Noinferiors (** No child mailboxes possible. *)
149+ | `Noselect (** Mailbox cannot be selected. *)
150+ | `Marked (** Mailbox has new messages. *)
151+ | `Unmarked (** Mailbox has no new messages. *)
152+ | `Subscribed (** Mailbox is subscribed. *)
153+ | `HasChildren (** Mailbox has child mailboxes. *)
154+ | `HasNoChildren (** Mailbox has no children. *)
155+ | `NonExistent (** Mailbox does not exist. *)
156+ | `Remote (** Mailbox is on a remote server. *)
157+ ]
158+159+ (** Special-use mailbox roles per RFC 6154 and RFC 8621. *)
160+ type special_use = [
161+ | `All (** Virtual mailbox with all messages. *)
162+ | `Archive (** Archive mailbox. *)
163+ | `Drafts (** Drafts mailbox. *)
164+ | `Flagged (** Virtual mailbox with flagged messages. *)
165+ | `Important (** Important messages mailbox. *)
166+ | `Inbox (** User's inbox. *)
167+ | `Junk (** Spam/junk mailbox. *)
168+ | `Sent (** Sent messages mailbox. *)
169+ | `Subscribed (** JMAP virtual subscribed mailbox. *)
170+ | `Trash (** Trash/deleted messages mailbox. *)
171+ | `Snoozed (** Snoozed messages (draft-ietf-mailmaint). *)
172+ | `Scheduled (** Scheduled to send (draft-ietf-mailmaint). *)
173+ | `Memos (** Memo messages (draft-ietf-mailmaint). *)
174+ ]
175+176+ (** Unified mailbox attribute type. *)
177+ type t = [ list_attr | special_use | `Extension of string ]
178+179+ (** {1 Conversion Functions} *)
180+181+ val of_string : string -> t
182+ (** [of_string s] parses a mailbox attribute from IMAP wire format.
183+ Unknown attributes become [`Extension]. *)
184+185+ val to_string : t -> string
186+ (** [to_string attr] converts to IMAP wire format with backslash prefix. *)
187+188+ val to_jmap_role : t -> string option
189+ (** [to_jmap_role attr] converts to JMAP role string (lowercase).
190+ Returns [None] for LIST attributes without JMAP equivalents. *)
191+192+ val of_jmap_role : string -> special_use option
193+ (** [of_jmap_role s] parses a JMAP role string into a special-use attribute. *)
194+195+ (** {1 Predicates} *)
196+197+ val is_special_use : t -> bool
198+ (** [is_special_use attr] returns [true] if attribute is a special-use role. *)
199+200+ val is_selectable : t -> bool
201+ (** [is_selectable attr] returns [false] for Noselect and NonExistent. *)
202+203+ (** {1 Pretty Printing} *)
204+205+ val pp : Format.formatter -> t -> unit
206+end
207+208+(** Apple Mail flag colors.
209+210+ See {!module:Flag_color} for the full API. *)
211+module Flag_color : sig
212+ (** Flag colors encoded as 3-bit values using [$MailFlagBit*] keywords. *)
213+ type t = [
214+ | `Red (** Bit pattern: 000 *)
215+ | `Orange (** Bit pattern: 100 *)
216+ | `Yellow (** Bit pattern: 010 *)
217+ | `Green (** Bit pattern: 110 *)
218+ | `Blue (** Bit pattern: 001 *)
219+ | `Purple (** Bit pattern: 101 *)
220+ | `Gray (** Bit pattern: 011 *)
221+ ]
222+223+ (** {1 Bit Pattern Conversion} *)
224+225+ val to_bits : t -> bool * bool * bool
226+ (** [to_bits color] returns [(bit0, bit1, bit2)] tuple. *)
227+228+ val of_bits : bool * bool * bool -> t option
229+ (** [of_bits (b0, b1, b2)] converts bit pattern to color.
230+ Returns [None] for undefined pattern (true, true, true). *)
231+232+ (** {1 Keyword Conversion} *)
233+234+ val to_keywords : t -> [ `MailFlagBit0 | `MailFlagBit1 | `MailFlagBit2 ] list
235+ (** [to_keywords color] returns keyword bits for the color. *)
236+237+ val of_keywords : [ `MailFlagBit0 | `MailFlagBit1 | `MailFlagBit2 ] list -> t option
238+ (** [of_keywords kws] extracts color from keyword bits.
239+ Returns [None] if no bits set (ambiguous) or pattern is 111 (undefined). *)
240+241+ val of_keywords_default_red : [ `MailFlagBit0 | `MailFlagBit1 | `MailFlagBit2 ] list -> t option
242+ (** Like {!of_keywords} but treats empty list as Red. *)
243+244+ (** {1 String Conversion} *)
245+246+ val to_string : t -> string
247+ (** [to_string color] returns lowercase color name. *)
248+249+ val of_string : string -> t option
250+ (** [of_string s] parses color name (case-insensitive, accepts "grey"). *)
251+252+ (** {1 Pretty Printing} *)
253+254+ val pp : Format.formatter -> t -> unit
255+end
256+257+(** {1 Type Aliases}
258+259+ Convenient type aliases for use without module qualification. *)
260+261+(** Standard message keywords that map to IMAP system flags. *)
262+type standard = Keyword.standard
263+264+(** Spam-related keywords. *)
265+type spam = Keyword.spam
266+267+(** Extended keywords from draft-ietf-mailmaint. *)
268+type extended = Keyword.extended
269+270+(** Apple Mail flag color bit keywords. *)
271+type flag_bit = Keyword.flag_bit
272+273+(** Unified message keyword type. *)
274+type keyword = Keyword.t
275+276+(** IMAP LIST response attributes. *)
277+type list_attr = Mailbox_attr.list_attr
278+279+(** Special-use mailbox roles. *)
280+type special_use = Mailbox_attr.special_use
281+282+(** Unified mailbox attribute type. *)
283+type mailbox_attr = Mailbox_attr.t
284+285+(** Apple Mail flag colors. *)
286+type flag_color = Flag_color.t
···1+(*---------------------------------------------------------------------------
2+ Copyright (c) 2025 Anil Madhavapeddy. All rights reserved.
3+ SPDX-License-Identifier: ISC
4+ ---------------------------------------------------------------------------*)
5+6+(** Unified Mailbox Attributes and Roles
7+8+ This module provides a unified representation of mailbox attributes
9+ across IMAP and JMAP protocols. It combines IMAP LIST response attributes
10+ ({{:https://www.rfc-editor.org/rfc/rfc9051#section-7.2.2}RFC 9051 Section 7.2.2}),
11+ special-use mailbox flags ({{:https://www.rfc-editor.org/rfc/rfc6154}RFC 6154}),
12+ and JMAP mailbox roles ({{:https://www.rfc-editor.org/rfc/rfc8621}RFC 8621}).
13+14+ {2 References}
15+ - {{:https://www.rfc-editor.org/rfc/rfc9051}RFC 9051} - IMAP4rev2
16+ - {{:https://www.rfc-editor.org/rfc/rfc6154}RFC 6154} - IMAP LIST Extension for Special-Use Mailboxes
17+ - {{:https://www.rfc-editor.org/rfc/rfc5258}RFC 5258} - IMAP4 LIST Command Extensions
18+ - {{:https://www.rfc-editor.org/rfc/rfc8457}RFC 8457} - IMAP \$Important Keyword and \Important Special-Use Attribute
19+ - {{:https://www.rfc-editor.org/rfc/rfc8621}RFC 8621} - JMAP for Mail *)
20+21+(** {1 IMAP LIST Attributes}
22+23+ Attributes returned in IMAP LIST responses per
24+ {{:https://www.rfc-editor.org/rfc/rfc9051#section-7.2.2}RFC 9051 Section 7.2.2}. *)
25+26+type list_attr = [
27+ | `Noinferiors
28+ (** [\Noinferiors] - No child mailboxes are possible under this mailbox.
29+ The mailbox cannot have inferior (child) mailboxes, either because the
30+ underlying storage doesn't support it or because the mailbox name is at
31+ the hierarchy depth limit for this mailbox store. *)
32+ | `Noselect
33+ (** [\Noselect] - This mailbox cannot be selected. It exists only to hold
34+ child mailboxes and is not a valid destination for messages. Stratum
35+ only, not a real mailbox. *)
36+ | `Marked
37+ (** [\Marked] - The mailbox has been marked "interesting" by the server.
38+ This typically indicates the mailbox contains new messages since the
39+ last time it was selected. *)
40+ | `Unmarked
41+ (** [\Unmarked] - The mailbox is not "interesting". The mailbox does not
42+ contain new messages since the last time it was selected. *)
43+ | `Subscribed
44+ (** [\Subscribed] - The mailbox is subscribed. Returned when the
45+ SUBSCRIBED selection option is specified or implied. *)
46+ | `HasChildren
47+ (** [\HasChildren] - The mailbox has child mailboxes. Part of the
48+ CHILDREN return option ({{:https://www.rfc-editor.org/rfc/rfc5258}RFC 5258}). *)
49+ | `HasNoChildren
50+ (** [\HasNoChildren] - The mailbox has no child mailboxes. Part of the
51+ CHILDREN return option ({{:https://www.rfc-editor.org/rfc/rfc5258}RFC 5258}). *)
52+ | `NonExistent
53+ (** [\NonExistent] - The mailbox name does not refer to an existing mailbox.
54+ This attribute is returned when a mailbox is part of the hierarchy but
55+ doesn't actually exist ({{:https://www.rfc-editor.org/rfc/rfc5258}RFC 5258}).
56+ Implies [\Noselect]. *)
57+ | `Remote
58+ (** [\Remote] - The mailbox is located on a remote server.
59+ ({{:https://www.rfc-editor.org/rfc/rfc5258}RFC 5258}) *)
60+]
61+62+(** {1 Special-Use Roles}
63+64+ Special-use mailbox roles per {{:https://www.rfc-editor.org/rfc/rfc6154}RFC 6154}
65+ and {{:https://www.rfc-editor.org/rfc/rfc8621}RFC 8621}. These identify
66+ mailboxes with specific purposes. *)
67+68+type special_use = [
69+ | `All
70+ (** [\All] - A virtual mailbox containing all messages in the user's
71+ message store. Implementations may omit some messages. *)
72+ | `Archive
73+ (** [\Archive] - A mailbox used to archive messages. The meaning of
74+ "archived" may vary by server. *)
75+ | `Drafts
76+ (** [\Drafts] - A mailbox used to hold draft messages, typically messages
77+ being composed but not yet sent. *)
78+ | `Flagged
79+ (** [\Flagged] - A virtual mailbox containing all messages marked with
80+ the [\Flagged] flag. *)
81+ | `Important
82+ (** [\Important] - A mailbox used to hold messages deemed important to
83+ the user. ({{:https://www.rfc-editor.org/rfc/rfc8457}RFC 8457}) *)
84+ | `Inbox
85+ (** [inbox] - The user's inbox. This is a JMAP role
86+ ({{:https://www.rfc-editor.org/rfc/rfc8621}RFC 8621}) without a direct
87+ IMAP special-use equivalent since INBOX is always special in IMAP. *)
88+ | `Junk
89+ (** [\Junk] - A mailbox used to hold messages that have been identified
90+ as spam or junk mail. Also known as "Spam" folder. *)
91+ | `Sent
92+ (** [\Sent] - A mailbox used to hold copies of messages that have been
93+ sent. *)
94+ | `Subscribed
95+ (** [subscribed] - A JMAP virtual mailbox role
96+ ({{:https://www.rfc-editor.org/rfc/rfc8621}RFC 8621}) representing
97+ all subscribed mailboxes. *)
98+ | `Trash
99+ (** [\Trash] - A mailbox used to hold messages that have been deleted or
100+ marked for deletion. *)
101+ | `Snoozed
102+ (** [snoozed] - A mailbox for messages that have been snoozed until a
103+ later time. (draft-ietf-mailmaint-special-use-extensions) *)
104+ | `Scheduled
105+ (** [scheduled] - A mailbox for messages scheduled to be sent at a
106+ future time. (draft-ietf-mailmaint-special-use-extensions) *)
107+ | `Memos
108+ (** [memos] - A mailbox for memo/note messages.
109+ (draft-ietf-mailmaint-special-use-extensions) *)
110+]
111+112+(** {1 Unified Attribute Type} *)
113+114+type t = [ list_attr | special_use | `Extension of string ]
115+(** The unified mailbox attribute type combining LIST attributes, special-use
116+ roles, and server-specific extensions. Extensions are represented with
117+ their original string form (without leading backslash if present). *)
118+119+(** {1 Conversion Functions} *)
120+121+val of_string : string -> t
122+(** [of_string s] parses a mailbox attribute from its IMAP wire format.
123+ The input may optionally include the leading backslash. Parsing is
124+ case-insensitive. Unknown attributes are returned as [`Extension s].
125+126+ Examples:
127+ - [of_string "\\Drafts"] returns [`Drafts]
128+ - [of_string "drafts"] returns [`Drafts]
129+ - [of_string "\\X-Custom"] returns [`Extension "X-Custom"] *)
130+131+val to_string : t -> string
132+(** [to_string attr] converts an attribute to its IMAP wire format with
133+ the leading backslash prefix for standard attributes.
134+135+ Examples:
136+ - [to_string `Drafts] returns ["\\Drafts"]
137+ - [to_string `HasChildren] returns ["\\HasChildren"]
138+ - [to_string (`Extension "X-Custom")] returns ["\\X-Custom"] *)
139+140+val to_jmap_role : t -> string option
141+(** [to_jmap_role attr] converts a special-use attribute to its JMAP role
142+ string (lowercase). Returns [None] for LIST attributes that don't
143+ correspond to JMAP roles.
144+145+ Examples:
146+ - [to_jmap_role `Drafts] returns [Some "drafts"]
147+ - [to_jmap_role `Inbox] returns [Some "inbox"]
148+ - [to_jmap_role `Noselect] returns [None] *)
149+150+val of_jmap_role : string -> special_use option
151+(** [of_jmap_role s] parses a JMAP role string into a special-use attribute.
152+ Returns [None] if the role string is not recognized. The input should
153+ be lowercase as per JMAP conventions.
154+155+ Examples:
156+ - [of_jmap_role "drafts"] returns [Some `Drafts]
157+ - [of_jmap_role "inbox"] returns [Some `Inbox]
158+ - [of_jmap_role "unknown"] returns [None] *)
159+160+(** {1 Predicates} *)
161+162+val is_special_use : t -> bool
163+(** [is_special_use attr] returns [true] if the attribute is a special-use
164+ role (as opposed to a LIST attribute or extension).
165+166+ Examples:
167+ - [is_special_use `Drafts] returns [true]
168+ - [is_special_use `Noselect] returns [false]
169+ - [is_special_use (`Extension "x")] returns [false] *)
170+171+val is_selectable : t -> bool
172+(** [is_selectable attr] returns [false] if the attribute indicates the
173+ mailbox cannot be selected. This is [true] for [`Noselect] and
174+ [`NonExistent] attributes, and [false] for all others.
175+176+ Note: A mailbox may have multiple attributes. To determine if a mailbox
177+ is selectable, check that no attribute returns [false] from this function.
178+179+ Examples:
180+ - [is_selectable `Noselect] returns [false]
181+ - [is_selectable `NonExistent] returns [false]
182+ - [is_selectable `Drafts] returns [true]
183+ - [is_selectable `HasChildren] returns [true] *)
184+185+(** {1 Pretty Printing} *)
186+187+val pp : Format.formatter -> t -> unit
188+(** [pp ppf attr] pretty-prints the attribute in IMAP wire format. *)