home-manager/modules/programs/neomutt.nix

381 lines
11 KiB
Nix
Raw Normal View History

2020-01-21 23:52:01 +00:00
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.programs.neomutt;
2020-02-01 23:39:17 +00:00
neomuttAccounts =
filter (a: a.neomutt.enable) (attrValues config.accounts.email.accounts);
2020-01-21 23:52:01 +00:00
sidebarModule = types.submodule {
options = {
enable = mkEnableOption "sidebar support";
width = mkOption {
type = types.int;
default = 22;
description = "Width of the sidebar";
};
shortPath = mkOption {
type = types.bool;
default = true;
description = ''
By default sidebar shows the full path of the mailbox, but
with this enabled only the relative name is shown.
'';
};
format = mkOption {
type = types.str;
default = "%D%?F? [%F]?%* %?N?%N/?%S";
2020-01-21 23:52:01 +00:00
description = ''
Sidebar format. Check neomutt documentation for details.
'';
};
};
};
sortOptions = [
"date"
"date-received"
"from"
"mailbox-order"
"score"
"size"
"spam"
"subject"
"threads"
"to"
];
2020-01-21 23:52:01 +00:00
bindModule = types.submodule {
options = {
map = mkOption {
type = let
menus = [
"alias"
"attach"
"browser"
"compose"
"editor"
"generic"
"index"
"mix"
"pager"
"pgp"
"postpone"
"query"
"smime"
];
in with types; either (enum menus) (listOf (enum menus));
2020-01-21 23:52:01 +00:00
default = "index";
description = "Select the menu to bind the command to.";
};
key = mkOption {
type = types.str;
example = "<left>";
description = "The key to bind.";
};
action = mkOption {
type = types.str;
example = "<enter-command>toggle sidebar_visible<enter><refresh>";
description = "Specify the action to take.";
};
};
};
setOption = n: v: if v == null then "unset ${n}" else "set ${n}=${v}";
2020-02-01 23:39:17 +00:00
escape = replaceStrings [ "%" ] [ "%25" ];
2020-01-21 23:52:01 +00:00
2020-02-01 23:39:17 +00:00
accountFilename = account: config.xdg.configHome + "/neomutt/" + account.name;
2020-01-21 23:52:01 +00:00
2020-02-01 23:39:17 +00:00
genCommonFolderHooks = account:
with account; {
2020-01-21 23:52:01 +00:00
from = "'${address}'";
realname = "'${realName}'";
spoolfile = "'+${folders.inbox}'";
record = if folders.sent == null then null else "'+${folders.sent}'";
postponed = "'+${folders.drafts}'";
trash = "'+${folders.trash}'";
};
2020-02-01 23:39:17 +00:00
mtaSection = account:
with account;
let passCmd = concatStringsSep " " passwordCommand;
in if neomutt.sendMailCommand != null then {
sendmail = "'${neomutt.sendMailCommand}'";
} else
let
2020-01-21 23:52:01 +00:00
smtpProto = if smtp.tls.enable then "smtps" else "smtp";
smtpPort = if smtp.port != null then ":${toString smtp.port}" else "";
2020-07-03 19:14:25 +00:00
smtpBaseUrl =
"${smtpProto}://${escape userName}@${smtp.host}${smtpPort}";
2020-01-21 23:52:01 +00:00
in {
smtp_url = "'${smtpBaseUrl}'";
smtp_pass = ''"`${passCmd}`"'';
2020-01-21 23:52:01 +00:00
};
2020-02-01 23:39:17 +00:00
genMaildirAccountConfig = account:
with account;
2020-01-21 23:52:01 +00:00
let
folderHook = mapAttrsToList setOption (genCommonFolderHooks account
// optionalAttrs cfg.changeFolderWhenSourcingAccount {
folder = "'${account.maildir.absPath}'";
});
2020-02-01 23:39:17 +00:00
in ''
${concatStringsSep "\n" folderHook}
'';
registerAccount = account:
let
mailboxes = if account.neomutt.mailboxName == null then
"mailboxes"
else
''named-mailboxes "${account.neomutt.mailboxName}"'';
extraMailboxes = concatMapStringsSep "\n" (extra:
if isString extra then
''mailboxes "${account.maildir.absPath}/${extra}"''
else if extra.name == null then
''mailboxes "${account.maildir.absPath}/${extra.mailbox}"''
else
''
named-mailboxes "${extra.name}" "${account.maildir.absPath}/${extra.mailbox}"'')
account.neomutt.extraMailboxes;
in with account; ''
2020-01-21 23:52:01 +00:00
# register account ${name}
${mailboxes} "${maildir.absPath}/${folders.inbox}"
${extraMailboxes}
folder-hook ${maildir.absPath}/ " \
2020-01-21 23:52:01 +00:00
source ${accountFilename account} "
'';
2020-02-01 23:39:17 +00:00
mraSection = account:
with account;
if account.maildir != null then
genMaildirAccountConfig account
else
throw "Only maildir is supported at the moment";
2020-01-21 23:52:01 +00:00
2020-02-01 23:39:17 +00:00
optionsStr = attrs: concatStringsSep "\n" (mapAttrsToList setOption attrs);
2020-01-21 23:52:01 +00:00
sidebarSection = ''
# Sidebar
set sidebar_visible = yes
set sidebar_short_path = ${lib.hm.booleans.yesNo cfg.sidebar.shortPath}
2020-01-21 23:52:01 +00:00
set sidebar_width = ${toString cfg.sidebar.width}
set sidebar_format = '${cfg.sidebar.format}'
'';
genBindMapper = bindType:
concatMapStringsSep "\n" (bind:
''
${bindType} ${
concatStringsSep "," (toList bind.map)
} ${bind.key} "${bind.action}"'');
2020-01-21 23:52:01 +00:00
bindSection = (genBindMapper "bind") cfg.binds;
macroSection = (genBindMapper "macro") cfg.macros;
2020-01-21 23:52:01 +00:00
mailCheckSection = ''
set mail_check_stats
set mail_check_stats_interval = ${toString cfg.checkStatsInterval}
'';
2020-02-01 23:39:17 +00:00
notmuchSection = account:
with account; ''
# notmuch section
set nm_default_uri = "notmuch://${config.accounts.email.maildirBasePath}"
virtual-mailboxes "My INBOX" "notmuch://?query=tag:inbox"
'';
2020-01-21 23:52:01 +00:00
2020-02-01 23:39:17 +00:00
accountStr = account:
with account;
let
signature = if account.signature.showSignature == "none" then
"unset signature"
else if account.signature.command != null then
''set signature = "${account.signature.command}|"''
else
"set signature = ${
pkgs.writeText "signature.txt" account.signature.text
}";
in ''
2020-02-01 23:39:17 +00:00
# Generated by Home Manager.
set ssl_force_tls = yes
set certificate_file=${toString config.accounts.email.certificatesFile}
2020-01-21 23:52:01 +00:00
2020-02-01 23:39:17 +00:00
# GPG section
set crypt_use_gpgme = yes
set crypt_autosign = ${lib.hm.booleans.yesNo (gpg.signByDefault or false)}
set crypt_opportunistic_encrypt = ${
lib.hm.booleans.yesNo (gpg.encryptByDefault or false)
}
2020-02-01 23:39:17 +00:00
set pgp_use_gpg_agent = yes
set mbox_type = ${if maildir != null then "Maildir" else "mbox"}
set sort = "${cfg.sort}"
2020-01-21 23:52:01 +00:00
2020-02-01 23:39:17 +00:00
# MTA section
${optionsStr (mtaSection account)}
2020-01-21 23:52:01 +00:00
2020-02-01 23:39:17 +00:00
${optionalString (cfg.checkStatsInterval != null) mailCheckSection}
2020-01-21 23:52:01 +00:00
2020-02-01 23:39:17 +00:00
${optionalString cfg.sidebar.enable sidebarSection}
2020-01-21 23:52:01 +00:00
2020-02-01 23:39:17 +00:00
# MRA section
${mraSection account}
2020-01-21 23:52:01 +00:00
2020-02-01 23:39:17 +00:00
# Extra configuration
${account.neomutt.extraConfig}
${signature}
'' + optionalString account.notmuch.enable (notmuchSection account);
2020-01-21 23:52:01 +00:00
2020-02-01 23:39:17 +00:00
in {
2020-01-21 23:52:01 +00:00
options = {
programs.neomutt = {
enable = mkEnableOption "the NeoMutt mail client";
package = mkOption {
type = types.package;
default = pkgs.neomutt;
defaultText = literalExpression "pkgs.neomutt";
description = "The neomutt package to use.";
};
2020-01-21 23:52:01 +00:00
sidebar = mkOption {
type = sidebarModule;
2020-02-01 23:39:17 +00:00
default = { };
2020-01-21 23:52:01 +00:00
description = "Options related to the sidebar.";
};
binds = mkOption {
type = types.listOf bindModule;
2020-02-01 23:39:17 +00:00
default = [ ];
2020-01-21 23:52:01 +00:00
description = "List of keybindings.";
};
macros = mkOption {
type = types.listOf bindModule;
2020-02-01 23:39:17 +00:00
default = [ ];
2020-01-21 23:52:01 +00:00
description = "List of macros.";
};
sort = mkOption {
# allow users to choose any option from sortOptions, or any option prefixed with "reverse-"
type = types.enum
(sortOptions ++ (map (option: "reverse-" + option) sortOptions));
2020-01-21 23:52:01 +00:00
default = "threads";
description = "Sorting method on messages.";
};
vimKeys = mkOption {
type = types.bool;
default = false;
description = "Enable vim-like bindings.";
};
checkStatsInterval = mkOption {
type = types.nullOr types.int;
default = null;
example = 60;
description = "Enable and set the interval of automatic mail check.";
};
editor = mkOption {
type = types.str;
default = "$EDITOR";
description = "Select the editor used for writing mail.";
};
settings = mkOption {
type = types.attrsOf types.str;
2020-02-01 23:39:17 +00:00
default = { };
2020-01-21 23:52:01 +00:00
description = "Extra configuration appended to the end.";
};
changeFolderWhenSourcingAccount =
mkEnableOption "changing the folder when sourcing an account" // {
default = true;
};
2020-01-21 23:52:01 +00:00
extraConfig = mkOption {
type = types.lines;
default = "";
description = "Extra configuration appended to the end.";
};
};
accounts.email.accounts = mkOption {
type = with types; attrsOf (submodule (import ./neomutt-accounts.nix));
};
2020-01-21 23:52:01 +00:00
};
config = mkIf cfg.enable {
home.packages = [ cfg.package ];
2020-01-21 23:52:01 +00:00
home.file = let
rcFile = account: {
"${accountFilename account}".text = accountStr account;
};
2020-02-01 23:39:17 +00:00
in foldl' (a: b: a // b) { } (map rcFile neomuttAccounts);
xdg.configFile."neomutt/neomuttrc" = mkIf (neomuttAccounts != [ ]) {
text = let
# Find the primary account, if it has neomutt enabled;
# otherwise use the first neomutt account as primary.
primary =
head (filter (a: a.primary) neomuttAccounts ++ neomuttAccounts);
2020-02-01 23:39:17 +00:00
in ''
# Generated by Home Manager.
set header_cache = "${config.xdg.cacheHome}/neomutt/headers/"
set message_cachedir = "${config.xdg.cacheHome}/neomutt/messages/"
set editor = "${cfg.editor}"
set implicit_autoview = yes
alternative_order text/enriched text/plain text
set delete = yes
# Binds
${bindSection}
# Macros
${macroSection}
${optionalString cfg.vimKeys
"source ${pkgs.neomutt}/share/doc/neomutt/vim-keys/vim-keys.rc"}
# Register accounts
${concatMapStringsSep "\n" registerAccount neomuttAccounts}
# Source primary account
source ${accountFilename primary}
# Extra configuration
${optionsStr cfg.settings}
${cfg.extraConfig}
'';
2020-01-21 23:52:01 +00:00
};
assertions = [{
assertion =
((filter (b: (length (toList b.map)) == 0) (cfg.binds ++ cfg.macros))
== [ ]);
message =
"The 'programs.neomutt.(binds|macros).map' list must contain at least one element.";
}];
warnings =
let hasOldBinds = binds: (filter (b: !(isList b.map)) binds) != [ ];
in mkIf (hasOldBinds (cfg.binds ++ cfg.macros)) [
"Specifying 'programs.neomutt.(binds|macros).map' as a string is deprecated, use a list of strings instead. See https://github.com/nix-community/home-manager/pull/1885."
];
2020-01-21 23:52:01 +00:00
};
}