feat(gmail): add gmail render command and read -o markdown - #1516
Merged
Conversation
gmail sync archives full-fidelity, byte-exact .eml files (ADR-0064), but nothing in the codebase turned one back into something readable: raw_message::extract_headers never decodes RFC 2047 encoded-word subjects, attachments.rs's real MIME parser only ever extracted attachment files, and gmail read's renderers never printed the body at all. Adds src/gmail/render.rs::render_markdown(raw) -> String, a sibling of attachments.rs rather than an extension of raw_message.rs's line-oriented scanner -- the same "needs real MIME parsing" reasoning that module's own doc comment already gives for attachment bytes applies at least as strongly here, plus RFC 2047 decoding that scanner explicitly skips. Renders a header block (Subject/From/To/Cc/Date/ Message-Id/In-Reply-To/References, RFC 2047-decoded via mail-parser's own header accessors -- no new decoding logic needed), the body (a genuine text/plain part preferred; text/html converted to Markdown via the new htmd crate otherwise, chosen over plain HTML-to-text since the output format already is Markdown), and a bullet list of attachment filenames (listed, never embedded -- this is a readable rendering, not an export). Never fails: an unparseable message degrades to a short placeholder rather than erroring, so one bad file can't abort a batch caller. omni-dev gmail render <PATH.eml>... [--out-dir DIR] [-o FORMAT] (src/cli/gmail/render.rs) is dispatched alongside sync-all/ extract-attachments's no-client special cases -- purely local, no GmailClient, no network, --account rejected outright. Deliberately takes bare file paths rather than a Manifest/--archive-dir + message id, so it works on any .eml file with no dependency on this tool having synced the mailbox at all. Default -o table prints each input's rendered Markdown straight to stdout (multiple inputs separated by a --- thematic break, and no trailing summary line in the all-success case, so `gmail render *.eml > combined.md` stays clean); --out-dir DIR instead writes one .md file per input (named after its stem) and prints a Saved to: line per file; -o json/yaml/yamls/jsonl emit one structured record per input via the shared blanket Vec<T> JsonlSerialize impl. A per-file read/write failure is recorded against that file rather than aborting the batch, though the command still exits non-zero if any file failed. gmail read -o markdown (ReadOutputFormat, src/cli/gmail/read.rs) reuses render_markdown against a live message, always fetching format=raw regardless of --detail (rendering needs the full raw MIME structure). Kept as a read-local enum extending the shared OutputFormat with a Markdown variant, rather than adding Markdown to the crate-wide OutputFormat every other CLI surface's -o renders generically via Serialize -- a Markdown variant only makes sense for a MIME message. Quote-folding and a Manifest-aware --archive-dir --all batch mode were raised as open questions and deliberately deferred to #1514 and #1515 rather than blocking this issue. Closes #1513
- docs/gmail.md gains a new "Render" section (Table of Contents included) following the Extract attachments style, plus an -o markdown paragraph under Messages and a cross-reference from Sync's Header fields paragraph -- the one that documents the RFC 2047 non-decoding gap -- pointing at gmail render/gmail read -o markdown as the fix for reading such a message properly. Output formats gains a note that gmail read additionally accepts -o markdown. - CHANGELOG.md's Unreleased/Added section gains an entry for #1513, matching the existing Gmail feature entries.
CoverageTotal: 97.32% 🟢 0.01 pp vs Comparing
Patch coveragePatch: 99.72% (700/702 new lines covered)
Uncovered new lines (2)
|
Adds tests for the branches the coverage bot flagged as uncovered: gmail read -o yaml/yamls/jsonl, the render fs::write error path, md_filename's no-stem fallback, render_report_text's saved_to and empty-entry branches, gmail render's In-Reply-To/References headers, bare-display-name addresses, and attachment-only bodies, and body_markdown's two content-type/body-mismatch fallback branches (constructed directly against synthetic Message values, since mail-parser's real parser never produces that mismatch).
newhoggy
force-pushed
the
issue-1513-gmail-render-eml-markdown
branch
from
August 7, 2026 12:41
3fd4165 to
b49787a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
omni-dev gmail render <PATH.eml>... [--out-dir DIR] [-o FORMAT], a purely-local, no-network, no-credential command that renders archived.emlfiles as human-readable Markdown (header block with RFC 2047-decoded Subject/From/To/Cc/Date/Message-Id/In-Reply-To/References,text/plain-preferred body with anhtmd-convertedtext/htmlfallback, and an attachment filename list).gmail read -o markdown, sharing the samerender_markdownfunction against a live message fetch (format=raw, ignoring--detail).Manifest-aware--archive-dir --allbatch mode) were deliberately deferred to follow-ups feat(gmail): fold/summarize deeply-nested quoted reply chains in gmail render #1514 and feat(gmail): gmail render --archive-dir PATH --all whole-archive batch mode #1515 rather than blocking this.## Rendersection indocs/gmail.md,-o markdowndocumented under Messages, a cross-reference from Sync's RFC 2047 non-decoding gap note, and a CHANGELOG entry.Test plan
cargo test --workspace --no-fail-fast— all pass except the pre-existing, environment-unrelatedtests/daemon_test.rslifecycle flake (confirmed reproducing in isolation, no daemon files touched by this branch)cargo clippy --all-targets -- -D warningscleancargo fmt --checkcleanupdate-snapshotsskill run;tests/snapshots/integration_test__help_all_output.snapreviewed and accepted