my-emacs-d/elpa/id-manager-20160425.216/id-manager.el

832 lines
29 KiB
EmacsLisp
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

;;; id-manager.el --- id-password management
;; Copyright (C) 2009, 2010, 2011, 2013 SAKURAI Masashi
;; Time-stamp: <2015-06-06 12:38:31 sakurai>
;; Author: SAKURAI Masashi <m.sakurai atmark kiwanami.net>
;; Keywords: password, convenience
;; Package-Version: 20160425.216
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; ID-password management utility.
;; This utility manages ID-password list and generates passwords.
;; The ID-password DB is saved in the tab-separated file. The default
;; file name of the DB `idm-database-file' is "~/.idm-db.gpg".
;; The file format is following:
;; (name)^t(ID)^t(password)^t(Update date "YYYY/MM/DD")[^t(memo)]
;; . One can prepare an initial data or modify the data by hand or
;; the Excel.
;;
;; Implicitly, this elisp program expects that the DB file is
;; encrypted by the some GPG encryption elisp, such as EasyPG or
;; alpaca.
;;
;; Excuting the command `idm-open-list-command', you can open the
;; ID-password list buffer. Check the function `describe-bindings'.
;;; Installation:
;; To use this program, locate this file to load-path directory,
;; and add the following code to your .emacs.
;; ------------------------------
;; (require 'id-manager)
;; ------------------------------
;; If you have helm.el, bind `id-manager' to key,
;; like (global-set-key (kbd "M-7") 'id-manager).
;;; Setting example:
;; For EasyPG users:
;;
;; (autoload 'id-manager "id-manager" nil t)
;; (global-set-key (kbd "M-7") 'id-manager) ; helm UI
;; (setq epa-file-cache-passphrase-for-symmetric-encryption t) ; saving password
;; (setenv "GPG_AGENT_INFO" nil) ; non-GUI password dialog.
;; For alpaca users:
;;
;; (autoload 'id-manager "id-manager" nil t)
;; (global-set-key (kbd "M-7") 'id-manager) ; helm UI
;; (setq idm-db-buffer-save-function ; adjustment for alpaca.el
;; (lambda (file)
;; (set-visited-file-name file)
;; (alpaca-save-buffer))
;; idm-db-buffer-password-var ; if you are using `alpaca-cache-passphrase'.
;; 'alpaca-passphrase)
;;; Current implementation:
;; This program generates passwords by using external command:
;; `idm-gen-password-cmd'. If you have some better idea, please let me
;; know.
;;
;; I think that this program makes lazy password management more
;; securely. But I'm not sure that this program is secure enough.
;; I'd like many people to check and advice me.
;;; Code:
(eval-when-compile (require 'cl))
(require 'widget)
(eval-when-compile (require 'wid-edit))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Setting
(defvar idm-database-file "~/.idm-db.gpg"
"Encripted id-password database file. The file name may
end with '.gpg' for encryption by the GnuPG.")
(defvar idm-gen-password-cmd
"head -c 10 < /dev/random | uuencode -m - | tail -n 2 |head -n 1 | head -c10"
"[String] Password generation command. If a function symbol or
lambda whose receive no parameter is set to this variable,
id-manager calls the function to generate the password.")
;; "openssl rand 32 | uuencode -m - | tail -n 2 |head -n 1 | head -c10"
;; ...any other password generation ?
(defvar idm-copy-action
(lambda (text) (x-select-text text))
"Action for copying a password text into clipboard.")
(defvar idm-db-buffer-load-function
'find-file-noselect
"File loading function. This function has one argument FILENAME and returns a buffer,
like `find-file-noselect'. Some decryption should work at this
function.")
(defvar idm-db-buffer-save-function
'write-file
"File saving function. This function has one arguments FILENAME,
like `write-file'. Some encryption should work at this
function.")
(defvar idm-db-buffer-password-var nil
"Password variable. See the text of settings for alpaca.el. ")
(defvar idm-clipboard-expire-time-sec 5
"Expire time for the clipboard content.")
(defvar idm-clipboard-expire-timer nil
"The timer object that will expire the clipboard content.")
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Macros
(defmacro idm--aif (test-form then-form &rest else-forms)
`(let ((it ,test-form))
(if it ,then-form ,@else-forms)))
(put 'idm--aif 'lisp-indent-function 2)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Management API
(defun idm-gen-password ()
"Generate a password."
(cond
((functionp idm-gen-password-cmd)
(funcall idm-gen-password-cmd))
((stringp idm-gen-password-cmd)
(let ((buf (get-buffer-create " *idm-work*")) ret)
(call-process-shell-command
idm-gen-password-cmd
nil buf nil)
(with-current-buffer buf
(setq ret (buffer-string)))
(kill-buffer buf)
ret))
(t (error "idm-gen-password-cmd is set to wrong value. [%S]"
idm-gen-password-cmd))))
;; record struct
(defstruct (idm-record
(:constructor make-idm-record-bylist
(name account-id password update-time
&optional memo)))
name account-id password update-time memo)
(defun idm-load-db ()
"Load the DB file `idm-database-file' and make a DB object."
(let* ((coding-system-for-read 'utf-8)
(tmpbuf
(funcall idm-db-buffer-load-function
(expand-file-name idm-database-file)))
db-object)
(unwind-protect
(let ((db (idm--make-db tmpbuf)))
(when idm-db-buffer-password-var
(with-current-buffer tmpbuf
(funcall db 'file-password
(symbol-value idm-db-buffer-password-var))))
db)
(kill-buffer tmpbuf))))
(defun idm--save-db (records file-vars &optional password)
"Save RECORDS into the DB file `idm-database-file'. This
function is called by a DB object."
(let ((coding-system-for-write 'utf-8)
(tmpbuf (get-buffer-create " *idm-tmp*")))
(with-current-buffer tmpbuf
(erase-buffer)
(goto-char (point-min))
(loop for (sym . v) in file-vars do
(set (make-local-variable sym) v))
(insert (format
";; -*- %s -*-"
(loop for (n . v) in file-vars concat
(format "%s: %S; " n v))) "\n")
(dolist (i records)
(insert (concat (idm-record-name i) "\t"
(idm-record-account-id i) "\t"
(idm-record-password i) "\t"
(idm-record-update-time i)
(idm--aif (idm-record-memo i)
(concat "\t" it))
"\n")))
(when password
(set idm-db-buffer-password-var password))
(funcall idm-db-buffer-save-function idm-database-file)
(kill-buffer tmpbuf))))
(defun idm--make-db (tmpbuf)
"Build a database management object from the given buffer text.
The object is a dispatch function. One can access the methods
`funcall' with the method name symbol and some method arguments."
(lexical-let (records
(db-modified nil)
file-vars ; file variables
file-password) ; password for alpaca
(setq file-vars (buffer-local-value 'file-local-variables-alist tmpbuf))
(idm--each-line
tmpbuf
(lambda (line)
(cond
((string-match "^;; " line) ; file variables
) ; ignore
(t ; entry lines
(let ((cols (split-string line "\t")))
(if (or (= 4 (length cols))
(= 5 (length cols)))
(push (apply 'make-idm-record-bylist cols)
records)))))))
(lambda (method &rest args)
(cond
((eq method 'get) ; get record object by name
(lexical-let ((name (car args)) ret)
(mapc (lambda (i)
(if (equal name (idm-record-name i))
(setq ret i)))
records)
ret))
((eq method 'get-all-records) records) ; get-all-records
((eq method 'add-record) ; add-record
(progn
(lexical-let* ((record (car args))
(name (idm-record-name record)))
(setf records (loop for i in records
unless (equal (idm-record-name i) name)
collect i))
(push record records)
(setq db-modified t))))
((eq method 'delete-record-by-name) ; delete-record-by-name
(lexical-let ((name (car args)))
(setf records (loop for i in records
unless (equal (idm-record-name i) name)
collect i))
(setq db-modified t)))
((eq method 'set-modified) ; set-modified
(setq db-modified t))
((eq method 'save) ; save
(when db-modified
(idm--save-db records file-vars file-password)
(setq db-modified nil)))
((eq method 'file-password) ; file-password
(setq file-password (car args)) nil)
(t (error "Unknown method [%s]" method))))))
(defun idm--each-line (buf task)
"Execute the function TASK with each lines in the buffer
`buf'. This function is called by `idm--make-db'."
(with-current-buffer buf
(goto-char (point-min))
(unless (eobp)
(while
(let ((line
(buffer-substring-no-properties
(line-beginning-position)
(line-end-position))))
(funcall task line)
(forward-line 1)
(not (eobp)))))))
(defun idm--strtime (time)
"Translate emacs time to formatted string."
(format-time-string "%Y/%m/%d" time))
(defun idm--parsetime (str)
"Translate formatted string to emacs time."
(when (string-match "\\([0-9]+\\)\\/\\([0-9]+\\)\\/\\([0-9]+\\)" str)
(apply 'encode-time
(let (ret)
(dotimes (i 6)
(push (string-to-number (match-string (+ i 1) str)) ret))
ret))))
(defun idm--message (&rest args)
"Show private text in the echo area without message buffer
recording."
(let (message-log-max)
(apply 'message args)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; GUI
(defvar idm-show-password nil
"Display passwords switch. If this variable is non-nil, some
functions show the password as plain text.")
(defun idm-toggle-show-password ()
"Toggle the switch for display passwords. This function does not update views."
(interactive)
(setq idm-show-password (not idm-show-password)))
(defun idm-add-record-dialog (db on-ok-func)
"Make an account record interactively and register it with DB."
(lexical-let ((db db) (on-ok-func on-ok-func))
(idm-edit-record-dialog
(make-idm-record)
(lambda (r)
(cond
((funcall db 'get (idm-record-name r))
(idm-edit-record-dialog r on-ok-func nil "Record [%s] exists!"))
(t (funcall on-ok-func r)))))))
(defun idm-edit-record-dialog (record on-ok-func &optional password-show error-msg)
"Pop up the edit buffer for the given record.
If the user pushes the `ok' button, the function
`idm-edit-record-dialog-commit' is called."
(let ((before-win-num (length (window-list)))
(main-buf (current-buffer))
(edit-buf (idm--edit-record-dialog-buffer record on-ok-func password-show error-msg)))
(with-current-buffer edit-buf
(set (make-local-variable 'idm-before-win-num) before-win-num)
(set (make-local-variable 'idm-main-buf) main-buf))
(pop-to-buffer edit-buf)))
(defun idm--edit-record-dialog-buffer (record on-ok-func &optional password-show error-msg)
"Return the editing buffer for the given record."
(let ((buf (get-buffer-create "*idm-record-dialog*")))
(with-current-buffer buf
(let ((inhibit-read-only t)) (erase-buffer))
(kill-all-local-variables)
(remove-overlays)
(widget-insert
(format "Record: %s\n\n"
(idm--aif (idm-record-name record) it "(new record)")))
(when error-msg
(widget-insert
(let ((text (substring-no-properties error-msg)))
(put-text-property 0 (length text) 'face 'font-lock-warning-face text)
text))
(widget-insert "\n\n"))
(lexical-let
((record record) (on-ok-func on-ok-func) (error-msg error-msg)
fname fid fpassword fmemo cbshow bgenerate fields)
;; create dialog fields
(setq fname (widget-create
'editable-field
:size 20 :format " Account Name: %v \n"
:value (or (idm-record-name record) ""))
fid (widget-create
'editable-field
:size 20 :format " Account ID : %v \n"
:value (or (idm-record-account-id record) ""))
fpassword (widget-create
'editable-field
:size 20 :format " Password: %v \n"
:secret (and (not password-show) ?*)
:value (or (idm-record-password record) "")))
(widget-insert " (show password ")
(setq cbshow
(widget-create 'checkbox :value password-show))
(widget-insert " ) ")
(setq bgenerate
(widget-create 'push-button "Generate"))
(widget-insert "\n")
(setq fmemo (widget-create
'editable-field
:size 20
:format " Memo : %v \n"
:value (or (idm-record-memo record) "")))
(setq fields
(list 'name fname 'id fid 'password fpassword 'memo fmemo 'password-show cbshow))
;; OK / Cancel
(widget-insert "\n")
(widget-create
'push-button
:notify (lambda (&rest ignore)
(idm-edit-record-dialog-commit record fields on-ok-func))
"Ok")
(widget-insert " ")
(widget-create
'push-button
:notify (lambda (&rest ignore)
(idm-edit-record-kill-buffer))
"Cancel")
(widget-insert "\n")
;; add event actions
(widget-put cbshow
:notify
(lambda (&rest ignore)
(let ((current-record
(make-idm-record
:name (widget-value fname)
:account-id (widget-value fid)
:password (widget-value fpassword)
:memo (widget-value fmemo)))
(password-show (widget-value cbshow)))
(message "CLICK : %s" password-show)
(idm-edit-record-kill-buffer)
(idm-edit-record-dialog
current-record on-ok-func password-show error-msg)
(widget-forward 3))))
(widget-put bgenerate
:notify
(lambda (&rest ignore)
(widget-value-set fpassword (idm-gen-password))
(widget-setup)))
;; setup widgets
(use-local-map widget-keymap)
(widget-setup)
(goto-char (point-min))
(widget-forward 1)))
buf))
(defun idm-edit-record-dialog-commit (record fields on-ok-func)
"edit-record-dialog-commit"
(let ((name-value (widget-value (plist-get fields 'name))))
(cond
((or (null name-value)
(string-equal "" name-value))
(idm-edit-record-kill-buffer)
(idm-edit-record-dialog
record on-ok-func
(widget-value (plist-get fields 'password-show))
"Should not be empty!"))
(t
(setf (idm-record-name record) name-value
(idm-record-account-id record)
(widget-value (plist-get fields 'id))
(idm-record-password record)
(widget-value (plist-get fields 'password))
(idm-record-memo record)
(widget-value (plist-get fields 'memo))
(idm-record-update-time record) (idm--strtime (current-time)))
(idm-edit-record-kill-buffer)
(funcall on-ok-func record)))))
(defun idm-edit-record-kill-buffer ()
"edit-record-kill-buffer"
(interactive)
(let ((cbuf (current-buffer))
(win-num (length (window-list)))
(next-win (get-buffer-window idm-main-buf)))
(when (and (not (one-window-p))
(> win-num idm-before-win-num))
(delete-window))
(kill-buffer cbuf)
(when next-win (select-window next-win))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; id-password list buffer
(defun idm-open-list (db)
"Open id-password list buffer."
(lexical-let ((buf (get-buffer-create "ID-Password List"))
(db db))
(with-current-buffer buf
(idm--layout-list db)
(idm--set-list-mode db)
)
(set-buffer buf)))
(defun idm--put-text-property (text attr val)
"Put a text property on the whole text."
(put-text-property 0 (length text) attr val text) text)
(defun idm--put-record-id (text id)
"Put the record id with the text property `idm-record-id'."
(idm--put-text-property text 'idm-record-id id))
(defun idm--get-record-id ()
"Get the record id on the current point."
(get-text-property (point) 'idm-record-id))
(defun idm--layout-list (db &optional order)
"Erase the content in the current buffer and insert record
lines. ORDER is sort key, which can be `time', `name' and `id'."
(unless order
(setq order 'name))
(let* ((name-max (length "Account Name"))
(id-max (length "ID"))
(pw-max (length "Password"))
(pw-mask "********")
(pw-getter (lambda (record)
(if idm-show-password
(idm-record-password record)
pw-mask)))
(cut (lambda (str) (substring str 0 (min (length str) 20))))
numcolm (count 1)
(line-format "%%-%ds|%%-10s | %%-%ds | %%-%ds : %%-%ds : %%s\n")
(records (funcall db 'get-all-records)))
(when records
(setq numcolm (fceiling (log10 (length records))))
(dolist (i records)
(setq name-max (min 20 (max name-max (length (idm-record-name i))))
id-max (min 20 (max id-max (length (idm-record-account-id i))))
pw-max (max pw-max (length (funcall pw-getter i)))))
(setq line-format (format line-format numcolm name-max id-max pw-max))
(let ((buffer-read-only nil) (prev-line (line-number-at-pos)))
(erase-buffer)
(goto-char (point-min))
(insert (format line-format
" " "Time" "Name" "ID" "Password" "Memo"))
(insert (make-string (- (window-width) 1) ?-) "\n")
(dolist (i (idm--sort-records records order))
(insert
(idm--put-record-id
(format line-format
count
(idm-record-update-time i)
(funcall cut (idm-record-name i))
(funcall cut (idm-record-account-id i))
(funcall pw-getter i)
(idm-record-memo i))
(idm-record-name i)))
(incf count))
(goto-char (point-min))
(when (< 1 prev-line)
(ignore-errors
(forward-line (1- prev-line))))))))
(defun idm--sort-records (records order)
"Sort records by the key ORDER, which can be `time', `name',
`memo' and `id'."
(let*
((comparator
(lambda (ref)
(lexical-let ((ref ref))
(lambda (i j)
(let ((ii (funcall ref i))
(jj (funcall ref j)))
(cond
((string= ii jj) 0)
((string< ii jj) -1)
(t 1)))))))
(to-bool
(lambda (f)
(lexical-let ((f f))
(lambda (i j)
(< (funcall f i j) 0)))))
(cmp-id (funcall comparator 'idm-record-account-id))
(cmp-name (funcall comparator 'idm-record-name))
(cmp-time (funcall comparator 'idm-record-update-time))
(cmp-memo (funcall comparator 'idm-record-memo))
(chain
(lambda (a b)
(lexical-let ((a a) (b b))
(lambda (i j)
(let ((v (funcall a i j)))
(if (= 0 v)
(funcall b i j)
v)))))))
(sort
(loop for i in records collect i) ; copy-list
(cond
((eq order 'id) ; id -> id, name
(funcall to-bool (funcall chain cmp-id cmp-name)))
((eq order 'name) ; name -> name
(funcall to-bool cmp-name))
((eq order 'time) ; time -> time, name
(funcall to-bool (funcall chain cmp-time cmp-name)))
((eq order 'memo) ; memo -> time, name
(funcall to-bool (funcall chain cmp-memo cmp-name)))
(t ; default
(funcall to-bool cmp-name))))))
(defvar idm-list-mode-map nil
"Keymap for `idm-list-mode'.")
(setq idm-list-mode-map nil) ; for debug
(unless idm-list-mode-map
(setq idm-list-mode-map (make-sparse-keymap))
(mapc (lambda (i)
(define-key idm-list-mode-map (car i) (cdr i)))
`(("q" . idm-list-mode-quit)
("Q" . idm-list-mode-quit-without-save)
("n" . next-line)
("p" . previous-line)
("j" . next-line)
("k" . previous-line)
("d" . idm-list-mode-delete)
("-" . idm-list-mode-delete)
("e" . idm-list-mode-edit-dialog)
("m" . idm-list-mode-edit-dialog)
("a" . idm-list-mode-add)
("+" . idm-list-mode-add)
("u" . idm-list-mode-reload)
("r" . idm-list-mode-reload)
("T" . idm-list-mode-sortby-time)
("N" . idm-list-mode-sortby-name)
("I" . idm-list-mode-sortby-id)
("M" . idm-list-mode-sortby-memo)
("S" . idm-list-mode-toggle-show-password)
("s" . idm-list-mode-show-password)
([return] . idm-list-mode-copy)
)))
(defun idm-list-mode-copy ()
(interactive)
(idm--aif (idm--get-record-id)
(let ((record (funcall idm-db 'get it)))
(when record
(idm--copy-password-action record)))))
(defun idm--copy-password-action (record)
(interactive)
(message (concat "Copied the password for the account ID: "
(idm-record-account-id record)))
(funcall idm-copy-action (idm-record-password record))
(idm-set-clipboard-expiry))
(defun idm--copy-id-action (record)
(interactive)
(message (concat "Copied account ID: "
(idm-record-account-id record)))
(funcall idm-copy-action (idm-record-account-id record))
(idm-set-clipboard-expiry))
(defun idm-set-clipboard-expiry ()
(when idm-clipboard-expire-timer
(cancel-timer idm-clipboard-expire-timer))
(when idm-clipboard-expire-time-sec
(setq idm-clipboard-expire-timer
(run-at-time idm-clipboard-expire-time-sec nil 'idm-expire-clipboard))))
(defun idm-expire-clipboard ()
"Clear clipboard"
(funcall idm-copy-action "")
(idm--message "ID Manager: expired."))
(defun idm-list-mode-sortby-id ()
(interactive)
(idm--layout-list idm-db 'id))
(defun idm-list-mode-sortby-name ()
(interactive)
(idm--layout-list idm-db 'name))
(defun idm-list-mode-sortby-time ()
(interactive)
(idm--layout-list idm-db 'time))
(defun idm-list-mode-sortby-memo ()
(interactive)
(idm--layout-list idm-db 'memo))
(defun idm-list-mode-reload ()
"Reload the id-password database file."
(interactive)
(setq idm-db (idm-load-db))
(idm--layout-list idm-db))
(defun idm-list-mode-toggle-show-password ()
"Toggle whether to show passwords."
(interactive)
(idm-toggle-show-password)
(idm--layout-list idm-db))
(defun idm-list-mode-show-password ()
"Show password of the selected record."
(interactive)
(idm--aif (idm--get-record-id)
(let ((record (funcall idm-db 'get it)))
(if record
(idm--message
(concat
"ID: " (idm-record-account-id record)
" / PW: "(idm-record-password record)))))))
(defun idm--set-list-mode (db)
"Set up major mode for id-password list mode."
(kill-all-local-variables)
(make-local-variable 'idm-db)
(setq idm-db db)
(setq truncate-lines t)
(use-local-map idm-list-mode-map)
(setq major-mode 'idm-list-mode
mode-name "ID-Password List")
(hl-line-mode 1))
(defun idm-list-mode-quit ()
"Save the DB and kill buffer."
(interactive)
(funcall idm-db 'save)
(kill-buffer (current-buffer)))
(defun idm-list-mode-quit-without-save ()
"Kill buffer without saving the DB."
(interactive)
(kill-buffer (current-buffer)))
(defun idm-list-mode-delete ()
"Delete a selected record from the DB. After deleting, update
the list buffer."
(interactive)
(idm--aif (idm--get-record-id)
(progn
(when (y-or-n-p (format "Delete this record[%s] ?" it))
(funcall idm-db 'delete-record-by-name it)
(idm--layout-list idm-db)))))
(defun idm-list-mode-add ()
"Add a new record. After adding, update the list buffer."
(interactive)
(lexical-let ((db idm-db)
(curbuf (current-buffer)))
(idm-add-record-dialog db
(lambda (r)
(with-current-buffer curbuf
(funcall db 'add-record r)
(idm--layout-list db))))))
(defun idm-list-mode-edit-dialog ()
"Edit the selected record. After editting, update the list
buffer."
(interactive)
(idm--aif (idm--get-record-id)
(let ((record (funcall idm-db 'get it)))
(if record
(lexical-let ((db idm-db) (prev record)
(curbuf (current-buffer)))
(idm-edit-record-dialog
record
(lambda (r)
(with-current-buffer curbuf
(funcall db 'delete-record-by-name (idm-record-name prev))
(funcall db 'add-record r)
(idm--layout-list db)))))))))
;;;###autoload
(defun idm-open-list-command (&optional db)
"Load the id-password DB and open a list buffer."
(interactive)
(unless db
(setq db (idm-load-db)))
(switch-to-buffer (idm-open-list db)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Helm UI
(defun idm--helm-add-dialog (db)
"Add a new record by the helm interface."
(interactive)
(lexical-let ((db db))
(idm-add-record-dialog db
(lambda (r)
(funcall db 'add-record r)
(funcall db 'save)
(when (eq major-mode 'idm-list-mode)
(idm--layout-list db))))))
(defun idm--helm-edit-dialog (db record)
"Edit a record selected by the helm interface."
(interactive)
(lexical-let ((db db) (prev record))
(idm-edit-record-dialog
record
(lambda (r)
(funcall db 'delete-record-by-name (idm-record-name prev))
(funcall db 'add-record r)
(funcall db 'save)
(when (eq major-mode 'idm-list-mode)
(idm--layout-list db))))))
;;;###autoload
(defun idm-helm-command ()
"Helm interface for id-manager."
(interactive)
(let* ((db (idm-load-db))
(source-commands
(helm-build-sync-source "id-manager-global-command"
:name "Global Command : "
:candidates '(("Add a record" . (lambda ()
(idm--helm-add-dialog db)))
("Show all records" . (lambda ()
(idm-open-list-command db))))
:action (helm-make-actions "Execute" (lambda (i) (funcall i)))))
(souce-records
(helm-build-sync-source "id-manager-source-commands"
:name "Accounts : "
:candidates (lambda ()
(mapcar
(lambda (record)
(cons (concat
(idm-record-name record)
" (" (idm-record-account-id record) ") "
" " (idm-record-memo record))
record))
(funcall db 'get-all-records)))
:action (helm-make-actions "Copy password" (lambda (record)
(idm--copy-password-action record))
"Copy id" (lambda (record)
(idm--copy-id-action record))
"Show ID / Password" (lambda (record)
(idm--message
(concat
"ID: " (idm-record-account-id record)
" / PW: "(idm-record-password record))))
"Edit fields" (lambda (record)
(idm--helm-edit-dialog db record)))
:migemo t)))
(helm
:sources '(source-commands souce-records)
:buffer "ID-Password Management : ")))
(defalias 'id-manager 'idm-open-list-command)
(eval-after-load "helm"
'(defalias 'id-manager 'idm-helm-command))
(provide 'id-manager)
;;; id-manager.el ends here