Add khard.el
This commit is contained in:
		
							
								
								
									
										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:
 | 
			
		||||
;;; Code:
 | 
			
		||||
 | 
			
		||||
;; Some other config files
 | 
			
		||||
(add-to-list 'load-path (expand-file-name "elisp"))
 | 
			
		||||
 | 
			
		||||
;; Set package dir to follow no-littering conventions
 | 
			
		||||
(setq package-user-dir "~/.emacs.d/var/elpa")
 | 
			
		||||
 | 
			
		||||
@ -375,6 +378,9 @@ visual states."
 | 
			
		||||
;; json
 | 
			
		||||
(use-package json-mode)
 | 
			
		||||
 | 
			
		||||
;; yaml
 | 
			
		||||
(use-package yaml-mode)
 | 
			
		||||
 | 
			
		||||
;; sly
 | 
			
		||||
(use-package sly
 | 
			
		||||
  :hook (lisp-mode . my/common-lisp-autoconnect-sly)
 | 
			
		||||
@ -420,16 +426,21 @@ visual states."
 | 
			
		||||
    "S" #'magit-stage-modified))
 | 
			
		||||
 | 
			
		||||
;; mu4e
 | 
			
		||||
(require 'auth-source-pass)
 | 
			
		||||
(auth-source-pass-enable)
 | 
			
		||||
(add-to-list 'load-path "/usr/share/emacs/site-lisp/mu4e/")
 | 
			
		||||
(require 'mu4e)
 | 
			
		||||
(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 ()
 | 
			
		||||
  (setq mu4e-hide-index-messages nil))
 | 
			
		||||
(defun my/mu4e-update-mail-and-index-silent ()
 | 
			
		||||
  "Run `mu4e-update-mail-and-index' without any messages in the background."
 | 
			
		||||
  (setq mu4e-hide-index-messages 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-index-update-error-warning nil
 | 
			
		||||
      mu4e-get-mail-command "mbsync protonmail"
 | 
			
		||||
@ -459,6 +470,8 @@ visual states."
 | 
			
		||||
(use-package mu4e-alert
 | 
			
		||||
  :after mu4e
 | 
			
		||||
  :hook (after-init . mu4e-alert-enable-notifications)
 | 
			
		||||
  :init
 | 
			
		||||
  (setq mu4e-alert-set-window-urgency nil)
 | 
			
		||||
  :config
 | 
			
		||||
  (mu4e-alert-set-default-style 'libnotify))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user