;;; 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