build: release v1.0.0

Refs: #514
This commit is contained in:
Clément DOUIN 2024-12-09 12:04:15 +01:00 committed by GitHub
parent 6e658fef33
commit ce0b2dd8d3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
50 changed files with 1021 additions and 788 deletions

View file

@ -1,13 +0,0 @@
name: Pre-releases
on:
workflow_dispatch:
push:
branch: master
jobs:
build:
uses: pimalaya/nix/.github/workflows/pre-releases.yml@master
secrets: inherit
with:
project: himalaya

View file

@ -1,86 +0,0 @@
name: release
on:
push:
tags:
- v*
jobs:
create-release:
runs-on: ubuntu-latest
outputs:
upload_url: ${{ steps.create-release.outputs.upload_url }}
steps:
- name: Create release
id: create-release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
draft: false
prerelease: false
deploy-releases:
runs-on: ${{ matrix.os }}
needs: create-release
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
host: x86_64-linux
target: x86_64-linux
- os: ubuntu-latest
host: x86_64-linux
target: aarch64-linux
- os: ubuntu-latest
host: x86_64-linux
target: x86_64-windows
- os: macos-13
host: x86_64-darwin
target: x86_64-darwin
- os: macos-14
host: aarch64-darwin
target: aarch64-darwin
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Nix
uses: cachix/install-nix-action@v27
with:
nix_path: nixpkgs=channel:nixos-24.05
extra_nix_config: |
experimental-features = nix-command flakes
- uses: cachix/cachix-action@v15
with:
name: soywod
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
extraPullNames: nix-community
- name: Build release archive
run: |
nix build -L --expr "
(builtins.getFlake \"git+file://${PWD}?shallow=1&rev=$(git rev-parse HEAD)\")
.outputs.packages.${{ matrix.host }}.${{ matrix.target }}.overrideAttrs {
GIT_DESCRIBE = \"$(git describe)\";
}"
cp result/himalaya* .
- name: Upload tgz release archive
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.create-release.outputs.upload_url }}
asset_path: himalaya.tgz
asset_name: himalaya.${{ matrix.target }}.tgz
asset_content_type: application/gzip
- name: Upload zip release archive
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.create-release.outputs.upload_url }}
asset_path: himalaya.zip
asset_name: himalaya.${{ matrix.target }}.zip
asset_content_type: application/zip

15
.github/workflows/releases.yml vendored Normal file
View file

@ -0,0 +1,15 @@
name: Releases
on:
push:
tags:
- v*
branches:
- master
jobs:
release:
uses: pimalaya/nix/.github/workflows/releases.yml@master
secrets: inherit
with:
project: himalaya

View file

@ -7,8 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [1.0.0] - 2024-12-09
The Himalaya CLI scope has changed. It does not include anymore the synchronization, nor the envelope watching. These scopes have moved to dedicated projects:
- [Neverest CLI](https://github.com/pimalaya/neverest), CLI to synchronize, backup and restore emails
- [Mirador CLI](https://github.com/pimalaya/mirador), CLI to watch mailbox changes
Due to the long time difference with the previous `v1.0.0-beta.4` release, this changelog may be incomplete. The simplest way to upgrade is to reconfigure Himalaya CLI from scratch, using the wizard or the [`config.sample.toml`](./config.sample.toml).
Himalaya CLI will now try to adopt the [conventional commits specification](https://github.com/conventional-commits/conventionalcommits.org). Tools like [`git-cliff`](https://git-cliff.org/) may help us generating more accurate changelogs in the future.
### Added
- Added `message edit` command to edit a message. To edit on place (replace a message), use `--on-place`.
- Added `account.list.table.preset` global config option, `accounts.<name>.folder.list.table.preset` and `accounts.<name>.envelope.list.table.preset` account config options.
These options customize the shape of tables, see examples at [`comfy_table::presets`](https://docs.rs/comfy-table/latest/comfy_table/presets/index.html). Defaults to `"|| |-||| "`, which corresponds to [`comfy_table::presets::ASCII_MARKDOWN`](https://docs.rs/comfy-table/latest/comfy_table/presets/constant.ASCII_MARKDOWN.html).
@ -30,6 +42,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Refactored the `account configure` command: this command stands now for creating or editing account configurations from the wizard. The command requires the `wizard` cargo feature.
- Improved the `account doctor` command: it now checks the state of the config, and the new `--fix` argument allows you to configure keyring, OAuth 2.0 etc.
- Improved long version `--version`. [#496]
- Improved error messages when missing cargo features. For example, if a TOML configuration uses the IMAP backend without the `imap` cargo features, the error `missing "imap" feature` is displayed. [#20](https://github.com/pimalaya/core/issues/20)
- Normalized enum-based configurations, using the [internally tagged representation](https://serde.rs/enum-representations.html#internally-tagged) `type =`. It should reduce issues due to misconfiguration, and improve othe error messages. Yet it is not perfect, see [#802](https://github.com/toml-rs/toml/issues/802):
@ -86,12 +100,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
message.send.backend.auth.method = "xoauth2"
```
- Moved IMAP and SMTP `encryption` to `encryption.type`.
This change prepares the config to accept different TLS providers with their options. The `true` and `false` variant have been removed as well:
```toml
# before
backend.encryption = "none" # or false
backend.encryption = "start-tls"
message.send.backend.encryption = "tls" # or true
# after
backend.encryption.type = "none"
backend.encryption.type = "start-tls"
message.send.backend.encryption.type = "tls"
```
### Fixed
- Fixed pre-release archives issue. [#492]
- Fixed mailto parsing issue. [core#10]
- Fixed `Answered` flag not set when replying to a message. [#508]
### Removed
- Removed systemd service from `assets/` folder, as Himalaya CLI scope does not include synchronization nor watching anymore.
## [1.0.0-beta.4] - 2024-04-16
### Added
@ -856,7 +890,10 @@ Few major concepts changed:
- Password from command
- Set up README
[Unreleased]: https://github.com/soywod/himalaya/compare/v1.0.0-beta.2...HEAD
[Unreleased]: https://github.com/soywod/himalaya/compare/v1.0.0...HEAD
[1.0.0]: https://github.com/soywod/himalaya/compare/v1.0.0-beta.4...v1.0.0
[1.0.0-beta.4]: https://github.com/soywod/himalaya/compare/v1.0.0-beta.3...v1.0.0-beta.4
[1.0.0-beta.3]: https://github.com/soywod/himalaya/compare/v1.0.0-beta.2...v1.0.0-beta.3
[1.0.0-beta.2]: https://github.com/soywod/himalaya/compare/v1.0.0-beta...v1.0.0-beta.2
[1.0.0-beta]: https://github.com/soywod/himalaya/compare/v0.9.0...v1.0.0-beta
[0.9.0]: https://github.com/soywod/himalaya/compare/v0.8.4...v0.9.0

View file

@ -17,15 +17,6 @@ $ cargo build
$ cargo run --feature pgp-gpg -- envelope list
```
## Contributing
## Commit style
Himalaya CLI supports open-source, hence the choice of using [SourceHut](https://sourcehut.org/) for managing the project. The only reason why the source code is hosted on GitHub is to build releases for all major platforms (using GitHub Actions). Don't worry, contributing on SourceHut is not a big deal: you just need to send emails! You don't need to create any account. Here a small comparison guide with GitHub:
The equivalent of **GitHub Discussions** are:
- The [Matrix](https://matrix.org/) chat room [#pimalaya.himalaya](https://matrix.to/#/#pimalaya.himalaya:matrix.org)
- The SourceHut mailing list. You can consult existing messages [here](https://lists.sr.ht/~soywod/pimalaya). You can "open a new discussion" by sending an email at [~soywod/pimalaya@lists.sr.ht](mailto:~soywod/pimalaya@lists.sr.ht). You can also [subscribe](mailto:~soywod/pimalaya+subscribe@lists.sr.ht) and [unsubscribe](mailto:~soywod/pimalaya+unsubscribe@lists.sr.ht) to the mailing list, so you can receive a copy of all discussions.
The equivalent of **GitHub Issues** is the SourceHut bug tracker. You can consult existing bugs [here](https://todo.sr.ht/~soywod/pimalaya), and you can "open a new issue" by sending an email at [~soywod/pimalaya@todo.sr.ht](mailto:~soywod/pimalaya@todo.sr.ht).
The equivalent of **GitHub Pull requests** is the SourceHut mailing list. You can "open a new pull request" by sending an email containing a git patch at [~soywod/pimalaya@lists.sr.ht](mailto:~soywod/pimalaya@lists.sr.ht). The simplest way to send a patch is to use [git send-email](https://git-scm.com/docs/git-send-email), follow [this guide](https://git-send-email.io/) to configure git properly.
Starting from the `v1.0.0`, Himalaya CLI tries to adopt the [conventional commits specification](https://github.com/conventional-commits/conventionalcommits.org).

396
Cargo.lock generated
View file

@ -78,9 +78,9 @@ checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
[[package]]
name = "allocator-api2"
version = "0.2.20"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "android-tzdata"
@ -261,7 +261,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.89",
"syn 2.0.90",
]
[[package]]
@ -296,7 +296,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.89",
"syn 2.0.90",
]
[[package]]
@ -432,7 +432,7 @@ checksum = "e0af050e27e5d57aa14975f97fe47a134c46a390f91819f23a625319a7111bfa"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.89",
"syn 2.0.90",
]
[[package]]
@ -509,9 +509,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.2"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc"
checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d"
dependencies = [
"jobserver",
"libc",
@ -557,9 +557,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "chrono"
version = "0.4.38"
version = "0.4.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
dependencies = [
"android-tzdata",
"iana-time-zone",
@ -594,9 +594,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.21"
version = "4.5.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f"
checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84"
dependencies = [
"clap_builder",
"clap_derive",
@ -604,9 +604,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.21"
version = "4.5.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec"
checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838"
dependencies = [
"anstream",
"anstyle",
@ -633,14 +633,14 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.89",
"syn 2.0.90",
]
[[package]]
name = "clap_lex"
version = "0.7.3"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7"
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
[[package]]
name = "clap_mangen"
@ -917,7 +917,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.89",
"syn 2.0.90",
]
[[package]]
@ -1143,7 +1143,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.89",
"syn 2.0.90",
]
[[package]]
@ -1219,8 +1219,9 @@ dependencies = [
[[package]]
name = "email-lib"
version = "0.26.0"
source = "git+https://github.com/pimalaya/core#e83f761fcb5fcd73659e933648d40dcca573da44"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8625e58d8fd41ac4f497ead618664d8ab7b86291cffbbf6feb2457c52561e721"
dependencies = [
"async-trait",
"chrono",
@ -1231,12 +1232,12 @@ dependencies = [
"hickory-resolver",
"http-lib",
"imap-client",
"imap-next",
"keyring-lib",
"mail-builder",
"mail-parser",
"mail-send",
"maildirs",
"mime_guess",
"mml-lib",
"notify",
"notmuch",
@ -1255,11 +1256,12 @@ dependencies = [
"shellexpand-utils",
"thiserror 1.0.69",
"tokio",
"tokio-rustls 0.26.0",
"tokio-rustls 0.26.1",
"tracing",
"tree_magic_mini",
"urlencoding",
"utf7-imap",
"uuid 1.11.0",
]
[[package]]
@ -1269,7 +1271,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f24a09fd651027f8764f8a12c12358715cb9bab622ab3125ede3dd6ae047c95"
dependencies = [
"quote",
"syn 2.0.89",
"syn 2.0.90",
]
[[package]]
@ -1302,7 +1304,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.89",
"syn 2.0.90",
]
[[package]]
@ -1323,7 +1325,7 @@ checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.89",
"syn 2.0.90",
]
[[package]]
@ -1355,9 +1357,9 @@ dependencies = [
[[package]]
name = "event-listener-strategy"
version = "0.5.2"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1"
checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2"
dependencies = [
"event-listener",
"pin-project-lite",
@ -1375,9 +1377,9 @@ dependencies = [
[[package]]
name = "fastrand"
version = "2.2.0"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "ff"
@ -1538,7 +1540,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.89",
"syn 2.0.90",
]
[[package]]
@ -1716,12 +1718,6 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]]
name = "hermit-abi"
version = "0.4.0"
@ -1797,6 +1793,7 @@ dependencies = [
"himalaya",
"mml-lib",
"once_cell",
"open",
"pimalaya-tui",
"secret-lib",
"serde",
@ -1806,7 +1803,7 @@ dependencies = [
"toml",
"tracing",
"url",
"uuid",
"uuid 0.8.2",
]
[[package]]
@ -1840,9 +1837,9 @@ dependencies = [
[[package]]
name = "http"
version = "1.1.0"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258"
checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea"
dependencies = [
"bytes",
"fnv",
@ -1852,7 +1849,8 @@ dependencies = [
[[package]]
name = "http-lib"
version = "0.1.0"
source = "git+https://github.com/pimalaya/core#e83f761fcb5fcd73659e933648d40dcca573da44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "994cbd23c90551cb5821d1c9d9b1e41383f338b31fc122671edc7d1695a61338"
dependencies = [
"thiserror 1.0.69",
"tokio",
@ -2004,7 +2002,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.89",
"syn 2.0.90",
]
[[package]]
@ -2055,17 +2053,16 @@ dependencies = [
[[package]]
name = "imap-client"
version = "0.1.5"
source = "git+https://github.com/pimalaya/imap-client#bca4048458585a4c3ed8e0fc71b82f8835ab8286"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "072d1848cdf0d9b2e1632cea3d22a0f935a610e7a9ea14e7c8b37e85fa131495"
dependencies = [
"futures-util",
"imap-next",
"rustls-platform-verifier",
"start-tls",
"thiserror 2.0.3",
"rip-starttls",
"rustls-platform-verifier 0.4.0",
"thiserror 2.0.6",
"tokio",
"tokio-rustls 0.26.0",
"tokio-util",
"tokio-rustls 0.26.1",
"tracing",
]
@ -2085,12 +2082,15 @@ dependencies = [
[[package]]
name = "imap-next"
version = "0.3.0"
source = "git+https://github.com/soywod/imap-next?branch=jakoschiko_futures-stream-without-split#5addf10140ce81e906f114ca2fc767b1c69428a4"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56d85520e742d9e8d9edbf9df9e0876f560ed08650db8f9de562bc7cd46b9b43"
dependencies = [
"futures-util",
"bytes",
"imap-codec",
"thiserror 1.0.69",
"thiserror 2.0.6",
"tokio",
"tokio-rustls 0.26.1",
"tracing",
]
@ -2116,9 +2116,9 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
[[package]]
name = "indexmap"
version = "2.6.0"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
dependencies = [
"equivalent",
"hashbrown 0.15.2",
@ -2198,6 +2198,25 @@ version = "2.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708"
[[package]]
name = "is-docker"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3"
dependencies = [
"once_cell",
]
[[package]]
name = "is-wsl"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5"
dependencies = [
"is-docker",
"once_cell",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
@ -2241,10 +2260,11 @@ dependencies = [
[[package]]
name = "js-sys"
version = "0.3.72"
version = "0.3.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9"
checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7"
dependencies = [
"once_cell",
"wasm-bindgen",
]
@ -2277,7 +2297,8 @@ dependencies = [
[[package]]
name = "keyring-lib"
version = "1.0.2"
source = "git+https://github.com/pimalaya/core#e83f761fcb5fcd73659e933648d40dcca573da44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56921558c465f33d51c6047b86b76764cd2a86b69b99653b43ba1f9a32965218"
dependencies = [
"keyring",
"once_cell",
@ -2318,9 +2339,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.167"
version = "0.2.168"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc"
checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d"
[[package]]
name = "libdbus-sys"
@ -2466,7 +2487,7 @@ dependencies = [
"rustls-pki-types",
"smtp-proto",
"tokio",
"tokio-rustls 0.26.0",
"tokio-rustls 0.26.1",
"webpki-roots",
]
@ -2536,6 +2557,22 @@ dependencies = [
"autocfg",
]
[[package]]
name = "mime"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "mime_guess"
version = "2.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
dependencies = [
"mime",
"unicase",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
@ -2574,11 +2611,10 @@ dependencies = [
[[package]]
name = "mio"
version = "1.0.2"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec"
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
dependencies = [
"hermit-abi 0.3.9",
"libc",
"wasi",
"windows-sys 0.52.0",
@ -2586,8 +2622,9 @@ dependencies = [
[[package]]
name = "mml-lib"
version = "1.1.0"
source = "git+https://github.com/pimalaya/core#e83f761fcb5fcd73659e933648d40dcca573da44"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39e7cb0490a34b47734055fd39a6df4349fcaf9ee425208e48172e443936c590"
dependencies = [
"async-recursion",
"chumsky",
@ -2739,7 +2776,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.89",
"syn 2.0.90",
]
[[package]]
@ -2785,8 +2822,9 @@ dependencies = [
[[package]]
name = "oauth-lib"
version = "1.0.0"
source = "git+https://github.com/pimalaya/core#e83f761fcb5fcd73659e933648d40dcca573da44"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d4a367f5fbc5e9d1971e3dae045c7d6c8b1f9984691d2c1624882b40057f92a"
dependencies = [
"http-lib",
"oauth2",
@ -2829,6 +2867,17 @@ version = "1.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]]
name = "open"
version = "5.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ecd52f0b8d15c40ce4820aa251ed5de032e5d91fab27f7db2f40d42a8bdf69c"
dependencies = [
"is-wsl",
"libc",
"pathdiff",
]
[[package]]
name = "openssl-probe"
version = "0.1.5"
@ -2979,6 +3028,12 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pathdiff"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
[[package]]
name = "pem-rfc7468"
version = "0.7.0"
@ -3059,7 +3114,8 @@ dependencies = [
[[package]]
name = "pgp-lib"
version = "1.0.0"
source = "git+https://github.com/pimalaya/core#e83f761fcb5fcd73659e933648d40dcca573da44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b1a64cb843daaa31d7109d77871ba6fcaa25be5b488c97eeb28fc378e5fed7"
dependencies = [
"async-recursion",
"futures",
@ -3076,8 +3132,9 @@ dependencies = [
[[package]]
name = "pimalaya-tui"
version = "0.1.0"
source = "git+https://github.com/pimalaya/tui#7acc0db96b4a131a679928cc069b8e89dbc5dc43"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "921add1c8f4ca3ad7f5ff3ea6f839ae08466ad39090627914db73ab8473bc4c7"
dependencies = [
"async-trait",
"clap",
@ -3092,6 +3149,7 @@ dependencies = [
"md5",
"mml-lib",
"oauth-lib",
"once_cell",
"petgraph",
"process-lib",
"secret-lib",
@ -3100,7 +3158,7 @@ dependencies = [
"serde_json",
"shellexpand-utils",
"sled",
"thiserror 2.0.3",
"thiserror 2.0.6",
"tokio",
"toml",
"toml_edit",
@ -3167,7 +3225,7 @@ checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f"
dependencies = [
"cfg-if",
"concurrent-queue",
"hermit-abi 0.4.0",
"hermit-abi",
"pin-project-lite",
"rustix",
"tracing",
@ -3237,7 +3295,8 @@ dependencies = [
[[package]]
name = "process-lib"
version = "1.0.0"
source = "git+https://github.com/pimalaya/core#e83f761fcb5fcd73659e933648d40dcca573da44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3eb47ed33aeaf6b32cecbbde6f56dde6c8740f2dac4a146179cc82f797918c46"
dependencies = [
"serde",
"thiserror 1.0.69",
@ -3435,6 +3494,16 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "rip-starttls"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edf1f62c7965137bde00221c63e20e511bb4d576e2ddf9bdf57e1d03042ba112"
dependencies = [
"tokio",
"tracing",
]
[[package]]
name = "ripemd"
version = "0.1.3"
@ -3487,15 +3556,15 @@ dependencies = [
[[package]]
name = "rustix"
version = "0.38.41"
version = "0.38.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6"
checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85"
dependencies = [
"bitflags 2.6.0",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@ -3583,6 +3652,27 @@ dependencies = [
"winapi",
]
[[package]]
name = "rustls-platform-verifier"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4c7dc240fec5517e6c4eab3310438636cfe6391dfc345ba013109909a90d136"
dependencies = [
"core-foundation 0.9.4",
"core-foundation-sys",
"jni",
"log",
"once_cell",
"rustls 0.23.19",
"rustls-native-certs",
"rustls-platform-verifier-android",
"rustls-webpki 0.102.8",
"security-framework 2.11.1",
"security-framework-sys",
"webpki-root-certs",
"windows-sys 0.52.0",
]
[[package]]
name = "rustls-platform-verifier-android"
version = "0.1.1"
@ -3673,7 +3763,8 @@ dependencies = [
[[package]]
name = "secret-lib"
version = "1.0.0"
source = "git+https://github.com/pimalaya/core#e83f761fcb5fcd73659e933648d40dcca573da44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba34b385def61154faed219ac86f80ad8c72877a278208bac2ecdd85d68f962f"
dependencies = [
"keyring-lib",
"process-lib",
@ -3782,7 +3873,7 @@ checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.89",
"syn 2.0.90",
]
[[package]]
@ -3815,7 +3906,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.89",
"syn 2.0.90",
]
[[package]]
@ -4005,15 +4096,6 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "start-tls"
version = "0.1.0"
source = "git+https://github.com/pimalaya/core#e83f761fcb5fcd73659e933648d40dcca573da44"
dependencies = [
"futures-util",
"tracing",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
@ -4054,7 +4136,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.89",
"syn 2.0.90",
]
[[package]]
@ -4076,9 +4158,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.89"
version = "2.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e"
checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
dependencies = [
"proc-macro2",
"quote",
@ -4093,7 +4175,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.89",
"syn 2.0.90",
]
[[package]]
@ -4130,9 +4212,9 @@ dependencies = [
[[package]]
name = "terminal_size"
version = "0.4.0"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef"
checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9"
dependencies = [
"rustix",
"windows-sys 0.59.0",
@ -4149,11 +4231,11 @@ dependencies = [
[[package]]
name = "thiserror"
version = "2.0.3"
version = "2.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa"
checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47"
dependencies = [
"thiserror-impl 2.0.3",
"thiserror-impl 2.0.6",
]
[[package]]
@ -4164,18 +4246,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.89",
"syn 2.0.90",
]
[[package]]
name = "thiserror-impl"
version = "2.0.3"
version = "2.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568"
checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.89",
"syn 2.0.90",
]
[[package]]
@ -4215,14 +4297,14 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.41.1"
version = "1.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33"
checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551"
dependencies = [
"backtrace",
"bytes",
"libc",
"mio 1.0.2",
"mio 1.0.3",
"pin-project-lite",
"signal-hook-registry",
"socket2",
@ -4239,7 +4321,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.89",
"syn 2.0.90",
]
[[package]]
@ -4254,26 +4336,11 @@ dependencies = [
[[package]]
name = "tokio-rustls"
version = "0.26.0"
version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4"
checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37"
dependencies = [
"rustls 0.23.19",
"rustls-pki-types",
"tokio",
]
[[package]]
name = "tokio-util"
version = "0.7.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a"
dependencies = [
"bytes",
"futures-core",
"futures-io",
"futures-sink",
"pin-project-lite",
"tokio",
]
@ -4330,7 +4397,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.89",
"syn 2.0.90",
]
[[package]]
@ -4345,9 +4412,9 @@ dependencies = [
[[package]]
name = "tracing-error"
version = "0.2.0"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e"
checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db"
dependencies = [
"tracing",
"tracing-subscriber",
@ -4366,9 +4433,9 @@ dependencies = [
[[package]]
name = "tracing-subscriber"
version = "0.3.18"
version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
dependencies = [
"matchers",
"nu-ansi-term",
@ -4421,6 +4488,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "unicase"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df"
[[package]]
name = "unicode-bidi"
version = "0.3.17"
@ -4481,7 +4554,7 @@ dependencies = [
"rustls 0.23.19",
"rustls-pemfile 2.2.0",
"rustls-pki-types",
"rustls-platform-verifier",
"rustls-platform-verifier 0.3.4",
"ureq-proto",
"utf-8",
"webpki-roots",
@ -4561,6 +4634,15 @@ dependencies = [
"getrandom",
]
[[package]]
name = "uuid"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a"
dependencies = [
"getrandom",
]
[[package]]
name = "valuable"
version = "0.1.0"
@ -4603,9 +4685,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.95"
version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e"
checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396"
dependencies = [
"cfg-if",
"once_cell",
@ -4614,24 +4696,23 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.95"
version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358"
checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.89",
"syn 2.0.90",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.95"
version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56"
checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@ -4639,22 +4720,31 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.95"
version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.89",
"syn 2.0.90",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.95"
version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6"
[[package]]
name = "webpki-root-certs"
version = "0.26.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cd5da49bdf1f30054cfe0b8ce2958b8fbeb67c4d82c8967a598af481bef255c"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "webpki-roots"
@ -4933,9 +5023,9 @@ dependencies = [
[[package]]
name = "xml-rs"
version = "0.8.23"
version = "0.8.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af310deaae937e48a26602b730250b4949e125f468f11e6990be3e5304ddd96f"
checksum = "ea8b391c9a790b496184c29f7f93b9ed5b16abb306c05415b68bcc16e4d06432"
[[package]]
name = "yansi"
@ -4963,7 +5053,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.89",
"syn 2.0.90",
"synstructure",
]
@ -5021,7 +5111,7 @@ dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn 2.0.89",
"syn 2.0.90",
"zvariant_utils",
]
@ -5054,7 +5144,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.89",
"syn 2.0.90",
]
[[package]]
@ -5074,7 +5164,7 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.89",
"syn 2.0.90",
"synstructure",
]
@ -5095,7 +5185,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.89",
"syn 2.0.90",
]
[[package]]
@ -5117,7 +5207,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.89",
"syn 2.0.90",
]
[[package]]
@ -5142,7 +5232,7 @@ dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn 2.0.89",
"syn 2.0.90",
"zvariant_utils",
]
@ -5154,5 +5244,5 @@ checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.89",
"syn 2.0.90",
]

View file

@ -30,7 +30,7 @@ pgp-gpg = ["email-lib/pgp-gpg", "mml-lib/pgp-gpg", "pimalaya-tui/pgp-gpg"]
pgp-native = ["email-lib/pgp-native", "mml-lib/pgp-native", "pimalaya-tui/pgp-native"]
[build-dependencies]
pimalaya-tui = { version = "=0.1", default-features = false, features = ["build-envs"] }
pimalaya-tui = { version = "0.2", default-features = false, features = ["build-envs"] }
[dev-dependencies]
himalaya = { path = ".", features = ["notmuch", "keyring", "oauth2", "pgp-gpg", "pgp-native"] }
@ -41,10 +41,11 @@ clap = { version = "4.4", features = ["derive", "env", "wrap_help"] }
clap_complete = "4.4"
clap_mangen = "0.2"
color-eyre = "0.6"
email-lib = { version = "=0.26", default-features = false, features = ["tokio-rustls", "derive", "thread"] }
email-lib = { version = "0.26", default-features = false, features = ["tokio-rustls", "derive", "thread"] }
mml-lib = { version = "1", default-features = false, features = ["compiler", "interpreter", "derive"] }
once_cell = "1.16"
pimalaya-tui = { version = "=0.1", default-features = false, features = ["email", "path", "cli", "himalaya", "tracing", "sled"] }
open = "5.3"
pimalaya-tui = { version = "0.2", default-features = false, features = ["rustls", "email", "path", "cli", "himalaya", "tracing", "sled"] }
secret-lib = { version = "1", default-features = false, features = ["tokio", "rustls", "command", "derive"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
@ -54,12 +55,3 @@ toml = "0.8"
tracing = "0.1"
url = "2.2"
uuid = { version = "0.8", features = ["v4"] }
[patch.crates-io]
email-lib = { git = "https://github.com/pimalaya/core" }
keyring-lib = { git = "https://github.com/pimalaya/core" }
mml-lib = { git = "https://github.com/pimalaya/core" }
oauth-lib = { git = "https://github.com/pimalaya/core" }
pimalaya-tui = { git = "https://github.com/pimalaya/tui" }
process-lib = { git = "https://github.com/pimalaya/core" }
secret-lib = { git = "https://github.com/pimalaya/core" }

View file

@ -17,49 +17,31 @@ $ himalaya envelope list --account posteo --folder Archives.FOSS --page 2
## Features
- Multi-accounting
- Interactive configuration via **wizard** (requires `wizard` feature)
- Mailbox, envelope, message and flag management
- Multi-accounting configuration:
- interactive via **wizard** (requires `wizard` feature)
- manual via **TOML**-based configuration file (see [`./config.sample.toml`](./config.sample.toml))
- Message composition based on `$EDITOR`
- **IMAP** backend (requires `imap` feature)
- **Maildir** backend (requires `maildir` feature)
- **Notmuch** backend (requires `notmuch` feature)
- **SMTP** backend (requires `smtp` feature)
- **Sendmail** backend (requires `sendmail` feature)
- Global system **keyring** for managing secrets (requires `keyring` feature)
- **OAuth 2.0** authorization (requires `oauth2` feature)
- Global system **keyring** for secret management (requires `keyring` feature)
- **OAuth 2.0** authorization flow (requires `oauth2` feature)
- **JSON** output via `--output json`
- **PGP** encryption:
- via shell commands (requires `pgp-commands` feature)
- via [GPG](https://www.gnupg.org/) bindings (requires `pgp-gpg` feature)
- via native implementation (requires `pgp-native` feature)
*Himalaya CLI is written in [Rust](https://www.rust-lang.org/), and relies on [cargo features](https://doc.rust-lang.org/cargo/reference/features.html) to enable or disable functionalities. Default features can be found in the `features` section of the [`Cargo.toml`](https://github.com/pimalaya/himalaya/blob/master/Cargo.toml#L18).*
*Himalaya CLI is written in [Rust](https://www.rust-lang.org/), and relies on [cargo features](https://doc.rust-lang.org/cargo/reference/features.html) to enable or disable functionalities. Default features can be found in the `features` section of the [`Cargo.toml`](./Cargo.toml#L18), or on [docs.rs](https://docs.rs/crate/himalaya/latest/features).*
## Installation
*The `v1.0.0` is currently being tested on the `master` branch, and is the prefered version to use. Previous versions (including GitHub beta releases and repositories published versions) are not recommended.*
### Pre-built binary
Himalaya CLI `v1.0.0` can be installed with a pre-built binary. Find the latest [`pre-releases`](https://github.com/pimalaya/himalaya/actions/workflows/pre-releases.yml) GitHub workflow and look for the *Artifacts* section. You should find a pre-built binary matching your OS.
### Cargo (git)
Himalaya CLI `v1.0.0` can also be installed with [cargo](https://doc.rust-lang.org/cargo/):
```bash
$ cargo install --frozen --force --git https://github.com/pimalaya/himalaya.git
```
### Other outdated methods
These installation methods should not be used until the `v1.0.0` is finally released, as they are all (temporarily) outdated:
<details>
<summary>Pre-built binary</summary>
Himalaya CLI can be installed with a prebuilt binary:
Himalaya CLI can be installed with the installer:
```bash
# As root:
@ -71,7 +53,9 @@ These installation methods should not be used until the `v1.0.0` is finally rele
These commands install the latest binary from the GitHub [releases](https://github.com/pimalaya/himalaya/releases) section.
*Binaries are built with [default](https://github.com/pimalaya/himalaya/blob/master/Cargo.toml#L18) cargo features. If you want to enable or disable a feature, please use another installation method.*
If you want a more up-to-date version than the latest release, check out the [`releases`](https://github.com/pimalaya/himalaya/actions/workflows/releases.yml) GitHub workflow and look for the *Artifacts* section. You should find a pre-built binary matching your OS. These pre-built binaries are built from the `master` branch.
*Such binaries are built with the default cargo features. If you want to enable or disable a feature, please use another installation method.*
</details>
<details>
@ -89,7 +73,7 @@ These installation methods should not be used until the `v1.0.0` is finally rele
You can also use the git repository for a more up-to-date (but less stable) version:
```bash
$ cargo install --git https://github.com/pimalaya/himalaya.git himalaya
$ cargo install --frozen --force --git https://github.com/pimalaya/himalaya.git
```
</details>
@ -215,6 +199,8 @@ These installation methods should not be used until the `v1.0.0` is finally rele
Just run `himalaya`, the wizard will help you to configure your default account.
Accounts can be (re)configured via the wizard using the command `himalaya account configure <name>`.
You can also manually edit your own configuration, from scratch:
- Copy the content of the documented [`./config.sample.toml`](./config.sample.toml)
@ -237,7 +223,7 @@ You can also manually edit your own configuration, from scratch:
backend.type = "imap"
backend.host = "127.0.0.1"
backend.port = 1143
backend.encryption = false
backend.encryption.type = "none"
backend.login = "example@proton.me"
backend.auth.type = "password"
backend.auth.raw = "*****"
@ -245,7 +231,7 @@ You can also manually edit your own configuration, from scratch:
message.send.backend.type = "smtp"
message.send.backend.host = "127.0.0.1"
message.send.backend.port = 1025
message.send.backend.encryption = false
message.send.backend.encryption.type = "none"
message.send.backend.login = "example@proton.me"
message.send.backend.auth.type = "password"
message.send.backend.auth.raw = "*****"
@ -387,7 +373,7 @@ You can also manually edit your own configuration, from scratch:
message.send.backend.type = "smtp"
message.send.backend.host = "smtp-mail.outlook.com"
message.send.backend.port = 587
message.send.backend.encryption = "start-tls"
message.send.backend.encryption.type = "start-tls"
message.send.backend.login = "example@outlook.com"
message.send.backend.auth.type = "password"
message.send.backend.auth.raw = "*****"
@ -474,7 +460,7 @@ You can also manually edit your own configuration, from scratch:
message.send.backend.type = "smtp"
message.send.backend.host = "smtp.mail.me.com"
message.send.backend.port = 587
message.send.backend.encryption = "start-tls"
message.send.backend.encryption.type = "start-tls"
message.send.backend.login = "johnappleseed@icloud.com"
message.send.backend.auth.type = "password"
message.send.backend.auth.raw = "*****"

View file

@ -1,13 +0,0 @@
[Unit]
Description=Email client Himalaya CLI envelopes watcher service
After=network.target
[Service]
Type=exec
ExecStart=%install_dir%/himalaya envelopes watch --account %i
ExecSearchPath=/bin
Restart=always
RestartSec=10
[Install]
WantedBy=default.target

View file

@ -1,5 +1,5 @@
################################################################################
#### Global configuration ######################################################
###[ Global configuration ]#####################################################
################################################################################
# Default display name for all accounts. It is used to build the full
@ -11,11 +11,11 @@ display-name = "Example"
# bottom of all messages. It can be a path or a string. Supports TOML
# multilines.
#
# signature = "/path/to/signature/file"
# signature = """
# Thanks you,
# Regards
# """
#signature = "/path/to/signature/file"
#signature = """
# Thanks you,
# Regards
#"""
signature = "Regards,\n"
# Default signature delimiter for all accounts. It delimits the end of
@ -27,10 +27,10 @@ signature-delim = "-- \n"
# for downloading attachments. Defaults to the system temporary
# directory.
#
downloads-dir = "~/downloads"
downloads-dir = "~/Downloads"
# Customizes the charset used to build the table. Defaults to markdown
# table style.
# Customizes the charset used to build the accounts listing
# table. Defaults to markdown table style.
#
# See <https://docs.rs/comfy-table/latest/comfy_table/presets/index.html>.
#
@ -52,9 +52,11 @@ account.list.table.backends-color = "blue"
account.list.table.default-color = "black"
################################################################################
#### Account configuration #####################################################
###[ Account configuration ]####################################################
################################################################################
# The account name should be unique.
#
[accounts.example]
# Defaultness of the account. The current account will be used by
@ -88,11 +90,10 @@ signature-delim = "-- \n"
# Downloads directory path. It is mostly used for downloading
# attachments. Defaults to the system temporary directory.
#
downloads-dir = "~/downloads"
########################################
#### Folder configuration ##############
########################################
# Defines aliases for your mailboxes. There are 4 special aliases used
# by the tool: inbox, sent, drafts and trash. Other aliases can be
@ -123,9 +124,7 @@ folder.list.table.name-color = "blue"
#
folder.list.table.desc-color = "green"
########################################
#### Envelope configuration ############
########################################
# Customizes the number of envelopes to show by page.
#
@ -194,9 +193,7 @@ envelope.list.table.sender-color = "blue"
#
envelope.list.table.date-color = "yellow"
########################################
#### Message configuration #############
########################################
# Defines headers to show at the top of messages when reading them.
#
@ -233,9 +230,7 @@ message.send.pre-hook = "process-markdown.sh"
#message.delete.style = "flag"
message.delete.style = "folder"
########################################
#### Template configuration ############
########################################
# Defines how and where the signature should be displayed when writing
# a new message.
@ -287,18 +282,14 @@ template.forward.signature-style = "inlined"
#
template.forward.quote-headline = "-------- Forwarded Message --------\n"
########################################
#### GPG-based PGP configuration #######
########################################
# Enables PGP using GPG bindings. It requires the GPG lib to be
# installed on the system, and the `pgp-gpg` cargo feature on.
#
#pgp.type = "gpg"
########################################
#### Command-based PGP configuration ###
########################################
# Enables PGP using shell commands. A PGP client needs to be installed
# on the system, like gpg. It also requires the `pgp-commands` cargo
@ -334,9 +325,7 @@ template.forward.quote-headline = "-------- Forwarded Message --------\n"
#
#pgp.verify-cmd = "gpg --verify --quiet"
########################################
#### Native PGP configuration ##########
########################################
# Enables the native Rust implementation of PGP. It requires the
# `pgp-native` cargo feature.
@ -363,9 +352,7 @@ template.forward.quote-headline = "-------- Forwarded Message --------\n"
#
#pgp.key-servers = ["hkps://keys.openpgp.org", "hkps://keys.mailvelope.com"]
########################################
#### IMAP configuration ################
########################################
# Defines the IMAP backend as the default one for all features.
#
@ -382,9 +369,9 @@ backend.port = 993
# IMAP server encryption.
#
#backend.encryption = "none" # or false
#backend.encryption = "start-tls"
backend.encryption = "tls" # or true
#backend.encryption.type = "none"
#backend.encryption.type = "start-tls"
backend.encryption.type = "tls"
# IMAP server login.
#
@ -488,9 +475,7 @@ backend.auth.cmd = "pass show example-imap"
#
#backend.auth.redirect-port = 9999
########################################
#### Maildir configuration #############
########################################
# Defines the Maildir backend as the default one for all features.
#
@ -507,9 +492,7 @@ backend.auth.cmd = "pass show example-imap"
#
#backend.maildirpp = false
########################################
#### Notmuch configuration #############
########################################
# Defines the Notmuch backend as the default one for all features.
#
@ -533,9 +516,7 @@ backend.auth.cmd = "pass show example-imap"
#
#backend.profile = "example"
########################################
#### SMTP configuration ################
########################################
# Defines the SMTP backend for the message sending feature.
#
@ -553,9 +534,9 @@ message.send.backend.port = 587
# SMTP server encryption.
#
#message.send.backend.encryption = "none" # or false
#message.send.backend.encryption = "start-tls"
message.send.backend.encryption = "tls" # or true
#message.send.backend.encryption.type = "none"
#message.send.backend.encryption.type = "start-tls"
message.send.backend.encryption.type = "tls"
# SMTP server login.
#
@ -659,9 +640,7 @@ message.send.backend.auth.cmd = "pass show example-smtp"
#
#message.send.backend.auth.redirect-port = 9999
########################################
#### Sendmail configuration ############
########################################
# Defines the Sendmail backend for the message sending feature.
#

View file

@ -9,7 +9,7 @@ die() {
DESTDIR="${DESTDIR:-}"
PREFIX="${PREFIX:-"$DESTDIR/usr/local"}"
RELEASES_URL="https://github.com/soywod/himalaya/releases"
RELEASES_URL="https://github.com/pimalaya/himalaya/releases"
binary=himalaya
system=$(uname -s | tr [:upper:] [:lower:])
@ -23,14 +23,17 @@ case $system in
linux|freebsd)
case $machine in
x86_64) target=x86_64-linux;;
x86|i386|i686) target=i686-linux;;
arm64|aarch64) target=aarch64-linux;;
armv6l) target=armv6l-linux;;
armv7l) target=armv7l-linux;;
*) die "Unsupported machine $machine for system $system";;
esac;;
darwin)
case $machine in
x86_64) target=x86_64-macos;;
arm64|aarch64) target=aarch64-macos;;
x86_64) target=x86_64-darwin;;
arm64|aarch64) target=aarch64-darwin;;
*) die "Unsupported machine $machine for system $system";;
esac;;

View file

@ -1,111 +0,0 @@
use std::sync::Arc;
use clap::Parser;
use color_eyre::Result;
#[cfg(feature = "imap")]
use email::imap::ImapContextBuilder;
#[cfg(feature = "maildir")]
use email::maildir::MaildirContextBuilder;
#[cfg(feature = "notmuch")]
use email::notmuch::NotmuchContextBuilder;
#[cfg(feature = "sendmail")]
use email::sendmail::SendmailContextBuilder;
#[cfg(feature = "smtp")]
use email::smtp::SmtpContextBuilder;
use email::{backend::BackendBuilder, config::Config};
use pimalaya_tui::{
himalaya::config::{Backend, SendingBackend},
terminal::{cli::printer::Printer, config::TomlConfig as _},
};
use tracing::info;
use crate::{account::arg::name::OptionalAccountNameArg, config::TomlConfig};
/// Check up the given account.
///
/// This command performs a checkup of the given account. It checks if
/// the configuration is valid, if backend can be created and if
/// sessions work as expected.
#[derive(Debug, Parser)]
pub struct AccountCheckUpCommand {
#[command(flatten)]
pub account: OptionalAccountNameArg,
}
impl AccountCheckUpCommand {
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
info!("executing check up account command");
printer.log("Checking configuration integrity…\n")?;
let (toml_account_config, account_config) = config
.clone()
.into_account_configs(self.account.name.as_deref(), |c: &Config, name| {
c.account(name).ok()
})?;
let account_config = Arc::new(account_config);
match toml_account_config.backend {
#[cfg(feature = "maildir")]
Some(Backend::Maildir(mdir_config)) => {
printer.log("Checking Maildir integrity…\n")?;
let ctx = MaildirContextBuilder::new(account_config.clone(), Arc::new(mdir_config));
BackendBuilder::new(account_config.clone(), ctx)
.check_up()
.await?;
}
#[cfg(feature = "imap")]
Some(Backend::Imap(imap_config)) => {
printer.log("Checking IMAP integrity…\n")?;
let ctx = ImapContextBuilder::new(account_config.clone(), Arc::new(imap_config))
.with_pool_size(1);
BackendBuilder::new(account_config.clone(), ctx)
.check_up()
.await?;
}
#[cfg(feature = "notmuch")]
Some(Backend::Notmuch(notmuch_config)) => {
printer.log("Checking Notmuch integrity…\n")?;
let ctx =
NotmuchContextBuilder::new(account_config.clone(), Arc::new(notmuch_config));
BackendBuilder::new(account_config.clone(), ctx)
.check_up()
.await?;
}
_ => (),
}
let sending_backend = toml_account_config
.message
.and_then(|msg| msg.send)
.and_then(|send| send.backend);
match sending_backend {
#[cfg(feature = "smtp")]
Some(SendingBackend::Smtp(smtp_config)) => {
printer.log("Checking SMTP integrity…\n")?;
let ctx = SmtpContextBuilder::new(account_config.clone(), Arc::new(smtp_config));
BackendBuilder::new(account_config.clone(), ctx)
.check_up()
.await?;
}
#[cfg(feature = "sendmail")]
Some(SendingBackend::Sendmail(sendmail_config)) => {
printer.log("Checking Sendmail integrity…\n")?;
let ctx =
SendmailContextBuilder::new(account_config.clone(), Arc::new(sendmail_config));
BackendBuilder::new(account_config.clone(), ctx)
.check_up()
.await?;
}
_ => (),
}
printer.out("Checkup successfully completed!\n")
}
}

View file

@ -1,130 +1,52 @@
use std::path::PathBuf;
use clap::Parser;
use color_eyre::Result;
#[cfg(feature = "imap")]
use email::imap::config::ImapAuthConfig;
#[cfg(feature = "smtp")]
use email::smtp::config::SmtpAuthConfig;
#[cfg(any(
feature = "imap",
feature = "smtp",
feature = "pgp-gpg",
feature = "pgp-commands",
feature = "pgp-native",
))]
use pimalaya_tui::terminal::prompt;
use pimalaya_tui::terminal::{cli::printer::Printer, config::TomlConfig as _};
use tracing::info;
#[cfg(any(feature = "imap", feature = "smtp"))]
use tracing::{debug, warn};
use crate::{account::arg::name::AccountNameArg, config::TomlConfig};
/// Configure an account.
/// Configure the given account.
///
/// This command is mostly used to define or reset passwords managed
/// by your global keyring. If you do not use the keyring system, you
/// can skip this command.
/// This command allows you to configure an existing account or to
/// create a new one, using the wizard. The `wizard` cargo feature is
/// required.
#[derive(Debug, Parser)]
pub struct AccountConfigureCommand {
#[command(flatten)]
pub account: AccountNameArg,
/// Reset keyring passwords.
///
/// This argument will force passwords to be prompted again, then
/// saved to your global keyring.
#[arg(long, short)]
pub reset: bool,
}
impl AccountConfigureCommand {
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
info!("executing configure account command");
#[cfg(feature = "wizard")]
pub async fn execute(
self,
mut config: TomlConfig,
config_path: Option<&PathBuf>,
) -> Result<()> {
use pimalaya_tui::{himalaya::wizard, terminal::config::TomlConfig as _};
use tracing::info;
let account = &self.account.name;
let (_, toml_account_config) = config.to_toml_account_config(Some(account))?;
info!("executing account configure command");
if self.reset {
#[cfg(feature = "imap")]
{
let reset = match toml_account_config.imap_auth_config() {
Some(ImapAuthConfig::Password(config)) => config.reset().await,
#[cfg(feature = "oauth2")]
Some(ImapAuthConfig::OAuth2(config)) => config.reset().await,
_ => Ok(()),
};
let path = match config_path {
Some(path) => path.clone(),
None => TomlConfig::default_path()?,
};
if let Err(err) = reset {
warn!("error while resetting imap secrets: {err}");
debug!("error while resetting imap secrets: {err:?}");
}
}
let account_name = Some(self.account.name.as_str());
#[cfg(feature = "smtp")]
{
let reset = match toml_account_config.smtp_auth_config() {
Some(SmtpAuthConfig::Password(config)) => config.reset().await,
#[cfg(feature = "oauth2")]
Some(SmtpAuthConfig::OAuth2(config)) => config.reset().await,
_ => Ok(()),
};
let account_config = config
.accounts
.remove(&self.account.name)
.unwrap_or_default();
if let Err(err) = reset {
warn!("error while resetting smtp secrets: {err}");
debug!("error while resetting smtp secrets: {err:?}");
}
}
wizard::edit(path, config, account_name, account_config).await?;
#[cfg(any(feature = "pgp-gpg", feature = "pgp-commands", feature = "pgp-native"))]
if let Some(config) = &toml_account_config.pgp {
config.reset().await?;
}
}
Ok(())
}
#[cfg(feature = "imap")]
match toml_account_config.imap_auth_config() {
Some(ImapAuthConfig::Password(config)) => {
config
.configure(|| Ok(prompt::password("IMAP password")?))
.await
}
#[cfg(feature = "oauth2")]
Some(ImapAuthConfig::OAuth2(config)) => {
config
.configure(|| Ok(prompt::secret("IMAP OAuth 2.0 client secret")?))
.await
}
_ => Ok(()),
}?;
#[cfg(feature = "smtp")]
match toml_account_config.smtp_auth_config() {
Some(SmtpAuthConfig::Password(config)) => {
config
.configure(|| Ok(prompt::password("SMTP password")?))
.await
}
#[cfg(feature = "oauth2")]
Some(SmtpAuthConfig::OAuth2(config)) => {
config
.configure(|| Ok(prompt::secret("SMTP OAuth 2.0 client secret")?))
.await
}
_ => Ok(()),
}?;
#[cfg(any(feature = "pgp-gpg", feature = "pgp-commands", feature = "pgp-native"))]
if let Some(config) = &toml_account_config.pgp {
config
.configure(&toml_account_config.email, || {
Ok(prompt::password("PGP secret key password")?)
})
.await?;
}
printer.out(format!(
"Account {account} successfully {}configured!\n",
if self.reset { "re" } else { "" }
))
#[cfg(not(feature = "wizard"))]
pub async fn execute(self, _: TomlConfig, _: Option<&PathBuf>) -> Result<()> {
color_eyre::eyre::bail!("This command requires the `wizard` cargo feature to work");
}
}

View file

@ -0,0 +1,233 @@
use std::{
io::{stdout, Write},
sync::Arc,
};
use clap::Parser;
use color_eyre::{Result, Section};
#[cfg(all(feature = "keyring", feature = "imap"))]
use email::imap::config::ImapAuthConfig;
#[cfg(feature = "imap")]
use email::imap::ImapContextBuilder;
#[cfg(feature = "maildir")]
use email::maildir::MaildirContextBuilder;
#[cfg(feature = "notmuch")]
use email::notmuch::NotmuchContextBuilder;
#[cfg(feature = "sendmail")]
use email::sendmail::SendmailContextBuilder;
#[cfg(all(feature = "keyring", feature = "smtp"))]
use email::smtp::config::SmtpAuthConfig;
#[cfg(feature = "smtp")]
use email::smtp::SmtpContextBuilder;
use email::{backend::BackendBuilder, config::Config};
#[cfg(feature = "keyring")]
use pimalaya_tui::terminal::prompt;
use pimalaya_tui::{
himalaya::config::{Backend, SendingBackend},
terminal::config::TomlConfig as _,
};
use crate::{account::arg::name::OptionalAccountNameArg, config::TomlConfig};
/// Diagnose and fix the given account.
///
/// This command diagnoses the given account and can even try to fix
/// it. It mostly checks if the configuration is valid, if backends
/// can be instanciated and if sessions work as expected.
#[derive(Debug, Parser)]
pub struct AccountDoctorCommand {
#[command(flatten)]
pub account: OptionalAccountNameArg,
/// Try to fix the given account.
///
/// This argument can be used to (re)configure keyring entries for
/// example.
#[arg(long, short)]
pub fix: bool,
}
impl AccountDoctorCommand {
pub async fn execute(self, config: &TomlConfig) -> Result<()> {
let mut stdout = stdout();
if let Some(name) = self.account.name.as_ref() {
print!("Checking TOML configuration integrity for account {name}");
} else {
print!("Checking TOML configuration integrity for default account… ");
}
stdout.flush()?;
let (toml_account_config, account_config) = config
.clone()
.into_account_configs(self.account.name.as_deref(), |c: &Config, name| {
c.account(name).ok()
})?;
let account_config = Arc::new(account_config);
println!("OK");
#[cfg(feature = "keyring")]
if self.fix {
if prompt::bool("Would you like to reset existing keyring entries?", false)? {
print!("Resetting keyring entries… ");
stdout.flush()?;
#[cfg(feature = "imap")]
match toml_account_config.imap_auth_config() {
Some(ImapAuthConfig::Password(config)) => config.reset().await?,
#[cfg(feature = "oauth2")]
Some(ImapAuthConfig::OAuth2(config)) => config.reset().await?,
_ => (),
}
#[cfg(feature = "smtp")]
match toml_account_config.smtp_auth_config() {
Some(SmtpAuthConfig::Password(config)) => config.reset().await?,
#[cfg(feature = "oauth2")]
Some(SmtpAuthConfig::OAuth2(config)) => config.reset().await?,
_ => (),
}
#[cfg(any(feature = "pgp-gpg", feature = "pgp-commands", feature = "pgp-native"))]
if let Some(config) = &toml_account_config.pgp {
config.reset().await?;
}
println!("OK");
}
#[cfg(feature = "imap")]
match toml_account_config.imap_auth_config() {
Some(ImapAuthConfig::Password(config)) => {
config
.configure(|| Ok(prompt::password("IMAP password")?))
.await?;
}
#[cfg(feature = "oauth2")]
Some(ImapAuthConfig::OAuth2(config)) => {
config
.configure(|| Ok(prompt::secret("IMAP OAuth 2.0 client secret")?))
.await?;
}
_ => (),
};
#[cfg(feature = "smtp")]
match toml_account_config.smtp_auth_config() {
Some(SmtpAuthConfig::Password(config)) => {
config
.configure(|| Ok(prompt::password("SMTP password")?))
.await?;
}
#[cfg(feature = "oauth2")]
Some(SmtpAuthConfig::OAuth2(config)) => {
config
.configure(|| Ok(prompt::secret("SMTP OAuth 2.0 client secret")?))
.await?;
}
_ => (),
};
#[cfg(any(feature = "pgp-gpg", feature = "pgp-commands", feature = "pgp-native"))]
if let Some(config) = &toml_account_config.pgp {
config
.configure(&toml_account_config.email, || {
Ok(prompt::password("PGP secret key password")?)
})
.await?;
}
}
match toml_account_config.backend {
#[cfg(feature = "maildir")]
Some(Backend::Maildir(mdir_config)) => {
print!("Checking Maildir integrity… ");
stdout.flush()?;
let ctx = MaildirContextBuilder::new(account_config.clone(), Arc::new(mdir_config));
BackendBuilder::new(account_config.clone(), ctx)
.check_up()
.await?;
println!("OK");
}
#[cfg(feature = "imap")]
Some(Backend::Imap(imap_config)) => {
print!("Checking IMAP integrity… ");
stdout.flush()?;
let ctx = ImapContextBuilder::new(account_config.clone(), Arc::new(imap_config))
.with_pool_size(1);
let res = BackendBuilder::new(account_config.clone(), ctx)
.check_up()
.await;
if self.fix {
res?;
} else {
res.note("Run with --fix to (re)configure your account.")?;
}
println!("OK");
}
#[cfg(feature = "notmuch")]
Some(Backend::Notmuch(notmuch_config)) => {
print!("Checking Notmuch integrity… ");
stdout.flush()?;
let ctx =
NotmuchContextBuilder::new(account_config.clone(), Arc::new(notmuch_config));
BackendBuilder::new(account_config.clone(), ctx)
.check_up()
.await?;
println!("OK");
}
_ => (),
}
let sending_backend = toml_account_config
.message
.and_then(|msg| msg.send)
.and_then(|send| send.backend);
match sending_backend {
#[cfg(feature = "smtp")]
Some(SendingBackend::Smtp(smtp_config)) => {
print!("Checking SMTP integrity… ");
stdout.flush()?;
let ctx = SmtpContextBuilder::new(account_config.clone(), Arc::new(smtp_config));
let res = BackendBuilder::new(account_config.clone(), ctx)
.check_up()
.await;
if self.fix {
res?;
} else {
res.note("Run with --fix to (re)configure your account.")?;
}
println!("OK");
}
#[cfg(feature = "sendmail")]
Some(SendingBackend::Sendmail(sendmail_config)) => {
print!("Checking Sendmail integrity… ");
stdout.flush()?;
let ctx =
SendmailContextBuilder::new(account_config.clone(), Arc::new(sendmail_config));
BackendBuilder::new(account_config.clone(), ctx)
.check_up()
.await?;
println!("OK");
}
_ => (),
}
Ok(())
}
}

View file

@ -8,18 +8,19 @@ use tracing::info;
use crate::config::TomlConfig;
/// List all accounts.
/// List all existing accounts.
///
/// This command lists all accounts defined in your TOML configuration
/// file.
/// This command lists all the accounts defined in your TOML
/// configuration file.
#[derive(Debug, Parser)]
pub struct AccountListCommand {
/// The maximum width the table should not exceed.
///
/// This argument will force the table not to exceed the given
/// width in pixels. Columns may shrink with ellipsis in order to
/// width, in pixels. Columns may shrink with ellipsis in order to
/// fit the width.
#[arg(long, short = 'w', name = "table_max_width", value_name = "PIXELS")]
#[arg(long = "max-width", short = 'w')]
#[arg(name = "table_max_width", value_name = "PIXELS")]
pub table_max_width: Option<u16>,
}
@ -35,7 +36,6 @@ impl AccountListCommand {
.with_some_backends_color(config.account_list_table_backends_color())
.with_some_default_color(config.account_list_table_default_color());
printer.out(table)?;
Ok(())
printer.out(table)
}
}

View file

@ -1,7 +1,9 @@
mod check_up;
mod configure;
mod doctor;
mod list;
use std::path::PathBuf;
use clap::Subcommand;
use color_eyre::Result;
use pimalaya_tui::terminal::cli::printer::Printer;
@ -9,33 +11,31 @@ use pimalaya_tui::terminal::cli::printer::Printer;
use crate::config::TomlConfig;
use self::{
check_up::AccountCheckUpCommand, configure::AccountConfigureCommand, list::AccountListCommand,
configure::AccountConfigureCommand, doctor::AccountDoctorCommand, list::AccountListCommand,
};
/// Manage accounts.
/// Configure, list and diagnose your accounts.
///
/// An account is a set of settings, identified by an account
/// name. Settings are directly taken from your TOML configuration
/// file. This subcommand allows you to manage them.
/// An account is a group of settings, identified by a unique
/// name. This subcommand allows you to manage your accounts.
#[derive(Debug, Subcommand)]
pub enum AccountSubcommand {
#[command(alias = "checkup")]
CheckUp(AccountCheckUpCommand),
#[command(alias = "cfg")]
Configure(AccountConfigureCommand),
#[command(alias = "lst")]
Doctor(AccountDoctorCommand),
List(AccountListCommand),
}
impl AccountSubcommand {
#[allow(unused)]
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
pub async fn execute(
self,
printer: &mut impl Printer,
config: TomlConfig,
config_path: Option<&PathBuf>,
) -> Result<()> {
match self {
Self::CheckUp(cmd) => cmd.execute(printer, config).await,
Self::Configure(cmd) => cmd.execute(printer, config).await,
Self::List(cmd) => cmd.execute(printer, config).await,
Self::Configure(cmd) => cmd.execute(config, config_path).await,
Self::Doctor(cmd) => cmd.execute(&config).await,
Self::List(cmd) => cmd.execute(printer, &config).await,
}
}
}

View file

@ -123,7 +123,7 @@ impl HimalayaCommand {
match self {
Self::Account(cmd) => {
let config = TomlConfig::from_paths_or_default(config_paths).await?;
cmd.execute(printer, &config).await
cmd.execute(printer, config, config_paths.first()).await
}
Self::Folder(cmd) => {
let config = TomlConfig::from_paths_or_default(config_paths).await?;

View file

@ -1,12 +1,13 @@
use std::io;
use clap::{value_parser, CommandFactory, Parser};
use clap_complete::Shell;
use color_eyre::Result;
use std::io;
use tracing::info;
use crate::cli::Cli;
/// Print completion script for a shell to stdout.
/// Print completion script for the given shell to stdout.
///
/// This command allows you to generate completion script for a given
/// shell. The script is printed to the standard output. If you want

View file

@ -18,12 +18,12 @@ use crate::{
folder::arg::name::FolderNameOptionalFlag,
};
/// List all envelopes.
/// Search and sort envelopes as a list.
///
/// This command allows you to list all envelopes included in the
/// given folder.
/// This command allows you to list envelopes included in the given
/// folder, matching the given query.
#[derive(Debug, Parser)]
pub struct ListEnvelopesCommand {
pub struct EnvelopeListCommand {
#[command(flatten)]
pub folder: FolderNameOptionalFlag,
@ -123,7 +123,7 @@ pub struct ListEnvelopesCommand {
pub query: Option<Vec<String>>,
}
impl Default for ListEnvelopesCommand {
impl Default for EnvelopeListCommand {
fn default() -> Self {
Self {
folder: Default::default(),
@ -136,7 +136,7 @@ impl Default for ListEnvelopesCommand {
}
}
impl ListEnvelopesCommand {
impl EnvelopeListCommand {
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
info!("executing list envelopes command");
@ -213,7 +213,6 @@ impl ListEnvelopesCommand {
.with_some_sender_color(toml_account_config.envelope_list_table_sender_color())
.with_some_date_color(toml_account_config.envelope_list_table_date_color());
printer.out(table)?;
Ok(())
printer.out(table)
}
}

View file

@ -7,9 +7,9 @@ use pimalaya_tui::terminal::cli::printer::Printer;
use crate::config::TomlConfig;
use self::{list::ListEnvelopesCommand, thread::ThreadEnvelopesCommand};
use self::{list::EnvelopeListCommand, thread::EnvelopeThreadCommand};
/// Manage envelopes.
/// List, search and sort your envelopes.
///
/// An envelope is a small representation of a message. It contains an
/// identifier (given by the backend), some flags as well as few
@ -18,10 +18,10 @@ use self::{list::ListEnvelopesCommand, thread::ThreadEnvelopesCommand};
#[derive(Debug, Subcommand)]
pub enum EnvelopeSubcommand {
#[command(alias = "lst")]
List(ListEnvelopesCommand),
List(EnvelopeListCommand),
#[command()]
Thread(ThreadEnvelopesCommand),
Thread(EnvelopeThreadCommand),
}
impl EnvelopeSubcommand {

View file

@ -17,12 +17,12 @@ use crate::{
folder::arg::name::FolderNameOptionalFlag,
};
/// Thread all envelopes.
/// Search and sort envelopes as a thread.
///
/// This command allows you to thread all envelopes included in the
/// given folder.
/// This command allows you to thread envelopes included in the given
/// folder, matching the given query.
#[derive(Debug, Parser)]
pub struct ThreadEnvelopesCommand {
pub struct EnvelopeThreadCommand {
#[command(flatten)]
pub folder: FolderNameOptionalFlag,
@ -33,11 +33,14 @@ pub struct ThreadEnvelopesCommand {
#[arg(long, short)]
pub id: Option<usize>,
/// The list envelopes filter and sort query.
///
/// See `envelope list --help` for more information.
#[arg(allow_hyphen_values = true, trailing_var_arg = true)]
pub query: Option<Vec<String>>,
}
impl ThreadEnvelopesCommand {
impl EnvelopeThreadCommand {
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
info!("executing thread envelopes command");
@ -102,9 +105,7 @@ impl ThreadEnvelopesCommand {
let tree = EnvelopesTree::new(account_config, envelopes);
printer.out(tree)?;
Ok(())
printer.out(tree)
}
}

View file

@ -16,7 +16,7 @@ use crate::{
folder::arg::name::FolderNameOptionalFlag,
};
/// Add flag(s) to an envelope.
/// Add flag(s) to the given envelope.
///
/// This command allows you to attach the given flag(s) to the given
/// envelope(s).

View file

@ -10,12 +10,11 @@ use crate::config::TomlConfig;
use self::{add::FlagAddCommand, remove::FlagRemoveCommand, set::FlagSetCommand};
/// Manage flags.
/// Add, change and remove your envelopes flags.
///
/// A flag is a tag associated to an envelope. Existing flags are
/// seen, answered, flagged, deleted, draft. Other flags are
/// considered custom, which are not always supported (the
/// synchronization does not take care of them yet).
/// considered custom, which are not always supported.
#[derive(Debug, Subcommand)]
pub enum FlagSubcommand {
#[command(arg_required_else_help = true)]

View file

@ -16,7 +16,7 @@ use crate::{
folder::arg::name::FolderNameOptionalFlag,
};
/// Remove flag(s) from an envelope.
/// Remove flag(s) from a given envelope.
///
/// This command allows you to remove the given flag(s) from the given
/// envelope(s).

View file

@ -16,7 +16,7 @@ use crate::{
folder::arg::name::FolderNameOptionalFlag,
};
/// Replace flag(s) of an envelope.
/// Replace flag(s) of a given envelope.
///
/// This command allows you to replace existing flags of the given
/// envelope(s) with the given flag(s).

View file

@ -14,7 +14,7 @@ use crate::{
folder::arg::name::FolderNameOptionalFlag,
};
/// Download all attachments for the given message.
/// Download all attachments found in the given message.
///
/// This command allows you to download all attachments found for the
/// given message to your downloads directory.
@ -69,14 +69,14 @@ impl AttachmentDownloadCommand {
let attachments = email.attachments()?;
if attachments.is_empty() {
printer.log(format!("No attachment found for message {id}!"))?;
printer.log(format!("No attachment found for message {id}!\n"))?;
continue;
} else {
emails_count += 1;
}
printer.log(format!(
"{} attachment(s) found for message {id}!",
"{} attachment(s) found for message {id}!\n",
attachments.len()
))?;
@ -86,7 +86,7 @@ impl AttachmentDownloadCommand {
.unwrap_or_else(|| Uuid::new_v4().to_string())
.into();
let filepath = account_config.get_download_file_path(&filename)?;
printer.log(format!("Downloading {:?}", filepath))?;
printer.log(format!("Downloading {:?}\n", filepath))?;
fs::write(&filepath, &attachment.body)
.with_context(|| format!("cannot save attachment at {filepath:?}"))?;
attachments_count += 1;
@ -94,10 +94,10 @@ impl AttachmentDownloadCommand {
}
match attachments_count {
0 => printer.out("No attachment found!"),
1 => printer.out("Downloaded 1 attachment!"),
0 => printer.out("No attachment found!\n"),
1 => printer.out("Downloaded 1 attachment!\n"),
n => printer.out(format!(
"Downloaded {} attachment(s) from {} messages(s)!",
"Downloaded {} attachment(s) from {} messages(s)!\n",
n, emails_count,
)),
}

View file

@ -8,14 +8,14 @@ use crate::config::TomlConfig;
use self::download::AttachmentDownloadCommand;
/// Manage attachments.
/// Download your message attachments.
///
/// A message body can be composed of multiple MIME parts. An
/// attachment is the representation of a binary part of a message
/// body.
#[derive(Debug, Subcommand)]
pub enum AttachmentSubcommand {
#[command(arg_required_else_help = true)]
#[command(arg_required_else_help = true, alias = "dl")]
Download(AttachmentDownloadCommand),
}

View file

@ -16,7 +16,8 @@ use crate::{
folder::arg::name::{SourceFolderNameOptionalFlag, TargetFolderNameArg},
};
/// Copy a message from a source folder to a target folder.
/// Copy the message associated to the given envelope id(s) to the
/// given target folder.
#[derive(Debug, Parser)]
pub struct MessageCopyCommand {
#[command(flatten)]

View file

@ -14,7 +14,7 @@ use crate::{
folder::arg::name::FolderNameOptionalFlag,
};
/// Mark as deleted a message from a folder.
/// Mark as deleted the message associated to the given envelope id(s).
///
/// This command does not really delete the message: if the given
/// folder points to the trash folder, it adds the "deleted" flag to

View file

@ -0,0 +1,103 @@
use std::sync::Arc;
use clap::Parser;
use color_eyre::{eyre::eyre, Result};
use email::{backend::feature::BackendFeatureSource, config::Config};
use pimalaya_tui::{
himalaya::{backend::BackendBuilder, editor},
terminal::{cli::printer::Printer, config::TomlConfig as _},
};
use tracing::info;
use crate::{
account::arg::name::AccountNameFlag, config::TomlConfig, envelope::arg::ids::EnvelopeIdArg,
folder::arg::name::FolderNameOptionalFlag,
};
/// Edit the message associated to the given envelope id.
///
/// This command allows you to edit the given message using the
/// editor defined in your environment variable $EDITOR. When the
/// edition process finishes, you can choose between saving or sending
/// the final message.
#[derive(Debug, Parser)]
pub struct MessageEditCommand {
#[command(flatten)]
pub folder: FolderNameOptionalFlag,
#[command(flatten)]
pub envelope: EnvelopeIdArg,
/// List of headers that should be visible at the top of the
/// message.
///
/// If a given header is not found in the message, it will not be
/// visible. If no header is given, defaults to the one set up in
/// your TOML configuration file.
#[arg(long = "header", short = 'H', value_name = "NAME")]
pub headers: Vec<String>,
/// Edit the message on place.
///
/// If set, the original message being edited will be removed at
/// the end of the command. Useful when you need, for example, to
/// edit a draft, send it then remove it from the Drafts folder.
#[arg(long, short = 'p')]
pub on_place: bool,
#[command(flatten)]
pub account: AccountNameFlag,
}
impl MessageEditCommand {
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
info!("executing edit message command");
let folder = &self.folder.name;
let (toml_account_config, account_config) = config
.clone()
.into_account_configs(self.account.name.as_deref(), |c: &Config, name| {
c.account(name).ok()
})?;
let account_config = Arc::new(account_config);
let backend = BackendBuilder::new(
Arc::new(toml_account_config),
account_config.clone(),
|builder| {
builder
.without_features()
.with_add_message(BackendFeatureSource::Context)
.with_send_message(BackendFeatureSource::Context)
.with_delete_messages(BackendFeatureSource::Context)
},
)
.build()
.await?;
let id = self.envelope.id;
let tpl = backend
.get_messages(folder, &[id])
.await?
.first()
.ok_or(eyre!("cannot find message"))?
.to_read_tpl(&account_config, |mut tpl| {
if !self.headers.is_empty() {
tpl = tpl.with_show_only_headers(&self.headers);
}
tpl
})
.await?;
editor::edit_tpl_with_editor(account_config, printer, &backend, tpl).await?;
if self.on_place {
backend.delete_messages(folder, &[id]).await?;
}
Ok(())
}
}

View file

@ -0,0 +1,155 @@
use std::{
env::temp_dir,
fs,
io::{stdout, Write},
path::PathBuf,
sync::Arc,
};
use clap::Parser;
use color_eyre::{eyre::eyre, Result};
use email::{backend::feature::BackendFeatureSource, config::Config};
use pimalaya_tui::{himalaya::backend::BackendBuilder, terminal::config::TomlConfig as _};
use tracing::info;
use crate::{
account::arg::name::AccountNameFlag, config::TomlConfig, envelope::arg::ids::EnvelopeIdArg,
folder::arg::name::FolderNameOptionalFlag,
};
/// Export the message associated to the given envelope id.
///
/// This command allows you to export a message. A message can be
/// fully exported in one single file, or exported in multiple files
/// (one per MIME part found in the message). This is useful, for
/// example, to read a HTML message.
#[derive(Debug, Parser)]
pub struct MessageExportCommand {
#[command(flatten)]
pub folder: FolderNameOptionalFlag,
#[command(flatten)]
pub envelope: EnvelopeIdArg,
/// Export the full raw message as one unique .eml file.
///
/// The raw message represents the headers and the body as it is
/// on the backend, unedited: not decoded nor decrypted. This is
/// useful for debugging faulty messages, but also for
/// saving/sending/transfering messages.
#[arg(long, short = 'F')]
pub full: bool,
/// Try to open the exported message, when applicable.
///
/// This argument only works with full message export, or when
/// HTML or plain text is present in the export.
#[arg(long, short = 'O')]
pub open: bool,
/// Where the message should be exported to.
///
/// The destination should point to a valid directory. If `--full`
/// is given, it can also point to a .eml file.
#[arg(long, short, alias = "dest")]
pub destination: Option<PathBuf>,
#[command(flatten)]
pub account: AccountNameFlag,
}
impl MessageExportCommand {
pub async fn execute(self, config: &TomlConfig) -> Result<()> {
info!("executing export message command");
let folder = &self.folder.name;
let id = &self.envelope.id;
let (toml_account_config, account_config) = config
.clone()
.into_account_configs(self.account.name.as_deref(), |c: &Config, name| {
c.account(name).ok()
})?;
let account_config = Arc::new(account_config);
let backend = BackendBuilder::new(
Arc::new(toml_account_config),
account_config.clone(),
|builder| {
builder
.without_features()
.with_get_messages(BackendFeatureSource::Context)
},
)
.without_sending_backend()
.build()
.await?;
let msgs = backend.get_messages(folder, &[*id]).await?;
let msg = msgs.first().ok_or(eyre!("cannot find message {id}"))?;
if self.full {
let bytes = msg.raw()?;
match self.destination {
Some(mut dest) if dest.is_dir() => {
dest.push(format!("{id}.eml"));
fs::write(&dest, bytes)?;
let dest = dest.display();
println!("Message {id} successfully exported at {dest}!");
}
Some(dest) => {
fs::write(&dest, bytes)?;
let dest = dest.display();
println!("Message {id} successfully exported at {dest}!");
}
None => {
stdout().write_all(bytes)?;
}
};
} else {
let dest = match self.destination {
Some(dest) if dest.is_dir() => {
let dest = msg.download_parts(&dest)?;
let d = dest.display();
println!("Message {id} successfully exported in {d}!");
dest
}
Some(dest) if dest.is_file() => {
let dest = dest.parent().unwrap_or(&dest);
let dest = msg.download_parts(&dest)?;
let d = dest.display();
println!("Message {id} successfully exported in {d}!");
dest
}
Some(dest) => {
return Err(eyre!("Destination {} does not exist!", dest.display()));
}
None => {
let dest = temp_dir();
let dest = msg.download_parts(&dest)?;
let d = dest.display();
println!("Message {id} successfully exported in {d}!");
dest
}
};
if self.open {
let index_html = dest.join("index.html");
if index_html.exists() {
return Ok(open::that(index_html)?);
}
let plain_txt = dest.join("plain.txt");
if plain_txt.exists() {
return Ok(open::that(plain_txt)?);
}
println!("--open was passed but nothing to open, ignoring");
}
}
Ok(())
}
}

View file

@ -17,7 +17,7 @@ use crate::{
message::arg::{body::MessageRawBodyArg, header::HeaderRawArgs},
};
/// Forward a message.
/// Forward the message associated to the given envelope id.
///
/// This command allows you to forward the given message using the
/// editor defined in your environment variable $EDITOR. When the
@ -65,7 +65,6 @@ impl MessageForwardCommand {
.with_send_message(BackendFeatureSource::Context)
},
)
.without_sending_backend()
.build()
.await?;

View file

@ -12,7 +12,7 @@ use url::Url;
use crate::{account::arg::name::AccountNameFlag, config::TomlConfig};
/// Parse and edit a message from a mailto URL string.
/// Parse and edit a message from the given mailto URL string.
///
/// This command allows you to edit a message from the mailto format
/// using the editor defined in your environment variable

View file

@ -1,5 +1,7 @@
pub mod copy;
pub mod delete;
pub mod edit;
pub mod export;
pub mod forward;
pub mod mailto;
pub mod r#move;
@ -17,13 +19,14 @@ use pimalaya_tui::terminal::cli::printer::Printer;
use crate::config::TomlConfig;
use self::{
copy::MessageCopyCommand, delete::MessageDeleteCommand, forward::MessageForwardCommand,
mailto::MessageMailtoCommand, r#move::MessageMoveCommand, read::MessageReadCommand,
reply::MessageReplyCommand, save::MessageSaveCommand, send::MessageSendCommand,
thread::MessageThreadCommand, write::MessageWriteCommand,
copy::MessageCopyCommand, delete::MessageDeleteCommand, edit::MessageEditCommand,
export::MessageExportCommand, forward::MessageForwardCommand, mailto::MessageMailtoCommand,
r#move::MessageMoveCommand, read::MessageReadCommand, reply::MessageReplyCommand,
save::MessageSaveCommand, send::MessageSendCommand, thread::MessageThreadCommand,
write::MessageWriteCommand,
};
/// Manage messages.
/// Read, write, send, copy, move and delete your messages.
///
/// A message is the content of an email. It is composed of headers
/// (located at the top of the message) and a body (located at the
@ -34,19 +37,22 @@ pub enum MessageSubcommand {
#[command(arg_required_else_help = true)]
Read(MessageReadCommand),
#[command(arg_required_else_help = true)]
Export(MessageExportCommand),
#[command(arg_required_else_help = true)]
Thread(MessageThreadCommand),
#[command(aliases = ["add", "create", "new", "compose"])]
Write(MessageWriteCommand),
#[command()]
Reply(MessageReplyCommand),
#[command(aliases = ["fwd", "fd"])]
Forward(MessageForwardCommand),
#[command()]
Edit(MessageEditCommand),
Mailto(MessageMailtoCommand),
Save(MessageSaveCommand),
@ -71,10 +77,12 @@ impl MessageSubcommand {
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
match self {
Self::Read(cmd) => cmd.execute(printer, config).await,
Self::Export(cmd) => cmd.execute(config).await,
Self::Thread(cmd) => cmd.execute(printer, config).await,
Self::Write(cmd) => cmd.execute(printer, config).await,
Self::Reply(cmd) => cmd.execute(printer, config).await,
Self::Forward(cmd) => cmd.execute(printer, config).await,
Self::Edit(cmd) => cmd.execute(printer, config).await,
Self::Mailto(cmd) => cmd.execute(printer, config).await,
Self::Save(cmd) => cmd.execute(printer, config).await,
Self::Send(cmd) => cmd.execute(printer, config).await,

View file

@ -17,7 +17,8 @@ use crate::{
folder::arg::name::{SourceFolderNameOptionalFlag, TargetFolderNameArg},
};
/// Move a message from a source folder to a target folder.
/// Move the message associated to the given envelope id(s) to the
/// given target folder.
#[derive(Debug, Parser)]
pub struct MessageMoveCommand {
#[command(flatten)]

View file

@ -3,7 +3,6 @@ use std::sync::Arc;
use clap::Parser;
use color_eyre::Result;
use email::{backend::feature::BackendFeatureSource, config::Config};
use mml::message::FilterParts;
use pimalaya_tui::{
himalaya::backend::BackendBuilder,
terminal::{cli::printer::Printer, config::TomlConfig as _},
@ -16,11 +15,12 @@ use crate::{
folder::arg::name::FolderNameOptionalFlag,
};
/// Read a message.
/// Read a human-friendly version of the message associated to the
/// given envelope id(s).
///
/// This command allows you to read a message. When reading a message,
/// the "seen" flag is automatically applied to the corresponding
/// envelope. To prevent this behaviour, use the --preview flag.
/// envelope. To prevent this behaviour, use the "--preview" flag.
#[derive(Debug, Parser)]
pub struct MessageReadCommand {
#[command(flatten)]
@ -34,31 +34,10 @@ pub struct MessageReadCommand {
#[arg(long, short)]
pub preview: bool,
/// Read the raw version of the given message.
///
/// The raw message represents the headers and the body as it is
/// on the backend, unedited: not decoded nor decrypted. This is
/// useful for debugging faulty messages, but also for
/// saving/sending/transfering messages.
#[arg(long, short)]
#[arg(conflicts_with = "no_headers")]
#[arg(conflicts_with = "headers")]
pub raw: bool,
/// Read only body of text/html parts.
///
/// This argument is useful when you need to read the HTML version
/// of a message. Combined with --no-headers, you can write it to
/// a .html file and open it with your favourite browser.
#[arg(long)]
#[arg(conflicts_with = "raw")]
pub html: bool,
/// Read only the body of the message.
///
/// All headers will be removed from the message.
#[arg(long)]
#[arg(conflicts_with = "raw")]
#[arg(conflicts_with = "headers")]
pub no_headers: bool,
@ -69,7 +48,6 @@ pub struct MessageReadCommand {
/// visible. If no header is given, defaults to the one set up in
/// your TOML configuration file.
#[arg(long = "header", short = 'H', value_name = "NAME")]
#[arg(conflicts_with = "raw")]
#[arg(conflicts_with = "no_headers")]
pub headers: Vec<String>,
@ -99,6 +77,7 @@ impl MessageReadCommand {
builder
.without_features()
.with_get_messages(BackendFeatureSource::Context)
.with_peek_messages(BackendFeatureSource::Context)
},
)
.without_sending_backend()
@ -117,28 +96,18 @@ impl MessageReadCommand {
for email in emails.to_vec() {
bodies.push_str(glue);
if self.raw {
// emails do not always have valid utf8, uses "lossy" to
// display what can be displayed
bodies.push_str(&String::from_utf8_lossy(email.raw()?));
} else {
let tpl = email
.to_read_tpl(&account_config, |mut tpl| {
if self.no_headers {
tpl = tpl.with_hide_all_headers();
} else if !self.headers.is_empty() {
tpl = tpl.with_show_only_headers(&self.headers);
}
let tpl = email
.to_read_tpl(&account_config, |mut tpl| {
if self.no_headers {
tpl = tpl.with_hide_all_headers();
} else if !self.headers.is_empty() {
tpl = tpl.with_show_only_headers(&self.headers);
}
if self.html {
tpl = tpl.with_filter_parts(FilterParts::Only("text/html".into()));
}
tpl
})
.await?;
bodies.push_str(&tpl);
}
tpl
})
.await?;
bodies.push_str(&tpl);
glue = "\n\n";
}

View file

@ -17,7 +17,7 @@ use crate::{
message::arg::{body::MessageRawBodyArg, header::HeaderRawArgs, reply::MessageReplyAllArg},
};
/// Reply to a message.
/// Reply to the message associated to the given envelope id.
///
/// This command allows you to reply to the given message using the
/// editor defined in your environment variable $EDITOR. When the

View file

@ -16,7 +16,7 @@ use crate::{
folder::arg::name::FolderNameOptionalFlag, message::arg::MessageRawArg,
};
/// Save a message to a folder.
/// Save the given raw message to the given folder.
///
/// This command allows you to add a raw message to the given folder.
#[derive(Debug, Parser)]

View file

@ -13,7 +13,7 @@ use tracing::info;
use crate::{account::arg::name::AccountNameFlag, config::TomlConfig, message::arg::MessageRawArg};
/// Send a message.
/// Send the given raw message.
///
/// This command allows you to send a raw message and to save a copy
/// to your send folder.

View file

@ -3,7 +3,6 @@ use std::sync::Arc;
use clap::Parser;
use color_eyre::Result;
use email::{backend::feature::BackendFeatureSource, config::Config};
use mml::message::FilterParts;
use pimalaya_tui::{
himalaya::backend::BackendBuilder,
terminal::{cli::printer::Printer, config::TomlConfig as _},
@ -17,7 +16,8 @@ use crate::{
folder::arg::name::FolderNameOptionalFlag,
};
/// Thread a message.
/// Read human-friendly version of messages associated to the
/// given envelope id's thread.
///
/// This command allows you to thread a message. When threading a message,
/// the "seen" flag is automatically applied to the corresponding
@ -35,31 +35,10 @@ pub struct MessageThreadCommand {
#[arg(long, short)]
pub preview: bool,
/// Thread the raw version of the given message.
///
/// The raw message represents the headers and the body as it is
/// on the backend, unedited: not decoded nor decrypted. This is
/// useful for debugging faulty messages, but also for
/// saving/sending/transfering messages.
#[arg(long, short)]
#[arg(conflicts_with = "no_headers")]
#[arg(conflicts_with = "headers")]
pub raw: bool,
/// Thread only body of text/html parts.
///
/// This argument is useful when you need to thread the HTML version
/// of a message. Combined with --no-headers, you can write it to
/// a .html file and open it with your favourite browser.
#[arg(long)]
#[arg(conflicts_with = "raw")]
pub html: bool,
/// Thread only the body of the message.
///
/// All headers will be removed from the message.
#[arg(long)]
#[arg(conflicts_with = "raw")]
#[arg(conflicts_with = "headers")]
pub no_headers: bool,
@ -70,7 +49,6 @@ pub struct MessageThreadCommand {
/// visible. If no header is given, defaults to the one set up in
/// your TOML configuration file.
#[arg(long = "header", short = 'H', value_name = "NAME")]
#[arg(conflicts_with = "raw")]
#[arg(conflicts_with = "no_headers")]
pub headers: Vec<String>,
@ -100,6 +78,7 @@ impl MessageThreadCommand {
builder
.without_features()
.with_get_messages(BackendFeatureSource::Context)
.with_peek_messages(BackendFeatureSource::Context)
.with_thread_envelopes(BackendFeatureSource::Context)
},
)
@ -130,29 +109,19 @@ impl MessageThreadCommand {
bodies.push_str(glue);
bodies.push_str(&format!("-------- Message {} --------\n\n", ids[i + 1]));
if self.raw {
// emails do not always have valid utf8, uses "lossy" to
// display what can be displayed
bodies.push_str(&String::from_utf8_lossy(email.raw()?));
} else {
let tpl = email
.to_read_tpl(&account_config, |mut tpl| {
if self.no_headers {
tpl = tpl.with_hide_all_headers();
} else if !self.headers.is_empty() {
tpl = tpl.with_show_only_headers(&self.headers);
}
let tpl = email
.to_read_tpl(&account_config, |mut tpl| {
if self.no_headers {
tpl = tpl.with_hide_all_headers();
} else if !self.headers.is_empty() {
tpl = tpl.with_show_only_headers(&self.headers);
}
if self.html {
tpl = tpl.with_filter_parts(FilterParts::Only("text/html".into()));
}
tpl
})
.await?;
bodies.push_str(&tpl);
}
tpl
})
.await?;
bodies.push_str(&tpl);
glue = "\n\n";
}

View file

@ -18,7 +18,7 @@ use crate::{
message::arg::{body::MessageRawBodyArg, header::HeaderRawArgs},
};
/// Write a new message.
/// Compose a new message, from scratch.
///
/// This command allows you to write a new message using the editor
/// defined in your environment variable $EDITOR. When the edition

View file

@ -15,15 +15,14 @@ use self::{
send::TemplateSendCommand, write::TemplateWriteCommand,
};
/// Manage templates.
/// Generate, save and send message templates.
///
/// A template is an editable version of a message (headers +
/// body). It uses a specific language called MML that allows you to
/// attach file or encrypt content. This subcommand allows you manage
/// them.
///
/// You can learn more about MML at
/// <https://crates.io/crates/mml-lib>.
/// Learn more about MML at: <https://crates.io/crates/mml-lib>.
#[derive(Debug, Subcommand)]
pub enum TemplateSubcommand {
#[command(aliases = ["add", "create", "new", "compose"])]

View file

@ -16,12 +16,12 @@ use crate::{
account::arg::name::AccountNameFlag, config::TomlConfig, folder::arg::name::FolderNameArg,
};
/// Create a new folder.
/// Create the given folder.
///
/// This command allows you to create a new folder using the given
/// name.
#[derive(Debug, Parser)]
pub struct AddFolderCommand {
pub struct FolderAddCommand {
#[command(flatten)]
pub folder: FolderNameArg,
@ -29,7 +29,7 @@ pub struct AddFolderCommand {
pub account: AccountNameFlag,
}
impl AddFolderCommand {
impl FolderAddCommand {
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
info!("executing create folder command");

View file

@ -16,7 +16,7 @@ use crate::{
account::arg::name::AccountNameFlag, config::TomlConfig, folder::arg::name::FolderNameArg,
};
/// Delete a folder.
/// Delete the given folder.
///
/// All emails from the given folder are definitely deleted. The
/// folder is also deleted after execution of the command.

View file

@ -15,7 +15,7 @@ use crate::{
account::arg::name::AccountNameFlag, config::TomlConfig, folder::arg::name::FolderNameArg,
};
/// Expunge a folder.
/// Expunge the given folder.
///
/// The concept of expunging is similar to the IMAP one: it definitely
/// deletes emails from the given folder that contain the "deleted"

View file

@ -28,9 +28,10 @@ pub struct FolderListCommand {
/// The maximum width the table should not exceed.
///
/// This argument will force the table not to exceed the given
/// width in pixels. Columns may shrink with ellipsis in order to
/// width, in pixels. Columns may shrink with ellipsis in order to
/// fit the width.
#[arg(long, short = 'w', name = "table_max_width", value_name = "PIXELS")]
#[arg(long = "max-width", short = 'w')]
#[arg(name = "table_max_width", value_name = "PIXELS")]
pub table_max_width: Option<u16>,
}

View file

@ -11,18 +11,18 @@ use pimalaya_tui::terminal::cli::printer::Printer;
use crate::config::TomlConfig;
use self::{
add::AddFolderCommand, delete::FolderDeleteCommand, expunge::FolderExpungeCommand,
add::FolderAddCommand, delete::FolderDeleteCommand, expunge::FolderExpungeCommand,
list::FolderListCommand, purge::FolderPurgeCommand,
};
/// Manage folders.
/// Create, list and purge your folders (as known as mailboxes).
///
/// A folder (as known as mailbox, or directory) contains one or more
/// emails. This subcommand allows you to manage them.
/// A folder (as known as mailbox, or directory) is a messages
/// container. This subcommand allows you to manage them.
#[derive(Debug, Subcommand)]
pub enum FolderSubcommand {
#[command(visible_alias = "create", alias = "new")]
Add(AddFolderCommand),
Add(FolderAddCommand),
#[command(alias = "lst")]
List(FolderListCommand),

View file

@ -13,7 +13,7 @@ use crate::{
account::arg::name::AccountNameFlag, config::TomlConfig, folder::arg::name::FolderNameArg,
};
/// Purge a folder.
/// Purge the given folder.
///
/// All emails from the given folder are definitely deleted. The
/// purged folder will remain empty after execution of the command.

View file

@ -1,7 +1,7 @@
use clap::Parser;
use color_eyre::Result;
use himalaya::{
cli::Cli, config::TomlConfig, envelope::command::list::ListEnvelopesCommand,
cli::Cli, config::TomlConfig, envelope::command::list::EnvelopeListCommand,
message::command::mailto::MessageMailtoCommand,
};
use pimalaya_tui::terminal::{
@ -37,7 +37,7 @@ async fn main() -> Result<()> {
Some(cmd) => cmd.execute(&mut printer, cli.config_paths.as_ref()).await,
None => {
let config = TomlConfig::from_paths_or_default(cli.config_paths.as_ref()).await?;
ListEnvelopesCommand::default()
EnvelopeListCommand::default()
.execute(&mut printer, &config)
.await
}

View file

@ -1,14 +1,15 @@
use std::{fs, path::PathBuf};
use clap::{CommandFactory, Parser};
use clap_mangen::Man;
use color_eyre::Result;
use pimalaya_tui::terminal::cli::printer::Printer;
use shellexpand_utils::{canonicalize, expand};
use std::{fs, path::PathBuf};
use tracing::info;
use crate::cli::Cli;
/// Generate manual pages to a directory.
/// Generate manual pages to the given directory.
///
/// This command allows you to generate manual pages (following the
/// man page format) to the given directory. If the directory does not
@ -34,7 +35,7 @@ impl ManualGenerateCommand {
Man::new(cmd).render(&mut buffer)?;
fs::create_dir_all(&self.dir)?;
printer.log(format!("Generating man page for command {cmd_name}"))?;
printer.log(format!("Generating man page for command {cmd_name}\n"))?;
fs::write(self.dir.join(format!("{}.1", cmd_name)), buffer)?;
for subcmd in subcmds {
@ -43,7 +44,9 @@ impl ManualGenerateCommand {
let mut buffer = Vec::new();
Man::new(subcmd).render(&mut buffer)?;
printer.log(format!("Generating man page for subcommand {subcmd_name}"))?;
printer.log(format!(
"Generating man page for subcommand {subcmd_name}…\n"
))?;
fs::write(
self.dir.join(format!("{}-{}.1", cmd_name, subcmd_name)),
buffer,
@ -51,8 +54,8 @@ impl ManualGenerateCommand {
}
printer.log(format!(
"{subcmds_len} man page(s) successfully generated in {:?}!",
self.dir
"{subcmds_len} man page(s) successfully generated in {}!\n",
self.dir.display()
))?;
Ok(())