Add khard.el
This commit is contained in:
parent
63b65ce662
commit
19043ee1bb
176
elisp/khard.el
Normal file
176
elisp/khard.el
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
;;; khard.el --- Emacs integration with khard
|
||||||
|
;;; Commentary:
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(require 'with-editor)
|
||||||
|
|
||||||
|
(add-to-list 'display-buffer-alist '("\\*khard output\\*" . (display-buffer-no-window)))
|
||||||
|
|
||||||
|
(defun khard--build-list-entry-detail (&rest items)
|
||||||
|
"Build a detail in the format \" (ITEMS)\", or an empty string."
|
||||||
|
(let ((clean-items (remove "" items)))
|
||||||
|
(if (not (seq-empty-p clean-items))
|
||||||
|
(format " (%s)"
|
||||||
|
(string-join clean-items ", "))
|
||||||
|
"")))
|
||||||
|
|
||||||
|
(defun khard--remove-leading-label (field)
|
||||||
|
"Remove a leading \"name: \" from FIELD."
|
||||||
|
(if-let (index (string-search ":" field))
|
||||||
|
(substring field (+ index 2))
|
||||||
|
field))
|
||||||
|
|
||||||
|
(defun khard--build-uid-email-phone-list ()
|
||||||
|
"Build a list in the format (info . uid)."
|
||||||
|
(let ((lines (process-lines "khard"
|
||||||
|
"ls"
|
||||||
|
"--parsable"
|
||||||
|
"--fields=uid,name,email,phone")))
|
||||||
|
(mapcar (lambda (line)
|
||||||
|
(let* ((fields (split-string line "\t"))
|
||||||
|
(uid (car fields))
|
||||||
|
(name (cadr fields))
|
||||||
|
(email (khard--remove-leading-label (caddr fields)))
|
||||||
|
(phone (khard--remove-leading-label (cadddr fields))))
|
||||||
|
(cons (format "%s%s"
|
||||||
|
name
|
||||||
|
(khard--build-list-entry-detail email phone uid))
|
||||||
|
uid)))
|
||||||
|
lines)))
|
||||||
|
|
||||||
|
(defun khard--prompt-contact (&optional prompt)
|
||||||
|
"Prompt user for a contact, optionally make the prompt text PROMPT."
|
||||||
|
(if-let ((uid-list (khard--build-uid-email-phone-list))
|
||||||
|
(resp (completing-read (or prompt "Contact ") uid-list)))
|
||||||
|
(assoc resp uid-list)))
|
||||||
|
|
||||||
|
(defun khard--process-sentinel (proc status)
|
||||||
|
"Process sentinel for kahrd commands.
|
||||||
|
For info on PROC and STATUS, see `set-process-sentinel'."
|
||||||
|
(when (memq (process-status proc) '(exit signal))
|
||||||
|
(shell-command-set-point-after-cmd (process-buffer proc))
|
||||||
|
(message "khard: %s." (substring status 0 -1))))
|
||||||
|
|
||||||
|
(defun khard-edit (uid)
|
||||||
|
"Edit the contact with UID.
|
||||||
|
When called interactively, prompt the user."
|
||||||
|
(interactive (list (cdr-safe (khard--prompt-contact "Edit Contact "))))
|
||||||
|
(let ((with-editor-shell-command-use-emacsclient nil))
|
||||||
|
(make-process :name "khard edit"
|
||||||
|
:command
|
||||||
|
`("khard" "edit" "--edit"
|
||||||
|
,(format "uid:%s" (cdr-safe contact)))
|
||||||
|
:buffer nil
|
||||||
|
:filter #'with-editor-process-filter
|
||||||
|
:sentinel #'khard--process-sentinel)))
|
||||||
|
|
||||||
|
(defun khard-delete (contact no-confirm)
|
||||||
|
"Delete CONTACT, which is of the form (name . uid).
|
||||||
|
When called interactively, prompt the user.
|
||||||
|
If NO-CONFIRM is nil, do not ask the user."
|
||||||
|
(interactive (list (khard--prompt-contact "Delete Contact ") nil))
|
||||||
|
(when (or no-confirm (yes-or-no-p (format "Really delete \"%s\"? "
|
||||||
|
(car-safe contact))))
|
||||||
|
(make-process :name "khard delete"
|
||||||
|
:command
|
||||||
|
`("khard" "delete" "--force"
|
||||||
|
,(format "uid:%s" (cdr-safe contact)))
|
||||||
|
:buffer nil
|
||||||
|
:sentinel #'khard--process-sentinel)))
|
||||||
|
|
||||||
|
(defun khard--prompt-address-book ()
|
||||||
|
"Prompt for an address book."
|
||||||
|
(completing-read "Address Book " (process-lines "khard" "abooks")))
|
||||||
|
|
||||||
|
(defun khard--new-process-filter (proc str)
|
||||||
|
"Process filter for `khard-new'.
|
||||||
|
PROC and STR are described in `set-process-filter'."
|
||||||
|
(let ((lines (string-split str "\n")))
|
||||||
|
(dolist (line lines)
|
||||||
|
(cond
|
||||||
|
((string-prefix-p "Error: " line)
|
||||||
|
(setq error-msg line))
|
||||||
|
((equal
|
||||||
|
"Do you want to open the editor again? (y/N) "
|
||||||
|
line)
|
||||||
|
(if (y-or-n-p (format "%s! Reopen the editor? "
|
||||||
|
(or error-msg
|
||||||
|
"Unknown error")))
|
||||||
|
(process-send-string proc "y\n")
|
||||||
|
(process-send-string proc "n\n"))))))
|
||||||
|
(with-editor-process-filter proc str t))
|
||||||
|
|
||||||
|
(defun khard-new (abook)
|
||||||
|
"Create a new card and open it in an new buffer to edit.
|
||||||
|
When called interactively, prompt for ABOOK."
|
||||||
|
(interactive (list (khard--prompt-address-book)))
|
||||||
|
(when abook
|
||||||
|
(let ((error-msg nil))
|
||||||
|
(make-process :name "khard new"
|
||||||
|
:command
|
||||||
|
`("env" ,(concat "EDITOR=" with-editor-sleeping-editor)
|
||||||
|
"khard" "new" "--edit" "-a" ,abook)
|
||||||
|
:buffer nil
|
||||||
|
:filter #'khard--new-process-filter
|
||||||
|
:sentinel #'khard--process-sentinel))))
|
||||||
|
|
||||||
|
(defun khard--parse-email-list (list-str)
|
||||||
|
"Parse LIST-STR, a python dictionary and array string of emails."
|
||||||
|
(if-let ((length (length list-str))
|
||||||
|
((>= length 2))
|
||||||
|
(no-braces (substring list-str 1 -1)))
|
||||||
|
(let ((output nil)
|
||||||
|
(in-quote nil)
|
||||||
|
(backslash nil)
|
||||||
|
(in-value nil)
|
||||||
|
(cur-str ""))
|
||||||
|
(dotimes (i (- length 2))
|
||||||
|
(let ((char (aref no-braces i)))
|
||||||
|
(cond
|
||||||
|
(in-quote
|
||||||
|
(cond
|
||||||
|
(backslash
|
||||||
|
(setq cur-str (concat cur-str char)
|
||||||
|
backslash nil))
|
||||||
|
((= char ?\\)
|
||||||
|
(setq backslash t))
|
||||||
|
((= char ?')
|
||||||
|
(add-to-list 'output cur-str)
|
||||||
|
(setq cur-str ""
|
||||||
|
in-quote nil))
|
||||||
|
(t
|
||||||
|
(setq cur-str (concat cur-str (list char))))))
|
||||||
|
((and in-value (= char ?'))
|
||||||
|
(setq in-quote t))
|
||||||
|
((= char ?\[)
|
||||||
|
(setq in-value t))
|
||||||
|
((= char ?\])
|
||||||
|
(setq in-value nil)))))
|
||||||
|
output)))
|
||||||
|
|
||||||
|
(defun khard--make-email-contacts-list ()
|
||||||
|
"Make a list of email contacts from khard."
|
||||||
|
(let ((lines (process-lines "khard"
|
||||||
|
"ls"
|
||||||
|
"--parsable"
|
||||||
|
"--fields=name,emails"))
|
||||||
|
(output nil))
|
||||||
|
(dolist (line lines)
|
||||||
|
(let* ((fields (split-string line "\t"))
|
||||||
|
(name (car fields))
|
||||||
|
(email-list (cadr fields)))
|
||||||
|
(dolist (email (khard--parse-email-list email-list))
|
||||||
|
(add-to-list 'output (format "%s <%s>"
|
||||||
|
name
|
||||||
|
email)))))
|
||||||
|
output))
|
||||||
|
|
||||||
|
(defun khard-insert-email-contact ()
|
||||||
|
"Use `completing-read' to prompt for and insert a khard contact."
|
||||||
|
(interactive)
|
||||||
|
(if-let (contact (completing-read "Insert Contact "
|
||||||
|
(khard--make-email-contacts-list)))
|
||||||
|
(insert contact)))
|
||||||
|
|
||||||
|
(provide 'khard)
|
||||||
|
;;; khard.el ends here
|
15
init.el
15
init.el
@ -2,6 +2,9 @@
|
|||||||
;;; Commentary:
|
;;; Commentary:
|
||||||
;;; Code:
|
;;; Code:
|
||||||
|
|
||||||
|
;; Some other config files
|
||||||
|
(add-to-list 'load-path (expand-file-name "elisp"))
|
||||||
|
|
||||||
;; Set package dir to follow no-littering conventions
|
;; Set package dir to follow no-littering conventions
|
||||||
(setq package-user-dir "~/.emacs.d/var/elpa")
|
(setq package-user-dir "~/.emacs.d/var/elpa")
|
||||||
|
|
||||||
@ -375,6 +378,9 @@ visual states."
|
|||||||
;; json
|
;; json
|
||||||
(use-package json-mode)
|
(use-package json-mode)
|
||||||
|
|
||||||
|
;; yaml
|
||||||
|
(use-package yaml-mode)
|
||||||
|
|
||||||
;; sly
|
;; sly
|
||||||
(use-package sly
|
(use-package sly
|
||||||
:hook (lisp-mode . my/common-lisp-autoconnect-sly)
|
:hook (lisp-mode . my/common-lisp-autoconnect-sly)
|
||||||
@ -420,16 +426,21 @@ visual states."
|
|||||||
"S" #'magit-stage-modified))
|
"S" #'magit-stage-modified))
|
||||||
|
|
||||||
;; mu4e
|
;; mu4e
|
||||||
|
(require 'auth-source-pass)
|
||||||
|
(auth-source-pass-enable)
|
||||||
(add-to-list 'load-path "/usr/share/emacs/site-lisp/mu4e/")
|
(add-to-list 'load-path "/usr/share/emacs/site-lisp/mu4e/")
|
||||||
(require 'mu4e)
|
(require 'mu4e)
|
||||||
(add-hook 'mu4e-index-updated-hook #'my/-mu4e-enable-index-messages)
|
(add-hook 'mu4e-index-updated-hook #'my/-mu4e-enable-index-messages)
|
||||||
|
(evil-define-key '(normal motion) mu4e-main-mode-map "q" #'bury-buffer)
|
||||||
(defun my/-mu4e-enable-index-messages ()
|
(defun my/-mu4e-enable-index-messages ()
|
||||||
(setq mu4e-hide-index-messages nil))
|
(setq mu4e-hide-index-messages nil))
|
||||||
(defun my/mu4e-update-mail-and-index-silent ()
|
(defun my/mu4e-update-mail-and-index-silent ()
|
||||||
"Run `mu4e-update-mail-and-index' without any messages in the background."
|
"Run `mu4e-update-mail-and-index' without any messages in the background."
|
||||||
(setq mu4e-hide-index-messages t)
|
(setq mu4e-hide-index-messages t)
|
||||||
(mu4e-update-mail-and-index t))
|
(mu4e-update-mail-and-index t))
|
||||||
(setq mu4e-change-filenames-when-moving t
|
(setq message-kill-buffer-on-exit t
|
||||||
|
message-send-mail-function 'sendmail-send-it
|
||||||
|
mu4e-change-filenames-when-moving t
|
||||||
mu4e-context-policy 'pick-first
|
mu4e-context-policy 'pick-first
|
||||||
mu4e-index-update-error-warning nil
|
mu4e-index-update-error-warning nil
|
||||||
mu4e-get-mail-command "mbsync protonmail"
|
mu4e-get-mail-command "mbsync protonmail"
|
||||||
@ -459,6 +470,8 @@ visual states."
|
|||||||
(use-package mu4e-alert
|
(use-package mu4e-alert
|
||||||
:after mu4e
|
:after mu4e
|
||||||
:hook (after-init . mu4e-alert-enable-notifications)
|
:hook (after-init . mu4e-alert-enable-notifications)
|
||||||
|
:init
|
||||||
|
(setq mu4e-alert-set-window-urgency nil)
|
||||||
:config
|
:config
|
||||||
(mu4e-alert-set-default-style 'libnotify))
|
(mu4e-alert-set-default-style 'libnotify))
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user