Space Panda


#!/usr/bin/env python
"""Generate (neo)mutt profiles from ~/.config/neomutt/profiles.yaml
The profiles.yaml can look like this:
    my profile:
            - ^this-is-special@example\.com$
            - ^me(\+[a-zA-Z0-9.-_]+)?@example\.com$
        name: My Name
        signature: |
            This is my
            fancy email signature
        shortcut: 1
            - "0x1BADC0FFEE"
        sent-mail: +sent-mail
        trash: +dustbin
        drafts: +postponed
    my other profile:
        name: Still My Name
        organization: Fancy Corp
        pgp: "0x1BADC0FFEE"
            X-SPAM: This is not spam. Really!
Only address and name are required. If 'shortcut' is provided, it will be prefixed with Esc.
'pgp' may be either only the key ID or a list of key ID and the URL of the public key.
If you plan to use this script, please be sure to check the DEFAULTS dict for the folder
defaults of trash, drafts, and sent-mail.
import os
import pathlib
import shutil
import yaml
__version__ = "0.1.1"
MUTT_PATH = '~/.config/neomutt'
DEFAULTS = {'sent-mail': '+Sent',
            'trash': '+Trash',
            'drafts': '+Drafts'}
BASEPATH = pathlib.Path(os.path.expanduser(MUTT_PATH))
PROFILES_YAML = BASEPATH / "profiles.yaml"
PROFILES_RC = BASEPATH / "profiles.rc"
PROFILES_DIR = BASEPATH / "profiles.d"
shutil.rmtree(PROFILES_DIR, ignore_errors=True)
os.makedirs(PROFILES_DIR, exist_ok=True)
headers = set(['Organization', 'X-PGP-Key', 'OpenPGP', 'X-Face'])
with open(PROFILES_YAML, 'rt', encoding='utf-8') as fd:
    data = yaml.safe_load(fd)
global_conf = ""
default_profile = ""
all_addresses = []
for idx, name in enumerate(data.keys()):
    config = data[name]
    realname = config.get('name', None)
    address = config.get('address', None)
    aliases = config.get('aliases', [])
    sentmail = config.get('sent-mail', DEFAULTS['sent-mail'])
    trash = config.get('trash', DEFAULTS['trash'])
    drafts = config.get('drafts', DEFAULTS['drafts'])
    signature = config.get('signature', '')
    shortcut = config.get('shortcut', None)
    organization = config.get('organization', None)
    pgp = config.get('pgp', None)
    xtra_headers = config.get('headers', None)
    if address is None:
        print(f'Ignoring profile {name}: no "address" given.')
    all_addresses += [address] + aliases
    if len(signature.strip()) > 0:
        with open(PROFILES_DIR / f"profile_{idx}.sig", "w", encoding="utf-8") as fd:
    with open(PROFILES_DIR / f"profile_{idx}.rc", "w", encoding="utf-8") as fd:
        fd.write(f"""source "{PROFILES_DIR}/clear.rc"
set from="{address}"\n""")
        if realname is not None:
            fd.write(f"""set realname = "{realname}"\n""")
            fd.write(f"""alias current-from {address} ({realname})\n""")
            fd.write(f"""alias current-from {address}\n""")
        if pgp is not None:
            pgpkey = pgp
            pgpurl = None
            if isinstance(pgp, list):
                pgpkey = pgp[0]
                pgpurl = pgp[1]
            fd.write(f"""set pgp_self_encrypt_as = {pgpkey}
set pgp_sign_as = {pgpkey}
set crypt_autosign = yes
set crypt_replysign = yes
set crypt_replyencrypt = yes
set crypt_replysignencrypted = yes
            if pgpurl is not None:
                fd.write(f"my_hdr X-PGP-Key: {pgpurl}\n")
                openpgpargs = [f'url="{pgpurl}"']
                if pgpkey.lower().startswith('0x'):
                openpgpargs = ' '.join(openpgpargs)
                fd.write(f"my_hdr Openpgp: {openpgpargs}\n")
        if len(signature.strip()) > 0:
            fd.write(f"""set signature = "{PROFILES_DIR}/profile_{idx}.sig"\n""")
        if organization is not None:
            fd.write(f"my_hdr Organization: {organization}\n")
        if isinstance(xtra_headers, dict):
            for hdrname, value in xtra_headers.items():
                fd.write(f"my_hdr {hdrname}: {value}\n")
        fd.write(f"set record = {sentmail}\n")
        fd.write(f"set postponed = {drafts}\n")
        fd.write(f"set trash = {trash}\n")
    if shortcut is not None:
        global_conf += f"macro index <Esc>{shortcut} '<enter-command>source {PROFILES_DIR}/profile_{idx}.rc<enter>'\n"
        global_conf += f"macro compose <Esc>{shortcut} '<enter-command>source {PROFILES_DIR}/profile_{idx}.rc<enter><edit-from><kill-line>current-from<enter>'\n"
    if len(default_profile) == 0:
        default_profile = f"""source "{PROFILES_DIR}/profile_{idx}.rc"\n"""
    for address in [address]+aliases:
        address = address.replace('\\', '\\\\\\')
        global_conf += f"""reply-hook '~C ^{address}$' 'source {PROFILES_DIR}/profile_{idx}.rc'\n"""
    global_conf += "\n"
alternates = ' '.join([f"'^{addr}$'" for addr in all_addresses])
with open(PROFILES_RC, "w", encoding="utf-8") as fd:
    fd.write(f"""{global_conf}\nalternates {alternates}\n{default_profile}\n""")
with open(PROFILES_DIR / "clear.rc", "w", encoding="utf-8") as fd:
    fd.write("""unset realname
unset from
unalias current-from
unset pgp_self_encrypt_as
unset pgp_sign_as
set crypt_autosign=no
set crypt_replysign=no
set crypt_replyencrypt=no
set crypt_replysignencrypted=no
unset signature
unset record
unset postponed
unset trash
    for hdr in headers:
        fd.write(f"""unmy_hdr {hdr}\n""")