org-passwords.el: Fix `org-passwords-generate-password-with-symbols` to not insert...
[org-mode.git] / contrib / lisp / org-passwords.el
blob9c3a9166d670a7d18440b01d07325021b2b2ee16
1 ;;; org-passwords.el --- org derived mode for managing passwords
3 ;; Author: Jorge A. Alfaro-Murillo <jorge.alfaro-murillo@yale.edu>
4 ;; Created: December 26, 2012
5 ;; Keywords: passwords, password
7 ;; This file is NOT part of GNU Emacs.
8 ;;
9 ;; This program is free software: you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation, either version 3 of the License, or
12 ;; (at your option) any later version.
14 ;; This program is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 ;; GNU General Public License for more details.
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
21 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
23 ;;; Commentary:
25 ;; This file contains the code for managing your passwords with
26 ;; Org-mode.
28 ;; A basic setup needs to indicate a passwords file, and a dictionary
29 ;; for the random words:
31 ;; (require org-passwords)
32 ;; (setq org-passwords-file "~/documents/passwords.gpg")
33 ;; (setq org-passwords-random-words-dictionary "/etc/dictionaries-common/words")
35 ;; Basic usage:
37 ;; `M-x org-passwords' opens the passwords file in
38 ;; `org-passwords-mode'.
40 ;; `M-x org-passwords-generate-password' generates a random string
41 ;; of numbers, lowercase letters and uppercase letters.
43 ;; `C-u M-x org-passwords-generate-password' generates a random
44 ;; string of numbers, lowercase letters, uppercase letters and
45 ;; symbols.
47 ;; `M-x org-passwords-random-words' concatenates random words from
48 ;; the dictionary defined by `org-passwords-random-words-dictionary'
49 ;; into a string, each word separated by the string defined in
50 ;; `org-passwords-random-words-separator'.
52 ;; `C-u M-x org-passwords-random-words' does the same as above, and
53 ;; also makes substitutions according to
54 ;; `org-passwords-random-words-substitutions'.
56 ;; It is also useful to set up keybindings for the functions
57 ;; `org-passwords-copy-username' and
58 ;; `org-passwords-copy-password' in the
59 ;; `org-passwords-mode', to easily make the passwords and usernames
60 ;; available to the facility for pasting text of the window system
61 ;; (clipboard on X and MS-Windows, pasteboard on Nextstep/Mac OS,
62 ;; etc.), without inserting them in the kill-ring. You can set for
63 ;; example:
65 ;; (eval-after-load "org-passwords"
66 ;; '(progn
67 ;; (define-key org-passwords-mode-map
68 ;; (kbd "C-c u")
69 ;; 'org-passwords-copy-username)
70 ;; (define-key org-passwords-mode-map
71 ;; (kbd "C-c p")
72 ;; 'org-passwords-copy-password)))
74 ;; Finally, to enter new passwords, you can use `org-capture' and a minimal template like:
76 ;; ("p" "password" entry (file "~/documents/passwords.gpg")
77 ;; "* %^{Title}\n %^{PASSWORD}p %^{USERNAME}p")
79 ;; When asked for the password you can then call either
80 ;; `org-passwords-generate-password' or `org-passwords-random-words'.
81 ;; Be sure to enable recursive minibuffers to call those functions
82 ;; from the minibuffer:
84 ;; (setq enable-recursive-minibuffers t)
86 ;;; Code:
88 (require 'org)
90 (define-derived-mode org-passwords-mode org-mode
91 "org-passwords-mode"
92 "Mode for storing passwords"
93 nil)
95 (defgroup org-passwords nil
96 "Options for password management."
97 :group 'org)
99 (defcustom org-passwords-password-property "PASSWORD"
100 "Name of the property for password entry password."
101 :type 'string
102 :group 'org-passwords)
104 (defcustom org-passwords-username-property "USERNAME"
105 "Name of the property for password entry user name."
106 :type 'string
107 :group 'org-passwords)
109 (defcustom org-passwords-file nil
110 "Default file name for the file that contains the passwords."
111 :type 'file
112 :group 'org-passwords)
114 (defcustom org-passwords-time-opened "1 min"
115 "Time that the password file will remain open. It has to be a
116 string, a number followed by units."
117 :type 'str
118 :group 'org-passwords)
120 (defcustom org-passwords-random-words-dictionary nil
121 "Default file name for the file that contains a dictionary of
122 words for `org-passwords-random-words'. Each non-empty line in
123 the file is considered a word."
124 :type 'file
125 :group 'org-passwords)
127 (defvar org-passwords-random-words-separator "-"
128 "A string to separate words in `org-passwords-random-words'.")
130 (defvar org-passwords-random-words-substitutions
131 '(("a" . "@")
132 ("e" . "3")
133 ("o" . "0"))
134 "A list of substitutions to be made with
135 `org-passwords-random-words' if it is called with
136 `universal-argument'. Each element is pair of
137 strings (SUBSTITUTE-THIS . BY-THIS).")
139 (defun org-passwords-copy-password ()
140 "Makes the password available to other programs. Puts the
141 password of the entry at the location of the cursor in the
142 facility for pasting text of the window system (clipboard on X
143 and MS-Windows, pasteboard on Nextstep/Mac OS, etc.), without
144 putting it in the kill ring."
145 (interactive)
146 (save-excursion
147 (search-backward-regexp "^\\*")
148 (search-forward-regexp (concat "^[[:space:]]*:"
149 org-passwords-password-property
150 ":[[:space:]]*"))
151 (funcall interprogram-cut-function
152 (buffer-substring-no-properties (point)
153 (funcall (lambda ()
154 (end-of-line)
155 (point)))))))
157 (defun org-passwords-copy-username ()
158 "Makes the password available to other programs. Puts the
159 username of the entry at the location of the cursor in the
160 facility for pasting text of the window system (clipboard on X
161 and MS-Windows, pasteboard on Nextstep/Mac OS, etc.), without
162 putting it in the kill ring."
163 (interactive)
164 (save-excursion
165 (search-backward-regexp "^\\*")
166 (search-forward-regexp (concat "^[[:space:]]*:"
167 org-passwords-username-property
168 ":[[:space:]]*"))
169 (funcall interprogram-cut-function
170 (buffer-substring-no-properties (point)
171 (funcall (lambda ()
172 (end-of-line)
173 (point)))))))
175 (defun org-passwords ()
176 "Open the password file. Open the password file defined by the
177 variable `org-password-file' in read-only mode and kill that
178 buffer later according to the value of the variable
179 `org-passwords-time-opened'. It also adds the `org-password-file'
180 to the auto-mode-alist so that it is opened with its mode being
181 `org-passwords-mode'."
182 (interactive)
183 (if org-passwords-file
184 (progn
185 (add-to-list 'auto-mode-alist
186 (cons
187 (regexp-quote
188 (expand-file-name org-passwords-file))
189 'org-passwords-mode))
190 (find-file-read-only org-passwords-file)
191 (org-passwords-set-up-kill-password-buffer))
192 (minibuffer-message "No default password file defined. Set the variable `org-password-file'.")))
194 (defun org-passwords-set-up-kill-password-buffer ()
195 (run-at-time org-passwords-time-opened
197 '(lambda ()
198 (if (get-file-buffer org-passwords-file)
199 (kill-buffer
200 (get-file-buffer org-passwords-file))))))
202 ;;; Password generator
204 ;; Set random number seed from current time and pid. Otherwise
205 ;; `random' gives the same results every time emacs restarts.
206 (random t)
208 (defun org-passwords-generate-password (arg)
209 "Ask a number of characters and insert a password of that size.
210 Password has a random string of numbers, lowercase letters, and
211 uppercase letters. Argument ARG include symbols."
212 (interactive "P")
213 (let ((number-of-chars
214 (string-to-number
215 (read-from-minibuffer "Number of Characters: "))))
216 (if arg
217 (insert (org-passwords-generate-password-with-symbols "" number-of-chars))
218 (insert (org-passwords-generate-password-without-symbols "" number-of-chars)))))
220 (defun org-passwords-generate-password-with-symbols (previous-string nums-of-chars)
221 "Return a string consisting of PREVIOUS-STRING and
222 NUMS-OF-CHARS random characters."
223 (if (eq nums-of-chars 0) previous-string
224 (org-passwords-generate-password-with-symbols
225 (concat previous-string
226 (char-to-string
227 ;; symbols, letters, numbers are from 33 to 126
228 (+ (random (- 127 33)) 33)))
229 (1- nums-of-chars))))
231 (defun org-passwords-generate-password-without-symbols (previous-string nums-of-chars)
232 "Return string consisting of PREVIOUS-STRING and NUMS-OF-CHARS
233 random numbers, lowercase letters, and numbers."
234 (if (eq nums-of-chars 0)
235 previous-string
236 ; There are 10 numbers, 26 lowercase letters and 26 uppercase
237 ; letters. 10 + 26 + 26 = 62. The number characters go from 48
238 ; to 57, the uppercase letters from 65 to 90, and the lowercase
239 ; from 97 to 122. The following makes each equally likely.
240 (let ((temp-value (random 62)))
241 (cond ((< temp-value 10)
242 ; If temp-value<10, then add a number
243 (org-passwords-generate-password-without-symbols
244 (concat previous-string
245 (char-to-string (+ 48 temp-value)))
246 (1- nums-of-chars)))
247 ((and (> temp-value 9) (< temp-value 36))
248 ; If 9<temp-value<36, then add an uppercase letter
249 (org-passwords-generate-password-without-symbols
250 (concat previous-string
251 (char-to-string (+ 65 (- temp-value 10))))
252 (1- nums-of-chars)))
253 ((> temp-value 35)
254 ; If temp-value>35, then add a lowecase letter
255 (org-passwords-generate-password-without-symbols
256 (concat previous-string
257 (char-to-string (+ 97 (- temp-value 36))))
258 (1- nums-of-chars)))))))
260 ;;; Random words
262 (defun org-passwords-random-words (arg)
263 "Ask for a number of words and inserts a sequence of that many
264 random words from the list in the file
265 `org-passwords-random-words-dictionary' separated by
266 `org-passwords-random-words-separator'. ARG make substitutions in
267 the words as defined by
268 `org-passwords-random-words-substitutions'."
269 (interactive "P")
270 (if org-passwords-random-words-dictionary
271 (let ((number-of-words
272 (string-to-number
273 (read-from-minibuffer "Number of words: ")))
274 (list-of-words
275 (with-temp-buffer
276 (insert-file-contents
277 org-passwords-random-words-dictionary)
278 (split-string (buffer-string) "\n" t))))
279 (insert
280 (org-passwords-substitute
281 (org-passwords-random-words-attach-number-of-words
282 (nth (random (length list-of-words))
283 list-of-words)
284 (1- number-of-words)
285 list-of-words
286 org-passwords-random-words-separator)
287 (if arg
288 org-passwords-random-words-substitutions
289 nil))))
290 (minibuffer-message
291 "No default dictionary file defined. Set the variable `org-passwords-random-words-dictionary'.")))
293 (defun org-passwords-random-words-attach-number-of-words
294 (previous-string number-of-words list-of-words separator)
295 "Returns a string consisting of PREVIOUS-STRING followed by a
296 succession of NUMBER-OF-WORDS random words from the list LIST-OF-WORDS
297 separated SEPARATOR."
298 (if (eq number-of-words 0)
299 previous-string
300 (org-passwords-random-words-attach-number-of-words
301 (concat previous-string
302 separator
303 (nth (random (length list-of-words)) list-of-words))
304 (1- number-of-words)
305 list-of-words
306 separator)))
308 (defun org-passwords-substitute (string-to-change list-of-substitutions)
309 "Substitutes each appearence in STRING-TO-CHANGE of the `car' of
310 each element of LIST-OF-SUBSTITUTIONS by the `cdr' of that
311 element. For example:
312 (org-passwords-substitute \"ab\" \'((\"a\" . \"b\") (\"b\" . \"c\")))
313 => \"bc\"
314 Substitutions are made in order of the list, so for example:
315 (org-passwords-substitute \"ab\" \'((\"ab\" . \"c\") (\"b\" . \"d\")))
316 => \"c\""
317 (if list-of-substitutions
318 (concat (org-passwords-concat-this-with-string
319 (cdar list-of-substitutions)
320 (mapcar (lambda (x)
321 (org-passwords-substitute
323 (cdr list-of-substitutions)))
324 (split-string string-to-change
325 (caar list-of-substitutions)))))
326 string-to-change))
328 (defun org-passwords-concat-this-with-string (this list-of-strings)
329 "Put the string THIS in between every string in LIST-OF-STRINGS. For example:
330 (org-passwords-concat-this-with-string \"Here\" \'(\"First\" \"Second\" \"Third\"))
331 => \"FirstHereSencondHereThird\""
332 (if (cdr list-of-strings)
333 (concat (car list-of-strings)
334 this
335 (org-passwords-concat-this-with-string
336 (cdr list-of-strings)
337 this))
338 (car list-of-strings)))
340 (provide 'org-passwords)
342 ;;; org-passwords.el ends here