Skip to content

Commit 1386692

Browse files
committed
feat: init code from tcard
1 parent 30aa67d commit 1386692

15 files changed

Lines changed: 737 additions & 691 deletions

File tree

.github/workflows/releases.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@ jobs:
1212
uses: pimalaya/nix/.github/workflows/releases.yml@master
1313
secrets: inherit
1414
with:
15-
project: tcard
15+
project: tcal
1616
nixpkgs: nixos-25.11

CHANGELOG.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12-
- Added the `project` / `apply` projection library between a calcard `VCard` and an ergonomic TOML buffer.
12+
- Added the `project` / `apply` projection library between a calcard `ICalendar` and an ergonomic TOML buffer.
1313

14-
`project` emits a fillable TOML form listing the modeled vCard vocabulary; fields are uncommented and empty (an empty value is ignored, like a removed line), prefilled when present, and carry an inline `# e.g. ...` hint only where the value is not self-evident (`geo`, `tz`, `impp`, `photo`, dates); the required `FN` is flagged `# required`. Typed properties (`email`, `tel`, `address`, `url`) keep a single section with their accepted `TYPE` values listed in a trailing comment. `apply` rebuilds modeled fields from the edited buffer and carries every unmodeled property (custom `X-*`, vendor extensions, Apple `item1.*` groups) over verbatim, since the TOML is an editing affordance rather than an interchange format.
14+
`project` emits a fillable TOML form from the first `VEVENT`, listing the modeled vocabulary; fields are uncommented and empty (an empty value is ignored, like a removed line), prefilled when present, and carry an inline `# e.g. ...` hint only where the value is not self-evident. Cryptic date-times become a friendly `2026-06-13 14:00` (with all-day, UTC and a broken-out `dtstart_tz` time-zone key), and attendees expand into `value` / `cn` / `role` / `partstat` blocks. `UID` and `DTSTAMP` are not modeled: they are app-managed (seeded for new events, preserved otherwise) and cannot be set through the buffer. `apply` rebuilds modeled fields from the edited buffer and carries every unmodeled property (`UID`, `DTSTAMP`, `SEQUENCE`, custom `X-*`) and every sibling component (`VALARM`, `VTIMEZONE`) over verbatim, since the TOML is an editing affordance rather than an interchange format.
1515

16-
- Added the `tcard` CLI with two verbs.
16+
- Added the `tcal` CLI with two verbs.
1717

18-
`template [SOURCE]` prints the TOML scaffold (blank or prefilled). `edit [SOURCE]` runs the full "project `$EDITOR` apply" round-trip and emits the resulting vCard, writing a file source back in place. `SOURCE` resolves deterministically: `-` reads stdin, an existing file is read, otherwise the value is treated as literal vCard contents, and omitting it starts from a blank template. A `-V`/`--version` flag on each verb selects the target vCard version (the root `--version` stays the app version), and new (sourceless) cards are seeded with a fresh `urn:uuid` v4 `UID`.
18+
`template [SOURCE]` prints the TOML scaffold (blank or prefilled). `edit [SOURCE]` runs the full "project to `$EDITOR` to apply" round-trip and emits the resulting iCalendar, writing a file source back in place. `SOURCE` resolves deterministically: `-` reads stdin, an existing file is read, otherwise the value is treated as literal iCalendar contents, and omitting it starts from a blank template. New (sourceless) events are seeded with a fresh `urn:uuid` v4 `UID` and a current `DTSTAMP`.

Cargo.lock

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
[package]
2-
name = "tcard"
3-
description = "CLI and lib to edit vCards as ergonomic TOML"
2+
name = "tcal"
3+
description = "CLI and lib to edit calendar events as ergonomic TOML"
44
version = "0.0.1"
55
authors = ["soywod <clement.douin@posteo.net>"]
66
rust-version = "1.88"
77
edition = "2024"
88
license = "MIT OR Apache-2.0"
99
categories = ["command-line-utilities", "text-processing"]
10-
keywords = ["vcard", "contact", "toml", "carddav", "editor"]
10+
keywords = ["icalendar", "calendar", "toml", "caldav", "editor"]
1111
homepage = "https://pimalaya.org"
12-
documentation = "https://docs.rs/tcard/latest/tcard"
13-
repository = "https://github.com/pimalaya/tcard"
12+
documentation = "https://docs.rs/tcal/latest/tcal"
13+
repository = "https://github.com/pimalaya/tcal"
1414

1515
[package.metadata.docs.rs]
1616
all-features = true
1717
rustdoc-args = ["--cfg", "docsrs"]
1818

1919
[features]
2020
default = ["cli"]
21-
cli = ["dep:anyhow", "dep:clap", "dep:edit", "dep:pimalaya-cli", "dep:uuid"]
21+
cli = ["dep:anyhow", "dep:chrono", "dep:clap", "dep:edit", "dep:pimalaya-cli", "dep:uuid"]
2222

2323
[profile.release]
2424
lto = "fat"
@@ -27,12 +27,12 @@ strip = "symbols"
2727
panic = "abort"
2828

2929
[[bin]]
30-
name = "tcard"
30+
name = "tcal"
3131
path = "src/main.rs"
3232
required-features = ["cli"]
3333

3434
[lib]
35-
name = "tcard"
35+
name = "tcal"
3636
path = "src/lib.rs"
3737

3838
[build-dependencies]
@@ -44,6 +44,7 @@ tempfile = "3"
4444
[dependencies]
4545
anyhow = { version = "1", optional = true }
4646
calcard = "0.3"
47+
chrono = { version = "0.4", optional = true }
4748
clap = { version = "4.4", features = ["derive", "env", "wrap_help"], optional = true }
4849
edit = { version = "0.1", optional = true }
4950
log = "0.4"

README.md

Lines changed: 62 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
# tcard [![Documentation](https://img.shields.io/docsrs/tcard?style=flat&logo=docs.rs&logoColor=white)](https://docs.rs/tcard/latest/tcard) [![Matrix](https://img.shields.io/badge/chat-%23pimalaya-blue?style=flat&logo=matrix&logoColor=white)](https://matrix.to/#/#pimalaya:matrix.org) [![Mastodon](https://img.shields.io/badge/news-%40pimalaya-blue?style=flat&logo=mastodon&logoColor=white)](https://fosstodon.org/@pimalaya)
1+
# tcal [![Documentation](https://img.shields.io/docsrs/tcal?style=flat&logo=docs.rs&logoColor=white)](https://docs.rs/tcal/latest/tcal) [![Matrix](https://img.shields.io/badge/chat-%23pimalaya-blue?style=flat&logo=matrix&logoColor=white)](https://matrix.to/#/#pimalaya:matrix.org) [![Mastodon](https://img.shields.io/badge/news-%40pimalaya-blue?style=flat&logo=mastodon&logoColor=white)](https://fosstodon.org/@pimalaya)
22

3-
CLI and lib to edit [vCards](https://www.rfc-editor.org/rfc/rfc6350) as ergonomic TOML: the TOML vCard, à la [jCard](https://www.rfc-editor.org/rfc/rfc7095).
3+
CLI and lib to edit calendar events ([iCalendar](https://www.rfc-editor.org/rfc/rfc5545) `VEVENT`) as ergonomic TOML: the TOML calendar, à la [jCal](https://www.rfc-editor.org/rfc/rfc7265).
44

5-
vCard is already plain text, so there is nothing to compress; what hurts is its crypticness (positional structured values like `N:Doe;John;;;`, opaque `TYPE` params) and the sheer number of properties nobody remembers. tcard projects a vCard into a commented, prefilled TOML scaffold you edit in `$EDITOR`, then folds your edits back onto the original card.
5+
iCalendar is already plain text, so there is nothing to compress; what hurts is its crypticness (date-times like `20260613T140000`, `;TZID=` parameters, opaque `PARTSTAT`/`ROLE` codes) and the sheer number of properties nobody remembers. tcal projects the `VEVENT` of an iCalendar into a commented, prefilled TOML scaffold you edit in `$EDITOR`, then folds your edits back onto the original calendar. Date-times become a friendly `2026-06-13 14:00` with the time zone on its own line.
66

77
This repository ships two layers:
88

9-
- Low-level **library** projecting between a [calcard](https://crates.io/crates/calcard) `VCard` and TOML: `project` emits the scaffold, `apply` rebuilds modeled fields from the edited buffer while carrying every unmodeled property (custom `X-*`, vendor extensions) over verbatim.
10-
- High-level **CLI** with two verbs: `template` prints the TOML scaffold (blank or prefilled), `edit` runs the full "project `$EDITOR` apply" round-trip and emits the resulting vCard.
9+
- Low-level **library** projecting between a [calcard](https://crates.io/crates/calcard) `ICalendar` and TOML: `project` emits the scaffold from the first `VEVENT`, `apply` rebuilds modeled fields from the edited buffer while carrying every unmodeled property (the app-managed `UID` and `DTSTAMP`, custom `X-*`) and every sibling component (`VALARM`, `VTIMEZONE`) over verbatim.
10+
- High-level **CLI** with two verbs: `template` prints the TOML scaffold (blank or prefilled), `edit` runs the full "project to `$EDITOR` to apply" round-trip and emits the resulting iCalendar.
1111

1212
## Table of contents
1313

@@ -28,151 +28,161 @@ This repository ships two layers:
2828

2929
## Features
3030

31-
- **vCard ↔ TOML projection**, backed by [calcard](https://crates.io/crates/calcard) (RFC 6350 / 6868 parser and writer).
32-
- **Discoverable form**: every modeled property is listed and empty (an empty value is ignored, like a removed line), prefilled when present, with a comment only where the value is not self-evident. Structured values (`N`, `ADR`) expand into named, ordered components instead of bare semicolons; typed properties (`email`, `tel`, ...) list their accepted `TYPE` values inline; new cards are seeded with a fresh `UID`.
33-
- **Lossless for unknown properties**: anything tcard does not model (`X-ABLabel`, `item1.*`, vendor extensions) is preserved verbatim through an `edit`. The TOML is an editing affordance, not an interchange format, so `apply` always works against the original card.
34-
- **Photo as a URI**: `PHOTO` accepts any local file or remote URL; no base64 blobs in your editor.
35-
- **Two verbs, no subcommand maze**: `template` always emits TOML, `edit` always emits a vCard; `SOURCE` resolves deterministically (`-` is stdin, an existing file is read, otherwise literal vCard contents, and omitting it starts a blank template).
31+
- **iCalendar event to TOML projection**, backed by [calcard](https://crates.io/crates/calcard) (RFC 5545 parser and writer).
32+
- **Friendly date-times**: the cryptic `20260613T140000` becomes `2026-06-13 14:00`, all-day events are `2026-06-13`, UTC values end in ` UTC`, and the time zone (`TZID`) moves onto its own `dtstart_tz` key.
33+
- **Discoverable form**: every modeled property is listed and empty (an empty value is ignored, like a removed line), prefilled when present, with a comment only where the value is not self-evident. Recurrence (`rrule`) and attendees (`role`, `partstat`) carry their accepted values inline; new events are seeded with a fresh `UID` and `DTSTAMP`.
34+
- **Lossless for everything unmodeled**: properties tcal does not list (the app-managed `UID` and `DTSTAMP`, `SEQUENCE`, custom `X-*`) and sibling components (`VALARM`, `VTIMEZONE`) are preserved verbatim through an `edit`. The TOML is an editing affordance, not an interchange format, so `apply` always works against the original calendar.
35+
- **Two verbs, no subcommand maze**: `template` always emits TOML, `edit` always emits an iCalendar; `SOURCE` resolves deterministically (`-` is stdin, an existing file is read, otherwise literal iCalendar contents, and omitting it starts a blank template).
3636

3737
> [!TIP]
38-
> tcard is written in [Rust](https://www.rust-lang.org/) and uses [cargo features](https://doc.rust-lang.org/cargo/reference/features.html) to gate the CLI. The default feature set is declared in [Cargo.toml](./Cargo.toml).
38+
> tcal is written in [Rust](https://www.rust-lang.org/) and uses [cargo features](https://doc.rust-lang.org/cargo/reference/features.html) to gate the CLI. The default feature set is declared in [Cargo.toml](./Cargo.toml).
3939
4040
## Installation
4141

4242
### Pre-built binary
4343

44-
The CLI binary `tcard` can be installed from the latest [GitHub release](https://github.com/pimalaya/tcard/releases) using the install script:
44+
The CLI binary `tcal` can be installed from the latest [GitHub release](https://github.com/pimalaya/tcal/releases) using the install script:
4545

4646
*As root:*
4747

4848
```sh
49-
curl -sSL https://raw.githubusercontent.com/pimalaya/tcard/master/install.sh | sudo sh
49+
curl -sSL https://raw.githubusercontent.com/pimalaya/tcal/master/install.sh | sudo sh
5050
```
5151

5252
*As a regular user:*
5353

5454
```sh
55-
curl -sSL https://raw.githubusercontent.com/pimalaya/tcard/master/install.sh | PREFIX=~/.local sh
55+
curl -sSL https://raw.githubusercontent.com/pimalaya/tcal/master/install.sh | PREFIX=~/.local sh
5656
```
5757

58-
For a more up-to-date version, check out the [pre-releases](https://github.com/pimalaya/tcard/actions/workflows/pre-releases.yml) GitHub workflow: pick the latest run and grab the artifact matching your OS. These are built from the `master` branch.
58+
For a more up-to-date version, check out the [pre-releases](https://github.com/pimalaya/tcal/actions/workflows/pre-releases.yml) GitHub workflow: pick the latest run and grab the artifact matching your OS. These are built from the `master` branch.
5959

6060
> [!NOTE]
6161
> Pre-built binaries are built with the default cargo features. If you need a different feature set, use another installation method.
6262
6363
### Cargo
6464

6565
```sh
66-
cargo install tcard --locked --features cli
66+
cargo install tcal --locked --features cli
6767
```
6868

6969
You can also use the git repository for a more up-to-date (but less stable) version:
7070

7171
```sh
72-
cargo install --locked --git https://github.com/pimalaya/tcard.git
72+
cargo install --locked --git https://github.com/pimalaya/tcal.git
7373
```
7474

75-
To use `tcard` as a library, add it to your `Cargo.toml`:
75+
To use `tcal` as a library, add it to your `Cargo.toml`:
7676

7777
```toml
7878
[dependencies]
79-
tcard = { version = "0.0.1", default-features = false }
79+
tcal = { version = "0.0.1", default-features = false }
8080
```
8181

82-
Dropping the default `cli` feature gives a slim library build with no clap, no editor integration: just the `project` / `apply` projection over a calcard `VCard`.
82+
Dropping the default `cli` feature gives a slim library build with no clap, no editor integration: just the `project` / `apply` projection over a calcard `ICalendar`.
8383

8484
### Nix
8585

8686
If you have the [Flakes](https://nixos.wiki/wiki/Flakes) feature enabled:
8787

8888
```sh
89-
nix profile install github:pimalaya/tcard
89+
nix profile install github:pimalaya/tcal
9090
```
9191

9292
Or run without installing:
9393

9494
```sh
95-
nix run github:pimalaya/tcard -- template < contact.vcf
95+
nix run github:pimalaya/tcal -- template < event.ics
9696
```
9797

9898
### Sources
9999

100100
```sh
101-
git clone https://github.com/pimalaya/tcard
102-
cd tcard
101+
git clone https://github.com/pimalaya/tcal
102+
cd tcal
103103
nix run
104104
```
105105

106106
## Usage
107107

108108
### Library
109109

110-
Project a vCard to TOML, then fold edits back:
110+
Project a calendar event to TOML, then fold edits back:
111111

112112
```rust,ignore
113-
use calcard::vcard::VCardVersion;
114-
use tcard::{template, vcard};
113+
use tcal::{ical, template};
115114
116-
let card = vcard::parse(input)?;
115+
let calendar = ical::parse(input)?;
117116
118-
// Emit the prefilled, documented scaffold.
119-
let scaffold = template::project(&card, VCardVersion::V4_0);
117+
// Emit the prefilled, documented scaffold from the first VEVENT.
118+
let scaffold = template::project(&calendar);
120119
121120
// ... user edits `scaffold` in an editor ...
122121
123-
// Rebuild modeled fields from the buffer; unknown properties of
124-
// `card` are preserved verbatim.
125-
let updated = template::apply(&card, &edited, VCardVersion::V4_0)?;
122+
// Rebuild modeled fields from the buffer; unknown properties and
123+
// sibling components of `calendar` are preserved verbatim.
124+
let updated = template::apply(&calendar, &edited)?;
126125
```
127126

128127
### CLI
129128

130129
Print a blank, fully-documented template:
131130

132131
```sh
133-
tcard template
132+
tcal template
134133
```
135134

136-
Project an existing vCard to TOML (path, stdin via `-`, or literal contents):
135+
Project an existing event to TOML (path, stdin via `-`, or literal contents):
137136

138137
```sh
139-
tcard template contact.vcf
140-
tcard template - < contact.vcf
138+
tcal template event.ics
139+
tcal template - < event.ics
141140
```
142141

143-
Edit a vCard in `$EDITOR`. With a file source, the result is written back in place; otherwise it goes to stdout (or `--output`):
142+
Edit an event in `$EDITOR`. With a file source, the result is written back in place; otherwise it goes to stdout (or `--output`):
144143

145144
```sh
146-
tcard edit contact.vcf
147-
tcard edit - < contact.vcf > updated.vcf
148-
tcard template | $EDITOR /dev/stdin # inspect the scaffold first
145+
tcal edit event.ics
146+
tcal edit - < event.ics > updated.ics
147+
tcal template | $EDITOR /dev/stdin # inspect the scaffold first
149148
```
150149

151-
Start a new card from scratch and write it out:
150+
Start a new event from scratch and write it out:
152151

153152
```sh
154-
tcard edit --output alice.vcf
155-
tcard edit --version 3.0 --output bob.vcf
153+
tcal edit --output meeting.ics
156154
```
157155

158156
## FAQ
159157

160158
<details>
161-
<summary>How does `tcard edit` pick the editor?</summary>
159+
<summary>Which calendar component does tcal edit?</summary>
162160

163-
The [edit](https://crates.io/crates/edit) crate resolves `$VISUAL` first, then `$EDITOR`, then an OS default. tcard does not expose a config override: set `VISUAL` / `EDITOR` in your shell rc file.
161+
The first `VEVENT` of the iCalendar. Other components (`VTIMEZONE`, `VALARM`, additional events) are kept verbatim but not surfaced in the scaffold. `VTODO`, `VJOURNAL` and friends are out of scope for now.
164162
</details>
165163

166164
<details>
167-
<summary>Why did my card get reformatted on the first edit?</summary>
165+
<summary>How do I write dates and times?</summary>
168166

169-
tcard serializes through calcard, which normalizes line folding and parameter casing (`TYPE=work` becomes `TYPE=WORK`). The first edit of a foreign card reflows it once; output is stable afterwards. Property values and every unmodeled property are preserved verbatim, so no data is lost: only whitespace and casing change.
167+
Use `YYYY-MM-DD HH:MM` for a timed event (`2026-06-13 14:00`), `YYYY-MM-DD` alone for an all-day event, and append ` UTC` for a UTC value. For a zoned time, set the adjacent `dtstart_tz` / `dtend_tz` key to an IANA zone like `Europe/Paris`; leave it empty for UTC or floating time. A raw iCalendar value (`20260613T140000`) is accepted too.
170168
</details>
171169

172170
<details>
173-
<summary>What happens to properties tcard does not list?</summary>
171+
<summary>How does `tcal edit` pick the editor?</summary>
174172

175-
They are kept verbatim. The scaffold only surfaces the modeled vocabulary, but `apply` carries every other property (custom `X-*`, Apple `item1.*` groups, vendor extensions) straight from the original card into the result.
173+
The [edit](https://crates.io/crates/edit) crate resolves `$VISUAL` first, then `$EDITOR`, then an OS default. tcal does not expose a config override: set `VISUAL` / `EDITOR` in your shell rc file.
174+
</details>
175+
176+
<details>
177+
<summary>Why did my calendar get reformatted on the first edit?</summary>
178+
179+
tcal serializes through calcard, which normalizes line folding and parameter casing. The first edit of a foreign calendar reflows it once; output is stable afterwards. Property values, every unmodeled property and every sibling component are preserved verbatim, so no data is lost: only whitespace and casing change.
180+
</details>
181+
182+
<details>
183+
<summary>What happens to properties and components tcal does not list?</summary>
184+
185+
They are kept verbatim. The scaffold only surfaces the modeled `VEVENT` vocabulary, but `apply` carries every other property (`DTSTAMP`, `SEQUENCE`, custom `X-*`) and every sibling component (`VALARM`, `VTIMEZONE`) straight from the original calendar into the result.
176186
</details>
177187

178188
<details>
@@ -181,7 +191,7 @@ tcard edit --version 3.0 --output bob.vcf
181191
Use `--log <level>` where `<level>` is one of `off`, `error`, `warn`, `info`, `debug`, `trace`:
182192

183193
```sh
184-
tcard --log trace template contact.vcf
194+
tcal --log trace template event.ics
185195
```
186196

187197
The `RUST_LOG` environment variable, when set, overrides `--log` and supports per-target filters (see the [env_logger](https://docs.rs/env_logger/latest/env_logger/#enabling-logging) documentation). `RUST_BACKTRACE=1` enables full error backtraces. Logs are written to `stderr`.
@@ -210,7 +220,7 @@ This project is developed with AI assistance. This section documents how, so use
210220

211221
- **Limitations**: AI models occasionally produce code that compiles and passes tests but is subtly wrong: off-by-one errors, missed edge cases, plausible but nonexistent APIs, stale RFC references. The verification workflow catches most of this; it does not catch all of it. Bug reports are welcome and taken seriously.
212222

213-
- **Last reviewed**: 12/06/2026
223+
- **Last reviewed**: 13/06/2026
214224

215225
## Social
216226

flake.nix

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
description = "CLI and lib to edit vCards as ergonomic TOML, written in Rust";
2+
description = "CLI and lib to edit calendar events as ergonomic TOML, written in Rust";
33

44
inputs = {
55
nixpkgs = {

0 commit comments

Comments
 (0)