home-manager/modules/programs/neomutt.nix
Emily 9f9e277b60 treewide: remove now-redundant lib.mdDoc calls
These (and the `*MD` functions apart from `literalMD`) are now no-ops
in nixpkgs and serve no purpose other than to add additional noise and
potentially mislead people into thinking unmarked DocBook documentation
will still be accepted.

Note that if backporting changes including documentation to 23.05,
the `mdDoc` calls will need to be re-added.

To reproduce this commit, run:

    $ NIX_PATH=nixpkgs=flake:nixpkgs/e7e69199f0372364a6106a1e735f68604f4c5a25 \
      nix shell nixpkgs#coreutils \
      -c find . -name '*.nix' \
      -exec nix run -- github:emilazy/nix-doc-munge/98dadf1f77351c2ba5dcb709a2a171d655f15099 \
      --strip {} +
    $ ./format
2023-07-17 18:49:09 +01:00

392 lines
11 KiB
Nix

{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.programs.neomutt;
neomuttAccounts =
filter (a: a.neomutt.enable) (attrValues config.accounts.email.accounts);
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";
description = ''
Sidebar format. Check neomutt documentation for details.
'';
};
};
};
sortOptions = [
"date"
"date-received"
"from"
"mailbox-order"
"score"
"size"
"spam"
"subject"
"threads"
"to"
];
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));
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.";
};
};
};
mkNotmuchVirtualboxes = virtualMailboxes:
"${concatStringsSep "\n" (map ({ name, query, limit, type }:
''
virtual-mailboxes "${name}" "notmuch://?query=${lib.escapeURL query}${
optionalString (limit != null) "&limit=${toString limit}"
}${optionalString (type != null) "&type=${type}"}"'')
virtualMailboxes)}";
setOption = n: v: if v == null then "unset ${n}" else "set ${n}=${v}";
escape = replaceStrings [ "%" ] [ "%25" ];
accountFilename = account: config.xdg.configHome + "/neomutt/" + account.name;
genCommonFolderHooks = account:
with account; {
from = "'${address}'";
realname = "'${realName}'";
spoolfile = "'+${folders.inbox}'";
record = if folders.sent == null then null else "'+${folders.sent}'";
postponed = "'+${folders.drafts}'";
trash = "'+${folders.trash}'";
};
mtaSection = account:
with account;
let passCmd = concatStringsSep " " passwordCommand;
in if neomutt.sendMailCommand != null then {
sendmail = "'${neomutt.sendMailCommand}'";
} else
let
smtpProto = if smtp.tls.enable then "smtps" else "smtp";
smtpPort = if smtp.port != null then ":${toString smtp.port}" else "";
smtpBaseUrl =
"${smtpProto}://${escape userName}@${smtp.host}${smtpPort}";
in {
smtp_url = "'${smtpBaseUrl}'";
smtp_pass = ''"`${passCmd}`"'';
};
genMaildirAccountConfig = account:
with account;
let
folderHook = mapAttrsToList setOption (genCommonFolderHooks account
// optionalAttrs cfg.changeFolderWhenSourcingAccount {
folder = "'${account.maildir.absPath}'";
});
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; ''
# register account ${name}
${mailboxes} "${maildir.absPath}/${folders.inbox}"
${extraMailboxes}
folder-hook ${maildir.absPath}/ " \
source ${accountFilename account} "
'';
mraSection = account:
with account;
if account.maildir != null then
genMaildirAccountConfig account
else
throw "Only maildir is supported at the moment";
optionsStr = attrs: concatStringsSep "\n" (mapAttrsToList setOption attrs);
sidebarSection = ''
# Sidebar
set sidebar_visible = yes
set sidebar_short_path = ${lib.hm.booleans.yesNo cfg.sidebar.shortPath}
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}"'');
bindSection = (genBindMapper "bind") cfg.binds;
macroSection = (genBindMapper "macro") cfg.macros;
mailCheckSection = ''
set mail_check_stats
set mail_check_stats_interval = ${toString cfg.checkStatsInterval}
'';
notmuchSection = account:
let virtualMailboxes = account.notmuch.neomutt.virtualMailboxes;
in with account; ''
# notmuch section
set nm_default_uri = "notmuch://${config.accounts.email.maildirBasePath}"
${optionalString
(notmuch.neomutt.enable && builtins.length virtualMailboxes > 0)
(mkNotmuchVirtualboxes virtualMailboxes)}
'';
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 ''
# Generated by Home Manager.
set ssl_force_tls = yes
set certificate_file=${toString config.accounts.email.certificatesFile}
# 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)
}
set pgp_use_gpg_agent = yes
set mbox_type = ${if maildir != null then "Maildir" else "mbox"}
set sort = "${cfg.sort}"
# MTA section
${optionsStr (mtaSection account)}
${optionalString (cfg.checkStatsInterval != null) mailCheckSection}
${optionalString cfg.sidebar.enable sidebarSection}
# MRA section
${mraSection account}
# Extra configuration
${account.neomutt.extraConfig}
${signature}
''
+ optionalString (account.notmuch.enable && account.notmuch.neomutt.enable)
(notmuchSection account);
in {
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.";
};
sidebar = mkOption {
type = sidebarModule;
default = { };
description = "Options related to the sidebar.";
};
binds = mkOption {
type = types.listOf bindModule;
default = [ ];
description = "List of keybindings.";
};
macros = mkOption {
type = types.listOf bindModule;
default = [ ];
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));
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;
default = { };
description = "Extra configuration appended to the end.";
};
changeFolderWhenSourcingAccount =
mkEnableOption "changing the folder when sourcing an account" // {
default = true;
};
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));
};
};
config = mkIf cfg.enable {
home.packages = [ cfg.package ];
home.file = let
rcFile = account: {
"${accountFilename account}".text = accountStr account;
};
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);
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}
'';
};
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."
];
};
}