Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: support Lua 5.4 for configuration files
Browse files Browse the repository at this point in the history
This commit enables the use of Lua (version 5.4) for configuration
files, providing greater flexibility and customization options for
users.
michaeladler committed Oct 2, 2024
1 parent fe73b11 commit 98372ca
Showing 12 changed files with 309 additions and 94 deletions.
4 changes: 4 additions & 0 deletions .ci/install-deps.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/sh
set -eux
apt-get update -q
apt-get install -y libnotmuch-dev liblua5.4-dev
6 changes: 4 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@ jobs:
fetch-depth: 0 # needed to embed version information
- uses: actions-rust-lang/setup-rust-toolchain@v1
- name: Install deps
run: sudo apt-get update -q && sudo apt-get install -y libnotmuch-dev
run: sudo .ci/install-deps.sh
- run: cargo build --release
- run: cargo test --all-features
- uses: actions/upload-artifact@v4
@@ -38,6 +38,8 @@ jobs:
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
components: clippy
- name: Install deps
run: sudo .ci/install-deps.sh
- run: cargo clippy

coverage:
@@ -50,7 +52,7 @@ jobs:
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov
- name: Install deps
run: sudo apt-get update -q && sudo apt-get install -y libnotmuch-dev
run: sudo .ci/install-deps.sh
- name: Generate code coverage
run: cargo llvm-cov --all-features --workspace --lcov --output-path lcov.info
- name: Upload coverage to Codecov
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@ jobs:
- uses: actions-rust-lang/setup-rust-toolchain@v1

- name: Install build deps
run: sudo apt-get update -q && sudo apt-get install -y libnotmuch-dev
run: sudo .ci/install-deps.sh

- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
130 changes: 130 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -33,6 +33,7 @@ shellexpand = "3.0.0"
clap = { version = "4.5.18", features = [ "derive", "cargo" ] }
git-version = "0.3.9"
konst = "0.3"
mlua = { version = "0.9.9", features = ["lua54", "serialize"] }

[build-dependencies]
clap = { version = "4.5.18", features = [ "derive", "cargo" ] }
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -37,10 +37,11 @@ $ nix-env -iA notmuch-mailmover

### Building from Source

Otherwise, you have to build from source. You need the following build dependencies:
Otherwise, you have to build from source. You need the following build dependencies (see [install-deps.sh](.ci/install-deps.sh)):

- Rust
- libnotmuch-dev
- liblua5.4-dev

Then run

@@ -57,6 +58,8 @@ You can also invoke `notmuch-mailmover` directly, but don't forget to run `notmu

Running `notmuch-mailmover` for the first time will create `$XDG_CONFIG_HOME/notmuch-mailmover/config.yaml`.
Then edit the file as you like, see below for an example.
Alternatively, it's possible to write the configuration in Lua 5.4 by creating `config.lua` instead.
See the provided [config.lua](example/config.lua) for an example.

The configuration is largely self-explanatory, except perhaps for the choice of the `rule_match_mode`.
You need to decide whether you want your rules to be pairwise distinct (meaning the queries must not overlap) or ambiguous (where the first or last matching rule wins).
54 changes: 54 additions & 0 deletions example/config.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
-- anything goes here, as long as it returns a table (which can be parsed into the corresponding `config::Config` struct)

--- @enum config.match_mode
--- Match modes for rules.
local match_modes = {
ALL = "all",
FIRST = "first",
UNIQUE = "unique",
}

--- Configuration for notmuch-mailmover.
--
--- @class config
--- @field maildir string Path to the maildir
--- @field notmuch_config string Path to the notmuch configuration
--- @field rename boolean Rename the files when moving
--- @field max_age_days number Maximum age (days) of the messages to be procssed
--- @field rule_match_mode config.match_mode Match mode for rules
--- @field rules rule[] List of rules
---
--- @class rule
--- @field folder string Folder to move the messages to
--- @field query string Notmuch query to match the messages
local config = {
maildir = os.getenv("HOME") .. "/mail",
notmuch_config = "~/.config/notmuch/notmuchrc",
rename = false,
max_age_days = 60,
rule_match_mode = match_modes.FIRST,
rules = {
{
folder = "Trash",
query = "tag:trash",
},
{
folder = "Spam",
query = "tag:spam",
},
{
folder = "Sent",
query = "tag:sent",
},
{
folder = "Archive",
query = "tag:archive",
},
{
folder = "INBOX",
query = "tag:inbox",
},
},
}

return config
18 changes: 9 additions & 9 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

123 changes: 62 additions & 61 deletions flake.nix
Original file line number Diff line number Diff line change
@@ -5,89 +5,90 @@
flake-utils.url = "github:numtide/flake-utils";
};

outputs = { self, nixpkgs, crane, flake-utils, ... }:

flake-utils.lib.eachDefaultSystem (system:
outputs =
{ self
, nixpkgs
, crane
, flake-utils
, ...
}:
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = nixpkgs.legacyPackages.${system};

inherit (pkgs) lib;

craneLib = crane.mkLib pkgs;

src = ./.;
src = craneLib.cleanCargoSource ./.;

# Build *just* the cargo dependencies, so we can reuse
# all of that work (e.g. via cachix) when running in CI
cargoArtifacts = craneLib.buildDepsOnly {
# Common arguments can be set here to avoid repeating them later
commonArgs = {
inherit src;
strictDeps = true;

nativeBuildInputs = [
pkgs.pkg-config
];

buildInputs = [
pkgs.notmuch
pkgs.lua5_4
];

};

# Build *just* the cargo dependencies, so we can reuse
# all of that work (e.g. via cachix) when running in CI
cargoArtifacts = craneLib.buildDepsOnly commonArgs;

# Build the actual crate itself, reusing the dependency
# artifacts from above.
notmuch-mailmover = craneLib.buildPackage {
inherit cargoArtifacts src;
my-crate = craneLib.buildPackage (
commonArgs
// {
inherit cargoArtifacts;

nativeBuildInputs = with pkgs; [
installShellFiles
];

buildInputs = with pkgs; [ notmuch ];
nativeBuildInputs = (commonArgs.nativeBuildInputs or [ ]) ++ [
pkgs.installShellFiles
];

postInstall = ''
installManPage share/notmuch-mailmover.1.gz
installShellCompletion --cmd notmuch-mailmover \
--bash share/notmuch-mailmover.bash \
--fish share/notmuch-mailmover.fish \
--zsh share/_notmuch-mailmover
'';
};
postInstall = ''
installManPage share/notmuch-mailmover.1.gz
installShellCompletion --cmd notmuch-mailmover \
--bash share/notmuch-mailmover.bash \
--fish share/notmuch-mailmover.fish \
--zsh share/_notmuch-mailmover
'';
}
);
in
{
checks = {
# Build the crate as part of `nix flake check` for convenience
inherit notmuch-mailmover;

# Run clippy (and deny all warnings) on the crate source,
# again, resuing the dependency artifacts from above.
#
# Note that this is done as a separate derivation so that
# we can block the CI if there are issues here, but not
# prevent downstream consumers from building our crate by itself.
notmuch-mailmover-clippy = craneLib.cargoClippy {
inherit cargoArtifacts src;
cargoClippyExtraArgs = "-- --deny warnings";

};

# Check formatting
notmuch-mailmover-fmt = craneLib.cargoFmt {
inherit src;
};

# Check code coverage (note: this will not upload coverage anywhere)
notmuch-mailmover-coverage = craneLib.cargoTarpaulin {
inherit cargoArtifacts src;

buildInputs = [ pkgs.notmuch ];
};
inherit my-crate;
};

packages.default = notmuch-mailmover;

apps.default = flake-utils.lib.mkApp {
drv = notmuch-mailmover;
packages = {
default = my-crate;
inherit my-crate;
};

devShells = {
default = pkgs.mkShell {
inputsFrom = builtins.attrValues self.checks;
devShells.default = craneLib.devShell {
# Inherit inputs from checks.
checks = self.checks.${system};

# Extra inputs can be added here
nativeBuildInputs = with pkgs; [
cargo
rustc
];
# Additional dev-shell environment variables can be set directly
# MY_CUSTOM_DEVELOPMENT_VAR = "something else";

};
# Extra inputs can be added here; cargo and rustc are provided by default.
packages = [
pkgs.notmuch
pkgs.lua5_4
pkgs.nodePackages.markdown-link-check
];
};
});
}
);
}
4 changes: 3 additions & 1 deletion shell.nix
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
{ pkgs ? import <nixpkgs> { } }:
{
pkgs ? import <nixpkgs> { },
}:

with pkgs;

51 changes: 33 additions & 18 deletions src/lib/config.rs
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ use std::{io::BufReader, path::PathBuf};
use anyhow::{anyhow, Result};
use directories::BaseDirs;
use log::debug;
use mlua::{Lua, LuaSerdeExt};
use serde::{Deserialize, Deserializer, Serialize};

#[derive(Debug, Serialize, Deserialize, Clone)]
@@ -98,36 +99,50 @@ pub fn load_config(fname: &Option<PathBuf>) -> Result<Config> {
let bd = BaseDirs::new().unwrap();
let basedir = bd.config_dir().join("notmuch-mailmover");
let default_cfg_path = basedir.join("config.yaml");
let default_lua_path = basedir.join("config.lua");

let fname: &PathBuf = match fname {
Some(fname) => fname,
None => {
if !default_cfg_path.exists() {
None => match (default_cfg_path.exists(), default_lua_path.exists()) {
(true, true) => {
return Err(anyhow!(
"Both {} and {} exist, please remove one",
default_cfg_path.to_string_lossy(),
default_lua_path.to_string_lossy(),
));
}
(true, false) => &default_cfg_path,
(false, true) => &default_lua_path,
(false, false) => {
fs::create_dir_all(basedir)?;
let f = File::create(&default_cfg_path)?;
let default_cfg: Config = Default::default();
serde_yaml::to_writer(f, &default_cfg)?;
&default_cfg_path
}
&default_cfg_path
}
},
};

debug!("loading config {:?}", fname);
match File::open(fname) {
Ok(f) => {
let reader = BufReader::new(f);
let mut cfg: Config = serde_yaml::from_reader(reader)?;

let db_path = shellexpand::full(&cfg.maildir)?;
cfg.maildir = db_path.to_string();
let mut cfg = if fname.extension().map_or(false, |ext| ext == "lua") {
let lua = Lua::new();
let val = lua.load(fname.clone()).eval()?;
let cfg: Config = lua.from_value(val)?;
cfg
} else {
let f = File::open(fname)?;
let reader = BufReader::new(f);
let cfg: Config = serde_yaml::from_reader(reader)?;
cfg
};

if let Some(cfg_path) = cfg.notmuch_config {
let path = shellexpand::full(&cfg_path)?;
cfg.notmuch_config = Some(path.to_string());
}
let db_path = shellexpand::full(&cfg.maildir)?;
cfg.maildir = db_path.to_string();

Ok(cfg)
}
Err(e) => Err(anyhow!("Failed to open {}: {}", fname.to_string_lossy(), e)),
if let Some(cfg_path) = cfg.notmuch_config {
let path = shellexpand::full(&cfg_path)?;
cfg.notmuch_config = Some(path.to_string());
}

Ok(cfg)
}
5 changes: 4 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
@@ -13,14 +13,17 @@ fn main() -> Result<()> {
env_logger::try_init_from_env(env)?;

let cfg = config::load_config(&opts.config)?;
debug!("successfully loaded {:?}", cfg);
let db_path: Option<String> = None;
debug!("loaded {:?}", cfg);

debug!("opening notmuch db");
let db = notmuch::Database::open_with_config(
db_path,
notmuch::DatabaseMode::ReadOnly,
cfg.notmuch_config.as_ref(),
None,
)?;
debug!("successfully opened notmuch db");

let start = Instant::now();

0 comments on commit 98372ca

Please sign in to comment.