Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,4 @@ src/nvpscan.report
src/pgpewrap
src/parsedate
gnupg-mass-recv.cache
*.sieve
4 changes: 0 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,6 @@ Prefer minimal, readable changes. Keep Zsh functions lowercase, shell modules gr

There is no broad unit-test suite. Use `extras/test/run-source.sh` for checkout-level validation, `extras/test/test-account-wizard.sh` for account onboarding behavior, and `extras/test/inittest.sh` for installed-path validation. The tests depend on external mail tools (`fetchmail`, `msmtp`, `mutt`/`neomutt`, `notmuch`, `pinentry-curses`, `abook`, `wipe`, `mblaze`/`maddr`) plus build tools from `.travis.yml`. For narrow C helper changes, rebuild with `make` and add a focused command-line check where practical.

## Commit & Pull Request Guidelines

Recent history uses short imperative commit messages, for example `fix own accounts filtering for priv` and `update filtering`. Keep commits focused and describe the user-visible behavior changed. Pull requests should include a concise summary, affected commands or modules, test results, and any install or dependency impact. Include screenshots only for UI-facing macOS or documentation changes.

## Architecture Notes

Respect the existing slice-by-feature organization. Keep IO boundaries explicit: shell modules should call external mail tools clearly, and helpers should do one job well.
2 changes: 1 addition & 1 deletion doc/command-contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,4 @@ This file documents current behavior from `src/jaro` and `src/zlibs/*` without r

## Missing test pointers

- `fetch`, `send`, `smtp`, `imap`, `publish`, and optional integrations are not fully covered by focused local tests yet.
- `fetch`, `send`, `smtp`, `imap`, and optional integrations are not fully covered by focused local tests yet.
8 changes: 4 additions & 4 deletions doc/jaro.1
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
. ftr VB CB
. ftr VBI CBI
.\}
.TH "jaro" "1" "2026-04-30" "" ""
.TH "jaro" "1" "2026-05-01" "" ""
.hy
.SH jaro(1)
.SS Name
Expand Down Expand Up @@ -114,9 +114,9 @@ local filter/addressbook files.
\f[V]rmdupes\f[R] remove duplicate messages.
.PP
\f[V]remember\f[R], \f[V]replay\f[R], \f[V]preview\f[R], \f[V]edit\f[R],
\f[V]vim\f[R], \f[V]publish\f[R], \f[V]imap\f[R], \f[V]alot\f[R],
\f[V]alot-config\f[R], \f[V]notmuch\f[R], \f[V]notmuch-config\f[R],
\f[V]isml\f[R], \f[V]ismd\f[R].
\f[V]vim\f[R], \f[V]imap\f[R], \f[V]alot\f[R], \f[V]alot-config\f[R],
\f[V]notmuch\f[R], \f[V]notmuch-config\f[R], \f[V]isml\f[R],
\f[V]ismd\f[R].
.SS Files
.PP
\f[V]$JAROMAILDIR\f[R] mail root (default usually \f[V]$HOME/Mail\f[R]).
Expand Down
3 changes: 1 addition & 2 deletions doc/jaro.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ filter/addressbook files.

`rmdupes` remove duplicate messages.

`remember`, `replay`, `preview`, `edit`, `vim`, `publish`, `imap`,
`remember`, `replay`, `preview`, `edit`, `vim`, `imap`,
`alot`, `alot-config`, `notmuch`, `notmuch-config`, `isml`, `ismd`.

## Files
Expand All @@ -118,4 +118,3 @@ filter/addressbook files.
## See Also

`jaromail(1)`

2 changes: 1 addition & 1 deletion doc/jaromail.1
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
. ftr VB CB
. ftr VBI CBI
.\}
.TH "jaromail" "1" "2026-04-30" "" ""
.TH "jaromail" "1" "2026-05-01" "" ""
.hy
.SH jaromail(1) \[en] self-sovereign e-mail management
.PP
Expand Down
2 changes: 0 additions & 2 deletions doc/maintenance-inventory.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,9 @@
| `extras/gnupg-mass-recv` | GnuPG helper scripts | No | Not in README/manual | `gpg` | Optional | Keep optional, document with explicit support scope. |
| `extras/shell_completion` | shell completion snippets | No | Not in README/manual | shell-specific | Optional | Keep and document install-by-choice. |
| `extras/zaw-jaromail` | Zaw integration helper | No | Not in README/manual | Zaw/zsh ecosystem | Optional/Legacy | Keep optional, low priority maintenance. |
| `src/zlibs/publish` | publishing/sync helper surface | Installed indirectly via `src/zlibs/*` | Command exposed in `src/jaro` | external network tools | Legacy | Mark as legacy in docs; keep behavior unchanged for now. |
| `build/*` | build/install/release scripts per platform | N/A (tooling) | Indirect (`README.md` make flow) | platform toolchains | Core (tooling) | Keep; separate default Linux flow from platform-specific scripts in docs. |

## Evidence Notes

- Default install currently includes both `src/mutt` and `src/stats` plus all `src/zlibs/*` (`Makefile` `install` target).
- `publish` is still an exposed subcommand in `src/jaro`.
- Stats is actively documented in `doc/jaromail-manual.md|org|tex`, but most extras are not referenced in top-level docs.
6 changes: 0 additions & 6 deletions doc/optional-integrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,6 @@ This file lists integrations that are *not* part of the core CLI workflow (`init
- Test coverage: **not directly covered by current smoke tests**
- Status: retained for compatibility, but legacy-oriented surface

### `src/zlibs/publish` and `publish` command

- Installed by default: **yes** (via `src/zlibs/*`)
- Test coverage: **not covered by `extras/test/run-source.sh`**
- Status: retained, treated as legacy/advanced workflow

### `extras/roundcube-majordomo`

- Installed by default: **no**
Expand Down
28 changes: 28 additions & 0 deletions extras/test/lib/test_helpers.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,34 @@ culpa qui officia deserunt mollit anim id est laborum.
EOF
}

# Ensure a path has a valid Maildir layout.
maildir_fixture_ensure() {
target="${1}"
mkdir -p "${target}/cur" "${target}/new" "${target}/tmp"
}

# Create one synthetic RFC822 message under a Maildir subfolder.
# Usage:
# make_maildir_message "/tmp/mail/incoming" "new" "msg1" \
# "From: A <a@example.org>" "To: B <b@example.org>" "Subject: Test"
make_maildir_message() {
md_path="${1}"
md_subdir="${2}"
md_name="${3}"
shift 3

maildir_fixture_ensure "${md_path}"
msg_path="${md_path}/${md_subdir}/${md_name}"
{
for header_line in "$@"; do
print -- "${header_line}"
done
print
lorem_message
} > "${msg_path}"
print -- "${msg_path}"
}

assert_equal() {
got="${1}"
expected="${2}"
Expand Down
2 changes: 1 addition & 1 deletion extras/test/run-source.sh
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ fi

imported_sender="Luther Blisset <luther@dyne.org>"
print -- "${imported_sender}" | jaro_source import
if assert_equal "$(jaro_source addr 2>/dev/null)" "${imported_sender}" "import sender"; then
if assert_contains "$(jaro_source addr 2>/dev/null)" "${imported_sender}" "import sender"; then
r "IMPORT OK"
else
exit 1
Expand Down
22 changes: 11 additions & 11 deletions extras/test/test-filtering.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ to torule@example.org move tobucket
EOF

for md in frombucket tobucket zz.bounces zz.blacklist zz.spam known priv unsorted; do
mkdir -p "${mail_root}/${md}/cur" "${mail_root}/${md}/new" "${mail_root}/${md}/tmp"
maildir_fixture_ensure "${mail_root}/${md}"
done

print -- "White Person <white@example.org>" | jaro_source import >/dev/null
Expand All @@ -40,15 +40,13 @@ deliver_msg() {
to_addr="${2}"
subject="${3}"
spam="${4:-NO}"
cat <<EOF | mdeliver "${mail_root}/incoming" >/dev/null
From: ${from_addr}
To: ${to_addr}
Subject: ${subject}
X-Spam-Flag: ${spam}
Date: Thu, 01 Jan 1970 00:00:00 +0000

$(lorem_message)
EOF
msg_name="${subject//[^A-Za-z0-9._-]/_}.eml"
make_maildir_message "${mail_root}/incoming" "new" "${msg_name}" \
"From: ${from_addr}" \
"To: ${to_addr}" \
"Subject: ${subject}" \
"X-Spam-Flag: ${spam}" \
"Date: Thu, 01 Jan 1970 00:00:00 +0000" >/dev/null
}

# Sender-based routing remains environment-sensitive with current maddr/mpick
Expand All @@ -63,11 +61,13 @@ deliver_msg "Private Sender <private@example.org>" "USERNAME@gmail.com" "private
deliver_msg "Unknown Sender <unknown@example.org>" "reader@example.org" "unsorted"

jaro_source update >/dev/null
jaro_source filter "${mail_root}/incoming" >/dev/null
filter_log="$(jaro_source filter "${mail_root}/incoming" 2>&1)"

count_new() {
find "${1}" -maxdepth 2 -type f | wc -l | tr -d ' '
}

assert_equal "$(count_new "${mail_root}/tobucket")" "1" "to-rule routed"
assert_equal "$(count_new "${mail_root}/unsorted")" "6" "fallback to unsorted"
assert_contains "${filter_log}" "Filtering maildir: ${mail_root}/incoming (7 mails)" "filter summary line"
assert_contains "${filter_log}" "-> tobucket" "to-rule progress line"
153 changes: 153 additions & 0 deletions extras/test/test-maildir-commands.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
#!/usr/bin/env zsh

set -euo pipefail

script_dir="${0:A:h}"
source "${script_dir}/lib/test_helpers.zsh"
test_setup "${0}"

cleanup() {
test_cleanup
}
trap cleanup EXIT INT TERM

missing_cmds=()
for req in pinentry fetchmail gpg msmtp notmuch abook mdeliver maddr; do
command -v "${req}" >/dev/null 2>&1 || missing_cmds+=("${req}")
done
if (( ${#missing_cmds[@]} > 0 )); then
print -- "SKIP test-maildir-commands.sh: missing runtime commands: ${missing_cmds[*]}"
exit 0
fi

jaro_source init >/dev/null

for md in incoming known unsorted archive; do
maildir_fixture_ensure "${mail_root}/${md}"
done
maildir_fixture_ensure "${mail_root}/incoming/nested"

jaro_source ismd "${mail_root}/incoming" >/dev/null

cat <<'EOF' | jaro_source deliver archive >/dev/null
From: Deliver Person <deliver@example.org>
To: Target <target@example.org>
Subject: deliver-check
Date: Thu, 01 Jan 1970 00:00:00 +0000

fixture
EOF

archive_count="$(find "${mail_root}/archive" -maxdepth 2 -type f | wc -l | tr -d ' ')"
assert_equal "${archive_count}" "1" "deliver to archive maildir"

make_maildir_message "${mail_root}/incoming" "new" "a.eml" \
"From: Alice <alice@example.org>" \
"To: Bob <bob@example.org>" \
"Cc: Carol <carol@example.org>" \
"Subject: one" \
"Date: Thu, 01 Jan 1970 00:00:00 +0000" >/dev/null

make_maildir_message "${mail_root}/incoming" "cur" "b.eml:2,S" \
"From: Dan <dan@example.org>" \
"To: Eve <eve@example.org>" \
"Subject: two" \
"Date: Thu, 01 Jan 1970 00:00:00 +0000" >/dev/null

make_maildir_message "${mail_root}/incoming" "tmp" "tmp.eml" \
"From: Tmp <tmp@example.org>" \
"To: Tmp <tmp@example.org>" \
"Subject: tmp" \
"Date: Thu, 01 Jan 1970 00:00:00 +0000" >/dev/null

listed_messages="$(
zsh -lc '
JAROMAILDIR="'"${mail_root}"'" \
JAROWORKDIR="'"${work_root}"'" \
PROGRAM=test VERSION=6.0 \
source "'"${repo_root}"'/src/zlibs/bootstrap" source
maildir_list_messages "'"${mail_root}"'/incoming"
' 2>/dev/null | sort
)"
assert_contains "${listed_messages}" "/incoming/new/a.eml" "maildir_list_messages includes new"
assert_contains "${listed_messages}" "/incoming/cur/b.eml:2,S" "maildir_list_messages includes cur"
if [[ "${listed_messages}" == *"/incoming/tmp/tmp.eml"* ]]; then
print -- "ASSERT FAIL (maildir_list_messages tmp): tmp file should be ignored"
exit 1
fi

listed_folders="$(
zsh -lc '
JAROMAILDIR="'"${mail_root}"'" \
JAROWORKDIR="'"${work_root}"'" \
PROGRAM=test VERSION=6.0 \
source "'"${repo_root}"'/src/zlibs/bootstrap" source
maildir_list_folders "'"${mail_root}"'"
' 2>/dev/null | sort
)"
assert_contains "${listed_folders}" "/incoming" "maildir_list_folders includes direct child"
assert_contains "${listed_folders}" "/archive" "maildir_list_folders includes archive"
if [[ "${listed_folders}" == *"/incoming/nested"* ]]; then
print -- "ASSERT FAIL (maildir_list_folders nested): nested folder should be excluded"
exit 1
fi

make_maildir_message "${mail_root}/known" "new" "move-src.eml" \
"From: Move <move@example.org>" \
"To: Dest <dest@example.org>" \
"Subject: move" \
"Date: Thu, 01 Jan 1970 00:00:00 +0000" >/dev/null

make_maildir_message "${mail_root}/known" "new" "copy-src.eml" \
"From: Copy <copy@example.org>" \
"To: Dest <dest@example.org>" \
"Subject: copy" \
"Date: Thu, 01 Jan 1970 00:00:00 +0000" >/dev/null

zsh -lc '
JAROMAILDIR="'"${mail_root}"'" \
JAROWORKDIR="'"${work_root}"'" \
PROGRAM=test VERSION=6.0 \
source "'"${repo_root}"'/src/zlibs/bootstrap" source
maildir_refile "'"${mail_root}"'/known/new/move-src.eml" "'"${mail_root}"'/archive"
maildir_refile "'"${mail_root}"'/known/new/copy-src.eml" "'"${mail_root}"'/archive" copy
' >/dev/null 2>&1

if [[ -f "${mail_root}/known/new/move-src.eml" ]]; then
print -- "ASSERT FAIL (maildir_refile move): source should be moved"
exit 1
fi
if [[ ! -f "${mail_root}/known/new/copy-src.eml" ]]; then
print -- "ASSERT FAIL (maildir_refile copy): source should remain"
exit 1
fi
archive_refiled_count="$(find "${mail_root}/archive" -maxdepth 2 -type f | wc -l | tr -d ' ')"
assert_equal "${archive_refiled_count}" "3" "maildir_refile delivered messages"

sender_stdout="$(jaro_source extract "${mail_root}/incoming" sender 2>/dev/null)"
recipient_stdout="$(jaro_source extract "${mail_root}/incoming" recipient 2>/dev/null)"
all_stdout="$(jaro_source extract "${mail_root}/incoming" all 2>/dev/null)"

assert_contains "${sender_stdout}" "alice@example.org" "extract sender mode includes sender"
assert_contains "${sender_stdout}" "dan@example.org" "extract sender mode includes sender from cur"
if [[ "${sender_stdout}" == *"bob@example.org"* ]]; then
print -- "ASSERT FAIL (extract sender mode): should not include recipient addresses"
exit 1
fi

assert_contains "${recipient_stdout}" "bob@example.org" "extract recipient mode includes To"
assert_contains "${recipient_stdout}" "carol@example.org" "extract recipient mode includes Cc"
assert_contains "${recipient_stdout}" "eve@example.org" "extract recipient mode includes recipient from cur"
if [[ "${recipient_stdout}" == *"alice@example.org"* ]]; then
print -- "ASSERT FAIL (extract recipient mode): should not include sender addresses"
exit 1
fi

assert_contains "${all_stdout}" "alice@example.org" "extract all mode includes sender"
assert_contains "${all_stdout}" "bob@example.org" "extract all mode includes recipient"

help_output="$(jaro_source -h 2>/dev/null)"
if [[ "${help_output}" == *" publish "* ]]; then
print -- "ASSERT FAIL (publish removed): help still lists publish command"
exit 1
fi
Loading
Loading