Skip to content

Commit 95c4507

Browse files
committed
clean: apply pimalaya guidelines
1 parent bd7d745 commit 95c4507

25 files changed

Lines changed: 690 additions & 514 deletions

‎CHANGELOG.md‎

Lines changed: 94 additions & 40 deletions
Large diffs are not rendered by default.

‎CONTRIBUTING.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Whether you are a human or an AI agent, read these in order before touching the
77
1. the [Pimalaya README](https://github.com/pimalaya) for what the project is and how its repositories stack;
88
2. the [Pimalaya CONTRIBUTING](https://github.com/pimalaya/.github/blob/master/CONTRIBUTING.md) guide, which chains to the shared architecture and guidelines;
99
3. the inline header documentation, starting with src/main.rs: it is the architecture document of this crate;
10-
4. the cairn/ folder, which follows [Cairn](https://github.com/pimalaya/cairn): `spec/` is current truth, `changes/` holds in-flight proposals, and `log/` is the dated history.
10+
4. the cairn/ folder, which follows [Cairn](https://github.com/pimalaya/cairn): spec/ is current truth, changes/ holds in-flight proposals, and log/ is the dated history.
1111

1212
Everything below documents only what differs from the Pimalaya standards.
1313

‎Cargo.toml‎

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name = "carillon"
33
description = "CLI to watch PIM collection changes"
44
version = "0.1.0"
5-
authors = ["Clément DOUIN (soywod) <clement.douin@posteo.net>"]
5+
authors = ["Clément DOUIN (soywod) <pimalaya.org@posteo.net>"]
66
rust-version = "1.89"
77
edition = "2024"
88
license = "MIT OR Apache-2.0"
@@ -11,10 +11,6 @@ keywords = ["cli", "watch", "imap", "jmap", "dav"]
1111
homepage = "https://pimalaya.org"
1212
repository = "https://github.com/pimalaya/carillon"
1313

14-
[package.metadata.docs.rs]
15-
all-features = true
16-
rustdoc-args = ["--cfg", "docsrs"]
17-
1814
[features]
1915
default = ["rustls-ring", "imap", "jmap", "maildir", "dav"]
2016
imap = ["dep:io-imap", "dep:io-pim-discovery", "io-imap/client", "pimalaya-cli/imap"]
@@ -44,13 +40,13 @@ anyhow = "1"
4440
base64 = { version = "0.23", optional = true }
4541
clap = { version = "4.4", features = ["derive", "env", "wrap_help"] }
4642
ctrlc = { version = "3", features = ["termination"] }
43+
io-http = { version = "0.5", default-features = false, optional = true }
4744
io-imap = { version = "0.6", default-features = false, optional = true }
4845
io-jmap = { version = "0.3", default-features = false, optional = true }
49-
io-http = { version = "0.5", default-features = false, optional = true }
5046
io-maildir = { version = "0.3", default-features = false, optional = true }
5147
io-pim-discovery = { version = "0.7", default-features = false, features = ["autoconfig", "pacc", "rfc6186", "rfc6764", "rfc8620"], optional = true }
52-
io-webdav = { version = "0.2", default-features = false, optional = true }
5348
io-sasl = { version = "0.1", default-features = false, features = ["scram"] }
49+
io-webdav = { version = "0.2", default-features = false, optional = true }
5450
log = "0.4"
5551
notify-rust = { version = "4", default-features = false, features = ["d"] }
5652
pimalaya-cli = { version = "0.2", default-features = false, features = ["terminal", "prompt", "spinner", "wizard"] }

‎README.md‎

Lines changed: 48 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -20,44 +20,55 @@ CLI to watch PIM collection changes, written in Rust
2020

2121
## Features
2222

23-
- **Five backends**, mail and not only mail: IMAP idles, JMAP is pushed to, Maildir re-lists, and CalDAV and CardDAV each ask a collection what moved.
24-
- **One account, one collection, one method**: all three are its config, so nothing is passed on the command line. Each backend names the collection it watches in its own word (`imap.mailbox`, `caldav.calendar`, `carddav.addressbook`), and a hook templates against that same word. Any backend can poll instead, for a server whose idle or push cannot be trusted.
25-
- **Events named after what they carry**: mail fires `on-message-*`, an addressbook `on-card-*`, a calendar `on-event-*` and `on-task-*`, and anything with flags `on-flag-*`, once per flag. Flag names are the same on every backend, so a filter written once fires everywhere.
26-
- **Hooks under their backend**: a desktop notification, a shell command, or both, with the event's fields as placeholders. Each backend takes only the events it can report, and each hook only the variables its event carries, so a hook that could never fire is refused when the file is read.
27-
- **Every account at once**, one thread each, reopening a dropped watch with a capped backoff and reading the credential again each time.
28-
- **Familiar configuration**: the account block keeps the shape `himalaya` and `himalaya-tui` read, and secrets come from your own password manager.
29-
- **One prompt to a working account**: `carillon configure` turns an email address into a discovered service, a tested connection and the best watch method its server supports, then writes the account where the loader reads it.
23+
- **Watch mail, calendars and contacts**: **IMAP**, **JMAP**, **Maildir**, **CalDAV**, **CardDAV**
24+
- **Desktop notifications**, **shell commands**, or both, on every change
25+
- **Instant where the protocol allows it**: IMAP idle, JMAP push, a poll everywhere else
26+
- **Every account at once**, each reconnecting on its own when its server drops it
27+
- **One prompt setup**: `carillon configure` turns your email address into a tested account
28+
- **Secrets from your own password manager**, never written into the configuration
29+
30+
> [!TIP]
31+
> Carillon is written in [Rust](https://www.rust-lang.org/) and uses [cargo features](https://doc.rust-lang.org/cargo/reference/features.html) to gate backend support. The default feature set is declared in [Cargo.toml](./Cargo.toml).
32+
3033
## Coverage
3134

32-
| Spec | What is covered |
33-
|-----------|-----------------|
34-
| [2177] | IMAP idle: the held connection a watch waits on, woken by the server on every change |
35-
| [7162] | Quick mailbox resynchronization: server-named deltas where the server offers them, a local re-read and diff where it does not |
36-
| [4959] | SASL initial response, forced on or off for providers that advertise it and then refuse it |
37-
| [2971] | IMAP identification, required by some providers right after authentication |
38-
| [4616] | SASL plain, the password mechanism |
39-
| [4505] | SASL anonymous, for servers accepting unauthenticated sessions |
40-
| [7677] | SASL scram-sha-256, the challenge-response mechanism |
41-
| [7628] | SASL oauthbearer, an OAuth 2.0 token issued by an external broker |
42-
| [8620] | The JMAP core: session discovery, the changes and get method shapes, request batching, and the event-source stream a push watch holds |
43-
| [8621] | JMAP for mail: mailboxes, emails, and the change stream a watch polls |
44-
| [maildir] | The original Maildir layout, plus the Maildir++ subfolder convention |
45-
| [4918] | WebDAV itself: the report a collection answers, and the etag that says an item moved |
46-
| [6578] | Collection synchronization: the token a poll carries, what changed since it, and what to do when the server refuses it |
35+
| Spec | What is covered |
36+
|--------------|-----------------|
37+
| [2177] | IMAP idle: the held connection a watch waits on, woken by the server on every change |
38+
| [2971] | IMAP identification, required by some providers right after authentication |
39+
| [4505] | SASL anonymous, for servers accepting unauthenticated sessions |
40+
| [4616] | SASL plain, the password mechanism |
41+
| [4918] | WebDAV itself: the report a collection answers, and the etag that says an item moved |
42+
| [4959] | SASL initial response, forced on or off for providers that advertise it and then refuse it |
43+
| [6186] | Mail service SRV records, one of the ways the wizard finds a server from an address |
44+
| [6578] | Collection synchronization: the token a poll carries, what changed since it, and what to do when the server refuses it |
45+
| [6764] | CalDAV and CardDAV bootstrapping, how the wizard finds a calendar or an addressbook |
46+
| [7162] | Quick mailbox resynchronization: server-named deltas where the server offers them, a local re-read and diff where it does not |
47+
| [7628] | SASL oauthbearer, an OAuth 2.0 token issued by an external broker |
48+
| [7677] | SASL scram-sha-256, the challenge-response mechanism |
49+
| [8620] | The JMAP core: session discovery, the changes and get method shapes, request batching, and the event-source stream a push watch holds |
50+
| [8621] | JMAP for mail: mailboxes, emails, and the change stream a watch polls |
51+
| [autoconfig] | Thunderbird autoconfiguration, the widest-published source of provider settings |
52+
| [maildir] | The original Maildir layout, plus the Maildir++ subfolder convention |
53+
| [pacc] | Provider account configuration, the successor the wizard prefers when a domain publishes one |
4754

4855
[2177]: https://www.rfc-editor.org/rfc/rfc2177
49-
[7162]: https://www.rfc-editor.org/rfc/rfc7162
50-
[4959]: https://www.rfc-editor.org/rfc/rfc4959
5156
[2971]: https://www.rfc-editor.org/rfc/rfc2971
52-
[4616]: https://www.rfc-editor.org/rfc/rfc4616
5357
[4505]: https://www.rfc-editor.org/rfc/rfc4505
54-
[7677]: https://www.rfc-editor.org/rfc/rfc7677
58+
[4616]: https://www.rfc-editor.org/rfc/rfc4616
59+
[4918]: https://www.rfc-editor.org/rfc/rfc4918
60+
[4959]: https://www.rfc-editor.org/rfc/rfc4959
61+
[6186]: https://www.rfc-editor.org/rfc/rfc6186
62+
[6578]: https://www.rfc-editor.org/rfc/rfc6578
63+
[6764]: https://www.rfc-editor.org/rfc/rfc6764
64+
[7162]: https://www.rfc-editor.org/rfc/rfc7162
5565
[7628]: https://www.rfc-editor.org/rfc/rfc7628
66+
[7677]: https://www.rfc-editor.org/rfc/rfc7677
5667
[8620]: https://www.rfc-editor.org/rfc/rfc8620
5768
[8621]: https://www.rfc-editor.org/rfc/rfc8621
69+
[autoconfig]: https://wiki.mozilla.org/Thunderbird:Autoconfiguration
5870
[maildir]: https://cr.yp.to/proto/maildir.html
59-
[4918]: https://www.rfc-editor.org/rfc/rfc4918
60-
[6578]: https://www.rfc-editor.org/rfc/rfc6578
71+
[pacc]: https://datatracker.ietf.org/doc/html/draft-ietf-mailmaint-pacc
6172

6273
## Installation
6374

@@ -107,7 +118,15 @@ Run `carillon` with no command: it offers to generate an account discovered from
107118

108119
Everything discovery does not cover is written by hand: copy the annotated [config.sample.toml](./config.sample.toml), keep one backend and the hooks you want, delete the rest. It documents every key.
109120

110-
A configuration is read from `$XDG_CONFIG_HOME/carillon/config.toml`, `$HOME/.config/carillon/config.toml` or `$HOME/.carillonrc`, overridden by `-c <PATH>` or `CARILLON_CONFIG=<PATH>`. Those are the paths [himalaya](https://github.com/pimalaya/himalaya) and [himalaya-tui](https://github.com/pimalaya/himalaya-tui) read too, and an account block is written the same way, but one file does not load in all three: every backend block is strict on each side and carries keys the others do not know.
121+
A configuration is loaded from the first valid path among:
122+
123+
- $XDG_CONFIG_HOME/carillon/config.toml
124+
- $HOME/.config/carillon/config.toml
125+
- $HOME/.carillonrc
126+
127+
Override the path with `-c <PATH>` or `CARILLON_CONFIG=<PATH>`. Multiple paths can be passed at once, separated by `:`. The first one is the base and the rest are deep-merged on top. The full field reference lives in [config.sample.toml](./config.sample.toml).
128+
129+
Those are the paths [himalaya](https://github.com/pimalaya/himalaya) and [himalaya-tui](https://github.com/pimalaya/himalaya-tui) read too, and an account block is written the same way, but one file does not load in all three: every backend block is strict on each side and carries keys the others do not know.
111130

112131
An account declares one backend block (`imap`, `jmap`, `maildir`, `caldav`, `carddav`) carrying everything that backend needs: the collection it watches under its own name, how it watches (`watch`), and what it fires (`hook`). Declaring several backends is allowed; `-b/--backend` then picks one.
113132

‎build.rs‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,23 @@
1+
use std::env;
2+
13
use pimalaya_cli::build::{features_env, git_envs, target_envs};
24

35
fn main() {
46
features_env(include_str!("./Cargo.toml"));
57
target_envs();
68
git_envs();
9+
10+
// NOTE: `backend` collapses the repeated backend feature list: it is
11+
// set when any backend is enabled, which is what makes the watch
12+
// vocabulary and the hook runner reachable. Cargo exports
13+
// `CARGO_FEATURE_<NAME>` for every enabled feature.
14+
println!("cargo::rustc-check-cfg=cfg(backend)");
15+
16+
let backend = ["IMAP", "JMAP", "MAILDIR", "DAV"]
17+
.iter()
18+
.any(|feature| env::var_os(format!("CARGO_FEATURE_{feature}")).is_some());
19+
20+
if backend {
21+
println!("cargo::rustc-cfg=backend");
22+
}
723
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
cairn: delta
3+
change: guidelines-alignment
4+
---
5+
6+
## MODIFIED Requirements
7+
8+
### Requirement: Every configured account, or a chosen one
9+
Bare `carillon watch` SHALL watch every configured account at once, one thread each under a single shared shutdown. `-a/--account` SHALL narrow the watch to that account. A name no account carries SHALL be an error listing the accounts the configuration does hold, and a command needing one account with no default to pick SHALL name both ways of choosing one, so a failure to resolve an account always says what to do next. One account's watch failure SHALL be logged and retried on its own without stalling the others.
10+
11+
#### Scenario: Watch everything
12+
- **GIVEN** a config with two accounts and no account flag
13+
- **WHEN** `carillon watch` runs
14+
- **THEN** both accounts are watched at once, each on its own collection, and Ctrl+C stops them together
15+
16+
#### Scenario: A name no account carries
17+
- **GIVEN** a configuration holding the accounts `perso` and `work`
18+
- **WHEN** `carillon -a wrok watch` runs
19+
- **THEN** it fails naming `wrok` and listing `perso, work`
20+
21+
#### Scenario: One account's server is unreachable
22+
- **GIVEN** two watched accounts, one of whose servers refuses connections
23+
- **WHEN** that watch fails
24+
- **THEN** the failure is logged and retried for that account alone, and the other account keeps watching
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
cairn: change
3+
id: guidelines-alignment
4+
status: landed
5+
created: 2026-08-22
6+
---
7+
8+
# Align the repository with the Pimalaya guidelines
9+
10+
## Why
11+
12+
The org-wide guidelines (.github/GUIDELINES.md) are the settled conventions every repository is checked against, and carillon had drifted from several of them. The drift is not cosmetic in every case: a module named after a banned word teaches the wrong vocabulary to the next reader, a changelog entry describing a removal that has since been undone is simply false, and an account error that does not say what the configuration holds leaves the user guessing.
13+
14+
## What
15+
16+
- naming: the driver module goes away, its code folded into the watch command it served. The word is banned across the codebase, and the module had one caller, so nothing justified a module of its own; the two remaining uses in the wizard docs go with it.
17+
- inline-005: the dashed section banners in the configuration module are removed. The types carry doc comments already, which is what the guideline says navigation should rest on.
18+
- inline-004: every remaining bare `//` comment is tagged NOTE, the only tag any of them warranted.
19+
- naming-012: user-facing error messages start with a capital, log messages stay lowercase.
20+
- cli-002: a missing named account lists the accounts the configuration holds, and a missing default account names both ways of picking one, which `watch` and `check` did not do.
21+
- cargo-008 and cargo-009: dependencies are alphabetical, and the docs.rs metadata block a binary has no use for is gone.
22+
- changelog-001 and changelog-002: every entry opens with a one-line summary, with the detail in indented paragraphs, and the section reports the net state rather than its own history. Two entries described work that has since been undone.
23+
- markdown-003 and readme-008 and readme-010: paths leave their backticks, the features read as user features rather than as an implementation tour, and the configuration paths follow the documented shape.
24+
- every feature combination compiles warning-free, including the backendless one, through a `backend` cfg the build script sets.
25+
26+
## Not done
27+
28+
AGENTS.md keeps its backticked file names: it is vendored from the Cairn convention repository, and diverging from upstream to satisfy markdown-003 would cost more than it buys.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
cairn: tasks
3+
change: guidelines-alignment
4+
---
5+
6+
- [x] Fold the driver module into watch and purge the banned word from the code and the current docs
7+
- [x] Remove the dashed section banners and tag every remaining comment
8+
- [x] Capitalise the user-facing error messages
9+
- [x] Name what is missing when an account cannot be resolved
10+
- [x] Align the manifest, the README, the sample configuration and the changelog
11+
- [x] Keep every feature combination warning-free
12+
- [x] Fold the delta into the spec and log the change
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
---
2+
cairn: log
3+
change: guidelines-alignment
4+
landed: 2026-08-22
5+
---
6+
7+
# Align the repository with the Pimalaya guidelines
8+
9+
A pass over the whole repository against .github/GUIDELINES.md, rule by rule.
10+
11+
## What landed
12+
13+
The driver module is now supervisor, which is what its own header called it all along. The word is banned across Pimalaya, and it had also reached two lines of wizard documentation ("email-driven discovery", "it drives the prompt") and one changelog entry; all of them now say what they mean instead. The cairn log entries keep their wording, being immutable history.
14+
15+
Three code rules were failing. The configuration module carried eight dashed section banners, which inline-005 bans outright: the types they separated already have doc comments, which is what navigation is supposed to rest on. Twenty-seven bare `//` comments carried no tag, and every one of them turned out to be a NOTE. Twenty-five user-facing error messages started lowercase, against naming-012, while the log messages that must stay lowercase already did.
16+
17+
One rule was failing in a way a user would feel. cli-002 asks that a failure to resolve an account name what is missing and what to do about it, and carillon answered "Account `x` not found in config" without saying which accounts exist, or "Cannot find account" without saying that `-a` and `default = true` are the two ways to pick one. Both now say it, which is the only behaviour this pass changed.
18+
19+
The manifest lost the docs.rs metadata block a binary publishes nothing to, sorted its dependencies, and took the org author address. The README features section was rewritten as user features rather than an implementation tour, and shortened; the coverage table is sorted by spec name and gained the four discovery mechanisms the wizard actually uses. The configuration section follows the documented path shape, and paths lost their backticks there and in CONTRIBUTING.md.
20+
21+
The changelog was reshaped to changelog-001, every entry now a one-line summary with its detail in indented paragraphs. Reading it that way surfaced two entries that had become false: one announced the removal of the wizard and its discovery dependency, both of which came back the same day, and one said Cargo.toml patches io-imap to a local path, which it no longer does. changelog-002 is why they had to go: a section reports the net state, and history belongs here.
22+
23+
Every feature combination now compiles warning-free. A build enabling no backend at all still compiles the change vocabulary and the hook runner nothing then feeds, so the build script sets a `backend` cfg and main.rs allows the dead code in that one configuration, rather than gating every item of two modules on four features.
24+
25+
## Verification
26+
27+
Build, clippy and fmt green on the empty, imap, jmap, maildir, dav and full feature sets; 41 tests pass. The sample configuration was loaded by `carillon check` to prove it still parses after the edits.
28+
29+
## Not done
30+
31+
AGENTS.md keeps its backticked file names. It is vendored from the Cairn convention repository, and the cost of diverging from upstream is higher than the rule it breaks.
32+
33+
## Follow-up: the module went away rather than being renamed
34+
35+
The rename landed as supervisor, and supervisor is the same kind of name as driver: it says what the code plays rather than what it is about, which no other module here does (config, event, hook, backend, watch are all subjects).
36+
37+
So the code moved instead. `watch_account` and `watch_session` now live in watch.rs, next to the command whose threads run them, which was their one and only caller. Nothing is named after a role any more, and there is one module fewer: the file reads as the command, the accounts it selects, what each thread does, and the helpers under it.

0 commit comments

Comments
 (0)