emacs-config/elisp/khard.el
2023-10-29 03:03:24 -07:00

175 lines
6.5 KiB
EmacsLisp

;;; 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-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 " *khard output*"
: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"))
(error-msg nil))
(dolist (line lines)
(if (equal
"Do you want to open the editor again? (y/N) "
line)
(if (y-or-n-p (format "%sReopen the editor? "
(or error-msg
"Unknown error")))
(process-send-string proc "y\n")
(process-send-string proc "n\n"))
(setq error-msg (concat error-msg "\n" line)))))
(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 " *khard output*"
:filter #'khard--new-process-filter
:sentinel #'khard--process-sentinel))))
(defun khard-edit (uid)
"Edit the contact with UID.
When called interactively, prompt the user."
(interactive (list (cdr-safe (khard--prompt-contact "Edit Contact "))))
(make-process :name "khard edit"
:command
`("env" ,(concat "EDITOR=" with-editor-sleeping-editor)
"khard" "edit" "--edit" ,(format "uid:%s" uid))
:buffer " *khard output*"
: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