Mercurial > emacs
comparison lisp/org/org-attach.el @ 98645:8339497a5b87
New files org-attach.el, org-list.el, org-plot.el.
author | Carsten Dominik <dominik@science.uva.nl> |
---|---|
date | Sun, 12 Oct 2008 06:14:01 +0000 |
parents | |
children | 7e941d6d7c4c |
comparison
equal
deleted
inserted
replaced
98644:e1cc41b9282d | 98645:8339497a5b87 |
---|---|
1 ;;; org-attach.el --- Manage file attachments to org-mode tasks | |
2 | |
3 ;; Copyright (C) 2008 Free Software Foundation, Inc. | |
4 | |
5 ;; Author: John Wiegley <johnw@newartisans.com> | |
6 ;; Keywords: org data task | |
7 ;; Version: 6.09a | |
8 | |
9 ;; This file is part of GNU Emacs. | |
10 ;; | |
11 ;; GNU Emacs is free software: you can redistribute it and/or modify | |
12 ;; it under the terms of the GNU General Public License as published by | |
13 ;; the Free Software Foundation, either version 3 of the License, or | |
14 ;; (at your option) any later version. | |
15 | |
16 ;; GNU Emacs is distributed in the hope that it will be useful, | |
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
19 ;; GNU General Public License for more details. | |
20 | |
21 ;; You should have received a copy of the GNU General Public License | |
22 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. | |
23 | |
24 ;;; Commentary: | |
25 | |
26 ;; See the Org-mode manual for information on how to use it. | |
27 ;; | |
28 ;; Attachments are managed in a special directory called "data", which | |
29 ;; lives in the directory given by `org-directory'. If this data | |
30 ;; directory is initialized as a Git repository, then org-attach will | |
31 ;; automatically commit changes when it sees them. | |
32 ;; | |
33 ;; Attachment directories are identified using a UUID generated for the | |
34 ;; task which has the attachments. These are added as property to the | |
35 ;; task when necessary, and should not be deleted or changed by the | |
36 ;; user, ever. UUIDs are generated by a mechanism defined in the variable | |
37 ;; `org-id-method'. | |
38 | |
39 ;;; Code: | |
40 | |
41 (eval-when-compile | |
42 (require 'cl)) | |
43 (require 'org-id) | |
44 (require 'org) | |
45 | |
46 (defgroup org-attach nil | |
47 "Options concerning entry attachments in Org-mode." | |
48 :tag "Org Attach" | |
49 :group 'org) | |
50 | |
51 (defcustom org-attach-directory "data/" | |
52 "The directory where attachments are stored. | |
53 If this is a relative path, it will be interpreted relative to the directory | |
54 where the Org file lives." | |
55 :group 'org-attach | |
56 :type 'direcory) | |
57 | |
58 (defcustom org-attach-auto-tag "ATTACH" | |
59 "Tag that will be triggered automatically when an entry has an attachment." | |
60 :group 'org-attach | |
61 :type '(choice | |
62 (const :tag "None" nil) | |
63 (string :tag "Tag"))) | |
64 | |
65 (defcustom org-attach-file-list-property "Attachments" | |
66 "The property used to keep a list of attachment belonging to this entry. | |
67 This is not really needed, so you may set this to nil if you don't want it." | |
68 :group 'org-attach | |
69 :type '(choice | |
70 (const :tag "None" nil) | |
71 (string :tag "Tag"))) | |
72 | |
73 (defcustom org-attach-method 'cp | |
74 "The preferred method to attach a file. | |
75 Allowed values are: | |
76 | |
77 mv rename the file to move it into the attachment directory | |
78 cp copy the file | |
79 ln create a hard link. Note that this is not supported | |
80 on all systems, and then the result is not defined." | |
81 :group 'org-attach | |
82 :type '(choice | |
83 (const :tag "Copy" cp) | |
84 (const :tag "Move/Rename" mv) | |
85 (const :tag "Link" ln))) | |
86 | |
87 (defcustom org-attach-expert nil | |
88 "Non-nil means do not show the splash buffer with the attach dispatcher." | |
89 :group 'org-attach | |
90 :type 'boolean) | |
91 | |
92 ;;;###autoload | |
93 (defun org-attach () | |
94 "The dispatcher for attachment commands. | |
95 Shows a list of commands and prompts for another key to execute a command." | |
96 (interactive) | |
97 (let (c marker) | |
98 (when (eq major-mode 'org-agenda-mode) | |
99 (setq marker (or (get-text-property (point) 'org-hd-marker) | |
100 (get-text-property (point) 'org-marker))) | |
101 (unless marker | |
102 (error "No task in current line"))) | |
103 (save-excursion | |
104 (when marker | |
105 (set-buffer (marker-buffer marker)) | |
106 (goto-char marker)) | |
107 (org-back-to-heading t) | |
108 (save-excursion | |
109 (save-window-excursion | |
110 (unless org-attach-expert | |
111 (with-output-to-temp-buffer "*Org Attach*" | |
112 (princ "Select an Attachment Command: | |
113 | |
114 a Select a file and attach it to the task, using `org-attach-method'. | |
115 c/m/l Attach a file using copy/move/link method. | |
116 n Create a new attachment, as an Emacs buffer. | |
117 z Synchronize the current task with its attachment | |
118 directory, in case you added attachments yourself. | |
119 | |
120 o Open current task's attachments. | |
121 O Like \"o\", but force opening in Emacs. | |
122 f Open current task's attachment directory. | |
123 F Like \"f\", but force using dired in Emacs. | |
124 | |
125 d Delete one attachment, you will be prompted for a file name. | |
126 D Delete all of a task's attachments. A safer way is | |
127 to open the directory in dired and delete from there."))) | |
128 (shrink-window-if-larger-than-buffer (get-buffer-window "*Org Attach*")) | |
129 (message "Select command: [acmlzoOfFdD]") | |
130 (setq c (read-char-exclusive)) | |
131 (and (get-buffer "*Org Attach*") (kill-buffer "*Org Attach*")))) | |
132 (cond | |
133 ((memq c '(?a ?\C-a)) (call-interactively 'org-attach-attach)) | |
134 ((memq c '(?c ?\C-c)) | |
135 (let ((org-attach-method 'cp)) (call-interactively 'org-attach-attach))) | |
136 ((memq c '(?m ?\C-m)) | |
137 (let ((org-attach-method 'mv)) (call-interactively 'org-attach-attach))) | |
138 ((memq c '(?l ?\C-l)) | |
139 (let ((org-attach-method 'ln)) (call-interactively 'org-attach-attach))) | |
140 ((memq c '(?n ?\C-n)) (call-interactively 'org-attach-new)) | |
141 ((memq c '(?z ?\C-z)) (call-interactively 'org-attach-sync)) | |
142 ((memq c '(?o ?\C-o)) (call-interactively 'org-attach-open)) | |
143 ((eq c ?O) (call-interactively 'org-attach-open-in-emacs)) | |
144 ((memq c '(?f ?\C-f)) (call-interactively 'org-attach-reveal)) | |
145 ((memq c '(?F)) (call-interactively 'org-attach-reveal-in-emacs)) | |
146 ((memq c '(?d ?\C-d)) (call-interactively | |
147 'org-attach-delete-one)) | |
148 ((eq c ?D) (call-interactively 'org-attach-delete-all)) | |
149 ((eq c ?q) (message "Abort")) | |
150 (t (error "No such attachment command %c" c)))))) | |
151 | |
152 (defun org-attach-dir (&optional create-if-not-exists-p) | |
153 "Return the directory associated with the current entry. | |
154 If the directory does not exist and CREATE-IF-NOT-EXISTS-P is non-nil, | |
155 the directory and the corresponding ID will be created." | |
156 (let ((uuid (org-id-get (point) create-if-not-exists-p))) | |
157 (when (or uuid create-if-not-exists-p) | |
158 (unless uuid | |
159 (let ((uuid-string (shell-command-to-string "uuidgen"))) | |
160 (setf uuid-string | |
161 (substring uuid-string 0 (1- (length uuid-string)))) | |
162 (org-entry-put (point) "ID" uuid-string) | |
163 (setf uuid uuid-string))) | |
164 (let ((attach-dir (expand-file-name | |
165 (format "%s/%s" | |
166 (substring uuid 0 2) | |
167 (substring uuid 2)) | |
168 (expand-file-name org-attach-directory)))) | |
169 (if (and create-if-not-exists-p | |
170 (not (file-directory-p attach-dir))) | |
171 (make-directory attach-dir t)) | |
172 (and (file-exists-p attach-dir) | |
173 attach-dir))))) | |
174 | |
175 (defun org-attach-commit () | |
176 "Commit changes to git if `org-attach-directory' is properly initialized. | |
177 This checks for the existence of a \".git\" directory in that directory." | |
178 (let ((dir (expand-file-name org-attach-directory))) | |
179 (if (file-exists-p (expand-file-name ".git" dir)) | |
180 (shell-command | |
181 (concat "(cd " dir "; " | |
182 " git add .; " | |
183 " git ls-files --deleted -z | xargs -0 git rm; " | |
184 " git commit -m 'Synchronized attachments')"))))) | |
185 | |
186 (defun org-attach-tag (&optional off) | |
187 "Turn the autotag on or (if OFF is set) off." | |
188 (when org-attach-auto-tag | |
189 (save-excursion | |
190 (org-back-to-heading t) | |
191 (org-toggle-tag org-attach-auto-tag (if off 'off 'on))))) | |
192 | |
193 (defun org-attach-untag () | |
194 "Turn the autotag off." | |
195 (org-attach-tag 'off)) | |
196 | |
197 (defun org-attach-attach (file &optional visit-dir method) | |
198 "Move/copy/link FILE into the attachment directory of the current task. | |
199 If VISIT-DIR is non-nil, visit the directory with dired. | |
200 METHOD may be `cp', `mv', or `ln', default taken from `org-attach-method'." | |
201 (interactive "fFile to keep as an attachment: \nP") | |
202 (setq method (or method org-attach-method)) | |
203 (let ((basename (file-name-nondirectory file))) | |
204 (when org-attach-file-list-property | |
205 (org-entry-add-to-multivalued-property | |
206 (point) org-attach-file-list-property basename)) | |
207 (let* ((attach-dir (org-attach-dir t)) | |
208 (fname (expand-file-name basename attach-dir))) | |
209 (cond | |
210 ((eq method 'mv) (rename-file file fname)) | |
211 ((eq method 'cp) (copy-file file fname)) | |
212 ((eq method 'ln) (add-name-to-file file fname))) | |
213 (org-attach-commit) | |
214 (org-attach-tag) | |
215 (if visit-dir | |
216 (dired attach-dir) | |
217 (message "File \"%s\" is now a task attachment." basename))))) | |
218 | |
219 (defun org-attach-attach-cp () | |
220 "Attach a file by copying it." | |
221 (interactive) | |
222 (let ((org-attach-method 'cp)) (call-interactively 'org-attach-attach))) | |
223 (defun org-attach-attach-mv () | |
224 "Attach a file by moving (renaming) it." | |
225 (interactive) | |
226 (let ((org-attach-method 'mv)) (call-interactively 'org-attach-attach))) | |
227 (defun org-attach-attach-ln () | |
228 "Attach a file by creating a hard link to it. | |
229 Beware that this does not work on systems that do not support hard links. | |
230 On some systems, this apparently does copy the file instead." | |
231 (interactive) | |
232 (let ((org-attach-method 'ln)) (call-interactively 'org-attach-attach))) | |
233 | |
234 (defun org-attach-new (file) | |
235 "Create a new attachment FILE for the current task. | |
236 The attachment is created as an Emacs buffer." | |
237 (interactive "sCreate attachment named: ") | |
238 (when org-attach-file-list-property | |
239 (org-entry-add-to-multivalued-property | |
240 (point) org-attach-file-list-property file)) | |
241 (let ((attach-dir (org-attach-dir t))) | |
242 (org-attach-tag) | |
243 (find-file (expand-file-name file attach-dir)) | |
244 (message "New attachment %s" file))) | |
245 | |
246 (defun org-attach-delete-one (&optional file) | |
247 "Delete a single attachment." | |
248 (interactive) | |
249 (let* ((attach-dir (org-attach-dir t)) | |
250 (files (org-attach-file-list attach-dir)) | |
251 (file (or file | |
252 (completing-read | |
253 "Delete attachment: " | |
254 (mapcar (lambda (f) | |
255 (list (file-name-nondirectory f))) | |
256 files))))) | |
257 (setq file (expand-file-name file attach-dir)) | |
258 (unless (file-exists-p file) | |
259 (error "No such attachment: %s" file)) | |
260 (delete-file file))) | |
261 | |
262 (defun org-attach-delete-all (&optional force) | |
263 "Delete all attachments from the current task. | |
264 This actually deletes the entire attachment directory. | |
265 A safer way is to open the directory in dired and delete from there." | |
266 (interactive "P") | |
267 (when org-attach-file-list-property | |
268 (org-entry-delete (point) org-attach-file-list-property)) | |
269 (let ((attach-dir (org-attach-dir))) | |
270 (when | |
271 (and attach-dir | |
272 (or force | |
273 (y-or-n-p "Are you sure you want to remove all attachments of this entry? "))) | |
274 (shell-command (format "rm -fr %s" attach-dir)) | |
275 (message "Attachment directory removed") | |
276 (org-attach-commit) | |
277 (org-attach-untag)))) | |
278 | |
279 (defun org-attach-sync () | |
280 "Synchronize the current tasks with its attachments. | |
281 This can be used after files have been added externally." | |
282 (interactive) | |
283 (org-attach-commit) | |
284 (when org-attach-file-list-property | |
285 (org-entry-delete (point) org-attach-file-list-property)) | |
286 (let ((attach-dir (org-attach-dir))) | |
287 (when attach-dir | |
288 (let ((files (org-attach-file-list attach-dir))) | |
289 (and files (org-attach-tag)) | |
290 (when org-attach-file-list-property | |
291 (dolist (file files) | |
292 (unless (string-match "^\\." file) | |
293 (org-entry-add-to-multivalued-property | |
294 (point) org-attach-file-list-property file)))))))) | |
295 | |
296 (defun org-attach-file-list (dir) | |
297 "Return a list of files in the attachment directory. | |
298 This ignores files starting with a \".\", and files ending in \"~\"." | |
299 (delq nil | |
300 (mapcar (lambda (x) (if (string-match "^\\." x) nil x)) | |
301 (directory-files dir nil "[^~]\\'")))) | |
302 | |
303 (defun org-attach-reveal () | |
304 "Show the attachment directory of the current task in dired." | |
305 (interactive) | |
306 (let ((attach-dir (org-attach-dir t))) | |
307 (org-open-file attach-dir))) | |
308 | |
309 (defun org-attach-reveal-in-emacs () | |
310 "Show the attachment directory of the current task. | |
311 This will attempt to use an external program to show the directory." | |
312 (interactive) | |
313 (let ((attach-dir (org-attach-dir t))) | |
314 (dired attach-dir))) | |
315 | |
316 (defun org-attach-open (&optional in-emacs) | |
317 "Open an attachment of the current task. | |
318 If there are more than one attachment, you will be prompted for the file name. | |
319 This command will open the file using the settings in `org-file-apps' | |
320 and in the system-specific variants of this variable. | |
321 If IN-EMACS is non-nil, force opening in Emacs." | |
322 (interactive "P") | |
323 (let* ((attach-dir (org-attach-dir t)) | |
324 (files (org-attach-file-list attach-dir)) | |
325 (file (if (= (length files) 1) | |
326 (car files) | |
327 (completing-read "Open attachment: " | |
328 (mapcar 'list files) nil t)))) | |
329 (org-open-file (expand-file-name file attach-dir) in-emacs))) | |
330 | |
331 (defun org-attach-open-in-emacs () | |
332 "Open attachment, force opening in Emacs. | |
333 See `org-attach-open'." | |
334 (interactive) | |
335 (org-attach-open 'in-emacs)) | |
336 | |
337 (provide 'org-attach) | |
338 | |
339 ;;; org-attach.el ends here |