:tools password-store -> :tools pass

A simpler name and matches the prefix of its module.
This commit is contained in:
Henrik Lissner 2019-05-19 00:03:15 -04:00
parent 0674e4b4b0
commit c3cb7c5000
No known key found for this signature in database
GPG key ID: 5F6C0EA160557395
9 changed files with 13 additions and 12 deletions

View file

@ -0,0 +1,140 @@
;;; tools/pass/autoload.el -*- lexical-binding: t; -*-
(defun +pass--open-url (entry)
(if-let* ((url (+pass-get-field entry +pass-url-fields)))
(and (or (string-match-p "https?://" url)
(error "Field for %s doesn't look like an url" item))
(browse-url url))
(error "url not found.")))
(defun +pass--copy (entry field text)
(password-store-clear)
(message "Copied %s's %s field to clipboard. Will clear in %s seconds"
entry field (password-store-timeout))
(kill-new text)
(setq password-store-timeout-timer
(run-at-time (password-store-timeout) nil 'password-store-clear)))
(defun +pass--copy-username (entry)
(if-let* ((user (+pass-get-field entry +pass-user-fields)))
(progn (password-store-clear)
(message "Copied username to the kill ring.")
(kill-new user))
(error "Username not found.")))
(defun +pass--completing-read-field (entry)
(let* ((data (+pass-get-entry entry))
(field (if data (completing-read "Field: " (mapcar #'car data) nil t))))
(+pass-get-field data field)))
;;
;; API
;;;###autoload (autoload 'auth-source-pass-parse-entry "auth-source-pass")
;;;###autoload
(defalias '+pass-get-entry #'auth-source-pass-parse-entry)
;;;###autoload
(defun +pass-get-field (entry fields &optional noerror)
"Fetches the value of a field. FIELDS can be a list of string field names or a
single one. If a list, the first field found will be returned. Will error out
otherwise, unless NOERROR is non-nill."
(if-let* ((data (if (listp entry) entry (+pass-get-entry entry))))
(cl-loop for key in (doom-enlist fields)
when (assoc key data)
return (cdr it))
(unless noerror
(error "Couldn't find entry: %s" entry))))
;;;###autoload
(defun +pass-get-user (entry)
"Fetches the user field from ENTRY. Each of `+pass-user-fields' are tried in
search of your username. May prompt for your gpg passphrase."
(+pass-get-field entry +pass-user-fields))
;;;###autoload
(defun +pass-get-secret (entry)
"Fetches your secret from ENTRY. May prompt for your gpg passphrase."
(+pass-get-field entry 'secret))
;;
;; Commands
;;;###autoload (autoload 'password-store-dir "password-store")
;;;###autoload (autoload 'password-store-list "password-store")
;;;###autoload (autoload 'password-store--completing-read "password-store")
;;;###autoload
(defun +pass/edit-entry (entry)
"Interactively search for and open a pass entry for editing."
(interactive
(list (password-store--completing-read)))
(find-file (concat (expand-file-name entry (password-store-dir)) ".gpg")))
;;;###autoload
(defun +pass/copy-field (entry)
"Interactively search for an entry and copy a particular field to your
clipboard."
(interactive
(list (password-store--completing-read)))
(let* ((data (+pass-get-entry entry))
(field (if data (completing-read "Field: " (mapcar #'car data) nil t))))
(+pass--copy entry field (+pass-get-field data field))))
;;;###autoload
(defun +pass/copy-secret (entry)
"Interactively search for an entry and copy its secret/password to your
clipboard."
(interactive
(list (password-store--completing-read)))
(+pass--copy entry 'secret (+pass-get-secret entry)))
;;;###autoload
(defun +pass/copy-user (entry)
"Interactively search for an entry and copy the login to your clipboard. The
fields in `+pass-user-fields' is used to find the login field."
(interactive
(list (password-store--completing-read)))
(+pass--copy-username entry))
;;;###autoload
(defun +pass/browse-url (entry)
"Interactively search for an entry and open its url in your browser. The
fields in `+pass-url-fields' is used to find the url field."
(interactive
(list (password-store--completing-read)))
(+pass--open-url entry))
;;
;; Ivy interface
;;;###autoload
(defun +pass/ivy (arg)
"TODO"
(interactive "P")
(ivy-read "Pass: " (password-store-list)
:action (if arg
#'password-store-url
#'password-store-copy)
:caller '+pass/ivy))
(after! ivy
(ivy-add-actions
'+pass/ivy
'(("o" password-store-copy "copy password")
("e" +pass/edit-entry "edit entry")
("u" +pass/copy-user "copy username")
("b" +pass/copy-url "open url in browser")
("f" +pass/copy-field "get field"))))
;;
;; TODO Helm interface
;; (defun +pass/helm ()
;; (interactive)
;; )

View file

@ -0,0 +1,40 @@
;;; tools/pass/config.el -*- lexical-binding: t; -*-
(defvar +pass-user-fields '("login" "user" "username" "email")
"A list of fields for `+pass/ivy' to search for the username.")
(defvar +pass-url-fields '("url" "site" "location")
"A list of fields for `+pass/ivy' to search for the username.")
;;
;; Packages
;; `password-store'
(setq password-store-password-length 12)
;; Fix hard-coded password-store location; respect PASSWORD_STORE_DIR envvar
(defun +pass*read-entry (entry)
"Return a string with the file content of ENTRY."
(with-temp-buffer
(insert-file-contents
(expand-file-name (format "%s.gpg" entry) (password-store-dir)))
(buffer-substring-no-properties (point-min) (point-max))))
(advice-add #'auth-source-pass--read-entry :override #'+pass*read-entry)
;; `pass'
(after! pass
(set-evil-initial-state! 'pass-mode 'emacs)
(set-popup-rule! "^\\*Password-Store" :side 'left :size 0.25 :quit nil)
(define-key! pass-mode-map
"j" #'pass-next-entry
"k" #'pass-prev-entry
"d" #'pass-kill
"\C-j" #'pass-next-directory
"\C-k" #'pass-prev-directory))
;; Is built into Emacs 26+
(when (and EMACS26+ (featurep! +auth))
(auth-source-pass-enable))

View file

@ -0,0 +1,15 @@
;; -*- no-byte-compile: t; -*-
;;; tools/pass/packages.el
(package! pass)
(package! password-store)
(package! password-store-otp)
;; `auto-source-pass' is built into Emacs 26+
(unless EMACS26+
(package! auth-source-pass))
(when (featurep! :completion ivy)
(package! ivy-pass))
(when (featurep! :completion helm)
(package! helm-pass))

View file

@ -0,0 +1,41 @@
;; -*- no-byte-compile: t; -*-
;;; tools/pass/test/test-pass.el
(describe "tools/pass"
(before-all
(load! "../autoload"))
(before-each
(spy-on 'auth-source-pass-parse-entry :and-call-fake
(lambda (entry)
(when (equal entry "fake/source")
'((secret . "defuse-account-gad")
("login" . "HL2532")
("alt-login" . "hlissner")
("email" . "henrik@lissner.net")
("url" . "https://some-place.net/login"))))))
(describe "get field"
(it "returns specific fields"
(expect (+pass-get-field "fake/source" "email")
:to-equal "henrik@lissner.net"))
(it "returns first existing of a list of fields"
(expect (+pass-get-field "fake/source" '("alt-login" "email"))
:to-equal "hlissner")
(expect (+pass-get-field "fake/source" '("username" "email"))
:to-equal "henrik@lissner.net"))
(it "returns nil for missing fields"
(expect (+pass-get-field "fake/source" '("x" "y" "z"))
:to-be nil))
(it "throws error on missing entries"
(expect (+pass-get-field "nonexistent/source" "login")
:to-throw)))
(describe "get user/secret"
(it "returns the user"
(let ((+pass-user-fields '("login" "user" "username" "email")))
(expect (+pass-get-user "fake/source")
:to-equal "HL2532")))
(it "returns the secret"
(expect (+pass-get-secret "fake/source")
:to-equal "defuse-account-gad"))))