2010-04-10 Carsten Dominik <carsten.dominik@gmail.com>
[bpt/emacs.git] / lisp / org / org-publish.el
CommitLineData
9542795a 1;;; org-publish.el --- publish related org-mode files as a website
ed21c5c8
CD
2;; Copyright (C) 2006, 2007, 2008, 2009, 2010
3;; Free Software Foundation, Inc.
9542795a
CD
4
5;; Author: David O'Toole <dto@gnu.org>
c8d0cf5c 6;; Maintainer: Carsten Dominik <carsten DOT dominik AT gmail DOT com>
699b9291 7;; Keywords: hypermedia, outlines, wp
ed21c5c8 8;; Version: 6.35i
9542795a 9
514a6ce6
CD
10;; This file is part of GNU Emacs.
11;;
b1fc2b50 12;; GNU Emacs is free software: you can redistribute it and/or modify
9542795a 13;; it under the terms of the GNU General Public License as published by
b1fc2b50
GM
14;; the Free Software Foundation, either version 3 of the License, or
15;; (at your option) any later version.
9542795a 16
514a6ce6 17;; GNU Emacs is distributed in the hope that it will be useful,
9542795a
CD
18;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20;; GNU General Public License for more details.
21
22;; You should have received a copy of the GNU General Public License
b1fc2b50 23;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
9542795a 24
9542795a
CD
25;;; Commentary:
26
699b9291
CD
27;; This program allow configurable publishing of related sets of
28;; Org-mode files as a complete website.
9542795a
CD
29;;
30;; org-publish.el can do the following:
31;;
c8d0cf5c 32;; + Publish all one's org-files to HTML or PDF
699b9291 33;; + Upload HTML, images, attachments and other files to a web server
9542795a 34;; + Exclude selected private pages from publishing
ed21c5c8 35;; + Publish a clickable sitemap of pages
699b9291 36;; + Manage local timestamps for publishing only changed files
9542795a
CD
37;; + Accept plugin functions to extend range of publishable content
38;;
c8d0cf5c 39;; Documentation for publishing is in the manual.
9542795a
CD
40
41;;; Code:
42
ed21c5c8
CD
43
44(defun org-publish-sanitize-plist (plist)
45 (mapcar (lambda (x)
46 (or (cdr (assq x '((:index-filename . :sitemap-filename)
47 (:index-title . :sitemap-title)
48 (:index-function . :sitemap-function)
49 (:index-style . :sitemap-style)
50 (:auto-index . :auto-sitemap))))
51 x))
52 plist))
53
9542795a
CD
54(eval-when-compile
55 (require 'cl))
b349f79f
CD
56(require 'org)
57(require 'org-exp)
9542795a 58
1e63a7fb
GM
59(eval-and-compile
60 (unless (fboundp 'declare-function)
61 (defmacro declare-function (fn file &optional arglist fileonly))))
62
9542795a 63(defgroup org-publish nil
b349f79f
CD
64 "Options for publishing a set of Org-mode and related files."
65 :tag "Org Publishing"
66 :group 'org)
9542795a 67
9542795a
CD
68(defcustom org-publish-project-alist nil
69 "Association list to control publishing behavior.
70Each element of the alist is a publishing 'project.' The CAR of
50243adf 71each element is a string, uniquely identifying the project. The
9542795a
CD
72CDR of each element is in one of the following forms:
73
74 (:property value :property value ... )
75
76OR,
77
78 (:components (\"project-1\" \"project-2\" ...))
79
80When the CDR of an element of org-publish-project-alist is in
81this second form, the elements of the list after :components are
82taken to be components of the project, which group together files
50243adf
JB
83requiring different publishing options. When you publish such a
84project with \\[org-publish], the components all publish.
9542795a
CD
85
86When a property is given a value in org-publish-project-alist, its
87setting overrides the value of the corresponding user variable
50243adf 88\(if any) during publishing. However, options set within a file
9542795a
CD
89override everything.
90
91Most properties are optional, but some should always be set:
92
699b9291
CD
93 :base-directory Directory containing publishing source files
94 :base-extension Extension (without the dot!) of source files.
95 This can be a regular expression.
96 :publishing-directory Directory (possibly remote) where output
97 files will be published
9542795a
CD
98
99The :exclude property may be used to prevent certain files from
50243adf 100being published. Its value may be a string or regexp matching
9542795a
CD
101file names you don't want to be published.
102
50243adf 103The :include property may be used to include extra files. Its
b349f79f
CD
104value may be a list of filenames to include. The filenames are
105considered relative to the base directory.
9542795a
CD
106
107When both :include and :exclude properties are given values, the
108exclusion step happens first.
109
110One special property controls which back-end function to use for
50243adf 111publishing files in the project. This can be used to extend the
9542795a
CD
112set of file types publishable by org-publish, as well as the set
113of output formats.
114
50243adf
JB
115 :publishing-function Function to publish file. The default is
116 `org-publish-org-to-html', but other
117 values are possible. May also be a
699b9291
CD
118 list of functions, in which case
119 each function in the list is invoked
120 in turn.
9542795a
CD
121
122Another property allows you to insert code that prepares a
50243adf 123project for publishing. For example, you could call GNU Make on a
699b9291 124certain makefile, to ensure published files are built up to date.
9542795a 125
699b9291 126 :preparation-function Function to be called before publishing
ed21c5c8
CD
127 this project. This may also be a list
128 of functions.
b349f79f 129 :completion-function Function to be called after publishing
ed21c5c8
CD
130 this project. This may also be a list
131 of functions.
9542795a
CD
132
133Some properties control details of the Org publishing process,
134and are equivalent to the corresponding user variables listed in
50243adf 135the right column. See the documentation for those variables to
9542795a
CD
136learn more about their use and default values.
137
50243adf
JB
138 :language `org-export-default-language'
139 :headline-levels `org-export-headline-levels'
140 :section-numbers `org-export-with-section-numbers'
141 :table-of-contents `org-export-with-toc'
142 :emphasize `org-export-with-emphasize'
143 :sub-superscript `org-export-with-sub-superscripts'
144 :TeX-macros `org-export-with-TeX-macros'
145 :fixed-width `org-export-with-fixed-width'
146 :tables `org-export-with-tables'
147 :table-auto-headline `org-export-highlight-first-table-line'
148 :style `org-export-html-style'
149 :convert-org-links `org-export-html-link-org-files-as-html'
150 :inline-images `org-export-html-inline-images'
151 :expand-quoted-html `org-export-html-expand'
152 :timestamp `org-export-html-with-timestamp'
153 :publishing-directory `org-export-publishing-directory'
154 :preamble `org-export-html-preamble'
155 :postamble `org-export-html-postamble'
156 :auto-preamble `org-export-html-auto-preamble'
157 :auto-postamble `org-export-html-auto-postamble'
158 :author `user-full-name'
159 :email `user-mail-address'
9542795a 160
ed21c5c8
CD
161The following properties may be used to control publishing of a
162sitemap of files or summary page for a given project.
9542795a 163
ed21c5c8 164 :auto-sitemap Whether to publish a sitemap during
50243adf 165 `org-publish-current-project' or `org-publish-all'.
ed21c5c8 166 :sitemap-filename Filename for output of sitemap. Defaults
c8d0cf5c 167 to 'sitemap.org' (which becomes 'sitemap.html').
ed21c5c8
CD
168 :sitemap-title Title of sitemap page. Defaults to name of file.
169 :sitemap-function Plugin function to use for generation of sitemap.
170 Defaults to `org-publish-org-sitemap', which
699b9291 171 generates a plain list of links to all files
2c3ad40d 172 in the project.
ed21c5c8 173 :sitemap-style Can be `list' (sitemap is just an itemized list
ff4be292 174 of the titles of the files involved) or
2c3ad40d 175 `tree' (the directory structure of the source
ed21c5c8 176 files is reflected in the sitemap). Defaults to
2c3ad40d 177 `tree'."
9542795a
CD
178 :group 'org-publish
179 :type 'alist)
180
9542795a
CD
181(defcustom org-publish-use-timestamps-flag t
182 "When non-nil, use timestamp checking to publish only changed files.
50243adf 183When nil, do no timestamp checking and always publish all files."
9542795a
CD
184 :group 'org-publish
185 :type 'boolean)
186
ff4be292 187(defcustom org-publish-timestamp-directory (convert-standard-filename
71d35b24 188 "~/.org-timestamps/")
9542795a
CD
189 "Name of directory in which to store publishing timestamps."
190 :group 'org-publish
699b9291 191 :type 'directory)
9542795a 192
c8d0cf5c 193(defcustom org-publish-list-skipped-files t
ed21c5c8 194 "Non-nil means show message about files *not* published."
c8d0cf5c
CD
195 :group 'org-publish
196 :type 'boolean)
197
699b9291
CD
198(defcustom org-publish-before-export-hook nil
199 "Hook run before export on the Org file.
c8d0cf5c 200The hook may modify the file in arbitrary ways before publishing happens.
8bfe682a 201The original version of the buffer will be restored after publishing."
699b9291
CD
202 :group 'org-publish
203 :type 'hook)
9542795a 204
699b9291
CD
205(defcustom org-publish-after-export-hook nil
206 "Hook run after export on the exported buffer.
c8d0cf5c 207Any changes made by this hook will be saved."
699b9291
CD
208 :group 'org-publish
209 :type 'hook)
9542795a 210
699b9291
CD
211;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
212;;; Timestamp-related functions
9542795a 213
c8d0cf5c 214(defun org-publish-timestamp-filename (filename &optional pub-dir pub-func)
9542795a 215 "Return path to timestamp file for filename FILENAME."
c8d0cf5c
CD
216 (setq filename (concat filename "::" (or pub-dir "") "::"
217 (format "%s" (or pub-func ""))))
2c3ad40d 218 (concat (file-name-as-directory org-publish-timestamp-directory)
33306645 219 "X" (if (fboundp 'sha1) (sha1 filename) (md5 filename))))
9542795a 220
c8d0cf5c
CD
221(defun org-publish-needed-p (filename &optional pub-dir pub-func true-pub-dir)
222 "Return `t' if FILENAME should be published in PUB-DIR using PUB-FUNC.
223TRUE-PUB-DIR is there the file will truely end up. Currently we are not using
224this - maybe it can eventually be used to check if the file is present at
225the target location, and how old it is. Right ow we cannot do this, because
226we do not know under what file name the file will be stored - the publishing
227function can still decide about that independently."
2c3ad40d
CD
228 (let ((rtn
229 (if org-publish-use-timestamps-flag
230 (if (file-exists-p org-publish-timestamp-directory)
231 ;; first handle possible wrong timestamp directory
232 (if (not (file-directory-p org-publish-timestamp-directory))
233 (error "Org publish timestamp: %s is not a directory"
234 org-publish-timestamp-directory)
235 ;; there is a timestamp, check if FILENAME is newer
236 (file-newer-than-file-p
c8d0cf5c
CD
237 filename (org-publish-timestamp-filename
238 filename pub-dir pub-func)))
2c3ad40d
CD
239 (make-directory org-publish-timestamp-directory)
240 t)
241 ;; don't use timestamps, always return t
242 t)))
243 (if rtn
c8d0cf5c
CD
244 (message "Publishing file %s using `%s'" filename pub-func)
245 (when org-publish-list-skipped-files
246 (message "Skipping unmodified file %s" filename)))
2c3ad40d 247 rtn))
9542795a 248
c8d0cf5c 249(defun org-publish-update-timestamp (filename &optional pub-dir pub-func)
699b9291
CD
250 "Update publishing timestamp for file FILENAME.
251If there is no timestamp, create one."
c8d0cf5c
CD
252 (let ((timestamp-file (org-publish-timestamp-filename
253 filename pub-dir pub-func))
699b9291
CD
254 newly-created-timestamp)
255 (if (not (file-exists-p timestamp-file))
256 ;; create timestamp file if needed
257 (with-temp-buffer
258 (make-directory (file-name-directory timestamp-file) t)
259 (write-file timestamp-file)
260 (setq newly-created-timestamp t)))
261 ;; Emacs 21 doesn't have `set-file-times'
262 (if (and (fboundp 'set-file-times)
263 (not newly-created-timestamp))
33306645 264 (set-file-times timestamp-file)
c8d0cf5c
CD
265 (call-process "touch" nil 0 nil (expand-file-name timestamp-file)))))
266
267(defun org-publish-remove-all-timestamps ()
268 "Remove all files in the timstamp directory."
269 (let ((dir org-publish-timestamp-directory)
270 files)
271 (when (and (file-exists-p dir)
272 (file-directory-p dir))
273 (mapc 'delete-file (directory-files dir 'full "[^.]\\'")))))
274
699b9291
CD
275
276;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
277;;; Mapping files to project names
278
279(defvar org-publish-files-alist nil
50243adf 280 "Alist of files and their parent projects.
699b9291
CD
281Each element of this alist is of the form:
282
283 (file-name . project-name)")
284
20908596
CD
285(defvar org-publish-initial-buffer nil
286 "The buffer `org-publish' has been called from.")
287(defvar org-publish-temp-files nil
288 "Temporary list of files to be published.")
289
699b9291
CD
290(defun org-publish-initialize-files-alist (&optional refresh)
291 "Set `org-publish-files-alist' if it is not set.
292Also set it if the optional argument REFRESH is non-nil."
293 (interactive "P")
294 (when (or refresh (not org-publish-files-alist))
295 (setq org-publish-files-alist
296 (org-publish-get-files org-publish-project-alist))))
297
93b62de8
CD
298(defun org-publish-validate-link (link &optional directory)
299 "Check if LINK points to a file in the current project."
300 (assoc (expand-file-name link directory) org-publish-files-alist))
301
699b9291
CD
302;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
303;;; Compatibility aliases
304
305;; Delete-dups is not in Emacs <22
306(if (fboundp 'delete-dups)
307 (defalias 'org-publish-delete-dups 'delete-dups)
308 (defun org-publish-delete-dups (list)
309 "Destructively remove `equal' duplicates from LIST.
310Store the result in LIST and return it. LIST must be a proper list.
311Of several `equal' occurrences of an element in LIST, the first
312one is kept.
313
314This is a compatibility function for Emacsen without `delete-dups'."
315 ;; Code from `subr.el' in Emacs 22:
316 (let ((tail list))
317 (while tail
318 (setcdr tail (delete (car tail) (cdr tail)))
319 (setq tail (cdr tail))))
320 list))
321
1e63a7fb 322(declare-function org-publish-delete-dups "org-publish" (list))
ed21c5c8 323(declare-function find-lisp-find-files "find-lisp" (directory regexp))
1e63a7fb 324
699b9291
CD
325;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
326;;; Getting project information out of org-publish-project-alist
327
328(defun org-publish-get-files (projects-alist &optional no-exclusion)
329 "Return the list of all publishable files for PROJECTS-ALIST.
330If NO-EXCLUSION is non-nil, don't exclude files."
331 (let (all-files)
332 ;; add all projects
333 (mapc
334 (lambda(p)
335 (let* ((exclude (plist-get (cdr p) :exclude))
336 (files (and p (org-publish-get-base-files p exclude))))
337 ;; add all files from this project
338 (mapc (lambda(f)
339 (add-to-list 'all-files
340 (cons (expand-file-name f) (car p))))
341 files)))
342 (org-publish-expand-projects projects-alist))
343 all-files))
344
345(defun org-publish-expand-projects (projects-alist)
621f83e4
CD
346 "Expand projects in PROJECTS-ALIST.
347This splices all the components into the list."
348 (let ((rest projects-alist) rtn p components)
349 (while (setq p (pop rest))
350 (if (setq components (plist-get (cdr p) :components))
351 (setq rest (append
352 (mapcar (lambda (x) (assoc x org-publish-project-alist))
353 components)
354 rest))
355 (push p rtn)))
356 (nreverse (org-publish-delete-dups (delq nil rtn)))))
ff4be292 357
20908596
CD
358(defun org-publish-get-base-files-1 (base-dir &optional recurse match skip-file skip-dir)
359 "Set `org-publish-temp-files' with files from BASE-DIR directory.
360If RECURSE is non-nil, check BASE-DIR recursively. If MATCH is
361non-nil, restrict this list to the files matching the regexp
362MATCH. If SKIP-FILE is non-nil, skip file matching the regexp
363SKIP-FILE. If SKIP-DIR is non-nil, don't check directories
33306645 364matching the regexp SKIP-DIR when recursing through BASE-DIR."
20908596 365 (mapc (lambda (f)
93b62de8 366 (let ((fd-p (file-directory-p f))
20908596
CD
367 (fnd (file-name-nondirectory f)))
368 (if (and fd-p recurse
369 (not (string-match "^\\.+$" fnd))
370 (if skip-dir (not (string-match skip-dir fnd)) t))
371 (org-publish-get-base-files-1 f recurse match skip-file skip-dir)
372 (unless (or fd-p ;; this is a directory
373 (and skip-file (string-match skip-file fnd))
93b62de8 374 (not (file-exists-p (file-truename f)))
20908596
CD
375 (not (string-match match fnd)))
376 (pushnew f org-publish-temp-files)))))
377 (directory-files base-dir t (unless recurse match))))
378
699b9291
CD
379(defun org-publish-get-base-files (project &optional exclude-regexp)
380 "Return a list of all files in PROJECT.
9542795a
CD
381If EXCLUDE-REGEXP is set, this will be used to filter out
382matching filenames."
699b9291
CD
383 (let* ((project-plist (cdr project))
384 (base-dir (file-name-as-directory
385 (plist-get project-plist :base-directory)))
33306645
CD
386 (include-list (plist-get project-plist :include))
387 (recurse (plist-get project-plist :recursive))
388 (extension (or (plist-get project-plist :base-extension) "org"))
c8d0cf5c
CD
389 (match (if (eq extension 'any)
390 "^[^\\.]"
391 (concat "^[^\\.].*\\.\\(" extension "\\)$"))))
20908596
CD
392 (setq org-publish-temp-files nil)
393 (org-publish-get-base-files-1 base-dir recurse match
394 ;; FIXME distinguish exclude regexp
395 ;; for skip-file and skip-dir?
396 exclude-regexp exclude-regexp)
b349f79f 397 (mapc (lambda (f)
ff4be292 398 (pushnew
b349f79f
CD
399 (expand-file-name (concat base-dir f))
400 org-publish-temp-files))
401 include-list)
20908596 402 org-publish-temp-files))
9542795a 403
c8d0cf5c 404(defun org-publish-get-project-from-filename (filename &optional up)
699b9291
CD
405 "Return the project FILENAME belongs."
406 (let* ((project-name (cdr (assoc (expand-file-name filename)
33306645 407 org-publish-files-alist))))
c8d0cf5c
CD
408 (when up
409 (dolist (prj org-publish-project-alist)
410 (if (member project-name (plist-get (cdr prj) :components))
411 (setq project-name (car prj)))))
699b9291 412 (assoc project-name org-publish-project-alist)))
daa89d0f 413
699b9291
CD
414;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
415;;; Pluggable publishing back-end functions
daa89d0f 416
699b9291 417(defun org-publish-org-to (format plist filename pub-dir)
daa89d0f
CD
418 "Publish an org file to FORMAT.
419PLIST is the property list for the given project.
699b9291
CD
420FILENAME is the filename of the org file to be published.
421PUB-DIR is the publishing directory."
03f3cf35 422 (require 'org)
699b9291
CD
423 (unless (file-exists-p pub-dir)
424 (make-directory pub-dir t))
71d35b24
CD
425 (let ((visiting (find-buffer-visiting filename)))
426 (save-excursion
427 (switch-to-buffer (or visiting (find-file filename)))
428 (let* ((plist (cons :buffer-will-be-killed (cons t plist)))
429 (init-buf (current-buffer))
430 (init-point (point))
431 (init-buf-string (buffer-string))
432 export-buf-or-file)
433 ;; run hooks before exporting
434 (run-hooks 'org-publish-before-export-hook)
435 ;; export the possibly modified buffer
436 (setq export-buf-or-file
437 (funcall (intern (concat "org-export-as-" format))
438 (plist-get plist :headline-levels)
439 nil plist nil nil pub-dir))
440 (when (and (bufferp export-buf-or-file)
441 (buffer-live-p export-buf-or-file))
442 (set-buffer export-buf-or-file)
443 ;; run hooks after export and save export
ed21c5c8
CD
444 (progn (run-hooks 'org-publish-after-export-hook)
445 (if (buffer-modified-p) (save-buffer)))
71d35b24
CD
446 (kill-buffer export-buf-or-file))
447 ;; maybe restore buffer's content
448 (set-buffer init-buf)
449 (when (buffer-modified-p init-buf)
450 (erase-buffer)
451 (insert init-buf-string)
452 (save-buffer)
453 (goto-char init-point))
454 (unless visiting
455 (kill-buffer init-buf))))))
699b9291 456
ed21c5c8
CD
457(defmacro org-publish-with-aux-preprocess-maybe (&rest body)
458 "Execute BODY with a modified hook to preprocess for index."
459 `(let ((org-export-preprocess-after-headline-targets-hook
460 (if (plist-get project-plist :makeindex)
461 (cons 'org-publish-aux-preprocess
462 org-export-preprocess-after-headline-targets-hook)
463 org-export-preprocess-after-headline-targets-hook)))
464 ,@body))
465
466(defvar project-plist)
699b9291
CD
467(defun org-publish-org-to-latex (plist filename pub-dir)
468 "Publish an org file to LaTeX.
469See `org-publish-org-to' to the list of arguments."
ed21c5c8
CD
470 (org-publish-with-aux-preprocess-maybe
471 (org-publish-org-to "latex" plist filename pub-dir)))
699b9291 472
71d35b24
CD
473(defun org-publish-org-to-pdf (plist filename pub-dir)
474 "Publish an org file to PDF (via LaTeX).
475See `org-publish-org-to' to the list of arguments."
ed21c5c8
CD
476 (org-publish-with-aux-preprocess-maybe
477 (org-publish-org-to "pdf" plist filename pub-dir)))
71d35b24 478
699b9291
CD
479(defun org-publish-org-to-html (plist filename pub-dir)
480 "Publish an org file to HTML.
481See `org-publish-org-to' to the list of arguments."
ed21c5c8
CD
482 (org-publish-with-aux-preprocess-maybe
483 (org-publish-org-to "html" plist filename pub-dir)))
699b9291 484
c8d0cf5c
CD
485(defun org-publish-org-to-org (plist filename pub-dir)
486 "Publish an org file to HTML.
487See `org-publish-org-to' to the list of arguments."
488 (org-publish-org-to "org" plist filename pub-dir))
489
699b9291 490(defun org-publish-attachment (plist filename pub-dir)
9542795a 491 "Publish a file with no transformation of any kind.
699b9291 492See `org-publish-org-to' to the list of arguments."
93b62de8 493 ;; make sure eshell/cp code is loaded
b349f79f
CD
494 (unless (file-directory-p pub-dir)
495 (make-directory pub-dir t))
c8d0cf5c
CD
496 (or (equal (expand-file-name (file-name-directory filename))
497 (file-name-as-directory (expand-file-name pub-dir)))
498 (copy-file filename
499 (expand-file-name (file-name-nondirectory filename) pub-dir)
500 t)))
699b9291
CD
501
502;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
503;;; Publishing files, sets of files, and indices
504
505(defun org-publish-file (filename &optional project)
506 "Publish file FILENAME from PROJECT."
c8d0cf5c
CD
507 (let* ((project
508 (or project
509 (or (org-publish-get-project-from-filename filename)
510 (if (y-or-n-p
511 (format "%s is not in a project. Re-read the list of projects files? "
512 (abbreviate-file-name filename)))
513 ;; If requested, re-initialize the list of projects files
514 (progn (org-publish-initialize-files-alist t)
515 (or (org-publish-get-project-from-filename filename)
516 (error "File %s not part of any known project"
517 (abbreviate-file-name filename))))
518 (error "Can't publish file outside of a project")))))
519 (project-plist (cdr project))
520 (ftname (file-truename filename))
521 (publishing-function
522 (or (plist-get project-plist :publishing-function)
523 'org-publish-org-to-html))
524 (base-dir (file-name-as-directory
525 (file-truename (plist-get project-plist :base-directory))))
526 (pub-dir (file-name-as-directory
527 (file-truename (plist-get project-plist :publishing-directory))))
528 tmp-pub-dir)
529 (setq tmp-pub-dir
530 (file-name-directory
531 (concat pub-dir
532 (and (string-match (regexp-quote base-dir) ftname)
533 (substring ftname (match-end 0))))))
534 (if (listp publishing-function)
535 ;; allow chain of publishing functions
536 (mapc (lambda (f)
537 (when (org-publish-needed-p filename pub-dir f tmp-pub-dir)
538 (funcall f project-plist filename tmp-pub-dir)
539 (org-publish-update-timestamp filename pub-dir f)))
540 publishing-function)
541 (when (org-publish-needed-p filename pub-dir publishing-function
542 tmp-pub-dir)
543 (funcall publishing-function project-plist filename tmp-pub-dir)
544 (org-publish-update-timestamp
545 filename pub-dir publishing-function)))))
699b9291
CD
546
547(defun org-publish-projects (projects)
548 "Publish all files belonging to the PROJECTS alist.
ed21c5c8
CD
549If :auto-sitemap is set, publish the sitemap too.
550If :makeindex is set, also produce a file theindex.org."
699b9291
CD
551 (mapc
552 (lambda (project)
b349f79f
CD
553 (let*
554 ((project-plist (cdr project))
555 (exclude-regexp (plist-get project-plist :exclude))
ed21c5c8
CD
556 (sitemap-p (plist-get project-plist :auto-sitemap))
557 (sitemap-filename (or (plist-get project-plist :sitemap-filename)
558 "sitemap.org"))
559 (sitemap-function (or (plist-get project-plist :sitemap-function)
560 'org-publish-org-sitemap))
b349f79f
CD
561 (preparation-function (plist-get project-plist :preparation-function))
562 (completion-function (plist-get project-plist :completion-function))
563 (files (org-publish-get-base-files project exclude-regexp)) file)
ed21c5c8
CD
564 (when preparation-function (run-hooks 'preparation-function))
565 (if sitemap-p (funcall sitemap-function project sitemap-filename))
699b9291 566 (while (setq file (pop files))
b349f79f 567 (org-publish-file file project))
ed21c5c8
CD
568 (when (plist-get project-plist :makeindex)
569 (org-publish-index-generate-theindex.inc
570 (plist-get project-plist :base-directory))
571 (org-publish-file (expand-file-name
572 "theindex.org"
573 (plist-get project-plist :base-directory))
574 project))
575 (when completion-function (run-hooks 'completion-function))))
699b9291
CD
576 (org-publish-expand-projects projects)))
577
ed21c5c8
CD
578(defun org-publish-org-sitemap (project &optional sitemap-filename)
579 "Create an sitemap of pages in set defined by PROJECT.
580Optionally set the filename of the sitemap with SITEMAP-FILENAME.
581Default for SITEMAP-FILENAME is 'sitemap.org'."
699b9291
CD
582 (let* ((project-plist (cdr project))
583 (dir (file-name-as-directory
584 (plist-get project-plist :base-directory)))
b349f79f
CD
585 (localdir (file-name-directory dir))
586 (indent-str (make-string 2 ?\ ))
699b9291 587 (exclude-regexp (plist-get project-plist :exclude))
b349f79f 588 (files (nreverse (org-publish-get-base-files project exclude-regexp)))
ed21c5c8
CD
589 (sitemap-filename (concat dir (or sitemap-filename "sitemap.org")))
590 (sitemap-title (or (plist-get project-plist :sitemap-title)
591 (concat "Sitemap for project " (car project))))
592 (sitemap-style (or (plist-get project-plist :sitemap-style)
2c3ad40d 593 'tree))
ed21c5c8
CD
594 (visiting (find-buffer-visiting sitemap-filename))
595 (ifn (file-name-nondirectory sitemap-filename))
596 file sitemap-buffer)
597 (with-current-buffer (setq sitemap-buffer
598 (or visiting (find-file sitemap-filename)))
ff4be292 599 (erase-buffer)
ed21c5c8 600 (insert (concat "#+TITLE: " sitemap-title "\n\n"))
699b9291 601 (while (setq file (pop files))
b349f79f
CD
602 (let ((fn (file-name-nondirectory file))
603 (link (file-relative-name file dir))
604 (oldlocal localdir))
ed21c5c8
CD
605 ;; sitemap shouldn't list itself
606 (unless (equal (file-truename sitemap-filename)
ff4be292 607 (file-truename file))
ed21c5c8
CD
608 (if (eq sitemap-style 'list)
609 (message "Generating list-style sitemap for %s" sitemap-title)
610 (message "Generating tree-style sitemap for %s" sitemap-title)
2c3ad40d
CD
611 (setq localdir (concat (file-name-as-directory dir)
612 (file-name-directory link)))
613 (unless (string= localdir oldlocal)
614 (if (string= localdir dir)
615 (setq indent-str (make-string 2 ?\ ))
616 (let ((subdirs
617 (split-string
618 (directory-file-name
619 (file-name-directory
620 (file-relative-name localdir dir))) "/"))
93b62de8
CD
621 (subdir "")
622 (old-subdirs (split-string
623 (file-relative-name oldlocal dir) "/")))
2c3ad40d 624 (setq indent-str (make-string 2 ?\ ))
93b62de8
CD
625 (while (string= (car old-subdirs) (car subdirs))
626 (setq indent-str (concat indent-str (make-string 2 ?\ )))
627 (pop old-subdirs)
628 (pop subdirs))
2c3ad40d
CD
629 (dolist (d subdirs)
630 (setq subdir (concat subdir d "/"))
93b62de8
CD
631 (insert (concat indent-str " + " d "\n"))
632 (setq indent-str (make-string
2c3ad40d
CD
633 (+ (length indent-str) 2) ?\ )))))))
634 ;; This is common to 'flat and 'tree
b349f79f 635 (insert (concat indent-str " + [[file:" link "]["
2c3ad40d 636 (org-publish-find-title file)
ff4be292
CD
637 "]]\n")))))
638 (save-buffer))
ed21c5c8 639 (or visiting (kill-buffer sitemap-buffer))))
9542795a 640
b349f79f
CD
641(defun org-publish-find-title (file)
642 "Find the title of file in project."
93b62de8
CD
643 (let* ((visiting (find-buffer-visiting file))
644 (buffer (or visiting (find-file-noselect file)))
645 title)
81ad75af 646 (with-current-buffer buffer
93b62de8
CD
647 (let* ((opt-plist (org-combine-plists (org-default-export-plist)
648 (org-infile-export-plist))))
649 (setq title
650 (or (plist-get opt-plist :title)
651 (and (not
652 (plist-get opt-plist :skip-before-1st-heading))
653 (org-export-grab-title-from-buffer))
654 (file-name-nondirectory (file-name-sans-extension file))))))
655 (unless visiting
656 (kill-buffer buffer))
657 title))
b349f79f 658
699b9291
CD
659;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
660;;; Interactive publishing functions
9542795a 661
71d35b24 662;;;###autoload
20908596 663(defalias 'org-publish-project 'org-publish)
9542795a
CD
664
665;;;###autoload
699b9291
CD
666(defun org-publish (project &optional force)
667 "Publish PROJECT."
c8d0cf5c
CD
668 (interactive
669 (list
54a0dee5 670 (assoc (org-icompleting-read
c8d0cf5c
CD
671 "Publish project: "
672 org-publish-project-alist nil t)
673 org-publish-project-alist)
674 current-prefix-arg))
20908596 675 (setq org-publish-initial-buffer (current-buffer))
9542795a 676 (save-window-excursion
c8d0cf5c 677 (let* ((org-publish-use-timestamps-flag
699b9291 678 (if force nil org-publish-use-timestamps-flag)))
c8d0cf5c 679 (org-publish-projects (list project)))))
9542795a
CD
680
681;;;###autoload
699b9291
CD
682(defun org-publish-all (&optional force)
683 "Publish all projects.
c8d0cf5c
CD
684With prefix argument, remove all files in the timestamp
685directory and force publishing all files."
9542795a 686 (interactive "P")
c8d0cf5c
CD
687 (when force
688 (org-publish-remove-all-timestamps))
ed21c5c8 689 (org-publish-initialize-files-alist force)
9542795a 690 (save-window-excursion
699b9291
CD
691 (let ((org-publish-use-timestamps-flag
692 (if force nil org-publish-use-timestamps-flag)))
693 (org-publish-projects org-publish-project-alist))))
9542795a 694
ed21c5c8 695
9542795a
CD
696;;;###autoload
697(defun org-publish-current-file (&optional force)
698 "Publish the current file.
699With prefix argument, force publish the file."
700 (interactive "P")
ed21c5c8 701 (org-publish-initialize-files-alist force)
9542795a
CD
702 (save-window-excursion
703 (let ((org-publish-use-timestamps-flag
699b9291 704 (if force nil org-publish-use-timestamps-flag)))
9542795a
CD
705 (org-publish-file (buffer-file-name)))))
706
9542795a 707;;;###autoload
699b9291
CD
708(defun org-publish-current-project (&optional force)
709 "Publish the project associated with the current file.
710With a prefix argument, force publishing of all files in
711the project."
9542795a 712 (interactive "P")
ed21c5c8 713 (org-publish-initialize-files-alist force)
9542795a 714 (save-window-excursion
c8d0cf5c 715 (let ((project (org-publish-get-project-from-filename (buffer-file-name) 'up))
699b9291
CD
716 (org-publish-use-timestamps-flag
717 (if force nil org-publish-use-timestamps-flag)))
718 (if (not project)
719 (error "File %s is not part of any known project" (buffer-file-name)))
720 (org-publish project))))
d5098885 721
ed21c5c8
CD
722
723;;; Index generation
724
725(defvar backend) ; dynamically scoped
726(defun org-publish-aux-preprocess ()
727 "Find index entries and write them to an .orgx file."
728 (let ((case-fold-search t)
729 entry index target)
730 (goto-char (point-min))
731 (while
732 (and
733 (re-search-forward "^[ \t]*#\\+index:[ \t]*\\(.*?\\)[ \t]*$" nil t)
734 (> (match-end 1) (match-beginning 1)))
735 (setq entry (match-string 1))
736 (when (eq backend 'latex)
737 (replace-match (format "\\index{%s}" entry) t t))
738 (save-excursion
739 (org-back-to-heading t)
740 (setq target (get-text-property (point) 'target))
741 (setq target (or (cdr (assoc target org-export-preferred-target-alist))
742 (cdr (assoc target org-export-id-target-alist))
743 target))
744 (push (cons entry target) index)))
745 (with-temp-file
746 (concat (file-name-sans-extension org-current-export-file) ".orgx")
747 (dolist (entry (nreverse index))
748 (insert (format "INDEX: (%s) %s\n" (cdr entry) (car entry)))))))
749
750(defun org-publish-index-generate-theindex.inc (directory)
751 "Generate the index from all .orgx files in the current directory and below."
752 (require 'find-lisp)
753 (let* ((fulldir (file-name-as-directory
754 (expand-file-name directory)))
755 (full-files (find-lisp-find-files directory "\\.orgx\\'"))
756 (re (concat "\\`" fulldir))
757 (files (mapcar (lambda (f) (if (string-match re f)
758 (substring f (match-end 0))
759 f))
760 full-files))
761 (default-directory directory)
762 index origfile buf target entry ibuffer
763 main last-main letter last-letter file sub link)
764 ;; `files' contains the list of relative file names
765 (dolist (file files)
766 (setq origfile (substring file 0 -1))
767 (setq buf (find-file-noselect file))
768 (with-current-buffer buf
769 (goto-char (point-min))
770 (while (re-search-forward "^INDEX: (\\(.*?\\)) \\(.*\\)" nil t)
771 (setq target (match-string 1)
772 entry (match-string 2))
773 (push (list entry origfile target) index)))
774 (kill-buffer buf))
775 (setq index (sort index (lambda (a b) (string< (downcase (car a))
776 (downcase (car b))))))
777 (setq ibuffer (find-file-noselect (expand-file-name "theindex.inc" directory)))
778 (with-current-buffer ibuffer
779 (erase-buffer)
780 (insert "* Index\n")
781 (setq last-letter nil)
782 (dolist (idx index)
783 (setq entry (car idx) file (nth 1 idx) target (nth 2 idx))
784 (setq letter (upcase (substring entry 0 1)))
785 (when (not (equal letter last-letter))
786 (insert "** " letter "\n")
787 (setq last-letter letter))
788 (if (string-match "!" entry)
789 (setq main (substring entry 0 (match-beginning 0))
790 sub (substring entry (match-end 0)))
791 (setq main nil sub nil last-main nil))
792 (when (and main (not (equal main last-main)))
793 (insert " - " main "\n")
794 (setq last-main main))
795 (setq link (concat "[[file:" file "::#" target "]"
796 "[" (or sub entry) "]]"))
797 (if (and main sub)
798 (insert " - " link "\n")
799 (insert " - " link "\n")))
800 (save-buffer))
801 (kill-buffer ibuffer)
802
803 (let ((index-file (expand-file-name "theindex.org" directory)))
804 (unless (file-exists-p index-file)
805 (setq ibuffer (find-file-noselect index-file))
806 (with-current-buffer ibuffer
807 (erase-buffer)
808 (insert "\n\n#+include: \"theindex.inc\"\n\n")
809 (save-buffer))
810 (kill-buffer ibuffer)))))
811
9542795a 812(provide 'org-publish)
4fbd4750 813
699b9291 814
4fbd4750 815;; arch-tag: 72807f3c-8af0-4a6b-8dca-c3376eb25adb
b349f79f 816
9542795a 817;;; org-publish.el ends here