diff --git a/Xresources.d/urxvt b/Xresources.d/urxvt index 08bf87e..e94c0e2 100644 --- a/Xresources.d/urxvt +++ b/Xresources.d/urxvt @@ -22,7 +22,7 @@ URxvt*scrollBar: false ! Font declaration -URxvt.font: xft:DejaVu Sans Mono for Powerline:size=12:antialias=true +URxvt.font: xft:DejaVu Sans Mono for Powerline:size=12:antialias=true,xft:Symbola:size=12:antialias=true ! Font spacing URxvt.letterSpace: 0 diff --git a/bashrc b/bashrc index fda1e9a..3d3c919 100644 --- a/bashrc +++ b/bashrc @@ -43,6 +43,9 @@ alias rm='rm -Iv --one-file-system' alias free='free -m' alias df='df -h' alias dmesg='dmesg --ctime' +alias ffmpeg='ffmpeg -hide_banner' +alias ffprobe='ffprobe -hide_banner' +alias ffplay='ffplay -hide_banner' # Frequent mistakes alias sl=ls @@ -62,31 +65,48 @@ alias tracefiles="strace -f -t -e trace=file" alias n='urxvtc &' # Superseding commands with better ones if they are present -function vi() { - if which vim &> /dev/null; then - alias vi='vim' - fi - vim "$@" +function _do_rank() { # executables... -- arguments... + for ex in "$@" + do + [ "$ex" == "--" ] && break + if which "$ex" &> /dev/null + then + for al in "$@" + do + shift + [ "$al" == "--" ] && break + alias "$al"="$ex" + done + "$ex" "$@" + return $? + fi + done + for ex in "$@" + do + [ "$al" == "--" ] && break + if -z "$list" + then + list=$ex + else + list=$list, $ex + fi + done + echo "Not installed: $list" } -function pass() { - if which gopass &> /dev/null; then - alias pass='gopass' - fi - gopass "$@" -} -function wol() { - if which wakeonlan &> /dev/null; then - alias wol='wakeonlan' - fi - wakeonlan "$@" -} -function mutt() { - if which neomutt &> /dev/null; then - alias mutt='neomutt' - fi - neomutt "$@" + +function _install_rank() { # executables... + for ex in "$@" + do + list=$@ + alias "$ex"="_do_rank $list --" + done } +_install_rank nvim vim vi +_install_rank gopass pass +_install_rank wakeonlan wol +_install_rank neomutt mutt + # SHELL CUSTOMIZATION complete -cf sudo diff --git a/scripts/mel b/scripts/mel new file mode 100755 index 0000000..2eecbe4 --- /dev/null +++ b/scripts/mel @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 + +""" +Meh mail client +""" + +import notmuch +import logging +import coloredlogs +import colorama +import datetime +import os +import progressbar +import time + +colorama.init() +coloredlogs.install(level='DEBUG', fmt='%(levelname)s %(message)s') +log = logging.getLogger() + +log.debug("Loading database") + +db = notmuch.Database(mode=notmuch.Database.MODE.READ_WRITE) +# TODO Open read-only when needed + +log.debug("Database loaded") + +def get_location(msg): + path = msg.get_filename() + path = os.path.dirname(path) + base = db.get_path() + assert path.startswith(base) + path = path[len(base):] + _, mailbox, folder, state = path.split('/') + assert state in {'cur', 'tmp', 'new'} + return (mailbox, folder, state) + +MAILBOX_COLORS = dict() + +def get_mailbox_color(mailbox): + if mailbox not in MAILBOX_COLORS: + colorfile = os.path.join(db.get_path(), mailbox, 'color') + assert os.path.isfile(colorfile) + with open(colorfile, 'r') as f: + colorStr = f.read() + colorStr = colorStr[1:] if colorStr[0] == '#' else colorStr + R = int(colorStr[0:2], 16) + G = int(colorStr[2:4], 16) + B = int(colorStr[4:6], 16) + MAILBOX_COLORS[mailbox] = '\x1b[38;2;{};{};{}m'.format(R, G, B) + return MAILBOX_COLORS[mailbox] + +def format_date(date): + now = datetime.datetime.now() + midnight = datetime.datetime(year=now.year, month=now.month, day=now.day) + if date > midnight: + return date.strftime('%H:%M:%S') + else: + return date.strftime('%d/%m/%y') + + +def print_msg(msg): + line = "" + tags = set(msg.get_tags()) + mailbox, folder, state = get_location(msg) + line += get_mailbox_color(mailbox) + + # Date + date = datetime.datetime.fromtimestamp(msg.get_date()) + line += format_date(date) + + # Icons + line += " " + def tags2col1(tag1, tag2, both, first, second, none): + nonlocal line + if tag1 in tags: + if tag2 in tags: + line += both + else: + line += first + else: + if tag2 in tags: + line += second + else: + line += none + + tags2col1('spam', 'draft', '??', '💥', '📝', ' ') + tags2col1('attachment', 'encrypted', '🔐', '📎', '🔑', ' ') + tags2col1('unread', 'flagged', '🏁', '🏳 ', '🏴', ' ') + tags2col1('sent', 'replied', '?', '↑', '↪', ' ') + + # TODO To: / From: + + # Subject + line += " " + line += msg.get_header("subject") + line += colorama.Style.RESET_ALL + print(line) + + +def retag_msg(msg): + msg.freeze() + mailbox, folder, state = get_location(msg) + + # Search-friendly folder name + if folder.startswith('INBOX.'): + folder = folder[6:] + folder = folder.upper() + + msg.remove_all_tags() + if folder.startswith('JUNK') or folder.startswith('SPAM'): + msg.add_tag('spam') + if folder.startswith('DRAFT'): + msg.add_tag('draft') + if folder.startswith('INBOX'): + msg.add_tag('inbox') + + # TODO 'sent' tag + + # Save + msg.thaw() + msg.tags_to_maildir_flags() + + +def applyMsgs(queryStr, function, useProgressbar=False): + query = notmuch.Query(db, queryStr) + query.set_sort(notmuch.Query.SORT.OLDEST_FIRST) + msgs = query.search_messages() + if useProgressbar: + nbMsgs = query.count_messages() + iterator = progressbar.progressbar(msgs, max_value=nbMsgs) + else: + iterator = msgs + for msg in iterator: + function(msg) + +applyMsgs('tag:inbox', print_msg) +# applyMsgs('tag:spam', print_msg) +# applyMsgs('tag:unread', print_msg) +# applyMsgs('from:geoffrey@frogeye.fr', print_msg) + +# applyMsgs('*', retag_msg, useProgressbar=True) diff --git a/scripts/melConf b/scripts/melConf new file mode 100755 index 0000000..3e850da --- /dev/null +++ b/scripts/melConf @@ -0,0 +1,176 @@ +#!/usr/bin/env python3 + +""" +Meh mail client conf generator for other things +""" + +import configparser +import os +import sys + +# TODO Find config file from XDG +# TODO Alias adresses +# TODO Signature file +# TODO Write ~/.mail/[mailbox]/color file if required by sth? +# Certificate file + +configPath = os.path.join(os.path.expanduser('~'), '.config', 'mel.conf') + +config = configparser.ConfigParser() +config.read(configPath) + +SERVER_DEFAULTS = { + "imap": {"port": 143, "starttls": True}, + "smtp": {"port": 587, "starttls": True}, + } +SERVER_ITEMS = {"host", "port", "user", "pass", "starttls"} + +# Reading sections +accounts = dict() + +for name in config.sections(): + if not name.islower(): + continue + section = config[name] + + data = dict() + for server in SERVER_DEFAULTS.keys(): + for item in SERVER_ITEMS: + key = server + item + try: + val = section.get(key) or section.get(item) or SERVER_DEFAULTS[server][item] + except KeyError: + raise KeyError("{}.{}".format(name, key)) + + if isinstance(val, str): + if val == "True": + val = True + elif val == "False": + val = False + elif val.isnumeric(): + val = int(val) + data[key] = val + + for key in section.keys(): + if key in SERVER_ITEMS: + continue + data[key] = section[key] + + data["name"] = name + data["storage"] = os.path.join(config['GLOBAL']['storage'], name) + data["storageInbox"] = os.path.join(data["storage"], "INBOX") + storageFull = os.path.expanduser(data["storage"]) + os.makedirs(storageFull, exist_ok=True) + accounts[name] = data + +# OfflineIMAP + +OFFLINEIMAP_BEGIN = """[general] +# List of accounts to be synced, separated by a comma. +accounts = {} +maxsyncaccounts = {} +stocktimeout = 60 +pythonfile = ~/.config/offlineimap.py + +[mbnames] +enabled = yes +filename = ~/.mutt/mailboxes +header = "mailboxes " +peritem = "+%(accountname)s/%(foldername)s" +sep = " " +footer = "\\n" + +""" + +OFFLINEIMAP_ACCOUNT = """[Account {name}] +localrepository = {name}-local +remoterepository = {name}-remote +autorefresh = 0.5 +quick = 10 +utf8foldernames = yes +postsynchook = ~/.mutt/postsync + +[Repository {name}-local] +type = Maildir +localfolders = {storage} + +[Repository {name}-remote] +type = IMAP +{secconf} +keepalive = 60 +holdconnectionopen = yes +remotehost = {imaphost} +remoteport = {imapport} +remoteuser = {imapuser} +remotepass = {imappass} + +""" + +offlineIMAPstr = OFFLINEIMAP_BEGIN.format(','.join(accounts), len(accounts)) +for name, account in accounts.items(): + if account["imapstarttls"]: + secconf = "ssl = no" + else: + secconf = "sslcacertfile = /etc/ssl/certs/ca-certificates.crt" + offlineIMAPstr += OFFLINEIMAP_ACCOUNT.format(**account, secconf=secconf) +# TODO Write + +# mbsync +MBSYNC_ACCOUNT = """IMAPAccount {name} +Host {imaphost} +User {imapuser} +Pass "{imappass}" +{secconf} + +IMAPStore {name}-remote +Account {name} + +MaildirStore {name}-local +Subfolders Verbatim +Path {storage}/ +Inbox {storageInbox}/ + +Channel {name} +Master :{name}-remote: +Slave :{name}-local: +Patterns * +Create Both +SyncState * + +""" + +mbsyncStr = "" +for name, account in accounts.items(): + if account["imapstarttls"]: + secconf = "SSLType STARTTLS" + else: + secconf = "SSLType IMAPS" + mbsyncStr += MBSYNC_ACCOUNT.format(**account, secconf=secconf) +msbsyncFilepath = os.path.join(os.path.expanduser('~'), '.mbsyncrc') +with open(msbsyncFilepath, 'w') as f: + f.write(mbsyncStr) + +# msmtp +MSMTP_BEGIN = """defaults +protocol smtp +auth on +tls_trust_file /etc/ssl/certs/ca-certificates.crt + +""" + +MSMTP_ACCOUNT = """account {name} +from {from} +user {smtpuser} +password {smtppass} +host {smtphost} +port {smtpport} +tls on + +""" + +msmtpStr = MSMTP_BEGIN +for name, account in accounts.items(): + msmtpStr += MSMTP_ACCOUNT.format(**account) +msbsyncFilepath = os.path.join(os.path.expanduser('~'), '.msmtprc') +with open(msbsyncFilepath, 'w') as f: + f.write(msmtpStr) diff --git a/scripts/optimize b/scripts/optimize index bbb3543..d127d83 100755 --- a/scripts/optimize +++ b/scripts/optimize @@ -146,20 +146,26 @@ do done <<< "$(find "$dir" -type f -iname "*.png")" -# SVG (requires svgo) -while read image -do - if [ -z "$image" ]; then continue; fi - echo Processing $image +# # SVG (requires scour) +# while read image +# do +# if [ -z "$image" ]; then continue; fi +# echo Processing $image +# +# temp=$(mktemp --suffix .svg) +# scour --quiet "$image" "$temp" --no-line-breaks +# echo "→ Optimize done" +# +# replaceImg "$temp" "$image" +# +# done <<< "$(find "$dir" -type f -iname "*.svg")" - temp=$(mktemp --suffix .svg) - cp "$image" "$temp" - svgo --quiet --config $HOME/.config/optiSvgo.yml "$temp" - echo "→ Optimize done" - - replaceImg "$temp" "$image" - -done <<< "$(find "$dir" -type f -iname "*.svg")" +# NOTE Explicitely disabled since: +# - I only have ~50 MiB of SVG in TOTAL +# - Most conversions are not image losseless +# - Even when they are losseless, they are mostly worthless +# - I might want to keep editor data and/or ids for some of them +# So rather use scour explicitely when needed cleandev diff --git a/vimrc b/vimrc index 733c99c..a8e9d10 100644 --- a/vimrc +++ b/vimrc @@ -52,7 +52,7 @@ else endif Plug 'zchee/deoplete-jedi' -Plug 'python-mode/python-mode', { 'branch': 'develop' } +" Plug 'python-mode/python-mode', { 'branch': 'develop' } Plug 'junegunn/fzf', {'do': './install --bin'} Plug 'junegunn/fzf.vim' Plug 'ervandew/supertab'