Be more cautious when preserving visibility
[org-mode/org-kjn.git] / contrib / lisp / org-passwords.el
blob4ebd5a6ebad2977dae6250668f738f8e1b7c0d47
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. It is part of org/contrib (see http://orgmode.org/). If
27 ;; you want to contribute with development, or have a problem, do it
28 ;; here: https://bitbucket.org/alfaromurillo/org-passwords.el
30 ;; A basic setup needs to indicate a passwords file, and a dictionary
31 ;; for the random words:
33 ;; (require 'org-passwords)
34 ;; (setq org-passwords-file "~/documents/passwords.gpg")
35 ;; (setq org-passwords-random-words-dictionary "/etc/dictionaries-common/words")
37 ;; Basic usage:
39 ;; `M-x org-passwords' opens the passwords file in
40 ;; `org-passwords-mode'.
42 ;; `M-x org-passwords-generate-password' generates a random string
43 ;; of numbers, lowercase letters and uppercase letters.
45 ;; `C-u M-x org-passwords-generate-password' generates a random
46 ;; string of numbers, lowercase letters, uppercase letters and
47 ;; symbols.
49 ;; `M-x org-passwords-random-words' concatenates random words from
50 ;; the dictionary defined by `org-passwords-random-words-dictionary'
51 ;; into a string, each word separated by the string defined in
52 ;; `org-passwords-random-words-separator'.
54 ;; `C-u M-x org-passwords-random-words' does the same as above, and
55 ;; also makes substitutions according to
56 ;; `org-passwords-random-words-substitutions'.
58 ;; It is also useful to set up keybindings for the functions
59 ;; `org-passwords-copy-username', `org-passwords-copy-password' and
60 ;; `org-passwords-open-url' in the `org-passwords-mode', to easily
61 ;; make the passwords and usernames available to the facility for
62 ;; pasting text of the window system (clipboard on X and MS-Windows,
63 ;; pasteboard on Nextstep/Mac OS, etc.), without inserting them in the
64 ;; kill-ring. You can set for example:
66 ;; (eval-after-load "org-passwords"
67 ;; '(progn
68 ;; (define-key org-passwords-mode-map
69 ;; (kbd "C-c u")
70 ;; 'org-passwords-copy-username)
71 ;; (define-key org-passwords-mode-map
72 ;; (kbd "C-c p")
73 ;; 'org-passwords-copy-password)
74 ;; (kbd "C-c o")
75 ;; 'org-passwords-open-url)))
77 ;; Finally, to enter new passwords, you can use `org-capture' and a
78 ;; minimal template like:
80 ;; ("p" "password" entry (file "~/documents/passwords.gpg")
81 ;; "* %^{Title}\n %^{URL}p %^{USERNAME}p %^{PASSWORD}p")
83 ;; When asked for the password you can then call either
84 ;; `org-passwords-generate-password' or `org-passwords-random-words'.
85 ;; Be sure to enable recursive minibuffers to call those functions
86 ;; from the minibuffer:
88 ;; (setq enable-recursive-minibuffers t)
90 ;;; Code:
92 (require 'org)
94 ;;;###autoload
95 (define-derived-mode org-passwords-mode org-mode
96 "org-passwords-mode"
97 "Mode for storing passwords"
98 nil)
100 (defgroup org-passwords nil
101 "Options for password management."
102 :group 'org)
104 (defcustom org-passwords-password-property "PASSWORD"
105 "Name of the property for password entry."
106 :type 'string
107 :group 'org-passwords)
109 (defcustom org-passwords-username-property "USERNAME"
110 "Name of the property for user name entry."
111 :type 'string
112 :group 'org-passwords)
114 (defcustom org-passwords-url-property "URL"
115 "Name of the property for URL entry."
116 :type 'string
117 :group 'org-passwords)
119 (defcustom org-passwords-file nil
120 "Default file name for the file that contains the passwords."
121 :type 'file
122 :group 'org-passwords)
124 (defcustom org-passwords-time-opened "1 min"
125 "Time that the password file will remain open. It has to be a
126 string, a number followed by units."
127 :type 'str
128 :group 'org-passwords)
130 (defcustom org-passwords-default-password-size "20"
131 "Default number of characters to use in
132 org-passwords-generate-password. It has to be a string."
133 :type 'str
134 :group 'org-passwords)
136 (defcustom org-passwords-random-words-dictionary nil
137 "Default file name for the file that contains a dictionary of
138 words for `org-passwords-random-words'. Each non-empty line in
139 the file is considered a word."
140 :type 'file
141 :group 'org-passwords)
143 (defcustom org-passwords-default-random-words-number "5"
144 "Default number of words to use in org-passwords-random-words.
145 It has to be a string."
146 :type 'str
147 :group 'org-passwords)
149 (defvar org-passwords-random-words-separator "-"
150 "A string to separate words in `org-passwords-random-words'.")
152 (defvar org-passwords-random-words-substitutions
153 '(("a" . "@")
154 ("e" . "3")
155 ("o" . "0"))
156 "A list of substitutions to be made with
157 `org-passwords-random-words' if it is called with
158 `universal-argument'. Each element is pair of
159 strings (SUBSTITUTE-THIS . BY-THIS).")
161 (defun org-passwords-copy-password ()
162 "Makes the password available to other programs. Puts the
163 password of the entry at the location of the cursor in the
164 facility for pasting text of the window system (clipboard on X
165 and MS-Windows, pasteboard on Nextstep/Mac OS, etc.), without
166 putting it in the kill ring."
167 (interactive)
168 (funcall interprogram-cut-function
169 (org-entry-get (point)
170 org-passwords-password-property)))
172 (defun org-passwords-copy-username ()
173 "Makes the password available to other programs. Puts the
174 username of the entry at the location of the cursor in the
175 facility for pasting text of the window system (clipboard on X
176 and MS-Windows, pasteboard on Nextstep/Mac OS, etc.), without
177 putting it in the kill ring."
178 (interactive)
179 (funcall interprogram-cut-function
180 (org-entry-get (point)
181 org-passwords-username-property
182 t)))
184 (defun org-passwords-open-url ()
185 "Browse the URL associated with the entry at the location of
186 the cursor."
187 (interactive)
188 (browse-url (org-entry-get (point)
189 org-passwords-url-property
190 t)))
192 ;;;###autoload
193 (defun org-passwords (&optional arg)
194 "Open the password file. Open the password file defined by the
195 variable `org-password-file' in read-only mode and kill that
196 buffer later according to the value of the variable
197 `org-passwords-time-opened'. It also adds the `org-password-file'
198 to the auto-mode-alist so that it is opened with its mode being
199 `org-passwords-mode'.
201 With prefix arg ARG, the command does not set up a timer to kill the buffer.
203 With a double prefix arg \\[universal-argument] \\[universal-argument], open the file for editing.
205 (interactive "P")
206 (if org-passwords-file
207 (progn
208 (add-to-list 'auto-mode-alist
209 (cons
210 (regexp-quote
211 (expand-file-name org-passwords-file))
212 'org-passwords-mode))
213 (if (equal arg '(4))
214 (find-file-read-only org-passwords-file)
215 (if (equal arg '(16))
216 (find-file org-passwords-file)
217 (progn
218 (find-file-read-only org-passwords-file)
219 (org-passwords-set-up-kill-password-buffer)))))
220 (minibuffer-message "No default password file defined. Set the variable `org-password-file'.")))
222 (defun org-passwords-set-up-kill-password-buffer ()
223 (run-at-time org-passwords-time-opened
225 '(lambda ()
226 (if (get-file-buffer org-passwords-file)
227 (kill-buffer
228 (get-file-buffer org-passwords-file))))))
230 ;;; Password generator
232 ;; Set random number seed from current time and pid. Otherwise
233 ;; `random' gives the same results every time emacs restarts.
234 (random t)
236 (defun org-passwords-generate-password (arg)
237 "Ask a number of characters and insert a password of that size.
238 Password has a random string of numbers, lowercase letters, and
239 uppercase letters. Argument ARG include symbols."
240 (interactive "P")
241 (let ((number-of-chars
242 (read-from-minibuffer
243 (concat "Number of characters (default "
244 org-passwords-default-password-size
245 "): ")
250 org-passwords-default-password-size)))
251 (if arg
252 (insert (org-passwords-generate-password-with-symbols "" number-of-chars))
253 (insert (org-passwords-generate-password-without-symbols "" number-of-chars)))))
255 (defun org-passwords-generate-password-with-symbols (previous-string nums-of-chars)
256 "Return a string consisting of PREVIOUS-STRING and
257 NUMS-OF-CHARS random characters."
258 (if (eq nums-of-chars 0) previous-string
259 (org-passwords-generate-password-with-symbols
260 (concat previous-string
261 (char-to-string
262 ;; symbols, letters, numbers are from 33 to 126
263 (+ (random (- 127 33)) 33)))
264 (1- nums-of-chars))))
266 (defun org-passwords-generate-password-without-symbols (previous-string nums-of-chars)
267 "Return string consisting of PREVIOUS-STRING and NUMS-OF-CHARS
268 random numbers, lowercase letters, and numbers."
269 (if (eq nums-of-chars 0)
270 previous-string
271 ; There are 10 numbers, 26 lowercase letters and 26 uppercase
272 ; letters. 10 + 26 + 26 = 62. The number characters go from 48
273 ; to 57, the uppercase letters from 65 to 90, and the lowercase
274 ; from 97 to 122. The following makes each equally likely.
275 (let ((temp-value (random 62)))
276 (cond ((< temp-value 10)
277 ; If temp-value<10, then add a number
278 (org-passwords-generate-password-without-symbols
279 (concat previous-string
280 (char-to-string (+ 48 temp-value)))
281 (1- nums-of-chars)))
282 ((and (> temp-value 9) (< temp-value 36))
283 ; If 9<temp-value<36, then add an uppercase letter
284 (org-passwords-generate-password-without-symbols
285 (concat previous-string
286 (char-to-string (+ 65 (- temp-value 10))))
287 (1- nums-of-chars)))
288 ((> temp-value 35)
289 ; If temp-value>35, then add a lowecase letter
290 (org-passwords-generate-password-without-symbols
291 (concat previous-string
292 (char-to-string (+ 97 (- temp-value 36))))
293 (1- nums-of-chars)))))))
295 ;;; Random words
297 (defun org-passwords-random-words (arg)
298 "Ask for a number of words and inserts a sequence of that many
299 random words from the list in the file
300 `org-passwords-random-words-dictionary' separated by
301 `org-passwords-random-words-separator'. ARG make substitutions in
302 the words as defined by
303 `org-passwords-random-words-substitutions'."
304 (interactive "P")
305 (if org-passwords-random-words-dictionary
306 (let ((number-of-words
307 (read-from-minibuffer
308 (concat "Number of words (default "
309 org-passwords-default-random-words-number
310 "): ")
315 org-passwords-default-random-words-number))
316 (list-of-words
317 (with-temp-buffer
318 (insert-file-contents
319 org-passwords-random-words-dictionary)
320 (split-string (buffer-string) "\n" t))))
321 (insert
322 (org-passwords-substitute
323 (org-passwords-random-words-attach-number-of-words
324 (nth (random (length list-of-words))
325 list-of-words)
326 (1- number-of-words)
327 list-of-words
328 org-passwords-random-words-separator)
329 (if arg
330 org-passwords-random-words-substitutions
331 nil))))
332 (minibuffer-message
333 "No default dictionary file defined. Set the variable `org-passwords-random-words-dictionary'.")))
335 (defun org-passwords-random-words-attach-number-of-words
336 (previous-string number-of-words list-of-words separator)
337 "Returns a string consisting of PREVIOUS-STRING followed by a
338 succession of NUMBER-OF-WORDS random words from the list LIST-OF-WORDS
339 separated SEPARATOR."
340 (if (eq number-of-words 0)
341 previous-string
342 (org-passwords-random-words-attach-number-of-words
343 (concat previous-string
344 separator
345 (nth (random (length list-of-words)) list-of-words))
346 (1- number-of-words)
347 list-of-words
348 separator)))
350 (defun org-passwords-substitute (string-to-change list-of-substitutions)
351 "Substitutes each appearence in STRING-TO-CHANGE of the `car' of
352 each element of LIST-OF-SUBSTITUTIONS by the `cdr' of that
353 element. For example:
354 (org-passwords-substitute \"ab\" \'((\"a\" . \"b\") (\"b\" . \"c\")))
355 => \"bc\"
356 Substitutions are made in order of the list, so for example:
357 (org-passwords-substitute \"ab\" \'((\"ab\" . \"c\") (\"b\" . \"d\")))
358 => \"c\""
359 (if list-of-substitutions
360 (concat (org-passwords-concat-this-with-string
361 (cdar list-of-substitutions)
362 (mapcar (lambda (x)
363 (org-passwords-substitute
365 (cdr list-of-substitutions)))
366 (split-string string-to-change
367 (caar list-of-substitutions)))))
368 string-to-change))
370 (defun org-passwords-concat-this-with-string (this list-of-strings)
371 "Put the string THIS in between every string in LIST-OF-STRINGS. For example:
372 (org-passwords-concat-this-with-string \"Here\" \'(\"First\" \"Second\" \"Third\"))
373 => \"FirstHereSencondHereThird\""
374 (if (cdr list-of-strings)
375 (concat (car list-of-strings)
376 this
377 (org-passwords-concat-this-with-string
378 this
379 (cdr list-of-strings)))
380 (car list-of-strings)))
382 (provide 'org-passwords)
384 ;;; org-passwords.el ends here