Compare commits
1 commit
Author | SHA1 | Date | |
---|---|---|---|
![]() |
2839f84e66 |
|
@ -1,2 +0,0 @@
|
|||
# Use cargo-derivefmt to sort derives alphabetically
|
||||
f900dbea468e822c5a510a72ecc6367549443927
|
|
@ -1,37 +0,0 @@
|
|||
# SPDX-License-Identifier: EUPL-1.2
|
||||
#
|
||||
# Makefile for: "Build and run Tests" workflow, in .gitea/workflows/build.yaml
|
||||
|
||||
.POSIX:
|
||||
.SUFFIXES:
|
||||
CARGO_INCREMENTAL ?= 0
|
||||
CARGO_NET_RETRY ?= 10
|
||||
CARGO_REGISTRIES_CRATES_IO_PROTOCOL ?= sparse
|
||||
RUSTFLAGS ?= -D warnings -W unreachable-pub -W rust-2021-compatibility -C debuginfo=0
|
||||
RUSTUP_MAX_RETRIES ?= 10
|
||||
RUST_BACKTRACE ?= short
|
||||
|
||||
.PHONY: all
|
||||
all: cargo-check cargo-test-compiles cargo-test rustdoc-build rustdoc-test
|
||||
@printf "All completed.\n"
|
||||
|
||||
.PHONY: cargo-check
|
||||
cargo-check:
|
||||
@printf "cargo-check\n"
|
||||
cargo check --all-features --all --tests --examples --benches --bins
|
||||
.PHONY: cargo-test-compiles
|
||||
cargo-test-compiles:
|
||||
@printf "cargo-test-compiles\n"
|
||||
cargo test --all --no-fail-fast --all-features --no-run --locked
|
||||
.PHONY: cargo-test
|
||||
cargo-test:
|
||||
@printf "cargo-test\n"
|
||||
cargo nextest run --all --no-fail-fast --all-features --future-incompat-report -E 'not (test(smtp::test::test_smtp))'
|
||||
.PHONY: rustdoc-build
|
||||
rustdoc-build:
|
||||
@printf "rustdoc-build\n"
|
||||
env DISPLAY= WAYLAND_DISPLAY= make build-rustdoc
|
||||
.PHONY: rustdoc-test
|
||||
rustdoc-test:
|
||||
@printf "rustdoc-test\n"
|
||||
make test-docs
|
|
@ -1,61 +0,0 @@
|
|||
# SPDX-License-Identifier: EUPL-1.2
|
||||
#
|
||||
# Makefile for: "Run cargo lints" workflow, in .gitea/workflows/lints.yaml
|
||||
|
||||
.POSIX:
|
||||
.SUFFIXES:
|
||||
CARGO_INCREMENTAL ?= 0
|
||||
CARGO_NET_RETRY ?= 10
|
||||
CARGO_REGISTRIES_CRATES_IO_PROTOCOL ?= sparse
|
||||
RUSTFLAGS ?= -D warnings -W unreachable-pub -W rust-2021-compatibility -C debuginfo=0
|
||||
RUSTUP_MAX_RETRIES ?= 10
|
||||
RUST_BACKTRACE ?= short
|
||||
NIGHTLY_EXISTS=`((cargo +nightly 2> /dev/null 1> /dev/null) && echo 0)|| echo 1)`
|
||||
GIT=env GIT_CONFIG_GLOBAL="" GIT_CONFIG_SYSTEM="" GIT_CONFIG_NOSYSTEM=1 git
|
||||
|
||||
.PHONY: all
|
||||
all: cargo-msrv rustfmt clippy cargo-derivefmt-melib cargo-derivefmt-meli cargo-derivefmt-tools
|
||||
@printf "All checks completed.\n"
|
||||
|
||||
# Check both melib and meli in the same Make target, because if melib does not
|
||||
# satisfy MSRV then meli won't either, since it depends on melib.
|
||||
.PHONY: cargo-msrv
|
||||
cargo-msrv:
|
||||
@printf "cargo-msrv\n"
|
||||
cargo msrv --output-format json --log-level trace --log-target stdout --path meli verify -- cargo check --all-targets
|
||||
cargo msrv --output-format json --log-level trace --log-target stdout --path melib verify -- cargo check --all-targets
|
||||
|
||||
.PHONY: rustfmt
|
||||
rustfmt:
|
||||
@printf "rustfmt\n"
|
||||
@((if [ "${NIGHTLY_EXISTS}" -eq 0 ]; then printf "running rustfmt with nightly toolchain\n"; else printf "running rustfmt with active toolchain\n"; fi))
|
||||
@((if [ "${NIGHTLY_EXISTS}" -eq 0 ]; then cargo +nightly fmt --check --all; else cargo fmt --check --all; fi))
|
||||
|
||||
.PHONY: clippy
|
||||
clippy:
|
||||
@printf "clippy\n"
|
||||
cargo clippy --no-deps --all-features --all --tests --examples --benches --bins
|
||||
|
||||
.PHONY: cargo-derivefmt-melib
|
||||
cargo-derivefmt-melib:
|
||||
@printf "cargo-derivefmt-melib\n"
|
||||
@printf "Checking that derives are sorted alphabetically...\n"
|
||||
cargo derivefmt --manifest-path ./melib/Cargo.toml
|
||||
@$(GIT) checkout --quiet meli/src/conf/overrides.rs
|
||||
@($(GIT) diff --quiet ./melib && $(GIT) diff --cached --quiet ./melib && printf "All ./melib derives are sorted alphabetically.\n") || (printf "Some derives in the ./melib crate are not sorted alphabetically, see diff:\n"; $(GIT) diff HEAD; exit 1)
|
||||
|
||||
.PHONY: cargo-derivefmt-meli
|
||||
cargo-derivefmt-meli:
|
||||
@printf "cargo-derivefmt-meli\n"
|
||||
@printf "Checking that derives are sorted alphabetically...\n"
|
||||
cargo derivefmt --manifest-path ./meli/Cargo.toml
|
||||
@$(GIT) checkout --quiet meli/src/conf/overrides.rs
|
||||
@($(GIT) diff --quiet ./meli && $(GIT) diff --cached --quiet ./meli && printf "All ./meli derives are sorted alphabetically.\n") || (printf "Some derives in the ./meli crate are not sorted alphabetically, see diff:\n"; $(GIT) diff HEAD; exit 1)
|
||||
|
||||
.PHONY: cargo-derivefmt-tools
|
||||
cargo-derivefmt-tools:
|
||||
@printf "cargo-derivefmt-tools\n"
|
||||
@printf "Checking that derives are sorted alphabetically...\n"
|
||||
cargo derivefmt --manifest-path ./tools/Cargo.toml
|
||||
@$(GIT) checkout --quiet meli/src/conf/overrides.rs
|
||||
@($(GIT) diff --quiet ./tools && $(GIT) diff --cached --quiet ./tools && printf "All ./tools derives are sorted alphabetically.\n") || (printf "Some derives in the ./tools crate are not sorted alphabetically, see diff:\n"; $(GIT) diff HEAD; exit 1)
|
|
@ -1,28 +0,0 @@
|
|||
# SPDX-License-Identifier: EUPL-1.2
|
||||
#
|
||||
# Makefile for: "Build and run Tests" workflow, in .gitea/workflows/build.yaml
|
||||
|
||||
.POSIX:
|
||||
.SUFFIXES:
|
||||
CARGO_INCREMENTAL ?= 0
|
||||
CARGO_NET_RETRY ?= 10
|
||||
CARGO_REGISTRIES_CRATES_IO_PROTOCOL ?= sparse
|
||||
RUSTFLAGS ?= -D warnings -W unreachable-pub -W rust-2021-compatibility -C debuginfo=0
|
||||
RUSTUP_MAX_RETRIES ?= 10
|
||||
RUST_BACKTRACE ?= short
|
||||
|
||||
.PHONY: all
|
||||
all: cargo-sort check-debian-changelog
|
||||
@printf "All checks completed.\n"
|
||||
|
||||
.PHONY: cargo-sort
|
||||
cargo-sort:
|
||||
@printf "cargo-sort\n"
|
||||
cargo-sort --check --check-format --grouped --order package,bin,lib,dependencies,features,build-dependencies,dev-dependencies,workspace fuzz
|
||||
cargo-sort --check --check-format --grouped --order package,bin,lib,dependencies,features,build-dependencies,dev-dependencies,workspace tools
|
||||
cargo-sort --check --check-format --grouped --order package,bin,lib,dependencies,features,build-dependencies,dev-dependencies,workspace --workspace
|
||||
|
||||
.PHONY: check-debian-changelog
|
||||
check-debian-changelog:
|
||||
@printf "Check debian/changelog is up-to-date.\n"
|
||||
./scripts/check_debian_changelog.sh
|
|
@ -1,41 +0,0 @@
|
|||
---
|
||||
|
||||
name: "Pull Request"
|
||||
about: "Basic pull request template"
|
||||
ref: "master"
|
||||
|
||||
---
|
||||
|
||||
<!-- If your PR is not ready to merge/review, you can add a `WIP: ` prefix to the title. -->
|
||||
<!--
|
||||
This template is just a suggestion, and is commented out using HTML comment syntax.
|
||||
It will not show up in your PR text unless you remove the comment markers.
|
||||
-->
|
||||
|
||||
<!--
|
||||
|
||||
## Summary of the PR
|
||||
|
||||
Changes introduced in this PR.
|
||||
|
||||
|
||||
## Requirements
|
||||
|
||||
Before submitting your PR, please make sure you have addressed the following
|
||||
requirements:
|
||||
|
||||
* [ ] All commits in this PR are signed (with `git commit -s`), and the commit
|
||||
has a message describing the motivation behind the change, if
|
||||
appropriate.
|
||||
* [ ] All added/changed public-facing functionality, especially configuration
|
||||
options, are documented in the manual pages.
|
||||
* [ ] Any newly added `unsafe` code is properly documented.
|
||||
* [ ] Each commit has been formatted with `rustfmt`. Run `make fmt` in the
|
||||
project root.
|
||||
* [ ] Each commit has been linted with `clippy`. Run `make lint` in the project
|
||||
root.
|
||||
* [ ] Each commit does not break any test. Run `make test` in the project root.
|
||||
If you have `cargo-nextest` installed, you can run `cargo nextest run
|
||||
--all --no-fail-fast --all-features --future-incompat-report` instead.
|
||||
|
||||
-->
|
|
@ -1,109 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
# SPDX-License-Identifier: EUPL-1.2 OR GPL-3.0-or-later
|
||||
|
||||
# Lint with shellcheck -s sh -S style check_dco.sh
|
||||
|
||||
# Notes:
|
||||
# ======
|
||||
#
|
||||
# - We need to make sure git commands do not read from any existing configs to
|
||||
# prevent surprises like default trailers being added.
|
||||
# - we need to pass `--always` to `git-format-patch` to check even empty
|
||||
# commits despite them not being something we would merge. This tripped me up
|
||||
# when debugging this workflow because I tested it with empty commits. My
|
||||
# fault.
|
||||
|
||||
export GIT_CONFIG_GLOBAL=""
|
||||
export GIT_CONFIG_SYSTEM=""
|
||||
export GIT_CONFIG_NOSYSTEM=1
|
||||
|
||||
ensure_env_var() {
|
||||
set | grep -q "^${1}=" || (printf "Environment variable %s missing from process environment, exiting.\n" "${1}"; exit "${2}")
|
||||
}
|
||||
|
||||
ensure_env_var "GITHUB_BASE_REF" 1 || exit $?
|
||||
ensure_env_var "GITHUB_HEAD_REF" 2 || exit $?
|
||||
|
||||
# contains_correct_signoff() {
|
||||
# author=$(git log --author="$1" --pretty="%an <%ae>" -1)
|
||||
# git format-patch --always --stdout "${1}^..${1}" | git interpret-trailers --parse | grep -q "^Signed-off-by: ${author}"
|
||||
# }
|
||||
contains_signoff() {
|
||||
GIT_CONFIG_GLOBAL="" git format-patch --always -1 --stdout "${1}" | git interpret-trailers --parse | grep -q "^Signed-off-by: "
|
||||
}
|
||||
|
||||
get_commit_sha() {
|
||||
if OUT=$(git rev-parse "${1}"); then
|
||||
printf "%s" "${OUT}"
|
||||
return
|
||||
fi
|
||||
printf "Could not git-rev-parse %s, falling back to HEAD...\n" "${1}" 1>&2
|
||||
git rev-parse HEAD
|
||||
}
|
||||
|
||||
echo "Debug workflow info:"
|
||||
echo "Base ref GITHUB_BASE_REF=${GITHUB_BASE_REF}"
|
||||
echo "Head ref GITHUB_HEAD_REF=${GITHUB_HEAD_REF}"
|
||||
BASE_REF=$(get_commit_sha "${GITHUB_BASE_REF}")
|
||||
HEAD_REF=$(get_commit_sha "${GITHUB_HEAD_REF}")
|
||||
echo "Processed base ref BASE_REF=${BASE_REF}"
|
||||
echo "Processed head ref HEAD_REF=${HEAD_REF}"
|
||||
|
||||
RANGE="${BASE_REF}..${HEAD_REF}"
|
||||
echo "Range to examine is RANGE=${RANGE}"
|
||||
|
||||
if ! SHA_LIST=$(git rev-list "${RANGE}"); then
|
||||
printf "Could not get commit range %s with git rev-list, bailing out...\n" "${RANGE}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "SHA list to examine is SHA_LIST="
|
||||
echo "---------------------------------------------------------------------"
|
||||
echo "${SHA_LIST}"
|
||||
echo "---------------------------------------------------------------------"
|
||||
echo ""
|
||||
echo "Starting checks..."
|
||||
|
||||
output=$(printf "%s" "${SHA_LIST}" | while read -r commit_sha; do
|
||||
contains_signoff_result=""
|
||||
|
||||
contains_signoff "${commit_sha}"; contains_signoff_result="$?"
|
||||
if [ "${contains_signoff_result}" -ne 0 ]; then
|
||||
printf "Commit does not contain Signed-off-by git trailer: %s\n\n" "${commit_sha}"
|
||||
echo "patch was:"
|
||||
echo "---------------------------------------------------------------------"
|
||||
GIT_CONFIG_GLOBAL="" git format-patch --always -1 --stdout "${commit_sha}"
|
||||
echo "---------------------------------------------------------------------"
|
||||
echo "trailers were:"
|
||||
echo "---------------------------------------------------------------------"
|
||||
GIT_CONFIG_GLOBAL="" git format-patch --always -1 --stdout "${commit_sha}" | git interpret-trailers --parse
|
||||
echo "---------------------------------------------------------------------"
|
||||
echo "commit was:"
|
||||
echo "---------------------------------------------------------------------"
|
||||
git log --no-decorate --pretty=oneline --abbrev-commit -n 1 "${commit_sha}"
|
||||
echo "---------------------------------------------------------------------"
|
||||
fi
|
||||
done)
|
||||
|
||||
if [ "${output}" = "" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "One or more of your commits in this Pull Request lack the Developer Certificate of Origin "
|
||||
echo "which is more commonly known as DCO or the \"Signed-off-by: \" trailer line in the "
|
||||
echo "git commit message."
|
||||
echo "For information, documentation, help, check: https://wiki.linuxfoundation.org/dco"
|
||||
|
||||
echo "The reported errors were:"
|
||||
printf "%s\n" "${output}" 1>&2
|
||||
|
||||
echo ""
|
||||
echo "Solution:"
|
||||
echo ""
|
||||
echo "- end all your commits with a 'Signed-off-by: User <user@localhost>' line, "
|
||||
echo " with your own display name and email address."
|
||||
echo "- Make sure the signoff is separated by the commit message body with an empty line."
|
||||
echo "- Make sure the signoff is the last line in your commit message."
|
||||
echo "- Lastly, make sure the signoff matches your git commit author name and email identity."
|
||||
|
||||
exit 1
|
|
@ -1,123 +0,0 @@
|
|||
# SPDX-License-Identifier: EUPL-1.2
|
||||
name: Build and run Tests
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_NET_RETRY: 10
|
||||
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
|
||||
RUSTFLAGS: "-D warnings -W unreachable-pub -W rust-2021-compatibility -C debuginfo=0"
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
RUST_BACKTRACE: short
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
paths:
|
||||
- '.gitea/**'
|
||||
- 'melib/src/**'
|
||||
- 'melib/Cargo.toml'
|
||||
- 'meli/src/**'
|
||||
- 'meli/Cargo.toml'
|
||||
- 'Cargo.toml'
|
||||
- 'Cargo.lock'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Run tests
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
build: [linux-amd64, linux-arm64]
|
||||
include:
|
||||
- build: linux-amd64
|
||||
arch: amd64
|
||||
os: ubuntu-latest
|
||||
rust: stable
|
||||
target: x86_64-unknown-linux-gnu
|
||||
- build: linux-arm64
|
||||
arch: arm64
|
||||
os: ubuntu-latest-arm64
|
||||
rust: stable
|
||||
target: aarch64-unknown-linux-gnu
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- id: os-deps
|
||||
name: install OS dependencies
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y libdbus-1-dev pkg-config mandoc libssl-dev make
|
||||
- name: Cache rustup
|
||||
id: cache-rustup
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.rustup/
|
||||
~/.cargo/env
|
||||
~/.cargo/config.toml
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
key: build-workflow-${{ matrix.build }}-rustup
|
||||
- id: rustup-setup
|
||||
if: steps.cache-rustup.outputs.cache-hit != 'true'
|
||||
name: Install rustup and toolchains
|
||||
shell: bash
|
||||
run: |
|
||||
if ! command -v rustup &>/dev/null; then
|
||||
curl --proto '=https' --tlsv1.2 --retry 10 --retry-connrefused --location --silent --show-error --fail "https://sh.rustup.rs" | sh -s -- --default-toolchain none -y
|
||||
source "${HOME}/.cargo/env"
|
||||
echo "${CARGO_HOME:-$HOME/.cargo}/bin" >> $GITHUB_PATH
|
||||
echo "CARGO_HOME=${CARGO_HOME:-$HOME/.cargo}" >> $GITHUB_ENV
|
||||
rustup toolchain install --profile minimal ${{ matrix.rust }} --target ${{ matrix.target }}
|
||||
fi
|
||||
- name: Source .cargo/env
|
||||
shell: bash
|
||||
run: |
|
||||
source "${HOME}/.cargo/env"
|
||||
echo "${CARGO_HOME:-$HOME/.cargo}/bin" >> $GITHUB_PATH
|
||||
echo "CARGO_HOME=${CARGO_HOME:-$HOME/.cargo}" >> $GITHUB_ENV
|
||||
- name: Setup Rust target
|
||||
if: steps.cache-rustup.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
mkdir -p "${{ env.CARGO_HOME }}"
|
||||
cat << EOF > "${{ env.CARGO_HOME }}"/config.toml
|
||||
[build]
|
||||
target = "${{ matrix.target }}"
|
||||
EOF
|
||||
- name: Add test dependencies
|
||||
if: steps.cache-rustup.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
cargo install --quiet --version 0.9.54 --target "${{ matrix.target }}" cargo-nextest
|
||||
- name: Restore build artifacts cache in target dir
|
||||
id: cache-deps
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: target/
|
||||
key: workflow-${{ matrix.build }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
- name: cargo-check
|
||||
run: |
|
||||
make -f ./.gitea/Makefile.build cargo-check
|
||||
- if: steps.cache-deps.outputs.cache-hit != 'true'
|
||||
name: Save build artifacts in target dir
|
||||
id: save-cache-deps
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: target/
|
||||
key: workflow-${{ matrix.build }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
- name: cargo-test-compiles
|
||||
if: success() || failure()
|
||||
run: |
|
||||
make -f ./.gitea/Makefile.build cargo-test-compiles
|
||||
- name: cargo-test
|
||||
run: |
|
||||
make -f ./.gitea/Makefile.build cargo-test
|
||||
- name: rustdoc build
|
||||
if: success() || failure()
|
||||
run: |
|
||||
make -f ./.gitea/Makefile.build rustdoc-build
|
||||
- name: rustdoc tests
|
||||
if: success() || failure()
|
||||
run: |
|
||||
make -f ./.gitea/Makefile.build rustdoc-test
|
|
@ -1,79 +0,0 @@
|
|||
# SPDX-License-Identifier: EUPL-1.2
|
||||
name: Build release binaries
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_NET_RETRY: 10
|
||||
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
|
||||
RUSTFLAGS: "-D warnings -W unreachable-pub -W rust-2021-compatibility"
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
RUST_BACKTRACE: short
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build release binary
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
build: [linux-amd64, linux-arm64]
|
||||
include:
|
||||
- build: linux-amd64
|
||||
arch: amd64
|
||||
os: ubuntu-latest
|
||||
rust: stable
|
||||
artifact_name: 'meli-linux-amd64'
|
||||
target: x86_64-unknown-linux-gnu
|
||||
- build: linux-arm64
|
||||
arch: arm64
|
||||
os: ubuntu-latest-arm64
|
||||
rust: stable
|
||||
artifact_name: 'meli-linux-arm64'
|
||||
target: aarch64-unknown-linux-gnu
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- id: os-deps
|
||||
name: install OS dependencies
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y libdbus-1-dev pkg-config mandoc libssl-dev
|
||||
- id: rustup-setup
|
||||
name: Install rustup and toolchains
|
||||
shell: bash
|
||||
run: |
|
||||
if ! command -v rustup &>/dev/null; then
|
||||
curl --proto '=https' --tlsv1.2 --retry 10 --retry-connrefused --location --silent --show-error --fail "https://sh.rustup.rs" | sh -s -- --default-toolchain none -y
|
||||
source "${HOME}/.cargo/env"
|
||||
echo "${CARGO_HOME:-$HOME/.cargo}/bin" >> $GITHUB_PATH
|
||||
echo "CARGO_HOME=${CARGO_HOME:-$HOME/.cargo}" >> $GITHUB_ENV
|
||||
rustup toolchain install --profile minimal ${{ matrix.rust }} --target ${{ matrix.target }}
|
||||
fi
|
||||
- name: Setup Rust target
|
||||
run: |
|
||||
mkdir -p "${{ env.CARGO_HOME }}"
|
||||
cat << EOF > "${{ env.CARGO_HOME }}"/config.toml
|
||||
[build]
|
||||
target = "${{ matrix.target }}"
|
||||
EOF
|
||||
- name: Build binary
|
||||
run: |
|
||||
VERSION=$(grep -m1 version meli/Cargo.toml | head -n1 | cut -d'"' -f 2 | head -n1)
|
||||
echo "VERSION=${VERSION}" >> $GITHUB_ENV
|
||||
make
|
||||
mkdir artifacts
|
||||
mv target/*/release/* target/ || true
|
||||
mv target/release/* target/ || true
|
||||
mv target/meli artifacts/meli-${VERSION}-${{ matrix.target }}
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ matrix.artifact_name }}-${{ env.VERSION }}
|
||||
path: artifacts/meli-${{ env.VERSION }}-${{ matrix.target }}
|
||||
if-no-files-found: error
|
||||
retention-days: 30
|
|
@ -1,71 +0,0 @@
|
|||
# SPDX-License-Identifier: EUPL-1.2
|
||||
name: Build .deb package
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_NET_RETRY: 10
|
||||
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
|
||||
RUSTFLAGS: "-D warnings -W unreachable-pub -W rust-2021-compatibility"
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
RUST_BACKTRACE: short
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
|
||||
jobs:
|
||||
build-debian:
|
||||
name: Create debian package
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
build: [linux-amd64, linux-arm64]
|
||||
include:
|
||||
- build: linux-amd64
|
||||
arch: amd64
|
||||
os: ubuntu-latest
|
||||
rust: stable
|
||||
artifact_name: 'linux-amd64'
|
||||
target: x86_64-unknown-linux-gnu
|
||||
- build: linux-arm64
|
||||
arch: arm64
|
||||
os: ubuntu-latest-arm64
|
||||
rust: stable
|
||||
artifact_name: 'linux-arm64'
|
||||
target: aarch64-unknown-linux-gnu
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- id: os-deps
|
||||
name: install OS dependencies
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y mandoc debhelper quilt build-essential
|
||||
- id: rustup-setup
|
||||
name: Install rustup and toolchains
|
||||
shell: bash
|
||||
run: |
|
||||
if ! command -v rustup &>/dev/null; then
|
||||
curl --proto '=https' --tlsv1.2 --retry 10 --retry-connrefused --location --silent --show-error --fail "https://sh.rustup.rs" | sh -s -- --default-toolchain none -y
|
||||
source "${HOME}/.cargo/env"
|
||||
echo "${CARGO_HOME:-$HOME/.cargo}/bin" >> $GITHUB_PATH
|
||||
echo "CARGO_HOME=${CARGO_HOME:-$HOME/.cargo}" >> $GITHUB_ENV
|
||||
rustup toolchain install --profile minimal ${{ matrix.rust }} --target ${{ matrix.target }}
|
||||
rustup default ${{ matrix.rust }}
|
||||
fi
|
||||
- name: Build binary
|
||||
run: |
|
||||
VERSION=$(grep -m1 version meli/Cargo.toml | head -n1 | cut -d'"' -f 2 | head -n1)
|
||||
make deb-dist
|
||||
mkdir artifacts
|
||||
echo "VERSION=${VERSION}" >> $GITHUB_ENV
|
||||
mv ../meli_*.deb artifacts/meli-${VERSION}-${{ matrix.artifact_name }}.deb
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: meli-${{env.VERSION}}-${{ matrix.artifact_name }}.deb
|
||||
path: artifacts/meli-${{env.VERSION}}-${{ matrix.artifact_name }}.deb
|
||||
if-no-files-found: error
|
||||
retention-days: 30
|
|
@ -1,26 +0,0 @@
|
|||
# SPDX-License-Identifier: EUPL-1.2
|
||||
name: Verify DCO
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Verify DCO signoff on commit messages
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
build: [linux-amd64, ]
|
||||
include:
|
||||
- build: linux-amd64
|
||||
os: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- id: check-dco
|
||||
shell: sh
|
||||
name: Check that commit messages end with a Signed-off-by git trailer
|
||||
run: |
|
||||
env GITHUB_BASE_REF="origin/${{env.GITHUB_BASE_REF}}" GITHUB_HEAD_REF="origin/${{env.GITHUB_HEAD_REF}}" sh ./.gitea/check_dco.sh
|
|
@ -1,140 +0,0 @@
|
|||
# SPDX-License-Identifier: EUPL-1.2
|
||||
name: Run cargo lints
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_NET_RETRY: 10
|
||||
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
|
||||
RUSTFLAGS: "-D warnings -W unreachable-pub -W rust-2021-compatibility -C debuginfo=0"
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
RUST_BACKTRACE: short
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
paths:
|
||||
- '.gitea/**'
|
||||
- 'melib/src/**'
|
||||
- 'melib/Cargo.toml'
|
||||
- 'meli/src/**'
|
||||
- 'meli/Cargo.toml'
|
||||
- 'Cargo.toml'
|
||||
- 'Cargo.lock'
|
||||
|
||||
jobs:
|
||||
lints:
|
||||
name: Run lints
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
build: [linux-amd64, ]
|
||||
include:
|
||||
- build: linux-amd64
|
||||
os: ubuntu-latest
|
||||
rust: stable
|
||||
target: x86_64-unknown-linux-gnu
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- id: os-deps
|
||||
name: install OS dependencies
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y libdbus-1-dev pkg-config mandoc libssl-dev
|
||||
- name: Find meli MSRV from meli/Cargo.toml.
|
||||
run: |
|
||||
echo MELI_MSRV=$(grep -m1 rust-version meli/Cargo.toml | head -n1 | cut -d'"' -f 2 | head -n1) >> $GITHUB_ENV
|
||||
printf "Rust MSRV is %s\n" $(grep -m1 rust-version meli/Cargo.toml | head -n1 | cut -d'"' -f 2 | head -n1)
|
||||
- name: Cache rustup
|
||||
id: cache-rustup
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.rustup/
|
||||
~/.cargo/env
|
||||
~/.cargo/config.toml
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
key: lints-workflow-${{ matrix.build }}-rustup
|
||||
- id: rustup-setup
|
||||
if: steps.cache-rustup.outputs.cache-hit != 'true'
|
||||
name: Install Rustup and toolchains
|
||||
shell: bash
|
||||
run: |
|
||||
if ! command -v rustup &>/dev/null; then
|
||||
curl --proto '=https' --tlsv1.2 --retry 10 --retry-connrefused --location --silent --show-error --fail "https://sh.rustup.rs" | sh -s -- --default-toolchain none -y
|
||||
source "${HOME}/.cargo/env"
|
||||
echo "${CARGO_HOME:-$HOME/.cargo}/bin" >> $GITHUB_PATH
|
||||
echo "CARGO_HOME=${CARGO_HOME:-$HOME/.cargo}" >> $GITHUB_ENV
|
||||
rustup toolchain install --profile minimal --component "rustfmt" --target "${{ matrix.target }}" -- "${{ env.MELI_MSRV }}"
|
||||
rustup component add rustfmt --toolchain ${{ env.MELI_MSRV }}-${{ matrix.target }}
|
||||
rustup toolchain install --profile minimal --component clippy,rustfmt --target "${{ matrix.target }}" -- "${{ matrix.rust }}"
|
||||
rustup component add rustfmt --toolchain ${{ matrix.rust }}-${{ matrix.target }}
|
||||
rustup default ${{ matrix.rust }}
|
||||
fi
|
||||
- name: Source .cargo/env
|
||||
shell: bash
|
||||
run: |
|
||||
source "${HOME}/.cargo/env"
|
||||
echo "${CARGO_HOME:-$HOME/.cargo}/bin" >> $GITHUB_PATH
|
||||
echo "CARGO_HOME=${CARGO_HOME:-$HOME/.cargo}" >> $GITHUB_ENV
|
||||
- name: Setup Rust target
|
||||
if: steps.cache-rustup.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
mkdir -p "${{ env.CARGO_HOME }}"
|
||||
cat << EOF > "${{ env.CARGO_HOME }}"/config.toml
|
||||
[build]
|
||||
target = "${{ matrix.target }}"
|
||||
EOF
|
||||
- name: Add lint dependencies
|
||||
if: steps.cache-rustup.outputs.cache-hit != 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
cargo install --version 0.15.1 --target "${{ matrix.target }}" cargo-msrv
|
||||
# "This package is currently implemented using rust-analyzer internals, so cannot be published on crates.io."
|
||||
RUSTFLAGS="" cargo install --locked --target "${{ matrix.target }}" --git https://github.com/dcchut/cargo-derivefmt --rev 95da8eee343de4adb25850893873b979258aed7f --bin cargo-derivefmt
|
||||
- name: Restore build artifacts cache in target dir
|
||||
id: cache-deps
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: target/
|
||||
key: workflow-${{ matrix.build }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
- name: clippy
|
||||
if: success() || failure()
|
||||
run: |
|
||||
source "${HOME}/.cargo/env"
|
||||
make -f .gitea/Makefile.lint clippy
|
||||
- if: steps.cache-deps.outputs.cache-hit != 'true'
|
||||
name: Save build artifacts in target dir
|
||||
id: save-cache-deps
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: target/
|
||||
key: workflow-${{ matrix.build }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
- name: cargo-msrv verify melib MSRV
|
||||
if: success() || failure()
|
||||
run: |
|
||||
source "${HOME}/.cargo/env"
|
||||
make -f ./.gitea/Makefile.lint cargo-msrv
|
||||
- name: rustfmt
|
||||
if: success() || failure()
|
||||
run: |
|
||||
source "${HOME}/.cargo/env"
|
||||
make -f .gitea/Makefile.lint rustfmt
|
||||
- name: cargo-derivefmt melib
|
||||
if: success() || failure()
|
||||
run: |
|
||||
source "${HOME}/.cargo/env"
|
||||
make -f .gitea/Makefile.lint cargo-derivefmt-melib
|
||||
- name: cargo-derivefmt meli
|
||||
if: success() || failure()
|
||||
run: |
|
||||
source "${HOME}/.cargo/env"
|
||||
make -f .gitea/Makefile.lint cargo-derivefmt-meli
|
||||
- name: cargo-derivefmt tools
|
||||
if: success() || failure()
|
||||
run: |
|
||||
source "${HOME}/.cargo/env"
|
||||
make -f .gitea/Makefile.lint cargo-derivefmt-tools
|
|
@ -1,98 +0,0 @@
|
|||
# SPDX-License-Identifier: EUPL-1.2
|
||||
name: Cargo manifest lints
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_NET_RETRY: 10
|
||||
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
|
||||
RUSTFLAGS: "-D warnings -W unreachable-pub -W rust-2021-compatibility -C debuginfo=0"
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
RUST_BACKTRACE: short
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
paths:
|
||||
- '.gitea/**'
|
||||
- 'melib/Cargo.toml'
|
||||
- 'meli/Cargo.toml'
|
||||
- 'fuzz/Cargo.toml'
|
||||
- 'tool/Cargo.toml'
|
||||
- 'Cargo.toml'
|
||||
- 'Cargo.lock'
|
||||
- '.cargo/config.toml'
|
||||
|
||||
jobs:
|
||||
manifest_lint:
|
||||
name: Run Cargo manifest etc lints
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
build: [linux-amd64, ]
|
||||
include:
|
||||
- build: linux-amd64
|
||||
os: ubuntu-latest
|
||||
rust: stable
|
||||
target: x86_64-unknown-linux-gnu
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- id: os-deps
|
||||
name: install OS dependencies
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y mandoc
|
||||
- name: Cache rustup
|
||||
id: cache-rustup
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.rustup/
|
||||
~/.cargo/env
|
||||
~/.cargo/config.toml
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
key: manifest_lints-workflow-${{ matrix.build }}-rustup
|
||||
- id: rustup-setup
|
||||
name: Install Rustup and toolchains
|
||||
shell: bash
|
||||
run: |
|
||||
if ! command -v rustup &>/dev/null; then
|
||||
curl --proto '=https' --tlsv1.2 --retry 10 --retry-connrefused --location --silent --show-error --fail "https://sh.rustup.rs" | sh -s -- --default-toolchain none -y
|
||||
source "${HOME}/.cargo/env"
|
||||
echo "${CARGO_HOME:-$HOME/.cargo}/bin" >> $GITHUB_PATH
|
||||
echo "CARGO_HOME=${CARGO_HOME:-$HOME/.cargo}" >> $GITHUB_ENV
|
||||
rustup toolchain install --profile minimal --component "rustfmt" --target "${{ matrix.target }}" -- "${{ matrix.rust }}"
|
||||
rustup component add rustfmt --toolchain ${{ matrix.rust }}-${{ matrix.target }}
|
||||
rustup default ${{ matrix.rust }}
|
||||
fi
|
||||
- name: Source .cargo/env
|
||||
shell: bash
|
||||
run: |
|
||||
source "${HOME}/.cargo/env"
|
||||
echo "${CARGO_HOME:-$HOME/.cargo}/bin" >> $GITHUB_PATH
|
||||
echo "CARGO_HOME=${CARGO_HOME:-$HOME/.cargo}" >> $GITHUB_ENV
|
||||
- name: Setup Rust target
|
||||
if: steps.cache-rustup.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
mkdir -p "${{ env.CARGO_HOME }}"
|
||||
cat << EOF > "${{ env.CARGO_HOME }}"/config.toml
|
||||
[build]
|
||||
target = "${{ matrix.target }}"
|
||||
EOF
|
||||
- name: Add manifest lint dependencies
|
||||
if: steps.cache-rustup.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
source "${HOME}/.cargo/env"
|
||||
cargo install --quiet --version 1.0.9 --target "${{ matrix.target }}" cargo-sort
|
||||
- name: cargo-sort
|
||||
if: success() || failure()
|
||||
run: |
|
||||
source "${HOME}/.cargo/env"
|
||||
make -f ./.gitea/Makefile.manifest-lint cargo-sort
|
||||
- name: Check debian/changelog is up-to-date.
|
||||
if: success() || failure()
|
||||
run: |
|
||||
make -f ./.gitea/Makefile.manifest-lint check-debian-changelog
|
3
.gitignore
vendored
|
@ -15,6 +15,3 @@ debian/debhelper-build-stamp
|
|||
debian/files
|
||||
debian/meli.substvars
|
||||
debian/meli/
|
||||
|
||||
# CLion IDE
|
||||
.idea
|
8
.mailmap
|
@ -1,8 +0,0 @@
|
|||
# Copyright (c) 2024 Manos Pitsidianakis <manos@pitsidianak.is>
|
||||
# Licensed under the EUPL-1.2-or-later.
|
||||
#
|
||||
# You may obtain a copy of the Licence at:
|
||||
# https://joinup.ec.europa.eu/software/page/eupl
|
||||
#
|
||||
# SPDX-License-Identifier: EUPL-1.2
|
||||
Manos Pitsidianakis <manos@pitsidianak.is> <el13635@mail.ntua.gr>
|
91
BUILD.md
|
@ -1,91 +0,0 @@
|
|||
# Build `meli`
|
||||
|
||||
For a quick start, build and install locally:
|
||||
|
||||
```sh
|
||||
PREFIX=~/.local make install
|
||||
```
|
||||
|
||||
Available subcommands for `make` are listed with `make help`.
|
||||
The Makefile *should* be POSIX portable and not require a specific `make` version.
|
||||
|
||||
`meli` requires rust version 1.70.0 or later and rust's package manager, Cargo.
|
||||
Information on how to get it on your system can be found here: <https://doc.rust-lang.org/cargo/getting-started/installation.html>
|
||||
|
||||
With Cargo available, the project can be built with `make` and the resulting binary will then be found under `target/release/meli`.
|
||||
Run `make install` to install the binary and man pages.
|
||||
This requires root, so I suggest you override the default paths and install it in your `$HOME`: `make PREFIX=${HOME}/.local install`.
|
||||
|
||||
You can build and run `meli` with one command: `cargo run --release`.
|
||||
|
||||
## Build features
|
||||
|
||||
Some functionality is held behind "feature gates", or compile-time flags.
|
||||
|
||||
Cargo features for `meli` are documented in its [`README.md`](./meli/README.md) file.
|
||||
|
||||
Cargo features for `melib` are documented in its [`README.md`](./melib/README.md) file.
|
||||
|
||||
The following list explains each feature's purpose:
|
||||
|
||||
- `gpgme` enables GPG support via `libgpgme` (on by default)
|
||||
- `dbus-notifications` enables showing notifications using `dbus` (on by default)
|
||||
- `notmuch` provides support for using a notmuch database as a mail backend (on by default)
|
||||
- `jmap` provides support for connecting to a jmap server and use it as a mail backend (on by default)
|
||||
- `sqlite3` provides support for builting fast search indexes in local sqlite3 databases (on by default)
|
||||
- `cli-docs` includes the manpage documentation compiled by either `mandoc` or `man` binary to plain text in `meli`'s command line. Embedded documentation can be viewed with the subcommand `meli man [PAGE]` (on by default).
|
||||
- `static` and `*-static` bundle C libraries in dependencies so that you don't need them installed in your system (on by default).
|
||||
|
||||
## Build Debian package (*deb*)
|
||||
|
||||
Building with Debian's packaged cargo might require the installation of these two packages: `librust-openssl-sys-dev librust-libdbus-sys-dev`
|
||||
|
||||
A `*.deb` package can be built with `make deb-dist`
|
||||
|
||||
## Using notmuch
|
||||
|
||||
To use the optional notmuch backend feature, you must have `libnotmuch5` installed in your system.
|
||||
In Debian-like systems, install the `libnotmuch5` packages.
|
||||
`meli` detects the library's presence on runtime.
|
||||
If it is not detected, you can use the `library_file_path` setting on your notmuch account to specify the absolute path of the library.
|
||||
|
||||
## Using GPG
|
||||
|
||||
To use the optional gpg feature, you must have `libgpgme` installed in your system.
|
||||
In Debian-like systems, install the `libgpgme11` package.
|
||||
`meli` detects the library's presence on runtime.
|
||||
|
||||
## Building and running on Android with `termux`
|
||||
|
||||
This is not a supported or stable setup so caveat emptor.
|
||||
|
||||
At the time of writing this, Android is not a stable Rust target.
|
||||
The packaged Rust from `termux` will be used.
|
||||
|
||||
The following steps should suffice to build and run `meli` on `termux`:
|
||||
|
||||
```console
|
||||
$ pkg install rust perl make m4 man
|
||||
$ cargo install meli # ensure .cargo/bin is in your PATH
|
||||
```
|
||||
|
||||
Exporting `EDITOR` and `PAGER` might be useful.
|
||||
|
||||
## Development
|
||||
|
||||
Development builds can be built and/or run with
|
||||
|
||||
```
|
||||
cargo build
|
||||
cargo run
|
||||
```
|
||||
|
||||
There is a debug/tracing log feature that can be enabled by using the flag `--feature debug-tracing` after uncommenting the features in `Cargo.toml`.
|
||||
The logs are printed in stderr when the env var `MELI_DEBUG_STDERR` is defined, thus you can run `meli` with a redirection (i.e `2> log`).
|
||||
|
||||
To trace network and protocol communications you can enable the following features:
|
||||
|
||||
- `imap-trace`
|
||||
- `jmap-trace`
|
||||
- `nntp-trace`
|
||||
- `smtp-trace`
|
1288
CHANGELOG.md
2009
Cargo.lock
generated
95
Cargo.toml
|
@ -1,10 +1,74 @@
|
|||
[workspace]
|
||||
resolver = "2"
|
||||
[package]
|
||||
name = "meli"
|
||||
version = "0.7.2"
|
||||
authors = ["Manos Pitsidianakis <el13635@mail.ntua.gr>"]
|
||||
edition = "2018"
|
||||
|
||||
members = [
|
||||
"meli",
|
||||
"melib",
|
||||
]
|
||||
license = "GPL-3.0-or-later"
|
||||
readme = "README.md"
|
||||
description = "terminal mail client"
|
||||
homepage = "https://meli.delivery"
|
||||
repository = "https://git.meli.delivery/meli/meli.git"
|
||||
keywords = ["mail", "mua", "maildir", "terminal", "imap"]
|
||||
categories = ["command-line-utilities", "email"]
|
||||
default-run = "meli"
|
||||
|
||||
[[bin]]
|
||||
name = "meli"
|
||||
path = "src/main.rs"
|
||||
|
||||
[lib]
|
||||
name = "meli"
|
||||
path = "src/lib.rs"
|
||||
|
||||
#[[bin]]
|
||||
#name = "managesieve-meli"
|
||||
#path = "src/managesieve.rs"
|
||||
|
||||
#[[bin]]
|
||||
#name = "async"
|
||||
#path = "src/async.rs"
|
||||
|
||||
[dependencies]
|
||||
async-task = "^4.2.0"
|
||||
bincode = { version = "^1.3.0", default-features = false }
|
||||
bitflags = "1.0"
|
||||
crossbeam = { version = "^0.8" }
|
||||
flate2 = { version = "1.0.16", optional = true }
|
||||
futures = "0.3.5"
|
||||
indexmap = { version = "^1.6", features = ["serde-1", ] }
|
||||
libc = { version = "0.2.125", default-features = false, features = ["extra_traits",] }
|
||||
linkify = { version = "^0.8", default-features = false }
|
||||
melib = { path = "melib", version = "0.7.2" }
|
||||
nix = { version = "^0.24", default-features = false }
|
||||
notify = { version = "4.0.1", default-features = false } # >:c
|
||||
num_cpus = "1.12.0"
|
||||
pcre2 = { version = "0.2.3", optional = true }
|
||||
|
||||
serde = "1.0.71"
|
||||
serde_derive = "1.0.71"
|
||||
serde_json = "1.0"
|
||||
signal-hook = { version = "^0.3", default-features = false }
|
||||
signal-hook-registry = { version = "1.2.0", default-features = false }
|
||||
smallvec = { version = "^1.5.0", features = ["serde", ] }
|
||||
structopt = { version = "0.3.14", default-features = false }
|
||||
svg_crate = { version = "^0.10", optional = true, package = "svg" }
|
||||
termion = { version = "1.5.1", default-features = false }
|
||||
toml = { version = "0.5.6", default-features = false, features = ["preserve_order", ] }
|
||||
unicode-segmentation = "1.2.1" # >:c
|
||||
xdg = "2.1.0"
|
||||
|
||||
[target.'cfg(target_os="linux")'.dependencies]
|
||||
notify-rust = { version = "^4", default-features = false, features = ["dbus", ], optional = true }
|
||||
|
||||
[build-dependencies]
|
||||
flate2 = { version = "1.0.16", optional = true }
|
||||
proc-macro2 = "1.0.37"
|
||||
quote = "^1.0"
|
||||
syn = { version = "1.0.92", features = [] }
|
||||
|
||||
[dev-dependencies]
|
||||
regex = "1"
|
||||
|
||||
[profile.release]
|
||||
lto = "fat"
|
||||
|
@ -12,3 +76,22 @@ codegen-units = 1
|
|||
opt-level = "s"
|
||||
debug = false
|
||||
strip = true
|
||||
|
||||
[workspace]
|
||||
members = ["melib", "tools", ]
|
||||
|
||||
[features]
|
||||
default = ["sqlite3", "notmuch", "regexp", "smtp", "dbus-notifications", "gpgme"]
|
||||
notmuch = ["melib/notmuch_backend", ]
|
||||
jmap = ["melib/jmap_backend",]
|
||||
sqlite3 = ["melib/sqlite3"]
|
||||
smtp = ["melib/smtp"]
|
||||
regexp = ["pcre2"]
|
||||
dbus-notifications = ["notify-rust",]
|
||||
cli-docs = ["flate2"]
|
||||
svgscreenshot = ["svg_crate"]
|
||||
gpgme = ["melib/gpgme"]
|
||||
|
||||
# Print tracing logs as meli runs in stderr
|
||||
# enable for debug tracing logs: build with --features=debug-tracing
|
||||
debug-tracing = ["melib/debug-tracing", ]
|
||||
|
|
21
Cross.toml
|
@ -1,21 +0,0 @@
|
|||
[target.aarch64-unknown-linux-gnu]
|
||||
# Build with -static features.
|
||||
pre-build = [
|
||||
"export DEBIAN_FRONTEND=noninteractive ",
|
||||
"dpkg --add-architecture $CROSS_DEB_ARCH",
|
||||
"apt-get update -y",
|
||||
"""
|
||||
apt-get install --assume-yes \
|
||||
pkg-config \
|
||||
libdbus-1-dev \
|
||||
libdbus-1-dev:$CROSS_DEB_ARCH \
|
||||
librust-libdbus-sys-dev \
|
||||
librust-libdbus-sys-dev:$CROSS_DEB_ARCH \
|
||||
librust-openssl-sys-dev \
|
||||
librust-openssl-sys-dev:$CROSS_DEB_ARCH \
|
||||
libsqlite3-dev:$CROSS_DEB_ARCH \
|
||||
libssl-dev \
|
||||
libssl-dev:$CROSS_DEB_ARCH \
|
||||
sqlite3:$CROSS_DEB_ARCH
|
||||
""",
|
||||
]
|
|
@ -1,67 +0,0 @@
|
|||
# Development
|
||||
|
||||
Code style follows the `rustfmt.toml` file.
|
||||
|
||||
## Trace logs
|
||||
|
||||
Enable trace logs to `stderr` with:
|
||||
|
||||
```sh
|
||||
export MELI_DEBUG_STDERR=yes
|
||||
```
|
||||
|
||||
This means you will have to to redirect `stderr` to a file like `meli 2> trace.log`.
|
||||
|
||||
Tracing is opt-in by build features:
|
||||
|
||||
```sh
|
||||
cargo build --features=debug-tracing,imap-trace,smtp-trace
|
||||
```
|
||||
|
||||
## use `.git-blame-ignore-revs` file _optional_
|
||||
|
||||
Use this file to ignore formatting commits from `git-blame`.
|
||||
It needs to be set up per project because `git-blame` will fail if it's missing.
|
||||
|
||||
```sh
|
||||
git config blame.ignoreRevsFile .git-blame-ignore-revs
|
||||
```
|
||||
|
||||
## Formatting with `rustfmt`
|
||||
|
||||
```sh
|
||||
make fmt
|
||||
```
|
||||
|
||||
## Linting with `clippy`
|
||||
|
||||
```sh
|
||||
make lint
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```sh
|
||||
make test
|
||||
```
|
||||
|
||||
How to run specific tests:
|
||||
|
||||
```sh
|
||||
cargo test -p {melib, meli} (-- --nocapture) (--test test_name)
|
||||
```
|
||||
|
||||
## Profiling
|
||||
|
||||
```sh
|
||||
perf record -g target/debug/meli
|
||||
perf script | stackcollapse-perf | rust-unmangle | flamegraph > perf.svg
|
||||
```
|
||||
<!-- -->
|
||||
<!-- ## Running fuzz targets -->
|
||||
<!-- -->
|
||||
<!-- Note: `cargo-fuzz` requires the nightly toolchain. -->
|
||||
<!-- -->
|
||||
<!-- ```sh -->
|
||||
<!-- cargo +nightly fuzz run envelope_parse -- -dict=fuzz/envelope_tokens.dict -->
|
||||
<!-- ``` -->
|
201
Makefile
|
@ -18,14 +18,6 @@
|
|||
# along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
.POSIX:
|
||||
.SUFFIXES:
|
||||
CARGO_TARGET_DIR ?= target
|
||||
CARGO_BIN ?= cargo
|
||||
TAGREF_BIN ?= tagref
|
||||
CARGO_ARGS ?=
|
||||
RUSTFLAGS ?= -D warnings -W unreachable-pub -W rust-2021-compatibility
|
||||
CARGO_SORT_BIN = cargo-sort
|
||||
CARGO_HACK_BIN = cargo-hack
|
||||
PRINTF := `command -v printf`
|
||||
|
||||
# Options
|
||||
PREFIX ?= /usr/local
|
||||
|
@ -33,16 +25,20 @@ EXPANDED_PREFIX := `cd ${PREFIX} && pwd -P`
|
|||
BINDIR ?= ${EXPANDED_PREFIX}/bin
|
||||
MANDIR ?= ${EXPANDED_PREFIX}/share/man
|
||||
|
||||
# Installation parameters
|
||||
DOCS_SUBDIR ?= meli/docs/
|
||||
MANPAGES ?= meli.1 meli.conf.5 meli-themes.5 meli.7
|
||||
FEATURES != [ -z "$${MELI_FEATURES}" ] && ($(PRINTF) -- '--all-features') || ($(PRINTF) -- '--features %s' "$${MELI_FEATURES}")
|
||||
CARGO_TARGET_DIR ?= target
|
||||
MIN_RUSTC ?= 1.39.0
|
||||
CARGO_BIN ?= cargo
|
||||
CARGO_ARGS ?=
|
||||
|
||||
MANPATHS != ACCUM="";for m in `manpath 2> /dev/null | tr ':' ' '`; do if [ -d "$${m}" ]; then REAL_PATH=`cd $${m} && pwd` ACCUM="$${ACCUM}:$${REAL_PATH}";fi;done;echo $${ACCUM}'\c' | sed 's/^://'
|
||||
VERSION = `grep -m1 version meli/Cargo.toml | head -n1 | cut -d'"' -f 2 | head -n1`
|
||||
MIN_RUSTC = `grep -m1 rust-version meli/Cargo.toml | head -n1 | cut -d'"' -f 2 | head -n1`
|
||||
GIT_COMMIT = `git show-ref -s --abbrev HEAD`
|
||||
DATE = `date -I`
|
||||
# Installation parameters
|
||||
DOCS_SUBDIR ?= docs/
|
||||
MANPAGES ?= meli.1 meli.conf.5 meli-themes.5
|
||||
FEATURES ?= --features "${MELI_FEATURES}"
|
||||
|
||||
MANPATHS != ACCUM="";for m in `manpath 2> /dev/null | tr ':' ' '`; do if [ -d "$${m}" ]; then REAL_PATH=`cd $${m} && pwd` ACCUM="$${ACCUM}:$${REAL_PATH}";fi;done;echo -n $${ACCUM} | sed 's/^://'
|
||||
VERSION != sed -n "s/^version\s*=\s*\"\(.*\)\"/\1/p" Cargo.toml
|
||||
GIT_COMMIT != git show-ref -s --abbrev HEAD
|
||||
DATE != date -I
|
||||
|
||||
# Output parameters
|
||||
BOLD ?= `[ -z $${TERM} ] && echo "" || tput bold`
|
||||
|
@ -51,21 +47,19 @@ ANSI_RESET ?= `[ -z $${TERM} ] && echo "" || tput sgr0`
|
|||
CARGO_COLOR ?= `[ -z $${NO_COLOR+x} ] && echo "" || echo "--color=never "`
|
||||
RED ?= `[ -z $${NO_COLOR+x} ] && ([ -z $${TERM} ] && echo "" || tput setaf 1) || echo ""`
|
||||
GREEN ?= `[ -z $${NO_COLOR+x} ] && ([ -z $${TERM} ] && echo "" || tput setaf 2) || echo ""`
|
||||
YELLOW ?= `[ -z $${NO_COLOR+x} ] && ([ -z $${TERM} ] && echo "" || tput setaf 3) || echo ""`
|
||||
|
||||
.PHONY: meli
|
||||
meli: check-deps
|
||||
@echo ${CARGO_BIN} build ${CARGO_ARGS} ${CARGO_COLOR}--target-dir=\""${CARGO_TARGET_DIR}"\" ${FEATURES} --release --bin meli
|
||||
@${CARGO_BIN} build ${CARGO_ARGS} ${CARGO_COLOR}--target-dir="${CARGO_TARGET_DIR}" ${FEATURES} --release --bin meli
|
||||
@${CARGO_BIN} build ${CARGO_ARGS} ${CARGO_COLOR}--target-dir="${CARGO_TARGET_DIR}" ${FEATURES} --release
|
||||
|
||||
.PHONY: help
|
||||
help:
|
||||
@echo "For a quick start, build and install locally:\n\n${BOLD}${GREEN}make PREFIX=~/.local install${ANSI_RESET}\n"
|
||||
@echo "For a quick start, build and install locally:\n ${BOLD}${GREEN}PREFIX=~/.local make install${ANSI_RESET}\n"
|
||||
@echo "Available subcommands:"
|
||||
@echo " - ${BOLD}meli${ANSI_RESET} (builds meli with optimizations in \$$CARGO_TARGET_DIR)"
|
||||
@echo " - ${BOLD}install${ANSI_RESET} (installs binary in \$$BINDIR and documentation to \$$MANDIR)"
|
||||
@echo " - ${BOLD}uninstall${ANSI_RESET}"
|
||||
@echo "\nSecondary subcommands:"
|
||||
@echo "Secondary subcommands:"
|
||||
@echo " - ${BOLD}clean${ANSI_RESET} (cleans build artifacts)"
|
||||
@echo " - ${BOLD}check-deps${ANSI_RESET} (checks dependencies)"
|
||||
@echo " - ${BOLD}install-bin${ANSI_RESET} (installs binary to \$$BINDIR)"
|
||||
|
@ -76,65 +70,31 @@ help:
|
|||
@echo " - ${BOLD}deb-dist${ANSI_RESET} (builds debian package in the parent directory)"
|
||||
@echo " - ${BOLD}distclean${ANSI_RESET} (cleans distribution build artifacts)"
|
||||
@echo " - ${BOLD}build-rustdoc${ANSI_RESET} (builds rustdoc documentation for all packages in \$$CARGO_TARGET_DIR)"
|
||||
@echo ""
|
||||
@echo "ENVIRONMENT variables of interest:"
|
||||
@$(PRINTF) "* MELI_FEATURES "
|
||||
@[ -z $${MELI_FEATURES+x} ] && echo "unset" || echo "= ${UNDERLINE}"$${MELI_FEATURES}${ANSI_RESET}
|
||||
@$(PRINTF) "* PREFIX "
|
||||
@[ -z ${EXPANDED_PREFIX} ] && echo "unset" || echo "= ${UNDERLINE}"${EXPANDED_PREFIX}${ANSI_RESET}
|
||||
@$(PRINTF) "* BINDIR = %s\n" "${UNDERLINE}${BINDIR}${ANSI_RESET}"
|
||||
@$(PRINTF) "* MANDIR "
|
||||
@[ -z ${MANDIR} ] && echo "unset" || echo "= ${UNDERLINE}"${MANDIR}${ANSI_RESET}
|
||||
@$(PRINTF) "* MANPATH = "
|
||||
@[ $${MANPATH+x} ] && echo ${UNDERLINE}$${MANPATH}${ANSI_RESET} || echo "unset"
|
||||
@echo "\nENVIRONMENT variables of interest:"
|
||||
@echo "* PREFIX = ${UNDERLINE}${EXPANDED_PREFIX}${ANSI_RESET}"
|
||||
@echo -n "* MELI_FEATURES = ${UNDERLINE}"
|
||||
@[ -z $${MELI_FEATURES+x} ] && echo -n "unset" || echo -n ${MELI_FEATURES}
|
||||
@echo ${ANSI_RESET}
|
||||
@echo "* BINDIR = ${UNDERLINE}${BINDIR}${ANSI_RESET}"
|
||||
@echo "* MANDIR = ${UNDERLINE}${MANDIR}${ANSI_RESET}"
|
||||
@echo -n "* MANPATH = ${UNDERLINE}"
|
||||
@[ $${MANPATH+x} ] && echo -n $${MANPATH} || echo -n "unset"
|
||||
@echo ${ANSI_RESET}
|
||||
@echo "* (cleaned) output of manpath(1) = ${UNDERLINE}${MANPATHS}${ANSI_RESET}"
|
||||
@$(PRINTF) "* NO_MAN "
|
||||
@[ $${NO_MAN+x} ] && echo "set" || echo "unset"
|
||||
@$(PRINTF) "* NO_COLOR "
|
||||
@([ $${NO_COLOR+x} ] && [ "$${NO_COLOR}" != "" ] && echo "set") || echo "unset"
|
||||
@echo -n "* NO_MAN ${UNDERLINE}"
|
||||
@[ $${NO_MAN+x} ] && echo -n "set" || echo -n "unset"
|
||||
@echo ${ANSI_RESET}
|
||||
@echo -n "* NO_COLOR ${UNDERLINE}"
|
||||
@[ $${NO_COLOR+x} ] && echo -n "set" || echo -n "unset"
|
||||
@echo ${ANSI_RESET}
|
||||
@echo "* CARGO_BIN = ${UNDERLINE}${CARGO_BIN}${ANSI_RESET}"
|
||||
@$(PRINTF) "* CARGO_ARGS "
|
||||
@([ -z "${CARGO_ARGS}" ] && echo "unset") || echo = ${UNDERLINE}${CARGO_ARGS}${ANSI_RESET}
|
||||
@$(PRINTF) "* RUSTFLAGS = "
|
||||
@([ -z "${RUSTFLAGS}" ] && echo "unset") || echo = ${UNDERLINE}${RUSTFLAGS}${ANSI_RESET}
|
||||
@$(PRINTF) "* AUTHOR (for deb-dist) "
|
||||
@[ -z $${AUTHOR+x} ] && echo "unset" || echo "= ${UNDERLINE}"$${AUTHOR}${ANSI_RESET}
|
||||
@echo "* CARGO_ARGS = ${UNDERLINE}${CARGO_ARGS}${ANSI_RESET}"
|
||||
@echo "* MIN_RUSTC = ${UNDERLINE}${MIN_RUSTC}${ANSI_RESET}"
|
||||
@echo "* VERSION = ${UNDERLINE}${VERSION}${ANSI_RESET}"
|
||||
@echo "* GIT_COMMIT = ${UNDERLINE}${GIT_COMMIT}${ANSI_RESET}"
|
||||
@echo "* CARGO_TARGET_DIR = ${CARGO_TARGET_DIR}"
|
||||
@echo ""
|
||||
@echo "Built-in/binary utilities"
|
||||
@echo "* PRINTF = ${UNDERLINE}${PRINTF}${ANSI_RESET}"
|
||||
@#@echo "* CARGO_COLOR = ${CARGO_COLOR}"
|
||||
|
||||
.PHONY: check
|
||||
check: check-tagrefs
|
||||
@echo RUSTFLAGS=\"'${RUSTFLAGS}'\" ${CARGO_BIN} check ${CARGO_ARGS} ${CARGO_COLOR}--target-dir=\""${CARGO_TARGET_DIR}"\" ${FEATURES} --all --tests --examples --benches --bins
|
||||
@RUSTFLAGS='${RUSTFLAGS}' ${CARGO_BIN} check ${CARGO_ARGS} ${CARGO_COLOR}--target-dir="${CARGO_TARGET_DIR}" ${FEATURES} --all --tests --examples --benches --bins
|
||||
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
$(CARGO_BIN) +nightly fmt --all || $(CARGO_BIN) fmt --all
|
||||
@OUT=$$($(CARGO_SORT_BIN) melib -w 2>&1 && $(CARGO_SORT_BIN) meli -w 2>&1) || $(PRINTF) "WARN: %s cargo-sort failed or binary not found in PATH.\n" "$$OUT"
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
@echo RUSTFLAGS=\"'${RUSTFLAGS}'\" $(CARGO_BIN) clippy --no-deps ${FEATURES} --all --tests --examples --benches --bins
|
||||
@RUSTFLAGS='${RUSTFLAGS}' $(CARGO_BIN) clippy --no-deps ${FEATURES} --all --tests --examples --benches --bins
|
||||
|
||||
.PHONY: test
|
||||
test: test-docs
|
||||
@echo RUSTFLAGS=\"'${RUSTFLAGS}'\" ${CARGO_BIN} test ${CARGO_ARGS} ${CARGO_COLOR}--target-dir=\""${CARGO_TARGET_DIR}"\" ${FEATURES} --all --tests --examples --benches --bins
|
||||
@RUSTFLAGS='${RUSTFLAGS}' ${CARGO_BIN} test ${CARGO_ARGS} ${CARGO_COLOR}--target-dir="${CARGO_TARGET_DIR}" ${FEATURES} --all --tests --examples --benches --bins
|
||||
|
||||
.PHONY: test-docs
|
||||
test-docs:
|
||||
@echo RUSTFLAGS=\"'${RUSTFLAGS}'\" ${CARGO_BIN} test ${CARGO_ARGS} ${CARGO_COLOR}--target-dir=\""${CARGO_TARGET_DIR}"\" ${FEATURES} --all --doc
|
||||
@RUSTFLAGS='${RUSTFLAGS}' ${CARGO_BIN} test ${CARGO_ARGS} ${CARGO_COLOR}--target-dir="${CARGO_TARGET_DIR}" ${FEATURES} --all --doc
|
||||
|
||||
.PHONY: test-feature-permutations
|
||||
test-feature-permutations:
|
||||
$(CARGO_HACK_BIN) hack --feature-powerset
|
||||
check:
|
||||
@${CARGO_BIN} test ${CARGO_ARGS} ${CARGO_COLOR}--target-dir="${CARGO_TARGET_DIR}" --workspace
|
||||
|
||||
.PHONY: check-deps
|
||||
check-deps:
|
||||
|
@ -147,116 +107,67 @@ clean:
|
|||
-rm -rf ./${CARGO_TARGET_DIR}/
|
||||
|
||||
.PHONY: distclean
|
||||
distclean:
|
||||
rm -f meli-${VERSION}.tar.gz
|
||||
rm -rf .pc # rm debian stuff
|
||||
distclean: clean
|
||||
@rm -f meli-${VERSION}.tar.gz
|
||||
|
||||
.PHONY: uninstall
|
||||
uninstall:
|
||||
rm -f $(DESTDIR)${BINDIR}/meli
|
||||
for MANPAGE in ${MANPAGES}; do \
|
||||
SECTION=`echo $${MANPAGE} | rev | cut -d "." -f 1`; \
|
||||
MANPAGEPATH="${DESTDIR}${MANDIR}/man$${SECTION}/$${MANPAGE}.gz"; \
|
||||
rm -f "$${MANAGEPATH}"
|
||||
; done
|
||||
-rm $(DESTDIR)${MANDIR}/man1/meli.1.gz
|
||||
-rm $(DESTDIR)${MANDIR}/man5/meli.conf.5.gz
|
||||
-rm $(DESTDIR)${MANDIR}/man5/meli-themes.5.gz
|
||||
|
||||
.PHONY: install-doc
|
||||
install-doc:
|
||||
@(if [ -z $${NO_MAN+x} ]; then \
|
||||
mkdir -p $(DESTDIR)${MANDIR}/man1 ; \
|
||||
mkdir -p $(DESTDIR)${MANDIR}/man5 ; \
|
||||
echo " - ${BOLD}Installing manpages to ${ANSI_RESET}${DESTDIR}${MANDIR}:" ; \
|
||||
for MANPAGE in ${MANPAGES}; do \
|
||||
SECTION=`echo $${MANPAGE} | rev | cut -d "." -f 1`; \
|
||||
mkdir -p $(DESTDIR)${MANDIR}/man$${SECTION} ; \
|
||||
MANPAGEPATH=${DESTDIR}${MANDIR}/man$${SECTION}/$${MANPAGE}.gz; \
|
||||
echo " * installing $${MANPAGE} → ${GREEN}$${MANPAGEPATH}${ANSI_RESET}"; \
|
||||
gzip -n < ${DOCS_SUBDIR}$${MANPAGE} > $${MANPAGEPATH} \
|
||||
; done ; \
|
||||
(case ":${MANPATHS}:" in \
|
||||
*:${DESTDIR}${MANDIR}:*) echo "\c";; \
|
||||
*:${DESTDIR}${MANDIR}:*) echo -n "";; \
|
||||
*) echo "\n${RED}${BOLD}WARNING${ANSI_RESET}: ${UNDERLINE}Path ${DESTDIR}${MANDIR} is not contained in your MANPATH variable or the output of \`manpath\` command.${ANSI_RESET} \`man\` might fail finding the installed manpages. Consider adding it if necessary.\nMANPATH variable / output of \`manpath\`: ${MANPATHS}" ;; \
|
||||
esac) ; \
|
||||
else echo "NO_MAN is defined, so no documentation is going to be installed." ; fi)
|
||||
|
||||
.PHONY: install-bin
|
||||
install-bin: meli
|
||||
mkdir -p $(DESTDIR)${BINDIR}
|
||||
@mkdir -p $(DESTDIR)${BINDIR}
|
||||
@echo " - ${BOLD}Installing binary to ${ANSI_RESET}${GREEN}${DESTDIR}${BINDIR}/meli${ANSI_RESET}"
|
||||
@case ":${PATH}:" in \
|
||||
*:${DESTDIR}${BINDIR}:*) echo "\n";; \
|
||||
*:${DESTDIR}${BINDIR}:*) echo -n "";; \
|
||||
*) echo "\n${RED}${BOLD}WARNING${ANSI_RESET}: ${UNDERLINE}Path ${DESTDIR}${BINDIR} is not contained in your PATH variable.${ANSI_RESET} Consider adding it if necessary.\nPATH variable: ${PATH}";; \
|
||||
esac
|
||||
mkdir -p $(DESTDIR)${BINDIR}
|
||||
rm -f $(DESTDIR)${BINDIR}/meli
|
||||
cp ./${CARGO_TARGET_DIR}/release/meli $(DESTDIR)${BINDIR}/meli
|
||||
chmod 755 $(DESTDIR)${BINDIR}/meli
|
||||
@mkdir -p $(DESTDIR)${BINDIR}
|
||||
@rm -f $(DESTDIR)${BINDIR}/meli
|
||||
@cp ./${CARGO_TARGET_DIR}/release/meli $(DESTDIR)${BINDIR}/meli
|
||||
@chmod 755 $(DESTDIR)${BINDIR}/meli
|
||||
|
||||
|
||||
.PHONY: install
|
||||
.NOTPARALLEL: yes
|
||||
install: meli install-bin install-doc
|
||||
@(if [ -z $${NO_MAN+x} ]; then \
|
||||
$(PRINTF) "\n You're ready to go. You might want to read the \"STARTING WITH meli\" section in the manpage (\`man meli\`)" ;\
|
||||
$(PRINTF) "\n or the tutorial in meli(7) (\`man 7 meli\`).\n" ;\
|
||||
echo "\n You're ready to go. You might want to read the \"STARTING WITH meli\" section in the manpage (\`man meli\`)" ;\
|
||||
fi)
|
||||
@$(PRINTF) " - Report bugs in the mailing list or git issue tracker ${UNDERLINE}https://git.meli-email.org${ANSI_RESET}\n"
|
||||
@$(PRINTF) " - If you have a specific feature or workflow you want to use, you can post in the mailing list or git issue tracker.\n"
|
||||
@echo " - Report bugs in the mailing list or git issue tracker ${UNDERLINE}https://git.meli.delivery${ANSI_RESET}"
|
||||
@echo " - If you have a specific feature or workflow you want to use, you can post in the mailing list or git issue tracker."
|
||||
|
||||
.PHONY: dist
|
||||
dist:
|
||||
git archive --format=tar.gz --prefix=meli-${VERSION}/ HEAD >meli-${VERSION}.tar.gz
|
||||
@git archive --format=tar.gz --prefix=meli-${VERSION}/ HEAD >meli-${VERSION}.tar.gz
|
||||
@echo meli-${VERSION}.tar.gz
|
||||
|
||||
AUTHOR ?= grep -m1 authors meli/Cargo.toml | head -n1 | cut -d'"' -f 2 | head -n1
|
||||
.PHONY: deb-dist
|
||||
deb-dist:
|
||||
@$(PRINTF) "Override AUTHOR environment variable to set package metadata.\n"
|
||||
dpkg-buildpackage -b -rfakeroot -us -uc --build-by="${AUTHOR}" --release-by="${AUTHOR}"
|
||||
@echo ${BOLD}${GREEN}Generated${ANSI_RESET} ../meli_${VERSION}-1_`dpkg --print-architecture`.deb
|
||||
@dpkg-buildpackage -b -rfakeroot -us -uc
|
||||
@echo ${BOLD}${GREEN}Generated${ANSI_RESET} ../meli_${VERSION}-1_amd64.deb
|
||||
|
||||
.PHONY: build-rustdoc
|
||||
build-rustdoc:
|
||||
@echo RUSTDOCFLAGS=\""--crate-version ${VERSION}_${GIT_COMMIT}_${DATE}"\" ${CARGO_BIN} doc ${CARGO_ARGS} ${CARGO_COLOR}--target-dir=\""${CARGO_TARGET_DIR}"\" --all-features --no-deps --workspace --document-private-items --open
|
||||
@RUSTDOCFLAGS="--crate-version ${VERSION}_${GIT_COMMIT}_${DATE}" ${CARGO_BIN} doc ${CARGO_ARGS} ${CARGO_COLOR}--target-dir="${CARGO_TARGET_DIR}" --all-features --no-deps --workspace --document-private-items --open
|
||||
|
||||
.PHONY: check-tagrefs
|
||||
check-tagrefs:
|
||||
@(if ! command -v "$(TAGREF_BIN)" > /dev/null;\
|
||||
then \
|
||||
$(PRINTF) "Warning: tagref binary not in PATH.\n" 1>&2;\
|
||||
exit;\
|
||||
else \
|
||||
$(TAGREF_BIN);\
|
||||
fi)
|
||||
|
||||
.PHONY: test-makefile
|
||||
test-makefile:
|
||||
@$(PRINTF) "Checking that current version is detected. "
|
||||
@([ ! -z "${VERSION}" ] && $(PRINTF) "${GREEN}OK${ANSI_RESET}\n") || $(PRINTF) "${RED}ERROR${ANSI_RESET}\nVERSION env var is empty, check its definition.\n" 1>&2
|
||||
@$(PRINTF) "Checking that 'date -I' works on this platform. "
|
||||
@export DATEVAL=$$(printf "%s" ${DATE} | wc -c | tr -d "[:blank:]" 2>&1); ([ "$${DATEVAL}" = "10" ] && $(PRINTF) "${GREEN}OK${ANSI_RESET}\n") || $(PRINTF) "${RED}ERROR${ANSI_RESET}\n'date -I' does not produce a YYYY-MM-DD output on this platform.\n" 1>&2
|
||||
@$(PRINTF) "Checking that the git commit SHA can be detected. "
|
||||
@([ ! -z "$(GIT_COMMIT)" ] && $(PRINTF) "${GREEN}OK${ANSI_RESET}\n") || $(PRINTF) "${YELLOW}WARN${ANSI_RESET}\nGIT_COMMIT env var is empty.\n" 1>&2
|
||||
|
||||
# Checking if mdoc changes produce new lint warnings from mandoc(1) compared to HEAD version:
|
||||
#
|
||||
# example invocation: `mandoc_lint meli.1`
|
||||
#
|
||||
# with diff(1)
|
||||
# ============
|
||||
#function mandoc_lint () {
|
||||
#diff <(mandoc -T lint <(git show HEAD:./meli/docs/$1) 2> /dev/null | cut -d':' -f 3-) <(mandoc -T lint ./meli/docs/$1 2> /dev/null | cut -d':' -f 3-)
|
||||
#}
|
||||
#
|
||||
# with sdiff(1) (side by side)
|
||||
# ============================
|
||||
#
|
||||
#function mandoc_lint () {
|
||||
#sdiff <(mandoc -T lint <(git show HEAD:./meli/docs/$1) 2> /dev/null | cut -d':' -f 3-) <(mandoc -T lint ./meli/docs/$1 2> /dev/null | cut -d':' -f 3-)
|
||||
#}
|
||||
#
|
||||
# with delta(1)
|
||||
# =============
|
||||
#
|
||||
#function mandoc_lint () {
|
||||
#delta --side-by-side <(mandoc -T lint <(git show HEAD:./meli/docs/$1) 2> /dev/null | cut -d':' -f 3-) <(mandoc -T lint ./meli/docs/$1 2> /dev/null | cut -d':' -f 3-)
|
||||
#}
|
||||
|
|
270
README.md
|
@ -1,188 +1,130 @@
|
|||
# meli   [](https://github.com/meli/meli/blob/master/COPYING) [](https://crates.io/crates/meli) [](ircs://irc.oftc.net:6697/%23meli)
|
||||
# meli [](https://github.com/meli/meli/blob/master/COPYING) [](https://crates.io/crates/meli)
|
||||
|
||||
**BSD/Linux/macos terminal email client with support for multiple accounts and Maildir / mbox / notmuch / IMAP / JMAP / NNTP (Usenet).**
|
||||
**BSD/Linux terminal email client with support for multiple accounts and Maildir / mbox / notmuch / IMAP / JMAP.**
|
||||
|
||||
Try an [old, outdated but online and interactive web demo](https://meli-email.org/wasm2.html "online interactive web demo") powered by WebAssembly!
|
||||
Community links:
|
||||
[mailing lists](https://lists.meli.delivery/) | `#meli` on OFTC IRC | Report bugs and/or feature requests in [meli's issue tracker](https://git.meli.delivery/meli/meli/issues "meli gitea issue tracker")
|
||||
|
||||
* `#meli` on OFTC IRC
|
||||
* [Mailing lists](https://lists.meli-email.org/)
|
||||
* Main repository <https://git.meli-email.org/meli/meli> Report bugs and/or feature requests in [meli's issue tracker](https://git.meli-email.org/meli/meli/issues "meli gitea issue tracker")<details><summary>Official git mirrors</summary>
|
||||
- <https://codeberg.org/meli/meli>
|
||||
- <https://github.com/meli/meli>
|
||||
- <https://ayllu-forge.org/meli/meli>
|
||||
- <https://gitlab.com/meli-project/meli>
|
||||
</details>
|
||||
| | | |
|
||||
:---:|:---:|:---:
|
||||
 |  | 
|
||||
Main view | Compact main view | Compose with embed terminal editor
|
||||
|
||||
**Table of contents**:
|
||||
Main repository:
|
||||
* https://git.meli.delivery/meli/meli
|
||||
|
||||
- [Install](#install)
|
||||
- [Build](#build)
|
||||
- [Quick start](#quick-start)
|
||||
- [Supported E-mail backends](#supported-e-mail-backends)
|
||||
- [E-mail submission backends](#e-mail-submission-backends)
|
||||
- [Non-exhaustive list of features](#non-exhaustive-list-of-features)
|
||||
- [HTML Rendering](#html-rendering)
|
||||
- [Documentation](#documentation)
|
||||
Official mirrors:
|
||||
* https://github.com/meli/meli
|
||||
|
||||
## Install
|
||||
- Try an [online interactive web demo](https://meli.delivery/wasm2.html "online interactive web demo") powered by WebAssembly
|
||||
- [`cargo install meli`](https://crates.io/crates/meli "crates.io meli package")
|
||||
- [Download and install pre-built debian package, static linux binary](https://github.com/meli/meli/releases/ "github releases for meli"), or
|
||||
- Install with [Nix](https://search.nixos.org/packages?show=meli&query=meli&from=0&size=30&sort=relevance&channel=unstable#disabled "nixos package search results for 'meli'")
|
||||
|
||||
<a href="https://repology.org/project/meli/versions">
|
||||
<img src="https://repology.org/badge/vertical-allrepos/meli.svg" alt="Packaging status table by repology.org" align="right">
|
||||
</a>
|
||||
## Documentation
|
||||
|
||||
- `cargo install meli` or `cargo install --git https://git.meli-email.org/meli/meli.git meli` [crates.io link](https://crates.io/crates/meli)
|
||||
- Official Debian packages <https://packages.debian.org/trixie/meli>
|
||||
- AUR (archlinux) <https://aur.archlinux.org/packages/meli>
|
||||
- NetBSD with pkgsrc <https://pkgsrc.se/mail/meli>
|
||||
- OpenBSD ports <https://openports.pl/path/mail/meli>
|
||||
- macOS with MacPorts <https://ports.macports.org/port/meli/>
|
||||
- Nix with Nixpkgs <https://search.nixos.org/packages?query=meli>
|
||||
- [Pre-built debian package, static binaries](https://github.com/meli/meli/releases/ "github releases for meli") for <code>amd64</code>, <code>arm64</code> architectures
|
||||
See also [Quickstart tutorial](https://meli.delivery/documentation.html#quick-start).
|
||||
|
||||
## Build
|
||||
After installing meli, see `meli(1)`, `meli.conf(5)` and `meli-themes(5)` for documentation. Sample configuration and theme files can be found in the `docs/samples/` subdirectory. Manual pages are also [hosted online](https://meli.delivery/documentation.html "meli documentation").
|
||||
|
||||
Run `make` or `cargo build --release --bin meli`.
|
||||
meli by default looks for a configuration file in this location: `$XDG_CONFIG_HOME/meli/config.toml`
|
||||
|
||||
For detailed building instructions, see [`BUILD.md`](./BUILD.md)
|
||||
|
||||
### Cargo Compile-time Features
|
||||
|
||||
`meli` supports opting in and out of features at compile time with cargo features.
|
||||
|
||||
The contents of the `default` feature are:
|
||||
|
||||
```toml
|
||||
default = ["sqlite3", "notmuch", "smtp", "dbus-notifications", "gpgme", "cli-docs", "jmap", "static"]
|
||||
```
|
||||
|
||||
A list of all the features and a description for each follows:
|
||||
|
||||
| Feature flag | Dependencies | Notes |
|
||||
|---------------------------------------------------------------|----------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| <a name="notmuch-feature">`notmuch`</a> | `maildir` feature | Provides the *notmuch* backend |
|
||||
| <a name="jmap-feature">`jmap`</a> | `http` feature, `url` crate with `serde` feature | Provides the *JMAP* backend |
|
||||
| <a name="smtp-feature">`smtp`</a> | `tls` feature | Integrated async *SMTP* client |
|
||||
| <a name="sqlite3-feature">`sqlite3`</a> | `rusqlite` crate with `bundled-full` feature | Used in caches |
|
||||
| <a name="sqlite3-static-feature">`sqlite3-static`</a> | `rusqlite` crate with `bundled-full` feature | Same as `sqlite3` feature but provided for consistency and in case `sqlite3` feature stops bundling libsqlite3 statically in the future. |
|
||||
| <a name="smtp-trace-feature">`smtp-trace`</a> | `smtp` feature | Connection trace logs on the `trace` logging level |
|
||||
| <a name="gpgme-feature">`gpgme`</a> | | *GPG* use by dynamically loading `libgpgme.so` |
|
||||
| <a name="tls-static-feature">`tls-static`</a> | `native-tls` crate with `vendored` feature | Links with `OpenSSL` statically where it's used |
|
||||
| <a name="http-static-feature">`http-static`</a> | `isahc` crate with `static-curl` feature | Links with `curl` statically |
|
||||
| <a name="dbus-notifications-feature">`dbus-notifications`</a> | `notify-rust` dependency | Uses DBus notifications |
|
||||
| <a name="dbus-static-feature">`dbus-static`</a> | `notify-rust` dependency and enableds its `d_vendored` feature | Includes the dbus library statically. |
|
||||
| <a name="cli-docs-feature">`cli-docs`</a> | `flate2` dependency | Includes the manpage documentation compiled by either `mandoc` or `man` binary to plain text in `meli`'s command line. Embedded documentation can be viewed with the subcommand `meli man [PAGE]` |
|
||||
| <a name="libz-static-feature">`libz-static`</a> | `libz-sys` dependency and enables its `static` feature | Allows for the transitive dependency libz (from `curl`) to be linked statically. |
|
||||
| <a name="static-feature">`static`</a> | enables `tls-static`, `http-static`, `sqlite3-static`, `dbus-static`, `libz-static` features | |
|
||||
|
||||
## Quick start
|
||||
|
||||
```sh
|
||||
# Create configuration file in ${XDG_CONFIG_HOME}/meli/config.toml:
|
||||
$ meli create-config
|
||||
# Edit configuration in ${EDITOR} or ${VISUAL}:
|
||||
$ meli edit-config
|
||||
# Optionally, install manual pages if installed via cargo:
|
||||
$ meli install-man
|
||||
# Ready to go.
|
||||
$ meli
|
||||
# You can read any manual page with the CLI subcommand `man`:
|
||||
$ meli man meli.7
|
||||
# See help output for all options and subcommands.
|
||||
$ meli --help
|
||||
```
|
||||
|
||||
See a comprehensive tour of `meli` in the manual page [`meli(7)`](./meli/docs/meli.7).
|
||||
|
||||
See also the [Quickstart tutorial](https://meli-email.org/documentation.html#quick-start) online.
|
||||
|
||||
After installing `meli`, see `meli(1)`, `meli.conf(5)`, `meli(7)` and `meli-themes(5)` for documentation.
|
||||
Sample configuration and theme files can be found in the `meli/docs/samples/` subdirectory.
|
||||
Examples for configuration file settings can be found in `meli.conf.examples(5)`
|
||||
Manual pages are also [hosted online](https://meli-email.org/documentation.html "meli documentation").
|
||||
`meli` by default looks for a configuration file in this location: `${XDG_CONFIG_HOME}/meli/config.toml`.
|
||||
|
||||
You can run meli with arbitrary configuration files by setting the `${MELI_CONFIG}` environment variable to their locations, i.e.:
|
||||
You can run meli with arbitrary configuration files by setting the `$MELI_CONFIG`
|
||||
environment variable to their locations, i.e.:
|
||||
|
||||
```sh
|
||||
MELI_CONFIG=./test_config cargo run
|
||||
```
|
||||
|
||||
See [`meli(7)`](./meli/docs/meli.7) for an extensive tutorial and [`meli.conf(5)`](./meli/docs/meli.conf.5) for all configuration values.
|
||||
|
||||
| Main view | Compact main view | Compose with embed terminal editor |
|
||||
|-----------|-------------------|------------------------------------|
|
||||
|  |  |  |
|
||||
|
||||
### Supported E-mail backends
|
||||
|
||||
| Protocol | Support |
|
||||
|---------------|------------|
|
||||
| IMAP | full |
|
||||
| Maildir | full |
|
||||
| notmuch | full[^0] |
|
||||
| mbox | read-only |
|
||||
| JMAP | functional |
|
||||
| NNTP / Usenet | functional |
|
||||
|
||||
[^0]: there's no support for searching through all email directly, you'd have to
|
||||
create a mailbox with a notmuch query that returns everything and search
|
||||
inside that mailbox.
|
||||
|
||||
### E-mail submission backends
|
||||
|
||||
- SMTP
|
||||
- Pipe to shell script
|
||||
- Server-side submission when supported
|
||||
|
||||
### Non-exhaustive list of features
|
||||
|
||||
- TLS
|
||||
- email threading support
|
||||
- multithreaded, async operation
|
||||
- optionally run your editor of choice inside meli, with an embedded
|
||||
xterm-compatible terminal emulator
|
||||
- plain text configuration in TOML
|
||||
- ability to open emails in UI tabs and switch to them
|
||||
- optional sqlite3 index search
|
||||
- override almost any setting per mailbox, per account
|
||||
- contact list (+read-only vCard and mutt alias file support)
|
||||
- forced UTF-8 (other encodings are read-only)
|
||||
- configurable shortcuts
|
||||
- theming
|
||||
- `NO_COLOR` support
|
||||
- ascii-only drawing characters option
|
||||
- view text/html attachments through an html filter command (w3m by default)
|
||||
- pipe attachments/mail to stuff
|
||||
- use external attachment file picker instead of typing in an attachment's full path
|
||||
- GPG signing, encryption, signing + encryption
|
||||
- GPG signature verification
|
||||
|
||||
## HTML Rendering
|
||||
|
||||
HTML rendering is achieved using [w3m](https://github.com/tats/w3m) by default.
|
||||
You can use the `pager.html_filter` setting to override this (for more details you can consult [`meli.conf(5)`](./meli/docs/meli.conf.5)).
|
||||
|
||||
|
||||
## Documentation
|
||||
|
||||
See a comprehensive tour of `meli` in the manual page [`meli(7)`](./meli/docs/meli.7).
|
||||
|
||||
See also the [Quickstart tutorial](https://meli-email.org/documentation.html#quick-start) online.
|
||||
|
||||
After installing `meli`, see `meli(1)`, `meli.conf(5)`, `meli(7)` and `meli-themes(5)` for documentation.
|
||||
Sample configuration and theme files can be found in the `meli/docs/samples/` subdirectory.
|
||||
Manual pages are also [hosted online](https://meli-email.org/documentation.html "meli documentation").
|
||||
|
||||
`meli` by default looks for a configuration file in this location: `${XDG_CONFIG_HOME}/meli/config.toml`
|
||||
|
||||
You can run meli with arbitrary configuration files by setting the `${MELI_CONFIG}` environment variable to their locations, or use the `[-c, --config]` argument:
|
||||
## Build
|
||||
For a quick start, build and install locally:
|
||||
|
||||
```sh
|
||||
MELI_CONFIG=./test_config meli
|
||||
PREFIX=~/.local make install
|
||||
```
|
||||
|
||||
or
|
||||
Available subcommands for `make` are listed with `make help`. The Makefile *should* be POSIX portable and not require a specific `make` version.
|
||||
|
||||
meli requires rust 1.39 and rust's package manager, Cargo. Information on how
|
||||
to get it on your system can be found here: <https://doc.rust-lang.org/cargo/getting-started/installation.html>
|
||||
|
||||
With Cargo available, the project can be built with `make` and the resulting binary will then be found under `target/release/meli`. Run `make install` to install the binary and man pages. This requires root, so I suggest you override the default paths and install it in your `$HOME`: `make PREFIX=$HOME/.local install`.
|
||||
|
||||
You can build and run meli with one command: `cargo run --release`.
|
||||
|
||||
### Build features
|
||||
|
||||
Some functionality is held behind "feature gates", or compile-time flags. The following list explains each feature's purpose:
|
||||
|
||||
- `gpgme` enables GPG support via `libgpgme` (on by default)
|
||||
- `dbus-notifications` enables showing notifications using `dbus` (on by default)
|
||||
- `notmuch` provides support for using a notmuch database as a mail backend (on by default)
|
||||
- `jmap` provides support for connecting to a jmap server and use it as a mail backend (off by default)
|
||||
- `sqlite3` provides support for builting fast search indexes in local sqlite3 databases (on by default)
|
||||
- `cli-docs` includes the manpage documentation compiled by either `mandoc` or `man` binary to plain text in `meli`'s command line. Embedded documentation can be viewed with the subcommand `meli man [PAGE]`
|
||||
- `svgscreenshot` provides support for taking screenshots of the current view of meli and saving it as SVG files. Its only purpose is taking screenshots for the official meli webpage. (off by default)
|
||||
- `debug-tracing` enables various trace debug logs from various places around the meli code base. The trace log is printed in `stderr`. (off by default)
|
||||
|
||||
### Build Debian package (*deb*)
|
||||
|
||||
Building with Debian's packaged cargo might require the installation of these
|
||||
two packages: `librust-openssl-sys-dev librust-libdbus-sys-dev`
|
||||
|
||||
A `*.deb` package can be built with `make deb-dist`
|
||||
|
||||
### Using notmuch
|
||||
|
||||
To use the optional notmuch backend feature, you must have `libnotmuch5` installed in your system. In Debian-like systems, install the `libnotmuch5` packages. meli detects the library's presence on runtime.
|
||||
|
||||
### Using GPG
|
||||
|
||||
To use the optional gpg feature, you must have `libgpgme` installed in your system. In Debian-like systems, install the `libgpgme11` package. meli detects the library's presence on runtime.
|
||||
|
||||
### Building with JMAP
|
||||
|
||||
To build with JMAP support, prepend the environment variable `MELI_FEATURES='jmap'` to your make invocation:
|
||||
|
||||
```sh
|
||||
meli -c ./test_config
|
||||
MELI_FEATURES="jmap" make
|
||||
```
|
||||
|
||||
or if building directly with cargo, use the flag `--features="jmap"'.
|
||||
|
||||
# Development
|
||||
|
||||
Development builds can be built and/or run with
|
||||
|
||||
```
|
||||
cargo build
|
||||
cargo run
|
||||
```
|
||||
|
||||
There is a debug/tracing log feature that can be enabled by using the flag
|
||||
`--feature debug-tracing` after uncommenting the features in `Cargo.toml`. The logs
|
||||
are printed in stderr, thus you can run meli with a redirection (i.e `2> log`)
|
||||
|
||||
Code style follows the default rustfmt profile.
|
||||
|
||||
## Testing
|
||||
|
||||
How to run specific tests:
|
||||
|
||||
```sh
|
||||
cargo test -p {melib, meli} (-- --nocapture) (--test test_name)
|
||||
```
|
||||
|
||||
## Profiling
|
||||
|
||||
```sh
|
||||
perf record -g target/debug/bin
|
||||
perf script | stackcollapse-perf | rust-unmangle | flamegraph > perf.svg
|
||||
```
|
||||
|
||||
## Running fuzz targets
|
||||
|
||||
Note: `cargo-fuzz` requires the nightly toolchain.
|
||||
|
||||
```sh
|
||||
cargo +nightly fuzz run envelope_parse -- -dict=fuzz/envelope_tokens.dict
|
||||
```
|
||||
|
|
106
build.rs
Normal file
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* meli - build.rs
|
||||
*
|
||||
* Copyright 2020 Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
extern crate proc_macro;
|
||||
extern crate quote;
|
||||
extern crate syn;
|
||||
mod config_macros;
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
config_macros::override_derive(&[
|
||||
("src/conf/pager.rs", "PagerSettings"),
|
||||
("src/conf/listing.rs", "ListingSettings"),
|
||||
("src/conf/notifications.rs", "NotificationsSettings"),
|
||||
("src/conf/shortcuts.rs", "Shortcuts"),
|
||||
("src/conf/composing.rs", "ComposingSettings"),
|
||||
("src/conf/tags.rs", "TagsSettings"),
|
||||
("src/conf/pgp.rs", "PGPSettings"),
|
||||
]);
|
||||
#[cfg(feature = "cli-docs")]
|
||||
{
|
||||
use flate2::Compression;
|
||||
use flate2::GzBuilder;
|
||||
const MANDOC_OPTS: &[&'static str] = &["-T", "utf8", "-I", "os=Generated by mandoc(1)"];
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
let mut out_dir_path = Path::new(&out_dir).to_path_buf();
|
||||
out_dir_path.push("meli.txt.gz");
|
||||
|
||||
let output = Command::new("mandoc")
|
||||
.args(MANDOC_OPTS)
|
||||
.arg("docs/meli.1")
|
||||
.output()
|
||||
.or_else(|_| Command::new("man").arg("-l").arg("docs/meli.1").output())
|
||||
.unwrap();
|
||||
|
||||
let file = File::create(&out_dir_path).unwrap();
|
||||
let mut gz = GzBuilder::new()
|
||||
.comment(output.stdout.len().to_string().into_bytes())
|
||||
.write(file, Compression::default());
|
||||
gz.write_all(&output.stdout).unwrap();
|
||||
gz.finish().unwrap();
|
||||
out_dir_path.pop();
|
||||
|
||||
out_dir_path.push("meli.conf.txt.gz");
|
||||
let output = Command::new("mandoc")
|
||||
.args(MANDOC_OPTS)
|
||||
.arg("docs/meli.conf.5")
|
||||
.output()
|
||||
.or_else(|_| {
|
||||
Command::new("man")
|
||||
.arg("-l")
|
||||
.arg("docs/meli.conf.5")
|
||||
.output()
|
||||
})
|
||||
.unwrap();
|
||||
let file = File::create(&out_dir_path).unwrap();
|
||||
let mut gz = GzBuilder::new()
|
||||
.comment(output.stdout.len().to_string().into_bytes())
|
||||
.write(file, Compression::default());
|
||||
gz.write_all(&output.stdout).unwrap();
|
||||
gz.finish().unwrap();
|
||||
out_dir_path.pop();
|
||||
|
||||
out_dir_path.push("meli-themes.txt.gz");
|
||||
let output = Command::new("mandoc")
|
||||
.args(MANDOC_OPTS)
|
||||
.arg("docs/meli-themes.5")
|
||||
.output()
|
||||
.or_else(|_| {
|
||||
Command::new("man")
|
||||
.arg("-l")
|
||||
.arg("docs/meli-themes.5")
|
||||
.output()
|
||||
})
|
||||
.unwrap();
|
||||
let file = File::create(&out_dir_path).unwrap();
|
||||
let mut gz = GzBuilder::new()
|
||||
.comment(output.stdout.len().to_string().into_bytes())
|
||||
.write(file, Compression::default());
|
||||
gz.write_all(&output.stdout).unwrap();
|
||||
gz.finish().unwrap();
|
||||
}
|
||||
}
|
166
cliff.toml
|
@ -1,166 +0,0 @@
|
|||
# configuration for https://github.com/orhun/git-cliff
|
||||
|
||||
[remote.gitea]
|
||||
owner = "meli"
|
||||
repo = "meli"
|
||||
|
||||
[changelog]
|
||||
# changelog header
|
||||
header = """
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
"""
|
||||
# template for the changelog body
|
||||
# https://keats.github.io/tera/docs/#introduction
|
||||
# note that the - before / after the % controls whether whitespace is rendered between each line.
|
||||
# Getting this right so that the markdown renders with the correct number of lines between headings
|
||||
# code fences and list items is pretty finicky. Note also that the 4 backticks in the commit macro
|
||||
# is intentional as this escapes any backticks in the commit body.
|
||||
body = """
|
||||
|
||||
{% if not version %}
|
||||
## Unreleased
|
||||
{% else %}
|
||||
## [{{ version }}]({{ "https://git.meli-email.org/meli/meli/releases/tag/" ~ version }}) - {{ timestamp | date(format="%Y-%m-%d") }}
|
||||
{% endif %}{% if get_env(name = "FRIENDS", default = "") != "" %}
|
||||
Contributors in alphabetical order:
|
||||
|
||||
{{ get_env(name = "FRIENDS") }}
|
||||
{%- endif -%}{% if gitea and gitea.contributors %}{% for contributor in gitea.contributors | filter(attribute="is_first_time", value=true) %}
|
||||
* [@{{ contributor.username }}](https://git.meli-email.org/{{ contributor.username }}) made their first contribution in [#{{ contributor.pr_number }}]({{ "https://git.meli-email.org/meli/meli/pulls/" ~ contributor.pr_number }})
|
||||
{%- endfor -%}{%- endif -%}{% macro commit(commit) -%}
|
||||
- [**`{{ commit.id | truncate(length=8, end="") }}`**]({{ "https://git.meli-email.org/meli/meli/commit/" ~ commit.id }}) {% if commit.scope %}*({{commit.scope | lower }})* {% endif %}`{{ commit.message | split(pat="\n")| first }}`{% if commit.remote and commit.remote.pr_number and commit.remote.pr_title %} in PR [`#{{ commit.remote.pr_number }}` "{{ commit.remote.pr_title }}"]({{ "https://git.meli-email.org/meli/meli/pulls/" ~ commit.remote.pr_number }}){%- endif -%}{% endmacro -%}
|
||||
{% for group, commits in commits | group_by(attribute="group") %}
|
||||
|
||||
### {{ group | striptags | trim | upper_first }}
|
||||
{% for commit in commits | filter(attribute="scope") | sort(attribute="scope") %}
|
||||
{{ self::commit(commit=commit) }}
|
||||
{%- endfor -%}
|
||||
{% for commit in commits %}
|
||||
{%- if not commit.scope %}
|
||||
{{ self::commit(commit=commit) }}
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
{%- endfor %}
|
||||
"""
|
||||
|
||||
# remove the leading and trailing whitespace from the template
|
||||
trim = true
|
||||
# changelog footer
|
||||
footer = """
|
||||
|
||||
|
||||
<!-- generated by git-cliff <https://git-cliff.org> -->
|
||||
"""
|
||||
|
||||
[git]
|
||||
# don't parse the commits based on https://www.conventionalcommits.org
|
||||
conventional_commits = false
|
||||
# filter out the commits that are not conventional
|
||||
filter_unconventional = false
|
||||
# process each line of a commit as an individual commit
|
||||
split_commits = false
|
||||
# regex for preprocessing the commit messages
|
||||
commit_preprocessors = [
|
||||
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://git.meli-email.org/meli/meli/issues/${2}))" }, # Replace the issue number with the link
|
||||
{ pattern = " +", replace = " "}, # Replace multiple spaces with a single space
|
||||
{ pattern = "`[^`]+`", replace_command = "pandoc -f commonmark -t plain" },
|
||||
]
|
||||
# regex for parsing and grouping commits
|
||||
commit_parsers = [
|
||||
{ message = "^chore\\(changelog\\)", skip = true },
|
||||
{ message = "^chore\\(deps\\)", skip = true },
|
||||
{ message = "^chore\\(pull\\)", skip = true },
|
||||
{ message = "^chore\\(pr\\)", skip = true },
|
||||
{ message = "^chore\\(release\\): prepare for", skip = true },
|
||||
{ message = "^CHANGELOG", skip = true },
|
||||
{ message = "(?i)^revert", group = "<!-- 11 -->Reverted Commits" },
|
||||
{ message = "^.github", group = "<!-- 10 -->Continuous Integration" },
|
||||
{ message = "^.gitea", group = "<!-- 10 -->Continuous Integration" },
|
||||
{ message = "(?i)^ci", group = "<!-- 10 -->Continuous Integration" },
|
||||
{ message = "^build", group = "<!-- 09 -->Build" },
|
||||
{ body = ".*security", group = "<!-- 08 -->Security" },
|
||||
{ message = "^scripts", group = "<!-- 07 -->Miscellaneous Tasks" },
|
||||
{ message = '(?i)^chore', group = "<!-- 07 -->Miscellaneous Tasks" },
|
||||
{ message = "^debian", group = "<!-- 06 -->Packaging" },
|
||||
{ message = "^test", group = "<!-- 06 -->Testing" },
|
||||
{ message = "^style", group = "<!-- 05 -->Styling" },
|
||||
{ message = "^perf", group = "<!-- 04 -->Performance" },
|
||||
{ message = "(?i)readme", group = "<!-- 03 -->Documentation" },
|
||||
{ message = "(?i)anpage", group = "<!-- 03 -->Documentation" },
|
||||
{ message = "(?i)anual", group = "<!-- 03 -->Documentation" },
|
||||
{ message = "meli.[17]", group = "<!-- 03 -->Documentation" },
|
||||
{ message = "meli.conf.5", group = "<!-- 03 -->Documentation" },
|
||||
{ message = "meli-themes.5", group = "<!-- 03 -->Documentation" },
|
||||
{ message = "README.md", group = "<!-- 03 -->Documentation" },
|
||||
{ message = "BUILD.md", group = "<!-- 03 -->Documentation" },
|
||||
{ message = "DEVELOPMENT.md", group = "<!-- 03 -->Documentation" },
|
||||
{ message = "^doc", group = "<!-- 03 -->Documentation" },
|
||||
{ message = "^[^.]*.rs:", group = "<!-- 02 -->Refactoring" },
|
||||
{ message = '(?i)^refactor\b', group = "<!-- 02 -->Refactoring" },
|
||||
{ message = '(?i)lints?\b', group = "<!-- 02 -->Refactoring" },
|
||||
{ message = '(?i)move\b', group = "<!-- 02 -->Refactoring" },
|
||||
{ message = '(?i)replace\b', group = "<!-- 02 -->Refactoring" },
|
||||
{ message = '(?i)remove\b', group = "<!-- 02 -->Refactoring" },
|
||||
{ message = '(?i)refactor\b', group = "<!-- 02 -->Refactoring" },
|
||||
{ message = '(?i)rename\b', group = "<!-- 02 -->Refactoring" },
|
||||
{ message = '(?i)formatting\b', group = "<!-- 02 -->Refactoring" },
|
||||
{ message = '(?i)cleanups?\b', group = "<!-- 02 -->Refactoring" },
|
||||
{ message = '(?i)fix\b', group = "<!-- 01 -->Bug Fixes" },
|
||||
{ message = '(?i)fixups?\b', group = "<!-- 01 -->Bug Fixes" },
|
||||
{ message = "(?i)implement", group = "<!-- 00 -->Added" },
|
||||
{ message = '(?i)add\b', group = "<!-- 00 -->Added" },
|
||||
{ message = '(?i)^update\b', group = "<!-- 02 -->Changes" },
|
||||
{ message = '(?i)\bdependency\b', group = "<!-- 02 -->Changes" },
|
||||
{ message = "^feat", group = "<!-- 00 -->Added" },
|
||||
{ message = '(?i)retry\b', group = "<!-- 02 -->Changes" },
|
||||
{ message = "^conf", group = "<!-- 02 -->Changes" },
|
||||
{ message = "^contacts?", group = "<!-- 02 -->Changes" },
|
||||
{ message = "^compos[ie]?", group = "<!-- 02 -->Changes" },
|
||||
{ message = "^types", group = "<!-- 02 -->Changes" },
|
||||
{ message = '(?i)^use', group = "<!-- 02 -->Changes" },
|
||||
{ message = "^terminal", group = "<!-- 02 -->Changes" },
|
||||
{ message = "^listing", group = "<!-- 02 -->Changes" },
|
||||
{ message = "^mail", group = "<!-- 02 -->Changes" },
|
||||
{ message = "^utilities", group = "<!-- 02 -->Changes" },
|
||||
{ message = "^view", group = "<!-- 02 -->Changes" },
|
||||
{ message = "^mail/view", group = "<!-- 02 -->Changes" },
|
||||
{ message = "^backends?", group = "<!-- 02 -->Changes" },
|
||||
{ message = "^commands?", group = "<!-- 02 -->Changes" },
|
||||
{ message = "^actions?", group = "<!-- 02 -->Changes" },
|
||||
{ message = "^log", group = "<!-- 02 -->Changes" },
|
||||
{ message = "^pgp", group = "<!-- 02 -->Changes" },
|
||||
{ message = "^gpgme", group = "<!-- 02 -->Changes" },
|
||||
{ message = "^manage", group = "<!-- 02 -->Changes" },
|
||||
{ message = "^smtp", group = "<!-- 02 -->Changes" },
|
||||
{ message = "^mbox", group = "<!-- 02 -->Changes" },
|
||||
{ message = "^jmap", group = "<!-- 02 -->Changes" },
|
||||
{ message = "^imap", group = "<!-- 02 -->Changes" },
|
||||
{ message = "^nntp", group = "<!-- 02 -->Changes" },
|
||||
{ message = "^notmuch", group = "<!-- 02 -->Changes" },
|
||||
{ message = "^melib", group = "<!-- 02 -->Changes" },
|
||||
{ message = "^meli", group = "<!-- 02 -->Changes" },
|
||||
{ message = "^accounts", group = "<!-- 02 -->Changes" },
|
||||
{ message = "^embedded", group = "<!-- 02 -->Changes" },
|
||||
{ message = "^jobs", group = "<!-- 02 -->Changes" },
|
||||
{ message = ".*", group = "<!-- 07 -->Miscellaneous Tasks" },
|
||||
]
|
||||
# protect breaking changes from being skipped due to matching a skipping commit_parser
|
||||
protect_breaking_commits = false
|
||||
# filter out the commits that are not matched by commit parsers
|
||||
filter_commits = false
|
||||
# glob pattern for matching git tags
|
||||
tag_pattern = "v[0-9]+|alpha-[0-9]+"
|
||||
# regex for ignoring tags
|
||||
ignore_tags = "v[^-]+-rc[.]?[0-9]+"
|
||||
# regex for skipping tags
|
||||
#skip_tags = "alpha"
|
||||
# sort the tags topologically
|
||||
topo_order = false
|
||||
# sort the commits inside sections by oldest/newest order
|
||||
sort_commits = "oldest"
|
|
@ -1,69 +0,0 @@
|
|||
{
|
||||
"@context": ["https://doi.org/10.5063/schema/codemeta-2.0", "http://schema.org/"],
|
||||
"@type": "SoftwareSourceCode",
|
||||
"applicationCategory": "E-mail client",
|
||||
"author": [
|
||||
{
|
||||
"@id": "https://pitsidianak.is/",
|
||||
"@type": "Person",
|
||||
"name": "epilys",
|
||||
"email": "manos@pitsidianak.is",
|
||||
"familyName": "Pitsidianakis",
|
||||
"givenName": "Manos",
|
||||
"url": "https://pitsidianak.is/"
|
||||
}
|
||||
],
|
||||
"codeRepository": "https://git.meli-email.org/meli/meli.git",
|
||||
"dateCreated": "2016-04-25",
|
||||
"dateModified": "2024-11-27",
|
||||
"datePublished": "2017-07-23",
|
||||
"description": "BSD/Linux/macos terminal email client with support for multiple accounts and Maildir / mbox / notmuch / IMAP / JMAP / NNTP (Usenet).",
|
||||
"downloadUrl": "https://git.meli-email.org/meli/meli/archive/v0.8.10.tar.gz",
|
||||
"identifier": "https://meli-email.org/",
|
||||
"isPartOf": "https://meli-email.org/",
|
||||
"keywords": [
|
||||
"e-mail",
|
||||
"email",
|
||||
"mail",
|
||||
"terminal user interface",
|
||||
"client",
|
||||
"mua",
|
||||
"mail user agent",
|
||||
"smtp",
|
||||
"imap",
|
||||
"jmap",
|
||||
"mbox",
|
||||
"maildir",
|
||||
"nntp"
|
||||
],
|
||||
"license": [
|
||||
"https://spdx.org/licenses/EUPL-1.2",
|
||||
"https://spdx.org/licenses/GPL-3.0-or-later"
|
||||
],
|
||||
"name": "meli",
|
||||
"operatingSystem": [
|
||||
"Linux",
|
||||
"macOS",
|
||||
"OpenBSD",
|
||||
"NetBSD"
|
||||
],
|
||||
"programmingLanguage": "Rust",
|
||||
"relatedLink": [
|
||||
"https://lists.meli-email.org/",
|
||||
"https://codeberg.org/meli/meli",
|
||||
"https://github.com/meli/meli",
|
||||
"https://gitlab.com/meli-project/meli",
|
||||
"https://crates.io/crates/meli",
|
||||
"https://packages.debian.org/trixie/meli",
|
||||
"https://pkgsrc.se/mail/meli",
|
||||
"https://openports.pl/path/mail/meli",
|
||||
"https://ports.macports.org/port/meli/",
|
||||
"https://search.nixos.org/packages?query=meli"
|
||||
],
|
||||
"version": "0.8.10",
|
||||
"contIntegration": "https://git.meli-email.org/meli/meli/actions",
|
||||
"developmentStatus": "active",
|
||||
"issueTracker": "https://git.meli-email.org/meli/meli/issues",
|
||||
"readme": "https://git.meli-email.org/meli/meli/raw/tag/v0.8.10/README.md",
|
||||
"buildInstructions": "https://git.meli-email.org/meli/meli/raw/tag/v0.8.10/BUILD.md"
|
||||
}
|
|
@ -19,21 +19,17 @@
|
|||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use std::{
|
||||
fs::File,
|
||||
io::prelude::*,
|
||||
process::{Command, Stdio},
|
||||
};
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
use quote::{format_ident, quote};
|
||||
use regex::Regex;
|
||||
|
||||
// Write ConfigStructOverride to overrides.rs
|
||||
pub(crate) fn override_derive(filenames: &[(&str, &str)]) {
|
||||
pub fn override_derive(filenames: &[(&str, &str)]) {
|
||||
let mut output_file =
|
||||
File::create("src/conf/overrides.rs").expect("Unable to open output file");
|
||||
let mut output_string = r##"// @generated
|
||||
/*
|
||||
let mut output_string = r##"/*
|
||||
* meli - conf/overrides.rs
|
||||
*
|
||||
* Copyright 2020 Manos Pitsidianakis
|
||||
|
@ -56,26 +52,15 @@ pub(crate) fn override_derive(filenames: &[(&str, &str)]) {
|
|||
|
||||
#![allow(clippy::derivable_impls)]
|
||||
|
||||
//! This module is automatically generated by `config_macros.rs`.
|
||||
|
||||
use melib::HeaderName;
|
||||
|
||||
use indexmap::IndexSet;
|
||||
|
||||
use crate::conf::{*, data_types::*};
|
||||
//! This module is automatically generated by config_macros.rs.
|
||||
use super::*;
|
||||
|
||||
"##
|
||||
.to_string();
|
||||
|
||||
let cfg_attr_default_attr_regex = Regex::new(r"\s*default\s*[,]").unwrap();
|
||||
let cfg_attr_default_val_attr_regex = Regex::new(r#"\s*default\s*=\s*"[^"]*"\s*,\s*"#).unwrap();
|
||||
let cfg_attr_skip_ser_attr_regex =
|
||||
Regex::new(r#"\s*,?\s*skip_serializing_if\s*=\s*"[^"]*"\s*,?\s*"#).unwrap();
|
||||
let cfg_attr_feature_regex = Regex::new(r"[(](?:not[(]\s*)?feature").unwrap();
|
||||
|
||||
'file_loop: for (filename, ident) in filenames {
|
||||
println!("cargo:rerun-if-changed={}", filename);
|
||||
let mut file = File::open(filename)
|
||||
let mut file = File::open(&filename)
|
||||
.unwrap_or_else(|err| panic!("Unable to open file `{}` {}", filename, err));
|
||||
|
||||
let mut src = String::new();
|
||||
|
@ -121,31 +106,10 @@ use crate::conf::{*, data_types::*};
|
|||
.iter()
|
||||
.filter_map(|f| {
|
||||
let mut new_attr = f.clone();
|
||||
if let proc_macro2::TokenTree::Group(g) =
|
||||
if let quote::__private::TokenTree::Group(g) =
|
||||
f.tokens.clone().into_iter().next().unwrap()
|
||||
{
|
||||
let mut attr_inner_value = f.tokens.to_string();
|
||||
if attr_inner_value.contains("skip_serializing_if") {
|
||||
attr_inner_value = cfg_attr_skip_ser_attr_regex
|
||||
.replace_all(&attr_inner_value, "")
|
||||
.to_string();
|
||||
let new_toks: proc_macro2::TokenStream =
|
||||
attr_inner_value.parse().unwrap();
|
||||
new_attr.tokens = quote! { #new_toks };
|
||||
}
|
||||
if cfg_attr_feature_regex.is_match(&attr_inner_value) {
|
||||
attr_inner_value = cfg_attr_default_val_attr_regex
|
||||
.replace_all(&attr_inner_value, "")
|
||||
.to_string();
|
||||
if attr_inner_value.contains("default") {
|
||||
attr_inner_value = cfg_attr_default_attr_regex
|
||||
.replace_all(&attr_inner_value, "")
|
||||
.to_string();
|
||||
}
|
||||
let new_toks: proc_macro2::TokenStream =
|
||||
attr_inner_value.parse().unwrap();
|
||||
new_attr.tokens = quote! { #new_toks };
|
||||
}
|
||||
let attr_inner_value = f.tokens.to_string();
|
||||
if !attr_inner_value.starts_with("( default")
|
||||
&& !attr_inner_value.starts_with("( default =")
|
||||
&& !attr_inner_value.starts_with("(default")
|
||||
|
@ -167,11 +131,6 @@ use crate::conf::{*, data_types::*};
|
|||
} else if attr_inner_value.starts_with("( default")
|
||||
|| attr_inner_value.starts_with("(default")
|
||||
{
|
||||
if attr_inner_value.ends_with("default)")
|
||||
|| attr_inner_value.ends_with("default )")
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let rest = g.stream().into_iter().skip(2);
|
||||
new_attr.tokens = quote! { ( #(#rest)*) };
|
||||
match new_attr.tokens.to_string().as_str() {
|
||||
|
@ -191,9 +150,7 @@ use crate::conf::{*, data_types::*};
|
|||
#[serde(default)]
|
||||
pub #ident : Option<#ty>
|
||||
};
|
||||
if !field_idents.contains(&ident) {
|
||||
field_idents.push(ident);
|
||||
}
|
||||
field_idents.push(ident);
|
||||
field_tokentrees.push(t);
|
||||
}
|
||||
//let fields = &s.fields;
|
||||
|
@ -210,7 +167,7 @@ use crate::conf::{*, data_types::*};
|
|||
#(#attrs_tokens)*
|
||||
impl Default for #override_ident {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
#override_ident {
|
||||
#(#field_idents: None),*
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
<!-- SPDX-License-Identifier: EUPL-1.2 OR GPL-3.0-or-later -->
|
||||
# Useful scripts and files for use with `meli`
|
||||
|
||||
This directory includes various useful scripts and files that are contributed
|
||||
by the community and not actively maintained or supported.
|
||||
|
||||
If you believe something in this directory needs updates to work with the
|
||||
current version of `meli` or there are bugs that need fixing, please file an
|
||||
issue on our issue tracker!
|
||||
|
||||
## Connecting to a Gmail account with OAUTH2
|
||||
|
||||
The script [`./oauth2.py`](./oauth2.py) is a helper script to authenticate to a Gmail account using IMAP OAUTH2 tokens.
|
||||
|
||||
See [`meli.conf(5)`](../meli/docs/meli.conf.5) for documentation.
|
||||
|
||||
If the script does not work and you're certain it's because it needs changes to
|
||||
work with Google's servers and not a user error on your part, please file a bug
|
||||
on our issue tracker!
|
||||
|
||||
## Using `meli` for `mailto:` links
|
||||
|
||||
To use `meli` to open `mailto:` links from your browser place the [`mailto-meli`](./mailto-meli) and [`mailto-meli-expect`](./mailto-meli-expect) scripts into `/usr/bin`
|
||||
(or `.local/bin`, and adjust the path in the script accordingly).
|
||||
|
||||
Ensure all scripts are executable by your user account, if not set the permissions accordingly:
|
||||
|
||||
```sh
|
||||
chmod u+x /path/to/mailto-meli
|
||||
```
|
||||
|
||||
and
|
||||
|
||||
```sh
|
||||
chmod u+x /path/to/mailto-meli-expect
|
||||
```
|
||||
|
||||
Then set `mailto-meli` as program to open `mailto` links
|
||||
in your browser.
|
||||
|
||||
E.g. in Firefox this can be done under "Settings" (`about:preferences`) which you can access from the menu button or `Edit -> Settings`.
|
||||
|
||||
```text
|
||||
General -> Applications -> Content-Type: mailto.
|
||||
```
|
||||
|
||||
You can test that it works by clicking the system menu entry `File -> Email link...`.
|
||||
|
||||
_NOTE_: that you need to have the [`expect`](https://en.wikipedia.org/wiki/Expect) binary installed for this to work.
|
||||
`expect` is a scripting language used for interactive with interactive terminal applications like `meli`.
|
|
@ -1,21 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
#
|
||||
# mailto-meli -- wrapper to use meli as mailto handler
|
||||
# To use meli as mailto: handler point your browser to use this as application for opening
|
||||
# mailto: links.
|
||||
# Note: This assumes that x-terminal-emulator supports the "-e" flag for passing along arguments.
|
||||
|
||||
# Copyright: 2024 Matthias Geiger <werdahias@debian.org>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# Check if mailto-meli and expect are present
|
||||
if ! command -v mailto-meli > /dev/null 2>&1
|
||||
then echo "mailto-meli not found" && exit 1
|
||||
else
|
||||
if ! command -v expect > /dev/null 2>&1
|
||||
then echo "expect not found" && exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
exec x-terminal-emulator -e mailto-meli-expect "$@"
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
#!/usr/bin/env -S expect -f
|
||||
# Copyright 2024 Manos Pitsidianakis
|
||||
#
|
||||
# SPDX-License-Identifier: EUPL-1.2 OR GPL-3.0-or-later
|
||||
|
||||
# Trap window resize signal
|
||||
trap {
|
||||
set rows [stty rows]
|
||||
set cols [stty columns]
|
||||
stty rows $rows columns $cols < $spawn_out(slave,name)
|
||||
} WINCH
|
||||
# send the input with human-like delay:
|
||||
set send_human {.001 .003 0.01 .005 .005}
|
||||
spawn meli
|
||||
send -h ":mailto "
|
||||
send -h [lindex $argv 0]
|
||||
send -h "\n"
|
||||
interact
|
875
debian/changelog
vendored
|
@ -1,876 +1,3 @@
|
|||
meli (0.8.10-1) bookworm; urgency=low
|
||||
|
||||
Highlights:
|
||||
===========
|
||||
|
||||
- added pipe-attachment command
|
||||
- added sample scripts for using meli as a mailto scheme handler in
|
||||
contrib/
|
||||
- fixed GPG encryption with libgpgme
|
||||
|
||||
Contributors in alphabetical order:
|
||||
===================================
|
||||
|
||||
- Manos Pitsidianakis
|
||||
- Matthias Geiger
|
||||
|
||||
Added
|
||||
=====
|
||||
|
||||
- 5e77821f mail/view: add pipe-attachment command in PR #540
|
||||
"mail/view: add pipe-attachment command"
|
||||
- fa896f6b contrib: add mailto: scheme handler scripts
|
||||
- 00ce9660
|
||||
melib/backends: add as_any/as_any_mut methods to BackendMailbox
|
||||
- fd243fa5 maildir: add mailbox creation tests
|
||||
- de65eec3 meli/accounts: add mailbox_by_path() tests in PR #535
|
||||
"Rework maildir mailbox path logic, add tests"
|
||||
- 6b363601 melib/gpgme: impl Display for gpgme::Key
|
||||
|
||||
Bug Fixes
|
||||
=========
|
||||
|
||||
- 60c90d75 melib/attachments: ensure MIME boundary prefixed with CRLF
|
||||
- 3433c5c3 compose/pgp: rewrite key selection logic in PR #541 "More
|
||||
gpgme/PGP fixes again"
|
||||
- 12de82e7 melib/conf: fix mutt_alias_file not being validated in PR
|
||||
#550 "Remove sealed_test dependency"
|
||||
- c8e055a7 Fix version migrations being triggered backwards in PR #557
|
||||
"Fix version migrations being triggered backwards"
|
||||
- efab99fd
|
||||
terminal: check for NO_COLOR env var without unicode validation
|
||||
- 36a63e88 melib/maildir: rewrite create_mailbox()
|
||||
- fcab855f view: ensure envelope headers are always populated in PR
|
||||
#538 "view: ensure envelope headers are always populated"
|
||||
- 84564f44 mailcap: don't drop File before opening it in PR #552
|
||||
"mailcap: don't drop File before opening it"
|
||||
|
||||
Changes
|
||||
=======
|
||||
|
||||
- ed85da51 Remove sealed_test dependency
|
||||
|
||||
Refactoring
|
||||
===========
|
||||
|
||||
- 03df2ac1 meli/utilities: add print utilities for tests
|
||||
- 18e9d5c1 conf.rs: impl From<melib::AccountSettings> for AccountConf
|
||||
- 1f2fec19 Fix 1.83.0 lints in PR #536 "CI: Add action to check for
|
||||
DCO signoffs in PRs"
|
||||
- 192ecea2 compose/gpg.rs: Fix msrv regression
|
||||
|
||||
Documentation
|
||||
=============
|
||||
|
||||
- 4a61a4b8 melib: include README.md as preamble of crate rustdocs
|
||||
- 80e53471 BUILD.md: move melib specific stuff to melib/README.md
|
||||
- 91a17ece melib/README.md: mention sqlite3-static feature
|
||||
- b77a691b meli/README.md: Add cargo features section in PR #549
|
||||
"Document cargo features in READMEs"
|
||||
- 91dc271d contrib: add a README.md file
|
||||
- 2e900be6 contrib/README.md: add section about oauth2.py
|
||||
- 07812d2c contrib/README.md: elaborate a bit about mailto in PR #545
|
||||
"Add external mailto: handler support via scripts in contrib"
|
||||
- e784e8d2 scripts: add markdown_doc_lints.py
|
||||
|
||||
Continuous Integration
|
||||
======================
|
||||
|
||||
- 77629851 CI: Add action to check for DCO signoffs in PRs
|
||||
- f944ebed CI: Add error msg when cargo-derivefmt check fails
|
||||
- d49344f9 CI: Move MSRV checks from manifest to lints in PR #553
|
||||
"ci-workflow-fixes"
|
||||
- ece6bfc2 CI: non-zero exit if cargo-derivefmt-* targets fail
|
||||
- 2257b91b CI: add actions/cache steps in PR #554 "CI: add
|
||||
actions/cache steps"
|
||||
- a1c9524f CI: fix check_dco.sh not working with other repos in PR
|
||||
#555 "CI: fix check_dco.sh not working with other repos"
|
||||
|
||||
-- Manos Pitsidianakis <manos@pitsidianak.is> Fri, 06 Dec 2024 07:03:58 +0200
|
||||
|
||||
meli (0.8.9-1) bookworm; urgency=low
|
||||
|
||||
This is mostly a fixups release.
|
||||
|
||||
Added
|
||||
=====
|
||||
|
||||
- cf16bf65 meli/sqlite3: add tests for reindexing
|
||||
- a389772d accounts: suggest tips on mailbox_by_path error
|
||||
|
||||
Bug Fixes
|
||||
=========
|
||||
|
||||
- 25f0a3f8 conf/terminal: fix serde of ProgressSpinnerSequence
|
||||
- c375b48e terminal: fix Synchronized Output response parsed as input
|
||||
in PR #523 "terminal: fix Synchronized Output response parsed as
|
||||
input"
|
||||
- b7e215f9
|
||||
melib/utils: fix test_fd_locks() on platforms without OFD support in
|
||||
PR #524 "melib/utils: fix test_fd_locks() on platforms without OFD
|
||||
support"
|
||||
- 25c32a6b meli/docs/meli.conf.examples.5: fix .Dt macro arguments
|
||||
- 18ae5848 meli: fix reindex of previously indexed account with sqlite3
|
||||
backend
|
||||
- 13e917d9 Fix some compilation errors with cfg feature attrs in PR #531
|
||||
"accounts: suggest tips on mailbox_by_path error"
|
||||
- 8c176d38 contacts/editor: fix crash on saving contact in PR #532
|
||||
"contacts/editor: fix crash on saving contact"
|
||||
- fb5a88c2
|
||||
melib/collection: ensure mailbox exists when inserting new envelopes
|
||||
in PR #529 "Small account stuff fixes"
|
||||
|
||||
Changes
|
||||
=======
|
||||
|
||||
- 7f8f1cf6 melib/gpgme bindings renewal in PR #533 "melib/gpgme
|
||||
bindings renewal"
|
||||
- 9b7825bc Update futures-util dep, remove stderrlog dep
|
||||
- 4be69360 Remove obsolete "encoding" dependency in PR #530
|
||||
"Remove/update obsolete dependencies"
|
||||
|
||||
Refactoring
|
||||
===========
|
||||
|
||||
- 5af6e059 meli/accounts: use Arc<str> for account name
|
||||
- 567270e1 melib: use Vec instead of SmallVec for search results
|
||||
- 2bd8d7ba
|
||||
conf/tests.rs: Rename test functions to follow path convention
|
||||
|
||||
Documentation
|
||||
=============
|
||||
|
||||
- 97242482 meli/docs: add meli.conf.examples to CLI and tests
|
||||
- 0f096338 README.md: Update ways to install, add gitlab mirror link
|
||||
in PR #528 "Integrate meli.conf.examples.5 into CLI and build, also
|
||||
update README with installation instructions"
|
||||
|
||||
Continuous Integration
|
||||
======================
|
||||
|
||||
- 630df308 CI: Add arm64 runners in job matrices in PR #527 "CI: Add
|
||||
arm64 runners in job matrices"
|
||||
- 49ecbb56 CI: .gitea/Makefile.lint: check if nightly exists
|
||||
|
||||
-- Manos Pitsidianakis <manos@pitsidianak.is> Wed, 27 Nov 2024 16:16:06 +0200
|
||||
|
||||
meli (0.8.8-1) bookworm; urgency=low
|
||||
|
||||
WARNING: This release contains a breaking change in the configuration
|
||||
file: a global composing option is not required anymore. Now, composing
|
||||
options are per account.
|
||||
|
||||
Added
|
||||
=====
|
||||
|
||||
- f3d59ebf accounts: add force: bool arg to load()
|
||||
- 33836a32 melib/error: add WrapResultIntoError helper trait
|
||||
- 3216324c melib/mbox: impl FromStr for MboxFormat
|
||||
- 94f345d7 Implement mailbox renaming command
|
||||
- 8d45ecc1 melib/error: add related_path field
|
||||
- bf3a4c5d error: add ErrorChainDisplay struct for better output
|
||||
- 6be5fd26
|
||||
themes: add inheritance, and use themes when initializing grids
|
||||
- 0ee7fc4d Print clickable path links with subcommands
|
||||
- aed7a60f samples: add ibm-modern theme in PR #469 "conf-refactor"
|
||||
- 4bbf446b utils: add unix file locks module
|
||||
- 6fbf569f search: add Message-ID, and other header search support
|
||||
- 26d33ce5 address: add separator argument to display_slice()
|
||||
- 32e3be8b
|
||||
sqlite3: add optional directory field in DatabaseDescription
|
||||
- dbbb1529 Add missing ComponentUnrealize handlers
|
||||
- 87d2cec9 Add sealed_test dependency
|
||||
- 604ae111 Impl From<&[u8]> for u64-based hash newtypes
|
||||
- 8205c7f5 melib: add JsContact module in PR #479 "view-filters"
|
||||
- 2af5c8b6 terminal: add QuerySynchronizedOutputSupport WIP
|
||||
- 5c4faea5 Add transpose shortcut and tests for text field
|
||||
- e9b87b2e melib/maildr: add rename_regex config option
|
||||
- 8f0e1d66 Add human-readable identifiers in temp draft files
|
||||
- 601e3711 Add vCard exports
|
||||
- 719e2eb2 listing: add customizable view divider like sidebar's in PR
|
||||
#485 "listing: add customizable view divider like sidebar's"
|
||||
- ba3ad8ed listing: always show mail_view_divider in PR #486 "listing:
|
||||
always show mail_view_divider"
|
||||
- 46b2c3b1 Add listing.thread_layout config flag in PR #487 "Add
|
||||
listing.thread_layout config flag"
|
||||
- aaea3a5a nntp: add timeout conf flag
|
||||
- d4636bcc nntp: interpret IMPLEMENTATION cap as metadata
|
||||
- 5f120309 nntp: add select_group_by_name() method
|
||||
- 9a9cd03d nntp: add NntpType::article_message_id() method
|
||||
- 7cfcbb7a Add patch_retrieve module in PR #489 "Add patch_retrieve
|
||||
module"
|
||||
- c82341f3 File: try trimming filename if ENAMETOOLONG
|
||||
- 23395491 compose/pgp: add encrypt_for_self flag
|
||||
- 0b6988b7 gpgme: add always trust flag to encrypt op
|
||||
- be3b3ef8 melib/utils: add fnmatch(3) interface
|
||||
- 32f7e50f Add version migration support
|
||||
- a6c7621c jscontact: add {created,updated} fields
|
||||
- 39592ad0 jmap: implement changing mailbox subscription
|
||||
- ca7eb792 jmap: Implement deleting email
|
||||
- b8e841bb jmap: implement mailbox deletion
|
||||
- 77e7c3df Add support for signatures in PR #500 "Add support for
|
||||
signatures"
|
||||
- dba5b68b components: add prelude module
|
||||
- f656aff0 composer: add discard-draft command
|
||||
- 789a88b2 shortcuts: add select_motion equivalent to select_entry
|
||||
- cb2dd5de listing/threaded: impl missing select functionality in PR
|
||||
#514 "listing/threaded: impl missing filter functionality"
|
||||
- c1901c96
|
||||
melib/email/compose: add Content-Type header for utf8 text plain attachments
|
||||
- 0e77bd5b
|
||||
melib/email/compose/tests: add multipart mixed attachment test in PR
|
||||
#515 "Fix incorrect multipart/mixed rendering when sending text with
|
||||
attachments under certain circumstances"
|
||||
- 7b1be139 melib: make mbox backend build by default
|
||||
- 7ff1db14 manage-mailboxes: add delete option in PR #520
|
||||
"manage-mailboxes: add delete option"
|
||||
|
||||
Bug Fixes
|
||||
=========
|
||||
|
||||
- 6b05279a Update time dep to fix 1.80.0 breakage
|
||||
- 2084ce93 Fix invalid cfg feature combinations for macos in PR #471
|
||||
"Fix invalid cfg feature combinations for macos"
|
||||
- 4707ec9f text/line_break: fix ReflowState::{No,All} break
|
||||
- 86e25bc0 sqlite: fix database reset sequence
|
||||
- 4d4e189c imap: code style fixups
|
||||
- 335cca88 listing: fix highlight_self flag off by one error in PR
|
||||
#477 "listing: fix highlight_self flag off by one error"
|
||||
- 80915832 mailto: rewrite parsing in PR #480 "mailto-rewrite"
|
||||
- 65b32e77 subcommands: Fix wrong help info in imap-shell prompt
|
||||
- d0c81749 conf::data_types: minor style and error msg fixups
|
||||
- 7dbee81d view: fix nested filter jobs never being completed
|
||||
- f78884ce melib/nntp: fix an ancient FIXME
|
||||
- e0cfe8e4 Fix compilation for 32-bit architectures in PR #492 "Fix
|
||||
compilation for 32-bit architectures"
|
||||
- 1b708a99 melib: attempt FromSql from Blob for u64 hash in PR #506
|
||||
"melib: attempt FromSql from Blob for u64 hash"
|
||||
- 6c315580 compose: fix add-attachment-file-picker
|
||||
- c6e9e424 listing/threaded: impl missing filter functionality
|
||||
- e7a164de Configure some gpgme stuff under gpgme feature
|
||||
|
||||
Changes
|
||||
=======
|
||||
|
||||
- 8e300c46 melib/jmap: call req text(). asap
|
||||
- 374ea8ba accounts: extract tests to tests.rs file
|
||||
- 7020cd66 meli: derive PartialEq/Eq for some types
|
||||
- 69065859 accounts: split mailbox to enum out of JobRequest
|
||||
- 14f2d911 melib/backends: change RefreshEvent field decl order
|
||||
- 56b1bf28 meli/accounts: batch process refresh events
|
||||
- 6513c188 melib/imap: on sync only update exists/unseen if loaded
|
||||
- a8dad317 melib/imap: renamed cache module to sync
|
||||
- 9e9c04a3 Update indexmap dep to 2.3.0
|
||||
- 2b3828d8 Update futures dependency to 0.3.30
|
||||
- 84812941
|
||||
melib/jmap: do not serialize server-set fields in Set create
|
||||
- eda6620c jmap: detect supported Auth schemes on connect in PR #467
|
||||
"jmap: detect supported Auth schemes on connect"
|
||||
- 35f12b15 embedded: prevent double-close of pty fd in PR #468
|
||||
"embedded: prevent double-close of pty fd"
|
||||
- 0bed37b5 melib: use IndexMap in conf fields
|
||||
- f3ad824d meli: use itoa to format offset indices in listings
|
||||
- 1cfb0b15 Update nix dependency to 0.29.0
|
||||
- 9c1b4424 jobs: make cancel flag an AtomicBool
|
||||
- f06a9072 jmap: fetch mailbox with receivedAt descending sort
|
||||
- 53b0d035 accounts: cancel any previous mailbox fetches
|
||||
- 60833ee5 accounts: make mailbox available as soon as possible
|
||||
- 28f45805 mail/view: try cancel env fetch on Drop
|
||||
- 2bb9b20d mail/view: do not highlight reply subjects in thread
|
||||
- a4f344b3 Use create_new to avoid overwriting files
|
||||
- d6197e8b listing: clear count modifier on Home/End
|
||||
- b798ca4a imap: return cached response in {select,examine}_mailbox()
|
||||
- 151fcebe imap: use BTreeMap for message sequence number store
|
||||
- e48fcc33 imap/protocol_parser: also populate other_headers
|
||||
- 1e11c29c imap: resync cache first when fetching a mailbox
|
||||
- 1779ad5d imap: interpret empty server response as BYE
|
||||
- 2d320688 mail/listing: pre-lookup conf values
|
||||
- 4e967280 nntp: don't needlessly select group before ARTICLE in PR
|
||||
#473 "Various"
|
||||
- 67b88d24 Update polling dependency from "2.8" to "3"
|
||||
- 14d74f36 Update smol dependency from "1" to "2"
|
||||
- b950fcea melib: Use IndexMap in VCard
|
||||
- 32acc347 view: show signature verification properly
|
||||
- ac1349b8 command: alias pwd to cwd
|
||||
- 7c056e4b Retry loading mailbox on recoverable error in PR #481
|
||||
"Retry loading mailbox on recoverable error"
|
||||
- cbafdcf7 terminal: color report WIP
|
||||
- 4a26cfa1 logging: disable tracing from output
|
||||
- 90974e7c imap: cache miss if row env hash != row hash
|
||||
- 4c44c440 melib: #[ignore] shellexpand tests
|
||||
- dc9e91df contacts/editor: Use FormButtonAction in form
|
||||
- c0511901 Update debian/meli.{docs,examples} and Cargo exclude
|
||||
- 592ce159 mbox: use Uuid::nil() as default envelope from
|
||||
- 6eeb4571 nntp: make all fields public
|
||||
- b27bac7f nntp: use DEFLATE when available by default
|
||||
- 128b959f
|
||||
nntp: prepend Newsgroups header if missing on NntpType::submit()
|
||||
- a69122f8
|
||||
pgp: use default sign/encrypt keys when no keys are selected
|
||||
- e6fa7093 view/envelope: trim headers values to 3 lines maximum
|
||||
- 7f0157a9 compose: make dialogs bigger in height in PR #490 "pgp: use
|
||||
default sign/encrypt keys when no keys are selected"
|
||||
- e032acfa view: pass filtered body to Composer as reply text in PR
|
||||
#493 "view: pass filtered body to Composer as reply text"
|
||||
- 49dcbc5e terminal: Extend Ask default actions, prompts
|
||||
- cd2e4bf3 melib/utils: vendor urn crate
|
||||
- 5915f125 backends: use IsSubscribedFn in method signatures
|
||||
- 4f927bbe nntp: properly return all nntp mailboxes
|
||||
- b930cb49 maildir: do not use rename_regex when only updating flags
|
||||
- 27486f29 Accept newer versions of base64 dependency
|
||||
- c3cac77d Update imap-codec dependency to 2.0.0-alpha.4
|
||||
- 05f404ba jobs: do not use AtomicU64 in PR #505 "jobs: do not use
|
||||
AtomicU64"
|
||||
- 46916895 melib/gpgme: s/NULL/NUL when referring to NUL byte
|
||||
- 81ace71b terminal/embedded: lift error checking earlier
|
||||
- 24114811 manage: parse scroll_{left,right} actions
|
||||
- d2559e42 imap: return all mailboxes, not just subscribed ones in PR
|
||||
#509 "compose: fix add-attachment-file-picker"
|
||||
- 320fddad melib/gpgme: disable layout tests on non-x86_64 hosts in PR
|
||||
#511 "melib/gpgme: disable layout tests on non-x86_64 hosts"
|
||||
- bcbcb012
|
||||
melib/email/compose: ensure boundary always prefixed with CRLF
|
||||
- d21c686d melib/attachments: Make AttachmentBuilder::set_raw generic
|
||||
- d5d34579 melib/email/compose/tests: normalise test fn names
|
||||
- e9ec6761 melib: make base64 dep mandatory
|
||||
- 30405216 melib: make notmuch feature depend on maildir feature
|
||||
- 35fa8e94 melib/imap: gracefully retry without DEFLATE on BYE in PR
|
||||
#517 "Fix some unrelated bugs I found while debugging build failure
|
||||
on armhf"
|
||||
|
||||
Refactoring
|
||||
===========
|
||||
|
||||
- 20d73292 melib: replace async-stream dep with async-fn-stream
|
||||
- 201081b6 meli/command: move tests to tests.rs
|
||||
- 84cfa358 conf: remove need for global send_mail setting
|
||||
- 7be8912c Cargo.tomls: make formatting more consistent
|
||||
- e6877e89 melib/jmap: refactor some parser imports
|
||||
- f7ec6d6b melib/jmap: implement mailbox rename
|
||||
- 15d24ab0 meli/jobs: refactor spawn_{blocking,specialized} to spawn()
|
||||
- 6ee148c0 Fix 1.80.0 clippy lints
|
||||
- de72bc6a melib/error.rs: move network stuff to submodule
|
||||
- a214a35c conf: refactor into submodules
|
||||
- 978cefbb Replace Escape ascii char with hex literal
|
||||
- 4b959f5c Remove pcre feature/dependency
|
||||
- 036586a2 Update serde dependency to 1.0.205
|
||||
- 191725b5
|
||||
Fix some borrow checker error/warnings from upcoming 2024 edition
|
||||
- 11798be8 Replace Envelope::message_id_display() with Display impls
|
||||
- 394236ba email/address: Refactor References struct
|
||||
- a7c73fc8 gpgme: refactor Rust interface, add tests
|
||||
- 41e1fdd5 Fix cargo-derivefmt lints
|
||||
- a44486d9 imap: fix minor clippy lint
|
||||
- 0c0f8210 Add a "move to Trash" shortcut
|
||||
- d20a9d0a Fix new clippy lints
|
||||
- e9a72072 Remove unused/obsolete plugins code and mentions
|
||||
- 2ddd28ee main.rs: always send a JobFinished event to all components
|
||||
- 571ae390 pager.rs: don't set self dirty after filter selector in PR
|
||||
#488 "view: fix nested filter jobs never being completed"
|
||||
- 6bc0caf4 melib: remove redundant get_path_hash macro
|
||||
- fc3308e4 melib: Add Mail::as_mbox() method
|
||||
- b1f24cbe view/filters: forward events on child filters
|
||||
- 1b201bf6 Remove GlobMatch trait, replace usage with Fnmatch
|
||||
- 8af003ab Rename addressbook stuff to "contacts"
|
||||
- 2069b4da errors: impl From<xdg::BaseDirectoriesError>
|
||||
- 7dee32ae contacts: refactor Card to its own module
|
||||
- 6d0d9680 jmap: move EmailObject state to Store
|
||||
- 0c590bbc contact-editor: remove empty space in PR #495 "Add version
|
||||
migration support"
|
||||
- b2200ec3 Remove unused smtp tests in PR #501 "Apply patches from
|
||||
upstream debian package"
|
||||
- ae294945 remove unused module file
|
||||
- 3558db51 Move jobs and mailbox management Components together
|
||||
- 3a931035 command: move Composer actions under TabActions
|
||||
- 441fda56 terminal: move TextPresentation trait to melib
|
||||
- ee897942 lints: deny clippy::or_fun_call
|
||||
- 0d088962 lints: Address clippy::too_long_first_doc_paragraph
|
||||
- ecc9b482 Small repo cleanups
|
||||
|
||||
Documentation
|
||||
=============
|
||||
|
||||
- a83b4176 meli.1: small fixes
|
||||
- 72dea6f3 Manpage fixes
|
||||
- a55f65e1
|
||||
meli.conf.5: Fix wrong default value type in default_header_values
|
||||
- 57b45a9c docs/historical-manpages: add DEP5 copyright file
|
||||
- 00236b86 docs: add meli.conf.examples(5) WIP
|
||||
- b88dc441 Comment out svgfeature; no need to ship it in PR #482
|
||||
"milestone/0.8.8"
|
||||
- b048c95a BUILD.md: add instructions for Android build
|
||||
- 593ed22b pgp: perform gpgme's sign+encrypt manually in PR #494 "pgp:
|
||||
perform gpgme's sign+encrypt manually"
|
||||
- 50922d97 melib/README.md: update and fix feature table
|
||||
- b912aabc docs: add examples of file picker usage in PR #516 "docs:
|
||||
add examples of file picker usage"
|
||||
|
||||
Packaging
|
||||
=========
|
||||
|
||||
- b55edd47 debian: update meli.docs and add meli.manpages
|
||||
|
||||
Miscellaneous Tasks
|
||||
===================
|
||||
|
||||
- 1232e16a scripts/make_html_manual_page.py: don't prettify
|
||||
- 6d520605 Vendor vobject crate
|
||||
- b33433e4
|
||||
Don't create backends as Box<dyn MailBackend>, but as Box<Self>
|
||||
- 2001b4dd Make subscribed_mailboxes conf val optional
|
||||
- 6cfe4da0 Enable rusqlite feature "modern_sqlite" always
|
||||
- 707a129e Coalesce repeating TUI notification messages
|
||||
- f036f95e scripts: add generate_release_changelog_entry.sh
|
||||
|
||||
Continuous Integration
|
||||
======================
|
||||
|
||||
- 4684b601 CI: remove env vars from action names in PR #458 "Minor QoL
|
||||
fixes"
|
||||
- 7419b465 CI: unpin rust version after updating time dependency in PR
|
||||
#460 "Update time dep to fix 1.80.0 breakage"
|
||||
- 77da86eb CI: Update cargo-derivefmt version
|
||||
- 1b3f2732 CI: Move build.yaml actions to Makefile.build
|
||||
- 598a70f9 CI: move lints.yaml actions to Makefile.lint
|
||||
- 7e800a8f
|
||||
CI: move manifest_lints.yaml actions to Makefile.manifest-lints
|
||||
- 98652110 CI: prepend printf commands with @
|
||||
- ad79bf84 .gitea/Makefile.lint: attempt cargo-fmt with +nightly
|
||||
|
||||
-- Manos Pitsidianakis <manos@pitsidianak.is> Tue, 19 Nov 2024 14:09:13 +0200
|
||||
meli (0.8.7-1) bookworm; urgency=low
|
||||
|
||||
Contributors in alphabetical order:
|
||||
|
||||
- Andrei Zisu
|
||||
- Damian Poddebniak
|
||||
- Herby Gillot
|
||||
- Manos Pitsidianakis
|
||||
|
||||
Added
|
||||
=====
|
||||
|
||||
- 9fcb0a04 Add cargo-deny configuration file deny.toml
|
||||
- 7e8d19af Add Envelope::sender_any
|
||||
- 9ab404c5 Add pgp signed attachment support
|
||||
- b4579075 Allow XOAUTH2 string passed as string
|
||||
- 0ffe7fa5 Add text/plain or text/html arg for text decoding
|
||||
- e107d613 Add prelude module for import cleanup
|
||||
- 7200589a Add ErrorKind::NotFound
|
||||
- 8c880dc7 Add {Error,ErrorKind}::is_recoverable()
|
||||
- eb27773b Add pager.named_filters setting
|
||||
- 84d93d65 Add support for ID extension (opt-in)
|
||||
- af6838c2 Add metadata field to MailBackendCapabilities
|
||||
- d1499242 Add From<Infallible> impl
|
||||
- 814af0e9 Add --gzipped flag to man subcommand
|
||||
- 475860c9 Accept - for stdio in `{create,test}_config`
|
||||
- 86f9b213 Add timeout conf field in validate()
|
||||
- dd525bd9 Use Error::is_recoverable
|
||||
- 6e1fea80 Show suggestions on Unauthorized error
|
||||
- 38620866 Detect DNS lookup std::io::Error
|
||||
- a330ff96 Retry on DNS failure
|
||||
- 2429f17b On invalid conf value, print what value is expected
|
||||
- 6379fbe8 Add support for Undercurl attribute
|
||||
- a13bf13f Add stub Undercurl support
|
||||
- f5f1e068 Add UIDPLUS support
|
||||
- afccebf3 Add AUTH=PLAIN support
|
||||
- 9fb5bc41 Impl AUTH=ANONYMOUS (RFC4505)
|
||||
|
||||
Bug Fixes
|
||||
=========
|
||||
|
||||
- ff3fe077 Fix new 1.79.0 clippy lints
|
||||
- 430cbdfd Fix python errors
|
||||
- e3c1656e Fix LOGINDISABLED support
|
||||
- a82d1e1e Fix RowsState::rename_env stale data
|
||||
- 8dc4465c Fix toml value ser after update of toml dependency
|
||||
- 39e903b1 Fix issues with ShellExpandTrait
|
||||
- 608301dc Expand save-to paths asap
|
||||
- 100fa8b3 Fix edge case in ShellExpandTrait
|
||||
- a85b3a08 Allow default_mailbox to be any mailbox
|
||||
- 0dc24623 Fix one by off error on menu unread count
|
||||
- 073aef86 Fix lints/errors when compiling specific feature combos
|
||||
- 12695a00 Fix MSRV breakage
|
||||
- 27ac3061 Fix tag support not being printed
|
||||
- 97af00cd Respect timeout value from user configuration
|
||||
- 824de287 Fix make_address! use
|
||||
- f2e9cac3 Use suggested minimum for maxObjectsInGet
|
||||
- 41d07fbc NewState in EmailImportResponse cannot be null
|
||||
- 197132cc Support fetching with BODY[] for buggy servers
|
||||
- 91fdef98 Return NotFound on cache miss
|
||||
- 96cc02a0 Do not use ErrorKind::Configuration
|
||||
- e96e9789 Don't discard pre-auth capabilities
|
||||
- 122a2a4d Drain event_queue when mailbox made available
|
||||
|
||||
Changes
|
||||
=======
|
||||
|
||||
- 27c4876f Prevent log flooding when drawing listing entries
|
||||
- 7bdc8f52 Highlight_self also when self is sender
|
||||
- c4f7b77a Rework attachment rendering logic with filters
|
||||
- 1cce8c11 Accept invalid "+" CRLF cont req
|
||||
- c04b593b Use BODY instead of RFC822
|
||||
- 084a222a Remove subscribed mailboxes list
|
||||
- 5b6c1aa8 Don't show all background jobs
|
||||
- f9a3b333 Return NotFound on empty FETCH
|
||||
- 15f3a3fb Retry fetch envelope only if err.is_recoverable()
|
||||
- 15eeac51 Enable dns_cache, tcp_keepalive & tcp_nodelay
|
||||
- 06437e60 Set not_yet_seen to 0 when inserting existing
|
||||
- 0b113cdb Use MELI_FEATURES in all cargo invocations
|
||||
|
||||
Refactoring
|
||||
===========
|
||||
|
||||
- 7856ea33 Transition more to imap-codec
|
||||
- 6f61176a Remove unecessary mut modifier
|
||||
- 3251e7bd Scrub skip_serializing_if from attributes
|
||||
- ebc1fa3b Move module to self dir
|
||||
- 5110813e Refactor MaildirOp and watch()
|
||||
- a9122c6e Draw with x range argument
|
||||
- 3ebf5510 Pass entire screen area when drawing overlay
|
||||
- 2dc1721a Move signal handling stuff to submodule
|
||||
- 738f7c46 Execute Opt subcommand in Opt::execute()
|
||||
- 46df4b57 Remove unused function stub
|
||||
- 52c75e92 Use HeaderName constants
|
||||
- 6da4e2ec Replace stringify! in Debug impls with type checked macro
|
||||
- 85a55ed6 Add some missing ErrorKinds to errors
|
||||
- 8b568f6e Add if_in_state argument in Set::new()
|
||||
- 1e2e3da0 Treat color input `; ;` as `; 0 ;`
|
||||
- 7c47f702 Extract test and parser modules to files
|
||||
- d40ee692 Extract tests mod from protocol_parser
|
||||
- 1e50911c Add utils module to protocol_parser
|
||||
- d3a45b34 Make default shared lib name a const
|
||||
- a9e9d952 Change termination_string arg to Option
|
||||
- fd76df78 Use MELI_CONFIG env var in mock tests
|
||||
- 8552e499 Replace std::mem::{replace,take}
|
||||
|
||||
Documentation
|
||||
=============
|
||||
|
||||
- dfc2bb43 Add link to MacPorts page for `meli`
|
||||
- 97aa6a8e Replace obsolete .Tn macro with .Em
|
||||
- a8e82a30 Add missing entries from JMAP
|
||||
|
||||
Miscellaneous Tasks
|
||||
===================
|
||||
|
||||
- bbe2cffa Add rust-bindgen's friends.sh to scripts/
|
||||
- a8956baf Update to `imap-codec` v2.0.0-alpha.1
|
||||
- c99633e1 Update futures dependency 0.3.28 -> 0.3.30
|
||||
- fe604bf0 Update "openssl" dependency to 0.10.64
|
||||
- 9daf9437 Add test_cli_subcommands.rs
|
||||
- 9f783d9a Pin assert_cmd ver to 2.0.13
|
||||
- b7da1d0f Check all targets in cargo-msrv verify test
|
||||
- 8a74920d Pin rust version to 1.79.0
|
||||
|
||||
-- Manos Pitsidianakis <manos@pitsidianak.is> Tue, 30 Jul 2024 14:21:31 +0300
|
||||
|
||||
meli (0.8.6-1) bookworm; urgency=low
|
||||
|
||||
Contributors in alphabetical order:
|
||||
|
||||
- euxane
|
||||
- Manos Pitsidianakis
|
||||
|
||||
Added
|
||||
=====
|
||||
|
||||
- 735b44f2 Add 'highlight_self' theme attribute
|
||||
- e187bb3f Add tools subcommand with smtp shell for debugging
|
||||
- 571bd984 Add proper imap-shell in tools subcommand for debugging
|
||||
- 0e1e5b9e Add support for Alternate Scroll Mode (xterm)
|
||||
- fe08d52a Add force_text_emoji_presentation option
|
||||
|
||||
Bug Fixes
|
||||
=========
|
||||
|
||||
- 3de4908d man.7 Fix typo for toggle_expand_headers
|
||||
- a8c7582f Fix ENVELOPE parsing in untagged responses
|
||||
- c65635ef Fix compilation for macos
|
||||
- 06ec2790 Fix str slice index panic
|
||||
- f2b59a76 Add RequestUrlTemplate type
|
||||
- 7eed944a Fix screwed up rfc8620 module split
|
||||
- 74a3539f Fix degenerate OOB cell access
|
||||
- e8e76970 Fix edge case with strings/linebreaking
|
||||
- 81955187 Fix decryption error not shown
|
||||
|
||||
Refactoring
|
||||
===========
|
||||
|
||||
- a9c3b151 Impl highlight_self in all index styles
|
||||
- 57e3e643 Remove excessive right padding in flags
|
||||
- a4ebe3b7 Add ErrorKind::Platform
|
||||
- 4bdfb3a3 Disable Nagle's algorithm by default
|
||||
- 4148aee5 Refactor smtp,draft errors and email tests
|
||||
- ed5a6b04 Add a symbols range to is_emoji check
|
||||
- fc1122a2 Rename to backend_mailbox.rs
|
||||
- 50ecade7 Merge rfc8620/tests.rs to tests.rs
|
||||
- a78f3f26 Move submodules to jmap/
|
||||
- f7838b1d Split to methods.rs and objects.rs
|
||||
- 74f0d12a Remove obsolete imapshell.rs and smtp_conn.rs
|
||||
- dce3852f Add capabilities module
|
||||
- 7ba7dc70 Imports cleanup in all modules
|
||||
- 45bfcf87 Minor refactors
|
||||
- 77867aee Unwrap object module
|
||||
- 33999fc6 Re-add Submission to USING
|
||||
- 6be25ac3 Don't use client field for get/posts
|
||||
|
||||
Documentation
|
||||
=============
|
||||
|
||||
- 4722d7cc Also mention server_password_command for jmap
|
||||
|
||||
Miscellaneous Tasks
|
||||
===================
|
||||
|
||||
- 2bfe6086 Hide self from "add contacts" options
|
||||
- 9ca34a68 Update MSRV to 1.70.0
|
||||
- 50ff16c4 Add LIGHT, DARK constant theme keys
|
||||
- 1abce964 Add Envelope::recipient_any method
|
||||
- 671d35e2 Update mailin-embedded dependency to 0.8.2
|
||||
- 39fbb164 Change info_message_{next,prev} shortcuts to '<, >'
|
||||
- 58d73271 Change new mail text content
|
||||
- f0d1b9cf Add ayllu mirror link
|
||||
- 3bab5324 Improve Debug impl for ContentType etc
|
||||
- e9dd6bec Comment out content
|
||||
- 8dd87c1a Add ContentType::is_text_plain()
|
||||
- 01bc62e0 Add new_plaintext method
|
||||
|
||||
-- Manos Pitsidianakis <manos@pitsidianak.is> Sat, 08 Jun 2024 11:47:40 +0300
|
||||
|
||||
meli (0.8.5-1) bookworm; urgency=low
|
||||
|
||||
Contributors in alphabetical order:
|
||||
|
||||
- Andrei Zisu
|
||||
- Ethra
|
||||
- Geert Stappers
|
||||
- Guillaume Ranquet
|
||||
- Manos Pitsidianakis
|
||||
|
||||
Added
|
||||
=====
|
||||
|
||||
- 0e3a0c4b Add safe UI widget area drawing API
|
||||
- 0114e695 Add next_search_result and previous_search_result shortcuts
|
||||
- 0b468d88 Improve Error messages
|
||||
- 5af2e1ee Add subcommand to print config file location
|
||||
- 62aee464 Add subcommand to print log file location
|
||||
- e2cdebe8 Add option to highlight self in mailing list threads
|
||||
- cd448924 Add clear-selection command
|
||||
- 3a5306e9 View manpages in pager inside meli
|
||||
- a37d5fc1 Implement a key to command mapping
|
||||
- ce4ba06c Add a flag set/unset command
|
||||
- 148f0433 Implement flag set/unset action in UI
|
||||
- 417b24cd Print invalid command on error
|
||||
- 4e941a9e Add default_mailbox setting
|
||||
- 974502c6 Impl Hash for Card
|
||||
- ba7a97e9 Add x axis scroll support
|
||||
- ccf6f9a2 Remember previous set index_style preferences
|
||||
|
||||
Bug Fixes
|
||||
=========
|
||||
|
||||
- bcec745c Fix command and status bar drawing
|
||||
- 62b8465f Fix ThreadView for new TUI API
|
||||
- 28fa66cc Fix ThreadedListing for new TUI API
|
||||
- 2c6f180d Fix macos compilation
|
||||
- 24971d19 Fix compilation with 1.70.0 cargo
|
||||
- 34a2d52e Fix rustdoc::redundant_explicit_links
|
||||
- f63774fa Fix new clippy lints (1.75)
|
||||
- 33408146 Fix feature permutation mis-compilations found with cargo-hack
|
||||
- e3351d27 Fix set unseen updating all mboxes
|
||||
- 8185f2cf Add deny clippy lints and fix them
|
||||
- 7861fb04 Fix typos found with typos tool
|
||||
- 64e60cb0 Fix select modifier regression
|
||||
- 60f26f9d Fix some old pre-intradoc rustdoc links
|
||||
- 1fe36192 Make conf validation recognize AccountSettings extra keys
|
||||
- c332c2f5 Fix new clippy lints (mostly clippy::blocks_in_conditions)
|
||||
- 070930e6 Fix auto index build when missing
|
||||
- 26928e3a Fix compilation for macos
|
||||
- 3884c0da Small typographic fixups
|
||||
- b820bd6d Remove unused imap_trace! and fix comp
|
||||
- a88b8c5e Debian/changelog warning fix
|
||||
- 4ce616ae Fix lints.yaml rustup install step
|
||||
- 264782d2 Various unimportant minor style/doc fixups
|
||||
- 475609fe Make {prev,next}_entry shortcut behavior consistent
|
||||
- a69c674c Fix new 1.77 clippy lints
|
||||
- 48cb9ee2 Fix compilation for macos
|
||||
- 8a16cf6d Fix wrong column index crash
|
||||
- bc1b6531 Fix constant redrawing
|
||||
- 29cc1bce Remove obsolete file melib/src/text/tables.rs.gz
|
||||
- ab041898 Fix new warnings for 1.78.0
|
||||
- 46e40856 Fix UIConfirmationDialog highlight printing
|
||||
- 3b93fa8e Don't draw messages above embedded terminal
|
||||
- 684fae3e Copy old content to new buf when resizing
|
||||
- 5d915baa Use Screen::resize instead of CellBuffer::resize
|
||||
- 6a66afe9 Make add contact dialog scrollable on overflow
|
||||
- aa5737a0 Prevent drawing pager on embedded mode
|
||||
- 07072e2e Prevent panic if envelope is deleted
|
||||
- 8ddd673d Update all mailboxes
|
||||
- 3691cd29 Send EnvelopeUpdate event after self.collection.update_flags()
|
||||
- 1fcb1d59 Remove rerun when build.rs changes
|
||||
- 933bf157 Ack \ as an atom
|
||||
- a1cbb198 Return Results instead of panicking
|
||||
- b5ddc397 Remove unwrap() from get_events() loop
|
||||
|
||||
Changes
|
||||
=======
|
||||
|
||||
- 61a0c3c2 Do not clear selection after action
|
||||
- 9af284b8 Don't hide unread count for mailboxes that are partly truncated
|
||||
- 35408b16 Run pager filter asynchronously
|
||||
- e80ea9c9 Changed default manpage install path
|
||||
- 742f038f Move sent_mailbox to settings
|
||||
- 86bbf1ea Refresh NotmuchMailbox counts when setting flags
|
||||
- f0866a39 Make config error more user-friendly
|
||||
- 11f3077b Add more possible values for manpage names
|
||||
- 1eca34b3 Set lowest priority to shortcut command UIEvents
|
||||
- 484712b0 Check for unrecoverable errors in is_online
|
||||
- 8ec6f220 Use ShellExpandTrait::expand in more user-provided paths
|
||||
|
||||
Refactoring
|
||||
===========
|
||||
|
||||
- 0500e451 Add missing EnvelopeRemove event handler
|
||||
- ab14f819 Make write_string_to_grid a CellBuffer method
|
||||
- e0adcdfe Move rest of methods under CellBuffer
|
||||
- 0a74c7d0 Overhaul refactor
|
||||
- 3b4acc15 Add tests
|
||||
- 7eedd860 Remove address_list! macro
|
||||
- f3e85738 Move build.rs scripts to build directory
|
||||
- 77325486 Remove on-push hooks for actions w/ run on-pr
|
||||
- 08518e1c Remove obsolete position.rs module
|
||||
- ddab3179 Move tests to tests module
|
||||
- 79520068 Remove doctests, add tests module
|
||||
- 4e7b6656 Sqlite caching refactor
|
||||
- b5fd3f57 Make self.view an Option
|
||||
- a3aaec38 Remove unused imports
|
||||
- 11a0586d Remove num_cpus dependency
|
||||
- 8f3dee9b Extract mod manpages to standalone file
|
||||
- 89c7972e Add suggestions to BadValue variant
|
||||
- 35a9f33a Extract common FlagString logic
|
||||
- 1b0bdd0a Split queries and mailbox into submodules
|
||||
- 506ae9f5 Add ErrorKind::LinkedLibrary variant
|
||||
- ebe1b3da Wrap *mut struct fields in NonNull<_>
|
||||
- ca7d7bb9 Use message freeze/thaw for flag changes
|
||||
- 4026e254 Add some doc comments
|
||||
- 808aa494 Rename text_processing to text for the whole brevity thing
|
||||
- bebb473d Derive extra traits for enums
|
||||
- ab1b946f Don't print details if it's an empty string.
|
||||
- f685726e Add backtrace field to ParsingError
|
||||
- 73d5b24e Merge integration tests in one crate
|
||||
- 31401fa3 Add LazyCountSet::contains method
|
||||
- 0270db01 From<&[u8]> -> From<B: AsRef<[u9]>>
|
||||
- 873a67d0 Replace erroneous use of set_err_kind with set_kind
|
||||
- 51e3f163 Use Url instead of String in deserializing
|
||||
- 8014af25 Reduce debug prints
|
||||
- f31b5c40 Don't print raw bytes as escaped unicode
|
||||
- 41e965b8 Split mbox/job stuff in submodules
|
||||
- ec01a441 Turn some sync connections to unsync
|
||||
- 3e914465 Store children process metadata
|
||||
- c53a32de Re-enables horizontal thread view
|
||||
- 36b7c00b Put doc text type names and co. in backtics
|
||||
- 634bd191 Convert log prints to traces
|
||||
- 1048ce68 Add hostname() utility function
|
||||
- 7645ff1b Rename write_string{to_grid,}
|
||||
- c2ae19d1 Return Option from current_pos
|
||||
- b61fc3ab Add HelpView struct for shortcuts widget
|
||||
- 3495ffd6 Change UIEvent::Notification structure
|
||||
- 23c15261 Abstract envelope view filters away
|
||||
- 031d0f7d Add area.is_empty() checks in cell iterators
|
||||
- e37997d6 Store Link URL value in Link type
|
||||
- b6f769b2 Add field names to row_attr! bool values
|
||||
- 0da97dd8 Check row_updates in is_dirty()
|
||||
- 6506fffb Rewrite email flag modifications
|
||||
- 23507932 Update cache on set_flags
|
||||
- 470cae6b Update thread cache on email flag modifications
|
||||
- 84f3641e Re-add on-screen message display
|
||||
- 54d21f25 Re-add contact list and editor support
|
||||
- 458258e1 Re-enable compact listing style
|
||||
- 1c1be7d6 Add display_name(), display_slice(), display_name_slice() methods
|
||||
- 5dd71ef1 Upgrade JobsView component to new TUI API
|
||||
- b5cc2a09 Upgrade MailboxManager component to new TUI API
|
||||
- ed8a5de2 Re-enable EditAttachments component
|
||||
- 77a8d9e2 Make ModSequence publicly accessible
|
||||
- 64898a05 Make UIDStore constructor pub
|
||||
|
||||
Documentation
|
||||
=============
|
||||
|
||||
- e4818803 Various manpage touchups and URL updates
|
||||
- 38bca8f8 Mention use_oauth2=true for gmail oauth2
|
||||
- 660022ce Add mailaddr.7 manpage
|
||||
- c5e9e676 Add historical-manpages dir
|
||||
- 5afc0785 Update README.md, DEVELOPMENT.md and create BUILD.md
|
||||
- d018f07a Retouch manual pages
|
||||
- 3adba40e Add macos manpage mirror url
|
||||
|
||||
Packaging
|
||||
=========
|
||||
|
||||
- cd2ba80f Update metadata
|
||||
- 5f8d7c80 Update deb-dist target command with author metadata
|
||||
- 59c99fdc Update debian package metadata
|
||||
- 97eb6363 Add dpkg --print-architecture to deb filename
|
||||
- 7412c238 Bump meli version to 0.8.5-rc.3
|
||||
- 500fe7f7 Update CHANGELOG.md
|
||||
- 5ff4e8ae Run builds.yaml when any manifest file changes
|
||||
- 0a617410 Split test.yaml to test.yaml and lints.yaml
|
||||
- 3ba1603a Add manifest file only lints workflow
|
||||
- 1617212c Add scripts/check_debian_changelog.sh lint
|
||||
- c41f35fd Use actions/checkout@v3
|
||||
- 876616d4 Use actions/upload-artifact@v3
|
||||
- 2419f4bd Add debian package build workflow
|
||||
- 10c3b0ea Bump version to 0.8.5-rc.1
|
||||
- d16afc7d Bump version to 0.8.5-rc.2
|
||||
- da251455 Bump meli version to 0.8.5-rc.2
|
||||
|
||||
Miscellaneous Tasks
|
||||
===================
|
||||
|
||||
- c4344529 Add .git-blame-ignore-revs file
|
||||
- f70496f1 Add codemeta.json
|
||||
- b3079715 Disable flakey test_smtp()
|
||||
- 8a95febb Set debuginfo=0 in test/lint builds
|
||||
- 81d1c053 Add mandoc_lint.sh
|
||||
- 8de8addd Add cfg for musl builds
|
||||
- 70fc2b45 Update nix dependency to 0.27
|
||||
- fd64fe0b Update codeberg.org URL
|
||||
- 30a3205e Add clippy::doc_markdown
|
||||
- c7aee725 Add clippy::doc_markdown
|
||||
- b8b24282 Update all instances of old domains with meli-email.org
|
||||
- ae96038f Make unicode-segmentation a hard dependency
|
||||
- 255e9376 Update linkify dep from 0.8.1 to 0.10.0
|
||||
- dedee908 Update notify dep from 4.0.17 to 6.1.1
|
||||
- c1c41c91 Update README.md and add Codeberg mirror
|
||||
- 71f3ffe7 Update Makefile
|
||||
- 63a63253 Use type alias for c_char
|
||||
- c751b2e8 Re-enable conversations listing style
|
||||
- 3a709794 Update minimum rust version from 1.65.0 to 1.68.2
|
||||
- f900dbea Use cargo-derivefmt to sort derives alphabetically
|
||||
- e19f3e57 Cargo-sort all Cargo.toml files
|
||||
|
||||
-- Manos Pitsidianakis <manos@pitsidianak.is> Sun, 05 May 2024 18:46:42 +0300
|
||||
|
||||
meli (0.8.5-rc.3-1) bookworm; urgency=low
|
||||
|
||||
* Update to 0.8.5-rc.3
|
||||
|
||||
-- Manos Pitsidianakis <manos@pitsidianak.is> Sun, 10 Dec 2023 15:22:18 +0000
|
||||
|
||||
meli (0.8.5-rc.2-1) bookworm; urgency=low
|
||||
|
||||
* Update to 0.8.5-rc.2
|
||||
|
||||
-- Manos Pitsidianakis <manos@pitsidianak.is> Mon, 4 Dec 2023 19:34:00 +0200
|
||||
|
||||
meli (0.8.4-1) bookworm; urgency=low
|
||||
|
||||
* Update to 0.8.4
|
||||
|
||||
-- Manos Pitsidianakis <manos@pitsidianak.is> Mon, 27 Nov 2023 19:34:00 +0200
|
||||
|
||||
meli (0.7.2-1) bullseye; urgency=low
|
||||
Added
|
||||
|
||||
|
@ -898,7 +25,7 @@ meli (0.7.1-1) bullseye; urgency=low
|
|||
- melib/nntp: implement refresh
|
||||
- melib/nntp: update total/new counters on new articles
|
||||
- melib/nntp: implement NNTP posting
|
||||
- configs: throw error on extra unused conf flags in some imap/nntp
|
||||
- configs: throw error on extra unusued conf flags in some imap/nntp
|
||||
- configs: throw error on missing `composing` section with explanation
|
||||
|
||||
Fixed
|
||||
|
|
1
debian/compat
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
11
|
18
debian/control
vendored
|
@ -1,20 +1,14 @@
|
|||
Source: meli
|
||||
Section: mail
|
||||
Priority: optional
|
||||
Maintainer: Manos Pitsidianakis <manos@pitsidianak.is>
|
||||
Build-Depends: debhelper-compat (=13), mandoc (>=1.14.4-1), quilt, libsqlite3-dev
|
||||
Maintainer: Manos Pitsidianakis <epilys@nessuent.xyz>
|
||||
Build-Depends: debhelper (>=11~), mandoc (>=1.14.4-1)
|
||||
Standards-Version: 4.1.4
|
||||
Rules-Requires-Root: no
|
||||
Vcs-Git: https://git.meli-email.org/meli/meli.git
|
||||
Vcs-Browser: https://git.meli-email.org/meli/meli
|
||||
Homepage: https://meli-email.org
|
||||
Homepage: https://meli.delivery
|
||||
|
||||
Package: meli
|
||||
Architecture: any
|
||||
Multi-Arch: foreign
|
||||
Depends: ${misc:Depends}, ${shlibs:Depends}
|
||||
Recommends: xdg-utils (>=1.1.3-1), w3m, mailcap
|
||||
Suggests: libnotmuch5, notmuch, rss2email, xterm, neovim, msmtp
|
||||
Provides: mail-reader, imap-client
|
||||
Description: terminal mail client.
|
||||
meli supports mbox, maildir, IMAP, JMAP, notmuch and NNTP (Usernet) with
|
||||
TLS/SSL, SASL, GPG features.
|
||||
Recommends: libnotmuch, xdg-utils (>=1.1.3-1)
|
||||
Description: terminal mail client
|
||||
|
|
4
debian/copyright
vendored
|
@ -1,11 +1,11 @@
|
|||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: meli
|
||||
Source: https://git.meli-email.org/meli/meli
|
||||
Source: <https://git.meli.delivery/meli/meli>
|
||||
#
|
||||
# Please double check copyright with the licensecheck(1) command.
|
||||
|
||||
Files: *
|
||||
Copyright: 2017-2023 Manos Pitsidianakis
|
||||
Copyright: 2017-2020 Manos Pitsidianakis
|
||||
License: GPL-3.0+
|
||||
#----------------------------------------------------------------------------
|
||||
# License file: COPYING
|
||||
|
|
8
debian/extra/meli.desktop
vendored
|
@ -1,8 +0,0 @@
|
|||
[Desktop Entry]
|
||||
Name=meli
|
||||
Exec=meli
|
||||
Categories=Office;Network;Email;
|
||||
Comment=Terminal mail client
|
||||
NoDisplay=false
|
||||
Terminal=true
|
||||
Type=Application
|
7
debian/meli.bug-presubj
vendored
|
@ -1,7 +0,0 @@
|
|||
WARNING: This package is not distributed by debian, it was generated from the source repository of meli.
|
||||
|
||||
Please do not report bugs to debian, but to the appropriate issue tracker for meli:
|
||||
|
||||
- https://git.meli-email.org/meli/meli/issues
|
||||
- Send e-mail to the mailing list, "meli general" <meli-general@meli-email.org>
|
||||
https://lists.meli-email.org/list/meli-general/
|
9
debian/meli.bug-script
vendored
|
@ -1,9 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
echo "Including output of \`meli -v\` and \`meli compiled-with\`..."
|
||||
|
||||
LC_ALL=C meli -v >&3
|
||||
|
||||
echo "\nEnabled compile-time features"
|
||||
echo "-----------------------------"
|
||||
LC_ALL=C meli compiled-with >&3 || true
|
5
debian/meli.doc-base
vendored
|
@ -1,5 +0,0 @@
|
|||
Document: meli
|
||||
Title: meli E-mail Client Manual
|
||||
Author: Various
|
||||
Abstract: Manual for meli the terminal e-mail client.
|
||||
Section: Network/Communication
|
7
debian/meli.docs
vendored
|
@ -1,4 +1,3 @@
|
|||
meli/docs/*.1
|
||||
meli/docs/*.5
|
||||
meli/docs/*.7
|
||||
meli/docs/external-tools.md
|
||||
docs/meli.1
|
||||
docs/meli.conf.5
|
||||
docs/meli-themes.5
|
||||
|
|
3
debian/meli.examples
vendored
|
@ -1,3 +0,0 @@
|
|||
contrib/*
|
||||
meli/docs/mail.vim
|
||||
meli/docs/samples/*
|
3
debian/meli.manpages
vendored
|
@ -1,3 +0,0 @@
|
|||
meli/docs/*.1
|
||||
meli/docs/*.5
|
||||
meli/docs/*.7
|
14
debian/patches/fix-prefix-for-debian.patch
vendored
|
@ -1,12 +1,10 @@
|
|||
Description: Fix PREFIX env var in Makefile for use in Debian
|
||||
Author: Manos Pitsidianakis <manos@pitsidianak.is>
|
||||
Last-Update: 2024-11-19
|
||||
Index: meli/Makefile
|
||||
===================================================================
|
||||
--- meli.orig/Makefile
|
||||
+++ meli/Makefile
|
||||
@@ -20,7 +20,7 @@
|
||||
.SUFFIXES:
|
||||
Author: Manos Pitsidianakis <epilys@nessuent.xyz>
|
||||
Last-Update: 2020-01-30
|
||||
--- a/Makefile
|
||||
+++ b/Makefile
|
||||
@@ -18,7 +18,7 @@
|
||||
# along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# Options
|
||||
-PREFIX ?= /usr/local
|
||||
|
|
1
debian/patches/series
vendored
|
@ -1,2 +1 @@
|
|||
fix-prefix-for-debian.patch
|
||||
usr_bin_editor.patch
|
||||
|
|
18
debian/patches/usr_bin_editor.patch
vendored
|
@ -1,18 +0,0 @@
|
|||
Description: If EDITOR or VISUAL is not set, fall back to /usr/bin/editor, which is set by update-alternatives.
|
||||
Author: Manos Pitsidianakis <manos@pitsidianak.is>
|
||||
Last-Update: 2024-11-19
|
||||
Index: meli/meli/src/subcommands.rs
|
||||
===================================================================
|
||||
--- meli.orig/meli/src/subcommands.rs
|
||||
+++ meli/meli/src/subcommands.rs
|
||||
@@ -56,9 +56,7 @@
|
||||
pub fn edit_config() -> Result<()> {
|
||||
let editor = std::env::var("EDITOR")
|
||||
.or_else(|_| std::env::var("VISUAL"))
|
||||
- .map_err(|err| {
|
||||
- format!("Could not find any value in environment variables EDITOR and VISUAL. {err}")
|
||||
- })?;
|
||||
+ .unwrap_or_else(|_| "/usr/bin/editor".into());
|
||||
let config_path = conf::get_config_file()?;
|
||||
|
||||
let mut cmd = Command::new(editor);
|
12
debian/rules
vendored
|
@ -1,22 +1,14 @@
|
|||
#!/usr/bin/make -f
|
||||
# You must remove unused comment lines for the released package.
|
||||
export RUSTUP_HOME=${HOME}/.rustup
|
||||
export DH_VERBOSE = 1
|
||||
export NO_MAN
|
||||
#export DH_VERBOSE = 1
|
||||
#export DEB_BUILD_MAINT_OPTIONS = hardening=+all
|
||||
#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic
|
||||
#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed
|
||||
#export MELI_FEATURES = cli-docs sqlite3
|
||||
export MELI_FEATURES = cli-docs sqlite3
|
||||
|
||||
%:
|
||||
dh $@ --with quilt
|
||||
|
||||
override_dh_auto_configure:
|
||||
true
|
||||
|
||||
override_dh_auto_test:
|
||||
true
|
||||
|
||||
#override_dh_auto_install:
|
||||
# dh_auto_install -- prefix=/usr
|
||||
|
||||
|
|
191
deny.toml
|
@ -1,191 +0,0 @@
|
|||
# cargo-deny configuration
|
||||
|
||||
[graph]
|
||||
# When creating the dependency graph used as the source of truth when checks are
|
||||
# executed, this field can be used to prune crates from the graph, removing them
|
||||
# from the view of cargo-deny. This is an extremely heavy hammer, as if a crate
|
||||
# is pruned from the graph, all of its dependencies will also be pruned unless
|
||||
# they are connected to another crate in the graph that hasn't been pruned,
|
||||
# so it should be used with care. The identifiers are [Package ID Specifications]
|
||||
# (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html)
|
||||
#exclude = []
|
||||
# If true, metadata will be collected with `--all-features`. Note that this can't
|
||||
# be toggled off if true, if you want to conditionally enable `--all-features` it
|
||||
# is recommended to pass `--all-features` on the cmd line instead
|
||||
all-features = false
|
||||
# If true, metadata will be collected with `--no-default-features`. The same
|
||||
# caveat with `all-features` applies
|
||||
no-default-features = false
|
||||
# If set, these feature will be enabled when collecting metadata. If `--features`
|
||||
# is specified on the cmd line they will take precedence over this option.
|
||||
#features = []
|
||||
|
||||
[output]
|
||||
feature-depth = 1
|
||||
|
||||
[advisories]
|
||||
ignore = [
|
||||
{ id = "RUSTSEC-2021-0145", reason = "Affects Windows, which we do not support officially." },
|
||||
#"RUSTSEC-0000-0000",
|
||||
#{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" },
|
||||
#"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
|
||||
#{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
|
||||
]
|
||||
# If this is true, then cargo deny will use the git executable to fetch advisory database.
|
||||
# If this is false, then it uses a built-in git library.
|
||||
# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support.
|
||||
# See Git Authentication for more information about setting up git authentication.
|
||||
#git-fetch-with-cli = true
|
||||
|
||||
# This section is considered when running `cargo deny check licenses`
|
||||
# More documentation for the licenses section can be found here:
|
||||
# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html
|
||||
[licenses]
|
||||
# List of explicitly allowed licenses
|
||||
# See https://spdx.org/licenses/ for list of possible licenses
|
||||
# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
|
||||
allow = [
|
||||
#"MIT",
|
||||
#"Apache-2.0",
|
||||
#"Apache-2.0 WITH LLVM-exception",
|
||||
]
|
||||
# The confidence threshold for detecting a license from license text.
|
||||
# The higher the value, the more closely the license text must be to the
|
||||
# canonical license text of a valid SPDX license file.
|
||||
# [possible values: any between 0.0 and 1.0].
|
||||
confidence-threshold = 0.8
|
||||
# Allow 1 or more licenses on a per-crate basis, so that particular licenses
|
||||
# aren't accepted for every possible crate as with the normal allow list
|
||||
exceptions = [
|
||||
# Each entry is the crate and version constraint, and its specific allow
|
||||
# list
|
||||
#{ allow = ["Zlib"], crate = "adler32" },
|
||||
]
|
||||
|
||||
# Some crates don't have (easily) machine readable licensing information,
|
||||
# adding a clarification entry for it allows you to manually specify the
|
||||
# licensing information
|
||||
#[[licenses.clarify]]
|
||||
# The package spec the clarification applies to
|
||||
#crate = "ring"
|
||||
# The SPDX expression for the license requirements of the crate
|
||||
#expression = "MIT AND ISC AND OpenSSL"
|
||||
# One or more files in the crate's source used as the "source of truth" for
|
||||
# the license expression. If the contents match, the clarification will be used
|
||||
# when running the license check, otherwise the clarification will be ignored
|
||||
# and the crate will be checked normally, which may produce warnings or errors
|
||||
# depending on the rest of your configuration
|
||||
#license-files = [
|
||||
# Each entry is a crate relative path, and the (opaque) hash of its contents
|
||||
#{ path = "LICENSE", hash = 0xbd0eed23 }
|
||||
#]
|
||||
|
||||
[licenses.private]
|
||||
# If true, ignores workspace crates that aren't published, or are only
|
||||
# published to private registries.
|
||||
# To see how to mark a crate as unpublished (to the official registry),
|
||||
# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field.
|
||||
ignore = false
|
||||
# One or more private registries that you might publish crates to, if a crate
|
||||
# is only published to private registries, and ignore is true, the crate will
|
||||
# not have its license(s) checked
|
||||
registries = [
|
||||
#"https://sekretz.com/registry
|
||||
]
|
||||
|
||||
# This section is considered when running `cargo deny check bans`.
|
||||
# More documentation about the 'bans' section can be found here:
|
||||
# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html
|
||||
[bans]
|
||||
# Lint level for when multiple versions of the same crate are detected
|
||||
multiple-versions = "warn"
|
||||
# Lint level for when a crate version requirement is `*`
|
||||
wildcards = "allow"
|
||||
# The graph highlighting used when creating dotgraphs for crates
|
||||
# with multiple versions
|
||||
# * lowest-version - The path to the lowest versioned duplicate is highlighted
|
||||
# * simplest-path - The path to the version with the fewest edges is highlighted
|
||||
# * all - Both lowest-version and simplest-path are used
|
||||
highlight = "all"
|
||||
# The default lint level for `default` features for crates that are members of
|
||||
# the workspace that is being checked. This can be overridden by allowing/denying
|
||||
# `default` on a crate-by-crate basis if desired.
|
||||
workspace-default-features = "allow"
|
||||
# The default lint level for `default` features for external crates that are not
|
||||
# members of the workspace. This can be overridden by allowing/denying `default`
|
||||
# on a crate-by-crate basis if desired.
|
||||
external-default-features = "allow"
|
||||
# List of crates that are allowed. Use with care!
|
||||
allow = [
|
||||
#"ansi_term@0.11.0",
|
||||
#{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is allowed" },
|
||||
]
|
||||
# List of crates to deny
|
||||
deny = [
|
||||
#"ansi_term@0.11.0",
|
||||
#{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" },
|
||||
# Wrapper crates can optionally be specified to allow the crate when it
|
||||
# is a direct dependency of the otherwise banned crate
|
||||
#{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] },
|
||||
]
|
||||
|
||||
# List of features to allow/deny
|
||||
# Each entry the name of a crate and a version range. If version is
|
||||
# not specified, all versions will be matched.
|
||||
#[[bans.features]]
|
||||
#crate = "reqwest"
|
||||
# Features to not allow
|
||||
#deny = ["json"]
|
||||
# Features to allow
|
||||
#allow = [
|
||||
# "rustls",
|
||||
# "__rustls",
|
||||
# "__tls",
|
||||
# "hyper-rustls",
|
||||
# "rustls",
|
||||
# "rustls-pemfile",
|
||||
# "rustls-tls-webpki-roots",
|
||||
# "tokio-rustls",
|
||||
# "webpki-roots",
|
||||
#]
|
||||
# If true, the allowed features must exactly match the enabled feature set. If
|
||||
# this is set there is no point setting `deny`
|
||||
#exact = true
|
||||
|
||||
# Certain crates/versions that will be skipped when doing duplicate detection.
|
||||
skip = [
|
||||
#"ansi_term@0.11.0",
|
||||
#{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" },
|
||||
]
|
||||
# Similarly to `skip` allows you to skip certain crates during duplicate
|
||||
# detection. Unlike skip, it also includes the entire tree of transitive
|
||||
# dependencies starting at the specified crate, up to a certain depth, which is
|
||||
# by default infinite.
|
||||
skip-tree = [
|
||||
#"ansi_term@0.11.0", # will be skipped along with _all_ of its direct and transitive dependencies
|
||||
#{ crate = "ansi_term@0.11.0", depth = 20 },
|
||||
]
|
||||
|
||||
# This section is considered when running `cargo deny check sources`.
|
||||
# More documentation about the 'sources' section can be found here:
|
||||
# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html
|
||||
[sources]
|
||||
# Lint level for what to happen when a crate from a crate registry that is not
|
||||
# in the allow list is encountered
|
||||
unknown-registry = "warn"
|
||||
# Lint level for what to happen when a crate from a git repository that is not
|
||||
# in the allow list is encountered
|
||||
unknown-git = "warn"
|
||||
# List of URLs for allowed crate registries. Defaults to the crates.io index
|
||||
# if not specified. If it is specified but empty, no registries are allowed.
|
||||
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
|
||||
# List of URLs for allowed Git repositories
|
||||
allow-git = []
|
||||
|
||||
[sources.allow-org]
|
||||
# 1 or more github.com organizations to allow git sources for
|
||||
github = [""]
|
||||
# 1 or more gitlab.com organizations to allow git sources for
|
||||
gitlab = [""]
|
||||
# 1 or more bitbucket.org organizations to allow git sources for
|
||||
bitbucket = [""]
|
|
@ -17,30 +17,29 @@
|
|||
.\" You should have received a copy of the GNU General Public License
|
||||
.\" along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
.\"
|
||||
.\".Dd November 11, 2022
|
||||
.Dd March 10, 2024
|
||||
.Dd January 23, 2020
|
||||
.Dt MELI-THEMES 5
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm meli-themes
|
||||
.Nd themes for the
|
||||
.Xr meli 1
|
||||
terminal e-mail client
|
||||
.Nm meli
|
||||
mail client
|
||||
.Sh SYNOPSIS
|
||||
.Nm meli
|
||||
comes with two themes,
|
||||
.Ic dark
|
||||
(default) and
|
||||
.Ic light .
|
||||
.Pp
|
||||
.sp
|
||||
Custom themes are defined as lists of key-values in the configuration files:
|
||||
.Bl -item -compact -offset 2
|
||||
.Bl -bullet -compact
|
||||
.It
|
||||
.Pa $XDG_CONFIG_HOME/meli/config.toml
|
||||
.It
|
||||
.Pa $XDG_CONFIG_HOME/meli/themes/*.toml
|
||||
.El
|
||||
.Pp
|
||||
.sp
|
||||
The application theme is defined in the configuration as follows:
|
||||
.Bd -literal
|
||||
[terminal]
|
||||
|
@ -57,9 +56,9 @@ keys are settings for the
|
|||
.Ic compact
|
||||
mail listing style.
|
||||
A setting contains three fields: fg for foreground color, bg for background color, and attrs for text attribute.
|
||||
.Pp
|
||||
.sp
|
||||
.Dl \&"widget.key.label\&" = { fg = \&"Default\&", bg = \&"Default\&", attrs = \&"Default\&" }
|
||||
.Pp
|
||||
.sp
|
||||
Each field contains a value, which may be either a color/attribute, a link (key name) or a valid alias.
|
||||
An alias is a string starting with the \&"\&$\&" character and must be declared in advance in the
|
||||
.Ic color_aliases
|
||||
|
@ -70,14 +69,10 @@ An alias' value can be any valid value, including links and other aliases, as lo
|
|||
In the case of a link the setting's real value depends on the value of the referred key.
|
||||
This allows for defaults within a group of associated values.
|
||||
Cyclic references in a theme results in an error:
|
||||
.Pp
|
||||
.sp
|
||||
.Dl spooky theme contains a cycle: fg: mail.listing.compact.even -> mail.listing.compact.highlighted -> mail.listing.compact.odd -> mail.listing.compact.even
|
||||
.Pp
|
||||
Two themes are included by default,
|
||||
.Ql light
|
||||
and
|
||||
.Ql dark Ns
|
||||
\&.
|
||||
Two themes are included by default, `light` and `dark`.
|
||||
.Sh EXAMPLES
|
||||
Specific settings from already defined themes can be overwritten:
|
||||
.Bd -literal
|
||||
|
@ -105,18 +100,18 @@ Custom themes can be included in your configuration files or be saved independen
|
|||
.Pa $XDG_CONFIG_HOME/meli/themes/
|
||||
directory as TOML files.
|
||||
To start creating a theme right away, you can begin by editing the default theme keys and values:
|
||||
.Pp
|
||||
.Dl meli print-default-theme > ~/.config/meli/themes/new_theme.toml
|
||||
.Pp
|
||||
.sp
|
||||
.Dl meli --print-default-theme > ~/.config/meli/themes/new_theme.toml
|
||||
.sp
|
||||
.Pa new_theme.toml
|
||||
will now include all keys and values of the "dark" theme.
|
||||
.Pp
|
||||
.Dl meli print-loaded-themes
|
||||
.Pp
|
||||
.sp
|
||||
.Dl meli --print-loaded-themes
|
||||
.sp
|
||||
will print all loaded themes with the links resolved.
|
||||
.Sh VALID ATTRIBUTE VALUES
|
||||
Case-sensitive.
|
||||
.Bl -dash -compact
|
||||
.Bl -bullet -compact
|
||||
.It
|
||||
"Default"
|
||||
.It
|
||||
|
@ -128,8 +123,6 @@ Case-sensitive.
|
|||
.It
|
||||
"Underline"
|
||||
.It
|
||||
"Undercurl"
|
||||
.It
|
||||
"Blink"
|
||||
.It
|
||||
"Reverse"
|
||||
|
@ -140,7 +133,7 @@ Any combo of the above separated by a bitwise XOR "\&|" eg "Dim | Italics"
|
|||
.El
|
||||
.Sh VALID COLOR VALUES
|
||||
Color values are of type String with the following valid contents:
|
||||
.Bl -dash -compact
|
||||
.Bl -bullet -compact
|
||||
.It
|
||||
"Default" is the terminal default. (Case-sensitive)
|
||||
.It
|
||||
|
@ -153,10 +146,8 @@ Three character shorthand is also valid, e.g. #09c → #0099cc (Case-insensitive
|
|||
name but with some modifications (for a full table see COLOR NAMES addendum) (Case-sensitive)
|
||||
.El
|
||||
.Sh NO COLOR
|
||||
To completely disable
|
||||
.Em ANSI
|
||||
colors, there are two options:
|
||||
.Bl -dash -compact
|
||||
To completely disable ANSI colors, there are two options:
|
||||
.Bl -bullet -compact
|
||||
.It
|
||||
Set the
|
||||
.Ic use_color
|
||||
|
@ -166,27 +157,24 @@ option (section
|
|||
.It
|
||||
The
|
||||
.Ev NO_COLOR
|
||||
environmental variable, when present (regardless of its value), prevents the addition of
|
||||
.Em ANSI
|
||||
color.
|
||||
environmental variable, when present (regardless of its value), prevents the addition of ANSI color.
|
||||
When the configuration value
|
||||
.Ic use_color
|
||||
is explicitly set to true by the user,
|
||||
.Ev NO_COLOR
|
||||
is ignored.
|
||||
.El
|
||||
.Pp
|
||||
In this mode, cursor locations (i.e., currently selected entries/items) will use the
|
||||
.Ql reverse video
|
||||
.Em ANSI
|
||||
attribute to invert the terminal's default foreground/background colors.
|
||||
.sp
|
||||
In this mode, cursor locations (i.e., currently selected entries/items) will use the "reverse video" ANSI attribute to invert the terminal's default foreground/background colors.
|
||||
.Sh VALID KEYS
|
||||
.Bl -dash -compact
|
||||
.Bl -bullet -compact
|
||||
.It
|
||||
theme_default
|
||||
.It
|
||||
error_message
|
||||
.It
|
||||
email_header
|
||||
.It
|
||||
highlight
|
||||
.It
|
||||
status.bar
|
||||
|
@ -251,10 +239,6 @@ mail.listing.compact.even_highlighted
|
|||
.It
|
||||
mail.listing.compact.odd_highlighted
|
||||
.It
|
||||
mail.listing.compact.even_highlighted_selected
|
||||
.It
|
||||
mail.listing.compact.odd_highlighted_selected
|
||||
.It
|
||||
mail.listing.plain.even
|
||||
.It
|
||||
mail.listing.plain.odd
|
||||
|
@ -271,10 +255,6 @@ mail.listing.plain.even_highlighted
|
|||
.It
|
||||
mail.listing.plain.odd_highlighted
|
||||
.It
|
||||
mail.listing.plain.even_highlighted_selected
|
||||
.It
|
||||
mail.listing.plain.odd_highlighted_selected
|
||||
.It
|
||||
mail.listing.conversations
|
||||
.It
|
||||
mail.listing.conversations.subject
|
||||
|
@ -289,8 +269,6 @@ mail.listing.conversations.highlighted
|
|||
.It
|
||||
mail.listing.conversations.selected
|
||||
.It
|
||||
mail.listing.conversations.highlighted_selected
|
||||
.It
|
||||
mail.view.headers
|
||||
.It
|
||||
mail.view.headers_names
|
||||
|
@ -326,7 +304,7 @@ pager.highlight_search_current
|
|||
allbox tab(:);
|
||||
lb|lb|l|lb|lb
|
||||
l l|l|l l.
|
||||
name \(da:byte:_:name:byte \(da
|
||||
name ↓:byte:_:name:byte ↓
|
||||
Aqua:14:_:Black:0
|
||||
Aquamarine1:122:_:Maroon:1
|
||||
Aquamarine2:86:_:Green:2
|
||||
|
@ -362,7 +340,7 @@ DarkMagenta1:91:_:SpringGreen6:29
|
|||
allbox tab(:);
|
||||
lb|lb|l|lb|lb
|
||||
l l|l|l l.
|
||||
name \(da:byte:_:name:byte \(da
|
||||
name ↓:byte:_:name:byte ↓
|
||||
DarkOliveGreen1:192:_:Turquoise4:30
|
||||
DarkOliveGreen2:155:_:DeepSkyBlue3:31
|
||||
DarkOliveGreen3:191:_:DeepSkyBlue4:32
|
||||
|
@ -398,7 +376,7 @@ DeepPink4:125:_:Grey37:59
|
|||
allbox tab(:);
|
||||
lb|lb|l|lb|lb
|
||||
l l|l|l l.
|
||||
name \(da:byte:_:name:byte \(da
|
||||
name ↓:byte:_:name:byte ↓
|
||||
DeepPink6:162:_:MediumPurple6:60
|
||||
DeepPink7:89:_:SlateBlue2:61
|
||||
DeepPink8:53:_:SlateBlue3:62
|
||||
|
@ -434,7 +412,7 @@ Grey19:236:_:DeepPink7:89
|
|||
allbox tab(:);
|
||||
lb|lb|l|lb|lb
|
||||
l l|l|l l.
|
||||
name \(da:byte:_:name:byte \(da
|
||||
name ↓:byte:_:name:byte ↓
|
||||
Grey23:237:_:DarkMagenta:90
|
||||
Grey27:238:_:DarkMagenta1:91
|
||||
Grey3:232:_:DarkViolet1:92
|
||||
|
@ -470,7 +448,7 @@ HotPink2:169:_:LightGreen:119
|
|||
allbox tab(:);
|
||||
lb|lb|l|lb|lb
|
||||
l l|l|l l.
|
||||
name \(da:byte:_:name:byte \(da
|
||||
name ↓:byte:_:name:byte ↓
|
||||
HotPink3:132:_:LightGreen1:120
|
||||
HotPink4:168:_:PaleGreen1:121
|
||||
IndianRed:131:_:Aquamarine1:122
|
||||
|
@ -506,7 +484,7 @@ LightSlateGrey:103:_:DarkOliveGreen6:149
|
|||
allbox tab(:);
|
||||
lb|lb|l|lb|lb
|
||||
l l|l|l l.
|
||||
name \(da:byte:_:name:byte \(da
|
||||
name ↓:byte:_:name:byte ↓
|
||||
LightSteelBlue:147:_:DarkSeaGreen6:150
|
||||
LightSteelBlue1:189:_:DarkSeaGreen3:151
|
||||
LightSteelBlue3:146:_:LightCyan3:152
|
||||
|
@ -542,7 +520,7 @@ NavajoWhite3:144:_:LightGoldenrod3:179
|
|||
allbox tab(:);
|
||||
lb|lb|l|lb|lb
|
||||
l l|l|l l.
|
||||
name \(da:byte:_:name:byte \(da
|
||||
name ↓:byte:_:name:byte ↓
|
||||
Navy:4:_:Tan:180
|
||||
NavyBlue:17:_:MistyRose3:181
|
||||
Olive:3:_:Thistle3:182
|
||||
|
@ -578,7 +556,7 @@ Purple5:55:_:Salmon1:209
|
|||
allbox tab(:);
|
||||
lb|lb|l|lb|lb
|
||||
l l|l|l l.
|
||||
name \(da:byte:_:name:byte \(da
|
||||
name ↓:byte:_:name:byte ↓
|
||||
Red:9:_:LightCoral:210
|
||||
Red1:196:_:PaleVioletRed1:211
|
||||
Red2:124:_:Orchid2:212
|
||||
|
@ -614,7 +592,7 @@ Tan:180:_:Grey30:239
|
|||
allbox tab(:);
|
||||
lb|lb|l|lb|lb
|
||||
l l|l|l l.
|
||||
name \(da:byte:_:name:byte \(da
|
||||
name ↓:byte:_:name:byte ↓
|
||||
Teal:6:_:Grey35:240
|
||||
Thistle1:225:_:Grey39:241
|
||||
Thistle3:182:_:Grey42:242
|
||||
|
@ -635,34 +613,15 @@ Yellow6:148:_:Grey93:255
|
|||
.Sh SEE ALSO
|
||||
.Xr meli 1 ,
|
||||
.Xr meli.conf 5
|
||||
.Sh STANDARDS
|
||||
.Bl -item -compact
|
||||
.It
|
||||
.Lk https://toml.io/en/v0.5.0 "TOML Standard v.0.5.0"
|
||||
.It
|
||||
.Lk https://no\-color.org/ "NO_COLOR: disabling ANSI color output by default"
|
||||
.El
|
||||
.Sh CONFORMING TO
|
||||
TOML Standard v.0.5.0 https://toml.io/en/v0.5.0
|
||||
.sp
|
||||
https://no-color.org/
|
||||
.Sh AUTHORS
|
||||
Copyright 2017\(en2024
|
||||
.An Manos Pitsidianakis Aq Mt manos@pitsidianak.is
|
||||
.Pp
|
||||
Copyright 2017-2019
|
||||
.An Manos Pitsidianakis Aq epilys@nessuent.xyz
|
||||
Released under the GPL, version 3 or greater.
|
||||
This software carries no warranty of any kind.
|
||||
.Po
|
||||
See
|
||||
.Pa COPYING
|
||||
for full copyright and warranty notices.
|
||||
.Pc
|
||||
.Ss Links
|
||||
.Bl -item -compact
|
||||
.It
|
||||
.Lk https://meli\-email.org "Website"
|
||||
.It
|
||||
.Lk https://git.meli\-email.org/meli/meli "Main\ git\ repository\ and\ issue\ tracker"
|
||||
.It
|
||||
.Lk https://codeberg.org/meli/meli "Official\ read-only\ git\ mirror\ on\ codeberg.org"
|
||||
.It
|
||||
.Lk https://github.com/meli/meli "Official\ read-only\ git\ mirror\ on\ github.com"
|
||||
.It
|
||||
.Lk https://crates.io/crates/meli "meli\ crate\ on\ crates.io"
|
||||
.El
|
||||
(See COPYING for full copyright and warranty notices.)
|
||||
.Pp
|
||||
.Aq https://meli.delivery
|
603
docs/meli.1
Normal file
|
@ -0,0 +1,603 @@
|
|||
.\" meli - meli.1
|
||||
.\"
|
||||
.\" Copyright 2017-2019 Manos Pitsidianakis
|
||||
.\"
|
||||
.\" This file is part of meli.
|
||||
.\"
|
||||
.\" meli is free software: you can redistribute it and/or modify
|
||||
.\" it under the terms of the GNU General Public License as published by
|
||||
.\" the Free Software Foundation, either version 3 of the License, or
|
||||
.\" (at your option) any later version.
|
||||
.\"
|
||||
.\" meli is distributed in the hope that it will be useful,
|
||||
.\" but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
.\" GNU General Public License for more details.
|
||||
.\"
|
||||
.\" You should have received a copy of the GNU General Public License
|
||||
.\" along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
.\"
|
||||
.Dd July 29, 2019
|
||||
.Dt MELI 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm meli
|
||||
.Nd Meli Mail User Agent. meli is the Greek word for honey
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl -help | h
|
||||
.Op Fl -version | v
|
||||
.Op Fl -config Ar path
|
||||
.Bl -tag -width flag -offset indent
|
||||
.It Fl -help | h
|
||||
Show help message and exit.
|
||||
.It Fl -version | v
|
||||
Show version and exit.
|
||||
.It Fl -config Ar path
|
||||
Start meli with given configuration file.
|
||||
.It Cm create-config Op Ar path
|
||||
Create configuration file in
|
||||
.Pa path
|
||||
if given, or at
|
||||
.Pa $XDG_CONFIG_HOME/meli/config.toml
|
||||
.It Cm test-config Op Ar path
|
||||
Test a configuration file for syntax issues or missing options.
|
||||
.It Cm man Op Ar page
|
||||
Print documentation page and exit (Piping to a pager is recommended.)
|
||||
.It Cm print-default-theme
|
||||
Print default theme keys and values in TOML syntax, to be used as a blueprint.
|
||||
.It Cm print-loaded-themes
|
||||
Print all loaded themes in TOML syntax.
|
||||
.It Cm compiled-with
|
||||
Print compile time feature flags of this binary.
|
||||
.It Cm view
|
||||
View mail from input file.
|
||||
.El
|
||||
.Sh DESCRIPTION
|
||||
.Nm
|
||||
is a terminal mail client aiming for extensive and user-frendly configurability.
|
||||
.Bd -literal
|
||||
^^ .-=-=-=-. ^^
|
||||
^^ (`-=-=-=-=-`) ^^
|
||||
(`-=-=-=-=-=-=-`) ^^ ^^
|
||||
^^ (`-=-=-=-=-=-=-=-`) ^^
|
||||
( `-=-=-=-(@)-=-=-` ) ^^
|
||||
(`-=-=-=-=-=-=-=-=-`) ^^
|
||||
(`-=-=-=-=-=-=-=-=-`) ^^
|
||||
(`-=-=-=-=-=-=-=-=-`)
|
||||
^^ (`-=-=-=-=-=-=-=-=-`) ^^
|
||||
^^ (`-=-=-=-=-=-=-=-`) ^^
|
||||
(`-=-=-=-=-=-=-`) ^^
|
||||
^^ (`-=-=-=-=-`)
|
||||
`-=-=-=-=-` ^^
|
||||
.Ed
|
||||
.Sh STARTING WITH meli
|
||||
When launched for the first time,
|
||||
.Nm
|
||||
will search for its configuration directory,
|
||||
.Pa $XDG_CONFIG_HOME/meli/ Ns
|
||||
\&.
|
||||
If it doesn't exist, you will be asked if you want to create one and presented with a sample configuration file
|
||||
.Pq Pa $XDG_CONFIG_HOME/meli/config.toml
|
||||
that includes the basic settings required for setting up accounts allowing you to copy and edit right away.
|
||||
See
|
||||
.Xr meli.conf 5
|
||||
for the available configuration options.
|
||||
.Pp
|
||||
At any time, you may press
|
||||
.Cm \&?
|
||||
for a searchable list of all available actions and shortcuts, along with every possible setting and command that your version supports.
|
||||
.Pp
|
||||
The main visual navigation tool, the left-side sidebar may be toggled with
|
||||
.Cm `
|
||||
(shortcuts.listing:
|
||||
.Ic toggle_menu_visibility Ns
|
||||
).
|
||||
.Pp
|
||||
Each mailbox may be viewed in 4 modes:
|
||||
Plain views each mail individually, Threaded shows their thread relationship visually, Conversations collapses each thread of emails into a single entry, Compact shows one row per thread.
|
||||
.Pp
|
||||
If you're using a light color palette in your terminal, you should set
|
||||
.Em theme = "light"
|
||||
in the
|
||||
.Em terminal
|
||||
section of your configuration.
|
||||
See
|
||||
.Xr meli-themes 5
|
||||
for complete documentation on user themes.
|
||||
.Sh VIEWING MAIL
|
||||
Open attachments by typing their index in the attachments list and then
|
||||
.Cm a
|
||||
.Po
|
||||
shortcut
|
||||
.Ic open_attachment
|
||||
.Pc .
|
||||
.Nm
|
||||
will attempt to open text inside its pager, and other content via
|
||||
.Cm xdg-open Ns
|
||||
\&.
|
||||
Press
|
||||
.Cm m
|
||||
.Po
|
||||
shortcut
|
||||
.Ic open_mailcap
|
||||
.Pc
|
||||
instead to use the mailcap entry for the MIME type of the attachment, if any.
|
||||
See
|
||||
.Sx FILES
|
||||
for the location of the mailcap files and
|
||||
.Xr mailcap 5
|
||||
for their syntax.
|
||||
You can save individual attachments with the
|
||||
.Em COMMAND
|
||||
.Cm save-attachment Ar INDEX Ar path-to-file
|
||||
where
|
||||
.Ar INDEX
|
||||
is the attachment's index in the listing.
|
||||
If the zeroth index is provided, the entire message is saved.
|
||||
If the path provided is a directory, the message is saved as an eml file with its filename set to the messages message-id.
|
||||
.Sh SEARCH
|
||||
Each e-mail storage backend has a default search method assigned.
|
||||
.Em IMAP
|
||||
uses the SEARCH command,
|
||||
.Em notmuch
|
||||
uses libnotmuch and
|
||||
.Em Maildir/mbox
|
||||
performs a slow linear search.
|
||||
It is advised to use a search backend on
|
||||
.Em Maildir/mbox
|
||||
accounts.
|
||||
.Nm Ns
|
||||
, if built with sqlite3, includes the ability to perform full text search on the following fields:
|
||||
.Em From ,
|
||||
.Em To ,
|
||||
.Em Cc ,
|
||||
.Em Bcc ,
|
||||
.Em In-Reply-To ,
|
||||
.Em References ,
|
||||
.Em Subject
|
||||
and
|
||||
.Em Date .
|
||||
The message body (in plain text human readable form) and the flags can also be queried.
|
||||
To enable sqlite3 indexing for an account set
|
||||
.Em search_backend
|
||||
to
|
||||
.Em sqlite3
|
||||
in the configuration file and to create the sqlite3 index issue command
|
||||
.Cm index Ar ACCOUNT_NAME Ns \&.
|
||||
.sp
|
||||
To search in the message body type your keywords without any special formatting.
|
||||
To search in specific fields, prepend your search keyword with "field:" like so:
|
||||
.Pp
|
||||
.D1 subject:helloooo or subject:\&"call for help\&" or \&"You remind me today of a small, Mexican chihuahua.\&"
|
||||
.Pp
|
||||
.D1 not ((from:unrealistic and (to:complex or not "query")) or flags:seen,draft)
|
||||
.Pp
|
||||
.D1 alladdresses:mailing@example.com and cc:me@example.com
|
||||
.Pp
|
||||
Boolean operators are
|
||||
.Em or Ns
|
||||
,
|
||||
.Em and
|
||||
and
|
||||
.Em not
|
||||
.Po
|
||||
alias:
|
||||
.Em \&!
|
||||
.Pc
|
||||
String keywords with spaces must be quoted.
|
||||
Quotes should always be escaped.
|
||||
.sp
|
||||
.Sy Important Notice about IMAP/JMAP
|
||||
.sp
|
||||
To prevent downloading all your messages from your IMAP/JMAP server, don't set
|
||||
.Em search_backend
|
||||
to
|
||||
.Em sqlite3 Ns
|
||||
\&.
|
||||
.Nm
|
||||
will relay your queries to the IMAP server.
|
||||
Expect a delay between query and response.
|
||||
Sqlite3 on the contrary at reasonable mailbox sizes should have a non noticable delay.
|
||||
.Ss QUERY ABNF SYNTAX
|
||||
.Bl -bullet
|
||||
.It
|
||||
.Li query = \&"(\&" query \&")\&" | from | to | cc | bcc | alladdresses | subject | flags | has_attachments | query \&"or\&" query | query \&"and\&" query | not query
|
||||
.It
|
||||
.Li not = \&"not\&" | \&"!\&"
|
||||
.It
|
||||
.Li quoted = ALPHA / SP *(ALPHA / DIGIT / SP)
|
||||
.It
|
||||
.Li term = ALPHA *(ALPHA / DIGIT) | DQUOTE quoted DQUOTE
|
||||
.It
|
||||
.Li tagname = term
|
||||
.It
|
||||
.Li flagval = \&"passed\&" | \&"replied\&" | \&"seen\&" | \&"read\&" | \&"junk\&" | \&"trash\&" | \&"trashed\&" | \&"draft\&" | \&"flagged\&" | tagname
|
||||
.It
|
||||
.Li flagterm = flagval | flagval \&",\&" flagterm
|
||||
.It
|
||||
.Li from = \&"from:\&" term
|
||||
.It
|
||||
.Li to = \&"to:\&" term
|
||||
.It
|
||||
.Li cc = \&"cc:\&" term
|
||||
.It
|
||||
.Li bcc = \&"bcc:\&" term
|
||||
.It
|
||||
.Li alladdresses = \&"alladdresses:\&" term
|
||||
.It
|
||||
.Li subject = \&"subject:\&" term
|
||||
.It
|
||||
.Li flags = \&"flags:\&" flag | \&"tags:\&" flag | \&"is:\&" flag
|
||||
.El
|
||||
.Sh TAGS
|
||||
.Nm
|
||||
supports tagging in notmuch and IMAP/JMAP backends.
|
||||
Tags can be searched with the `tags:` or `flags:` prefix in a search query, and can be modified by
|
||||
.Cm tag add TAG
|
||||
and
|
||||
.Cm tag remove TAG
|
||||
(see
|
||||
.Xr meli.conf 5 TAGS Ns
|
||||
, settings
|
||||
.Ic colors
|
||||
and
|
||||
.Ic ignore_tags
|
||||
for how to set tag colors and tag visiblity)
|
||||
.Sh COMPOSING
|
||||
.Ss Opening the message Composer tab
|
||||
To create a new mail message, press
|
||||
.Cm m
|
||||
(shortcut
|
||||
.Ic new_mail Ns
|
||||
) while viewing a mailbox.
|
||||
To reply to a mail, press
|
||||
.Cm R
|
||||
.Po
|
||||
shortcut
|
||||
.Ic reply
|
||||
.Pc .
|
||||
Both these actions open the mail composer view in a new tab.
|
||||
.Ss Editing text
|
||||
.Bl -bullet -compact
|
||||
.It
|
||||
Edit the header fields by selecting with the arrow keys and pressing
|
||||
.Cm enter
|
||||
to enter
|
||||
.Em INSERT
|
||||
mode and
|
||||
.Cm Esc
|
||||
key to exit.
|
||||
.It
|
||||
At any time you may press
|
||||
.Cm e
|
||||
(shortcut
|
||||
.Ic edit_mail Ns
|
||||
) to launch your editor (see
|
||||
.Xr meli.conf 5 COMPOSING Ns
|
||||
, setting
|
||||
.Ic editor_command
|
||||
for how to select which editor to launch).
|
||||
.It
|
||||
Your editor can be used in
|
||||
.Nm Ns
|
||||
\&'s embed terminal emulator by setting
|
||||
.Ic embed
|
||||
to
|
||||
.Em true
|
||||
in your composing settings.
|
||||
.It
|
||||
When launched, your editor captures all input until it exits or stops.
|
||||
.It
|
||||
To stop your editor and return to
|
||||
.Nm
|
||||
press Ctrl-z and to resume editing press the
|
||||
.Ic edit_mail
|
||||
command again
|
||||
.Po
|
||||
default
|
||||
.Em e
|
||||
.Pc .
|
||||
.El
|
||||
.Ss Attachments
|
||||
Attachments may be handled with the
|
||||
.Cm add-attachment Ns
|
||||
,
|
||||
.Cm remove-attachment
|
||||
commands (see below).
|
||||
.Ss Sending
|
||||
Finally, pressing
|
||||
.Cm s
|
||||
(shortcut
|
||||
.Ic send_mail Ns
|
||||
) will send your message according to your settings
|
||||
.Po
|
||||
see
|
||||
.Xr meli.conf 5 COMPOSING Ns
|
||||
, setting
|
||||
.Ic send_mail
|
||||
.Pc Ns
|
||||
\&.
|
||||
With no Draft or Sent mailbox,
|
||||
.Nm
|
||||
tries first saving mail in your INBOX and then at any other mailbox.
|
||||
On complete failure to save your draft or sent message it will be saved in your
|
||||
.Em tmp
|
||||
directory instead and you will be notified of its location.
|
||||
.Ss Drafts
|
||||
To save your draft without sending it, issue
|
||||
.Em COMMAND
|
||||
.Cm close
|
||||
and select 'save as draft'.
|
||||
.sp
|
||||
To open a draft for further editing, select your draft in the mail listing and press
|
||||
.Ic edit_mail Ns
|
||||
\&.
|
||||
.Sh CONTACTS
|
||||
.Nm
|
||||
supports two kinds of contact backends:
|
||||
.sp
|
||||
.Bl -enum -compact -offset indent
|
||||
.It
|
||||
an internal format that gets saved under
|
||||
.Pa $XDG_DATA_HOME/meli/account_name/addressbook Ns
|
||||
\&.
|
||||
.It
|
||||
vCard files (v3, v4) through the
|
||||
.Ic vcard_folder
|
||||
option in the account section.
|
||||
The path defined as
|
||||
.Ic vcard_folder
|
||||
can hold multiple vCards per file.
|
||||
They are loaded read only.
|
||||
.El
|
||||
.sp
|
||||
See
|
||||
.Xr meli.conf 5 ACCOUNTS
|
||||
for the complete account configuration values.
|
||||
.Sh MODES
|
||||
.Bl -tag -compact -width 8n
|
||||
.It NORMAL
|
||||
is the default mode
|
||||
.It COMMAND
|
||||
commands are issued in
|
||||
.Em COMMAND
|
||||
mode, by default started with
|
||||
.Cm \&:
|
||||
and exited with
|
||||
.Cm Esc
|
||||
key.
|
||||
.It EMBED
|
||||
is the mode of the embed terminal emulator
|
||||
.It INSERT
|
||||
captures all input as text input, and is exited with
|
||||
.Cm Esc
|
||||
key.
|
||||
.El
|
||||
.Ss COMMAND Mode
|
||||
.Ss Mail listing commands
|
||||
.Bl -tag -width 36n
|
||||
.It Cm set Ar plain | threaded | compact | conversations
|
||||
set the way mailboxes are displayed
|
||||
.El
|
||||
.TS
|
||||
allbox tab(:);
|
||||
lb l.
|
||||
conversations:shows one entry per thread
|
||||
compact:shows one row per thread
|
||||
threaded:shows threads as a tree structure
|
||||
plain:shows one row per mail, regardless of threading
|
||||
.TE
|
||||
.Bl -tag -width 36n
|
||||
.It Cm sort Ar subject | date \ Ar asc | desc
|
||||
sort mail listing
|
||||
.It Cm subsort Ar subject | date \ Ar asc | desc
|
||||
sorts only the first level of replies.
|
||||
.It Cm go Ar n
|
||||
where
|
||||
.Ar n
|
||||
is a mailbox prefixed with the
|
||||
.Ar n
|
||||
number in the side menu for the current account
|
||||
.It Cm toggle thread_snooze
|
||||
don't issue notifications for thread under cursor in thread listing
|
||||
.It Cm search Ar STRING
|
||||
search mailbox with
|
||||
.Ar STRING
|
||||
query.
|
||||
Escape exits search results.
|
||||
.It Cm select Ar STRING
|
||||
select threads matching
|
||||
.Ar STRING
|
||||
query.
|
||||
.It Cm set seen, set unseen
|
||||
Set seen status of message.
|
||||
.It Cm import Ar FILEPATH Ar MAILBOX_PATH
|
||||
Import mail from file into given mailbox.
|
||||
.It Cm copyto, moveto Ar MAILBOX_PATH
|
||||
Copy or move to other mailbox.
|
||||
.It Cm copyto, moveto Ar ACCOUNT Ar MAILBOX_PATH
|
||||
Copy or move to another account's mailbox.
|
||||
.It Cm delete
|
||||
Delete selected threads.
|
||||
.It Cm export-mbox Ar FILEPATH
|
||||
Export selected threads to mboxcl2 file.
|
||||
.It Cm create-mailbox Ar ACCOUNT Ar MAILBOX_PATH
|
||||
create mailbox with given path.
|
||||
Be careful with backends and separator sensitivity (eg IMAP)
|
||||
.It Cm subscribe-mailbox Ar ACCOUNT Ar MAILBOX_PATH
|
||||
subscribe to mailbox with given path
|
||||
.It Cm unsubscribe-mailbox Ar ACCOUNT Ar MAILBOX_PATH
|
||||
unsubscribe to mailbox with given path
|
||||
.It Cm rename-mailbox Ar ACCOUNT Ar MAILBOX_PATH_SRC Ar MAILBOX_PATH_DEST
|
||||
rename mailbox
|
||||
.It Cm delete-mailbox Ar ACCOUNT Ar MAILBOX_PATH
|
||||
deletes mailbox in the mail backend.
|
||||
This action is unreversible.
|
||||
.El
|
||||
.Ss Mail view commands
|
||||
.Bl -tag -width 36n
|
||||
.It Cm pipe Ar EXECUTABLE Ar ARGS
|
||||
pipe pager contents to binary
|
||||
.It Cm filter Ar EXECUTABLE Ar ARGS
|
||||
filter and display pager contents through command
|
||||
.It Cm list-post
|
||||
post in list of viewed envelope
|
||||
.It Cm list-unsubscribe
|
||||
unsubscribe automatically from list of viewed envelope
|
||||
.It Cm list-archive
|
||||
open list archive with
|
||||
.Cm xdg-open
|
||||
.El
|
||||
.Ss composing mail commands
|
||||
.Bl -tag -width 36n
|
||||
.It Cm add-attachment Ar PATH
|
||||
in composer, add
|
||||
.Ar PATH
|
||||
as an attachment
|
||||
.It Cm add-attachment < Ar CMD Ar ARGS
|
||||
in composer, pipe
|
||||
.Ar CMD Ar ARGS
|
||||
output into an attachment
|
||||
.It Cm add-attachment-file-picker
|
||||
Launch command defined in the configuration value
|
||||
.Ic file_picker_command
|
||||
in
|
||||
.Xr meli.conf 5 TERMINAL
|
||||
.It Cm add-attachment-file-picker < Ar CMD Ar ARGS
|
||||
Launch command
|
||||
.Ar CMD Ar ARGS Ns
|
||||
\&.
|
||||
The command should print file paths in stderr, separated by NULL bytes.
|
||||
.It Cm remove-attachment Ar INDEX
|
||||
remove attachment with given index
|
||||
.It Cm toggle sign
|
||||
toggle between signing and not signing this message.
|
||||
If the gpg invocation fails then the mail won't be sent.
|
||||
See
|
||||
.Xr meli.conf 5 PGP
|
||||
for PGP configuration.
|
||||
.It Cm save-draft
|
||||
saves a copy of the draft in the Draft folder
|
||||
.El
|
||||
.Ss generic commands
|
||||
.Bl -tag -width 36n
|
||||
.It Cm open-in-tab
|
||||
opens envelope view in new tab
|
||||
.It Cm close
|
||||
closes closeable tabs
|
||||
.It Cm setenv Ar KEY=VALUE
|
||||
set environment variable
|
||||
.Ar KEY
|
||||
to
|
||||
.Ar VALUE
|
||||
.It Cm printenv Ar KEY
|
||||
print environment variable
|
||||
.Ar KEY
|
||||
.It Cm quit
|
||||
Quits
|
||||
.Nm Ns
|
||||
\&.
|
||||
.It Cm reload-config
|
||||
Reloads configuration but only if account configuration is unchanged.
|
||||
Useful if you want to reload some settings without restarting
|
||||
.Nm Ns
|
||||
\&.
|
||||
.El
|
||||
.Sh SHORTCUTS
|
||||
See
|
||||
.Xr meli.conf 5 SHORTCUTS
|
||||
for shortcuts and their default values.
|
||||
.Sh EXIT STATUS
|
||||
.Nm
|
||||
exits with 0 on a successful run.
|
||||
Other exit statuses are:
|
||||
.Bl -tag -width 5n
|
||||
.It 1
|
||||
catchall for general errors
|
||||
.It 101
|
||||
process panic
|
||||
.El
|
||||
.Sh ENVIRONMENT
|
||||
.Bl -tag -width "$XDG_CONFIG_HOME/meli/plugins/*" -offset indent
|
||||
.It Ev EDITOR
|
||||
Specifies the editor to use
|
||||
.It Ev MELI_CONFIG
|
||||
Override the configuration file
|
||||
.It Ev NO_COLOR
|
||||
When present (regardless of its value), prevents the addition of ANSI color.
|
||||
The configuration value
|
||||
.Ic use_color
|
||||
overrides this.
|
||||
.El
|
||||
.Sh FILES
|
||||
.Nm
|
||||
uses the following parts of the XDG standard:
|
||||
.Bl -tag -width "$XDG_CONFIG_HOME/meli/plugins/*" -offset indent
|
||||
.It Ev XDG_CONFIG_HOME
|
||||
defaults to
|
||||
.Pa ~/.config/
|
||||
.It Ev XDG_CACHE_HOME
|
||||
defaults to
|
||||
.Pa ~/.cache/
|
||||
.El
|
||||
.Pp
|
||||
and appropriates the following locations:
|
||||
.Bl -tag -width "$XDG_CONFIG_HOME/meli/plugins/*" -offset indent
|
||||
.It Pa $XDG_CONFIG_HOME/meli/
|
||||
User configuration directory
|
||||
.It Pa $XDG_CONFIG_HOME/meli/config.toml
|
||||
User configuration file, see
|
||||
.Xr meli.conf 5
|
||||
for its syntax and values.
|
||||
.It Pa $XDG_CONFIG_HOME/meli/hooks/*
|
||||
Reserved for event hooks.
|
||||
.It Pa $XDG_CONFIG_HOME/meli/plugins/*
|
||||
Reserved for plugin files.
|
||||
.It Pa $XDG_CACHE_HOME/meli/*
|
||||
Internal cached data used by meli.
|
||||
.It Pa $XDG_DATA_HOME/meli/*
|
||||
Internal data used by meli.
|
||||
.It Pa $XDG_DATA_HOME/meli/meli.log
|
||||
Operation log.
|
||||
.It Pa /tmp/meli/*
|
||||
Temporary files generated by
|
||||
.Nm Ns
|
||||
\&.
|
||||
.El
|
||||
.Pp
|
||||
Mailcap entries are searched for in the following files, in this order:
|
||||
.Pp
|
||||
.Bl -enum -compact -offset indent
|
||||
.It
|
||||
.Pa $XDG_CONFIG_HOME/meli/mailcap
|
||||
.It
|
||||
.Pa $XDG_CONFIG_HOME/.mailcap
|
||||
.It
|
||||
.Pa $HOME/.mailcap
|
||||
.It
|
||||
.Pa /etc/mailcap
|
||||
.It
|
||||
.Pa /usr/etc/mailcap
|
||||
.It
|
||||
.Pa /usr/local/etc/mailcap
|
||||
.El
|
||||
.Sh SEE ALSO
|
||||
.Xr meli.conf 5 ,
|
||||
.Xr meli-themes 5 ,
|
||||
.Xr xdg-open 1 ,
|
||||
.Xr mailcap 5
|
||||
.Sh CONFORMING TO
|
||||
XDG Standard
|
||||
.Aq https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html Ns
|
||||
, maildir
|
||||
.Aq https://cr.yp.to/proto/maildir.html Ns
|
||||
, IMAPv4rev1 RFC3501, The JSON Meta Application Protocol (JMAP) RFC8620, The JSON Meta Application Protocol (JMAP) for Mail RFC8621.
|
||||
.Sh AUTHORS
|
||||
Copyright 2017-2019
|
||||
.An Manos Pitsidianakis Aq epilys@nessuent.xyz
|
||||
Released under the GPL, version 3 or greater.
|
||||
This software carries no warranty of any kind.
|
||||
(See COPYING for full copyright and warranty notices.)
|
||||
.Pp
|
||||
.Aq https://meli.delivery
|
1487
docs/meli.conf.5
Normal file
|
@ -11,12 +11,9 @@
|
|||
#[accounts.account-name]
|
||||
#root_mailbox = "/path/to/root/mailbox"
|
||||
#format = "Maildir"
|
||||
#send_mail = 'msmtp --read-recipients --read-envelope-from'
|
||||
##send_mail = { hostname = "smtp.example.com", port = 587, auth = { type = "auto", username = "user", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/user.gpg" } }, security = { type = "STARTTLS" } }
|
||||
#listing.index_style = "Conversations" # or [plain, threaded, compact]
|
||||
#identity="email@example.com"
|
||||
#display_name = "Name"
|
||||
## Need to explicitly list mailboxes of interest:
|
||||
#subscribed_mailboxes = ["INBOX", "INBOX/Sent", "INBOX/Drafts", "INBOX/Junk"]
|
||||
#
|
||||
## Set mailbox-specific settings
|
||||
|
@ -29,7 +26,6 @@
|
|||
#[accounts.mbox]
|
||||
#root_mailbox = "/var/mail/username"
|
||||
#format = "mbox"
|
||||
#send_mail = 'false'
|
||||
#listing.index_style = "Compact"
|
||||
#identity="username@hostname.local"
|
||||
#
|
||||
|
@ -37,7 +33,6 @@
|
|||
#[accounts."imap"]
|
||||
#root_mailbox = "INBOX"
|
||||
#format = "imap"
|
||||
#send_mail = { hostname = "smtp.example.com", port = 587, auth = { type = "auto", username = "user", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/user.gpg" } }, security = { type = "STARTTLS" } }
|
||||
#server_hostname="mail.example.com"
|
||||
#server_password="pha2hiLohs2eeeish2phaii1We3ood4chakaiv0hien2ahie3m"
|
||||
#server_username="username@example.com"
|
||||
|
@ -47,14 +42,15 @@
|
|||
#listing.index_style = "Conversations"
|
||||
#identity = "username@example.com"
|
||||
#display_name = "Name Name"
|
||||
### show only specific mailboxes, overriding the server's subscribed status.
|
||||
### match every mailbox:
|
||||
#subscribed_mailboxes = ["*" ]
|
||||
### match specific mailboxes:
|
||||
##subscribed_mailboxes = ["INBOX", "INBOX/Sent", "INBOX/Drafts", "INBOX/Junk"]
|
||||
#
|
||||
## Setting up an account for an already existing notmuch database
|
||||
##[accounts.notmuch]
|
||||
##root_mailbox = "/path/to/folder" # where .notmuch/ directory is located
|
||||
##format = "notmuch"
|
||||
##send_mail = 'msmtp --read-recipients --read-envelope-from'
|
||||
##listing.index_style = "conversations"
|
||||
##identity="username@example.com"
|
||||
##display_name = "Name Name"
|
||||
|
@ -68,7 +64,6 @@
|
|||
#[accounts."gmail"]
|
||||
#root_mailbox = '[Gmail]'
|
||||
#format = "imap"
|
||||
#send_mail = { hostname = "smtp.gmail.com", port = 587, auth = { type = "auto", username = "user", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/user.gpg" } }, security = { type = "STARTTLS" } }
|
||||
#server_hostname='imap.gmail.com'
|
||||
#server_password="password"
|
||||
#server_username="username@gmail.com"
|
||||
|
@ -76,23 +71,17 @@
|
|||
#listing.index_style = "Conversations"
|
||||
#identity = "username@gmail.com"
|
||||
#display_name = "Name Name"
|
||||
## Gmail auto saves sent mail to Sent folder, so don't duplicate the effort:
|
||||
### match every mailbox:
|
||||
#subscribed_mailboxes = ["*" ]
|
||||
#composing.send_mail = 'msmtp --read-recipients --read-envelope-from'
|
||||
### Gmail auto saves sent mail to Sent folder, so don't duplicate the effort:
|
||||
#composing.store_sent_mail = false
|
||||
#
|
||||
##[accounts."jmap account"]
|
||||
##root_mailbox = "INBOX"
|
||||
##format = "jmap"
|
||||
##send_mail = 'server_submission'
|
||||
##server_url="http://localhost:8080"
|
||||
##server_username="user@hostname.local"
|
||||
##server_password="changeme"
|
||||
##listing.index_style = "Conversations"
|
||||
##identity = "user@hostname.local"
|
||||
#
|
||||
#[pager]
|
||||
#filter = "COLUMNS=72 /usr/local/bin/pygmentize -l email"
|
||||
#pager_context = 0 # default, optional
|
||||
#sticky_headers = true # default, optional
|
||||
#headers_sticky = true # default, optional
|
||||
#
|
||||
#[notifications]
|
||||
#script = "notify-send"
|
||||
|
@ -102,7 +91,11 @@
|
|||
#
|
||||
###shortcuts
|
||||
#[shortcuts.composing]
|
||||
#edit = 'e'
|
||||
#edit_mail = 'e'
|
||||
#
|
||||
##Thread view defaults:
|
||||
#[shortcuts.compact-listing]
|
||||
#exit_thread = 'i'
|
||||
#
|
||||
#[shortcuts.contact-list]
|
||||
#create_contact = 'c'
|
||||
|
@ -118,7 +111,6 @@
|
|||
#next_account = 'h'
|
||||
#new_mail = 'm'
|
||||
#set_seen = 'n'
|
||||
#exit_entry = 'i'
|
||||
#
|
||||
##Pager defaults
|
||||
#
|
||||
|
@ -129,8 +121,12 @@
|
|||
#page_down = "PageDown"
|
||||
#
|
||||
#[composing]
|
||||
##required for sending e-mail
|
||||
#send_mail = 'msmtp --read-recipients --read-envelope-from'
|
||||
##send_mail = { hostname = "smtp.example.com", port = 587, auth = { type = "auto", username = "user", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/user.gpg" } }, security = { type = "STARTTLS" } }
|
||||
#editor_command = 'vim +/^$' # optional, by default $EDITOR is used.
|
||||
#
|
||||
#
|
||||
#[pgp]
|
||||
#auto_sign = false # always sign sent messages
|
||||
#auto_verify_signatures = true # always verify signatures when reading signed e-mails
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 204 KiB After Width: | Height: | Size: 204 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
|
@ -8,14 +8,17 @@ edition = "2018"
|
|||
[package.metadata]
|
||||
cargo-fuzz = true
|
||||
|
||||
[[bin]]
|
||||
name = "envelope_parse"
|
||||
path = "fuzz_targets/envelope_parse.rs"
|
||||
|
||||
[dependencies]
|
||||
libfuzzer-sys = "0.3"
|
||||
melib = { path = "../melib" }
|
||||
|
||||
[dependencies.melib]
|
||||
path = "../melib"
|
||||
features = ["unicode_algorithms"]
|
||||
|
||||
# Prevent this from interfering with workspaces
|
||||
[workspace]
|
||||
members = ["."]
|
||||
|
||||
[[bin]]
|
||||
name = "envelope_parse"
|
||||
path = "fuzz_targets/envelope_parse.rs"
|
||||
|
|
|
@ -1,91 +0,0 @@
|
|||
[package]
|
||||
name = "meli"
|
||||
version = "0.8.10"
|
||||
authors = ["Manos Pitsidianakis <manos@pitsidianak.is>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.70.0"
|
||||
license = "EUPL-1.2 OR GPL-3.0-or-later"
|
||||
readme = "README.md"
|
||||
description = "terminal e-mail client"
|
||||
homepage = "https://meli-email.org"
|
||||
repository = "https://git.meli-email.org/meli/meli.git"
|
||||
keywords = ["mail", "mua", "maildir", "terminal", "imap"]
|
||||
categories = ["command-line-utilities", "email"]
|
||||
default-run = "meli"
|
||||
exclude = ["/docs/historical-manpages"]
|
||||
|
||||
[[bin]]
|
||||
name = "meli"
|
||||
path = "src/main.rs"
|
||||
|
||||
[lib]
|
||||
name = "meli"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
aho-corasick = { version = "1.1.3" }
|
||||
async-task = { version = "^4.2.0" }
|
||||
bitflags = { version = "2.4", features = ["serde"] }
|
||||
crossbeam = { version = "^0.8" }
|
||||
flate2 = { version = "1", optional = true }
|
||||
futures = { version = "0.3.30", default-features = false, features = ["async-await", "executor", "std"] }
|
||||
indexmap = { version = "^2.3", default-features = false, features = ["serde", "std"] }
|
||||
itoa = { version = "1.0.11", default-features = false }
|
||||
libc = { version = "0.2.125", default-features = false, features = ["extra_traits"] }
|
||||
libz-sys = { version = "1.1", features = ["static"], optional = true }
|
||||
linkify = { version = "^0.10", default-features = false }
|
||||
melib = { path = "../melib", version = "0.8.10", features = [] }
|
||||
nix = { version = "0.29", default-features = false, features = ["signal", "poll", "term", "ioctl", "process"] }
|
||||
regex = { version = "1" }
|
||||
serde = { version = "1.0.71" }
|
||||
serde_derive = { version = "1.0.71" }
|
||||
serde_json = { version = "1.0" }
|
||||
signal-hook = { version = "^0.3", default-features = false, features = ["iterator"] }
|
||||
signal-hook-registry = { version = "1.2.0", default-features = false }
|
||||
smallvec = { version = "^1.5.0", features = ["serde"] }
|
||||
structopt = { version = "0.3.26", default-features = false }
|
||||
# svg_crate = { version = "^0.13", optional = true, package = "svg" }
|
||||
termion = { version = "1.5.1", default-features = false }
|
||||
toml = { version = "0.8", default-features = false, features = ["display","preserve_order","parse"] }
|
||||
xdg = { version = "2.1.0" }
|
||||
|
||||
[features]
|
||||
default = ["sqlite3", "notmuch", "smtp", "dbus-notifications", "gpgme", "cli-docs", "jmap", "static"]
|
||||
notmuch = ["melib/notmuch"]
|
||||
jmap = ["melib/jmap"]
|
||||
sqlite3 = ["melib/sqlite3"]
|
||||
smtp = ["melib/smtp"]
|
||||
smtp-trace = ["smtp", "melib/smtp-trace"]
|
||||
dbus-notifications = ["dep:notify-rust"]
|
||||
cli-docs = ["dep:flate2"]
|
||||
# svgscreenshot = ["dep:svg_crate"]
|
||||
gpgme = ["melib/gpgme"]
|
||||
# Static / vendoring features.
|
||||
tls-static = ["melib/tls-static"]
|
||||
http-static = ["melib/http-static"]
|
||||
sqlite3-static = ["melib/sqlite3-static"]
|
||||
dbus-static = ["dep:notify-rust", "notify-rust?/d_vendored"]
|
||||
libz-static = ["dep:libz-sys", "libz-sys?/static"]
|
||||
static = ["tls-static", "http-static", "sqlite3-static", "dbus-static", "libz-static"]
|
||||
|
||||
# Print tracing logs as meli runs in stderr
|
||||
# enable for debug tracing logs: build with --features=debug-tracing and export MELI_DEBUG_STDERR
|
||||
debug-tracing = ["melib/debug-tracing"]
|
||||
|
||||
[build-dependencies]
|
||||
flate2 = { version = "1", optional = true }
|
||||
proc-macro2 = { version = "1.0.37" }
|
||||
quote = { version = "^1.0" }
|
||||
regex = { version = "1" }
|
||||
syn = { version = "1", features = [] }
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = { version = "=2.0.13" }
|
||||
flate2 = { version = "1" }
|
||||
predicates = { version = "3" }
|
||||
regex = { version = "1" }
|
||||
rusty-fork = { version = "0.3.0" }
|
||||
tempfile = { version = "3.3" }
|
||||
|
||||
[target.'cfg(target_os="linux")'.dependencies]
|
||||
notify-rust = { version = "^4", default-features = false, features = ["dbus"], optional = true }
|
|
@ -1 +0,0 @@
|
|||
../README.md
|
|
@ -1,95 +0,0 @@
|
|||
/*
|
||||
* meli - build.rs
|
||||
*
|
||||
* Copyright 2020 Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
extern crate proc_macro;
|
||||
extern crate quote;
|
||||
extern crate syn;
|
||||
include!("config_macros.rs");
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=src/conf/.rebuild.overrides.rs");
|
||||
override_derive(&[
|
||||
("src/conf/pager.rs", "PagerSettings"),
|
||||
("src/conf/listing.rs", "ListingSettings"),
|
||||
("src/conf/notifications.rs", "NotificationsSettings"),
|
||||
("src/conf/shortcuts.rs", "Shortcuts"),
|
||||
("src/conf/composing.rs", "ComposingSettings"),
|
||||
("src/conf/tags.rs", "TagsSettings"),
|
||||
("src/conf/pgp.rs", "PGPSettings"),
|
||||
]);
|
||||
#[cfg(feature = "cli-docs")]
|
||||
{
|
||||
use flate2::{Compression, GzBuilder};
|
||||
const MANDOC_OPTS: &[&str] = &["-T", "utf8", "-I", "os=Generated by mandoc(1)"];
|
||||
use std::{env, io::prelude::*, path::Path};
|
||||
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
let mut out_dir_path = Path::new(&out_dir).to_path_buf();
|
||||
|
||||
let mut cl = |filepath: &str, output: &str, source: bool| {
|
||||
out_dir_path.push(output);
|
||||
let output = if source {
|
||||
std::fs::read_to_string(filepath).unwrap().into_bytes()
|
||||
} else {
|
||||
let output = Command::new("mandoc")
|
||||
.args(MANDOC_OPTS)
|
||||
.arg(filepath)
|
||||
.output()
|
||||
.or_else(|_| Command::new("man").arg("-l").arg(filepath).output())
|
||||
.expect(
|
||||
"could not execute `mandoc` or `man`. If the binaries are not available \
|
||||
in the PATH, disable `cli-docs` feature to be able to continue \
|
||||
compilation.",
|
||||
);
|
||||
output.stdout
|
||||
};
|
||||
|
||||
let file = File::create(&out_dir_path).unwrap_or_else(|err| {
|
||||
panic!("Could not create file {}: {}", out_dir_path.display(), err)
|
||||
});
|
||||
let mut gz = GzBuilder::new()
|
||||
.comment(output.len().to_string().into_bytes())
|
||||
.write(file, Compression::default());
|
||||
gz.write_all(&output).unwrap();
|
||||
gz.finish().unwrap();
|
||||
out_dir_path.pop();
|
||||
};
|
||||
|
||||
cl("docs/meli.1", "meli.txt.gz", false);
|
||||
cl("docs/meli.conf.5", "meli.conf.txt.gz", false);
|
||||
cl(
|
||||
"docs/meli.conf.examples.5",
|
||||
"meli.conf.examples.txt.gz",
|
||||
false,
|
||||
);
|
||||
cl("docs/meli-themes.5", "meli-themes.txt.gz", false);
|
||||
cl("docs/meli.7", "meli.7.txt.gz", false);
|
||||
cl("docs/meli.1", "meli.mdoc.gz", true);
|
||||
cl("docs/meli.conf.5", "meli.conf.mdoc.gz", true);
|
||||
cl(
|
||||
"docs/meli.conf.examples.5",
|
||||
"meli.conf.examples.mdoc.gz",
|
||||
true,
|
||||
);
|
||||
cl("docs/meli-themes.5", "meli-themes.mdoc.gz", true);
|
||||
cl("docs/meli.7", "meli.7.mdoc.gz", true);
|
||||
}
|
||||
}
|
|
@ -1,150 +0,0 @@
|
|||
# Using other apps with `meli`
|
||||
|
||||
## Sending mail with a command line tool
|
||||
|
||||
`send_mail` can use either settings for an SMTP server or a shell
|
||||
command to which it pipes new mail to.
|
||||
|
||||
### `msmtp` and `send_mail`
|
||||
|
||||
[`msmtp`][msmtp] is a command line SMTP client that can be configured to work
|
||||
with many SMTP servers. It supports queuing and other small useful features.
|
||||
See [the documentation](https://marlam.de/msmtp/msmtp.html).
|
||||
|
||||
```toml
|
||||
send_mail = 'msmtp --logfile=/home/user/.mail/msmtp.log --read-recipients
|
||||
--read-envelope-from'
|
||||
```
|
||||
[msmtp]: https://marlam.de/msmtp/
|
||||
|
||||
## Editor
|
||||
|
||||
Any editor you specify in `composing.editor_cmd` will be invoked with the
|
||||
e-mail draft file path appended as an argument to it. For example, if your
|
||||
setting is `editor_cmd = 'nano'`, `meli` will execute `nano /tmp/meli/...`.
|
||||
|
||||
### Configuration
|
||||
|
||||
#### `vim` / `neovim` command
|
||||
|
||||
The following command setting in your `meli` configuration file makes editing
|
||||
start at the first empty line, that is, after the e-mail headers. This allows
|
||||
you to start writing the e-mail body right away after opening the editor from
|
||||
`meli`.
|
||||
|
||||
```toml
|
||||
[composing]
|
||||
editor_cmd = '~/.local/bin/vim +/^$'
|
||||
```
|
||||
|
||||
In `vim`, the `+` argument positions the cursor at the first file argument. `/`
|
||||
specifies a pattern position instead of a line number. `^` specifies the start
|
||||
of a line, and `$` the end of the line. The pattern altogether matches an empty
|
||||
line, which will be after the e-mail headers.
|
||||
|
||||
### Composing with `format=flowed`
|
||||
|
||||
`format=flowed` is a proposed IETF standard[^formatflowed] that lets you
|
||||
preserve the structure of paragraphs by disambiguating a *hard* and a *soft*
|
||||
line break. A line break that is preceded by a space character is *soft* and
|
||||
does not terminate the paragraph, while a line break without a space is a
|
||||
*hard* one and creates a new paragraph. This allows text to be re-flowed in
|
||||
e-mail clients at different display widths and font sizes without messing up
|
||||
the author's formatting.
|
||||
|
||||
#### `vim` / `neovim` and `format=flowed`
|
||||
|
||||
Create a `mail.vim` file type plugin in:
|
||||
|
||||
- `$HOME/.vim/after/ftplugin/mail.vim` for vim
|
||||
- `$HOME/.config/nvim/after/ftplugin/mail.vim` for neovim
|
||||
|
||||
```vim
|
||||
setlocal nomodeline
|
||||
setlocal textwidth=72
|
||||
setlocal formatoptions=aqtw2r
|
||||
setlocal nojoinspaces
|
||||
setlocal nosmartindent
|
||||
setlocal comments+=nb:>
|
||||
match ErrorMsg '\s\+$'
|
||||
```
|
||||
|
||||
Also, don't forget that you can easily quote stuff with `MailQuote`.
|
||||
From `:help ft-mail-plugin`:
|
||||
|
||||
> Local mappings:
|
||||
> `<LocalLeader>q` or `\\MailQuote`
|
||||
> Quotes the text selected in Visual mode, or from the cursor position
|
||||
> to the end of the file in Normal mode.
|
||||
> This means "> " is inserted in each line.
|
||||
|
||||
See the accompanying [`mail.vim`](./mail.vim) for comments for each setting.
|
||||
|
||||
## `xbiff`
|
||||
|
||||
[`xbiff(1)`][xbiff] manual page says:[^xbiffmanpage]
|
||||
|
||||
> The `xbiff` program displays a little image of a mailbox. When there is no
|
||||
> mail, the flag on the mailbox is down. When mail arrives, the flag goes up
|
||||
> and the mailbox beeps.
|
||||
|
||||
This tool is very outdated, but some users might still have use for it.
|
||||
Therefore `meli` provides support (also, it's easy to support this feature).
|
||||
|
||||
Specify a file path in `notifications.xbiff_file_path` and `meli` will write to
|
||||
it when new mail arrives. This file can the be used as input to `xbiff`.
|
||||
|
||||
```toml
|
||||
[notifications]
|
||||
xbiff_file_path = "/tmp/xbiff"
|
||||
```
|
||||
|
||||
[xbiff]: https://en.wikipedia.org/wiki/Xbiff
|
||||
[^xbiffmanpage]: https://www.x.org/releases/X11R7.0/doc/html/xbiff.1.html
|
||||
|
||||
## Viewing HTML e-mail
|
||||
|
||||
By default `meli` tries to render HTML e-mail with `w3m`. You can override this
|
||||
by setting the `pager.html_filter` setting. The default setting corresponds to:
|
||||
|
||||
```toml
|
||||
[pager]
|
||||
html_filter = "w3m -I utf-8 -T text/html"
|
||||
```
|
||||
|
||||
The HTML of the e-mail is piped into `html_filter`'s standard input.
|
||||
|
||||
## Externally refreshing e-mail accounts
|
||||
|
||||
If your account's syncing is handled by an external tool, you can use the
|
||||
refresh shortcuts within `meli` to call this tool with
|
||||
`accounts.refresh_command`.
|
||||
|
||||
## Viewing binary attachments such as images inside your terminal
|
||||
|
||||
If you have a specific terminal tool that lets you pipe binary data to it and
|
||||
it outputs command suitable for the terminal, you can use the `pipe-attachment`
|
||||
command to view/preview attachments without leaving `meli` or opening a GUI app.
|
||||
|
||||
This requires the output to be interactive otherwise `meli` will run the tool
|
||||
and immediately return, probably too quickly for you to notice the output in
|
||||
your terminal. A general solution is to pipe the output to an interactive pager
|
||||
like `less` which requires the user to exit it interactively.
|
||||
|
||||
The [`chafa`] tool can be used for images in this example:
|
||||
|
||||
Write a wrapper script that outputs the tool's output into a pager, for example
|
||||
`less`. If the output contains ANSI escape codes (i.e. colors, or bold/italic
|
||||
text) make sure to use `less -r` to preserve those codes.
|
||||
|
||||
```sh
|
||||
#!/bin/sh
|
||||
|
||||
/bin/chafa "$@" | less -r
|
||||
```
|
||||
|
||||
Save it somewhere as a file with executable permissions and you can use
|
||||
`pipe-attachment 1 /path/to/your/chafa/wrapper` to view the first attachment as
|
||||
an image with [`chafa`].
|
||||
|
||||
[`chafa`]: https://hpjansson.org/chafa/
|
|
@ -1,42 +0,0 @@
|
|||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Comment: This directory contains various manual pages that may be of use to meli users.
|
||||
|
||||
Files: mailaddr.7.gz
|
||||
Comment: Sourced from debian manpages package.
|
||||
Copyright: Copyright (c) 1983, 1987 The Regents of the University of California.
|
||||
License: 6.5 (Berkeley) 2/14/89
|
||||
Redistribution and use in source and binary forms are permitted
|
||||
provided that the above copyright notice and this paragraph are
|
||||
duplicated in all such forms and that any documentation,
|
||||
advertising materials, and other materials related to such
|
||||
distribution and use acknowledge that the software was developed
|
||||
by the University of California, Berkeley. The name of the
|
||||
University may not be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
Files: maildir.5.en.gz
|
||||
Comment: Sourced from debian maildrop package.
|
||||
Copyright: Copyright 1998 - 2007 Double Precision, Inc.
|
||||
License: GPLv3 with OpenSSL linking extension
|
||||
This software is released under the GPL, version 3 (see COPYING.GPL).
|
||||
Additionally, compiling, linking, and/or using the OpenSSL toolkit in
|
||||
conjunction with this software is allowed.
|
||||
|
||||
Files: mbox.5.en.gz
|
||||
Comment: Sourced from debian mutt package.
|
||||
Copyright: Copyright (C) 2000 Thomas Roessler <roessler@does-not-exist.org>
|
||||
License: public-domain
|
||||
This document is in the public domain and may be distributed and
|
||||
changed arbitrarily.
|
||||
|
||||
Files: mbox.5qmail.en.gz
|
||||
Comment: Sourced from (now obsolete) debian qmail package.
|
||||
Copyright: D. J. Bernstein
|
||||
License: From http://cr.yp.to/qmail/dist.html
|
||||
I hereby place the qmail package (in particular, qmail-1.03.tar.gz,
|
||||
with MD5 checksum 622f65f982e380dbe86e6574f3abcb7c) into the public
|
||||
domain. You are free to modify the package, distribute modified
|
||||
versions, etc.
|
|
@ -1,239 +0,0 @@
|
|||
.TH maildir 5
|
||||
.SH "NAME"
|
||||
maildir \- directory for incoming mail messages
|
||||
.SH "INTRODUCTION"
|
||||
.I maildir
|
||||
is a structure for
|
||||
directories of incoming mail messages.
|
||||
It solves the reliability problems that plague
|
||||
.I mbox
|
||||
files and
|
||||
.I mh
|
||||
folders.
|
||||
.SH "RELIABILITY ISSUES"
|
||||
A machine may crash while it is delivering a message.
|
||||
For both
|
||||
.I mbox
|
||||
files and
|
||||
.I mh
|
||||
folders this means that the message will be silently truncated.
|
||||
Even worse: for
|
||||
.I mbox
|
||||
format, if the message is truncated in the middle of a line,
|
||||
it will be silently joined to the next message.
|
||||
The mail transport agent will try again later to deliver the message,
|
||||
but it is unacceptable that a corrupted message should show up at all.
|
||||
In
|
||||
.IR maildir ,
|
||||
every message is guaranteed complete upon delivery.
|
||||
|
||||
A machine may have two programs simultaneously delivering mail
|
||||
to the same user.
|
||||
The
|
||||
.I mbox
|
||||
and
|
||||
.I mh
|
||||
formats require the programs to update a single central file.
|
||||
If the programs do not use some locking mechanism,
|
||||
the central file will be corrupted.
|
||||
There are several
|
||||
.I mbox
|
||||
and
|
||||
.I mh
|
||||
locking mechanisms,
|
||||
none of which work portably and reliably.
|
||||
In contrast, in
|
||||
.IR maildir ,
|
||||
no locks are ever necessary.
|
||||
Different delivery processes never touch the same file.
|
||||
|
||||
A user may try to delete messages from his mailbox at the same
|
||||
moment that the machine delivers a new message.
|
||||
For
|
||||
.I mbox
|
||||
and
|
||||
.I mh
|
||||
formats, the user's mail-reading program must know
|
||||
what locking mechanism the mail-delivery programs use.
|
||||
In contrast, in
|
||||
.IR maildir ,
|
||||
any delivered message
|
||||
can be safely updated or deleted by a mail-reading program.
|
||||
|
||||
Many sites use Sun's
|
||||
.B Network F\fPa\fBil\fPur\fBe System
|
||||
(NFS),
|
||||
presumably because the operating system vendor does not offer
|
||||
anything else.
|
||||
NFS exacerbates all of the above problems.
|
||||
Some NFS implementations don't provide
|
||||
.B any
|
||||
reliable locking mechanism.
|
||||
With
|
||||
.I mbox
|
||||
and
|
||||
.I mh
|
||||
formats,
|
||||
if two machines deliver mail to the same user,
|
||||
or if a user reads mail anywhere except the delivery machine,
|
||||
the user's mail is at risk.
|
||||
.I maildir
|
||||
works without trouble over NFS.
|
||||
.SH "THE MAILDIR STRUCTURE"
|
||||
A directory in
|
||||
.I maildir
|
||||
format has three subdirectories,
|
||||
all on the same filesystem:
|
||||
.BR tmp ,
|
||||
.BR new ,
|
||||
and
|
||||
.BR cur .
|
||||
|
||||
Each file in
|
||||
.B new
|
||||
is a newly delivered mail message.
|
||||
The modification time of the file is the delivery date of the message.
|
||||
The message is delivered
|
||||
.I without
|
||||
an extra UUCP-style
|
||||
.B From_
|
||||
line,
|
||||
.I without
|
||||
any
|
||||
.B >From
|
||||
quoting,
|
||||
and
|
||||
.I without
|
||||
an extra blank line at the end.
|
||||
The message is normally in RFC 822 format,
|
||||
starting with a
|
||||
.B Return-Path
|
||||
line and a
|
||||
.B Delivered-To
|
||||
line,
|
||||
but it could contain arbitrary binary data.
|
||||
It might not even end with a newline.
|
||||
|
||||
Files in
|
||||
.B cur
|
||||
are just like files in
|
||||
.BR new .
|
||||
The big difference is that files in
|
||||
.B cur
|
||||
are no longer new mail:
|
||||
they have been seen by the user's mail-reading program.
|
||||
.SH "HOW A MESSAGE IS DELIVERED"
|
||||
The
|
||||
.B tmp
|
||||
directory is used to ensure reliable delivery,
|
||||
as discussed here.
|
||||
|
||||
A program delivers a mail message in six steps.
|
||||
First, it
|
||||
.B chdir()\fPs
|
||||
to the
|
||||
.I maildir
|
||||
directory.
|
||||
Second, it
|
||||
.B stat()s
|
||||
the name
|
||||
.BR tmp/\fItime.pid.host ,
|
||||
where
|
||||
.I time
|
||||
is the number of seconds since the beginning of 1970 GMT,
|
||||
.I pid
|
||||
is the program's process ID,
|
||||
and
|
||||
.I host
|
||||
is the host name.
|
||||
Third, if
|
||||
.B stat()
|
||||
returned anything other than ENOENT,
|
||||
the program sleeps for two seconds, updates
|
||||
.IR time ,
|
||||
and tries the
|
||||
.B stat()
|
||||
again, a limited number of times.
|
||||
Fourth, the program
|
||||
creates
|
||||
.BR tmp/\fItime.pid.host .
|
||||
Fifth, the program
|
||||
.I NFS-writes
|
||||
the message to the file.
|
||||
Sixth, the program
|
||||
.BR link() s
|
||||
the file to
|
||||
.BR new/\fItime.pid.host .
|
||||
At that instant the message has been successfully delivered.
|
||||
|
||||
The delivery program is required to start a 24-hour timer before
|
||||
creating
|
||||
.BR tmp/\fItime.pid.host ,
|
||||
and to abort the delivery
|
||||
if the timer expires.
|
||||
Upon error, timeout, or normal completion,
|
||||
the delivery program may attempt to
|
||||
.B unlink()
|
||||
.BR tmp/\fItime.pid.host .
|
||||
|
||||
.I NFS-writing
|
||||
means
|
||||
(1) as usual, checking the number of bytes returned from each
|
||||
.B write()
|
||||
call;
|
||||
(2) calling
|
||||
.B fsync()
|
||||
and checking its return value;
|
||||
(3) calling
|
||||
.B close()
|
||||
and checking its return value.
|
||||
(Standard NFS implementations handle
|
||||
.B fsync()
|
||||
incorrectly
|
||||
but make up for it by abusing
|
||||
.BR close() .)
|
||||
.SH "HOW A MESSAGE IS READ"
|
||||
A mail reader operates as follows.
|
||||
|
||||
It looks through the
|
||||
.B new
|
||||
directory for new messages.
|
||||
Say there is a new message,
|
||||
.BR new/\fIunique .
|
||||
The reader may freely display the contents of
|
||||
.BR new/\fIunique ,
|
||||
delete
|
||||
.BR new/\fIunique ,
|
||||
or rename
|
||||
.B new/\fIunique
|
||||
as
|
||||
.BR cur/\fIunique:info .
|
||||
See
|
||||
.B http://pobox.com/~djb/proto/maildir.html
|
||||
for the meaning of
|
||||
.IR info .
|
||||
|
||||
The reader is also expected to look through the
|
||||
.B tmp
|
||||
directory and to clean up any old files found there.
|
||||
A file in
|
||||
.B tmp
|
||||
may be safely removed if it
|
||||
has not been accessed in 36 hours.
|
||||
|
||||
It is a good idea for readers to skip all filenames in
|
||||
.B new
|
||||
and
|
||||
.B cur
|
||||
starting with a dot.
|
||||
Other than this, readers should not attempt to parse filenames.
|
||||
.SH "ENVIRONMENT VARIABLES"
|
||||
Mail readers supporting
|
||||
.I maildir
|
||||
use the
|
||||
.B MAILDIR
|
||||
environment variable
|
||||
as the name of the user's primary mail directory.
|
||||
.SH "SEE ALSO"
|
||||
mbox(5),
|
||||
qmail-local(8)
|
|
@ -1,87 +0,0 @@
|
|||
" Place this plugin in
|
||||
"
|
||||
" `$HOME/.vim/after/ftplugin/mail.vim` for vim
|
||||
" `$HOME/.config/nvim/after/ftplugin/mail.vim` for neovim
|
||||
|
||||
" Don't use modelines in e-mail messages
|
||||
setlocal nomodeline
|
||||
setlocal textwidth=72
|
||||
|
||||
" *fo-a*
|
||||
" a Automatic formatting of paragraphs.
|
||||
" Every time text is inserted or deleted the paragraph will be reformatted.
|
||||
" *fo-w*
|
||||
" w Trailing white space indicates a paragraph continues in the next line.
|
||||
" A line that ends in a non-white character ends a paragraph.
|
||||
" *fo-q*
|
||||
" q Allow formatting of comments with "gq".
|
||||
" *fo-t*
|
||||
" t Auto-wrap text using textwidth
|
||||
" *fo-r*
|
||||
" r Automatically insert the current comment leader after hitting <Enter> in
|
||||
" Insert mode.
|
||||
" *fo-c*
|
||||
" c Auto-wrap comments using textwidth, inserting the current comment leader
|
||||
" automatically.
|
||||
" *fo-2*
|
||||
" 2 When formatting text, use the indent of the second line of a paragraph for
|
||||
" the rest of the paragraph, instead of the indent of the first line.
|
||||
" This supports paragraphs in which the first line has a different indent than
|
||||
" the rest.
|
||||
" Note that 'autoindent' must be set too.
|
||||
" Example:
|
||||
" first line of a paragraph
|
||||
" second line of the same paragraph
|
||||
" third line.
|
||||
" This also works inside comments, ignoring the comment leader.
|
||||
setlocal formatoptions=aqtw2r
|
||||
|
||||
" Disable adding two spaces after '.', '?' and '!' with a join command.
|
||||
setlocal nojoinspaces
|
||||
|
||||
" Disable smartident (meant for source code)
|
||||
setlocal nosmartindent
|
||||
|
||||
" *'comments'* *'com'* *E524* *E525*
|
||||
" A comma-separated list of strings that can start a comment line.
|
||||
" See |format-comments|.
|
||||
" See |option-backslash| about using backslashes to insert a space.
|
||||
"
|
||||
"
|
||||
" The 'comments' option is a comma-separated list of parts.
|
||||
" Each part defines a type of comment string.
|
||||
" A part consists of: {flags}:{string}
|
||||
"
|
||||
" {string} is the literal text that must appear.
|
||||
"
|
||||
" {flags}:
|
||||
" n Nested comment.
|
||||
" Nesting with mixed parts is allowed.
|
||||
" If 'comments' is "n:),n:>" a line starting with "> ) >" is a comment.
|
||||
"
|
||||
" b Blank (<Space>, <Tab> or <EOL>) required after {string}.
|
||||
setlocal comments+=nb:>
|
||||
|
||||
" Highlight trailing whitespace as errors.
|
||||
match ErrorMsg '\s\+$'
|
||||
|
||||
" MAIL *mail.vim* *ft-mail.vim*
|
||||
" By default mail.vim synchronises syntax to 100 lines before the first
|
||||
" displayed line.
|
||||
" If you have a slow machine, and generally deal with emails with short
|
||||
" headers, you can change this to a smaller value:
|
||||
|
||||
let mail_minlines = 30
|
||||
|
||||
|
||||
" *no_mail_maps* *g:no_mail_maps*
|
||||
" Disable defining mappings for a specific filetype by setting a variable,
|
||||
" which contains the name of the filetype.
|
||||
" For the "mail" filetype this would be:
|
||||
let no_mail_maps = 1
|
||||
|
||||
" Local mappings:
|
||||
" <LocalLeader>q or \\MailQuote
|
||||
" Quotes the text selected in Visual mode, or from the cursor position
|
||||
" to the end of the file in Normal mode.
|
||||
" This means "> " is inserted in each line.
|
1010
meli/docs/meli.1
768
meli/docs/meli.7
|
@ -1,768 +0,0 @@
|
|||
.\" meli - meli.7
|
||||
.\"
|
||||
.\" Copyright 2017-2022 Manos Pitsidianakis
|
||||
.\"
|
||||
.\" This file is part of meli.
|
||||
.\"
|
||||
.\" meli is free software: you can redistribute it and/or modify
|
||||
.\" it under the terms of the GNU General Public License as published by
|
||||
.\" the Free Software Foundation, either version 3 of the License, or
|
||||
.\" (at your option) any later version.
|
||||
.\"
|
||||
.\" meli is distributed in the hope that it will be useful,
|
||||
.\" but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
.\" GNU General Public License for more details.
|
||||
.\"
|
||||
.\" You should have received a copy of the GNU General Public License
|
||||
.\" along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
.\"
|
||||
.\".de Hr
|
||||
.\".Bd -literal -offset center
|
||||
.\"╌╍─────────────────────────────────────────────────────────╍╌
|
||||
.\".Ed
|
||||
.\"..
|
||||
.de Shortcut
|
||||
.Sm
|
||||
.Aq \\$1
|
||||
\
|
||||
.Po
|
||||
.Em shortcuts.\\$2\&. Ns
|
||||
.Em \\$3
|
||||
.Pc
|
||||
.Sm
|
||||
..
|
||||
.de ShortcutPeriod
|
||||
.Aq \\$1
|
||||
.Po
|
||||
.Em shortcuts.\\$2\&. Ns
|
||||
.Em \\$3
|
||||
.Pc Ns
|
||||
..
|
||||
.de Command
|
||||
.Bd -ragged -offset 1n
|
||||
.Cm \\$*
|
||||
.Ed
|
||||
..
|
||||
.\".Dd November 11, 2022
|
||||
.Dd March 10, 2024
|
||||
.Dt MELI 7
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm meli
|
||||
.Nd Tutorial for the meli terminal e\-mail client
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op ...
|
||||
.Sh DESCRIPTION
|
||||
.Nm
|
||||
is a terminal mail client aiming for extensive and user\-friendly configurability.
|
||||
.Bd -literal -offset center
|
||||
^^ .-=-=-=-. ^^
|
||||
^^ (`-=-=-=-=-`) ^^
|
||||
(`-=-=-=-=-=-=-`) ^^ ^^
|
||||
^^ (`-=-=-=-=-=-=-=-`) ^^
|
||||
( `-=-=-=-(@)-=-=-` ) ^^
|
||||
(`-=-=-=-=-=-=-=-=-`) ^^
|
||||
(`-=-=-=-=-=-=-=-=-`) ^^
|
||||
(`-=-=-=-=-=-=-=-=-`)
|
||||
^^ (`-=-=-=-=-=-=-=-=-`) ^^
|
||||
^^ (`-=-=-=-=-=-=-=-`) ^^
|
||||
(`-=-=-=-=-=-=-`) ^^
|
||||
^^ (`-=-=-=-=-`)
|
||||
`-=-=-=-=-` ^^
|
||||
.Ed
|
||||
.Sh INTRODUCTION
|
||||
To quit
|
||||
.Nm
|
||||
press
|
||||
.Shortcut q general quit
|
||||
at any time.
|
||||
When launched for the first time,
|
||||
.Nm
|
||||
will search for its configuration directory,
|
||||
.Pa $XDG_CONFIG_HOME/meli/ Ns
|
||||
\&.
|
||||
If it doesn't exist, you will be asked if you want to create one and presented with a sample configuration file
|
||||
.Pq Pa $XDG_CONFIG_HOME/meli/config.toml
|
||||
that includes the basic settings required for setting up accounts allowing you to copy and edit right away.
|
||||
See
|
||||
.Xr meli.conf 5
|
||||
for the available configuration options.
|
||||
.Pp
|
||||
At any time, you may press
|
||||
.Shortcut \&? general toggle_help
|
||||
for a searchable list of all available actions and shortcuts, along with every possible setting and command that your version supports.
|
||||
.Pp
|
||||
Each time a shortcut is mentioned in this document, you will find a parenthesis next to it with the name of the shortcut setting along with its section in the configuration settings so that you can modify it if you wish.
|
||||
.Pp
|
||||
For example, to set the
|
||||
.Em toggle_help
|
||||
shortcut mentioned in the previous paragraph, add the following to your configuration:
|
||||
.Bd -literal -offset center
|
||||
[shortcuts]
|
||||
general.toggle_help = 'F1'
|
||||
.Ed
|
||||
.sp
|
||||
Or alternatively:
|
||||
.Bd -literal -offset center
|
||||
[shortcuts.general]
|
||||
toggle_help = 'F1'
|
||||
.Ed
|
||||
.Pp
|
||||
To go to the next tab on the right, press
|
||||
.ShortcutPeriod T general next_tab
|
||||
\&.
|
||||
.Sh INTERACTING WITH Nm
|
||||
You will be interacting with
|
||||
.Nm
|
||||
in four primary ways:
|
||||
.Bl -column
|
||||
.It 1.
|
||||
keyboard shortcuts in
|
||||
.Sy NORMAL
|
||||
mode.
|
||||
.It 2.
|
||||
commands with arguments in
|
||||
.Sy COMMAND
|
||||
mode.
|
||||
.It 3.
|
||||
regular text input in text input widgets in
|
||||
.Sy INSERT
|
||||
mode.
|
||||
.It 4.
|
||||
any kind of input that gets passed directly into an embedded terminal in
|
||||
.Sy EMBED
|
||||
mode.
|
||||
.El
|
||||
.Sh MODES
|
||||
.Nm
|
||||
is a modal application, just like
|
||||
.Xr vi 1 Ns
|
||||
\&.
|
||||
This means that pressing the same keys in different modes would yield different results.
|
||||
This allows you to separate how the input is interpreted without the need to focus your input with a mouse.
|
||||
.Bl -tag -width 8n
|
||||
.It NORMAL
|
||||
This is the default mode of
|
||||
.Nm Ns
|
||||
\&.
|
||||
All keyboard shortcuts work in this mode.
|
||||
.It COMMAND
|
||||
Commands are issued in
|
||||
.Sy COMMAND
|
||||
mode, by default started with
|
||||
.Shortcut \&: general enter_command_mode
|
||||
and exited with
|
||||
.Aq Esc
|
||||
key.
|
||||
.It EMBED
|
||||
This is the mode of the embed terminal emulator.
|
||||
To exit an embedded application, issue
|
||||
.Aq Ctrl\-C
|
||||
to kill it or
|
||||
.Aq Ctrl\-Z
|
||||
to stop the program and follow the instructions on
|
||||
.Nm
|
||||
to exit.
|
||||
.It INSERT
|
||||
This mode is entered when pressing
|
||||
.Aq Enter
|
||||
on a cursor selected text input field, and it captures all input as text input.
|
||||
It is exited with the
|
||||
.Aq Esc
|
||||
key.
|
||||
.El
|
||||
.Sh ACTIVE SHORTCUTS POPUP
|
||||
By pressing
|
||||
.Shortcut \&? general toggle_help
|
||||
at any time, the shortcuts popup display status gets toggled.
|
||||
You can find all valid shortcuts for the current UI state you are in.
|
||||
.Bd -literal -offset center
|
||||
┌─shortcuts──Press ? to close────────────────────────────────┐
|
||||
│ ▀│
|
||||
│ use COMMAND "search" to find shortcuts █│
|
||||
│ Use Up, Down, Left, Right to scroll. █│
|
||||
│ █│
|
||||
│ pager █│
|
||||
│ █│
|
||||
│ PageDown page_down █│
|
||||
│ PageUp page_up │
|
||||
│ j scroll_down │
|
||||
│ k scroll_up │
|
||||
│ │
|
||||
│ view mail │
|
||||
│ │
|
||||
│ c add_addresses_to_contacts │
|
||||
│ e edit │
|
||||
│ u toggle_url_mode │
|
||||
│ a open_attachment │
|
||||
│ m open_mailcap │
|
||||
│ R reply │
|
||||
│ C-r reply_to_author │
|
||||
│ C-g reply_to_all │
|
||||
│ C-f forward │
|
||||
│ M-r view_raw_source │
|
||||
│ h toggle_expand_headers ▄│
|
||||
└────────────────────────────────────────────────────────────┘
|
||||
.Ed
|
||||
.Bd -ragged -offset 3n
|
||||
.Em Shows\ active\ shortcuts\ in\ order\ of\ the\ widget\ hierarchy\&.
|
||||
.Ed
|
||||
.Sh MAIN VIEW
|
||||
.Bd -literal -offset center
|
||||
┌───────────────────────┐
|
||||
├────┼──────────────────┤
|
||||
│___ │ ___________ │
|
||||
│ _ │ _______________ │
|
||||
│ _ │__________________│
|
||||
│ _ │ ___________ │
|
||||
│ │ _____ │
|
||||
│ │ │
|
||||
└────┴──────────────────┘
|
||||
.Ed
|
||||
.Bd -ragged -offset 3n
|
||||
.Em The\ main\ view's\ layout\&.
|
||||
.Ed
|
||||
.sp
|
||||
This is the view you will spend more time with in
|
||||
.Nm Ns
|
||||
\&.
|
||||
.Pp
|
||||
Press
|
||||
.Shortcut \(ga listing toggle_menu_visibility
|
||||
to toggle the sidebars visibility.
|
||||
.Pp
|
||||
Press
|
||||
.Shortcut Left listing focus_right
|
||||
to switch focus on the sidebar menu.
|
||||
Press
|
||||
.Shortcut Right listing focus_left
|
||||
to switch focus on the e\-mail list.
|
||||
.Pp
|
||||
On the e\-mail list, press
|
||||
.Shortcut k listing scroll_up
|
||||
to scroll up, and
|
||||
.Shortcut j listing scroll_down
|
||||
to scroll down.
|
||||
Press
|
||||
.Shortcut Enter listing open_entry
|
||||
to open an e\-mail entry and
|
||||
.Shortcut i listing exit_entry
|
||||
to exit it.
|
||||
.Bd -ragged
|
||||
.Sy The sidebar\&.
|
||||
.Ed
|
||||
.Bd -literal -offset center
|
||||
┌─────────────┉┉┉┉┉✂
|
||||
│ mail▐ contact li✂
|
||||
│personal account ✂
|
||||
│ 0 INBOX ✂
|
||||
│ 1 ┣━Sent ✂
|
||||
│ 2 ┣━Lists ✂
|
||||
│ 3 ┃ ┣━meli-dev ✂
|
||||
│ 4 ┃ ┗━meli ✂
|
||||
│ 5 ┣━Drafts ✂
|
||||
│ 6 ┣━Trash ✂
|
||||
│ 7 ┗━foobar ✂
|
||||
┇ 8 Trash ✂
|
||||
✂ ✂ ✂ ✂ ✂ ✂ ✂ ✂ ✂ ✂
|
||||
.Ed
|
||||
.sp
|
||||
Press
|
||||
.Shortcut k listing scroll_up
|
||||
to scroll up, and
|
||||
.Shortcut j listing scroll_down
|
||||
to scroll down.
|
||||
.Pp
|
||||
Press
|
||||
.Shortcut Enter listing open_mailbox
|
||||
to open an entry (either a mailbox or an account name).
|
||||
Entering an account name will show you a page with details about the account and its network connection, depending on the backend.
|
||||
.Pp
|
||||
While focused in the sidebar, you can
|
||||
.Dq collapse
|
||||
a mailbox tree, if it has children, and you can open it with
|
||||
.ShortcutPeriod Space listing toggle_mailbox_collapse
|
||||
\&.
|
||||
You can have mailbox trees collapsed on startup by default by setting a mailbox's
|
||||
.Ic collapsed
|
||||
setting to
|
||||
.Em true Ns
|
||||
\&.
|
||||
See
|
||||
.Xr meli.conf 5 section MAILBOXES
|
||||
for details.
|
||||
.Pp
|
||||
You can increase the sidebar's width with
|
||||
.Shortcut Ctrl\-p listing increase_sidebar
|
||||
and decrease with
|
||||
.ShortcutPeriod Ctrl\-o listing decrease_sidebar
|
||||
\&.
|
||||
.Bd -ragged
|
||||
.Sy The status bar.
|
||||
.Ed
|
||||
.Bd -literal -offset center
|
||||
┌────────────────────────────────────────────────────┈┈
|
||||
│NORMAL | Mailbox: Inbox, Messages: 25772, New: 3006
|
||||
└────────────────────────────────────────────────────┈┈
|
||||
.Ed
|
||||
.Pp
|
||||
The status bar shows which mode you are, and the status message of the current view.
|
||||
In the pictured example, it shows the status of a mailbox called
|
||||
.Dq Inbox
|
||||
with lots of e\-mails.
|
||||
.Bd -ragged
|
||||
.Sy The number modifier buffer.
|
||||
.Ed
|
||||
.Bd -literal -offset center
|
||||
┈┈────────────┐
|
||||
12 │
|
||||
┈┈────────────┘
|
||||
.Ed
|
||||
.Pp
|
||||
Some commands may accept a number modifier.
|
||||
.Tg number-modifier
|
||||
For example, scroll down commands can receive a multiplier
|
||||
.Em n
|
||||
to scroll down
|
||||
.Em n
|
||||
entries.
|
||||
Another use of the number buffer is opening URLs inside the pager.
|
||||
See
|
||||
.Sx PAGER
|
||||
for an explanation of interacting with URLs in e\-mails.
|
||||
.Pp
|
||||
Pressing numbers in
|
||||
.Sy NORMAL
|
||||
mode will populate this buffer.
|
||||
To erase it, press the
|
||||
.Aq Esc
|
||||
key.
|
||||
.Sh MAIL LIST
|
||||
There are four different list styles:
|
||||
.Bl -hyphen -compact
|
||||
.It
|
||||
.Qq plain
|
||||
which shows one line per e\-mail.
|
||||
.It
|
||||
.Qq threaded
|
||||
which shows a threaded view with drawn tree structure.
|
||||
.It
|
||||
.Qq compact
|
||||
which shows one line per thread which can include multiple e\-mails.
|
||||
.It
|
||||
.Qq conversations
|
||||
which shows more than one line per thread which can include multiple e\-mails with more details about the thread.
|
||||
.El
|
||||
.Bd -ragged
|
||||
.Sy Plain view\&.
|
||||
.Ed
|
||||
.Bd -literal -offset center
|
||||
│42 Fri, 02 Sep 2022 19:51 xxxxxxxxxxxxx < [PATCH 3/8] │
|
||||
│43 Fri, 02 Sep 2022 19:51 xxxxxxxxxxxxx < [PATCH 2/8] │
|
||||
│44 Fri, 02 Sep 2022 19:51 xxxxxxxxxxxxx < [PATCH 1/8] │
|
||||
|45 Fri, 02 Sep 2022 19:51 xxxxxxxxxxxxx < [PATCH 0/8] |
|
||||
│46 Fri, 02 Sep 2022 18:18 xxxxxxxx <xxxxx Re: [PATCH 3│
|
||||
.Ed
|
||||
.Bd -ragged
|
||||
.Sy Threaded view\&.
|
||||
.Ed
|
||||
.Bd -literal -offset center
|
||||
│12 9 hours ago xxxxxxxxxxxxxxx [PATCH v3 0│
|
||||
│13 9 hours ago xxxxxxxxxxxxxxx ├─>[PATCH │
|
||||
│14 9 hours ago xxxxxxxxxxxxxxx ├─>[PATCH │
|
||||
|15 9 hours ago xxxxxxxxxxxxxxx ├─>[PATCH |
|
||||
│16 9 hours ago xxxxxxxxxxxxxxx ├─>[PATCH │
|
||||
│17 9 hours ago xxxxxxxxxxxxxxx └─>[PATCH │
|
||||
│18 2022-08-23 01:23:51 xxxxxxxxxxxxxxx [RFC v4 00/│
|
||||
│19 2022-08-23 01:23:52 xxxxxxxxxxxxxxx ├─>[RFC v4│
|
||||
|20 2022-08-30 10:30:16 xxxxxxxxxxxxxxx │ └─> |
|
||||
│21 6 days ago xxxxxxxxxxxxxxx │ └─> │
|
||||
│22 2022-08-23 01:23:53 xxxxxxxxxxxxxxx ├─>[RFC v4│
|
||||
.Ed
|
||||
.Bd -ragged
|
||||
.Sy Compact view\&.
|
||||
.Ed
|
||||
.Bd -literal -offset center
|
||||
│18 2022-…:38 xxxxxxxxxxxxxxx [PATCH v3 3/3] u…_l() (2) │
|
||||
|19 2022-…:49 xxxxxxxxxxxxxxx [PATCH v8 0/7] A…e (3) |
|
||||
│20 2022-…:10 xxxxxxxxxxxxxxx [PATCH v8 2/7] f…s (2) │
|
||||
│21 2022-…:38 xxxxxxxxxxxxxxx [PATCH v8 3/7] b…s (2) │
|
||||
│22 2022-…:53 xxxxxxxxxxxxxxx [PATCH v6 00/10] p…g (31) │
|
||||
.Ed
|
||||
.Bd -ragged
|
||||
.Sy Conversations view\&.
|
||||
.Ed
|
||||
.Bd -literal -offset center
|
||||
│[PATCH v2] xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (5) │
|
||||
|1 day ago▁▁▁▁xxxxxxxxxxxxx <xxxxxxxxxxxxx@xxxxxxxxxx>, xxxxx│
|
||||
│ |
|
||||
│[PATCH v2 0/8] xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx│
|
||||
│1 day ago▁▁▁▁xxxxxxxxxxxxxxx <xxxxxxxxxx@xxxxxxxxxxxxxx>, xx│
|
||||
| │
|
||||
│[PATCH 0/2] xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (4) |
|
||||
│2 days ago▁▁▁▁xxxxxxxxxxxxxxxx <xxxxxxxx@xxxxxxxxxxx>, xxxxx│
|
||||
│ │
|
||||
│[PATCH 0/8] xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (12) │
|
||||
│2 days ago▁▁▁▁xxxxxxxxxxxxx <xxxxxxxx@xxxxxxxxxx>, xxxxxxxxx│
|
||||
.Ed
|
||||
.sp
|
||||
.sp
|
||||
.Sy Performing actions on entries and/or selections\&.
|
||||
.Pp
|
||||
Press
|
||||
.Shortcut V listing select_entry
|
||||
to toggle the selection of a single entry.
|
||||
.Shortcut v listing select_motion
|
||||
can be prefixed by a number modifier and affixed by a scrolling motion (up or down) to select multiple entries.
|
||||
.Tg number-modifier
|
||||
Simple set operations can be performed on a selection with these shortcut modifiers:
|
||||
.sp
|
||||
.Bl -hyphen -compact
|
||||
.It
|
||||
Union modifier:
|
||||
.Shortcut Ctrl\-u listing union_modifier
|
||||
.It
|
||||
Difference modifier:
|
||||
.Shortcut Ctrl\-d listing diff_modifier
|
||||
.It
|
||||
Intersection modifier:
|
||||
.Shortcut Ctrl\-i listing intersection_modifier
|
||||
.El
|
||||
.Pp
|
||||
To set an entry as
|
||||
.Qq read
|
||||
\&, use the
|
||||
.Shortcut n listing set_seen
|
||||
shortcut.
|
||||
To set an entry as
|
||||
.Qq unread
|
||||
\&, use the command
|
||||
.Command set unseen
|
||||
.sp
|
||||
which also has its complement
|
||||
.Command set seen
|
||||
.sp
|
||||
action.
|
||||
.Pp
|
||||
For e\-mail backends that support flags you can use the following commands on entries and selections to modify them:
|
||||
.Command flag set FLAG
|
||||
.Command flag unset FLAG
|
||||
.Pp
|
||||
For e\-mail backends that support tags
|
||||
.Po
|
||||
like
|
||||
.Qq IMAP
|
||||
or
|
||||
.Qq notmuch Ns
|
||||
.Pc
|
||||
you can use the following commands on entries and selections to modify them:
|
||||
.Command tag add TAG
|
||||
.Command tag remove TAG
|
||||
.sp
|
||||
(see
|
||||
.Xr meli.conf 5 TAGS Ns
|
||||
, settings
|
||||
.Ic colors
|
||||
and
|
||||
.Ic ignore_tags
|
||||
for how to set tag colors and tag visibility)
|
||||
You can clear the selection with the
|
||||
.Aq Esc
|
||||
key.
|
||||
.Sh PAGER
|
||||
You can open an e\-mail entry by pressing
|
||||
.ShortcutPeriod Enter listing open_entry
|
||||
\&. This brings up the e\-mail view with the e\-mail content inside a pager.
|
||||
.Bd -literal -offset center
|
||||
┌────────────────────────────────────────────────────────────┐
|
||||
│Date: Sat, 21 May 2022 16:16:11 +0300 ▀│
|
||||
│From: Narrator <narrator@example.com> █│
|
||||
│To: Stanley <427@example.com> █│
|
||||
│Subject: The e-mail ending █│
|
||||
│Message-ID: <gambheerata@example.com> █│
|
||||
│ █│
|
||||
│The story, and the choices, or what have you, and therefore█│
|
||||
│by becoming it is! So on and so forth, until inevitably, we │
|
||||
│all until the end of time. At which time, everything all at │
|
||||
│once, so now you see? Blah, blah, blah, rah, rah, rah... │
|
||||
│We've eaten too much and it can't be just yet. No, no! │
|
||||
│Until two-hundred and forty-five! But the logic of │
|
||||
│elimination, working backwards, the deduction therefore │
|
||||
│becomes impossible to manufacture. It went on for nearly │
|
||||
│ten thousand years, until just yesterday. Here and there, │
|
||||
│forward and back, and never a moment before lunchtime. It │
|
||||
│can't be! It's the only thing there is! How many billions │
|
||||
│left until so much more than forever ago! Which is why I │
|
||||
│say: │
|
||||
│ │
|
||||
│The story, and the choices, or what have you, and therefore │
|
||||
│by becoming it is! So on and so forth, until inevitably, we▄│
|
||||
└────────────────────────────────────────────────────────────┘
|
||||
.Ed
|
||||
.Bd -ragged -offset 3n
|
||||
.Em The\ pager\ displaying\ an\ e\-mail\&.
|
||||
.Ed
|
||||
.Pp
|
||||
The pager is simple to use.
|
||||
Scroll with the following:
|
||||
.Bl -hang -width 27n
|
||||
.It Go to next pager page
|
||||
.Shortcut PageDown pager page_down
|
||||
.It Go to previous pager page
|
||||
.Shortcut PageUp pager page_up
|
||||
.It Scroll down pager.
|
||||
.Shortcut j pager scroll_down
|
||||
.It Scroll up pager.
|
||||
.Shortcut k pager scroll_up
|
||||
.El
|
||||
.sp
|
||||
All scrolling shortcuts can be prefixed with a number modifier
|
||||
.Tg number-modifier
|
||||
which will act as a multiplier.
|
||||
.Pp
|
||||
The pager can enter a special
|
||||
.Em url
|
||||
mode which will prefix all detected hyperlinks and e\-mail addresses with a number inside square brackets
|
||||
.ShortcutPeriod u pager toggle_url_mode
|
||||
\&.
|
||||
Writing down a chosen number as a number modifier
|
||||
.Tg number-modifier
|
||||
and pressing
|
||||
.Shortcut g envelope_view go_to_url
|
||||
will attempt to open the link with the system's default open command
|
||||
.Po
|
||||
.Xr xdg-open 1
|
||||
in supported OSes,
|
||||
and
|
||||
.Xr open 1
|
||||
on MacOS
|
||||
.Pc Ns
|
||||
\&.
|
||||
To override with a custom launcher, see
|
||||
.Qo
|
||||
.Li pager
|
||||
.Qc
|
||||
configuration setting
|
||||
.Qo
|
||||
.Li url_launcher
|
||||
.Qc
|
||||
.Po
|
||||
see
|
||||
.Xr meli.conf 5 PAGER
|
||||
for more details
|
||||
.Pc Ns
|
||||
\&.
|
||||
.Sh MAIL VIEW
|
||||
Other things you can do when viewing e\-mail:
|
||||
.Bl -dash -compact
|
||||
.It
|
||||
Most importantly, you can exit the mail view with:
|
||||
.Shortcut i listing exit_entry
|
||||
.It
|
||||
Add addresses from the e\-mail headers to contacts:
|
||||
.Shortcut c envelope_view add_addresses_to_contacts
|
||||
.It
|
||||
Open an attachment by entering its index as a number modifier and pressing:
|
||||
.Tg number-modifier
|
||||
.Shortcut a envelope_view open_attachment
|
||||
.It
|
||||
Open an attachment by its
|
||||
.Xr mailcap 4
|
||||
entry by entering its index as a number modifier and pressing:
|
||||
.Shortcut m envelope_view open_mailcap
|
||||
.It
|
||||
Reply to envelope:
|
||||
.Shortcut R envelope_view reply
|
||||
.It
|
||||
Reply to author:
|
||||
.Shortcut Ctrl\-r envelope_view reply_to_author
|
||||
.It
|
||||
Reply to all/Reply to list/Follow up:
|
||||
.Shortcut Ctrl\-g envelope_view reply_to_all
|
||||
.It
|
||||
Forward e\-mail:
|
||||
.Shortcut Ctrl\-f envelope_view forward
|
||||
.It
|
||||
Expand extra headers: (References and others)
|
||||
.Shortcut h envelope_view toggle_expand_headers
|
||||
.It
|
||||
View envelope source in a pager: (toggles between raw and decoded source)
|
||||
.Shortcut M\-r envelope_view view_raw_source
|
||||
.It
|
||||
Return to envelope_view if viewing raw source or attachment:
|
||||
.Shortcut r envelope_view return_to_normal_view
|
||||
.El
|
||||
.Sh COMPOSING
|
||||
To compose an e\-mail, you can either start with an empty draft by pressing
|
||||
.Shortcut m listing new_mail
|
||||
which opens a composer view in a new tab.
|
||||
To reply to a specific e\-mail, when in envelope view you can select the specific action you want to take:
|
||||
.sp
|
||||
.Bl -dash -compact
|
||||
.It
|
||||
Reply to envelope.
|
||||
.Shortcut R envelope_view reply
|
||||
.It
|
||||
Reply to author.
|
||||
.Shortcut Ctrl\-r envelope_view reply_to_author
|
||||
.It
|
||||
Reply to all.
|
||||
.Shortcut Ctrl\-g envelope_view reply_to_all
|
||||
.El
|
||||
.sp
|
||||
To launch your editor, press
|
||||
.ShortcutPeriod e composing edit
|
||||
\&.
|
||||
To send your draft, press
|
||||
.ShortcutPeriod s composing send_mail
|
||||
\&.
|
||||
To save the draft without submission, enter the command
|
||||
.Command close
|
||||
.sp
|
||||
and select
|
||||
.Qq save as draft Ns
|
||||
\&.
|
||||
You can return to the draft by going to your
|
||||
.Qq Drafts
|
||||
mailbox and selecting
|
||||
.ShortcutPeriod e envelope_view edit
|
||||
\&.
|
||||
.Bd -literal -offset center
|
||||
┌────────────────────────────────────────────────────────────┐
|
||||
│ mail▐ contact list ▐ composing ▍███████████████████████│
|
||||
│ COMPOSING MESSAGE │
|
||||
│ Date Mon, 05 Sep 2022 17:49:19 +0300 │
|
||||
│ From myself <myself@example.com>░░░░ │
|
||||
│ To friend <myfriend@example.com>░░ │
|
||||
│ Cc ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │
|
||||
│ Bcc ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │
|
||||
│ Subject This is my subject!░░░░░░░░░░░░ │
|
||||
│ │
|
||||
│ Hello friend!░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │
|
||||
│ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │
|
||||
│ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │
|
||||
│ │
|
||||
│ ☐ don't sign │
|
||||
│ ☐ don't encrypt │
|
||||
│ no attachments │
|
||||
│ │
|
||||
│NORMAL | Mailbox: Inbox, Messages: 25772, New: 3006 │
|
||||
└────────────────────────────────────────────────────────────┘
|
||||
.Ed
|
||||
.Bd -ragged -offset 3n
|
||||
.Em The\ lightly\ highlighted\ cells\ represent\ text\ input\ fields\&.
|
||||
.Ed
|
||||
.sp
|
||||
If you enable the embed terminal option, you can launch your terminal editor of choice when you press
|
||||
.Ic edit Ns
|
||||
\&.
|
||||
.Bd -literal -offset center
|
||||
┌────────────────────────────────────────────────────────────┐
|
||||
│ mail▐ contact list ▐ composing ▍███████████████████████│
|
||||
│ ╓COMPOSING MESSAGE┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╖ │
|
||||
│ ║ p/v/f/h/5/T/m/07f56b6e-ec09-49d9-b8d8-f0c5a81e7826 ║ │
|
||||
│ ║ 7 Date: Mon, 05 Sep 2022 18:43:10 +0300 ║ │
|
||||
│ ║ 6 From: Mister Cardholder <mrholder@example.com> ║ │
|
||||
│ ║ 5 To: ║ │
|
||||
│ ║ 4 Cc: ║ │
|
||||
│ ║ 3 Bcc: ║ │
|
||||
│ ║ 2 Subject: ║ │
|
||||
│ ║ 1 User-Agent: meli 0.7.2 ║ │
|
||||
│ ║8 █ ║ │
|
||||
│ ║~ ║ │
|
||||
│ ║~ ║ │
|
||||
│ ║~ ║ │
|
||||
│ ║~ ║ │
|
||||
│ ║ N… <6e-ec09-49d9-b8d8-f0c5a81e7826 100% ㏑:8 ℅:1║ │
|
||||
│ ╚════════════════════════════════════════════════════╝ │
|
||||
│ │
|
||||
│ │
|
||||
│ ☐ don't sign │
|
||||
│ ☐ don't encrypt │
|
||||
│ no attachments │
|
||||
│ │
|
||||
│EMBED | Mailbox: Inbox, Messages: 25772, New: 3006 │
|
||||
└────────────────────────────────────────────────────────────┘
|
||||
.Ed
|
||||
.Bd -ragged -offset 3n
|
||||
.Bf -emphasis
|
||||
.Xr nvim 1 Ns
|
||||
\ running\ inside\ the\ composing\ tab\&.
|
||||
.Ef
|
||||
The\ double\ line\ border\ annotates\ the\ area\ of\ the\ embedded\ terminal,
|
||||
the\ actual\ embedding\ is\ seamless\&.
|
||||
.Ed
|
||||
.Ss composing mail commands
|
||||
.Bl -tag -width 36n
|
||||
.It Cm add\-attachment Ar PATH
|
||||
in composer, add
|
||||
.Ar PATH
|
||||
as an attachment
|
||||
.It Cm add\-attachment < Ar CMD Ar ARGS
|
||||
in composer, pipe
|
||||
.Ar CMD Ar ARGS
|
||||
output into an attachment
|
||||
.It Cm add\-attachment\-file\-picker
|
||||
Launch command defined in the configuration value
|
||||
.Ic file_picker_command
|
||||
in
|
||||
.Xr meli.conf 5 TERMINAL
|
||||
.It Cm add\-attachment\-file\-picker < Ar CMD Ar ARGS
|
||||
Launch command
|
||||
.Ar CMD Ar ARGS Ns
|
||||
\&.
|
||||
The command should print file paths in stdout, separated by NUL bytes.
|
||||
Example usage with
|
||||
.Xr fzf Ns
|
||||
:
|
||||
.D1 add-attachment-file-picker < fzf --print0
|
||||
.It Cm remove\-attachment Ar INDEX
|
||||
remove attachment with given index
|
||||
.It Cm toggle sign
|
||||
toggle between signing and not signing this message.
|
||||
If the gpg invocation fails then the mail won't be sent.
|
||||
See
|
||||
.Xr meli.conf 5 PGP
|
||||
for PGP configuration.
|
||||
.It Cm save\-draft
|
||||
saves a copy of the draft in the Draft folder
|
||||
.El
|
||||
.\" [ref:TODO]: add contacts section
|
||||
.Sh THEMES
|
||||
See
|
||||
.Xr meli-themes 5
|
||||
for documentation on how to theme
|
||||
.Nm Ns
|
||||
\&.
|
||||
.Sh SEE ALSO
|
||||
.Xr meli 1 ,
|
||||
.Xr meli.conf 5 ,
|
||||
.Xr meli-themes 5 ,
|
||||
.Xr xdg-open 1 ,
|
||||
.Xr mailcap 5
|
||||
.Sh AUTHORS
|
||||
Copyright 2017\(en2024
|
||||
.An Manos Pitsidianakis Aq Mt manos@pitsidianak.is
|
||||
.Pp
|
||||
Released under the GPL, version 3 or greater.
|
||||
This software carries no warranty of any kind.
|
||||
.Po
|
||||
See
|
||||
.Pa COPYING
|
||||
for full copyright and warranty notices.
|
||||
.Pc
|
||||
.Ss Links
|
||||
.Bl -item -compact
|
||||
.It
|
||||
.Lk https://meli\-email.org "Website"
|
||||
.It
|
||||
.Lk https://git.meli\-email.org/meli/meli "Main\ git\ repository\ and\ issue\ tracker"
|
||||
.It
|
||||
.Lk https://codeberg.org/meli/meli "Official\ read-only\ git\ mirror\ on\ codeberg.org"
|
||||
.It
|
||||
.Lk https://github.com/meli/meli "Official\ read-only\ git\ mirror\ on\ github.com"
|
||||
.It
|
||||
.Lk https://crates.io/crates/meli "meli\ crate\ on\ crates.io"
|
||||
.El
|
|
@ -1,227 +0,0 @@
|
|||
.\" meli - meli.conf.examples.5
|
||||
.\"
|
||||
.\" Copyright 2024 Manos Pitsidianakis
|
||||
.\"
|
||||
.\" This file is part of meli.
|
||||
.\"
|
||||
.\" meli is free software: you can redistribute it and/or modify
|
||||
.\" it under the terms of the GNU General Public License as published by
|
||||
.\" the Free Software Foundation, either version 3 of the License, or
|
||||
.\" (at your option) any later version.
|
||||
.\"
|
||||
.\" meli is distributed in the hope that it will be useful,
|
||||
.\" but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
.\" GNU General Public License for more details.
|
||||
.\"
|
||||
.\" You should have received a copy of the GNU General Public License
|
||||
.\" along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
.\"
|
||||
.\" SPDX-License-Identifier: EUPL-1.2 OR GPL-3.0-or-later
|
||||
|
||||
.de HorizontalRule
|
||||
.\"\l'\n(.l\(ru1.25'
|
||||
.sp
|
||||
..
|
||||
.de LiteralStringValue
|
||||
.Sm
|
||||
.Po Qo
|
||||
.Em Li \\$1
|
||||
.Qc Pc
|
||||
.Sm
|
||||
..
|
||||
.de LiteralStringValueRenders
|
||||
.LiteralStringValue \\$1
|
||||
.shift 1
|
||||
.Bo
|
||||
.Sm
|
||||
Rendered as:
|
||||
.Li r##
|
||||
.Qo
|
||||
\\$1
|
||||
.Qc
|
||||
.Li ##
|
||||
.Bc
|
||||
.Sm
|
||||
..
|
||||
.\".Dd November 11, 2022
|
||||
.Dd November 22, 2024
|
||||
.Dt MELI.CONF.EXAMPLES 5
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm meli.conf examples
|
||||
.Nd Example configurations for various mail backends supported by the
|
||||
.Xr meli 1
|
||||
terminal e-mail client
|
||||
.\"
|
||||
.\"
|
||||
.\"
|
||||
.\"
|
||||
.\"
|
||||
.\".Sh SYNOPSIS
|
||||
.\".Pa $XDG_CONFIG_HOME/meli/config.toml
|
||||
.\".\"
|
||||
.\".\"
|
||||
.\".\"
|
||||
.\".\"
|
||||
.\".\"
|
||||
.\".Sh DESCRIPTION
|
||||
.Sh MAILDIR ACCOUNT
|
||||
An example configuration:
|
||||
.\"
|
||||
.\"
|
||||
.\"
|
||||
.Bd -literal
|
||||
[accounts.account-name]
|
||||
root_mailbox = "/path/to/root/folder"
|
||||
format = "Maildir"
|
||||
listing.index_style = "Compact"
|
||||
identity="email@example.com"
|
||||
display_name = "Name"
|
||||
send_mail = 'msmtp --read-recipients --read-envelope-from'
|
||||
#send_mail = { hostname = "smtp.example.com", port = 587, auth = { type = "auto", username = "user", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/user.gpg" } }, security = { type = "STARTTLS" } }
|
||||
|
||||
# Set mailbox-specific settings
|
||||
[accounts.account-name.mailboxes]
|
||||
"INBOX" = { alias="Inbox" } #inline table
|
||||
"drafts" = { alias="Drafts" } #inline table
|
||||
[accounts.account-name.mailboxes."foobar-devel"] # or a regular table
|
||||
ignore = true # don't show notifications for this mailbox
|
||||
.Ed
|
||||
.\"
|
||||
.\"
|
||||
.\"
|
||||
.Sh MBOX ACCOUNT
|
||||
An example configuration:
|
||||
.\"
|
||||
.\"
|
||||
.\"
|
||||
.Bd -literal
|
||||
[accounts.account-name]
|
||||
root_mailbox = "/var/mail/username"
|
||||
format = "mbox"
|
||||
listing.index_style = "Compact"
|
||||
identity="username@hostname.local"
|
||||
composing.send_mail = '/bin/false'
|
||||
.Ed
|
||||
.Sh IMAP ACCOUNT
|
||||
.Bd -literal
|
||||
[accounts."account-name"]
|
||||
root_mailbox = "INBOX"
|
||||
format = "imap"
|
||||
server_hostname="mail.example.com"
|
||||
server_password="pha2hiLohs2eeeish2phaii1We3ood4chakaiv0hien2ahie3m"
|
||||
server_username="username@example.com"
|
||||
#server_port="993" # imaps
|
||||
server_port="143" # STARTTLS
|
||||
use_starttls=true #optional
|
||||
send_mail = { hostname = "smtp.example.com", port = 587, auth = { type = "auto", username = "user", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/user.gpg" } }, security = { type = "STARTTLS" } }
|
||||
display_name = "Name Name"
|
||||
identity = "username@example.com"
|
||||
## show only specific mailboxes:
|
||||
#subscribed_mailboxes = ["INBOX", "INBOX/Sent", "INBOX/Drafts", "INBOX/Junk"]
|
||||
.Ed
|
||||
.Ss Gmail account example
|
||||
.Bd -literal
|
||||
[accounts."account-name"]
|
||||
root_mailbox = '[Gmail]'
|
||||
format = "imap"
|
||||
send_mail = { hostname = "smtp.gmail.com", port = 587, auth = { type = "auto", username = "user", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/user.gpg" } }, security = { type = "STARTTLS" } }
|
||||
server_hostname='imap.gmail.com'
|
||||
server_password="password"
|
||||
server_username="username@gmail.com"
|
||||
server_port="993"
|
||||
listing.index_style = "Conversations"
|
||||
identity = "username@gmail.com"
|
||||
display_name = "Name Name"
|
||||
# Gmail auto saves sent mail to Sent folder, so don't duplicate the effort:
|
||||
composing.store_sent_mail = false
|
||||
.Ed
|
||||
|
||||
.Sh JMAP ACCOUNT
|
||||
The
|
||||
.Ic server_url
|
||||
option must hold the address of the server's session endpoint.
|
||||
.Bd -literal
|
||||
[accounts."account-name"]
|
||||
root_mailbox = "INBOX"
|
||||
format = "jmap"
|
||||
send_mail = 'server_submission'
|
||||
server_url="http://localhost:8080"
|
||||
server_username="user@hostname.local"
|
||||
server_password="changeme"
|
||||
identity = "user@hostname.local"
|
||||
.Ed
|
||||
.Ss fastmail.com account example
|
||||
.Lk https://fastmail.com/ Fastmail
|
||||
uses the
|
||||
.Em Bearer token
|
||||
authentication mechanism, so the option
|
||||
.Ic use_token
|
||||
must be enabled:
|
||||
.Bd -literal
|
||||
[accounts."fastmail-jmap"]
|
||||
root_mailbox = "INBOX"
|
||||
format = "jmap"
|
||||
server_url="https://api.fastmail.com/jmap/session"
|
||||
server_username="user@fastmail.com"
|
||||
server_password="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
use_token=true
|
||||
identity = "My Name <user@fastmail.com>"
|
||||
send_mail = "server_submission"
|
||||
.Ed
|
||||
.Sh NOTMUCH ACCOUNT
|
||||
TODO
|
||||
.Sh NNTP / USENET ACCOUNT
|
||||
TODO
|
||||
.Sh SEE ALSO
|
||||
.Xr meli.conf 5 ,
|
||||
.Xr meli 1 ,
|
||||
.Xr meli-themes 5
|
||||
.Sh AUTHORS
|
||||
Copyright 2017\(en2024
|
||||
.An Manos Pitsidianakis Aq Mt manos@pitsidianak.is
|
||||
.Pp
|
||||
Released under the GPL, version 3 or greater.
|
||||
This software carries no warranty of any kind.
|
||||
.Po
|
||||
See
|
||||
.Pa COPYING
|
||||
for full copyright and warranty notices.
|
||||
.Pc
|
||||
.Ss Links
|
||||
.Bl -item -compact
|
||||
.It
|
||||
.Lk https://meli\-email.org "Website"
|
||||
.It
|
||||
.Lk https://git.meli\-email.org/meli/meli "Main\ git\ repository\ and\ issue\ tracker"
|
||||
.It
|
||||
.Lk https://codeberg.org/meli/meli "Official\ read-only\ git\ mirror\ on\ codeberg.org"
|
||||
.It
|
||||
.Lk https://github.com/meli/meli "Official\ read-only\ git\ mirror\ on\ github.com"
|
||||
.It
|
||||
.Lk https://crates.io/crates/meli "meli\ crate\ on\ crates.io"
|
||||
.El
|
||||
.\" [pager]
|
||||
.\" filter = "COLUMNS=72 /usr/local/bin/pygmentize -l email"
|
||||
.\" html_filter = "w3m -I utf-8 -T text/html"
|
||||
|
||||
.\" [notifications]
|
||||
.\" script = "notify-send"
|
||||
|
||||
.\" [composing]
|
||||
.\" # required for sending e-mail
|
||||
.\" send_mail = 'msmtp --read-recipients --read-envelope-from'
|
||||
.\" #send_mail = { hostname = "smtp.example.com", port = 587, auth = { type = "auto", username = "user", password = { type = "command_eval", value = "gpg2 --no-tty -q -d ~/.passwords/user.gpg" } }, security = { type = "STARTTLS" } }
|
||||
.\" editor_command = 'vim +/^$'
|
||||
|
||||
.\" [shortcuts]
|
||||
.\" [shortcuts.composing]
|
||||
.\" edit = 'e'
|
||||
|
||||
.\" [shortcuts.listing]
|
||||
.\" new_mail = 'm'
|
||||
.\" set_seen = 'n'
|
||||
|
||||
.\" [terminal]
|
||||
.\" theme = "light"
|
|
@ -1,73 +0,0 @@
|
|||
[terminal.themes.ibm-modern]
|
||||
"theme_default" = { fg = "$Black100", bg = "$White0", attrs = "Default" }
|
||||
"status.bar" = { fg = "$Black100", bg = "$Magenta40", attrs = "theme_default" }
|
||||
"status.notification" = { fg = "$Black100", bg = "$Magenta40", attrs = "theme_default" }
|
||||
"tab.focused" = { fg = "$White0", bg = "$Purple40", attrs = "theme_default" }
|
||||
"tab.unfocused" = { fg = "theme_default", bg = "theme_default", attrs = "theme_default" }
|
||||
"tab.bar" = { fg = "theme_default", bg = "theme_default", attrs = "theme_default" }
|
||||
"widgets.list.header" = { fg = "theme_default", bg = "theme_default", attrs = "Bold" }
|
||||
"widgets.form.label" = { fg = "theme_default", bg = "theme_default", attrs = "Bold" }
|
||||
"widgets.form.field" = { fg = "theme_default", bg = "theme_default", attrs = "theme_default" }
|
||||
"widgets.form.highlighted" = { fg = "theme_default", bg = "$Cyan30", attrs = "theme_default" }
|
||||
"widgets.options.highlighted" = { fg = "$Cyan10", bg = "$Teal30", attrs = "theme_default" }
|
||||
"mail.sidebar" = { fg = "theme_default", bg = "$CoolGray10", attrs = "theme_default" }
|
||||
"mail.sidebar_account_name" = { fg = "mail.sidebar", attrs = "Bold" }
|
||||
"mail.sidebar_unread_count" = { fg = "$Magenta40", bg = "$CoolGray10" }
|
||||
"mail.sidebar_index" = { fg = "theme_default", bg = "theme_default" }
|
||||
"mail.sidebar_highlighted" = { fg = "theme_default", bg = "$CoolGray10" }
|
||||
"mail.sidebar_highlighted_unread_count" = { from = "mail.sidebar_highlighted" }
|
||||
"mail.sidebar_highlighted_index" = { fg = "mail.sidebar_index", bg = "mail.sidebar_highlighted", attrs = "theme_default" }
|
||||
"mail.sidebar_highlighted_account" = { fg = "mail.sidebar_highlighted", bg = "mail.sidebar_highlighted", attrs = "theme_default" }
|
||||
"mail.sidebar_highlighted_account_name" = { fg = "mail.sidebar_highlighted_account", bg = "mail.sidebar_highlighted_account", attrs = "Bold" }
|
||||
"mail.sidebar_highlighted_account_unread_count" = { fg = "mail.sidebar_unread_count", bg = "mail.sidebar_highlighted_account", attrs = "theme_default" }
|
||||
"mail.sidebar_highlighted_account_index" = { fg = "mail.sidebar_index", bg = "mail.sidebar_highlighted_account", attrs = "theme_default" }
|
||||
"mail.listing.compact.even" = { fg = "theme_default", bg = "theme_default", attrs = "theme_default" }
|
||||
"mail.listing.compact.odd" = { fg = "theme_default", bg = "theme_default", attrs = "theme_default" }
|
||||
"mail.listing.compact.even_unseen" = { fg = "$Black100", bg = "theme_default", attrs = "theme_default" }
|
||||
"mail.listing.compact.odd_unseen" = { fg = "$Black100", bg = "theme_default", attrs = "theme_default" }
|
||||
"mail.listing.compact.even_selected" = { fg = "$CoolGray10", attrs = "theme_default" }
|
||||
"mail.listing.compact.odd_selected" = { fg = "$CoolGray10", attrs = "theme_default" }
|
||||
"mail.listing.compact.even_highlighted" = { fg = "theme_default", bg = "$CoolGray10", attrs = "theme_default" }
|
||||
"mail.listing.compact.odd_highlighted" = { fg = "theme_default", bg = "$CoolGray10", attrs = "theme_default" }
|
||||
"mail.listing.conversations" = { fg = "theme_default", bg = "theme_default", attrs = "theme_default" }
|
||||
"mail.listing.conversations.subject" = { fg = "theme_default", bg = "theme_default", attrs = "theme_default" }
|
||||
"mail.listing.conversations.from" = { fg = "theme_default", bg = "theme_default", attrs = "theme_default" }
|
||||
"mail.listing.conversations.date" = { fg = "theme_default", bg = "theme_default", attrs = "theme_default" }
|
||||
"mail.listing.conversations.unseen" = { fg = "theme_default", bg = "theme_default", attrs = "theme_default" }
|
||||
"mail.listing.conversations.highlighted" = { fg = "theme_default", bg = "$CoolGray10", attrs = "Bold" }
|
||||
"mail.listing.conversations.selected" = { fg = "$CoolGray10", bg = "$CoolGray30", attrs = "theme_default" }
|
||||
"mail.listing.plain.even" = { fg = "mail.listing.compact.even", bg = "mail.listing.compact.even", attrs = "theme_default" }
|
||||
"mail.listing.plain.odd" = { fg = "mail.listing.compact.odd", bg = "mail.listing.compact.odd", attrs = "theme_default" }
|
||||
"mail.listing.plain.even_unseen" = { fg = "$Black100", bg = "$CoolGray30", attrs = "theme_default" }
|
||||
"mail.listing.plain.odd_unseen" = { fg = "$Black100", bg = "$CoolGray30", attrs = "theme_default" }
|
||||
"mail.listing.plain.even_selected" = { fg = "theme_default", bg = "LightCoral", attrs = "theme_default" }
|
||||
"mail.listing.plain.odd_selected" = { fg = "theme_default", bg = "LightCoral", attrs = "theme_default" }
|
||||
"mail.listing.plain.even_highlighted" = { fg = "theme_default", bg = "$CoolGray10", attrs = "theme_default" }
|
||||
"mail.listing.plain.odd_highlighted" = { fg = "theme_default", bg = "$CoolGray10", attrs = "theme_default" }
|
||||
"mail.view.headers" = { fg = "$Black100", bg = "$Purple40", attrs = "theme_default" }
|
||||
"mail.view.headers_names" = { fg = "$Black100", bg = "$Magenta40", attrs = "mail.view.headers" }
|
||||
"mail.view.headers_area" = { fg = "theme_default", bg = "$Purple40", attrs = "theme_default" }
|
||||
"mail.view.body" = { fg = "theme_default", bg = "theme_default", attrs = "theme_default" }
|
||||
"mail.view.thread.indentation.a" = { fg = "theme_default", bg = "CornflowerBlue", attrs = "theme_default" }
|
||||
"mail.view.thread.indentation.b" = { fg = "theme_default", bg = "Red1", attrs = "theme_default" }
|
||||
"mail.view.thread.indentation.c" = { fg = "theme_default", bg = "Pink3", attrs = "theme_default" }
|
||||
"mail.view.thread.indentation.d" = { fg = "theme_default", bg = "Gold1", attrs = "theme_default" }
|
||||
"mail.view.thread.indentation.e" = { fg = "theme_default", bg = "Orange3", attrs = "theme_default" }
|
||||
"mail.view.thread.indentation.f" = { fg = "theme_default", bg = "CadetBlue", attrs = "theme_default" }
|
||||
"mail.listing.attachment_flag" = { fg = "$CoolGray30", bg = "theme_default", attrs = "theme_default" }
|
||||
"mail.listing.thread_snooze_flag" = { fg = "$Magenta40", bg = "theme_default", attrs = "theme_default" }
|
||||
"mail.listing.tag_default" = { fg = "$White0", bg = "$Black100", attrs = "Bold" }
|
||||
"pager.highlight_search" = { fg = "theme_default", bg = "$Teal30", attrs = "Bold" }
|
||||
"pager.highlight_search_current" = { fg = "$CoolGray10", bg = "$Teal30", attrs = "Bold" }
|
||||
|
||||
[terminal.themes.ibm-modern.color_aliases]
|
||||
"Blue60" = "#0f62fe"
|
||||
"Black100" = "#000000"
|
||||
"White0" = "#ffffff"
|
||||
"Cyan30" = "#82cfff"
|
||||
"Purple40" = "#be95ff"
|
||||
"Magenta40" = "#ff7eb6"
|
||||
"Teal30" = "#3ddbd9"
|
||||
"Cyan10" = "#e5f6ff"
|
||||
"CoolGray10" = "#f2f4f8"
|
||||
"CoolGray30" = "#c1c7cd"
|
1894
meli/src/accounts.rs
|
@ -1,92 +0,0 @@
|
|||
/*
|
||||
* meli - accounts module.
|
||||
*
|
||||
* Copyright 2023 Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//! Account mail backend operations.
|
||||
|
||||
use super::*;
|
||||
|
||||
impl Account {
|
||||
pub fn set_flags(
|
||||
&mut self,
|
||||
env_hashes: EnvelopeHashBatch,
|
||||
mailbox_hash: MailboxHash,
|
||||
flags: SmallVec<[FlagOp; 8]>,
|
||||
) -> Result<JobId> {
|
||||
let fut = self.backend.write().unwrap().set_flags(
|
||||
env_hashes.clone(),
|
||||
mailbox_hash,
|
||||
flags.clone(),
|
||||
)?;
|
||||
let handle =
|
||||
self.main_loop_handler
|
||||
.job_executor
|
||||
.spawn("set-flags".into(), fut, self.is_async());
|
||||
let job_id = handle.job_id;
|
||||
self.insert_job(
|
||||
job_id,
|
||||
JobRequest::SetFlags {
|
||||
env_hashes,
|
||||
mailbox_hash,
|
||||
flags,
|
||||
handle,
|
||||
},
|
||||
);
|
||||
|
||||
Ok(job_id)
|
||||
}
|
||||
|
||||
// #[cfg(not(feature = "sqlite3"))]
|
||||
// pub(super) fn update_cached_env(&mut self, _: Envelope, _:
|
||||
// Option<EnvelopeHash>) {}
|
||||
|
||||
#[cfg(feature = "sqlite3")]
|
||||
pub(super) fn update_cached_env(&mut self, env: Envelope, old_hash: Option<EnvelopeHash>) {
|
||||
if self.settings.conf.search_backend == SearchBackend::Sqlite3 {
|
||||
let msg_id = env.message_id().to_string();
|
||||
let name = self.name.clone();
|
||||
let backend = self.backend.clone();
|
||||
let fut = async move {
|
||||
crate::sqlite3::AccountCache::remove(
|
||||
name.clone(),
|
||||
old_hash.unwrap_or_else(|| env.hash()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
crate::sqlite3::AccountCache::insert(env, backend, name).await?;
|
||||
Ok(())
|
||||
};
|
||||
let handle = self.main_loop_handler.job_executor.spawn(
|
||||
"sqlite3::remove".into(),
|
||||
fut,
|
||||
crate::sqlite3::AccountCache::is_async(),
|
||||
);
|
||||
self.insert_job(
|
||||
handle.job_id,
|
||||
JobRequest::Generic {
|
||||
name: format!("Update envelope {} in sqlite3 cache", msg_id).into(),
|
||||
handle,
|
||||
log_level: LogLevel::TRACE,
|
||||
on_finish: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,247 +0,0 @@
|
|||
//
|
||||
// meli - accounts module.
|
||||
//
|
||||
// Copyright 2017 Emmanouil Pitsidianakis <manos@pitsidianak.is>
|
||||
//
|
||||
// This file is part of meli.
|
||||
//
|
||||
// meli is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// meli is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// SPDX-License-Identifier: EUPL-1.2 OR GPL-3.0-or-later
|
||||
|
||||
use std::{borrow::Cow, collections::HashMap, pin::Pin};
|
||||
|
||||
use futures::stream::Stream;
|
||||
use melib::{backends::*, email::*, error::Result, LogLevel};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::{is_variant, jobs::JoinHandle, StatusEvent};
|
||||
|
||||
pub enum MailboxJobRequest {
|
||||
Mailboxes {
|
||||
handle: JoinHandle<Result<HashMap<MailboxHash, Mailbox>>>,
|
||||
},
|
||||
CreateMailbox {
|
||||
path: String,
|
||||
handle: JoinHandle<Result<(MailboxHash, HashMap<MailboxHash, Mailbox>)>>,
|
||||
},
|
||||
DeleteMailbox {
|
||||
mailbox_hash: MailboxHash,
|
||||
handle: JoinHandle<Result<HashMap<MailboxHash, Mailbox>>>,
|
||||
},
|
||||
RenameMailbox {
|
||||
mailbox_hash: MailboxHash,
|
||||
new_path: String,
|
||||
handle: JoinHandle<Result<Mailbox>>,
|
||||
},
|
||||
SetMailboxPermissions {
|
||||
mailbox_hash: MailboxHash,
|
||||
handle: JoinHandle<Result<()>>,
|
||||
},
|
||||
SetMailboxSubscription {
|
||||
mailbox_hash: MailboxHash,
|
||||
new_value: bool,
|
||||
handle: JoinHandle<Result<()>>,
|
||||
},
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for MailboxJobRequest {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::CreateMailbox { .. } => write!(f, "JobRequest::CreateMailbox"),
|
||||
Self::DeleteMailbox { mailbox_hash, .. } => {
|
||||
write!(f, "JobRequest::DeleteMailbox({})", mailbox_hash)
|
||||
}
|
||||
Self::RenameMailbox {
|
||||
mailbox_hash,
|
||||
new_path,
|
||||
..
|
||||
} => {
|
||||
write!(f, "JobRequest::RenameMailbox {mailbox_hash} to {new_path} ")
|
||||
}
|
||||
Self::SetMailboxPermissions { .. } => {
|
||||
write!(f, "JobRequest::SetMailboxPermissions")
|
||||
}
|
||||
Self::SetMailboxSubscription { .. } => {
|
||||
write!(f, "JobRequest::SetMailboxSubscription")
|
||||
}
|
||||
Self::Mailboxes { .. } => write!(f, "JobRequest::Mailboxes"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for MailboxJobRequest {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Mailboxes { .. } => write!(f, "Get mailbox list"),
|
||||
Self::CreateMailbox { path, .. } => write!(f, "Create mailbox {}", path),
|
||||
Self::DeleteMailbox { .. } => write!(f, "Delete mailbox"),
|
||||
Self::RenameMailbox { new_path, .. } => write!(f, "Rename mailbox to {new_path}"),
|
||||
Self::SetMailboxPermissions { .. } => write!(f, "Set mailbox permissions"),
|
||||
Self::SetMailboxSubscription { .. } => write!(f, "Set mailbox subscription"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MailboxJobRequest {
|
||||
pub fn cancel(&self) -> Option<StatusEvent> {
|
||||
match self {
|
||||
Self::Mailboxes { handle } => handle.cancel(),
|
||||
Self::CreateMailbox { handle, .. } => handle.cancel(),
|
||||
Self::DeleteMailbox { handle, .. } => handle.cancel(),
|
||||
Self::RenameMailbox { handle, .. } => handle.cancel(),
|
||||
Self::SetMailboxPermissions { handle, .. } => handle.cancel(),
|
||||
Self::SetMailboxSubscription { handle, .. } => handle.cancel(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum JobRequest {
|
||||
Fetch {
|
||||
mailbox_hash: MailboxHash,
|
||||
#[allow(clippy::type_complexity)]
|
||||
handle: JoinHandle<(
|
||||
Option<Result<Vec<Envelope>>>,
|
||||
Pin<Box<dyn Stream<Item = Result<Vec<Envelope>>> + Send + 'static>>,
|
||||
)>,
|
||||
},
|
||||
Generic {
|
||||
name: Cow<'static, str>,
|
||||
log_level: LogLevel,
|
||||
handle: JoinHandle<Result<()>>,
|
||||
on_finish: Option<crate::types::CallbackFn>,
|
||||
},
|
||||
IsOnline {
|
||||
handle: JoinHandle<Result<()>>,
|
||||
},
|
||||
Refresh {
|
||||
mailbox_hash: MailboxHash,
|
||||
handle: JoinHandle<Result<()>>,
|
||||
},
|
||||
SetFlags {
|
||||
env_hashes: EnvelopeHashBatch,
|
||||
mailbox_hash: MailboxHash,
|
||||
flags: SmallVec<[FlagOp; 8]>,
|
||||
handle: JoinHandle<Result<()>>,
|
||||
},
|
||||
SaveMessage {
|
||||
bytes: Vec<u8>,
|
||||
mailbox_hash: MailboxHash,
|
||||
handle: JoinHandle<Result<()>>,
|
||||
},
|
||||
SendMessage,
|
||||
SendMessageBackground {
|
||||
handle: JoinHandle<Result<()>>,
|
||||
},
|
||||
DeleteMessages {
|
||||
env_hashes: EnvelopeHashBatch,
|
||||
handle: JoinHandle<Result<()>>,
|
||||
},
|
||||
Watch {
|
||||
handle: JoinHandle<Result<()>>,
|
||||
},
|
||||
Mailbox(MailboxJobRequest),
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for JobRequest {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Generic { name, .. } => write!(f, "JobRequest::Generic({})", name),
|
||||
Self::Mailbox(inner) => std::fmt::Debug::fmt(inner, f),
|
||||
Self::Fetch { mailbox_hash, .. } => {
|
||||
write!(f, "JobRequest::Fetch({})", mailbox_hash)
|
||||
}
|
||||
Self::IsOnline { .. } => write!(f, "JobRequest::IsOnline"),
|
||||
Self::Refresh { .. } => write!(f, "JobRequest::Refresh"),
|
||||
Self::SetFlags {
|
||||
env_hashes,
|
||||
mailbox_hash,
|
||||
flags,
|
||||
..
|
||||
} => f
|
||||
.debug_struct(stringify!(JobRequest::SetFlags))
|
||||
.field("env_hashes", &env_hashes)
|
||||
.field("mailbox_hash", &mailbox_hash)
|
||||
.field("flags", &flags)
|
||||
.finish(),
|
||||
Self::SaveMessage { .. } => write!(f, "JobRequest::SaveMessage"),
|
||||
Self::DeleteMessages { .. } => write!(f, "JobRequest::DeleteMessages"),
|
||||
Self::Watch { .. } => write!(f, "JobRequest::Watch"),
|
||||
Self::SendMessage => write!(f, "JobRequest::SendMessage"),
|
||||
Self::SendMessageBackground { .. } => {
|
||||
write!(f, "JobRequest::SendMessageBackground")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for JobRequest {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Generic { name, .. } => write!(f, "{}", name),
|
||||
Self::Mailbox(inner) => std::fmt::Display::fmt(inner, f),
|
||||
Self::Fetch { .. } => write!(f, "Mailbox fetch"),
|
||||
Self::IsOnline { .. } => write!(f, "Online status check"),
|
||||
Self::Refresh { .. } => write!(f, "Refresh mailbox"),
|
||||
Self::SetFlags {
|
||||
env_hashes, flags, ..
|
||||
} => write!(
|
||||
f,
|
||||
"Set flags for {} message{}: {:?}",
|
||||
env_hashes.len(),
|
||||
if env_hashes.len() == 1 { "" } else { "s" },
|
||||
flags
|
||||
),
|
||||
Self::SaveMessage { .. } => write!(f, "Save message"),
|
||||
Self::DeleteMessages { env_hashes, .. } => write!(
|
||||
f,
|
||||
"Delete {} message{}",
|
||||
env_hashes.len(),
|
||||
if env_hashes.len() == 1 { "" } else { "s" }
|
||||
),
|
||||
Self::Watch { .. } => write!(f, "Background watch"),
|
||||
Self::SendMessageBackground { .. } | Self::SendMessage => {
|
||||
write!(f, "Sending message")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl JobRequest {
|
||||
is_variant! { is_watch, Watch { .. } }
|
||||
is_variant! { is_online, IsOnline { .. } }
|
||||
is_variant! { is_any_fetch, Fetch { .. } }
|
||||
|
||||
pub fn is_fetch(&self, mailbox_hash: MailboxHash) -> bool {
|
||||
matches!(self, Self::Fetch {
|
||||
mailbox_hash: h, ..
|
||||
} if *h == mailbox_hash)
|
||||
}
|
||||
|
||||
pub fn cancel(&self) -> Option<StatusEvent> {
|
||||
match self {
|
||||
Self::Generic { handle, .. } => handle.cancel(),
|
||||
Self::Mailbox(inner) => inner.cancel(),
|
||||
Self::Fetch { handle, .. } => handle.cancel(),
|
||||
Self::IsOnline { handle, .. } => handle.cancel(),
|
||||
Self::Refresh { handle, .. } => handle.cancel(),
|
||||
Self::SetFlags { handle, .. } => handle.cancel(),
|
||||
Self::SaveMessage { handle, .. } => handle.cancel(),
|
||||
Self::DeleteMessages { handle, .. } => handle.cancel(),
|
||||
Self::Watch { handle, .. } => handle.cancel(),
|
||||
Self::SendMessage => None,
|
||||
Self::SendMessageBackground { handle, .. } => handle.cancel(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,261 +0,0 @@
|
|||
//
|
||||
// meli - accounts module.
|
||||
//
|
||||
// Copyright 2017 Emmanouil Pitsidianakis <manos@pitsidianak.is>
|
||||
//
|
||||
// This file is part of meli.
|
||||
//
|
||||
// meli is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// meli is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// SPDX-License-Identifier: EUPL-1.2 OR GPL-3.0-or-later
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use melib::{
|
||||
backends::{Mailbox, MailboxHash},
|
||||
error::Error,
|
||||
log,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::{conf::FileMailboxConf, is_variant};
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub enum MailboxStatus {
|
||||
Available,
|
||||
Failed(Error),
|
||||
/// first argument is done work, and second is total work
|
||||
Parsing(usize, usize),
|
||||
#[default]
|
||||
None,
|
||||
}
|
||||
|
||||
impl MailboxStatus {
|
||||
is_variant! { is_available, Available }
|
||||
is_variant! { is_parsing, Parsing(_, _) }
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MailboxEntry {
|
||||
pub status: MailboxStatus,
|
||||
pub name: String,
|
||||
pub path: String,
|
||||
pub ref_mailbox: Mailbox,
|
||||
pub conf: FileMailboxConf,
|
||||
}
|
||||
|
||||
impl MailboxEntry {
|
||||
pub fn new(
|
||||
status: MailboxStatus,
|
||||
name: String,
|
||||
ref_mailbox: Mailbox,
|
||||
conf: FileMailboxConf,
|
||||
) -> Self {
|
||||
let mut ret = Self {
|
||||
status,
|
||||
name,
|
||||
path: ref_mailbox.path().into(),
|
||||
ref_mailbox,
|
||||
conf,
|
||||
};
|
||||
match ret.conf.mailbox_conf.extra.get("encoding") {
|
||||
None => {}
|
||||
Some(v) if ["utf-8", "utf8"].iter().any(|e| v.eq_ignore_ascii_case(e)) => {}
|
||||
Some(v) if ["utf-7", "utf7"].iter().any(|e| v.eq_ignore_ascii_case(e)) => {
|
||||
ret.name = melib::backends::utf7::decode_utf7_imap(&ret.name);
|
||||
ret.path = melib::backends::utf7::decode_utf7_imap(&ret.path);
|
||||
}
|
||||
Some(other) => {
|
||||
log::warn!(
|
||||
"mailbox `{}`: unrecognized mailbox name charset: {}",
|
||||
&ret.name,
|
||||
other
|
||||
);
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn status(&self) -> String {
|
||||
match self.status {
|
||||
MailboxStatus::Available => format!(
|
||||
"{} [{} messages]",
|
||||
self.name(),
|
||||
self.ref_mailbox.count().ok().unwrap_or((0, 0)).1
|
||||
),
|
||||
MailboxStatus::Failed(ref e) => e.to_string(),
|
||||
MailboxStatus::None => "Retrieving mailbox.".to_string(),
|
||||
MailboxStatus::Parsing(done, total) => {
|
||||
format!("Parsing messages. [{}/{}]", done, total)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
if let Some(name) = self.conf.mailbox_conf.alias.as_ref() {
|
||||
name
|
||||
} else {
|
||||
self.ref_mailbox.name()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize)]
|
||||
pub struct MailboxNode {
|
||||
pub hash: MailboxHash,
|
||||
pub depth: usize,
|
||||
pub indentation: u32,
|
||||
pub has_sibling: bool,
|
||||
pub children: Vec<MailboxNode>,
|
||||
}
|
||||
|
||||
pub fn build_mailboxes_order(
|
||||
tree: &mut Vec<MailboxNode>,
|
||||
mailbox_entries: &IndexMap<MailboxHash, MailboxEntry>,
|
||||
mailboxes_order: &mut Vec<MailboxHash>,
|
||||
) {
|
||||
tree.clear();
|
||||
mailboxes_order.clear();
|
||||
for (h, f) in mailbox_entries.iter() {
|
||||
if f.ref_mailbox.parent().is_none() {
|
||||
fn rec(
|
||||
h: MailboxHash,
|
||||
mailbox_entries: &IndexMap<MailboxHash, MailboxEntry>,
|
||||
depth: usize,
|
||||
) -> MailboxNode {
|
||||
let mut node = MailboxNode {
|
||||
hash: h,
|
||||
children: Vec::new(),
|
||||
depth,
|
||||
indentation: 0,
|
||||
has_sibling: false,
|
||||
};
|
||||
for &c in mailbox_entries[&h].ref_mailbox.children() {
|
||||
if mailbox_entries.contains_key(&c) {
|
||||
node.children.push(rec(c, mailbox_entries, depth + 1));
|
||||
}
|
||||
}
|
||||
node
|
||||
}
|
||||
|
||||
tree.push(rec(*h, mailbox_entries, 0));
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! mailbox_eq_key {
|
||||
($mailbox:expr) => {{
|
||||
if let Some(sort_order) = $mailbox.conf.mailbox_conf.sort_order {
|
||||
(0, sort_order, $mailbox.ref_mailbox.path())
|
||||
} else {
|
||||
(1, 0, $mailbox.ref_mailbox.path())
|
||||
}
|
||||
}};
|
||||
}
|
||||
tree.sort_unstable_by(|a, b| {
|
||||
if mailbox_entries[&b.hash]
|
||||
.conf
|
||||
.mailbox_conf
|
||||
.sort_order
|
||||
.is_none()
|
||||
&& mailbox_entries[&b.hash]
|
||||
.ref_mailbox
|
||||
.path()
|
||||
.eq_ignore_ascii_case("INBOX")
|
||||
{
|
||||
std::cmp::Ordering::Greater
|
||||
} else if mailbox_entries[&a.hash]
|
||||
.conf
|
||||
.mailbox_conf
|
||||
.sort_order
|
||||
.is_none()
|
||||
&& mailbox_entries[&a.hash]
|
||||
.ref_mailbox
|
||||
.path()
|
||||
.eq_ignore_ascii_case("INBOX")
|
||||
{
|
||||
std::cmp::Ordering::Less
|
||||
} else {
|
||||
mailbox_eq_key!(mailbox_entries[&a.hash])
|
||||
.cmp(&mailbox_eq_key!(mailbox_entries[&b.hash]))
|
||||
}
|
||||
});
|
||||
|
||||
let mut stack: SmallVec<[Option<&MailboxNode>; 16]> = SmallVec::new();
|
||||
for n in tree.iter_mut() {
|
||||
mailboxes_order.push(n.hash);
|
||||
n.children.sort_unstable_by(|a, b| {
|
||||
if mailbox_entries[&b.hash]
|
||||
.conf
|
||||
.mailbox_conf
|
||||
.sort_order
|
||||
.is_none()
|
||||
&& mailbox_entries[&b.hash]
|
||||
.ref_mailbox
|
||||
.path()
|
||||
.eq_ignore_ascii_case("INBOX")
|
||||
{
|
||||
std::cmp::Ordering::Greater
|
||||
} else if mailbox_entries[&a.hash]
|
||||
.conf
|
||||
.mailbox_conf
|
||||
.sort_order
|
||||
.is_none()
|
||||
&& mailbox_entries[&a.hash]
|
||||
.ref_mailbox
|
||||
.path()
|
||||
.eq_ignore_ascii_case("INBOX")
|
||||
{
|
||||
std::cmp::Ordering::Less
|
||||
} else {
|
||||
mailbox_eq_key!(mailbox_entries[&a.hash])
|
||||
.cmp(&mailbox_eq_key!(mailbox_entries[&b.hash]))
|
||||
}
|
||||
});
|
||||
stack.extend(n.children.iter().rev().map(Some));
|
||||
while let Some(Some(next)) = stack.pop() {
|
||||
mailboxes_order.push(next.hash);
|
||||
stack.extend(next.children.iter().rev().map(Some));
|
||||
}
|
||||
}
|
||||
drop(stack);
|
||||
for node in tree.iter_mut() {
|
||||
fn rec(
|
||||
node: &mut MailboxNode,
|
||||
mailbox_entries: &IndexMap<MailboxHash, MailboxEntry>,
|
||||
mut indentation: u32,
|
||||
has_sibling: bool,
|
||||
) {
|
||||
node.indentation = indentation;
|
||||
node.has_sibling = has_sibling;
|
||||
let mut iter = (0..node.children.len())
|
||||
.filter(|i| {
|
||||
mailbox_entries[&node.children[*i].hash]
|
||||
.ref_mailbox
|
||||
.is_subscribed()
|
||||
})
|
||||
.collect::<SmallVec<[_; 8]>>()
|
||||
.into_iter()
|
||||
.peekable();
|
||||
indentation <<= 1;
|
||||
if has_sibling {
|
||||
indentation |= 1;
|
||||
}
|
||||
while let Some(i) = iter.next() {
|
||||
let c = &mut node.children[i];
|
||||
rec(c, mailbox_entries, indentation, iter.peek().is_some());
|
||||
}
|
||||
}
|
||||
|
||||
rec(node, mailbox_entries, 0, false);
|
||||
}
|
||||
}
|
|
@ -1,439 +0,0 @@
|
|||
//
|
||||
// meli
|
||||
//
|
||||
// Copyright 2024 Emmanouil Pitsidianakis <manos@pitsidianak.is>
|
||||
//
|
||||
// This file is part of meli.
|
||||
//
|
||||
// meli is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// meli is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// SPDX-License-Identifier: EUPL-1.2 OR GPL-3.0-or-later
|
||||
|
||||
use melib::conf::ToggleFlag;
|
||||
|
||||
use super::*;
|
||||
use crate::command::actions::MailboxOperation;
|
||||
|
||||
impl Account {
|
||||
pub fn mailbox_operation(&mut self, op: MailboxOperation) -> Result<JobId> {
|
||||
if self.settings.account.read_only {
|
||||
return Err(Error::new("Account is read-only."));
|
||||
}
|
||||
match op {
|
||||
MailboxOperation::Create(path) => {
|
||||
let job = self
|
||||
.backend
|
||||
.write()
|
||||
.unwrap()
|
||||
.create_mailbox(path.to_string())?;
|
||||
let handle = self.main_loop_handler.job_executor.spawn(
|
||||
"create_mailbox".into(),
|
||||
job,
|
||||
self.is_async(),
|
||||
);
|
||||
let job_id = handle.job_id;
|
||||
self.insert_job(
|
||||
handle.job_id,
|
||||
JobRequest::Mailbox(MailboxJobRequest::CreateMailbox { path, handle }),
|
||||
);
|
||||
Ok(job_id)
|
||||
}
|
||||
MailboxOperation::Delete(path) => {
|
||||
if self.mailbox_entries.len() == 1 {
|
||||
return Err(Error::new("Cannot delete only mailbox."));
|
||||
}
|
||||
|
||||
let mailbox_hash = self.mailbox_by_path(&path)?;
|
||||
let job = self.backend.write().unwrap().delete_mailbox(mailbox_hash)?;
|
||||
let handle = self.main_loop_handler.job_executor.spawn(
|
||||
"delete-mailbox".into(),
|
||||
job,
|
||||
self.is_async(),
|
||||
);
|
||||
let job_id = handle.job_id;
|
||||
self.insert_job(
|
||||
handle.job_id,
|
||||
JobRequest::Mailbox(MailboxJobRequest::DeleteMailbox {
|
||||
mailbox_hash,
|
||||
handle,
|
||||
}),
|
||||
);
|
||||
Ok(job_id)
|
||||
}
|
||||
MailboxOperation::Subscribe(path) => {
|
||||
let mailbox_hash = self.mailbox_by_path(&path)?;
|
||||
let job = self
|
||||
.backend
|
||||
.write()
|
||||
.unwrap()
|
||||
.set_mailbox_subscription(mailbox_hash, true)?;
|
||||
let handle = self.main_loop_handler.job_executor.spawn(
|
||||
"subscribe-mailbox".into(),
|
||||
job,
|
||||
self.is_async(),
|
||||
);
|
||||
let job_id = handle.job_id;
|
||||
self.insert_job(
|
||||
handle.job_id,
|
||||
JobRequest::Mailbox(MailboxJobRequest::SetMailboxSubscription {
|
||||
mailbox_hash,
|
||||
new_value: true,
|
||||
handle,
|
||||
}),
|
||||
);
|
||||
Ok(job_id)
|
||||
}
|
||||
MailboxOperation::Unsubscribe(path) => {
|
||||
let mailbox_hash = self.mailbox_by_path(&path)?;
|
||||
let job = self
|
||||
.backend
|
||||
.write()
|
||||
.unwrap()
|
||||
.set_mailbox_subscription(mailbox_hash, false)?;
|
||||
let handle = self.main_loop_handler.job_executor.spawn(
|
||||
"unsubscribe-mailbox".into(),
|
||||
job,
|
||||
self.is_async(),
|
||||
);
|
||||
let job_id = handle.job_id;
|
||||
self.insert_job(
|
||||
job_id,
|
||||
JobRequest::Mailbox(MailboxJobRequest::SetMailboxSubscription {
|
||||
mailbox_hash,
|
||||
new_value: false,
|
||||
handle,
|
||||
}),
|
||||
);
|
||||
Ok(job_id)
|
||||
}
|
||||
MailboxOperation::Rename(path, new_path) => {
|
||||
let mailbox_hash = self.mailbox_by_path(&path)?;
|
||||
let job = self
|
||||
.backend
|
||||
.write()
|
||||
.unwrap()
|
||||
.rename_mailbox(mailbox_hash, new_path.clone())?;
|
||||
let handle = self.main_loop_handler.job_executor.spawn(
|
||||
format!("rename-mailbox {path} to {new_path}").into(),
|
||||
job,
|
||||
self.is_async(),
|
||||
);
|
||||
let job_id = handle.job_id;
|
||||
self.insert_job(
|
||||
job_id,
|
||||
JobRequest::Mailbox(MailboxJobRequest::RenameMailbox {
|
||||
handle,
|
||||
mailbox_hash,
|
||||
new_path,
|
||||
}),
|
||||
);
|
||||
Ok(job_id)
|
||||
}
|
||||
MailboxOperation::SetPermissions(_) => Err(Error::new("Not implemented.")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_mailbox_event(&mut self, job_id: JobId, mut job: MailboxJobRequest) {
|
||||
macro_rules! try_handle {
|
||||
($handle:ident, $binding:pat => $then:block) => {{
|
||||
try_handle! { $handle, Err(err) => {
|
||||
self.main_loop_handler
|
||||
.job_executor
|
||||
.set_job_success(job_id, false);
|
||||
self.main_loop_handler
|
||||
.send(ThreadEvent::UIEvent(UIEvent::Notification {
|
||||
title: None,
|
||||
body: format!("{}: {} failed", &self.name, job).into(),
|
||||
kind: Some(NotificationType::Error(err.kind)),
|
||||
source: Some(err),
|
||||
}));
|
||||
return;
|
||||
},
|
||||
$binding => $then
|
||||
}
|
||||
}};
|
||||
($handle:ident, Err($err:pat) => $then_err: block, $binding:pat => $then:block) => {{
|
||||
match $handle.chan.try_recv() {
|
||||
_err @ Ok(None) | _err @ Err(_) => {
|
||||
/* canceled */
|
||||
#[cfg(debug_assertions)]
|
||||
log::trace!(
|
||||
"handle.chan.try_recv() for job {} returned {:?}",
|
||||
job_id,
|
||||
_err
|
||||
);
|
||||
self.main_loop_handler
|
||||
.job_executor
|
||||
.set_job_success(job_id, false);
|
||||
}
|
||||
Ok(Some(Err($err))) => $then_err,
|
||||
Ok(Some(Ok($binding))) => $then,
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
match job {
|
||||
MailboxJobRequest::Mailboxes { ref mut handle } => {
|
||||
if let Ok(Some(mailboxes)) = handle.chan.try_recv() {
|
||||
if let Err(err) = mailboxes.and_then(|mailboxes| self.init(mailboxes)) {
|
||||
if !err.is_recoverable() {
|
||||
self.main_loop_handler.send(ThreadEvent::UIEvent(
|
||||
UIEvent::Notification {
|
||||
title: Some(self.name.to_string().into()),
|
||||
source: Some(err.clone()),
|
||||
body: err.to_string().into(),
|
||||
kind: Some(NotificationType::Error(err.kind)),
|
||||
},
|
||||
));
|
||||
self.main_loop_handler.send(ThreadEvent::UIEvent(
|
||||
UIEvent::AccountStatusChange(
|
||||
self.hash,
|
||||
Some(err.to_string().into()),
|
||||
),
|
||||
));
|
||||
self.is_online.set_err(err);
|
||||
self.main_loop_handler
|
||||
.job_executor
|
||||
.set_job_success(job_id, false);
|
||||
return;
|
||||
}
|
||||
let mailboxes_job = self.backend.read().unwrap().mailboxes();
|
||||
if let Ok(mailboxes_job) = mailboxes_job {
|
||||
let handle = self.main_loop_handler.job_executor.spawn(
|
||||
"list-mailboxes".into(),
|
||||
mailboxes_job,
|
||||
self.is_async(),
|
||||
);
|
||||
self.insert_job(
|
||||
handle.job_id,
|
||||
JobRequest::Mailbox(MailboxJobRequest::Mailboxes { handle }),
|
||||
);
|
||||
};
|
||||
} else {
|
||||
self.main_loop_handler.send(ThreadEvent::UIEvent(
|
||||
UIEvent::AccountStatusChange(
|
||||
self.hash,
|
||||
Some("Loaded mailboxes.".into()),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
MailboxJobRequest::CreateMailbox { ref mut handle, .. } => {
|
||||
try_handle! { handle, (mailbox_hash, mut mailboxes) => {
|
||||
self.main_loop_handler.send(ThreadEvent::UIEvent(
|
||||
UIEvent::MailboxCreate((self.hash, mailbox_hash)),
|
||||
));
|
||||
let mut new = FileMailboxConf::default();
|
||||
new.mailbox_conf.subscribe = ToggleFlag::InternalVal(true);
|
||||
new.mailbox_conf.usage = if mailboxes[&mailbox_hash].special_usage()
|
||||
!= SpecialUsageMailbox::Normal
|
||||
{
|
||||
Some(mailboxes[&mailbox_hash].special_usage())
|
||||
} else {
|
||||
let tmp = SpecialUsageMailbox::detect_usage(
|
||||
mailboxes[&mailbox_hash].name(),
|
||||
);
|
||||
if let Some(tmp) = tmp.filter(|&v| v != SpecialUsageMailbox::Normal)
|
||||
{
|
||||
mailboxes.entry(mailbox_hash).and_modify(|entry| {
|
||||
let _ = entry.set_special_usage(tmp);
|
||||
});
|
||||
}
|
||||
tmp
|
||||
};
|
||||
// if new mailbox has parent, we need to update its children field
|
||||
if let Some(parent_hash) = mailboxes[&mailbox_hash].parent() {
|
||||
self.mailbox_entries
|
||||
.entry(parent_hash)
|
||||
.and_modify(|parent| {
|
||||
parent.ref_mailbox =
|
||||
mailboxes.remove(&parent_hash).unwrap();
|
||||
});
|
||||
}
|
||||
let status = MailboxStatus::default();
|
||||
|
||||
self.mailbox_entries.insert(
|
||||
mailbox_hash,
|
||||
MailboxEntry::new(
|
||||
status,
|
||||
mailboxes[&mailbox_hash].path().to_string(),
|
||||
mailboxes.remove(&mailbox_hash).unwrap(),
|
||||
new,
|
||||
),
|
||||
);
|
||||
self.collection
|
||||
.threads
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(mailbox_hash, Threads::default());
|
||||
self.collection
|
||||
.mailboxes
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(mailbox_hash, Default::default());
|
||||
build_mailboxes_order(
|
||||
&mut self.tree,
|
||||
&self.mailbox_entries,
|
||||
&mut self.mailboxes_order,
|
||||
);
|
||||
}}
|
||||
}
|
||||
MailboxJobRequest::DeleteMailbox {
|
||||
mailbox_hash,
|
||||
ref mut handle,
|
||||
..
|
||||
} => {
|
||||
try_handle! { handle, mut mailboxes => {
|
||||
self.main_loop_handler
|
||||
.send(ThreadEvent::UIEvent(UIEvent::MailboxDelete((
|
||||
self.hash,
|
||||
mailbox_hash,
|
||||
))));
|
||||
if let Some(pos) =
|
||||
self.mailboxes_order.iter().position(|&h| h == mailbox_hash)
|
||||
{
|
||||
self.mailboxes_order.remove(pos);
|
||||
}
|
||||
if let Some(pos) = self.tree.iter().position(|n| n.hash == mailbox_hash) {
|
||||
self.tree.remove(pos);
|
||||
}
|
||||
if self.settings.sent_mailbox == Some(mailbox_hash) {
|
||||
self.settings.sent_mailbox = None;
|
||||
}
|
||||
self.collection
|
||||
.threads
|
||||
.write()
|
||||
.unwrap()
|
||||
.remove(&mailbox_hash);
|
||||
let deleted_mailbox =
|
||||
self.mailbox_entries.shift_remove(&mailbox_hash).unwrap();
|
||||
// if deleted mailbox had parent, we need to update its children field
|
||||
if let Some(parent_hash) = deleted_mailbox.ref_mailbox.parent() {
|
||||
self.mailbox_entries
|
||||
.entry(parent_hash)
|
||||
.and_modify(|parent| {
|
||||
parent.ref_mailbox = mailboxes.remove(&parent_hash).unwrap();
|
||||
});
|
||||
}
|
||||
self.collection
|
||||
.mailboxes
|
||||
.write()
|
||||
.unwrap()
|
||||
.remove(&mailbox_hash);
|
||||
build_mailboxes_order(
|
||||
&mut self.tree,
|
||||
&self.mailbox_entries,
|
||||
&mut self.mailboxes_order,
|
||||
);
|
||||
// [ref:FIXME] remove from settings as well
|
||||
|
||||
self.main_loop_handler
|
||||
.send(ThreadEvent::UIEvent(UIEvent::Notification {
|
||||
title: Some(
|
||||
format!("{}: mailbox deleted successfully", &self.name).into(),
|
||||
),
|
||||
source: None,
|
||||
body: "".into(),
|
||||
kind: Some(NotificationType::Info),
|
||||
}));
|
||||
}}
|
||||
}
|
||||
MailboxJobRequest::RenameMailbox {
|
||||
ref mut handle,
|
||||
mailbox_hash,
|
||||
ref mut new_path,
|
||||
} => {
|
||||
use indexmap::map::MutableKeys;
|
||||
try_handle! { handle, mailbox => {
|
||||
let new_hash = mailbox.hash();
|
||||
if let Some((_, key, entry)) = self.mailbox_entries.get_full_mut2(&mailbox_hash) {
|
||||
*key = new_hash;
|
||||
*entry = MailboxEntry::new(entry.status.clone(), std::mem::take(new_path), mailbox, entry.conf.clone());
|
||||
}
|
||||
if let Some(key) = self.mailboxes_order.iter_mut().find(|k| **k == mailbox_hash) {
|
||||
*key = new_hash;
|
||||
}
|
||||
if let Some((_, key, _)) = self.event_queue.get_full_mut2(&mailbox_hash) {
|
||||
*key = new_hash;
|
||||
}
|
||||
{
|
||||
let mut threads = self.collection.threads.write().unwrap();
|
||||
if let Some(entry) = threads.remove(&mailbox_hash) {
|
||||
threads.insert(new_hash, entry);
|
||||
}
|
||||
}
|
||||
{
|
||||
let mut mailboxes = self.collection.mailboxes.write().unwrap();
|
||||
if let Some(entry) = mailboxes.remove(&mailbox_hash) {
|
||||
mailboxes.insert(new_hash, entry);
|
||||
}
|
||||
}
|
||||
build_mailboxes_order(
|
||||
&mut self.tree,
|
||||
&self.mailbox_entries,
|
||||
&mut self.mailboxes_order,
|
||||
);
|
||||
}}
|
||||
}
|
||||
MailboxJobRequest::SetMailboxPermissions { ref mut handle, .. } => {
|
||||
try_handle! { handle, _ => {
|
||||
self.main_loop_handler
|
||||
.send(ThreadEvent::UIEvent(UIEvent::Notification {
|
||||
title: Some(
|
||||
format!("{}: mailbox permissions set successfully", &self.name)
|
||||
.into(),
|
||||
),
|
||||
source: None,
|
||||
body: "".into(),
|
||||
kind: Some(NotificationType::Info),
|
||||
}));
|
||||
}}
|
||||
}
|
||||
MailboxJobRequest::SetMailboxSubscription {
|
||||
ref mut handle,
|
||||
ref mailbox_hash,
|
||||
ref new_value,
|
||||
} => {
|
||||
try_handle! { handle, () => {
|
||||
if self.mailbox_entries.contains_key(mailbox_hash) {
|
||||
self.mailbox_entries.entry(*mailbox_hash).and_modify(|m| {
|
||||
m.conf.mailbox_conf.subscribe = if *new_value {
|
||||
ToggleFlag::True
|
||||
} else {
|
||||
ToggleFlag::False
|
||||
};
|
||||
let _ = m.ref_mailbox.set_is_subscribed(*new_value);
|
||||
});
|
||||
self.main_loop_handler
|
||||
.send(ThreadEvent::UIEvent(UIEvent::Notification {
|
||||
title: Some(
|
||||
format!(
|
||||
"{}: `{}` has been {}subscribed.",
|
||||
&self.name,
|
||||
self.mailbox_entries[mailbox_hash].name(),
|
||||
if *new_value { "" } else { "un" }
|
||||
)
|
||||
.into(),
|
||||
),
|
||||
source: None,
|
||||
body: "".into(),
|
||||
kind: Some(NotificationType::Info),
|
||||
}));
|
||||
}
|
||||
}}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,565 +0,0 @@
|
|||
//
|
||||
// meli
|
||||
//
|
||||
// Copyright 2024 Emmanouil Pitsidianakis <manos@pitsidianak.is>
|
||||
//
|
||||
// This file is part of meli.
|
||||
//
|
||||
// meli is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// meli is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// SPDX-License-Identifier: EUPL-1.2 OR GPL-3.0-or-later
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use melib::{
|
||||
backends::{prelude::*, Mailbox, MailboxHash},
|
||||
error::Result,
|
||||
maildir::MaildirType,
|
||||
smol, MailboxPermissions, SpecialUsageMailbox,
|
||||
};
|
||||
use tempfile::TempDir;
|
||||
|
||||
use crate::{
|
||||
accounts::{AccountConf, FileMailboxConf, MailboxEntry, MailboxStatus},
|
||||
command::actions::MailboxOperation,
|
||||
utilities::tests::{eprint_step_fn, eprintln_ok_fn},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_mailbox_utf7() {
|
||||
#[derive(Debug)]
|
||||
struct TestMailbox(String);
|
||||
|
||||
impl melib::BackendMailbox for TestMailbox {
|
||||
fn hash(&self) -> MailboxHash {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
|
||||
fn path(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
|
||||
fn children(&self) -> &[MailboxHash] {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn clone(&self) -> Mailbox {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn special_usage(&self) -> SpecialUsageMailbox {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn parent(&self) -> Option<MailboxHash> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn permissions(&self) -> MailboxPermissions {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn is_subscribed(&self) -> bool {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn set_is_subscribed(&mut self, _: bool) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn set_special_usage(&mut self, _: SpecialUsageMailbox) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn count(&self) -> Result<(usize, usize)> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
for (n, d) in [
|
||||
("~peter/mail/&U,BTFw-/&ZeVnLIqe-", "~peter/mail/台北/日本語"),
|
||||
("&BB4EQgQ,BEAEMAQyBDsENQQ9BD0ESwQ1-", "Отправленные"),
|
||||
] {
|
||||
let ref_mbox = TestMailbox(n.to_string());
|
||||
let mut conf: melib::MailboxConf = Default::default();
|
||||
conf.extra.insert("encoding".to_string(), "utf7".into());
|
||||
|
||||
let entry = MailboxEntry::new(
|
||||
MailboxStatus::None,
|
||||
n.to_string(),
|
||||
Box::new(ref_mbox),
|
||||
FileMailboxConf {
|
||||
mailbox_conf: conf,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
assert_eq!(&entry.path, d);
|
||||
}
|
||||
}
|
||||
|
||||
fn new_maildir_backend(
|
||||
temp_dir: &TempDir,
|
||||
acc_name: &str,
|
||||
event_consumer: BackendEventConsumer,
|
||||
with_root_mailbox: bool,
|
||||
) -> Result<(PathBuf, AccountConf, Box<MaildirType>)> {
|
||||
let root_mailbox = temp_dir.path().join("inbox");
|
||||
{
|
||||
std::fs::create_dir(&root_mailbox).expect("Could not create root mailbox directory.");
|
||||
if with_root_mailbox {
|
||||
for d in &["cur", "new", "tmp"] {
|
||||
std::fs::create_dir(root_mailbox.join(d))
|
||||
.expect("Could not create root mailbox directory contents.");
|
||||
}
|
||||
}
|
||||
}
|
||||
let subscribed_mailboxes = if with_root_mailbox {
|
||||
vec!["inbox".into()]
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
let mailboxes = if with_root_mailbox {
|
||||
vec![(
|
||||
"inbox".into(),
|
||||
melib::conf::MailboxConf {
|
||||
extra: indexmap::indexmap! {
|
||||
"path".into() => root_mailbox.display().to_string(),
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
)]
|
||||
.into_iter()
|
||||
.collect()
|
||||
} else {
|
||||
indexmap::indexmap! {}
|
||||
};
|
||||
let extra = if with_root_mailbox {
|
||||
indexmap::indexmap! {
|
||||
"root_mailbox".into() => root_mailbox.display().to_string(),
|
||||
}
|
||||
} else {
|
||||
indexmap::indexmap! {}
|
||||
};
|
||||
|
||||
let account_conf = melib::AccountSettings {
|
||||
name: acc_name.to_string(),
|
||||
root_mailbox: root_mailbox.display().to_string(),
|
||||
format: "maildir".to_string(),
|
||||
identity: "user@localhost".to_string(),
|
||||
extra_identities: vec![],
|
||||
read_only: false,
|
||||
display_name: None,
|
||||
order: Default::default(),
|
||||
subscribed_mailboxes,
|
||||
mailboxes,
|
||||
manual_refresh: true,
|
||||
extra,
|
||||
};
|
||||
|
||||
let maildir = MaildirType::new(&account_conf, Default::default(), event_consumer)?;
|
||||
Ok((root_mailbox, account_conf.into(), maildir))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_accounts_mailbox_by_path_error_msg() {
|
||||
const ACCOUNT_NAME: &str = "test";
|
||||
|
||||
let eprintln_ok = eprintln_ok_fn();
|
||||
let mut eprint_step_closure = eprint_step_fn();
|
||||
macro_rules! eprint_step {
|
||||
($($arg:tt)+) => {{
|
||||
eprint_step_closure(format_args!($($arg)+));
|
||||
}};
|
||||
}
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
{
|
||||
eprint_step!(
|
||||
"Create maildir backend with a root mailbox, \"inbox\" which will be a valid maildir \
|
||||
folder because it will contain cur, new, tmp subdirectories..."
|
||||
);
|
||||
let mut ctx = crate::Context::new_mock(&temp_dir);
|
||||
let backend_event_queue = Arc::new(std::sync::Mutex::new(
|
||||
std::collections::VecDeque::with_capacity(16),
|
||||
));
|
||||
|
||||
let backend_event_consumer = {
|
||||
let backend_event_queue = Arc::clone(&backend_event_queue);
|
||||
|
||||
BackendEventConsumer::new(Arc::new(move |ah, be| {
|
||||
backend_event_queue.lock().unwrap().push_back((ah, be));
|
||||
}))
|
||||
};
|
||||
|
||||
let (root_mailbox, settings, maildir) =
|
||||
new_maildir_backend(&temp_dir, ACCOUNT_NAME, backend_event_consumer, true).unwrap();
|
||||
eprintln_ok();
|
||||
let name = maildir.account_name.to_string();
|
||||
let account_hash = maildir.account_hash;
|
||||
let backend = maildir as Box<dyn MailBackend>;
|
||||
let ref_mailboxes = smol::block_on(backend.mailboxes().unwrap()).unwrap();
|
||||
let contacts = melib::contacts::Contacts::new(name.to_string());
|
||||
|
||||
let mut account = super::Account {
|
||||
hash: account_hash,
|
||||
name: name.into(),
|
||||
is_online: super::IsOnline::True,
|
||||
mailbox_entries: Default::default(),
|
||||
mailboxes_order: Default::default(),
|
||||
tree: Default::default(),
|
||||
contacts,
|
||||
collection: backend.collection(),
|
||||
settings,
|
||||
main_loop_handler: ctx.main_loop_handler.clone(),
|
||||
active_jobs: HashMap::default(),
|
||||
active_job_instants: std::collections::BTreeMap::default(),
|
||||
event_queue: IndexMap::default(),
|
||||
backend_capabilities: backend.capabilities(),
|
||||
backend: Arc::new(std::sync::RwLock::new(backend)),
|
||||
};
|
||||
account.init(ref_mailboxes).unwrap();
|
||||
while let Ok(thread_event) = ctx.receiver.try_recv() {
|
||||
if let crate::ThreadEvent::JobFinished(job_id) = thread_event {
|
||||
if !account.process_event(&job_id) {
|
||||
assert!(
|
||||
ctx.accounts[0].process_event(&job_id),
|
||||
"unclaimed job id: {:?}",
|
||||
job_id
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
eprint_step!("Assert that mailbox_by_path(\"inbox\") returns the root mailbox...");
|
||||
account.mailbox_by_path("inbox").unwrap();
|
||||
eprintln_ok();
|
||||
eprint_step!(
|
||||
"Assert that mailbox_by_path(\"box\") returns an error mentioning the root mailbox..."
|
||||
);
|
||||
assert_eq!(
|
||||
account.mailbox_by_path("box").unwrap_err().to_string(),
|
||||
Error {
|
||||
summary: "Mailbox with that path not found.".into(),
|
||||
details: Some(
|
||||
"Some matching paths that were found: [\"inbox\"]. You can inspect the list \
|
||||
of mailbox paths of an account with the manage-mailboxes command."
|
||||
.into()
|
||||
),
|
||||
source: None,
|
||||
inner: None,
|
||||
related_path: None,
|
||||
kind: ErrorKind::NotFound
|
||||
}
|
||||
.to_string()
|
||||
);
|
||||
eprintln_ok();
|
||||
|
||||
macro_rules! wait_for_job {
|
||||
($job_id:expr) => {{
|
||||
let wait_for = $job_id;
|
||||
while let Ok(thread_event) = ctx.receiver.recv() {
|
||||
if let crate::ThreadEvent::JobFinished(job_id) = thread_event {
|
||||
if !account.process_event(&job_id) {
|
||||
assert!(
|
||||
ctx.accounts[0].process_event(&job_id),
|
||||
"unclaimed job id: {:?}",
|
||||
job_id
|
||||
);
|
||||
} else if job_id == wait_for {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}};
|
||||
}
|
||||
eprint_step!(
|
||||
"Create new mailboxes: \"Sent\", \"Trash\", \"Drafts\", \"Archive\", \"Outbox\", \
|
||||
\"Archive/Archive (old)\"..."
|
||||
);
|
||||
wait_for_job!(account
|
||||
.mailbox_operation(MailboxOperation::Create("Sent".to_string()))
|
||||
.unwrap());
|
||||
wait_for_job!(account
|
||||
.mailbox_operation(MailboxOperation::Create("Trash".to_string()))
|
||||
.unwrap());
|
||||
wait_for_job!(account
|
||||
.mailbox_operation(MailboxOperation::Create("Drafts".to_string()))
|
||||
.unwrap());
|
||||
wait_for_job!(account
|
||||
.mailbox_operation(MailboxOperation::Create("Archive".to_string()))
|
||||
.unwrap());
|
||||
wait_for_job!(account
|
||||
.mailbox_operation(MailboxOperation::Create("Outbox".to_string()))
|
||||
.unwrap());
|
||||
wait_for_job!(account
|
||||
.mailbox_operation(MailboxOperation::Create(
|
||||
"inbox/Archive/Archive (old)".to_string(),
|
||||
))
|
||||
.unwrap());
|
||||
eprintln_ok();
|
||||
eprint_step!(
|
||||
"Assert that mailbox_by_path(\"rchive\") returns an error and mentions matching \
|
||||
archives with mailboxes with the least depth in the tree hierarchy of mailboxes \
|
||||
mentioned first..."
|
||||
);
|
||||
assert_eq!(
|
||||
account.mailbox_by_path("rchive").unwrap_err().to_string(),
|
||||
Error {
|
||||
summary: "Mailbox with that path not found.".into(),
|
||||
details: Some(
|
||||
"Some matching paths that were found: [\"inbox/Archive\", \
|
||||
\"inbox/Archive/Archive (old)\"]. You can inspect the list of mailbox paths \
|
||||
of an account with the manage-mailboxes command."
|
||||
.into()
|
||||
),
|
||||
source: None,
|
||||
inner: None,
|
||||
related_path: None,
|
||||
kind: ErrorKind::NotFound
|
||||
}
|
||||
.to_string()
|
||||
);
|
||||
eprintln_ok();
|
||||
eprint_step!("Create \"inbox/Archive/Archive{{1,2,3,4,5,6,7,8,9,10}}\" mailboxes...");
|
||||
for i in 1..=10 {
|
||||
wait_for_job!(account
|
||||
.mailbox_operation(MailboxOperation::Create(format!(
|
||||
"inbox/Archive/Archive{i}"
|
||||
)))
|
||||
.unwrap());
|
||||
}
|
||||
eprintln_ok();
|
||||
eprint_step!(
|
||||
"Assert that mailbox_by_path(\"inbox/Archive/Archive{{n}}\") works, i.e. we have to \
|
||||
specify the root prefix \"inbox\"..."
|
||||
);
|
||||
for i in 1..=10 {
|
||||
account
|
||||
.mailbox_by_path(&format!("inbox/Archive/Archive{i}"))
|
||||
.unwrap();
|
||||
account
|
||||
.mailbox_by_path(&format!("Archive/Archive{i}"))
|
||||
.unwrap_err();
|
||||
}
|
||||
eprintln_ok();
|
||||
eprint_step!(
|
||||
"Assert that mailbox_by_path(\"rchive\") returns and error and truncates the matching \
|
||||
mailbox paths to 5 maximum..."
|
||||
);
|
||||
assert_eq!(
|
||||
account.mailbox_by_path("rchive").unwrap_err().to_string(),
|
||||
Error {
|
||||
summary: "Mailbox with that path not found.".into(),
|
||||
details: Some(
|
||||
"Some matching paths that were found: [\"inbox/Archive\", \
|
||||
\"inbox/Archive/Archive1\", \"inbox/Archive/Archive2\", \
|
||||
\"inbox/Archive/Archive3\", \"inbox/Archive/Archive4\"] and 7 others. You \
|
||||
can inspect the list of mailbox paths of an account with the \
|
||||
manage-mailboxes command."
|
||||
.into()
|
||||
),
|
||||
source: None,
|
||||
inner: None,
|
||||
related_path: None,
|
||||
kind: ErrorKind::NotFound
|
||||
}
|
||||
.to_string()
|
||||
);
|
||||
eprintln_ok();
|
||||
eprint_step!(
|
||||
"Assert that mailbox_by_path(\"inbox/Archive\") returns a valid result (since the \
|
||||
root mailbox is a valid maildir folder)..."
|
||||
);
|
||||
account.mailbox_by_path("inbox/Archive").unwrap();
|
||||
eprintln_ok();
|
||||
|
||||
eprint_step!("Cleanup maildir account with valid root mailbox...");
|
||||
std::fs::remove_dir_all(root_mailbox).unwrap();
|
||||
eprintln_ok();
|
||||
}
|
||||
|
||||
{
|
||||
eprint_step!(
|
||||
"Create maildir backend with a root mailbox, \"inbox\" which will NOT be a valid \
|
||||
maildir folder because it will NOT contain cur, new, tmp subdirectories..."
|
||||
);
|
||||
let mut ctx = crate::Context::new_mock(&temp_dir);
|
||||
let backend_event_queue = Arc::new(std::sync::Mutex::new(
|
||||
std::collections::VecDeque::with_capacity(16),
|
||||
));
|
||||
|
||||
let backend_event_consumer = {
|
||||
let backend_event_queue = Arc::clone(&backend_event_queue);
|
||||
|
||||
BackendEventConsumer::new(Arc::new(move |ah, be| {
|
||||
backend_event_queue.lock().unwrap().push_back((ah, be));
|
||||
}))
|
||||
};
|
||||
|
||||
let (_root_mailbox, settings, maildir) =
|
||||
new_maildir_backend(&temp_dir, ACCOUNT_NAME, backend_event_consumer, false).unwrap();
|
||||
eprintln_ok();
|
||||
let name = maildir.account_name.to_string();
|
||||
let account_hash = maildir.account_hash;
|
||||
let backend = maildir as Box<dyn MailBackend>;
|
||||
let ref_mailboxes = smol::block_on(backend.mailboxes().unwrap()).unwrap();
|
||||
eprint_step!("Assert that created account has no mailboxes at all...");
|
||||
assert!(
|
||||
ref_mailboxes.is_empty(),
|
||||
"ref_mailboxes were not empty: {:?}",
|
||||
ref_mailboxes
|
||||
);
|
||||
eprintln_ok();
|
||||
let contacts = melib::contacts::Contacts::new(name.to_string());
|
||||
|
||||
let mut account = super::Account {
|
||||
hash: account_hash,
|
||||
name: name.into(),
|
||||
is_online: super::IsOnline::True,
|
||||
mailbox_entries: Default::default(),
|
||||
mailboxes_order: Default::default(),
|
||||
tree: Default::default(),
|
||||
contacts,
|
||||
collection: backend.collection(),
|
||||
settings,
|
||||
main_loop_handler: ctx.main_loop_handler.clone(),
|
||||
active_jobs: HashMap::default(),
|
||||
active_job_instants: std::collections::BTreeMap::default(),
|
||||
event_queue: IndexMap::default(),
|
||||
backend_capabilities: backend.capabilities(),
|
||||
backend: Arc::new(std::sync::RwLock::new(backend)),
|
||||
};
|
||||
account.init(ref_mailboxes).unwrap();
|
||||
while let Ok(thread_event) = ctx.receiver.try_recv() {
|
||||
if let crate::ThreadEvent::JobFinished(job_id) = thread_event {
|
||||
if !account.process_event(&job_id) {
|
||||
assert!(
|
||||
ctx.accounts[0].process_event(&job_id),
|
||||
"unclaimed job id: {:?}",
|
||||
job_id
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
eprint_step!(
|
||||
"Assert that mailbox_by_path(\"inbox\") does not return a valid result (there are no \
|
||||
mailboxes)..."
|
||||
);
|
||||
assert_eq!(
|
||||
account.mailbox_by_path("inbox").unwrap_err().to_string(),
|
||||
Error {
|
||||
summary: "Mailbox with that path not found.".into(),
|
||||
details: Some(
|
||||
"You can inspect the list of mailbox paths of an account with the \
|
||||
manage-mailboxes command."
|
||||
.into()
|
||||
),
|
||||
source: None,
|
||||
inner: None,
|
||||
related_path: None,
|
||||
kind: ErrorKind::NotFound
|
||||
}
|
||||
.to_string()
|
||||
);
|
||||
eprintln_ok();
|
||||
eprint_step!(
|
||||
"Create multiple maildir folders \"inbox/Archive{{1,2,3,4,5,6,7,8,9,10}}\"..."
|
||||
);
|
||||
macro_rules! wait_for_job {
|
||||
($job_id:expr) => {{
|
||||
let wait_for = $job_id;
|
||||
while let Ok(thread_event) = ctx.receiver.recv() {
|
||||
if let crate::ThreadEvent::JobFinished(job_id) = thread_event {
|
||||
if !account.process_event(&job_id) {
|
||||
assert!(
|
||||
ctx.accounts[0].process_event(&job_id),
|
||||
"unclaimed job id: {:?}",
|
||||
job_id
|
||||
);
|
||||
} else if job_id == wait_for {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}};
|
||||
}
|
||||
for i in 1..=10 {
|
||||
wait_for_job!(account
|
||||
.mailbox_operation(MailboxOperation::Create(format!("inbox/Archive{i}")))
|
||||
.unwrap());
|
||||
}
|
||||
eprintln_ok();
|
||||
eprint_step!(
|
||||
"Assert that mailbox_by_path(\"Archive{{n}}\") works, and that we don't have to \
|
||||
specify the root prefix \"inbox\"..."
|
||||
);
|
||||
for i in 1..=10 {
|
||||
account.mailbox_by_path(&format!("Archive{i}")).unwrap();
|
||||
}
|
||||
eprintln_ok();
|
||||
eprint_step!(
|
||||
"Assert that mailbox_by_path(\"rchive\") returns an error message with matches..."
|
||||
);
|
||||
assert_eq!(
|
||||
account.mailbox_by_path("rchive").unwrap_err().to_string(),
|
||||
Error {
|
||||
summary: "Mailbox with that path not found.".into(),
|
||||
details: Some(
|
||||
"Some matching paths that were found: [\"Archive1\", \"Archive2\", \
|
||||
\"Archive3\", \"Archive4\", \"Archive5\"] and 5 others. You can inspect the \
|
||||
list of mailbox paths of an account with the manage-mailboxes command."
|
||||
.into()
|
||||
),
|
||||
source: None,
|
||||
inner: None,
|
||||
related_path: None,
|
||||
kind: ErrorKind::NotFound
|
||||
}
|
||||
.to_string()
|
||||
);
|
||||
eprintln_ok();
|
||||
eprint_step!(
|
||||
"Assert that mailbox_by_path(\"inbox/Archive{{n}}\") does not return a valid result..."
|
||||
);
|
||||
assert_eq!(
|
||||
account
|
||||
.mailbox_by_path("inbox/Archive1")
|
||||
.unwrap_err()
|
||||
.to_string(),
|
||||
Error {
|
||||
summary: "Mailbox with that path not found.".into(),
|
||||
details: Some(
|
||||
"You can inspect the list of mailbox paths of an account with the \
|
||||
manage-mailboxes command."
|
||||
.into()
|
||||
),
|
||||
source: None,
|
||||
inner: None,
|
||||
related_path: None,
|
||||
kind: ErrorKind::NotFound
|
||||
}
|
||||
.to_string()
|
||||
);
|
||||
eprintln_ok();
|
||||
}
|
||||
}
|
269
meli/src/args.rs
|
@ -1,269 +0,0 @@
|
|||
/*
|
||||
* meli - args.rs
|
||||
*
|
||||
* Copyright 2017-2023 Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//! Command line arguments.
|
||||
|
||||
use std::{ffi::OsStr, os::unix::ffi::OsStrExt};
|
||||
|
||||
use super::*;
|
||||
#[cfg(feature = "cli-docs")]
|
||||
use crate::manpages;
|
||||
|
||||
fn try_path_or_stdio(input: &OsStr) -> PathOrStdio {
|
||||
if input.as_bytes() == b"-" {
|
||||
PathOrStdio::Stdio
|
||||
} else {
|
||||
PathOrStdio::Path(PathBuf::from(input))
|
||||
}
|
||||
}
|
||||
|
||||
/// `Pathbuf` or standard stream (`-` operand).
|
||||
#[derive(Debug)]
|
||||
pub enum PathOrStdio {
|
||||
/// Path
|
||||
Path(PathBuf),
|
||||
/// standard stream (`-` operand)
|
||||
Stdio,
|
||||
}
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
#[structopt(name = "meli", about = "terminal mail client", version_short = "v")]
|
||||
pub struct Opt {
|
||||
/// use specified configuration file
|
||||
#[structopt(short, long, parse(from_os_str))]
|
||||
pub config: Option<PathBuf>,
|
||||
|
||||
#[structopt(subcommand)]
|
||||
pub subcommand: Option<SubCommand>,
|
||||
}
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
pub enum SubCommand {
|
||||
/// print default theme in full to stdout and exit.
|
||||
PrintDefaultTheme,
|
||||
/// print loaded themes in full to stdout and exit.
|
||||
PrintLoadedThemes,
|
||||
/// print all directories that meli creates/uses.
|
||||
PrintAppDirectories,
|
||||
/// print location of configuration file that will be loaded on normal app
|
||||
/// startup.
|
||||
PrintConfigPath,
|
||||
/// edit configuration files with `$EDITOR`/`$VISUAL`.
|
||||
EditConfig,
|
||||
/// create a sample configuration file with available configuration options.
|
||||
/// If `PATH` is not specified, meli will try to create it in
|
||||
/// `$XDG_CONFIG_HOME/meli/config.toml`. Path `-` will output to standard
|
||||
/// output instead.
|
||||
#[structopt(display_order = 1)]
|
||||
CreateConfig {
|
||||
#[structopt(value_name = "NEW_CONFIG_PATH", parse(from_os_str = try_path_or_stdio))]
|
||||
path: Option<PathOrStdio>,
|
||||
},
|
||||
/// test a configuration file for syntax issues or missing options.
|
||||
/// If `PATH` is not specified, meli will try to read it from
|
||||
/// `$XDG_CONFIG_HOME/meli/config.toml`. Path `-` will read input from
|
||||
/// standard input instead.
|
||||
#[structopt(display_order = 2)]
|
||||
TestConfig {
|
||||
#[structopt(value_name = "CONFIG_PATH", parse(from_os_str = try_path_or_stdio))]
|
||||
path: Option<PathOrStdio>,
|
||||
},
|
||||
#[structopt(display_order = 3)]
|
||||
/// Testing tools such as IMAP, SMTP shells for debugging.
|
||||
Tools(ToolOpt),
|
||||
#[structopt(visible_alias="docs", aliases=&["docs", "manpage", "manpages"])]
|
||||
#[structopt(display_order = 4)]
|
||||
/// print documentation page and exit (Piping to a pager is recommended.).
|
||||
Man(ManOpt),
|
||||
#[structopt(display_order = 5)]
|
||||
/// Install manual pages to the first location provided by `$MANPATH` /
|
||||
/// `manpath(1)`, unless you specify the directory as an argument.
|
||||
InstallMan {
|
||||
#[structopt(value_name = "DESTINATION_PATH", parse(from_os_str))]
|
||||
destination_path: Option<PathBuf>,
|
||||
},
|
||||
#[structopt(display_order = 6)]
|
||||
/// Print compile time feature flags of this binary
|
||||
CompiledWith,
|
||||
/// Print log file location.
|
||||
PrintLogPath,
|
||||
/// View mail from input file.
|
||||
View {
|
||||
#[structopt(value_name = "INPUT", parse(from_os_str))]
|
||||
path: PathBuf,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
pub struct ManOpt {
|
||||
/// If set, output text in stdout instead of spawning `$PAGER`.
|
||||
#[cfg(feature = "cli-docs")]
|
||||
#[cfg_attr(feature = "cli-docs", structopt(long = "no-raw", alias = "no-raw"))]
|
||||
pub no_raw: bool,
|
||||
/// If set, output compressed gzip manpage in binary form in stdout.
|
||||
#[cfg(feature = "cli-docs")]
|
||||
#[cfg_attr(feature = "cli-docs", structopt(long = "gzipped"))]
|
||||
pub gzipped: bool,
|
||||
#[cfg(feature = "cli-docs")]
|
||||
#[cfg_attr(feature = "cli-docs", structopt(default_value = "meli", possible_values=manpages::POSSIBLE_VALUES, value_name="PAGE", parse(try_from_str = manpages::parse_manpage)))]
|
||||
/// Name of manual page.
|
||||
pub page: manpages::ManPages,
|
||||
}
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
pub enum ToolOpt {
|
||||
ImapShell {
|
||||
#[structopt(value_name = "CONFIG_TOML_ACCOUNT_NAME")]
|
||||
account: String,
|
||||
},
|
||||
#[cfg(feature = "smtp")]
|
||||
SmtpShell {
|
||||
#[structopt(value_name = "CONFIG_TOML_ACCOUNT_NAME")]
|
||||
account: String,
|
||||
},
|
||||
#[cfg(feature = "jmap")]
|
||||
JmapShell {
|
||||
#[structopt(value_name = "CONFIG_TOML_ACCOUNT_NAME")]
|
||||
account: String,
|
||||
},
|
||||
}
|
||||
|
||||
fn print_path(path: &std::path::Path) {
|
||||
if let Some(hostname) = nix::unistd::gethostname()
|
||||
.ok()
|
||||
.and_then(|s| s.into_string().ok())
|
||||
{
|
||||
println!(
|
||||
"{}",
|
||||
Hyperlink::new(
|
||||
&path.display(),
|
||||
&format_args!("file://{hostname}{}", path.display())
|
||||
)
|
||||
);
|
||||
} else {
|
||||
println!("{}", path.display());
|
||||
}
|
||||
}
|
||||
|
||||
impl Opt {
|
||||
/// Execute `self.subcommand` if any, and return its result. Otherwise
|
||||
/// return `None`.
|
||||
pub fn execute(self) -> Option<Result<()>> {
|
||||
macro_rules! ret_err {
|
||||
($sth:expr) => {
|
||||
match $sth {
|
||||
Ok(v) => v,
|
||||
Err(err) => return Some(Err(err.into())),
|
||||
}
|
||||
};
|
||||
}
|
||||
Some(match self.subcommand? {
|
||||
SubCommand::View { .. } => { return None ; }
|
||||
SubCommand::TestConfig { path } => {
|
||||
subcommands::test_config(path)
|
||||
}
|
||||
SubCommand::Tools(toolopt) => {
|
||||
subcommands::tool(self.config, toolopt)
|
||||
}
|
||||
SubCommand::CreateConfig { path } => {
|
||||
subcommands::create_config(path)
|
||||
}
|
||||
SubCommand::EditConfig => {
|
||||
subcommands::edit_config()
|
||||
}
|
||||
SubCommand::PrintConfigPath => {
|
||||
let config_path = ret_err!(crate::conf::get_config_file());
|
||||
print_path(&config_path);
|
||||
Ok(())
|
||||
}
|
||||
#[cfg(not(feature = "cli-docs"))]
|
||||
SubCommand::Man(ManOpt {}) => {
|
||||
Err(Error::new("error: this version of meli was not build with embedded documentation (cargo feature `cli-docs`). You might have it installed as manpages (eg `man meli`), otherwise check https://meli-email.org"))
|
||||
}
|
||||
#[cfg(feature = "cli-docs")]
|
||||
SubCommand::Man(ManOpt {
|
||||
page,
|
||||
no_raw,
|
||||
gzipped: true,
|
||||
}) => {
|
||||
use std::io::Write;
|
||||
|
||||
ret_err!(std::io::stdout().write_all(if no_raw {
|
||||
page.text_gz()
|
||||
} else {
|
||||
page.mdoc_gz()
|
||||
}));
|
||||
Ok(())
|
||||
}
|
||||
#[cfg(feature = "cli-docs")]
|
||||
SubCommand::Man(ManOpt {
|
||||
page,
|
||||
no_raw,
|
||||
gzipped: false,
|
||||
}) => {
|
||||
subcommands::man(page, false).and_then(|s| subcommands::pager(s, no_raw))
|
||||
}
|
||||
SubCommand::CompiledWith => {
|
||||
subcommands::compiled_with()
|
||||
}
|
||||
SubCommand::PrintLoadedThemes => {
|
||||
let s = ret_err!(conf::FileSettings::new());
|
||||
print!("{}", s.terminal.themes);
|
||||
Ok(())
|
||||
}
|
||||
SubCommand::PrintDefaultTheme => {
|
||||
print!("{}", conf::Themes::default().key_to_string("dark", false));
|
||||
Ok(())
|
||||
}
|
||||
SubCommand::PrintAppDirectories => {
|
||||
print_path(&xdg::BaseDirectories::with_prefix("meli")
|
||||
.expect(
|
||||
"Could not find your XDG directories. If this is unexpected, please \
|
||||
report it as a bug."
|
||||
)
|
||||
.get_data_file(""));
|
||||
let mut temp_dir = std::env::temp_dir();
|
||||
temp_dir.push("meli");
|
||||
print_path(&temp_dir);
|
||||
Ok(())
|
||||
}
|
||||
#[cfg(not(feature = "cli-docs"))]
|
||||
SubCommand::InstallMan {
|
||||
destination_path: _,
|
||||
} => {
|
||||
Err(Error::new("error: this version of meli was not build with embedded documentation (cargo feature `cli-docs`). You might have it installed as manpages (eg `man meli`), otherwise check https://meli-email.org"))
|
||||
}
|
||||
#[cfg(feature = "cli-docs")]
|
||||
SubCommand::InstallMan { destination_path } => {
|
||||
match crate::manpages::ManPages::install(destination_path) {
|
||||
Ok(p) => println!("Installed at {}.", p.display()),
|
||||
Err(err) => return Some(Err(err)),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
SubCommand::PrintLogPath => {
|
||||
let settings = ret_err!(crate::conf::Settings::new());
|
||||
print_path(&settings._logger.log_dest());
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,565 +0,0 @@
|
|||
/*
|
||||
* meli
|
||||
*
|
||||
* Copyright 2017-2018 Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//! A parser module for user commands passed through
|
||||
//! [`Command`](crate::types::UIMode::Command) mode.
|
||||
|
||||
use std::{borrow::Cow, collections::HashSet, str::FromStr};
|
||||
|
||||
use melib::{
|
||||
nom::{
|
||||
self,
|
||||
branch::alt,
|
||||
bytes::complete::{is_a, is_not, tag, take_until},
|
||||
character::complete::{digit1, not_line_ending},
|
||||
combinator::{map, map_res},
|
||||
error::Error as NomError,
|
||||
multi::separated_list1,
|
||||
sequence::{pair, preceded, separated_pair},
|
||||
IResult,
|
||||
},
|
||||
parser::BytesExt,
|
||||
SortField, SortOrder,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub mod actions;
|
||||
#[macro_use]
|
||||
pub mod error;
|
||||
#[macro_use]
|
||||
pub mod argcheck;
|
||||
pub mod history;
|
||||
pub mod parser;
|
||||
use actions::MailboxOperation;
|
||||
use error::CommandError;
|
||||
pub use parser::parse_command;
|
||||
|
||||
pub use crate::actions::{
|
||||
AccountAction::{self, *},
|
||||
Action::{self, *},
|
||||
ComposeAction::{self, *},
|
||||
ComposerTabAction, FlagAction,
|
||||
ListingAction::{self, *},
|
||||
MailingListAction::{self, *},
|
||||
TabAction::{self, *},
|
||||
TagAction,
|
||||
ViewAction::{self, *},
|
||||
};
|
||||
|
||||
/// Helper macro to convert an array of tokens into a `TokenStream`
|
||||
macro_rules! to_stream {
|
||||
($token: expr) => {
|
||||
TokenStream {
|
||||
tokens: &[$token],
|
||||
}
|
||||
};
|
||||
($($tokens:expr),*) => {
|
||||
TokenStream {
|
||||
tokens: &[$($tokens),*],
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Macro to create a const table with every command part that can be
|
||||
/// auto-completed and its description
|
||||
macro_rules! define_commands {
|
||||
( [$({ tags: [$( $tags:literal),*], desc: $desc:literal, tokens: $tokens:expr, parser: $parser:path}),*]) => {
|
||||
pub const COMMAND_COMPLETION: &[(&str, &str, TokenStream, fn(&[u8]) -> IResult<&[u8], Result<Action, CommandError>>)] = &[$($( ($tags, $desc, TokenStream { tokens: $tokens }, $parser) ),*),* ];
|
||||
};
|
||||
}
|
||||
|
||||
pub fn quoted_argument(input: &[u8]) -> IResult<&[u8], &str> {
|
||||
if input.is_empty() {
|
||||
return Err(nom::Err::Error(NomError {
|
||||
input,
|
||||
code: nom::error::ErrorKind::Tag,
|
||||
}));
|
||||
}
|
||||
|
||||
if input[0] == b'"' {
|
||||
let mut i = 1;
|
||||
while i < input.len() {
|
||||
if input[i] == b'\"' && input[i - 1] != b'\\' {
|
||||
return Ok((&input[i + 1..], unsafe {
|
||||
std::str::from_utf8_unchecked(&input[1..i])
|
||||
}));
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
Err(nom::Err::Error(NomError {
|
||||
input,
|
||||
code: nom::error::ErrorKind::Tag,
|
||||
}))
|
||||
} else {
|
||||
map_res(is_not(" "), std::str::from_utf8)(input)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct TokenStream {
|
||||
tokens: &'static [TokenAdicity],
|
||||
}
|
||||
|
||||
use Token::*;
|
||||
use TokenAdicity::*;
|
||||
|
||||
impl TokenStream {
|
||||
fn matches<'s>(&self, s: &mut &'s str, sugg: &mut HashSet<String>) -> Vec<(&'s str, Token)> {
|
||||
let mut tokens = vec![];
|
||||
for t in self.tokens.iter() {
|
||||
let mut ptr = 0;
|
||||
while ptr + 1 < s.len() && s.as_bytes()[ptr].is_ascii_whitespace() {
|
||||
ptr += 1;
|
||||
}
|
||||
*s = &s[ptr..];
|
||||
//println!("\t before s.is_empty() {:?} {:?}", t, s);
|
||||
if s.is_empty() || *s == " " {
|
||||
match t.inner() {
|
||||
Literal(lit) => {
|
||||
sugg.insert(format!("{}{}", if s.is_empty() { " " } else { "" }, lit));
|
||||
}
|
||||
Alternatives(v) => {
|
||||
for t in v.iter() {
|
||||
//println!("adding empty suggestions for {:?}", t);
|
||||
let mut _s = *s;
|
||||
let mut m = t.matches(&mut _s, sugg);
|
||||
tokens.append(&mut m);
|
||||
}
|
||||
}
|
||||
AlternativeStrings(v) => {
|
||||
for t in v.iter() {
|
||||
sugg.insert(format!("{}{}", if s.is_empty() { " " } else { "" }, t));
|
||||
}
|
||||
}
|
||||
Seq(_s) => {}
|
||||
RestOfStringValue => {
|
||||
sugg.insert(String::new());
|
||||
}
|
||||
t @ AttachmentIndexValue
|
||||
| t @ MailboxIndexValue
|
||||
| t @ IndexValue
|
||||
| t @ Filepath
|
||||
| t @ AccountName
|
||||
| t @ MailboxPath
|
||||
| t @ QuotedStringValue
|
||||
| t @ AlphanumericStringValue => {
|
||||
let _t = t;
|
||||
//sugg.insert(format!("{}{:?}", if s.is_empty() { " " }
|
||||
// else { "" }, t));
|
||||
}
|
||||
}
|
||||
tokens.push((*s, *t.inner()));
|
||||
return tokens;
|
||||
}
|
||||
match t.inner() {
|
||||
Literal(lit) => {
|
||||
if lit.starts_with(*s) && lit.len() != s.len() {
|
||||
sugg.insert(lit[s.len()..].to_string());
|
||||
tokens.push((s, *t.inner()));
|
||||
return tokens;
|
||||
} else if s.starts_with(lit) {
|
||||
tokens.push((&s[..lit.len()], *t.inner()));
|
||||
*s = &s[lit.len()..];
|
||||
} else {
|
||||
return vec![];
|
||||
}
|
||||
}
|
||||
Alternatives(v) => {
|
||||
let mut cont = true;
|
||||
for t in v.iter() {
|
||||
let mut _s = *s;
|
||||
let mut m = t.matches(&mut _s, sugg);
|
||||
if !m.is_empty() {
|
||||
tokens.append(&mut m);
|
||||
//println!("_s is empty {}", _s.is_empty());
|
||||
cont = !_s.is_empty();
|
||||
*s = _s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if tokens.is_empty() {
|
||||
return tokens;
|
||||
}
|
||||
if !cont {
|
||||
*s = "";
|
||||
}
|
||||
}
|
||||
AlternativeStrings(v) => {
|
||||
for lit in v.iter() {
|
||||
if lit.starts_with(*s) && lit.len() != s.len() {
|
||||
sugg.insert(lit[s.len()..].to_string());
|
||||
tokens.push((s, *t.inner()));
|
||||
return tokens;
|
||||
} else if s.starts_with(lit) {
|
||||
tokens.push((&s[..lit.len()], *t.inner()));
|
||||
*s = &s[lit.len()..];
|
||||
} else {
|
||||
return vec![];
|
||||
}
|
||||
}
|
||||
}
|
||||
Seq(_s) => {
|
||||
return vec![];
|
||||
}
|
||||
RestOfStringValue => {
|
||||
tokens.push((*s, *t.inner()));
|
||||
return tokens;
|
||||
}
|
||||
AttachmentIndexValue
|
||||
| MailboxIndexValue
|
||||
| IndexValue
|
||||
| Filepath
|
||||
| AccountName
|
||||
| MailboxPath
|
||||
| QuotedStringValue
|
||||
| AlphanumericStringValue => {
|
||||
let mut ptr = 0;
|
||||
while ptr + 1 < s.len() && !s.as_bytes()[ptr].is_ascii_whitespace() {
|
||||
ptr += 1;
|
||||
}
|
||||
tokens.push((&s[..ptr + 1], *t.inner()));
|
||||
*s = &s[ptr + 1..];
|
||||
}
|
||||
}
|
||||
}
|
||||
tokens
|
||||
}
|
||||
}
|
||||
|
||||
/// `Token` wrapper that defines how many times a token is expected to be
|
||||
/// repeated
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum TokenAdicity {
|
||||
ZeroOrOne(Token),
|
||||
ZeroOrMore(Token),
|
||||
One(Token),
|
||||
OneOrMore(Token),
|
||||
}
|
||||
|
||||
impl TokenAdicity {
|
||||
fn inner(&self) -> &Token {
|
||||
match self {
|
||||
ZeroOrOne(ref t) => t,
|
||||
ZeroOrMore(ref t) => t,
|
||||
One(ref t) => t,
|
||||
OneOrMore(ref t) => t,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A token encountered in the UI's command execution bar
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum Token {
|
||||
Literal(&'static str),
|
||||
Filepath,
|
||||
Alternatives(&'static [TokenStream]),
|
||||
AlternativeStrings(&'static [&'static str]),
|
||||
Seq(&'static [TokenAdicity]),
|
||||
AccountName,
|
||||
MailboxPath,
|
||||
QuotedStringValue,
|
||||
RestOfStringValue,
|
||||
AlphanumericStringValue,
|
||||
AttachmentIndexValue,
|
||||
MailboxIndexValue,
|
||||
IndexValue,
|
||||
}
|
||||
|
||||
fn eof(input: &[u8]) -> IResult<&[u8], ()> {
|
||||
if input.is_empty() {
|
||||
Ok((input, ()))
|
||||
} else {
|
||||
Err(nom::Err::Error(NomError {
|
||||
input,
|
||||
code: nom::error::ErrorKind::Tag,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
define_commands!([
|
||||
{ tags: ["set", "set seen", "set unseen", "set plain", "set threaded", "set compact"],
|
||||
desc: "set [seen/unseen], toggles message's Seen flag. set [plain/threaded/compact/conversations] changes the mail listing view",
|
||||
tokens: &[One(Literal("set")),
|
||||
One(
|
||||
Alternatives(&[
|
||||
to_stream!(One(Literal("seen"))),
|
||||
to_stream!(One(Literal("unseen"))),
|
||||
to_stream!(One(Literal("plain"))),
|
||||
to_stream!(One(Literal("threaded"))),
|
||||
to_stream!(One(Literal("compact"))),
|
||||
to_stream!(One(Literal("conversations")))
|
||||
])
|
||||
)
|
||||
],
|
||||
parser: parser::set
|
||||
},
|
||||
{ tags: ["delete"],
|
||||
desc: "delete message",
|
||||
tokens: &[One(Literal("delete"))],
|
||||
parser: parser::delete_message
|
||||
},
|
||||
{ tags: ["copyto", "moveto"],
|
||||
desc: "copy/move message",
|
||||
tokens: &[One(Alternatives(&[to_stream!(One(Literal("copyto"))), to_stream!(One(Literal("moveto")))])), ZeroOrOne(AccountName), One(MailboxPath)],
|
||||
parser: parser::copymove
|
||||
},
|
||||
{ tags: ["import "],
|
||||
desc: "import FILESYSTEM_PATH MAILBOX_PATH",
|
||||
tokens: &[One(Literal("import")), One(Filepath), One(MailboxPath)],
|
||||
parser: parser::import
|
||||
},
|
||||
{ tags: ["close"],
|
||||
desc: "close non-sticky tabs",
|
||||
tokens: &[One(Literal("close"))],
|
||||
parser: parser::close
|
||||
},
|
||||
{ tags: ["go"],
|
||||
desc: "go <n>, switch to nth mailbox in this account",
|
||||
tokens: &[One(Literal("goto")), One(MailboxIndexValue)],
|
||||
parser: parser::goto
|
||||
},
|
||||
{ tags: ["subsort"],
|
||||
desc: "subsort [date/subject] [asc/desc], sorts first level replies in threads.",
|
||||
tokens: &[One(Literal("subsort")), One(Alternatives(&[to_stream!(One(Literal("date"))), to_stream!(One(Literal("subject")))])), One(Alternatives(&[to_stream!(One(Literal("asc"))), to_stream!(One(Literal("desc")))])) ],
|
||||
parser: parser::subsort
|
||||
},
|
||||
{ tags: ["sort"],
|
||||
desc: "sort [date/subject] [asc/desc], sorts threads.",
|
||||
tokens: &[One(Literal("sort")), One(Alternatives(&[to_stream!(One(Literal("date"))), to_stream!(One(Literal("subject")))])), One(Alternatives(&[to_stream!(One(Literal("asc"))), to_stream!(One(Literal("desc")))])) ],
|
||||
parser: parser::sort
|
||||
},
|
||||
{ tags: ["sort"],
|
||||
desc: "sort <column index> [asc/desc], sorts table columns.",
|
||||
tokens: &[One(Literal("sort")), One(IndexValue), ZeroOrOne(Alternatives(&[to_stream!(One(Literal("asc"))), to_stream!(One(Literal("desc")))])) ],
|
||||
parser: parser::sort_column
|
||||
},
|
||||
{ tags: ["toggle thread_snooze"],
|
||||
desc: "turn off new notifications for this thread",
|
||||
tokens: &[One(Literal("toggle")), One(Literal("thread_snooze"))],
|
||||
parser: parser::toggle
|
||||
},
|
||||
{ tags: ["search"],
|
||||
desc: "search <TERM>, searches list with given term",
|
||||
tokens: &[One(Literal("search")), One(RestOfStringValue)],
|
||||
parser: parser::search
|
||||
},
|
||||
{ tags: ["clear-selection"],
|
||||
desc: "clear-selection",
|
||||
tokens: &[One(Literal("clear-selection"))],
|
||||
parser: parser::select
|
||||
},
|
||||
{ tags: ["select"],
|
||||
desc: "select <TERM>, selects envelopes matching with given term",
|
||||
tokens: &[One(Literal("select")), One(RestOfStringValue)],
|
||||
parser: parser::select
|
||||
},
|
||||
{ tags: ["export-mbox "],
|
||||
desc: "export-mbox PATH",
|
||||
tokens: &[One(Literal("export-mbox")), One(Filepath)],
|
||||
parser: parser::export_mbox
|
||||
},
|
||||
{ tags: ["list-archive", "list-post", "list-unsubscribe", "list-"],
|
||||
desc: "list-[unsubscribe/post/archive]",
|
||||
tokens: &[One(Alternatives(&[to_stream!(One(Literal("list-archive"))), to_stream!(One(Literal("list-post"))), to_stream!(One(Literal("list-unsubscribe")))]))],
|
||||
parser: parser::mailinglist
|
||||
},
|
||||
{ tags: ["setenv "],
|
||||
desc: "setenv VAR=VALUE",
|
||||
tokens: &[One(Literal("setenv")), OneOrMore(Seq(&[One(AlphanumericStringValue), One(Literal("=")), One(QuotedStringValue)]))],
|
||||
parser: parser::setenv
|
||||
},
|
||||
{ tags: ["printenv "],
|
||||
desc: "printenv VAR",
|
||||
tokens: &[],
|
||||
parser: parser::printenv
|
||||
},
|
||||
{ tags: ["mailto "],
|
||||
desc: "mailto MAILTO_ADDRESS",
|
||||
tokens: &[One(Literal("mailto")), One(QuotedStringValue)],
|
||||
parser: parser::mailto
|
||||
},
|
||||
/* Pipe pager contents to binary */
|
||||
{ tags: ["pipe "],
|
||||
desc: "pipe EXECUTABLE ARGS",
|
||||
tokens: &[One(Literal("pipe")), One(Filepath), ZeroOrMore(QuotedStringValue)],
|
||||
parser: parser::pipe
|
||||
},
|
||||
/* Filter pager contents through binary */
|
||||
{ tags: ["filter "],
|
||||
desc: "filter EXECUTABLE ARGS",
|
||||
tokens: &[One(Literal("filter")), One(Filepath), ZeroOrMore(QuotedStringValue)],
|
||||
parser: parser::filter
|
||||
},
|
||||
{ tags: ["add-attachment ", "add-attachment-file-picker "],
|
||||
desc: "add-attachment PATH",
|
||||
tokens: &[One(
|
||||
Alternatives(&[to_stream!(One(Literal("add-attachment")), One(Filepath)), to_stream!(One(Literal("add-attachment-file-picker")))]))],
|
||||
parser: parser::add_attachment
|
||||
},
|
||||
{ tags: ["remove-attachment "],
|
||||
desc: "remove-attachment INDEX",
|
||||
tokens: &[One(Literal("remove-attachment")), One(IndexValue)],
|
||||
parser: parser::remove_attachment
|
||||
},
|
||||
{ tags: ["save-draft"],
|
||||
desc: "save draft",
|
||||
tokens: &[One(Literal("save-draft"))],
|
||||
parser: parser::save_draft
|
||||
},
|
||||
{ tags: ["discard-draft"],
|
||||
desc: "discard draft",
|
||||
tokens: &[One(Literal("discard-draft"))],
|
||||
parser: parser::discard_draft
|
||||
},
|
||||
{ tags: ["toggle sign "],
|
||||
desc: "switch between sign/unsign for this draft",
|
||||
tokens: &[One(Literal("toggle")), One(Literal("sign"))],
|
||||
parser: parser::toggle
|
||||
},
|
||||
{ tags: ["toggle encrypt"],
|
||||
desc: "toggle encryption for this draft",
|
||||
tokens: &[One(Literal("toggle")), One(Literal("encrypt"))],
|
||||
parser: parser::toggle
|
||||
},
|
||||
{ tags: ["create-mailbox "],
|
||||
desc: "create-mailbox ACCOUNT MAILBOX_PATH",
|
||||
tokens: &[One(Literal("create-mailbox")), One(AccountName), One(MailboxPath)],
|
||||
parser: parser::create_mailbox
|
||||
},
|
||||
{ tags: ["subscribe-mailbox "],
|
||||
desc: "subscribe-mailbox ACCOUNT MAILBOX_PATH",
|
||||
tokens: &[One(Literal("subscribe-mailbox")), One(AccountName), One(MailboxPath)],
|
||||
parser: parser::sub_mailbox
|
||||
},
|
||||
{ tags: ["unsubscribe-mailbox "],
|
||||
desc: "unsubscribe-mailbox ACCOUNT MAILBOX_PATH",
|
||||
tokens: &[One(Literal("unsubscribe-mailbox")), One(AccountName), One(MailboxPath)],
|
||||
parser: parser::unsub_mailbox
|
||||
},
|
||||
{ tags: ["rename-mailbox "],
|
||||
desc: "rename-mailbox ACCOUNT MAILBOX_PATH_SRC MAILBOX_PATH_DEST",
|
||||
tokens: &[One(Literal("rename-mailbox")), One(AccountName), One(MailboxPath), One(MailboxPath)],
|
||||
parser: parser::rename_mailbox
|
||||
},
|
||||
{ tags: ["delete-mailbox "],
|
||||
desc: "delete-mailbox ACCOUNT MAILBOX_PATH",
|
||||
tokens: &[One(Literal("delete-mailbox")), One(AccountName), One(MailboxPath)],
|
||||
parser: parser::delete_mailbox
|
||||
},
|
||||
{ tags: ["reindex "],
|
||||
desc: "reindex ACCOUNT, rebuild account cache in the background",
|
||||
tokens: &[One(Literal("reindex")), One(AccountName)],
|
||||
parser: parser::reindex
|
||||
},
|
||||
{ tags: ["open-in-tab"],
|
||||
desc: "opens envelope view in new tab",
|
||||
tokens: &[One(Literal("open-in-tab"))],
|
||||
parser: parser::open_in_new_tab
|
||||
},
|
||||
{ tags: ["save-attachment "],
|
||||
desc: "save-attachment INDEX PATH",
|
||||
tokens: &[One(Literal("save-attachment")), One(AttachmentIndexValue), One(Filepath)],
|
||||
parser: parser::save_attachment
|
||||
},
|
||||
{ tags: ["export-mail "],
|
||||
desc: "export-mail PATH",
|
||||
tokens: &[One(Literal("export-mail")), One(Filepath)],
|
||||
parser: parser::export_mail
|
||||
},
|
||||
{ tags: ["add-addresses-to-contacts "],
|
||||
desc: "add-addresses-to-contacts",
|
||||
tokens: &[One(Literal("add-addresses-to-contacts"))],
|
||||
parser: parser::add_addresses_to_contacts
|
||||
},
|
||||
{ tags: ["tag", "tag add", "tag remove"],
|
||||
desc: "tag [add/remove], edits message's tags.",
|
||||
tokens: &[One(Literal("tag")), One(Alternatives(&[to_stream!(One(Literal("add"))), to_stream!(One(Literal("remove")))]))],
|
||||
parser: parser::_tag
|
||||
},
|
||||
{ tags: ["print "],
|
||||
desc: "print ACCOUNT SETTING",
|
||||
tokens: &[One(Literal("print")), One(AccountName), One(QuotedStringValue)],
|
||||
parser: parser::print_account_setting
|
||||
},
|
||||
{ tags: ["print "],
|
||||
desc: "print SETTING",
|
||||
tokens: &[One(Literal("print")), One(QuotedStringValue)],
|
||||
parser: parser::print_setting
|
||||
},
|
||||
{ tags: ["toggle mouse"],
|
||||
desc: "toggle mouse support",
|
||||
tokens: &[One(Literal("toggle")), One(Literal("mouse"))],
|
||||
parser: parser::toggle
|
||||
},
|
||||
{ tags: ["manage-mailboxes"],
|
||||
desc: "view and manage mailbox preferences",
|
||||
tokens: &[One(Literal("manage-mailboxes"))],
|
||||
parser: parser::manage_mailboxes
|
||||
},
|
||||
{ tags: ["man"],
|
||||
desc: "read documentation",
|
||||
tokens: {
|
||||
#[cfg(feature = "cli-docs")]
|
||||
{
|
||||
&[One(Literal("man")), One(AlternativeStrings(crate::manpages::POSSIBLE_VALUES))]
|
||||
}
|
||||
#[cfg(not(feature = "cli-docs"))]
|
||||
{ &[] }
|
||||
},
|
||||
parser: parser::view_manpage
|
||||
},
|
||||
{ tags: ["manage-jobs"],
|
||||
desc: "view and manage jobs",
|
||||
tokens: &[One(Literal("manage-jobs"))],
|
||||
parser: parser::manage_jobs
|
||||
},
|
||||
{ tags: ["quit"],
|
||||
desc: "quit meli",
|
||||
tokens: &[One(Literal("quit"))],
|
||||
parser: parser::quit
|
||||
},
|
||||
{ tags: ["reload-config"],
|
||||
desc: "reload configuration file",
|
||||
tokens: &[One(Literal("reload-config"))],
|
||||
parser: parser::reload_config
|
||||
}
|
||||
]);
|
||||
|
||||
/// Get command suggestions for input
|
||||
pub fn command_completion_suggestions(input: &str) -> Vec<String> {
|
||||
use crate::melib::ShellExpandTrait;
|
||||
let mut sugg: HashSet<String> = Default::default();
|
||||
for (_tags, _desc, tokens, _) in COMMAND_COMPLETION.iter() {
|
||||
let _m = tokens.matches(&mut &(*input), &mut sugg);
|
||||
if _m.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if let Some((s, Filepath)) = _m.last() {
|
||||
let p = std::path::Path::new(s);
|
||||
sugg.extend(p.complete(true, s.ends_with('/')).into_iter());
|
||||
}
|
||||
}
|
||||
sugg.into_iter()
|
||||
.map(|s| format!("{}{}", input, s.as_str()))
|
||||
.collect::<Vec<String>>()
|
||||
}
|
|
@ -1,180 +0,0 @@
|
|||
/*
|
||||
* meli
|
||||
*
|
||||
* Copyright 2017 Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//! Helper type for showing the exact reason why a command was invalid.
|
||||
|
||||
use super::*;
|
||||
|
||||
pub enum ArgCheck<const MIN: u8, const MAX: u8> {
|
||||
Start { __func__: &'static str },
|
||||
BeforeArgument { so_far: u8, __func__: &'static str },
|
||||
Eof { so_far: u8, __func__: &'static str },
|
||||
}
|
||||
|
||||
impl<const MIN: u8, const MAX: u8> ArgCheck<MIN, MAX> {
|
||||
#[inline]
|
||||
pub fn new(__func__: &'static str) -> Self {
|
||||
Self::Start { __func__ }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn start(&mut self, input: &[u8]) -> Result<(), CommandError> {
|
||||
let Self::Start { __func__ } = *self else {
|
||||
unreachable!(
|
||||
"ArgCheck::start called with invalid variant: {}",
|
||||
if matches!(self, Self::BeforeArgument { .. }) {
|
||||
"BeforeArgument"
|
||||
} else {
|
||||
"Eof"
|
||||
}
|
||||
);
|
||||
};
|
||||
let is_empty = input.trim().is_empty();
|
||||
if is_empty && MIN > 0 {
|
||||
return Err(CommandError::WrongNumberOfArguments {
|
||||
too_many: false,
|
||||
takes: (MIN, MAX.into()),
|
||||
given: 0,
|
||||
__func__,
|
||||
inner: format!(
|
||||
"needs {}{} arguments.",
|
||||
if MIN == MAX { "at least " } else { "" },
|
||||
MIN
|
||||
)
|
||||
.into(),
|
||||
});
|
||||
}
|
||||
*self = Self::BeforeArgument {
|
||||
so_far: 0,
|
||||
__func__,
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn inc(&mut self, input: &[u8]) -> Result<(), CommandError> {
|
||||
let Self::BeforeArgument { __func__, so_far } = *self else {
|
||||
unreachable!(
|
||||
"ArgCheck::inc called with invalid variant: {}",
|
||||
if matches!(self, Self::Start { .. }) {
|
||||
"Start"
|
||||
} else {
|
||||
"Eof"
|
||||
}
|
||||
);
|
||||
};
|
||||
let is_empty = input.trim().is_empty();
|
||||
let new_value = so_far + 1;
|
||||
if is_empty && new_value > MAX {
|
||||
return Err(CommandError::WrongNumberOfArguments {
|
||||
too_many: true,
|
||||
takes: (MIN, MAX.into()),
|
||||
given: new_value,
|
||||
__func__,
|
||||
inner: format!(
|
||||
"needs {}{} arguments.",
|
||||
if MIN == MAX { "at least " } else { "" },
|
||||
MIN
|
||||
)
|
||||
.into(),
|
||||
});
|
||||
}
|
||||
*self = Self::BeforeArgument {
|
||||
so_far: new_value,
|
||||
__func__,
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn finish(&mut self, input: &[u8]) -> Result<(), CommandError> {
|
||||
let Self::BeforeArgument { __func__, so_far } = *self else {
|
||||
unreachable!(
|
||||
"ArgCheck::finish called with invalid variant: {}",
|
||||
if matches!(self, Self::Start { .. }) {
|
||||
"Start"
|
||||
} else {
|
||||
"Eof"
|
||||
}
|
||||
);
|
||||
};
|
||||
let is_empty = input.trim().is_empty();
|
||||
if !is_empty {
|
||||
assert!(so_far <= MAX);
|
||||
assert!(so_far >= MIN);
|
||||
return Err(CommandError::WrongNumberOfArguments {
|
||||
too_many: true,
|
||||
takes: (MIN, MAX.into()),
|
||||
given: so_far + 1,
|
||||
__func__,
|
||||
inner: format!(
|
||||
"needs {}{} arguments.",
|
||||
if MIN == MAX { "at least " } else { "" },
|
||||
MIN
|
||||
)
|
||||
.into(),
|
||||
});
|
||||
}
|
||||
*self = Self::Eof { so_far, __func__ };
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! arg_init {
|
||||
(min_arg: $n:expr, max_arg: $x:expr, $func:tt) => {{
|
||||
ArgCheck::<$n, $x>::new(stringify!($func))
|
||||
}};
|
||||
}
|
||||
|
||||
//macro_rules! arg_value_check {
|
||||
// ($tag:literal, $input:expr) => {{
|
||||
// if tag::<&'_ str, &'_ [u8],
|
||||
// melib::nom::error::Error<&[u8]>>($tag)($input).is_err() { return
|
||||
// Ok(( $input,
|
||||
// Err(CommandError::BadValue {
|
||||
// inner: $tag.to_string().into(),
|
||||
// }),
|
||||
// ));
|
||||
// }
|
||||
// tag($tag)($input)
|
||||
// }};
|
||||
//}
|
||||
|
||||
macro_rules! arg_chk {
|
||||
(start $check:ident, $input:expr) => {{
|
||||
if let Err(err) = $check.start($input) {
|
||||
return Ok(($input, Err(err)));
|
||||
};
|
||||
}};
|
||||
(inc $check:ident, $input:expr) => {{
|
||||
if let Err(err) = $check.inc($input) {
|
||||
return Ok(($input, Err(err)));
|
||||
};
|
||||
}};
|
||||
(finish $check:ident, $input:expr) => {{
|
||||
if let Err(err) = $check.finish($input) {
|
||||
return Ok(($input, Err(err)));
|
||||
};
|
||||
}};
|
||||
}
|
|
@ -1,142 +0,0 @@
|
|||
/*
|
||||
* meli
|
||||
*
|
||||
* Copyright 2017 Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum CommandError {
|
||||
Parsing {
|
||||
inner: Cow<'static, str>,
|
||||
kind: Cow<'static, str>,
|
||||
},
|
||||
BadValue {
|
||||
inner: Cow<'static, str>,
|
||||
suggestions: Option<&'static [&'static str]>,
|
||||
},
|
||||
WrongNumberOfArguments {
|
||||
too_many: bool,
|
||||
takes: (u8, Option<u8>),
|
||||
given: u8,
|
||||
__func__: &'static str,
|
||||
inner: Cow<'static, str>,
|
||||
},
|
||||
Other {
|
||||
inner: Cow<'static, str>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a> From<nom::Err<melib::nom::error::Error<&'a [u8]>>> for CommandError {
|
||||
fn from(res: nom::Err<melib::nom::error::Error<&'a [u8]>>) -> Self {
|
||||
match res {
|
||||
nom::Err::Incomplete(_) => Self::Parsing {
|
||||
inner: res.to_string().into(),
|
||||
kind: "".into(),
|
||||
},
|
||||
nom::Err::Error(e) | nom::Err::Failure(e) => Self::Parsing {
|
||||
inner: String::from_utf8_lossy(e.input).to_string().into(),
|
||||
kind: format!("{:?}", e.code).into(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for CommandError {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Parsing { inner, kind: _ } => {
|
||||
write!(fmt, "Could not parse command: {}", inner)
|
||||
}
|
||||
Self::BadValue {
|
||||
inner,
|
||||
suggestions: Some(suggs),
|
||||
} => {
|
||||
write!(fmt, "Bad value/argument: {}. Possible values are: ", inner)?;
|
||||
let len = suggs.len();
|
||||
for (i, val) in suggs.iter().enumerate() {
|
||||
if i == len.saturating_sub(1) {
|
||||
write!(fmt, "{}", val)?;
|
||||
} else {
|
||||
write!(fmt, "{}, ", val)?;
|
||||
}
|
||||
}
|
||||
write!(fmt, "")
|
||||
}
|
||||
Self::BadValue {
|
||||
inner,
|
||||
suggestions: None,
|
||||
} => {
|
||||
write!(fmt, "Bad value/argument: {}", inner)
|
||||
}
|
||||
Self::WrongNumberOfArguments {
|
||||
too_many,
|
||||
takes,
|
||||
given,
|
||||
__func__,
|
||||
inner: _,
|
||||
} => {
|
||||
if *too_many {
|
||||
match takes {
|
||||
(min, None) => {
|
||||
write!(
|
||||
fmt,
|
||||
"{}: Too many arguments. Command takes {} arguments, but {} were \
|
||||
given.",
|
||||
__func__, min, given
|
||||
)
|
||||
}
|
||||
(min, Some(max)) => {
|
||||
write!(
|
||||
fmt,
|
||||
"{}: Too many arguments. Command takes from {} to {} arguments, \
|
||||
but {} were given.",
|
||||
__func__, min, max, given
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match takes {
|
||||
(min, None) => {
|
||||
write!(
|
||||
fmt,
|
||||
"{}: Not enough arguments. Command takes {} arguments, but {} \
|
||||
were given.",
|
||||
__func__, min, given
|
||||
)
|
||||
}
|
||||
(min, Some(max)) => {
|
||||
write!(
|
||||
fmt,
|
||||
"{}: Not enough arguments. Command takes from {} to {} arguments, \
|
||||
but {} were given.",
|
||||
__func__, min, max, given
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Self::Other { inner } => {
|
||||
write!(fmt, "Error: {}", inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for CommandError {}
|
|
@ -1,206 +0,0 @@
|
|||
//
|
||||
// meli
|
||||
//
|
||||
// Copyright 2017- Emmanouil Pitsidianakis <manos@pitsidianak.is>
|
||||
//
|
||||
// This file is part of meli.
|
||||
//
|
||||
// meli is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// meli is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// SPDX-License-Identifier: EUPL-1.2 OR GPL-3.0-or-later
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_command_parser() {
|
||||
let mut input = "sort".to_string();
|
||||
macro_rules! match_input {
|
||||
($input:expr) => {{
|
||||
let mut sugg: HashSet<String> = Default::default();
|
||||
//print!("{}", $input);
|
||||
for (_tags, _desc, tokens, _) in COMMAND_COMPLETION.iter() {
|
||||
// //println!("{:?}, {:?}, {:?}", _tags, _desc, tokens);
|
||||
let _ = tokens.matches(&mut $input.as_str(), &mut sugg);
|
||||
// if !m.is_empty() {
|
||||
// //print!("{:?} ", desc);
|
||||
// //println!(" result = {:#?}\n\n", m);
|
||||
// }
|
||||
}
|
||||
//println!("suggestions = {:#?}", sugg);
|
||||
sugg.into_iter()
|
||||
.map(|s| format!("{}{}", $input.as_str(), s.as_str()))
|
||||
.collect::<HashSet<String>>()
|
||||
}};
|
||||
}
|
||||
assert_eq!(
|
||||
&match_input!(input),
|
||||
&IntoIterator::into_iter(["sort date".to_string(), "sort subject".to_string()]).collect(),
|
||||
);
|
||||
input = "so".to_string();
|
||||
assert_eq!(
|
||||
&match_input!(input),
|
||||
&IntoIterator::into_iter(["sort".to_string()]).collect(),
|
||||
);
|
||||
input = "so ".to_string();
|
||||
assert_eq!(&match_input!(input), &HashSet::default(),);
|
||||
input = "to".to_string();
|
||||
assert_eq!(
|
||||
&match_input!(input),
|
||||
&IntoIterator::into_iter(["toggle".to_string()]).collect(),
|
||||
);
|
||||
input = "toggle ".to_string();
|
||||
assert_eq!(
|
||||
&match_input!(input),
|
||||
&IntoIterator::into_iter([
|
||||
"toggle mouse".to_string(),
|
||||
"toggle sign".to_string(),
|
||||
"toggle encrypt".to_string(),
|
||||
"toggle thread_snooze".to_string()
|
||||
])
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_command_parser_interactive() {
|
||||
use std::io;
|
||||
let mut input = String::new();
|
||||
loop {
|
||||
input.clear();
|
||||
print!("> ");
|
||||
match io::stdin().read_line(&mut input) {
|
||||
Ok(_n) => {
|
||||
println!("Input is {:?}", input.as_str().trim());
|
||||
let mut sugg: HashSet<String> = Default::default();
|
||||
let mut vec = vec![];
|
||||
//print!("{}", input);
|
||||
for (_tags, _desc, tokens, _) in COMMAND_COMPLETION.iter() {
|
||||
//println!("{:?}, {:?}, {:?}", _tags, _desc, tokens);
|
||||
let m = tokens.matches(&mut input.as_str().trim(), &mut sugg);
|
||||
if !m.is_empty() {
|
||||
vec.push(tokens);
|
||||
//print!("{:?} ", desc);
|
||||
//println!(" result = {:#?}\n\n", m);
|
||||
}
|
||||
}
|
||||
println!(
|
||||
"suggestions = {:#?}",
|
||||
sugg.into_iter()
|
||||
.zip(vec.into_iter())
|
||||
.map(|(s, v)| format!(
|
||||
"{}{} {:?}",
|
||||
input.as_str().trim(),
|
||||
if input.trim().is_empty() {
|
||||
s.trim()
|
||||
} else {
|
||||
s.as_str()
|
||||
},
|
||||
v
|
||||
))
|
||||
.collect::<Vec<String>>()
|
||||
);
|
||||
if input.trim() == "quit" {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(error) => println!("error: {}", error),
|
||||
}
|
||||
}
|
||||
println!("alright");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_command_parser_all() {
|
||||
use CommandError::*;
|
||||
|
||||
for cmd in [
|
||||
"set unseen",
|
||||
"set seen",
|
||||
"delete",
|
||||
"copyto somewhere",
|
||||
"moveto somewhere",
|
||||
"import fpath mpath",
|
||||
"close ",
|
||||
"go 5",
|
||||
] {
|
||||
parse_command(cmd.as_bytes()).unwrap_or_else(|err| panic!("{} failed {}", cmd, err));
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
parse_command(b"setfafsfoo").unwrap_err().to_string(),
|
||||
Parsing {
|
||||
inner: "setfafsfoo".into(),
|
||||
kind: "".into(),
|
||||
}
|
||||
.to_string(),
|
||||
);
|
||||
assert_eq!(
|
||||
parse_command(b"set foo").unwrap_err().to_string(),
|
||||
BadValue {
|
||||
inner: "foo".into(),
|
||||
suggestions: Some(&[
|
||||
"seen",
|
||||
"unseen",
|
||||
"plain",
|
||||
"threaded",
|
||||
"compact",
|
||||
"conversations"
|
||||
])
|
||||
}
|
||||
.to_string(),
|
||||
);
|
||||
assert_eq!(
|
||||
parse_command(b"moveto ").unwrap_err().to_string(),
|
||||
WrongNumberOfArguments {
|
||||
too_many: false,
|
||||
takes: (1, Some(1)),
|
||||
given: 0,
|
||||
__func__: "moveto",
|
||||
inner: "".into(),
|
||||
}
|
||||
.to_string(),
|
||||
);
|
||||
assert_eq!(
|
||||
parse_command(b"reindex 1 2 3").unwrap_err().to_string(),
|
||||
WrongNumberOfArguments {
|
||||
too_many: true,
|
||||
takes: (1, Some(1)),
|
||||
given: 2,
|
||||
__func__: "reindex",
|
||||
inner: "".into(),
|
||||
}
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_command_error_display() {
|
||||
assert_eq!(
|
||||
&CommandError::BadValue {
|
||||
inner: "foo".into(),
|
||||
suggestions: Some(&[
|
||||
"seen",
|
||||
"unseen",
|
||||
"plain",
|
||||
"threaded",
|
||||
"compact",
|
||||
"conversations"
|
||||
])
|
||||
}
|
||||
.to_string(),
|
||||
"Bad value/argument: foo. Possible values are: seen, unseen, plain, threaded, compact, \
|
||||
conversations"
|
||||
);
|
||||
}
|
|
@ -1,341 +0,0 @@
|
|||
/*
|
||||
* meli
|
||||
*
|
||||
* Copyright 2017-2018 Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//! Components visual and logical separations of application interfaces.
|
||||
use indexmap::IndexMap;
|
||||
///
|
||||
/// They can draw on the terminal and receive events, but also do other stuff
|
||||
/// as well.
|
||||
/// For an example, see the [`notifications`] module.
|
||||
/// See also the [`Component`] trait for more details.
|
||||
use smallvec::SmallVec;
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Clone, Copy, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
|
||||
#[repr(transparent)]
|
||||
pub struct ComponentId(Uuid);
|
||||
|
||||
impl AsRef<Uuid> for ComponentId {
|
||||
fn as_ref(&self) -> &Uuid {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ComponentId {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
std::fmt::Display::fmt(&self.0.as_simple(), fmt)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for ComponentId {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
std::fmt::Display::fmt(&self.0.as_simple(), fmt)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ComponentId {
|
||||
fn default() -> Self {
|
||||
Self(Uuid::new_v4())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::LowerHex for ComponentId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
std::fmt::LowerHex::fmt(self.0.as_hyphenated(), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::UpperHex for ComponentId {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
std::fmt::UpperHex::fmt(self.0.as_hyphenated(), f)
|
||||
}
|
||||
}
|
||||
|
||||
pub type ShortcutMap = IndexMap<&'static str, Key>;
|
||||
pub type ShortcutMaps = IndexMap<&'static str, ShortcutMap>;
|
||||
|
||||
mod private {
|
||||
pub trait Sealed {}
|
||||
}
|
||||
impl private::Sealed for ShortcutMaps {}
|
||||
|
||||
pub trait ExtendShortcutsMaps: private::Sealed {
|
||||
fn extend_shortcuts(&mut self, other: Self);
|
||||
}
|
||||
|
||||
impl ExtendShortcutsMaps for ShortcutMaps {
|
||||
fn extend_shortcuts(&mut self, mut other: Self) {
|
||||
other.retain(|k, v| {
|
||||
if let Some(m) = self.get_mut(k) {
|
||||
m.extend(v.iter().map(|(k, v)| (*k, v.clone())));
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
self.extend(other);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum PageMovement {
|
||||
Up(usize),
|
||||
Right(usize),
|
||||
Left(usize),
|
||||
Down(usize),
|
||||
PageUp(usize),
|
||||
PageDown(usize),
|
||||
Home,
|
||||
End,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct ScrollContext {
|
||||
pub shown_lines: usize,
|
||||
pub total_lines: usize,
|
||||
pub has_more_lines: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum ScrollUpdate {
|
||||
End(ComponentId),
|
||||
Update {
|
||||
id: ComponentId,
|
||||
context: ScrollContext,
|
||||
},
|
||||
}
|
||||
|
||||
/// A user interface component (also referred to as a UI widget).
|
||||
///
|
||||
/// Types implementing this Trait can draw on the terminal and receive events.
|
||||
/// If a type wants to skip drawing if it has not changed anything, it can hold
|
||||
/// some flag in its fields (eg `self.dirty = false`) and act upon that in their
|
||||
/// [`draw`](Component::draw) implementation.
|
||||
pub trait Component: std::fmt::Display + std::fmt::Debug + Send + Sync {
|
||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context);
|
||||
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool;
|
||||
fn is_dirty(&self) -> bool;
|
||||
/// If the component is meant to be currently visible to the user.
|
||||
fn is_visible(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// If the component can quit right away without any unsaved or ongoing
|
||||
/// operations.
|
||||
fn can_quit_cleanly(&mut self, _context: &Context) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn set_dirty(&mut self, value: bool);
|
||||
|
||||
fn kill(&mut self, _id: ComponentId, _context: &mut Context) {}
|
||||
|
||||
fn id(&self) -> ComponentId;
|
||||
|
||||
fn shortcuts(&self, _context: &Context) -> ShortcutMaps {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Get status message for the status line.
|
||||
fn status(&self, _context: &Context) -> String {
|
||||
String::new()
|
||||
}
|
||||
|
||||
fn attributes(&self) -> &'static ComponentAttr {
|
||||
&ComponentAttr::DEFAULT
|
||||
}
|
||||
|
||||
fn children(&self) -> IndexMap<ComponentId, &dyn Component> {
|
||||
IndexMap::default()
|
||||
}
|
||||
|
||||
fn children_mut(&mut self) -> IndexMap<ComponentId, &mut dyn Component> {
|
||||
IndexMap::default()
|
||||
}
|
||||
|
||||
fn realize(&self, parent: Option<ComponentId>, context: &mut Context) {
|
||||
// log::trace!("Realizing id {} w/ parent {:?}", self.id(), &parent);
|
||||
context.realized.insert(self.id(), parent);
|
||||
}
|
||||
|
||||
fn unrealize(&self, context: &mut Context) {
|
||||
// log::trace!("Unrealizing id {}", self.id());
|
||||
context.unrealized.insert(self.id());
|
||||
context
|
||||
.replies
|
||||
.push_back(UIEvent::ComponentUnrealize(self.id()));
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Box<dyn Component> {
|
||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
(**self).draw(grid, area, context)
|
||||
}
|
||||
|
||||
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
|
||||
(**self).process_event(event, context)
|
||||
}
|
||||
|
||||
fn is_dirty(&self) -> bool {
|
||||
(**self).is_dirty()
|
||||
}
|
||||
|
||||
fn is_visible(&self) -> bool {
|
||||
(**self).is_visible()
|
||||
}
|
||||
|
||||
fn can_quit_cleanly(&mut self, context: &Context) -> bool {
|
||||
(**self).can_quit_cleanly(context)
|
||||
}
|
||||
|
||||
fn set_dirty(&mut self, value: bool) {
|
||||
(**self).set_dirty(value)
|
||||
}
|
||||
|
||||
fn kill(&mut self, id: ComponentId, context: &mut Context) {
|
||||
(**self).kill(id, context)
|
||||
}
|
||||
|
||||
fn id(&self) -> ComponentId {
|
||||
(**self).id()
|
||||
}
|
||||
|
||||
fn shortcuts(&self, context: &Context) -> ShortcutMaps {
|
||||
(**self).shortcuts(context)
|
||||
}
|
||||
|
||||
fn status(&self, context: &Context) -> String {
|
||||
(**self).status(context)
|
||||
}
|
||||
|
||||
fn attributes(&self) -> &'static ComponentAttr {
|
||||
(**self).attributes()
|
||||
}
|
||||
|
||||
fn children(&self) -> IndexMap<ComponentId, &dyn Component> {
|
||||
(**self).children()
|
||||
}
|
||||
|
||||
fn children_mut(&mut self) -> IndexMap<ComponentId, &mut dyn Component> {
|
||||
(**self).children_mut()
|
||||
}
|
||||
|
||||
fn realize(&self, parent: Option<ComponentId>, context: &mut Context) {
|
||||
(**self).realize(parent, context)
|
||||
}
|
||||
|
||||
fn unrealize(&self, context: &mut Context) {
|
||||
(**self).unrealize(context)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Box<dyn Component> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self == other || self.id() == other.id()
|
||||
}
|
||||
}
|
||||
|
||||
bitflags::bitflags! {
|
||||
/// Attributes of a [`Component`] widget.
|
||||
///
|
||||
/// `ComponentAttr::DEFAULT` represents no attribute.
|
||||
pub struct ComponentAttr: u8 {
|
||||
/// Nothing special going on.
|
||||
const DEFAULT = 0;
|
||||
const HAS_ANIMATIONS = 1;
|
||||
const CONTAINER = 1 << 1;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ComponentAttr {
|
||||
fn default() -> Self {
|
||||
Self::DEFAULT
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct ComponentPath {
|
||||
pub id: ComponentId,
|
||||
pub tail: SmallVec<[ComponentId; 8]>,
|
||||
}
|
||||
|
||||
impl ComponentPath {
|
||||
pub fn new(id: ComponentId) -> Self {
|
||||
Self {
|
||||
id,
|
||||
tail: SmallVec::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_front(&mut self, id: ComponentId) {
|
||||
self.tail.insert(0, self.id);
|
||||
self.id = id;
|
||||
}
|
||||
|
||||
pub fn push_back(&mut self, id: ComponentId) {
|
||||
self.tail.push(id);
|
||||
}
|
||||
|
||||
pub fn resolve<'c>(&self, root: &'c dyn Component) -> Option<&'c dyn Component> {
|
||||
let mut cursor = root;
|
||||
for id in self.tail.iter().rev().chain(std::iter::once(&self.id)) {
|
||||
// log::trace!("resolve cursor = {} next id is {}", cursor.id(), &id);
|
||||
if *id == cursor.id() {
|
||||
// log::trace!("continue;");
|
||||
continue;
|
||||
}
|
||||
cursor = cursor.children().shift_remove(id)?;
|
||||
}
|
||||
Some(cursor)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn parent(&self) -> Option<&ComponentId> {
|
||||
self.tail.first()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn root(&self) -> Option<&ComponentId> {
|
||||
self.tail.last()
|
||||
}
|
||||
}
|
||||
|
||||
pub mod prelude {
|
||||
pub use std::borrow::Cow;
|
||||
|
||||
pub use indexmap::IndexMap;
|
||||
|
||||
pub use crate::{
|
||||
accounts::MailboxEntry,
|
||||
command::*,
|
||||
components::{
|
||||
Component, ComponentAttr, ComponentId, ComponentPath, ExtendShortcutsMaps,
|
||||
ScrollContext, *,
|
||||
},
|
||||
jobs::{JobId, JobMetadata},
|
||||
melib::{text::TextProcessing, utils::datetime, SortOrder},
|
||||
shortcut, AccountHash, Action, Area, Attr, CellBuffer, Context, DataColumns, EnvelopeHash,
|
||||
Key, MailboxHash, Shortcuts, StatusEvent, ThemeAttribute, UIDialog, UIEvent, UIMode,
|
||||
};
|
||||
}
|
769
meli/src/conf.rs
|
@ -1,769 +0,0 @@
|
|||
/*
|
||||
* meli - configuration module.
|
||||
*
|
||||
* Copyright 2017 Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//! Configuration logic and `config.toml` interfaces.
|
||||
|
||||
extern crate serde;
|
||||
extern crate toml;
|
||||
extern crate xdg;
|
||||
|
||||
use std::{
|
||||
env,
|
||||
fs::OpenOptions,
|
||||
io::Write,
|
||||
os::unix::fs::PermissionsExt,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use melib::{
|
||||
backends::{MailboxHash, TagHash},
|
||||
conf::{ActionFlag, MailboxConf, ToggleFlag},
|
||||
error::*,
|
||||
search::Query,
|
||||
ShellExpandTrait, SortField, SortOrder, StderrLogger,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{conf::deserializers::non_empty_opt_string, terminal::Color};
|
||||
|
||||
pub mod default_values;
|
||||
pub mod preprocessing;
|
||||
use preprocessing as pp;
|
||||
|
||||
pub mod data_types;
|
||||
#[cfg(test)]
|
||||
pub mod tests;
|
||||
#[rustfmt::skip]
|
||||
mod overrides;
|
||||
pub use overrides::*;
|
||||
pub mod composing;
|
||||
pub mod notifications;
|
||||
pub mod pager;
|
||||
pub mod pgp;
|
||||
pub mod tags;
|
||||
#[macro_use]
|
||||
pub mod shortcuts;
|
||||
mod listing;
|
||||
pub mod terminal;
|
||||
mod themes;
|
||||
use default_values::*;
|
||||
pub use themes::*;
|
||||
|
||||
pub use self::{composing::*, pgp::*, shortcuts::*, tags::*};
|
||||
|
||||
/// Utility macro to access an [`AccountConf`] setting field from
|
||||
/// [`Context`](crate::Context) indexed by `$account_hash`
|
||||
///
|
||||
/// The value returned is the optionally overriden one in the
|
||||
/// [`AccountConf::conf_override`] field, otherwise the global one.
|
||||
///
|
||||
/// See also the [`mailbox_settings`](crate::mailbox_settings) macro.
|
||||
#[macro_export]
|
||||
macro_rules! account_settings {
|
||||
($context:ident[$account_hash:expr].$setting:ident.$field:ident) => {{
|
||||
$context.accounts[&$account_hash]
|
||||
.settings
|
||||
.conf_override
|
||||
.$setting
|
||||
.$field
|
||||
.as_ref()
|
||||
.unwrap_or(&$context.settings.$setting.$field)
|
||||
}};
|
||||
($context:ident[$account_hash:expr].$field:ident) => {{
|
||||
&$context.accounts[&$account_hash].settings.$field
|
||||
}};
|
||||
}
|
||||
|
||||
/// Utility macro to access an [`AccountConf`] setting field from
|
||||
/// [`Context`](crate::Context) indexed by `$account_hash` and a mailbox.
|
||||
///
|
||||
/// The value returned is the optionally overriden one in the
|
||||
/// [`FileMailboxConf::conf_override`] field, otherwise the
|
||||
/// [`AccountConf::conf_override`] field, otherwise the global one.
|
||||
///
|
||||
/// See also the [`account_settings`] macro.
|
||||
#[macro_export]
|
||||
macro_rules! mailbox_settings {
|
||||
($context:ident[$account_hash:expr][$mailbox_path:expr].$setting:ident.$field:ident) => {{
|
||||
$context.accounts[&$account_hash][$mailbox_path]
|
||||
.conf
|
||||
.conf_override
|
||||
.$setting
|
||||
.$field
|
||||
.as_ref()
|
||||
.or($context.accounts[&$account_hash]
|
||||
.settings
|
||||
.conf_override
|
||||
.$setting
|
||||
.$field
|
||||
.as_ref())
|
||||
.unwrap_or(&$context.settings.$setting.$field)
|
||||
}};
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct MailUIConf {
|
||||
pub send_mail: Option<SendMail>,
|
||||
#[serde(default)]
|
||||
pub pager: PagerSettingsOverride,
|
||||
#[serde(default)]
|
||||
pub listing: ListingSettingsOverride,
|
||||
#[serde(default)]
|
||||
pub notifications: NotificationsSettingsOverride,
|
||||
#[serde(default)]
|
||||
pub shortcuts: ShortcutsOverride,
|
||||
#[serde(default)]
|
||||
pub composing: ComposingSettingsOverride,
|
||||
#[serde(default)]
|
||||
pub identity: Option<String>,
|
||||
#[serde(default)]
|
||||
pub tags: TagsSettingsOverride,
|
||||
#[serde(default)]
|
||||
pub themes: Option<Themes>,
|
||||
#[serde(default)]
|
||||
pub pgp: PGPSettingsOverride,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[serde(default)]
|
||||
pub struct FileMailboxConf {
|
||||
#[serde(flatten)]
|
||||
pub conf_override: MailUIConf,
|
||||
#[serde(default = "false_val")]
|
||||
pub collapsed: bool,
|
||||
#[serde(flatten)]
|
||||
pub mailbox_conf: MailboxConf,
|
||||
}
|
||||
|
||||
impl FileMailboxConf {
|
||||
pub fn conf_override(&self) -> &MailUIConf {
|
||||
&self.conf_override
|
||||
}
|
||||
|
||||
pub fn mailbox_conf(&self) -> &MailboxConf {
|
||||
&self.mailbox_conf
|
||||
}
|
||||
}
|
||||
|
||||
use crate::conf::deserializers::extra_settings;
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
pub struct FileAccount {
|
||||
pub root_mailbox: String,
|
||||
/// The mailbox that is the default to open / view for this account. Must be
|
||||
/// a valid mailbox path.
|
||||
///
|
||||
/// If not specified, the default is [`Self::root_mailbox`].
|
||||
#[serde(default = "none", skip_serializing_if = "Option::is_none")]
|
||||
pub default_mailbox: Option<String>,
|
||||
pub format: String,
|
||||
pub send_mail: SendMail,
|
||||
pub identity: String,
|
||||
#[serde(default)]
|
||||
pub extra_identities: Vec<String>,
|
||||
#[serde(default = "none", skip_serializing_if = "Option::is_none")]
|
||||
pub display_name: Option<String>,
|
||||
#[serde(default = "false_val")]
|
||||
pub read_only: bool,
|
||||
#[serde(default)]
|
||||
pub subscribed_mailboxes: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub mailboxes: IndexMap<String, FileMailboxConf>,
|
||||
#[serde(default)]
|
||||
pub search_backend: data_types::SearchBackend,
|
||||
#[serde(default)]
|
||||
pub order: (SortField, SortOrder),
|
||||
#[serde(default = "false_val")]
|
||||
pub manual_refresh: bool,
|
||||
#[serde(default = "none", skip_serializing_if = "Option::is_none")]
|
||||
pub refresh_command: Option<String>,
|
||||
#[serde(flatten)]
|
||||
pub conf_override: MailUIConf,
|
||||
#[serde(flatten)]
|
||||
#[serde(
|
||||
deserialize_with = "extra_settings",
|
||||
skip_serializing_if = "IndexMap::is_empty"
|
||||
)]
|
||||
/// Use custom deserializer to convert any given value (eg `bool`, number,
|
||||
/// etc) to `String`.
|
||||
pub extra: IndexMap<String, String>,
|
||||
}
|
||||
|
||||
impl FileAccount {
|
||||
pub fn mailboxes(&self) -> &IndexMap<String, FileMailboxConf> {
|
||||
&self.mailboxes
|
||||
}
|
||||
|
||||
pub fn search_backend(&self) -> &data_types::SearchBackend {
|
||||
&self.search_backend
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct FileSettings {
|
||||
pub accounts: IndexMap<String, FileAccount>,
|
||||
#[serde(default)]
|
||||
pub pager: pager::PagerSettings,
|
||||
#[serde(default)]
|
||||
pub listing: listing::ListingSettings,
|
||||
#[serde(default)]
|
||||
pub notifications: notifications::NotificationsSettings,
|
||||
#[serde(default)]
|
||||
pub shortcuts: shortcuts::Shortcuts,
|
||||
#[serde(default)]
|
||||
pub composing: composing::ComposingSettings,
|
||||
#[serde(default)]
|
||||
pub tags: tags::TagsSettings,
|
||||
#[serde(default)]
|
||||
pub pgp: pgp::PGPSettings,
|
||||
#[serde(default)]
|
||||
pub terminal: terminal::TerminalSettings,
|
||||
#[serde(default)]
|
||||
pub log: LogSettings,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize)]
|
||||
pub struct AccountConf {
|
||||
pub account: melib::AccountSettings,
|
||||
/// How to send e-mail for this account.
|
||||
/// Required
|
||||
pub send_mail: SendMail,
|
||||
pub default_mailbox: Option<MailboxHash>,
|
||||
pub sent_mailbox: Option<MailboxHash>,
|
||||
pub conf: FileAccount,
|
||||
pub conf_override: MailUIConf,
|
||||
pub mailbox_confs: IndexMap<String, FileMailboxConf>,
|
||||
}
|
||||
|
||||
impl AccountConf {
|
||||
pub fn account(&self) -> &melib::AccountSettings {
|
||||
&self.account
|
||||
}
|
||||
pub fn account_mut(&mut self) -> &mut melib::AccountSettings {
|
||||
&mut self.account
|
||||
}
|
||||
pub fn conf(&self) -> &FileAccount {
|
||||
&self.conf
|
||||
}
|
||||
pub fn conf_mut(&mut self) -> &mut FileAccount {
|
||||
&mut self.conf
|
||||
}
|
||||
}
|
||||
|
||||
impl From<melib::AccountSettings> for AccountConf {
|
||||
fn from(account: melib::AccountSettings) -> Self {
|
||||
Self {
|
||||
account,
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<FileAccount> for AccountConf {
|
||||
fn from(x: FileAccount) -> Self {
|
||||
let format = x.format.to_lowercase();
|
||||
let root_mailbox = x.root_mailbox.clone();
|
||||
let identity = x.identity.clone();
|
||||
let display_name = x.display_name.clone();
|
||||
let order = x.order;
|
||||
let mailboxes = x
|
||||
.mailboxes
|
||||
.iter()
|
||||
.map(|(k, v)| (k.clone(), v.mailbox_conf.clone()))
|
||||
.collect();
|
||||
|
||||
let account = melib::AccountSettings {
|
||||
name: String::new(),
|
||||
root_mailbox,
|
||||
format,
|
||||
identity,
|
||||
extra_identities: x.extra_identities.clone(),
|
||||
read_only: x.read_only,
|
||||
display_name,
|
||||
order,
|
||||
subscribed_mailboxes: x.subscribed_mailboxes.clone(),
|
||||
mailboxes,
|
||||
manual_refresh: x.manual_refresh,
|
||||
extra: x.extra.clone().into_iter().collect(),
|
||||
};
|
||||
|
||||
let mailbox_confs = x.mailboxes.clone();
|
||||
Self {
|
||||
send_mail: x.send_mail.clone(),
|
||||
default_mailbox: None,
|
||||
sent_mailbox: None,
|
||||
conf_override: x.conf_override.clone(),
|
||||
conf: x,
|
||||
mailbox_confs,
|
||||
..Self::from(account)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_config_file() -> Result<PathBuf> {
|
||||
if let Ok(path) = env::var("MELI_CONFIG") {
|
||||
return Ok(PathBuf::from(path).expand());
|
||||
}
|
||||
let xdg_dirs = xdg::BaseDirectories::with_prefix("meli")?;
|
||||
xdg_dirs
|
||||
.place_config_file("config.toml")
|
||||
.chain_err_summary(|| {
|
||||
format!(
|
||||
"Cannot create configuration directory in {}",
|
||||
xdg_dirs.get_config_home().display()
|
||||
)
|
||||
})
|
||||
.chain_err_kind(ErrorKind::Platform)
|
||||
}
|
||||
|
||||
impl FileSettings {
|
||||
pub const EXAMPLE_CONFIG: &'static str = include_str!("../docs/samples/sample-config.toml");
|
||||
|
||||
pub fn new() -> Result<Self> {
|
||||
let config_path = get_config_file()?;
|
||||
if !config_path.exists() {
|
||||
let path_string = config_path.display().to_string();
|
||||
if path_string.is_empty() {
|
||||
return Err(Error::new("Given configuration path is empty.")
|
||||
.set_kind(ErrorKind::Configuration));
|
||||
}
|
||||
#[cfg(not(test))]
|
||||
let ask = crate::terminal::Ask::new(format!(
|
||||
"No configuration found. Would you like to generate one in {}?",
|
||||
path_string
|
||||
));
|
||||
#[cfg(not(test))]
|
||||
let mut stdout = std::io::stdout();
|
||||
#[cfg(not(test))]
|
||||
let stdin = std::io::stdin();
|
||||
#[cfg(not(test))]
|
||||
if ask.run(&mut stdout, &mut stdin.lock()) {
|
||||
create_config_file(&config_path)?;
|
||||
return Err(
|
||||
Error::new("Edit the sample configuration and relaunch meli.")
|
||||
.set_kind(ErrorKind::Configuration),
|
||||
);
|
||||
}
|
||||
#[cfg(test)]
|
||||
return Ok(Self::default());
|
||||
#[cfg(not(test))]
|
||||
return Err(
|
||||
Error::new("No configuration file found.").set_kind(ErrorKind::Configuration)
|
||||
);
|
||||
}
|
||||
|
||||
let mut stdout = std::io::stdout();
|
||||
let stdin = std::io::stdin();
|
||||
crate::version_migrations::version_setup(&config_path, &mut stdout, &mut stdin.lock())?;
|
||||
Self::validate(config_path, false)
|
||||
}
|
||||
|
||||
/// Validate configuration from `input` string.
|
||||
pub fn validate_string(s: String, clear_extras: bool) -> Result<Self> {
|
||||
let _: toml::value::Table = melib::serde_path_to_error::deserialize(
|
||||
toml::Deserializer::new(&s),
|
||||
)
|
||||
.map_err(|err| {
|
||||
Error::new("Config file is invalid TOML")
|
||||
.set_source(Some(Arc::new(err)))
|
||||
.set_kind(ErrorKind::ValueError)
|
||||
})?;
|
||||
|
||||
let mut s: Self = melib::serde_path_to_error::deserialize(toml::Deserializer::new(&s))
|
||||
.map_err(|err| {
|
||||
Error::new("Input contains errors")
|
||||
.set_source(Some(Arc::new(err)))
|
||||
.set_kind(ErrorKind::Configuration)
|
||||
})?;
|
||||
let backends = melib::backends::Backends::new();
|
||||
let Themes {
|
||||
light: default_light,
|
||||
dark: default_dark,
|
||||
..
|
||||
} = Themes::default();
|
||||
for (k, v) in default_light.keys.into_iter() {
|
||||
if !s.terminal.themes.light.contains_key(&k) {
|
||||
s.terminal.themes.light.insert(k, v);
|
||||
}
|
||||
}
|
||||
for theme in s.terminal.themes.other_themes.values_mut() {
|
||||
for (k, v) in default_dark.keys.clone().into_iter() {
|
||||
if !theme.contains_key(&k) {
|
||||
theme.insert(k, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (k, v) in default_dark.keys.into_iter() {
|
||||
if !s.terminal.themes.dark.contains_key(&k) {
|
||||
s.terminal.themes.dark.insert(k, v);
|
||||
}
|
||||
}
|
||||
match s.terminal.theme.as_str() {
|
||||
themes::DARK | themes::LIGHT => {}
|
||||
t if s.terminal.themes.other_themes.contains_key(t) => {}
|
||||
t => {
|
||||
return Err(Error::new(format!("Theme `{}` was not found.", t))
|
||||
.set_kind(ErrorKind::Configuration));
|
||||
}
|
||||
}
|
||||
|
||||
s.terminal.themes.validate()?;
|
||||
for (name, acc) in s.accounts.iter_mut() {
|
||||
let FileAccount {
|
||||
root_mailbox,
|
||||
format,
|
||||
send_mail: _,
|
||||
identity,
|
||||
extra_identities,
|
||||
read_only,
|
||||
display_name,
|
||||
order,
|
||||
subscribed_mailboxes,
|
||||
mailboxes,
|
||||
extra,
|
||||
manual_refresh,
|
||||
default_mailbox: _,
|
||||
refresh_command: _,
|
||||
search_backend: _,
|
||||
conf_override: _,
|
||||
} = acc.clone();
|
||||
|
||||
let lowercase_format = format.to_lowercase();
|
||||
let mut s = melib::AccountSettings {
|
||||
name: name.to_string(),
|
||||
root_mailbox,
|
||||
format: format.clone(),
|
||||
identity,
|
||||
extra_identities,
|
||||
read_only,
|
||||
display_name,
|
||||
order,
|
||||
subscribed_mailboxes,
|
||||
manual_refresh,
|
||||
mailboxes: mailboxes
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k, v.mailbox_conf))
|
||||
.collect(),
|
||||
extra: extra.into_iter().collect(),
|
||||
};
|
||||
s.validate_config()?;
|
||||
backends.validate_config(&lowercase_format, &mut s)?;
|
||||
if !s.extra.is_empty() {
|
||||
return Err(Error::new(format!(
|
||||
"Unrecognised configuration values: {:?}",
|
||||
s.extra
|
||||
))
|
||||
.set_kind(ErrorKind::Configuration));
|
||||
}
|
||||
if clear_extras {
|
||||
acc.extra.clear();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
/// Validate `path` and print errors.
|
||||
pub fn validate(path: PathBuf, clear_extras: bool) -> Result<Self> {
|
||||
let s = pp::pp(&path)?;
|
||||
let _: toml::value::Table = toml::from_str(&s).map_err(|err| {
|
||||
Error::new(format!(
|
||||
"{}: Config file is invalid TOML; {}",
|
||||
path.display(),
|
||||
err
|
||||
))
|
||||
})?;
|
||||
|
||||
let mut s: Self = toml::from_str(&s).map_err(|err| {
|
||||
Error::new(format!("{}: Config file contains errors", path.display()))
|
||||
.set_source(Some(Arc::new(err)))
|
||||
.set_kind(ErrorKind::Configuration)
|
||||
})?;
|
||||
let backends = melib::backends::Backends::new();
|
||||
let Themes {
|
||||
light: default_light,
|
||||
dark: default_dark,
|
||||
..
|
||||
} = Themes::default();
|
||||
for (k, v) in default_light.keys.into_iter() {
|
||||
if !s.terminal.themes.light.contains_key(&k) {
|
||||
s.terminal.themes.light.insert(k, v);
|
||||
}
|
||||
}
|
||||
for theme in s.terminal.themes.other_themes.values_mut() {
|
||||
for (k, v) in default_dark.keys.clone().into_iter() {
|
||||
if !theme.contains_key(&k) {
|
||||
theme.insert(k, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (k, v) in default_dark.keys.into_iter() {
|
||||
if !s.terminal.themes.dark.contains_key(&k) {
|
||||
s.terminal.themes.dark.insert(k, v);
|
||||
}
|
||||
}
|
||||
match s.terminal.theme.as_str() {
|
||||
themes::DARK | themes::LIGHT => {}
|
||||
t if s.terminal.themes.other_themes.contains_key(t) => {}
|
||||
t => {
|
||||
return Err(Error::new(format!("Theme `{}` was not found.", t))
|
||||
.set_kind(ErrorKind::Configuration));
|
||||
}
|
||||
}
|
||||
|
||||
s.terminal.themes.validate()?;
|
||||
for (name, acc) in s.accounts.iter_mut() {
|
||||
let FileAccount {
|
||||
root_mailbox,
|
||||
format,
|
||||
send_mail: _,
|
||||
identity,
|
||||
extra_identities,
|
||||
read_only,
|
||||
display_name,
|
||||
order,
|
||||
subscribed_mailboxes,
|
||||
mailboxes,
|
||||
extra,
|
||||
manual_refresh,
|
||||
default_mailbox: _,
|
||||
refresh_command: _,
|
||||
search_backend: _,
|
||||
conf_override: _,
|
||||
} = acc.clone();
|
||||
|
||||
let lowercase_format = format.to_lowercase();
|
||||
let mut s = melib::AccountSettings {
|
||||
name: name.to_string(),
|
||||
root_mailbox,
|
||||
format: format.clone(),
|
||||
identity,
|
||||
extra_identities,
|
||||
read_only,
|
||||
display_name,
|
||||
order,
|
||||
subscribed_mailboxes,
|
||||
manual_refresh,
|
||||
mailboxes: mailboxes
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k, v.mailbox_conf))
|
||||
.collect(),
|
||||
extra: extra.into_iter().collect(),
|
||||
};
|
||||
s.validate_config()?;
|
||||
backends.validate_config(&lowercase_format, &mut s)?;
|
||||
if !s.extra.is_empty() {
|
||||
return Err(Error::new(format!(
|
||||
"Unrecognised configuration values: {:?}",
|
||||
s.extra
|
||||
))
|
||||
.set_kind(ErrorKind::Configuration));
|
||||
}
|
||||
if clear_extras {
|
||||
acc.extra.clear();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize)]
|
||||
pub struct Settings {
|
||||
pub accounts: IndexMap<String, AccountConf>,
|
||||
pub pager: pager::PagerSettings,
|
||||
pub listing: listing::ListingSettings,
|
||||
pub notifications: notifications::NotificationsSettings,
|
||||
pub shortcuts: shortcuts::Shortcuts,
|
||||
pub tags: tags::TagsSettings,
|
||||
pub composing: composing::ComposingSettings,
|
||||
pub pgp: pgp::PGPSettings,
|
||||
pub terminal: terminal::TerminalSettings,
|
||||
pub log: LogSettings,
|
||||
#[serde(skip)]
|
||||
pub _logger: StderrLogger,
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
pub fn new() -> Result<Self> {
|
||||
let fs = FileSettings::new()?;
|
||||
let mut s: IndexMap<String, AccountConf> = IndexMap::new();
|
||||
|
||||
for (id, x) in fs.accounts {
|
||||
let mut ac = AccountConf::from(x);
|
||||
ac.account.name.clone_from(&id);
|
||||
|
||||
s.insert(id, ac);
|
||||
}
|
||||
|
||||
let mut _logger = StderrLogger::new(fs.log.maximum_level);
|
||||
|
||||
if let Some(ref log_path) = fs.log.log_file {
|
||||
_logger.change_log_dest(log_path.into());
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
accounts: s,
|
||||
pager: fs.pager,
|
||||
listing: fs.listing,
|
||||
notifications: fs.notifications,
|
||||
shortcuts: fs.shortcuts,
|
||||
tags: fs.tags,
|
||||
composing: fs.composing,
|
||||
pgp: fs.pgp,
|
||||
terminal: fs.terminal,
|
||||
log: fs.log,
|
||||
_logger,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn without_accounts() -> Result<Self> {
|
||||
let fs = FileSettings::new()?;
|
||||
let mut _logger = StderrLogger::new(fs.log.maximum_level);
|
||||
|
||||
if let Some(ref log_path) = fs.log.log_file {
|
||||
_logger.change_log_dest(log_path.into());
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
accounts: IndexMap::new(),
|
||||
pager: fs.pager,
|
||||
listing: fs.listing,
|
||||
notifications: fs.notifications,
|
||||
shortcuts: fs.shortcuts,
|
||||
tags: fs.tags,
|
||||
composing: fs.composing,
|
||||
pgp: fs.pgp,
|
||||
terminal: fs.terminal,
|
||||
log: fs.log,
|
||||
_logger,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
mod deserializers {
|
||||
use serde::{de, Deserialize, Deserializer};
|
||||
|
||||
pub(in crate::conf) fn non_empty_opt_string<'de, D, T: std::convert::From<Option<String>>>(
|
||||
deserializer: D,
|
||||
) -> std::result::Result<T, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = <String>::deserialize(deserializer)?;
|
||||
if s.is_empty() {
|
||||
Ok(None.into())
|
||||
} else {
|
||||
Ok(Some(s).into())
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::conf) fn non_empty_string<'de, D, T: std::convert::From<String>>(
|
||||
deserializer: D,
|
||||
) -> std::result::Result<T, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = <String>::deserialize(deserializer)?;
|
||||
if s.is_empty() {
|
||||
Err(de::Error::custom("This field value cannot be empty."))
|
||||
} else {
|
||||
Ok(s.into())
|
||||
}
|
||||
}
|
||||
|
||||
use toml::Value;
|
||||
fn any_of<'de, D>(deserializer: D) -> std::result::Result<String, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let v: Value = Deserialize::deserialize(deserializer)?;
|
||||
if let Some(s) = v.as_str() {
|
||||
return Ok(s.to_string());
|
||||
}
|
||||
let mut ret = v.to_string();
|
||||
if (ret.starts_with('"') && ret.ends_with('"'))
|
||||
|| (ret.starts_with('\"') && ret.ends_with('\''))
|
||||
{
|
||||
ret.drain(0..1).count();
|
||||
ret.drain(ret.len() - 1..).count();
|
||||
}
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
use indexmap::IndexMap;
|
||||
pub(in crate::conf) fn extra_settings<'de, D>(
|
||||
deserializer: D,
|
||||
) -> std::result::Result<IndexMap<String, String>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
/* Why is this needed? If the user gives a configuration value such as key =
|
||||
* true, the parsing will fail since it expects string values. We
|
||||
* want to accept key = true as well as key = "true". */
|
||||
#[derive(Deserialize)]
|
||||
struct Wrapper(#[serde(deserialize_with = "any_of")] String);
|
||||
|
||||
let v = <IndexMap<String, Wrapper>>::deserialize(deserializer)?;
|
||||
Ok(v.into_iter().map(|(k, Wrapper(v))| (k, v)).collect())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_config_file(p: &Path) -> Result<()> {
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
.create_new(true)
|
||||
.open(p)
|
||||
.chain_err_summary(|| format!("Cannot create configuration file in {}", p.display()))?;
|
||||
file.write_all(FileSettings::EXAMPLE_CONFIG.as_bytes())
|
||||
.and_then(|()| file.flush())
|
||||
.chain_err_summary(|| format!("Could not write to configuration file {}", p.display()))?;
|
||||
println!("Written example configuration to {}", p.display());
|
||||
let set_permissions = |file: std::fs::File| -> Result<()> {
|
||||
let metadata = file.metadata()?;
|
||||
let mut permissions = metadata.permissions();
|
||||
|
||||
permissions.set_mode(0o600); // Read/write for owner only.
|
||||
file.set_permissions(permissions)?;
|
||||
Ok(())
|
||||
};
|
||||
if let Err(err) = set_permissions(file) {
|
||||
println!(
|
||||
"Warning: Could not set permissions of {} to 0o600: {}",
|
||||
p.display(),
|
||||
err
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct LogSettings {
|
||||
#[serde(default)]
|
||||
pub log_file: Option<PathBuf>,
|
||||
#[serde(default)]
|
||||
pub maximum_level: melib::LogLevel,
|
||||
}
|
||||
|
||||
pub use data_types::dotaddressable::*;
|
|
@ -1,353 +0,0 @@
|
|||
/*
|
||||
* meli - conf module
|
||||
*
|
||||
* Copyright 2019 Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//! Configuration for composing email.
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use melib::{conf::ActionFlag, email::HeaderName};
|
||||
use serde::{de, Deserialize, Deserializer};
|
||||
|
||||
use crate::conf::{
|
||||
default_values::{ask, false_val, none, true_val},
|
||||
deserializers::non_empty_string,
|
||||
};
|
||||
|
||||
/// Settings for writing and sending new e-mail
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ComposingSettings {
|
||||
/// Command to launch editor. Can have arguments. Draft filename is given as
|
||||
/// the last argument. If it's missing, the environment variable $EDITOR is
|
||||
/// looked up.
|
||||
#[serde(
|
||||
default = "none",
|
||||
alias = "editor-command",
|
||||
alias = "editor-cmd",
|
||||
alias = "editor_cmd"
|
||||
)]
|
||||
pub editor_command: Option<String>,
|
||||
/// Embedded editor (for terminal interfaces) instead of forking and
|
||||
/// waiting.
|
||||
#[serde(default = "false_val", alias = "embed")]
|
||||
pub embedded_pty: bool,
|
||||
/// Set "format=flowed" in plain text attachments.
|
||||
/// Default: true
|
||||
#[serde(default = "true_val", alias = "format-flowed")]
|
||||
pub format_flowed: bool,
|
||||
/// Set User-Agent
|
||||
/// Default: empty
|
||||
#[serde(default = "true_val", alias = "insert_user_agent")]
|
||||
pub insert_user_agent: bool,
|
||||
/// Set default header values for new drafts
|
||||
/// Default: empty
|
||||
#[serde(default, alias = "default-header-values")]
|
||||
pub default_header_values: IndexMap<HeaderName, String>,
|
||||
/// Wrap header preamble when editing a draft in an editor. This allows you
|
||||
/// to write non-plain text email without the preamble creating syntax
|
||||
/// errors. They are stripped when you return from the editor. The
|
||||
/// values should be a two element array of strings, a prefix and suffix.
|
||||
/// Default: None
|
||||
#[serde(default, alias = "wrap-header-preamble")]
|
||||
pub wrap_header_preamble: Option<(String, String)>,
|
||||
/// Store sent mail after successful submission. This setting is meant to be
|
||||
/// disabled for non-standard behaviour in gmail, which auto-saves sent
|
||||
/// mail on its own. Default: true
|
||||
#[serde(default = "true_val")]
|
||||
pub store_sent_mail: bool,
|
||||
/// The attribution line that appears above the quoted reply text.
|
||||
///
|
||||
/// The format specifiers for the replied address are:
|
||||
/// - `%+f` — the sender's name and email address.
|
||||
/// - `%+n` — the sender's name (or email address, if no name is included).
|
||||
/// - `%+a` — the sender's email address.
|
||||
///
|
||||
/// The format string is passed to strftime(3) with the replied envelope's
|
||||
/// date. Default: "On %a, %0e %b %Y %H:%M, %+f wrote:%n"
|
||||
#[serde(default = "none")]
|
||||
pub attribution_format_string: Option<String>,
|
||||
/// Whether the strftime call for the attribution string uses the POSIX
|
||||
/// locale instead of the user's active locale
|
||||
/// Default: true
|
||||
#[serde(default = "true_val")]
|
||||
pub attribution_use_posix_locale: bool,
|
||||
/// Forward emails as attachment? (Alternative is inline)
|
||||
/// Default: ask
|
||||
#[serde(default = "ask", alias = "forward-as-attachment")]
|
||||
pub forward_as_attachment: ActionFlag,
|
||||
/// Alternative lists of reply prefixes (etc. ["Re:", "RE:", ...]) to strip
|
||||
/// Default: `["Re:", "RE:", "Fwd:", "Fw:", "回复:", "回覆:", "SV:", "Sv:",
|
||||
/// "VS:", "Antw:", "Doorst:", "VS:", "VL:", "REF:", "TR:", "TR:", "AW:",
|
||||
/// "WG:", "ΑΠ:", "Απ:", "απ:", "ΠΡΘ:", "Πρθ:", "πρθ:", "ΣΧΕΤ:", "Σχετ:",
|
||||
/// "σχετ:", "ΠΡΘ:", "Πρθ:", "πρθ:", "Vá:", "Továbbítás:", "R:", "I:",
|
||||
/// "RIF:", "FS:", "BLS:", "TRS:", "VS:", "VB:", "RV:", "RES:", "Res",
|
||||
/// "ENC:", "Odp:", "PD:", "YNT:", "İLT:", "ATB:", "YML:"]`
|
||||
#[serde(default, alias = "reply-prefix-list-to-strip")]
|
||||
pub reply_prefix_list_to_strip: Option<Vec<String>>,
|
||||
/// The prefix to use in reply subjects. The de facto prefix is "Re:".
|
||||
#[serde(default = "res", alias = "reply-prefix")]
|
||||
pub reply_prefix: String,
|
||||
/// Custom `compose-hooks`.
|
||||
#[serde(default, alias = "custom-compose-hooks")]
|
||||
pub custom_compose_hooks: Vec<ComposeHook>,
|
||||
/// Disabled `compose-hooks`.
|
||||
#[serde(default, alias = "disabled-compose-hooks")]
|
||||
pub disabled_compose_hooks: Vec<String>,
|
||||
/// Plain text file with signature that will pre-populate an email draft.
|
||||
///
|
||||
/// Signatures must be explicitly enabled to be used, otherwise this setting
|
||||
/// will be ignored.
|
||||
///
|
||||
/// Default: `None`
|
||||
#[serde(default, alias = "signature-file")]
|
||||
pub signature_file: Option<PathBuf>,
|
||||
/// Pre-populate email drafts with signature, if any.
|
||||
///
|
||||
/// `meli` will lookup the signature value in this order:
|
||||
///
|
||||
/// 1. The `signature_file` setting.
|
||||
/// 2. `${XDG_CONFIG_DIR}/meli/<account>/signature`
|
||||
/// 3. `${XDG_CONFIG_DIR}/meli/signature`
|
||||
/// 4. `${XDG_CONFIG_DIR}/signature`
|
||||
/// 5. `${HOME}/.signature`
|
||||
/// 6. No signature otherwise.
|
||||
///
|
||||
/// Default: `false`
|
||||
#[serde(default = "false_val", alias = "use-signature")]
|
||||
pub use_signature: bool,
|
||||
/// Signature delimiter, that is, text that will be prefixed to your
|
||||
/// signature to separate it from the email body.
|
||||
///
|
||||
/// Default: `"\n\n-- \n"`
|
||||
#[serde(default, alias = "signature-delimiter")]
|
||||
pub signature_delimiter: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for ComposingSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
editor_command: None,
|
||||
embedded_pty: false,
|
||||
format_flowed: true,
|
||||
insert_user_agent: true,
|
||||
default_header_values: IndexMap::default(),
|
||||
store_sent_mail: true,
|
||||
wrap_header_preamble: None,
|
||||
attribution_format_string: None,
|
||||
attribution_use_posix_locale: true,
|
||||
forward_as_attachment: ActionFlag::Ask,
|
||||
reply_prefix_list_to_strip: None,
|
||||
reply_prefix: res(),
|
||||
custom_compose_hooks: vec![],
|
||||
disabled_compose_hooks: vec![],
|
||||
signature_file: None,
|
||||
use_signature: false,
|
||||
signature_delimiter: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn res() -> String {
|
||||
"Re:".to_string()
|
||||
}
|
||||
|
||||
macro_rules! named_unit_variant {
|
||||
($variant:ident) => {
|
||||
pub mod $variant {
|
||||
pub fn serialize<S>(serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(stringify!($variant))
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<(), D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
struct V;
|
||||
impl<'de> serde::de::Visitor<'de> for V {
|
||||
type Value = ();
|
||||
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
f.write_str(concat!("\"", stringify!($variant), "\""))
|
||||
}
|
||||
fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
|
||||
if value == stringify!($variant) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(E::invalid_value(serde::de::Unexpected::Str(value), &self))
|
||||
}
|
||||
}
|
||||
}
|
||||
deserializer.deserialize_str(V)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub mod strings {
|
||||
named_unit_variant!(server_submission);
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum SendMail {
|
||||
#[cfg(feature = "smtp")]
|
||||
Smtp(melib::smtp::SmtpServerConf),
|
||||
#[serde(with = "strings::server_submission")]
|
||||
ServerSubmission,
|
||||
ShellCommand(String),
|
||||
}
|
||||
|
||||
impl Default for SendMail {
|
||||
/// Returns the `false` POSIX shell utility, in order to return an error
|
||||
/// when called.
|
||||
fn default() -> Self {
|
||||
Self::ShellCommand("false".into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Shell command compose hooks (See
|
||||
/// [`crate::mail::compose::hooks::Hook`])
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ComposeHook {
|
||||
#[serde(deserialize_with = "non_empty_string")]
|
||||
name: String,
|
||||
#[serde(deserialize_with = "non_empty_string")]
|
||||
command: String,
|
||||
}
|
||||
|
||||
impl From<ComposeHook> for crate::mail::hooks::Hook {
|
||||
fn from(c: ComposeHook) -> Self {
|
||||
Self::new_shell_command(c.name.into(), c.command)
|
||||
}
|
||||
}
|
||||
const SENDMAIL_ERR_HELP: &str = r#"Invalid `send_mail` value.
|
||||
|
||||
Here are some valid examples:
|
||||
|
||||
Use server submission in protocols that support it (JMAP, NNTP)
|
||||
===============================================================
|
||||
|
||||
send_mail = "server_submission"
|
||||
|
||||
Using a shell script
|
||||
====================
|
||||
|
||||
send_mail = "msmtp --read-recipients --read-envelope-from"
|
||||
|
||||
Direct SMTP connection
|
||||
======================
|
||||
|
||||
[accounts.account-name]
|
||||
send_mail = { hostname = "mail.example.com", port = 587, auth = { type = "auto", password = { type = "raw", value = "hunter2" } }, security = { type = "STARTTLS" } }
|
||||
|
||||
[accounts.account-name.send_mail]
|
||||
hostname = "mail.example.com"
|
||||
port = 587
|
||||
auth = { type = "auto", password = { type = "command_eval", value = "/path/to/password_script.sh" } }
|
||||
security = { type = "TLS", danger_accept_invalid_certs = true } }
|
||||
|
||||
|
||||
`send_mail` direct SMTP connection fields:
|
||||
- hostname: text
|
||||
- port: valid port number
|
||||
- envelope_from: text (optional, default is empty),
|
||||
- auth: ...
|
||||
- security: ... (optional, default is "auto")
|
||||
- extensions: ... (optional, default is PIPELINING, CHUNKING, PRDR, 8BITMIME, BINARYMIME, SMTPUTF8, AUTH and DSN_NOTIFY)
|
||||
|
||||
Possible values for `send_mail.auth`:
|
||||
|
||||
No authentication:
|
||||
|
||||
auth = { type = "none" }
|
||||
|
||||
Regular authentication:
|
||||
Note: `require_auth` and `auth_type` are optional and can be skipped.
|
||||
|
||||
auth = { type = "auto", username = "...", password = "...", require_auth = true, auth_type = ... }
|
||||
|
||||
password can be:
|
||||
password = { type = "raw", value = "..." }
|
||||
password = { type = "command_eval", value = "/path/to/password_script.sh" }
|
||||
|
||||
XOAuth2 authentication:
|
||||
Note: `require_auth` is optional and can be skipped.
|
||||
auth = { type = "xoauth2", token_command = "...", require_auth = true }
|
||||
|
||||
Possible values for `send_mail.auth.auth_type` when `auth.type` is "auto":
|
||||
|
||||
auth_type = { plain = false, login = true }
|
||||
|
||||
Possible values for `send_mail.security`:
|
||||
Note that in all cases field `danger_accept_invalid_certs` is optional and its default value is false.
|
||||
|
||||
security = "none"
|
||||
security = { type = "auto", danger_accept_invalid_certs = false }
|
||||
security = { type = "STARTTLS", danger_accept_invalid_certs = false }
|
||||
security = { type = "TLS", danger_accept_invalid_certs = false }
|
||||
|
||||
Possible values for `send_mail.extensions` (All optional and have default values `true`:
|
||||
pipelining
|
||||
chunking
|
||||
8bitmime
|
||||
prdr
|
||||
binarymime
|
||||
smtputf8
|
||||
auth
|
||||
dsn_notify: Array of options e.g. ["FAILURE"]
|
||||
"#;
|
||||
|
||||
impl<'de> Deserialize<'de> for SendMail {
|
||||
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
use serde::de::Error;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum SendMailInner {
|
||||
#[cfg(feature = "smtp")]
|
||||
Smtp(melib::smtp::SmtpServerConf),
|
||||
#[serde(with = "strings::server_submission")]
|
||||
ServerSubmission,
|
||||
ShellCommand(String),
|
||||
}
|
||||
|
||||
match melib::serde_path_to_error::deserialize(deserializer) {
|
||||
#[cfg(feature = "smtp")]
|
||||
Ok(SendMailInner::Smtp(v)) => Ok(Self::Smtp(v)),
|
||||
Ok(SendMailInner::ServerSubmission) => Ok(Self::ServerSubmission),
|
||||
Ok(SendMailInner::ShellCommand(v)) => Ok(Self::ShellCommand(v)),
|
||||
Err(err)
|
||||
if err.inner().to_string() == D::Error::missing_field("send_mail").to_string() =>
|
||||
{
|
||||
// Surely there should be a better way to do this...
|
||||
Err(err.into_inner())
|
||||
}
|
||||
Err(_err) => Err(de::Error::custom(SENDMAIL_ERR_HELP)),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,294 +0,0 @@
|
|||
//
|
||||
// meli
|
||||
//
|
||||
// Copyright 2017- Emmanouil Pitsidianakis <manos@pitsidianak.is>
|
||||
//
|
||||
// This file is part of meli.
|
||||
//
|
||||
// meli is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// meli is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// SPDX-License-Identifier: EUPL-1.2 OR GPL-3.0-or-later
|
||||
|
||||
use crate::conf::*;
|
||||
|
||||
pub trait DotAddressable: serde::Serialize {
|
||||
fn lookup(&self, parent_field: &str, path: &[&str]) -> Result<String> {
|
||||
if !path.is_empty() {
|
||||
Err(Error::new(format!(
|
||||
"{} has no fields, it is of type {}",
|
||||
parent_field,
|
||||
std::any::type_name::<Self>()
|
||||
)))
|
||||
} else {
|
||||
Ok(toml::Value::try_from(self)
|
||||
.map_err(|err| err.to_string())?
|
||||
.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DotAddressable for bool {}
|
||||
|
||||
impl DotAddressable for String {}
|
||||
impl DotAddressable for char {}
|
||||
impl DotAddressable for data_types::IndexStyle {}
|
||||
impl DotAddressable for data_types::SearchBackend {}
|
||||
impl DotAddressable for data_types::ThreadLayout {}
|
||||
impl DotAddressable for u64 {}
|
||||
impl DotAddressable for TagHash {}
|
||||
impl DotAddressable for crate::terminal::Color {}
|
||||
impl DotAddressable for crate::terminal::Attr {}
|
||||
impl DotAddressable for crate::terminal::Key {}
|
||||
impl DotAddressable for usize {}
|
||||
impl DotAddressable for Query {}
|
||||
impl DotAddressable for melib::LogLevel {}
|
||||
impl DotAddressable for PathBuf {}
|
||||
impl DotAddressable for ToggleFlag {}
|
||||
impl DotAddressable for ActionFlag {}
|
||||
impl DotAddressable for melib::SpecialUsageMailbox {}
|
||||
impl<T: DotAddressable> DotAddressable for Option<T> {}
|
||||
impl<T: DotAddressable> DotAddressable for Vec<T> {}
|
||||
// impl<K: DotAddressable + std::cmp::Eq + std::hash::Hash, V: DotAddressable>
|
||||
// DotAddressable for HashMap<K, V>
|
||||
// {
|
||||
// }
|
||||
// impl<K: DotAddressable + std::cmp::Eq + std::hash::Hash> DotAddressable for
|
||||
// HashSet<K> {}
|
||||
impl<K: DotAddressable + std::cmp::Eq + std::hash::Hash, V: DotAddressable> DotAddressable
|
||||
for indexmap::IndexMap<K, V>
|
||||
{
|
||||
}
|
||||
impl<K: DotAddressable + std::cmp::Eq + std::hash::Hash> DotAddressable for indexmap::IndexSet<K> {}
|
||||
impl DotAddressable for (SortField, SortOrder) {}
|
||||
|
||||
impl DotAddressable for LogSettings {
|
||||
fn lookup(&self, parent_field: &str, path: &[&str]) -> Result<String> {
|
||||
match path.first() {
|
||||
Some(field) => {
|
||||
let tail = &path[1..];
|
||||
match *field {
|
||||
"log_file" => self.log_file.lookup(field, tail),
|
||||
"maximum_level" => self.maximum_level.lookup(field, tail),
|
||||
|
||||
other => Err(Error::new(format!(
|
||||
"{} has no field named {}",
|
||||
parent_field, other
|
||||
))),
|
||||
}
|
||||
}
|
||||
None => Ok(toml::Value::try_from(self)
|
||||
.map_err(|err| err.to_string())?
|
||||
.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DotAddressable for Settings {
|
||||
fn lookup(&self, parent_field: &str, path: &[&str]) -> Result<String> {
|
||||
match path.first() {
|
||||
Some(field) => {
|
||||
let tail = &path[1..];
|
||||
match *field {
|
||||
"accounts" => self.accounts.lookup(field, tail),
|
||||
"pager" => self.pager.lookup(field, tail),
|
||||
"listing" => self.listing.lookup(field, tail),
|
||||
"notifications" => self.notifications.lookup(field, tail),
|
||||
"shortcuts" => self.shortcuts.lookup(field, tail),
|
||||
"tags" => Err(Error::new("unimplemented")),
|
||||
"composing" => Err(Error::new("unimplemented")),
|
||||
"pgp" => Err(Error::new("unimplemented")),
|
||||
"terminal" => self.terminal.lookup(field, tail),
|
||||
"log" => self.log.lookup(field, tail),
|
||||
|
||||
other => Err(Error::new(format!(
|
||||
"{} has no field named {}",
|
||||
parent_field, other
|
||||
))),
|
||||
}
|
||||
}
|
||||
None => Ok(toml::Value::try_from(self)
|
||||
.map_err(|err| err.to_string())?
|
||||
.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DotAddressable for AccountConf {
|
||||
fn lookup(&self, parent_field: &str, path: &[&str]) -> Result<String> {
|
||||
match path.first() {
|
||||
Some(field) => {
|
||||
let tail = &path[1..];
|
||||
match *field {
|
||||
"account" => self.account.lookup(field, tail),
|
||||
"conf" => self.conf.lookup(field, tail),
|
||||
"conf_override" => self.conf_override.lookup(field, tail),
|
||||
"mailbox_confs" => self.mailbox_confs.lookup(field, tail),
|
||||
other => Err(Error::new(format!(
|
||||
"{} has no field named {}",
|
||||
parent_field, other
|
||||
))),
|
||||
}
|
||||
}
|
||||
None => Ok(toml::Value::try_from(self)
|
||||
.map_err(|err| err.to_string())?
|
||||
.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DotAddressable for MailUIConf {
|
||||
fn lookup(&self, parent_field: &str, path: &[&str]) -> Result<String> {
|
||||
match path.first() {
|
||||
Some(field) => {
|
||||
let _tail = &path[1..];
|
||||
match *field {
|
||||
"pager" => Err(Error::new("unimplemented")), /* self.pager.lookup(field, */
|
||||
// tail),
|
||||
"listing" => Err(Error::new("unimplemented")), /* self.listing.lookup(field, */
|
||||
// tail),
|
||||
"notifications" => Err(Error::new("unimplemented")), /* self.notifications.lookup(field, tail), */
|
||||
"shortcuts" => Err(Error::new("unimplemented")), /* self.shortcuts. */
|
||||
// lookup(field,
|
||||
// tail),
|
||||
"composing" => Err(Error::new("unimplemented")), /* self.composing. */
|
||||
// lookup(field, tail),
|
||||
"identity" => Err(Error::new("unimplemented")), /* self.identity. */
|
||||
// lookup(field,
|
||||
// tail)<String>,
|
||||
"tags" => Err(Error::new("unimplemented")), /* self.tags.lookup(field, */
|
||||
// tail),
|
||||
"themes" => Err(Error::new("unimplemented")), /* self.themes. */
|
||||
// lookup(field,
|
||||
// tail)<Themes>,
|
||||
"pgp" => Err(Error::new("unimplemented")), //self.pgp.lookup(field, tail),
|
||||
|
||||
other => Err(Error::new(format!(
|
||||
"{} has no field named {}",
|
||||
parent_field, other
|
||||
))),
|
||||
}
|
||||
}
|
||||
None => Ok(toml::Value::try_from(self)
|
||||
.map_err(|err| err.to_string())?
|
||||
.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DotAddressable for FileMailboxConf {
|
||||
fn lookup(&self, parent_field: &str, path: &[&str]) -> Result<String> {
|
||||
match path.first() {
|
||||
Some(field) => {
|
||||
let tail = &path[1..];
|
||||
match *field {
|
||||
"conf_override" => self.conf_override.lookup(field, tail),
|
||||
"mailbox_conf" => self.mailbox_conf.lookup(field, tail),
|
||||
other => Err(Error::new(format!(
|
||||
"{} has no field named {}",
|
||||
parent_field, other
|
||||
))),
|
||||
}
|
||||
}
|
||||
None => Ok(toml::Value::try_from(self)
|
||||
.map_err(|err| err.to_string())?
|
||||
.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DotAddressable for FileAccount {
|
||||
fn lookup(&self, parent_field: &str, path: &[&str]) -> Result<String> {
|
||||
match path.first() {
|
||||
Some(field) => {
|
||||
let tail = &path[1..];
|
||||
match *field {
|
||||
"root_mailbox" => self.root_mailbox.lookup(field, tail),
|
||||
"format" => self.format.lookup(field, tail),
|
||||
"identity" => self.identity.lookup(field, tail),
|
||||
"display_name" => self.display_name.lookup(field, tail),
|
||||
"read_only" => self.read_only.lookup(field, tail),
|
||||
"subscribed_mailboxes" => self.subscribed_mailboxes.lookup(field, tail),
|
||||
"mailboxes" => self.mailboxes.lookup(field, tail),
|
||||
"search_backend" => self.search_backend.lookup(field, tail),
|
||||
"manual_refresh" => self.manual_refresh.lookup(field, tail),
|
||||
"refresh_command" => self.refresh_command.lookup(field, tail),
|
||||
"conf_override" => self.conf_override.lookup(field, tail),
|
||||
"extra" => self.extra.lookup(field, tail),
|
||||
"order" => self.order.lookup(field, tail),
|
||||
other => Err(Error::new(format!(
|
||||
"{} has no field named {}",
|
||||
parent_field, other
|
||||
))),
|
||||
}
|
||||
}
|
||||
None => Ok(toml::Value::try_from(self)
|
||||
.map_err(|err| err.to_string())?
|
||||
.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DotAddressable for melib::AccountSettings {
|
||||
fn lookup(&self, parent_field: &str, path: &[&str]) -> Result<String> {
|
||||
match path.first() {
|
||||
Some(field) => {
|
||||
let tail = &path[1..];
|
||||
match *field {
|
||||
"name" => self.name.lookup(field, tail),
|
||||
"root_mailbox" => self.root_mailbox.lookup(field, tail),
|
||||
"format" => self.format.lookup(field, tail),
|
||||
"identity" => self.identity.lookup(field, tail),
|
||||
"read_only" => self.read_only.lookup(field, tail),
|
||||
"display_name" => self.display_name.lookup(field, tail),
|
||||
"subscribed_mailboxes" => self.subscribed_mailboxes.lookup(field, tail),
|
||||
"mailboxes" => self.mailboxes.lookup(field, tail),
|
||||
"manual_refresh" => self.manual_refresh.lookup(field, tail),
|
||||
"extra" => self.extra.lookup(field, tail),
|
||||
other => Err(Error::new(format!(
|
||||
"{} has no field named {}",
|
||||
parent_field, other
|
||||
))),
|
||||
}
|
||||
}
|
||||
None => Ok(toml::Value::try_from(self)
|
||||
.map_err(|err| err.to_string())?
|
||||
.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DotAddressable for melib::MailboxConf {
|
||||
fn lookup(&self, parent_field: &str, path: &[&str]) -> Result<String> {
|
||||
match path.first() {
|
||||
Some(field) => {
|
||||
let tail = &path[1..];
|
||||
match *field {
|
||||
"alias" => self.alias.lookup(field, tail),
|
||||
"autoload" => self.autoload.lookup(field, tail),
|
||||
"subscribe" => self.subscribe.lookup(field, tail),
|
||||
"ignore" => self.ignore.lookup(field, tail),
|
||||
"usage" => self.usage.lookup(field, tail),
|
||||
"extra" => self.extra.lookup(field, tail),
|
||||
other => Err(Error::new(format!(
|
||||
"{} has no field named {}",
|
||||
parent_field, other
|
||||
))),
|
||||
}
|
||||
}
|
||||
None => Ok(toml::Value::try_from(self)
|
||||
.map_err(|err| err.to_string())?
|
||||
.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,163 +0,0 @@
|
|||
//
|
||||
// meli
|
||||
//
|
||||
// Copyright 2024 Emmanouil Pitsidianakis <manos@pitsidianak.is>
|
||||
//
|
||||
// This file is part of meli.
|
||||
//
|
||||
// meli is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// meli is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// SPDX-License-Identifier: EUPL-1.2 OR GPL-3.0-or-later
|
||||
|
||||
pub mod dotaddressable;
|
||||
pub mod regex_pattern;
|
||||
|
||||
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
|
||||
pub enum IndexStyle {
|
||||
Plain,
|
||||
Threaded,
|
||||
#[default]
|
||||
Compact,
|
||||
Conversations,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for IndexStyle {
|
||||
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = <String>::deserialize(deserializer)?;
|
||||
match s.as_str() {
|
||||
plain if plain.eq_ignore_ascii_case("plain") => Ok(Self::Plain),
|
||||
threaded if threaded.eq_ignore_ascii_case("threaded") => Ok(Self::Threaded),
|
||||
compact if compact.eq_ignore_ascii_case("compact") => Ok(Self::Compact),
|
||||
conversations if conversations.eq_ignore_ascii_case("conversations") => {
|
||||
Ok(Self::Conversations)
|
||||
}
|
||||
_ => Err(de::Error::custom(
|
||||
"invalid `index_style` value, expected one of: \"plain\", \"threaded\", \
|
||||
\"compact\" or \"conversations\".",
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for IndexStyle {
|
||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match self {
|
||||
Self::Plain => serializer.serialize_str("plain"),
|
||||
Self::Threaded => serializer.serialize_str("threaded"),
|
||||
Self::Compact => serializer.serialize_str("compact"),
|
||||
Self::Conversations => serializer.serialize_str("conversations"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
pub enum SearchBackend {
|
||||
None,
|
||||
#[default]
|
||||
Auto,
|
||||
#[cfg(feature = "sqlite3")]
|
||||
Sqlite3,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for SearchBackend {
|
||||
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = <String>::deserialize(deserializer)?;
|
||||
match s.as_str() {
|
||||
#[cfg(feature = "sqlite3")]
|
||||
sqlite3
|
||||
if sqlite3.eq_ignore_ascii_case("sqlite3")
|
||||
|| sqlite3.eq_ignore_ascii_case("sqlite") =>
|
||||
{
|
||||
Ok(Self::Sqlite3)
|
||||
}
|
||||
none if none.eq_ignore_ascii_case("none")
|
||||
|| none.eq_ignore_ascii_case("nothing")
|
||||
|| none.is_empty() =>
|
||||
{
|
||||
Ok(Self::None)
|
||||
}
|
||||
auto if auto.eq_ignore_ascii_case("auto") => Ok(Self::Auto),
|
||||
_ => Err(de::Error::custom(if cfg!(feature = "sqlite3") {
|
||||
"invalid `search_backend` value, expected one of: \"sqlite3\", \"sqlite\", \
|
||||
\"none\" or \"auto\"."
|
||||
} else {
|
||||
"invalid `search_backend` value, expected one of: \"none\" or \"auto\"."
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for SearchBackend {
|
||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match self {
|
||||
#[cfg(feature = "sqlite3")]
|
||||
Self::Sqlite3 => serializer.serialize_str("sqlite3"),
|
||||
Self::None => serializer.serialize_str("none"),
|
||||
Self::Auto => serializer.serialize_str("auto"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
|
||||
pub enum ThreadLayout {
|
||||
Vertical,
|
||||
Horizontal,
|
||||
#[default]
|
||||
Auto,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ThreadLayout {
|
||||
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = <String>::deserialize(deserializer)?;
|
||||
match s.as_str() {
|
||||
vertical if vertical.eq_ignore_ascii_case("vertical") => Ok(Self::Vertical),
|
||||
horizontal if horizontal.eq_ignore_ascii_case("horizontal") => Ok(Self::Horizontal),
|
||||
auto if auto.eq_ignore_ascii_case("auto") => Ok(Self::Auto),
|
||||
_ => Err(de::Error::custom(
|
||||
"invalid `thread_layout` value, expected one of: \"vertical\", \"horizontal\" or \
|
||||
\"auto\".",
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for ThreadLayout {
|
||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match self {
|
||||
Self::Vertical => serializer.serialize_str("vertical"),
|
||||
Self::Horizontal => serializer.serialize_str("horizontal"),
|
||||
Self::Auto => serializer.serialize_str("auto"),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,215 +0,0 @@
|
|||
//
|
||||
// meli
|
||||
//
|
||||
// Copyright 2024 Emmanouil Pitsidianakis <manos@pitsidianak.is>
|
||||
//
|
||||
// This file is part of meli.
|
||||
//
|
||||
// meli is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// meli is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// SPDX-License-Identifier: EUPL-1.2 OR GPL-3.0-or-later
|
||||
|
||||
use melib::error::{Result, WrapResultIntoError};
|
||||
use serde::{Deserialize, Deserializer};
|
||||
|
||||
const fn lf_val() -> u8 {
|
||||
b'\n'
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum RegexValue {
|
||||
Default {
|
||||
pattern: regex::Regex,
|
||||
},
|
||||
Builder {
|
||||
pattern: regex::Regex,
|
||||
options: RegexOptions,
|
||||
},
|
||||
}
|
||||
|
||||
impl RegexValue {
|
||||
pub fn new_with_options(pattern: &str, o: RegexOptions) -> Result<Self> {
|
||||
let mut b = regex::RegexBuilder::new(pattern);
|
||||
b.unicode(o.unicode)
|
||||
.case_insensitive(o.case_insensitive)
|
||||
.multi_line(o.multi_line)
|
||||
.dot_matches_new_line(o.dot_matches_new_line)
|
||||
.crlf(o.crlf)
|
||||
.line_terminator(o.line_terminator)
|
||||
.swap_greed(o.swap_greed)
|
||||
.ignore_whitespace(o.ignore_whitespace)
|
||||
.octal(o.octal);
|
||||
if let Some(v) = o.size_limit {
|
||||
b.size_limit(v);
|
||||
}
|
||||
let pattern = b
|
||||
.build()
|
||||
.wrap_err(|| format!("Could not compile regular expression `{}`", pattern))?;
|
||||
Ok(Self::Builder {
|
||||
pattern,
|
||||
options: o,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn find_iter<'w, 's>(&'w self, s: &'s str) -> FindIter<'w, 's> {
|
||||
let (Self::Default { pattern } | Self::Builder { pattern, .. }) = self;
|
||||
FindIter {
|
||||
iter: pattern.find_iter(s),
|
||||
char_indices: s.char_indices(),
|
||||
char_offset: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
|
||||
pub struct RegexOptions {
|
||||
#[serde(default = "crate::conf::true_val")]
|
||||
unicode: bool,
|
||||
#[serde(default = "crate::conf::false_val")]
|
||||
case_insensitive: bool,
|
||||
#[serde(default = "crate::conf::false_val")]
|
||||
multi_line: bool,
|
||||
#[serde(default = "crate::conf::false_val")]
|
||||
dot_matches_new_line: bool,
|
||||
#[serde(default = "crate::conf::false_val")]
|
||||
crlf: bool,
|
||||
#[serde(default = "lf_val")]
|
||||
line_terminator: u8,
|
||||
#[serde(default = "crate::conf::false_val")]
|
||||
swap_greed: bool,
|
||||
#[serde(default = "crate::conf::false_val")]
|
||||
ignore_whitespace: bool,
|
||||
#[serde(default = "crate::conf::false_val")]
|
||||
octal: bool,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
size_limit: Option<usize>,
|
||||
}
|
||||
|
||||
impl Default for RegexOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
unicode: true,
|
||||
case_insensitive: false,
|
||||
multi_line: false,
|
||||
dot_matches_new_line: false,
|
||||
crlf: false,
|
||||
line_terminator: b'\n',
|
||||
swap_greed: false,
|
||||
ignore_whitespace: false,
|
||||
octal: false,
|
||||
size_limit: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for RegexValue {
|
||||
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
// [ref:FIXME]: clippy false positive, remove when resolved.
|
||||
#![allow(clippy::collection_is_never_read)]
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum Inner<'a> {
|
||||
Default {
|
||||
pattern: &'a str,
|
||||
},
|
||||
Builder {
|
||||
pattern: &'a str,
|
||||
#[serde(flatten)]
|
||||
o: RegexOptions,
|
||||
},
|
||||
}
|
||||
let s = <Inner>::deserialize(deserializer);
|
||||
Ok(
|
||||
match s.map_err(|err| {
|
||||
serde::de::Error::custom(format!(
|
||||
r#"expected one of "true", "false", "ask", found `{}`"#,
|
||||
err
|
||||
))
|
||||
})? {
|
||||
Inner::Default { pattern } => Self::Default {
|
||||
pattern: regex::Regex::new(pattern).map_err(|err| {
|
||||
serde::de::Error::custom(format!(
|
||||
"Could not compile regular expression `{}`: {}",
|
||||
pattern, err
|
||||
))
|
||||
})?,
|
||||
},
|
||||
Inner::Builder { pattern, o } => {
|
||||
let mut b = regex::RegexBuilder::new(pattern);
|
||||
b.unicode(o.unicode)
|
||||
.case_insensitive(o.case_insensitive)
|
||||
.multi_line(o.multi_line)
|
||||
.dot_matches_new_line(o.dot_matches_new_line)
|
||||
.crlf(o.crlf)
|
||||
.line_terminator(o.line_terminator)
|
||||
.swap_greed(o.swap_greed)
|
||||
.ignore_whitespace(o.ignore_whitespace)
|
||||
.octal(o.octal);
|
||||
if let Some(v) = o.size_limit {
|
||||
b.size_limit(v);
|
||||
}
|
||||
let pattern = b.build().map_err(|err| {
|
||||
serde::de::Error::custom(format!(
|
||||
"Could not compile regular expression `{}`: {}",
|
||||
pattern, err
|
||||
))
|
||||
})?;
|
||||
Self::Builder {
|
||||
pattern,
|
||||
options: o,
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FindIter<'r, 's> {
|
||||
iter: regex::Matches<'r, 's>,
|
||||
char_indices: std::str::CharIndices<'s>,
|
||||
char_offset: usize,
|
||||
}
|
||||
|
||||
impl Iterator for FindIter<'_, '_> {
|
||||
type Item = (usize, usize);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let next_byte_offset = self.iter.next()?;
|
||||
|
||||
let mut next_char_index = self.char_indices.next()?;
|
||||
|
||||
while next_byte_offset.start() < next_char_index.0 {
|
||||
self.char_offset += 1;
|
||||
next_char_index = self.char_indices.next()?;
|
||||
}
|
||||
let start = self.char_offset;
|
||||
|
||||
while next_byte_offset.end()
|
||||
> self
|
||||
.char_indices
|
||||
.next()
|
||||
.map(|(v, _)| v)
|
||||
.unwrap_or_else(|| next_byte_offset.end())
|
||||
{
|
||||
self.char_offset += 1;
|
||||
}
|
||||
let end = self.char_offset + 1;
|
||||
|
||||
Some((start, end))
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
//
|
||||
// meli
|
||||
//
|
||||
// Copyright 2017 Emmanouil Pitsidianakis <manos@pitsidianak.is>
|
||||
//
|
||||
// This file is part of meli.
|
||||
//
|
||||
// meli is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// meli is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// SPDX-License-Identifier: EUPL-1.2 OR GPL-3.0-or-later
|
||||
|
||||
//! default value functions for deserializing
|
||||
|
||||
pub fn false_val<T: From<bool>>() -> T {
|
||||
false.into()
|
||||
}
|
||||
|
||||
pub fn true_val<T: From<bool>>() -> T {
|
||||
true.into()
|
||||
}
|
||||
|
||||
pub fn zero_val<T: From<usize>>() -> T {
|
||||
0.into()
|
||||
}
|
||||
|
||||
pub fn eighty_val<T: From<usize>>() -> T {
|
||||
80.into()
|
||||
}
|
||||
|
||||
pub fn none<T>() -> Option<T> {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn internal_value_false<T: From<melib::conf::ToggleFlag>>() -> T {
|
||||
melib::conf::ToggleFlag::InternalVal(false).into()
|
||||
}
|
||||
|
||||
pub fn internal_value_true<T: From<melib::conf::ToggleFlag>>() -> T {
|
||||
melib::conf::ToggleFlag::InternalVal(true).into()
|
||||
}
|
||||
|
||||
pub fn action_internal_value_false<T: From<melib::ActionFlag>>() -> T {
|
||||
melib::conf::ActionFlag::InternalVal(false).into()
|
||||
}
|
||||
|
||||
//pub fn action_internal_value_true<
|
||||
// T: From<melib::conf::ActionFlag>,
|
||||
//>() -> T {
|
||||
// melib::conf::ActionFlag::InternalVal(true).into()
|
||||
//}
|
||||
|
||||
pub fn ask<T: From<melib::conf::ActionFlag>>() -> T {
|
||||
melib::conf::ActionFlag::Ask.into()
|
||||
}
|
|
@ -1,281 +0,0 @@
|
|||
//
|
||||
// meli
|
||||
//
|
||||
// Copyright 2017 Emmanouil Pitsidianakis <manos@pitsidianak.is>
|
||||
//
|
||||
// This file is part of meli.
|
||||
//
|
||||
// meli is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// meli is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// SPDX-License-Identifier: EUPL-1.2 OR GPL-3.0-or-later
|
||||
|
||||
//! Preprocess configuration files by unfolding `include` macros.
|
||||
|
||||
use std::{
|
||||
io::{self, BufRead, Read, Write},
|
||||
path::{Path, PathBuf},
|
||||
process::{Command, Stdio},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use melib::{
|
||||
error::{Error, ErrorKind, Result, ResultIntoError, WrapResultIntoError},
|
||||
utils::parsec::*,
|
||||
ShellExpandTrait,
|
||||
};
|
||||
|
||||
/// Try to parse line into a path to be included.
|
||||
pub fn include_directive<'a>() -> impl Parser<'a, Option<&'a str>> {
|
||||
move |input: &'a str| {
|
||||
enum State {
|
||||
Start,
|
||||
Path,
|
||||
}
|
||||
use State::*;
|
||||
let mut state = State::Start;
|
||||
|
||||
let mut i = 0;
|
||||
while i < input.len() {
|
||||
match (&state, input.as_bytes()[i]) {
|
||||
(Start, b'#') => {
|
||||
return Ok(("", None));
|
||||
}
|
||||
(Start, b) if (b as char).is_whitespace() => { /* consume */ }
|
||||
(Start, _) if input.as_bytes()[i..].starts_with(b"include(") => {
|
||||
i += "include(".len();
|
||||
state = Path;
|
||||
continue;
|
||||
}
|
||||
(Start, _) => {
|
||||
return Ok(("", None));
|
||||
}
|
||||
(Path, b'"') | (Path, b'\'') | (Path, b'`') => {
|
||||
let mut end = i + 1;
|
||||
while end < input.len() && input.as_bytes()[end] != input.as_bytes()[i] {
|
||||
end += 1;
|
||||
}
|
||||
if end == input.len() {
|
||||
return Err(input);
|
||||
}
|
||||
let ret = &input[i + 1..end];
|
||||
end += 1;
|
||||
if end < input.len() && input.as_bytes()[end] != b')' {
|
||||
/* Nothing else allowed in line */
|
||||
return Err(input);
|
||||
}
|
||||
end += 1;
|
||||
while end < input.len() {
|
||||
if !(input.as_bytes()[end] as char).is_whitespace() {
|
||||
/* Nothing else allowed in line */
|
||||
return Err(input);
|
||||
}
|
||||
end += 1;
|
||||
}
|
||||
return Ok(("", Some(ret)));
|
||||
}
|
||||
(Path, _) => return Err(input),
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
Ok(("", None))
|
||||
}
|
||||
}
|
||||
|
||||
/// Expands `include` macros in path.
|
||||
fn pp_helper(path: &Path, level: u8) -> Result<String> {
|
||||
if level > 7 {
|
||||
return Err(Error::new(format!(
|
||||
"Maximum recursion limit reached while unfolding include directives in {}. Have you \
|
||||
included a config file within itself?",
|
||||
path.display()
|
||||
))
|
||||
.set_kind(ErrorKind::ValueError));
|
||||
}
|
||||
let mut contents = String::new();
|
||||
let mut file = std::fs::File::open(path)?;
|
||||
file.read_to_string(&mut contents)?;
|
||||
let mut ret = String::with_capacity(contents.len());
|
||||
|
||||
for (i, l) in contents.lines().enumerate() {
|
||||
if let (_, Some(sub_path)) = include_directive().parse(l).map_err(|l| {
|
||||
Error::new(format!(
|
||||
"Malformed include directive in line {} of file {}: {}\nConfiguration uses the \
|
||||
standard m4 macro include(\"filename\").",
|
||||
i,
|
||||
path.display(),
|
||||
l
|
||||
))
|
||||
.set_kind(ErrorKind::ValueError)
|
||||
})? {
|
||||
let mut p = Path::new(sub_path).expand();
|
||||
if p.is_relative() {
|
||||
/* We checked that path is ok above so we can do unwrap here */
|
||||
let prefix = path.parent().unwrap();
|
||||
p = prefix.join(p)
|
||||
}
|
||||
|
||||
ret.push_str(&pp_helper(&p, level + 1).chain_err_related_path(&p)?);
|
||||
} else {
|
||||
ret.push_str(l);
|
||||
ret.push('\n');
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
fn pp_inner(path: &Path) -> Result<String> {
|
||||
let p_buf: PathBuf = if path.is_relative() {
|
||||
path.expand().canonicalize()?
|
||||
} else {
|
||||
path.expand()
|
||||
};
|
||||
|
||||
let mut ret = expand_config(&p_buf)?;
|
||||
if let Ok(xdg_dirs) = xdg::BaseDirectories::with_prefix("meli") {
|
||||
for theme_mailbox in xdg_dirs.find_config_files("themes") {
|
||||
let read_dir =
|
||||
std::fs::read_dir(&theme_mailbox).chain_err_related_path(&theme_mailbox)?;
|
||||
for theme in read_dir {
|
||||
let theme_path = theme?.path();
|
||||
if let Some(extension) = theme_path.extension() {
|
||||
if extension == "toml" {
|
||||
ret.push_str(
|
||||
&pp_helper(&theme_path, 0).chain_err_related_path(&theme_path)?,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
/// Expands `include` macros in configuration file and other configuration
|
||||
/// files (eg. themes) in the filesystem.
|
||||
pub fn pp(path: &Path) -> Result<String> {
|
||||
pp_inner(path)
|
||||
.wrap_err(|| "Could not preprocess configuration file")
|
||||
.chain_err_related_path(path)
|
||||
.chain_err_kind(ErrorKind::Configuration)
|
||||
}
|
||||
|
||||
pub fn expand_config(conf_path: &Path) -> Result<String> {
|
||||
fn inner(conf_path: &Path) -> Result<String> {
|
||||
let _paths = get_included_configs(conf_path)?;
|
||||
const M4_PREAMBLE: &str = r#"define(`builtin_include', defn(`include'))dnl
|
||||
define(`include', `builtin_include(substr($1,1,decr(decr(len($1)))))dnl')dnl
|
||||
"#;
|
||||
let mut contents = String::new();
|
||||
contents.clear();
|
||||
let mut file = std::fs::File::open(conf_path)?;
|
||||
file.read_to_string(&mut contents)?;
|
||||
|
||||
let mut handle = Command::new("m4")
|
||||
.current_dir(conf_path.parent().unwrap_or_else(|| Path::new("/")))
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?;
|
||||
let mut stdin = handle.stdin.take().unwrap();
|
||||
stdin.write_all(M4_PREAMBLE.as_bytes())?;
|
||||
stdin.write_all(contents.as_bytes())?;
|
||||
drop(stdin);
|
||||
let stdout = handle.wait_with_output()?.stdout;
|
||||
Ok(String::from_utf8_lossy(&stdout).to_string())
|
||||
}
|
||||
|
||||
inner(conf_path).chain_err_related_path(conf_path)
|
||||
}
|
||||
|
||||
pub fn get_included_configs(conf_path: &Path) -> Result<Vec<PathBuf>> {
|
||||
const M4_PREAMBLE: &str = r#"divert(-1)dnl
|
||||
define(`include', `divert(0)$1
|
||||
divert(-1)
|
||||
')dnl
|
||||
changequote(`"', `"')dnl
|
||||
"#;
|
||||
let mut ret = vec![];
|
||||
let prefix = conf_path.parent().unwrap().to_path_buf();
|
||||
let mut stack = vec![(None::<PathBuf>, conf_path.to_path_buf())];
|
||||
let mut contents = String::new();
|
||||
while let Some((parent, p)) = stack.pop() {
|
||||
if !p.exists() || p.is_dir() {
|
||||
return Err(Error::new(format!(
|
||||
"Path {}{included}{in_parent} {msg}.",
|
||||
p.display(),
|
||||
included = if parent.is_some() {
|
||||
" which is included in "
|
||||
} else {
|
||||
""
|
||||
},
|
||||
in_parent = if let Some(parent) = parent {
|
||||
std::borrow::Cow::Owned(parent.display().to_string())
|
||||
} else {
|
||||
std::borrow::Cow::Borrowed("")
|
||||
},
|
||||
msg = if !p.exists() {
|
||||
"does not exist"
|
||||
} else {
|
||||
"is a directory, not a text file"
|
||||
}
|
||||
))
|
||||
.set_kind(ErrorKind::ValueError));
|
||||
}
|
||||
contents.clear();
|
||||
let mut file = std::fs::File::open(&p).chain_err_related_path(&p)?;
|
||||
file.read_to_string(&mut contents)
|
||||
.chain_err_related_path(&p)?;
|
||||
|
||||
let mut handle = match Command::new("m4")
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
{
|
||||
Ok(handle) => handle,
|
||||
Err(err) => match err.kind() {
|
||||
io::ErrorKind::NotFound => {
|
||||
return Err(Error::new(
|
||||
"`m4` executable not found in PATH. Please provide an m4 binary.",
|
||||
)
|
||||
.set_kind(ErrorKind::Platform))
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::new("Could not process configuration with `m4`")
|
||||
.set_source(Some(Arc::new(err)))
|
||||
.set_kind(ErrorKind::Platform))
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let mut stdin = handle.stdin.take().unwrap();
|
||||
stdin.write_all(M4_PREAMBLE.as_bytes())?;
|
||||
stdin.write_all(contents.as_bytes())?;
|
||||
drop(stdin);
|
||||
let stdout = handle.wait_with_output()?.stdout.clone();
|
||||
for subpath in stdout.lines() {
|
||||
let subpath = subpath?;
|
||||
let path = &Path::new(&subpath);
|
||||
if path.is_absolute() {
|
||||
stack.push((Some(p.to_path_buf()), path.to_path_buf()));
|
||||
} else {
|
||||
stack.push((Some(p.to_path_buf()), prefix.join(path)));
|
||||
}
|
||||
}
|
||||
ret.push(p.to_path_buf());
|
||||
}
|
||||
|
||||
Ok(ret)
|
||||
}
|
|
@ -1,215 +0,0 @@
|
|||
/*
|
||||
* meli - configuration module.
|
||||
*
|
||||
* Copyright 2019 Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//! Settings for terminal display
|
||||
|
||||
use melib::{Error, Result, ToggleFlag};
|
||||
|
||||
use super::{deserializers::non_empty_opt_string, DotAddressable, Themes};
|
||||
|
||||
/// Settings for terminal display
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
pub struct TerminalSettings {
|
||||
/// light, dark
|
||||
pub theme: String,
|
||||
pub themes: Themes,
|
||||
pub ascii_drawing: bool,
|
||||
pub use_color: ToggleFlag,
|
||||
/// Try forcing text presentations of symbols and emoji as much as possible.
|
||||
/// Might not work on all non-text symbols and is experimental.
|
||||
pub force_text_presentation: ToggleFlag,
|
||||
/// Use mouse events. This will disable text selection, but you will be able
|
||||
/// to resize some widgets.
|
||||
/// Default: False
|
||||
pub use_mouse: ToggleFlag,
|
||||
/// String to show in status bar if mouse is active.
|
||||
/// Default: "🖱️ "
|
||||
#[serde(deserialize_with = "non_empty_opt_string")]
|
||||
pub mouse_flag: Option<String>,
|
||||
#[serde(deserialize_with = "non_empty_opt_string")]
|
||||
pub window_title: Option<String>,
|
||||
#[serde(deserialize_with = "non_empty_opt_string")]
|
||||
pub file_picker_command: Option<String>,
|
||||
/// Choose between 30-something built in sequences (integers between 0-30)
|
||||
/// or define your own list of strings for the progress spinner
|
||||
/// animation. Default: 0
|
||||
#[serde(default)]
|
||||
pub progress_spinner_sequence: Option<ProgressSpinnerSequence>,
|
||||
}
|
||||
|
||||
impl Default for TerminalSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
theme: "dark".to_string(),
|
||||
themes: Themes::default(),
|
||||
ascii_drawing: false,
|
||||
force_text_presentation: ToggleFlag::InternalVal(false),
|
||||
use_color: ToggleFlag::InternalVal(true),
|
||||
use_mouse: ToggleFlag::InternalVal(false),
|
||||
mouse_flag: Some("🖱️ ".to_string()),
|
||||
window_title: Some("meli".to_string()),
|
||||
file_picker_command: None,
|
||||
progress_spinner_sequence: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TerminalSettings {
|
||||
#[inline]
|
||||
pub fn use_color(&self) -> bool {
|
||||
// Don't use color if
|
||||
// - Either NO_COLOR is set and user hasn't explicitly set use_colors or
|
||||
// - User has explicitly set use_colors to false
|
||||
!((std::env::var_os("NO_COLOR").is_some()
|
||||
&& (self.use_color.is_false() || self.use_color.is_internal()))
|
||||
|| (self.use_color.is_false() && !self.use_color.is_internal()))
|
||||
}
|
||||
|
||||
pub fn use_text_presentation(&self) -> bool {
|
||||
self.force_text_presentation.is_true() || !self.use_color()
|
||||
}
|
||||
}
|
||||
|
||||
impl DotAddressable for TerminalSettings {
|
||||
fn lookup(&self, parent_field: &str, path: &[&str]) -> Result<String> {
|
||||
match path.first() {
|
||||
Some(field) => {
|
||||
let tail = &path[1..];
|
||||
match *field {
|
||||
"theme" => self.theme.lookup(field, tail),
|
||||
"themes" => Err(Error::new("unimplemented")),
|
||||
"ascii_drawing" => self.ascii_drawing.lookup(field, tail),
|
||||
"force_text_presentation" => self.force_text_presentation.lookup(field, tail),
|
||||
"use_color" => self.use_color.lookup(field, tail),
|
||||
"use_mouse" => self.use_mouse.lookup(field, tail),
|
||||
"mouse_flag" => self.mouse_flag.lookup(field, tail),
|
||||
"window_title" => self.window_title.lookup(field, tail),
|
||||
"file_picker_command" => self.file_picker_command.lookup(field, tail),
|
||||
"progress_spinner_sequence" => {
|
||||
self.progress_spinner_sequence.lookup(field, tail)
|
||||
}
|
||||
other => Err(Error::new(format!(
|
||||
"{} has no field named {}",
|
||||
parent_field, other
|
||||
))),
|
||||
}
|
||||
}
|
||||
None => Ok(toml::Value::try_from(self)
|
||||
.map_err(|err| err.to_string())?
|
||||
.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum ProgressSpinnerSequence {
|
||||
Integer(usize),
|
||||
Custom {
|
||||
frames: Vec<String>,
|
||||
interval_ms: u64,
|
||||
},
|
||||
}
|
||||
|
||||
impl ProgressSpinnerSequence {
|
||||
pub const fn interval_ms(&self) -> u64 {
|
||||
match self {
|
||||
Self::Integer(_) => interval_ms_val(),
|
||||
Self::Custom {
|
||||
frames: _,
|
||||
interval_ms,
|
||||
} => *interval_ms,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
const fn interval_ms_val() -> u64 {
|
||||
crate::utilities::ProgressSpinner::INTERVAL_MS
|
||||
}
|
||||
|
||||
impl DotAddressable for ProgressSpinnerSequence {}
|
||||
|
||||
impl<'de> Deserialize<'de> for ProgressSpinnerSequence {
|
||||
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(untagged)]
|
||||
enum Inner {
|
||||
Integer(usize),
|
||||
Frames(Vec<String>),
|
||||
Custom {
|
||||
frames: Vec<String>,
|
||||
#[serde(default = "interval_ms_val")]
|
||||
interval_ms: u64,
|
||||
},
|
||||
}
|
||||
let s = <Inner>::deserialize(deserializer)?;
|
||||
match s {
|
||||
Inner::Integer(i) => Ok(Self::Integer(i)),
|
||||
Inner::Frames(frames) => Ok(Self::Custom {
|
||||
frames,
|
||||
interval_ms: interval_ms_val(),
|
||||
}),
|
||||
Inner::Custom {
|
||||
frames,
|
||||
interval_ms,
|
||||
} => Ok(Self::Custom {
|
||||
frames,
|
||||
interval_ms,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for ProgressSpinnerSequence {
|
||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match self {
|
||||
Self::Integer(i) => serializer.serialize_i64(*i as i64),
|
||||
Self::Custom {
|
||||
frames,
|
||||
interval_ms,
|
||||
} => {
|
||||
if *interval_ms == interval_ms_val() {
|
||||
use serde::ser::SerializeSeq;
|
||||
let mut seq = serializer.serialize_seq(Some(frames.len()))?;
|
||||
for element in frames {
|
||||
seq.serialize_element(element)?;
|
||||
}
|
||||
seq.end()
|
||||
} else {
|
||||
use serde::ser::SerializeMap;
|
||||
|
||||
let mut map = serializer.serialize_map(Some(2))?;
|
||||
map.serialize_entry("frames", frames)?;
|
||||
map.serialize_entry("interval_ms", interval_ms)?;
|
||||
map.end()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|