org-publish.el 28.6 KB
Newer Older
Carsten Dominik's avatar
Carsten Dominik committed
1
;;; org-publish.el --- publish related org-mode files as a website
Glenn Morris's avatar
Glenn Morris committed
2
;; Copyright (C) 2006, 2007, 2008  Free Software Foundation, Inc.
Carsten Dominik's avatar
Carsten Dominik committed
3 4

;; Author: David O'Toole <dto@gnu.org>
5 6
;; Maintainer: Bastien Guerry <bzg AT altern DOT org>
;; Keywords: hypermedia, outlines, wp
7
;; Version: 6.10c
Carsten Dominik's avatar
Carsten Dominik committed
8

9 10
;; This file is part of GNU Emacs.
;;
11
;; GNU Emacs is free software: you can redistribute it and/or modify
Carsten Dominik's avatar
Carsten Dominik committed
12
;; it under the terms of the GNU General Public License as published by
13 14
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
Carsten Dominik's avatar
Carsten Dominik committed
15

16
;; GNU Emacs is distributed in the hope that it will be useful,
Carsten Dominik's avatar
Carsten Dominik committed
17 18 19 20 21
;; 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
22
;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
Carsten Dominik's avatar
Carsten Dominik committed
23 24 25

;;; Commentary:

26 27
;; This program allow configurable publishing of related sets of
;; Org-mode files as a complete website.
Carsten Dominik's avatar
Carsten Dominik committed
28 29 30
;;
;; org-publish.el can do the following:
;;
31 32
;; + Publish all one's org-files to HTML or LaTeX
;; + Upload HTML, images, attachments and other files to a web server
Carsten Dominik's avatar
Carsten Dominik committed
33 34
;; + Exclude selected private pages from publishing
;; + Publish a clickable index of pages
35
;; + Manage local timestamps for publishing only changed files
Carsten Dominik's avatar
Carsten Dominik committed
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
;; + Accept plugin functions to extend range of publishable content
;;
;; Special thanks to the org-mode maintainer Carsten Dominik for his
;; ideas, enthusiasm, and cooperation.

;;; Installation:

;; Put org-publish.el in your load path, byte-compile it, and then add
;; the following lines to your emacs initialization file:

;; (autoload 'org-publish "org-publish" nil t)
;; (autoload 'org-publish "org-publish-all" nil t)
;; (autoload 'org-publish "org-publish-current-file" nil t)
;; (autoload 'org-publish "org-publish-current-project" nil t)

;; NOTE: When org-publish.el is included with org.el, those forms are
;; already in the file org-install.el, and hence don't need to be put
;; in your emacs initialization file in this case.

;;; Usage:
;;
;; The program's main configuration variable is
;; `org-publish-project-alist'. See below for example configurations
;; with commentary.

;; The main interactive functions are:
;;
;; M-x org-publish
;; M-x org-publish-all
;; M-x org-publish-current-file
;; M-x org-publish-current-project

;;;; Simple example configuration:

;; (setq org-publish-project-alist
;;       (list
;;        '("org" . (:base-directory "~/org/"
;; 		     :base-extension "org"
;; 		     :publishing-directory "~/public_html"
;;                   :with-section-numbers nil
;; 		     :table-of-contents nil
77
;;                   :recursive t
Carsten Dominik's avatar
Carsten Dominik committed
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
;; 		     :style "<link rel=stylesheet href=\"../other/mystyle.css\" type=\"text/css\">")))

;;;; More complex example configuration:

;; Imagine your *.org files are kept in ~/org, your images in
;; ~/images, and stylesheets in ~/other. Now imagine you want to
;; publish the files through an ssh connection to a remote host, via
;; Tramp-mode. To maintain relative links from *.org files to /images
;; and /other, we should replicate the same directory structure in
;; your web server account's designated html root (in this case,
;; assumed to be ~/html)

;; Once you've done created the proper directories, you can adapt the
;; following example configuration to your specific paths, run M-x
;; org-publish-all, and it should publish the files to the correct
;; directories on the web server, transforming the *.org files into
;; HTML, and leaving other files alone.

;; (setq org-publish-project-alist
;;       (list
;;        '("orgfiles" :base-directory "~/org/"
;; 		       :base-extension "org"
;; 		       :publishing-directory "/ssh:user@host:~/html/notebook/"
;; 		       :publishing-function org-publish-org-to-html
;; 		       :exclude "PrivatePage.org"   ;; regexp
;; 		       :headline-levels 3
;;                     :with-section-numbers nil
;; 		       :table-of-contents nil
;; 		       :style "<link rel=stylesheet href=\"../other/mystyle.css\" type=\"text/css\">"
;; 		       :auto-preamble t
;; 		       :auto-postamble nil)
;;         ("images" :base-directory "~/images/"
;; 	             :base-extension "jpg\\|gif\\|png"
;; 		     :publishing-directory "/ssh:user@host:~/html/images/"
;; 		     :publishing-function org-publish-attachment)
;;         ("other"  :base-directory "~/other/"
;; 	   	     :base-extension "css"
;; 		     :publishing-directory "/ssh:user@host:~/html/other/"
;; 		     :publishing-function org-publish-attachment)
;;         ("website" :components ("orgfiles" "images" "other"))))

;; For more information, see the documentation for the variable
;; `org-publish-project-alist'.

;; Of course, you don't have to publish to remote directories from
;; within emacs. You can always just publish to local folders, and
;; then use the synchronization/upload tool of your choice.

;;; List of user-visible changes since version 1.27

;; 1.78: Allow list-valued :publishing-function
;; 1.77: Added :preparation-function, this allows you to use GNU Make etc.
;; 1.65: Remove old "composite projects". They're redundant.
;; 1.64: Allow meta-projects with :components
;; 1.57: Timestamps flag is now called "org-publish-use-timestamps-flag"
;; 1.52: Properly set default for :index-filename
;; 1.48: Composite projects allowed.
;;       :include keyword allowed.
;; 1.43: Index no longer includes itself in the index.
;; 1.42: Fix "function definition is void" error
;;       when :publishing-function not set in org-publish-current-file.
;; 1.41: Fixed bug where index isn't published on first try.
;; 1.37: Added interactive function "org-publish". Prompts for particular
;;       project name to publish.
;; 1.34: Added force-publish option to all interactive functions.
;; 1.32: Fixed "index.org has changed on disk" error during index publishing.
;; 1.30: Fixed startup error caused by (require 'em-unix)

;;; Code:

(eval-when-compile
  (require 'cl))
150 151
(require 'org)
(require 'org-exp)
Carsten Dominik's avatar
Carsten Dominik committed
152

153 154 155 156
(eval-and-compile
  (unless (fboundp 'declare-function)
    (defmacro declare-function (fn file &optional arglist fileonly))))

Carsten Dominik's avatar
Carsten Dominik committed
157
(defgroup org-publish nil
158 159 160
  "Options for publishing a set of Org-mode and related files."
  :tag "Org Publishing"
  :group 'org)
Carsten Dominik's avatar
Carsten Dominik committed
161 162 163 164

(defcustom org-publish-project-alist nil
  "Association list to control publishing behavior.
Each element of the alist is a publishing 'project.'  The CAR of
165
each element is a string, uniquely identifying the project.  The
Carsten Dominik's avatar
Carsten Dominik committed
166 167 168 169 170 171 172 173 174 175 176
CDR of each element is in one of the following forms:

  (:property value :property value ... )

OR,

  (:components (\"project-1\" \"project-2\" ...))

When the CDR of an element of org-publish-project-alist is in
this second form, the elements of the list after :components are
taken to be components of the project, which group together files
177 178
requiring different publishing options.  When you publish such a
project with \\[org-publish], the components all publish.
Carsten Dominik's avatar
Carsten Dominik committed
179 180 181

When a property is given a value in org-publish-project-alist, its
setting overrides the value of the corresponding user variable
182
\(if any) during publishing.  However, options set within a file
Carsten Dominik's avatar
Carsten Dominik committed
183 184 185 186
override everything.

Most properties are optional, but some should always be set:

187 188 189 190 191
  :base-directory        Directory containing publishing source files
  :base-extension        Extension (without the dot!) of source files.
                         This can be a regular expression.
  :publishing-directory  Directory (possibly remote) where output
                         files will be published
Carsten Dominik's avatar
Carsten Dominik committed
192 193

The :exclude property may be used to prevent certain files from
194
being published.  Its value may be a string or regexp matching
Carsten Dominik's avatar
Carsten Dominik committed
195 196
file names you don't want to be published.

197
The :include property may be used to include extra files.  Its
198 199
value may be a list of filenames to include. The filenames are
considered relative to the base directory.
Carsten Dominik's avatar
Carsten Dominik committed
200 201 202 203 204

When both :include and :exclude properties are given values, the
exclusion step happens first.

One special property controls which back-end function to use for
205
publishing files in the project.  This can be used to extend the
Carsten Dominik's avatar
Carsten Dominik committed
206 207 208
set of file types publishable by org-publish, as well as the set
of output formats.

209 210 211
  :publishing-function     Function to publish file.  The default is
                           `org-publish-org-to-html', but other
                           values are possible.  May also be a
212 213 214
                           list of functions, in which case
                           each function in the list is invoked
                           in turn.
Carsten Dominik's avatar
Carsten Dominik committed
215 216

Another property allows you to insert code that prepares a
217
project for publishing.  For example, you could call GNU Make on a
218
certain makefile, to ensure published files are built up to date.
Carsten Dominik's avatar
Carsten Dominik committed
219

220 221
  :preparation-function   Function to be called before publishing
                          this project.
222 223
  :completion-function    Function to be called after publishing
                          this project.
Carsten Dominik's avatar
Carsten Dominik committed
224 225 226

Some properties control details of the Org publishing process,
and are equivalent to the corresponding user variables listed in
227
the right column.  See the documentation for those variables to
Carsten Dominik's avatar
Carsten Dominik committed
228 229
learn more about their use and default values.

230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
  :language              `org-export-default-language'
  :headline-levels       `org-export-headline-levels'
  :section-numbers       `org-export-with-section-numbers'
  :table-of-contents     `org-export-with-toc'
  :emphasize             `org-export-with-emphasize'
  :sub-superscript       `org-export-with-sub-superscripts'
  :TeX-macros            `org-export-with-TeX-macros'
  :fixed-width           `org-export-with-fixed-width'
  :tables                `org-export-with-tables'
  :table-auto-headline   `org-export-highlight-first-table-line'
  :style                 `org-export-html-style'
  :convert-org-links     `org-export-html-link-org-files-as-html'
  :inline-images         `org-export-html-inline-images'
  :expand-quoted-html    `org-export-html-expand'
  :timestamp             `org-export-html-with-timestamp'
  :publishing-directory  `org-export-publishing-directory'
  :preamble              `org-export-html-preamble'
  :postamble             `org-export-html-postamble'
  :auto-preamble         `org-export-html-auto-preamble'
  :auto-postamble        `org-export-html-auto-postamble'
  :author                `user-full-name'
  :email                 `user-mail-address'
Carsten Dominik's avatar
Carsten Dominik committed
252 253 254 255

The following properties may be used to control publishing of an
index of files or summary page for a given project.

256
  :auto-index            Whether to publish an index during
257 258 259 260
                         `org-publish-current-project' or `org-publish-all'.
  :index-filename        Filename for output of index.  Defaults
                         to 'index.org' (which becomes 'index.html').
  :index-title           Title of index page.  Defaults to name of file.
261
  :index-function        Plugin function to use for generation of index.
262
                         Defaults to `org-publish-org-index', which
263
                         generates a plain list of links to all files
264 265 266 267 268 269
                         in the project.
  :index-style           Can be `list' (index is just an itemized list
                         of the titles of the files involved) or 
                         `tree' (the directory structure of the source
                         files is reflected in the index).  Defaults to
                         `tree'."
Carsten Dominik's avatar
Carsten Dominik committed
270 271 272 273 274
  :group 'org-publish
  :type 'alist)

(defcustom org-publish-use-timestamps-flag t
  "When non-nil, use timestamp checking to publish only changed files.
275
When nil, do no timestamp checking and always publish all files."
Carsten Dominik's avatar
Carsten Dominik committed
276 277 278
  :group 'org-publish
  :type 'boolean)

279 280
(defcustom org-publish-timestamp-directory (convert-standard-filename 
					    "~/.org-timestamps/")
Carsten Dominik's avatar
Carsten Dominik committed
281 282
  "Name of directory in which to store publishing timestamps."
  :group 'org-publish
283
  :type 'directory)
Carsten Dominik's avatar
Carsten Dominik committed
284

285 286 287 288 289 290 291
(defcustom org-publish-before-export-hook nil
  "Hook run before export on the Org file.
If the functions in this hook modify the original Org buffer, the
modified buffer will be used for export, but the buffer will be
restored and saved back to its initial state after export."
  :group 'org-publish
  :type 'hook)
Carsten Dominik's avatar
Carsten Dominik committed
292

293 294 295 296 297
(defcustom org-publish-after-export-hook nil
  "Hook run after export on the exported buffer.
If functions in this hook modify the buffer, it will be saved."
  :group 'org-publish
  :type 'hook)
Carsten Dominik's avatar
Carsten Dominik committed
298

299 300
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Timestamp-related functions
Carsten Dominik's avatar
Carsten Dominik committed
301 302 303

(defun org-publish-timestamp-filename (filename)
  "Return path to timestamp file for filename FILENAME."
304 305
  (concat (file-name-as-directory org-publish-timestamp-directory)
         "X" (if (fboundp 'sha1) (sha1 filename) (md5 filename))))
Carsten Dominik's avatar
Carsten Dominik committed
306 307

(defun org-publish-needed-p (filename)
308
  "Return `t' if FILENAME should be published."
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326
  (let ((rtn
	 (if org-publish-use-timestamps-flag
	     (if (file-exists-p org-publish-timestamp-directory)
		 ;; first handle possible wrong timestamp directory
		 (if (not (file-directory-p org-publish-timestamp-directory))
		     (error "Org publish timestamp: %s is not a directory"
			    org-publish-timestamp-directory)
		   ;; there is a timestamp, check if FILENAME is newer
		   (file-newer-than-file-p
		    filename (org-publish-timestamp-filename filename)))
	       (make-directory org-publish-timestamp-directory)
	       t)
	   ;; don't use timestamps, always return t
	   t)))
    (if rtn
	(message "Publishing file %s" filename)
      (message   "Skipping unmodified file %s" filename))
    rtn))
Carsten Dominik's avatar
Carsten Dominik committed
327 328

(defun org-publish-update-timestamp (filename)
329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348
  "Update publishing timestamp for file FILENAME.
If there is no timestamp, create one."
  (let ((timestamp-file (org-publish-timestamp-filename filename))
	newly-created-timestamp)
    (if (not (file-exists-p timestamp-file))
	;; create timestamp file if needed
	(with-temp-buffer
	  (make-directory (file-name-directory timestamp-file) t)
	  (write-file timestamp-file)
	  (setq newly-created-timestamp t)))
    ;; Emacs 21 doesn't have `set-file-times'
    (if (and (fboundp 'set-file-times)
	     (not newly-created-timestamp))
        (set-file-times timestamp-file)
      (call-process "touch" nil 0 nil timestamp-file))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Mapping files to project names

(defvar org-publish-files-alist nil
349
  "Alist of files and their parent projects.
350 351 352 353
Each element of this alist is of the form:

  (file-name . project-name)")

354 355 356 357 358
(defvar org-publish-initial-buffer nil
  "The buffer `org-publish' has been called from.")
(defvar org-publish-temp-files nil
  "Temporary list of files to be published.")

359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386
(defun org-publish-initialize-files-alist (&optional refresh)
  "Set `org-publish-files-alist' if it is not set.
Also set it if the optional argument REFRESH is non-nil."
  (interactive "P")
  (when (or refresh (not org-publish-files-alist))
    (setq org-publish-files-alist
	  (org-publish-get-files org-publish-project-alist))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Compatibility aliases

;; Delete-dups is not in Emacs <22
(if (fboundp 'delete-dups)
    (defalias 'org-publish-delete-dups 'delete-dups)
  (defun org-publish-delete-dups (list)
    "Destructively remove `equal' duplicates from LIST.
Store the result in LIST and return it.  LIST must be a proper list.
Of several `equal' occurrences of an element in LIST, the first
one is kept.

This is a compatibility function for Emacsen without `delete-dups'."
    ;; Code from `subr.el' in Emacs 22:
    (let ((tail list))
      (while tail
	(setcdr tail (delete (car tail) (cdr tail)))
	(setq tail (cdr tail))))
    list))

387 388
(declare-function org-publish-delete-dups "org-publish" (list))

389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Getting project information out of org-publish-project-alist

(defun org-publish-get-files (projects-alist &optional no-exclusion)
  "Return the list of all publishable files for PROJECTS-ALIST.
If NO-EXCLUSION is non-nil, don't exclude files."
  (let (all-files)
    ;; add all projects
    (mapc
     (lambda(p)
       (let* ((exclude (plist-get (cdr p) :exclude))
	      (files (and p (org-publish-get-base-files p exclude))))
	 ;; add all files from this project
	 (mapc (lambda(f)
		 (add-to-list 'all-files
			      (cons (expand-file-name f) (car p))))
	       files)))
     (org-publish-expand-projects projects-alist))
    all-files))

(defun org-publish-expand-projects (projects-alist)
410 411 412 413 414 415 416 417 418 419 420 421
  "Expand projects in PROJECTS-ALIST.
This splices all the components into the list."
  (let ((rest projects-alist) rtn p components)
    (while (setq p (pop rest))
      (if (setq components (plist-get (cdr p) :components))
	  (setq rest (append
		      (mapcar (lambda (x) (assoc x org-publish-project-alist))
			      components)
		      rest))
	(push p rtn)))
    (nreverse (org-publish-delete-dups (delq nil rtn)))))
	
422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441
(defun org-publish-get-base-files-1 (base-dir &optional recurse match skip-file skip-dir)
  "Set `org-publish-temp-files' with files from BASE-DIR directory.
If RECURSE is non-nil, check BASE-DIR recursively.  If MATCH is
non-nil, restrict this list to the files matching the regexp
MATCH.  If SKIP-FILE is non-nil, skip file matching the regexp
SKIP-FILE.  If SKIP-DIR is non-nil, don't check directories
matching the regexp SKIP-DIR when recursiing through BASE-DIR."
  (mapc (lambda (f)
	  (let ((fd-p (car (file-attributes f)))
		(fnd (file-name-nondirectory f)))
	    (if (and fd-p recurse
		     (not (string-match "^\\.+$" fnd))
		     (if skip-dir (not (string-match skip-dir fnd)) t))
		(org-publish-get-base-files-1 f recurse match skip-file skip-dir)
	      (unless (or fd-p ;; this is a directory
			  (and skip-file (string-match skip-file fnd))
			  (not (string-match match fnd)))
		(pushnew f org-publish-temp-files)))))
	(directory-files base-dir t (unless recurse match))))

442 443
(defun org-publish-get-base-files (project &optional exclude-regexp)
  "Return a list of all files in PROJECT.
Carsten Dominik's avatar
Carsten Dominik committed
444 445
If EXCLUDE-REGEXP is set, this will be used to filter out
matching filenames."
446 447 448 449
  (let* ((project-plist (cdr project))
	 (base-dir (file-name-as-directory
		    (plist-get project-plist :base-directory)))
 	 (include-list (plist-get project-plist :include))
450
 	 (recurse (plist-get project-plist :recursive))
451
 	 (extension (or (plist-get project-plist :base-extension) "org"))
452 453 454 455 456 457
 	 (match (concat "^[^\\.].*\\.\\(" extension "\\)$")))
    (setq org-publish-temp-files nil)
    (org-publish-get-base-files-1 base-dir recurse match
				  ;; FIXME distinguish exclude regexp
				  ;; for skip-file and skip-dir?
				  exclude-regexp exclude-regexp)
458 459 460 461 462
    (mapc (lambda (f)
	    (pushnew 
	     (expand-file-name (concat base-dir f))
	     org-publish-temp-files))
	  include-list)
463
    org-publish-temp-files))
Carsten Dominik's avatar
Carsten Dominik committed
464 465

(defun org-publish-get-project-from-filename (filename)
466 467 468 469
  "Return the project FILENAME belongs."
  (let* ((project-name (cdr (assoc (expand-file-name filename)
				   org-publish-files-alist))))
    (assoc project-name org-publish-project-alist)))
470

471 472
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Pluggable publishing back-end functions
473

474
(defun org-publish-org-to (format plist filename pub-dir)
475 476
  "Publish an org file to FORMAT.
PLIST is the property list for the given project.
477 478
FILENAME is the filename of the org file to be published.
PUB-DIR is the publishing directory."
John Wiegley's avatar
John Wiegley committed
479
  (require 'org)
480 481
  (unless (file-exists-p pub-dir)
    (make-directory pub-dir t))
482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512
  (let ((visiting (find-buffer-visiting filename)))
    (save-excursion
      (switch-to-buffer (or visiting (find-file filename)))
      (let* ((plist (cons :buffer-will-be-killed (cons t plist)))
	     (init-buf (current-buffer))
	     (init-point (point))
	     (init-buf-string (buffer-string))
	     export-buf-or-file)
	;; run hooks before exporting
	(run-hooks 'org-publish-before-export-hook)
	;; export the possibly modified buffer
	(setq export-buf-or-file
	      (funcall (intern (concat "org-export-as-" format))
		       (plist-get plist :headline-levels)
		       nil plist nil nil pub-dir))
	(when (and (bufferp export-buf-or-file)
		   (buffer-live-p export-buf-or-file))
	  (set-buffer export-buf-or-file)
	  ;; run hooks after export and save export
	  (and (run-hooks 'org-publish-after-export-hook)
	       (if (buffer-modified-p) (save-buffer)))
	  (kill-buffer export-buf-or-file))
	;; maybe restore buffer's content
	(set-buffer init-buf)
	(when (buffer-modified-p init-buf)
	  (erase-buffer)
	  (insert init-buf-string)
	  (save-buffer)
	  (goto-char init-point))
	(unless visiting
	  (kill-buffer init-buf))))))
513 514 515 516 517 518

(defun org-publish-org-to-latex (plist filename pub-dir)
  "Publish an org file to LaTeX.
See `org-publish-org-to' to the list of arguments."
  (org-publish-org-to "latex" plist filename pub-dir))

519 520 521 522 523
(defun org-publish-org-to-pdf (plist filename pub-dir)
  "Publish an org file to PDF (via LaTeX).
See `org-publish-org-to' to the list of arguments."
  (org-publish-org-to "pdf" plist filename pub-dir))

524 525 526 527 528
(defun org-publish-org-to-html (plist filename pub-dir)
  "Publish an org file to HTML.
See `org-publish-org-to' to the list of arguments."
  (org-publish-org-to "html" plist filename pub-dir))

Glenn Morris's avatar
Glenn Morris committed
529
(autoload 'eshell/cp "em-unix")		; why the eshell version?
530

531
(defun org-publish-attachment (plist filename pub-dir)
Carsten Dominik's avatar
Carsten Dominik committed
532
  "Publish a file with no transformation of any kind.
533
See `org-publish-org-to' to the list of arguments."
534 535
  (unless (file-directory-p pub-dir)
    (make-directory pub-dir t))
536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556
  (eshell/cp filename pub-dir))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Publishing files, sets of files, and indices

(defun org-publish-file (filename &optional project)
  "Publish file FILENAME from PROJECT."
  (when (org-publish-needed-p filename)
    (let* ((project
	    (or project
		(or (org-publish-get-project-from-filename filename)
		    (if (y-or-n-p
			 (format "%s is not in a project.  Re-read the list of projects files? "
				 (abbreviate-file-name filename)))
			;; If requested, re-initialize the list of projects files
			(progn (org-publish-initialize-files-alist t)
			       (or (org-publish-get-project-from-filename filename)
				   (error "File %s not part of any known project"
					  (abbreviate-file-name filename))))
		      (error "Can't publish file outside of a project")))))
	   (project-plist (cdr project))
557
	   (ftname (file-truename filename))
558
	   (publishing-function
559 560 561 562 563 564 565 566 567 568
	    (or (plist-get project-plist :publishing-function)
		'org-publish-org-to-html))
	   (base-dir (file-name-as-directory
		      (file-truename (plist-get project-plist :base-directory))))
	   (pub-dir (file-name-as-directory
		     (file-truename (plist-get project-plist :publishing-directory))))
	   tmp-pub-dir)
      (setq tmp-pub-dir
	    (file-name-directory
	     (concat pub-dir
569 570
		     (and (string-match (regexp-quote base-dir) ftname)
			  (substring ftname (match-end 0))))))
Carsten Dominik's avatar
Carsten Dominik committed
571 572 573
      (if (listp publishing-function)
	  ;; allow chain of publishing functions
	  (mapc (lambda (f)
574
		  (funcall f project-plist filename tmp-pub-dir))
Carsten Dominik's avatar
Carsten Dominik committed
575
		publishing-function)
576 577 578 579 580 581 582 583
	(funcall publishing-function project-plist filename tmp-pub-dir)))
    (org-publish-update-timestamp filename)))

(defun org-publish-projects (projects)
  "Publish all files belonging to the PROJECTS alist.
If :auto-index is set, publish the index too."
  (mapc
   (lambda (project)
584 585 586 587 588 589 590 591 592 593 594
     (let*
	 ((project-plist (cdr project))
	  (exclude-regexp (plist-get project-plist :exclude))
	  (index-p (plist-get project-plist :auto-index))
	  (index-filename (or (plist-get project-plist :index-filename)
			      "index.org"))
	  (index-function (or (plist-get project-plist :index-function)
			      'org-publish-org-index))
	  (preparation-function (plist-get project-plist :preparation-function))
	  (completion-function (plist-get project-plist :completion-function))
	  (files (org-publish-get-base-files project exclude-regexp)) file)
595 596 597
       (when preparation-function (funcall preparation-function))
       (if index-p (funcall index-function project index-filename))
       (while (setq file (pop files))
598 599
	 (org-publish-file file project))
       (when completion-function (funcall completion-function))))
600 601 602 603 604 605 606 607 608
   (org-publish-expand-projects projects)))

(defun org-publish-org-index (project &optional index-filename)
  "Create an index of pages in set defined by PROJECT.
Optionally set the filename of the index with INDEX-FILENAME.
Default for INDEX-FILENAME is 'index.org'."
  (let* ((project-plist (cdr project))
	 (dir (file-name-as-directory
	       (plist-get project-plist :base-directory)))
609 610
	 (localdir (file-name-directory dir))
	 (indent-str (make-string 2 ?\ ))
611
	 (exclude-regexp (plist-get project-plist :exclude))
612
	 (files (nreverse (org-publish-get-base-files project exclude-regexp)))
Carsten Dominik's avatar
Carsten Dominik committed
613
	 (index-filename (concat dir (or index-filename "index.org")))
614 615
	 (index-title (or (plist-get project-plist :index-title)
			  (concat "Index for project " (car project))))
616 617
	 (index-style (or (plist-get project-plist :index-style)
			  'tree))
Carsten Dominik's avatar
Carsten Dominik committed
618 619
	 (index-buffer (find-buffer-visiting index-filename))
	 (ifn (file-name-nondirectory index-filename))
620
	 file)
Carsten Dominik's avatar
Carsten Dominik committed
621 622 623 624
    ;; if buffer is already open, kill it to prevent error message
    (if index-buffer
	(kill-buffer index-buffer))
    (with-temp-buffer
625
      (insert (concat index-title "\n\n"))
626
      (while (setq file (pop files))
627 628 629
	(let ((fn (file-name-nondirectory file))
	      (link (file-relative-name file dir))
	      (oldlocal localdir))
630 631
	  ;; index shouldn't index itself
	  (unless (string= fn ifn)
632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653
	    (if (eq index-style 'list)
		(message "Generating list-style index for %s" index-title)
	      (message "Generating tree-style index for %s" index-title)
	      (setq localdir (concat (file-name-as-directory dir)
				     (file-name-directory link)))
	      (unless (string= localdir oldlocal)
		(if (string= localdir dir)
		    (setq indent-str (make-string 2 ?\ ))
		  (let ((subdirs
			 (split-string
			  (directory-file-name
			   (file-name-directory
			    (file-relative-name localdir dir))) "/"))
			(subdir ""))
		    (setq indent-str (make-string 2 ?\ ))
		    (dolist (d subdirs)
		      (setq subdir (concat subdir d "/"))
		      (insert (concat indent-str " + [[file:" 
				      subdir "][" d "/]]\n"))
		      (setq indent-str (make-string 
					(+ (length indent-str) 2) ?\ )))))))
	    ;; This is common to 'flat and 'tree
654
	    (insert (concat indent-str " + [[file:" link "]["
655 656 657
			    (org-publish-find-title file)
			    "]]\n"))
	    )))
Carsten Dominik's avatar
Carsten Dominik committed
658 659 660
      (write-file index-filename)
      (kill-buffer (current-buffer)))))

661 662 663 664 665 666 667 668 669 670
(defun org-publish-find-title (file)
  "Find the title of file in project."
  (save-excursion
    (set-buffer (find-file-noselect file))
    (let* ((opt-plist (org-combine-plists (org-default-export-plist)
 					  (org-infile-export-plist))))
      (or (plist-get opt-plist :title)
 	  (and (not
 		(plist-get opt-plist :skip-before-1st-heading))
 	       (org-export-grab-title-from-buffer))
671 672
	  (file-name-nondirectory (file-name-sans-extension file))))))

673

674 675
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Interactive publishing functions
Carsten Dominik's avatar
Carsten Dominik committed
676

677
;;;###autoload
678
(defalias 'org-publish-project 'org-publish)
Carsten Dominik's avatar
Carsten Dominik committed
679 680

;;;###autoload
681 682 683
(defun org-publish (project &optional force)
  "Publish PROJECT."
  (interactive "P")
684
  (setq org-publish-initial-buffer (current-buffer))
Carsten Dominik's avatar
Carsten Dominik committed
685
  (save-window-excursion
686 687 688 689 690 691 692 693 694
    (let* ((force current-prefix-arg)
	   (org-publish-use-timestamps-flag
	    (if force nil org-publish-use-timestamps-flag)))
      (org-publish-projects
       (list (or project
		 (assoc (completing-read
			 "Publish project: "
			 org-publish-project-alist nil t)
			org-publish-project-alist)))))))
Carsten Dominik's avatar
Carsten Dominik committed
695 696

;;;###autoload
697 698 699
(defun org-publish-all (&optional force)
  "Publish all projects.
With prefix argument, force publish all files."
Carsten Dominik's avatar
Carsten Dominik committed
700
  (interactive "P")
701
  (org-publish-initialize-files-alist)
Carsten Dominik's avatar
Carsten Dominik committed
702
  (save-window-excursion
703 704 705
    (let ((org-publish-use-timestamps-flag
	   (if force nil org-publish-use-timestamps-flag)))
      (org-publish-projects org-publish-project-alist))))
Carsten Dominik's avatar
Carsten Dominik committed
706 707 708 709 710 711

;;;###autoload
(defun org-publish-current-file (&optional force)
  "Publish the current file.
With prefix argument, force publish the file."
  (interactive "P")
712
  (org-publish-initialize-files-alist)
Carsten Dominik's avatar
Carsten Dominik committed
713 714
  (save-window-excursion
    (let ((org-publish-use-timestamps-flag
715
	   (if force nil org-publish-use-timestamps-flag)))
Carsten Dominik's avatar
Carsten Dominik committed
716 717 718
      (org-publish-file (buffer-file-name)))))

;;;###autoload
719 720 721 722
(defun org-publish-current-project (&optional force)
  "Publish the project associated with the current file.
With a prefix argument, force publishing of all files in
the project."
Carsten Dominik's avatar
Carsten Dominik committed
723
  (interactive "P")
724
  (org-publish-initialize-files-alist)
Carsten Dominik's avatar
Carsten Dominik committed
725
  (save-window-excursion
726 727 728 729 730 731
    (let ((project (org-publish-get-project-from-filename (buffer-file-name)))
	  (org-publish-use-timestamps-flag
	   (if force nil org-publish-use-timestamps-flag)))
      (if (not project)
	  (error "File %s is not part of any known project" (buffer-file-name)))
      (org-publish project))))
732

Carsten Dominik's avatar
Carsten Dominik committed
733
(provide 'org-publish)
Carsten Dominik's avatar
Carsten Dominik committed
734

735

Carsten Dominik's avatar
Carsten Dominik committed
736
;; arch-tag: 72807f3c-8af0-4a6b-8dca-c3376eb25adb
737

Carsten Dominik's avatar
Carsten Dominik committed
738
;;; org-publish.el ends here