org-publish.el 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541
  1. ;;; org-publish.el --- publish related org-mode files as a website
  2. ;; Copyright (C) 2006 David O'Toole
  3. ;; Author: David O'Toole <dto@gnu.org>
  4. ;; Keywords: hypermedia, outlines
  5. ;; Version:
  6. ;; $Id: org-publish.el,v 1.64 2006/05/19 19:45:34 dto Exp dto $
  7. ;; This file is free software; you can redistribute it and/or modify
  8. ;; it under the terms of the GNU General Public License as published by
  9. ;; the Free Software Foundation; either version 2, or (at your option)
  10. ;; any later version.
  11. ;; This file is distributed in the hope that it will be useful,
  12. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. ;; GNU General Public License for more details.
  15. ;; You should have received a copy of the GNU General Public License
  16. ;; along with GNU Emacs; see the file COPYING. If not, write to
  17. ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  18. ;; Boston, MA 02110-1301, USA.
  19. ;; This file is NOT part of GNU Emacs.
  20. ;;; Commentary:
  21. ;; Requires at least version 4.27 of org.el
  22. ;;
  23. ;; The official org-mode website:
  24. ;; http://staff.science.uva.nl/~dominik/Tools/org/
  25. ;;
  26. ;; Home page for org-publish.el:
  27. ;; http://dto.freeshell.org/notebook/OrgMode.html
  28. ;; This program extends the HTML publishing support of Emacs Org-mode
  29. ;; to allow configurable publishing of related sets of files as a
  30. ;; complete website.
  31. ;;
  32. ;; org-publish.el can do the following:
  33. ;;
  34. ;; + Publish all one's org-files to html
  35. ;; + Upload html, images, attachments and other files to a web server
  36. ;; + Exclude selected private pages from publishing
  37. ;; + Publish a clickable index of pages
  38. ;; + Manage local timestamps, for publishing only changed files
  39. ;; + Accept plugin functions to extend range of publishable content
  40. ;;
  41. ;; Special thanks to the org-mode maintainer Carsten Dominik for his
  42. ;; ideas, enthusiasm, and cooperation.
  43. ;;; Installation:
  44. ;; Put org-publish.el in your load path, byte-compile it, and then add
  45. ;; the following lines to your emacs initialization file:
  46. ;; (autoload 'org-publish "org-publish" nil t)
  47. ;; (autoload 'org-publish "org-publish-all" nil t)
  48. ;; (autoload 'org-publish "org-publish-current-file" nil t)
  49. ;; (autoload 'org-publish "org-publish-current-project" nil t)
  50. ;;; Usage:
  51. ;;
  52. ;; The program's main configuration variable is
  53. ;; `org-publish-project-alist'. See below for example configurations
  54. ;; with commentary.
  55. ;; The main interactive functions are:
  56. ;;
  57. ;; M-x org-publish
  58. ;; M-x org-publish-all
  59. ;; M-x org-publish-current-file
  60. ;; M-x org-publish-current-project
  61. ;;;; Simple example configuration:
  62. ;; (setq org-publish-project-alist
  63. ;; (list
  64. ;; '("org" . (:base-directory "~/org/"
  65. ;; :base-extension "org"
  66. ;; :publishing-directory "~/public_html"
  67. ;; :with-section-numbers nil
  68. ;; :table-of-contents nil
  69. ;; :style "<link rel=stylesheet href=\"../other/mystyle.css\" type=\"text/css\">")))
  70. ;;;; More complex example configuration:
  71. ;; Imagine your *.org files are kept in ~/org, your images in
  72. ;; ~/images, and stylesheets in ~/other. Now imagine you want to
  73. ;; publish the files through an ssh connection to a remote host, via
  74. ;; Tramp-mode. To maintain relative links from *.org files to /images
  75. ;; and /other, we should replicate the same directory structure in
  76. ;; your web server account's designated html root (in this case,
  77. ;; assumed to be ~/html)
  78. ;; Once you've done created the proper directories, you can adapt the
  79. ;; following example configuration to your specific paths, run M-x
  80. ;; org-publish-all, and it should publish the files to the correct
  81. ;; directories on the web server, transforming the *.org files into
  82. ;; HTML, and leaving other files alone.
  83. ;; (setq org-publish-project-alist
  84. ;; (list
  85. ;; '("website" .
  86. ;; (("orgfiles" :base-directory "~/org/"
  87. ;; :base-extension "org"
  88. ;; :publishing-directory "/ssh:user@host:~/html/notebook/"
  89. ;; :publishing-function org-publish-org-to-html
  90. ;; :exclude "PrivatePage.org" ;; regexp
  91. ;; :headline-levels 3
  92. ;; :with-section-numbers nil
  93. ;; :table-of-contents nil
  94. ;; :style "<link rel=stylesheet href=\"../other/mystyle.css\" type=\"text/css\">"
  95. ;; :auto-preamble t
  96. ;; :auto-postamble nil)
  97. ;;
  98. ;; ("images" :base-directory "~/images/"
  99. ;; :base-extension "jpg\\|gif\\|png"
  100. ;; :publishing-directory "/ssh:user@host:~/html/images/"
  101. ;; :publishing-function org-publish-attachment)
  102. ;;
  103. ;; ("other" :base-directory "~/other/"
  104. ;; :base-extension "css"
  105. ;; :publishing-directory "/ssh:user@host:~/html/other/"
  106. ;; :publishing-function org-publish-attachment)))))
  107. ;; For more information, see the documentation for the variable
  108. ;; `org-publish-project-alist'.
  109. ;; Of course, you don't have to publish to remote directories from
  110. ;; within emacs. You can always just publish to local folders, and
  111. ;; then use the synchronization/upload tool of your choice.
  112. ;;; List of user-visible changes since version 1.27
  113. ;; 1.57: Timestamps flag is now called "org-publish-use-timestamps-flag"
  114. ;; 1.52: Properly set default for :index-filename
  115. ;; 1.48: Composite projects allowed.
  116. ;; :include keyword allowed.
  117. ;; 1.43: Index no longer includes itself in the index.
  118. ;; 1.42: Fix "function definition is void" error
  119. ;; when :publishing-function not set in org-publish-current-file.
  120. ;; 1.41: Fixed bug where index isn't published on first try.
  121. ;; 1.37: Added interactive function "org-publish". Prompts for particular
  122. ;; project name to publish.
  123. ;; 1.34: Added force-publish option to all interactive functions.
  124. ;; 1.32: Fixed "index.org has changed on disk" error during index publishing.
  125. ;; 1.30: Fixed startup error caused by (require 'em-unix)
  126. ;;; Code:
  127. (eval-when-compile
  128. (require 'cl))
  129. (defgroup org-publish nil
  130. "Options for publishing a set of Org-mode and related files."
  131. :tag "Org Publishing"
  132. :group 'org)
  133. (defcustom org-publish-project-alist nil
  134. "Association list to control publishing behavior.
  135. Each element of the alist is a publishing 'project.' The CAR of
  136. each element is a string, uniquely identifying the project. The
  137. CDR of each element is either a property list with configuration
  138. options for the publishing process (see below), or a list of the
  139. following form:
  140. ((\"component1\" :property value :property value ... )
  141. (\"component2\" :property value :property value ... ))
  142. When the CDR of an element of org-publish-project-alist is in
  143. this second form, the elements of this list are taken to be
  144. components of the project, which group together files requiring
  145. different publishing options.
  146. When a property is given a value in org-publish-project-alist, its
  147. setting overrides the value of the corresponding user variable
  148. (if any) during publishing. However, options set within a file
  149. override everything.
  150. Most properties are optional, but some should always be set:
  151. :base-directory Directory containing publishing source files
  152. :base-extension Extension (without the dot!) of source files.
  153. This can be a regular expression.
  154. :publishing-directory Directory (possibly remote) where output
  155. files will be published
  156. The :exclude property may be used to prevent certain files from
  157. being published. Its value may be a string or regexp matching
  158. file names you don't want to be published.
  159. The :include property may be used to include extra files. Its
  160. value may be a list of filenames to include. The filenames are
  161. considered relative to the publishing directory.
  162. When both :include and :exclude properties are given values, the
  163. exclusion step happens first.
  164. One special property controls which back-end function to use for
  165. publishing files in the project. This can be used to extend the
  166. set of file types publishable by org-publish, as well as the set
  167. of output formats.
  168. :publishing-function Function to publish file. The default is
  169. org-publish-org-to-html, but other
  170. values are possible.
  171. Some properties control details of the Org publishing process,
  172. and are equivalent to the corresponding user variables listed in
  173. the right column. See the documentation for those variables to
  174. learn more about their use and default values.
  175. :language org-export-default-language
  176. :headline-levels org-export-headline-levels
  177. :section-numbers org-export-with-section-numbers
  178. :table-of-contents org-export-with-toc
  179. :emphasize org-export-with-emphasize
  180. :sub-superscript org-export-with-sub-superscripts
  181. :TeX-macros org-export-with-TeX-macros
  182. :fixed-width org-export-with-fixed-width
  183. :tables org-export-with-tables
  184. :table-auto-headline org-export-highlight-first-table-line
  185. :style org-export-html-style
  186. :convert-org-links org-export-html-link-org-files-as-html
  187. :inline-images org-export-html-inline-images
  188. :expand-quoted-html org-export-html-expand
  189. :timestamp org-export-html-with-timestamp
  190. :publishing-directory org-export-publishing-directory
  191. :preamble org-export-html-preamble
  192. :postamble org-export-html-postamble
  193. :auto-preamble org-export-html-auto-preamble
  194. :auto-postamble org-export-html-auto-postamble
  195. :author user-full-name
  196. :email user-mail-address
  197. The following properties may be used to control publishing of an
  198. index of files or summary page for a given project.
  199. :auto-index Whether to publish an index during
  200. org-publish-current-project or org-publish-all.
  201. :index-filename Filename for output of index. Defaults
  202. to 'index.org' (which becomes 'index.html')
  203. :index-title Title of index page. Defaults to name of file.
  204. :index-function Plugin function to use for generation of index.
  205. Defaults to 'org-publish-org-index', which
  206. generates a plain list of links to all files
  207. in the project.
  208. "
  209. :group 'org-publish
  210. :type 'alist)
  211. (defcustom org-publish-use-timestamps-flag t
  212. "When non-nil, use timestamp checking to publish only changed files.
  213. When nil, do no timestamp checking and always publish all
  214. files."
  215. :group 'org-publish
  216. :type 'boolean)
  217. (defcustom org-publish-timestamp-directory "~/.org-timestamps/"
  218. "Name of directory in which to store publishing timestamps."
  219. :group 'org-publish
  220. :type 'string)
  221. ;;;; Timestamp-related functions
  222. (defun org-publish-timestamp-filename (filename)
  223. "Return path to timestamp file for filename FILENAME."
  224. (while (string-match "~\\|/" filename)
  225. (setq filename (replace-match "_" nil t filename)))
  226. (concat org-publish-timestamp-directory filename ".timestamp"))
  227. (defun org-publish-needed-p (filename)
  228. "Check whether file should be published.
  229. If org-publish-use-timestamps-flag is set to nil, this function always
  230. returns t. Otherwise, check the timestamps folder to determine
  231. whether file should be published."
  232. (if org-publish-use-timestamps-flag
  233. (progn
  234. ;;
  235. ;; create folder if needed
  236. (if (not (file-exists-p org-publish-timestamp-directory))
  237. (make-directory org-publish-timestamp-directory)
  238. (if (not (file-directory-p org-publish-timestamp-directory))
  239. (error "org-publish-timestamp-directory must be a directory.")))
  240. ;;
  241. ;; check timestamp. ok if timestamp file doesn't exist
  242. (let* ((timestamp (org-publish-timestamp-filename filename))
  243. (rtn (file-newer-than-file-p filename timestamp)))
  244. (if rtn
  245. ;; handle new timestamps
  246. (if (not (file-exists-p timestamp))
  247. ;; create file
  248. (with-temp-buffer
  249. (write-file timestamp)
  250. (kill-buffer (current-buffer)))))
  251. rtn))
  252. t))
  253. (defun org-publish-update-timestamp (filename)
  254. "Update publishing timestamp for file FILENAME."
  255. (let ((timestamp (org-publish-timestamp-filename filename)))
  256. (set-file-times timestamp)))
  257. ;;;; Getting project information out of org-publish-project-alist
  258. (defun org-publish-meta-project-p (element)
  259. "Tell whether an ELEMENT of org-publish-project-alist is a metaproject."
  260. (plist-get (cdr element) :components))
  261. (defun org-publish-get-plists (&optional project-name)
  262. "Return a list of property lists for project PROJECT-NAME.
  263. When argument is not given, return all property lists for all projects."
  264. (let ((alist (if project-name
  265. (list (assoc project-name org-publish-project-alist))
  266. org-publish-project-alist))
  267. (project nil)
  268. (plists nil))
  269. (while (setq project (pop alist))
  270. (if (org-publish-meta-project-p project)
  271. ;; meta project
  272. (let* ((components (plist-get (cdr project) :components))
  273. (components-plists (mapcar 'org-publish-get-plists components)))
  274. (setq plists (append plists components-plists)))
  275. ;; normal project
  276. (let ((p (cdr project)))
  277. (setq p (plist-put p :project-name (car project)))
  278. (setq plists (append plists (list (cdr project)))))))
  279. ;;
  280. plists))
  281. (defun org-publish-get-base-files (plist &optional exclude-regexp)
  282. "Return a list of all files in project defined by PLIST.
  283. If EXCLUDE-REGEXP is set, this will be used to filter out
  284. matching filenames."
  285. (let* ((dir (file-name-as-directory (plist-get plist :base-directory)))
  286. (include-list (plist-get plist :include))
  287. (extension (or (plist-get plist :base-extension) "org"))
  288. (regexp (concat "^[^\\.].*\\.\\(" extension "\\)$"))
  289. (allfiles (directory-files dir t regexp)))
  290. ;;
  291. ;; exclude files
  292. (setq allfiles
  293. (if (not exclude-regexp)
  294. allfiles
  295. (delq nil
  296. (mapcar (lambda (x)
  297. (if (string-match exclude-regexp x) nil x))
  298. allfiles))))
  299. ;;
  300. ;; include extra files
  301. (let ((inc nil))
  302. (while (setq inc (pop include-list))
  303. (setq allfiles (cons (concat dir inc) allfiles))))
  304. allfiles))
  305. (defun org-publish-get-project-from-filename (filename)
  306. "Figure out which project a given FILENAME belongs to, if any.
  307. Filename should contain full path. Returns name of project, or
  308. nil if not found."
  309. (let ((found nil))
  310. (mapcar
  311. (lambda (plist)
  312. (let ((files (org-publish-get-base-files plist)))
  313. (if (member (expand-file-name filename) files)
  314. (setq found (plist-get plist :project-name)))))
  315. (org-publish-get-plists))
  316. found))
  317. (defun org-publish-get-plist-from-filename (filename)
  318. "Return publishing configuration plist for file FILENAME."
  319. (let ((found nil))
  320. (mapcar
  321. (lambda (plist)
  322. (let ((files (org-publish-get-base-files plist)))
  323. (if (member (expand-file-name filename) files)
  324. (setq found plist))))
  325. (org-publish-get-plists))
  326. found))
  327. ;;;; Pluggable publishing back-end functions
  328. (defun org-publish-org-to-html (plist filename)
  329. "Publish an org file to HTML.
  330. PLIST is the property list for the given project.
  331. FILENAME is the filename of the org file to be published."
  332. (require 'org)
  333. (let* ((arg (plist-get plist :headline-levels)))
  334. (progn
  335. (find-file filename)
  336. (org-export-as-html arg nil plist)
  337. ;; get rid of HTML buffer
  338. (kill-buffer (current-buffer)))))
  339. (defun org-publish-attachment (plist filename)
  340. "Publish a file with no transformation of any kind.
  341. PLIST is the property list for the given project.
  342. FILENAME is the filename of the file to be published."
  343. ;; make sure eshell/cp code is loaded
  344. (require 'eshell)
  345. (require 'esh-maint)
  346. (require 'em-unix)
  347. (let ((destination (file-name-as-directory (plist-get plist :publishing-directory))))
  348. (eshell/cp filename destination)))
  349. ;;;; Publishing files, sets of files, and indices
  350. (defun org-publish-file (filename)
  351. "Publish file FILENAME."
  352. (let* ((project-name (org-publish-get-project-from-filename filename))
  353. (plist (org-publish-get-plist-from-filename filename))
  354. (publishing-function (or (plist-get plist :publishing-function) 'org-publish-org-to-html)))
  355. (if (not project-name)
  356. (error (format "File %s is not part of any known project." filename)))
  357. (when (org-publish-needed-p filename)
  358. (funcall publishing-function plist filename)
  359. (org-publish-update-timestamp filename))))
  360. (defun org-publish-plist (plist)
  361. "Publish all files in set defined by PLIST.
  362. If :auto-index is set, publish the index too."
  363. (let* ((exclude-regexp (plist-get plist :exclude))
  364. (publishing-function (or (plist-get plist :publishing-function) 'org-publish-org-to-html))
  365. (buf (current-buffer))
  366. (index-p (plist-get plist :auto-index))
  367. (index-filename (or (plist-get plist :index-filename) "index.org"))
  368. (index-function (or (plist-get plist :index-function) 'org-publish-org-index))
  369. (f nil))
  370. ;;
  371. (if index-p
  372. (funcall index-function plist index-filename))
  373. (let ((files (org-publish-get-base-files plist exclude-regexp)))
  374. (while (setq f (pop files))
  375. ;; check timestamps
  376. (when (org-publish-needed-p f)
  377. (funcall publishing-function plist f)
  378. (org-publish-update-timestamp f))))
  379. ;; back to original buffer
  380. (switch-to-buffer buf)))
  381. (defun org-publish-org-index (plist &optional index-filename)
  382. "Create an index of pages in set defined by PLIST.
  383. Optionally set the filename of the index with INDEX-FILENAME;
  384. default is 'index.org'."
  385. (let* ((dir (file-name-as-directory (plist-get plist :base-directory)))
  386. (exclude-regexp (plist-get plist :exclude))
  387. (files (org-publish-get-base-files plist exclude-regexp))
  388. (index-filename (concat dir (or index-filename "index.org")))
  389. (index-buffer (find-buffer-visiting index-filename))
  390. (ifn (file-name-nondirectory index-filename))
  391. (f nil))
  392. ;;
  393. ;; if buffer is already open, kill it to prevent error message
  394. (if index-buffer
  395. (kill-buffer index-buffer))
  396. (with-temp-buffer
  397. (while (setq f (pop files))
  398. (let ((fn (file-name-nondirectory f)))
  399. (unless (string= fn ifn) ;; index shouldn't index itself
  400. (insert (concat " + [[file:" fn "]["
  401. (file-name-sans-extension fn)
  402. "]]\n")))))
  403. (write-file index-filename)
  404. (kill-buffer (current-buffer)))))
  405. ;(defun org-publish-meta-index (meta-plist &optional index-filename)
  406. ; "Create an index for a metaproject."
  407. ; (let* ((plists (
  408. ;;;; Interactive publishing functions
  409. ;;;###autoload
  410. (defun org-publish (project-name &optional force)
  411. "Publish the project PROJECT-NAME."
  412. (interactive "sProject name: \nP")
  413. (let ((org-publish-use-timestamps-flag (if force nil t))
  414. (plists (org-publish-get-plists project-name)))
  415. (mapcar 'org-publish-plist plists)))
  416. ;;;###autoload
  417. (defun org-publish-current-project (&optional force)
  418. "Publish the project associated with the current file.
  419. With prefix argument, force publishing all files in project."
  420. (interactive "P")
  421. (let* ((project-name (org-publish-get-project-from-filename (buffer-file-name)))
  422. (org-publish-use-timestamps-flag (if force nil t)))
  423. (if (not project-name)
  424. (error (format "File %s is not part of any known project." (buffer-file-name))))
  425. (org-publish project-name)))
  426. ;;;###autoload
  427. (defun org-publish-current-file (&optional force)
  428. "Publish the current file.
  429. With prefix argument, force publish the file."
  430. (interactive "P")
  431. (let ((org-publish-use-timestamps-flag
  432. (if force nil t)))
  433. (org-publish-file (buffer-file-name))))
  434. ;;;###autoload
  435. (defun org-publish-all (&optional force)
  436. "Publish all projects.
  437. With prefix argument, force publish all files."
  438. (interactive "P")
  439. (let ((org-publish-use-timestamps-flag
  440. (if force nil t))
  441. (plists (org-publish-get-plists)))
  442. (mapcar 'org-publish-plist plists)))
  443. (provide 'org-publish)
  444. ;;; org-publish.el ends here