config

Personal configuration.
git clone git://code.dwrz.net/config
Log | Files | Refs

ox-publish.el (52610B)


      1 ;;; ox-publish.el --- Publish Related Org Mode Files as a Website -*- lexical-binding: t; -*-
      2 ;; Copyright (C) 2006-2024 Free Software Foundation, Inc.
      3 
      4 ;; Author: David O'Toole <dto@gnu.org>
      5 ;; Keywords: hypermedia, outlines, text
      6 
      7 ;; This file is part of GNU Emacs.
      8 ;;
      9 ;; GNU Emacs is free software: you can redistribute it and/or modify
     10 ;; it under the terms of the GNU General Public License as published by
     11 ;; the Free Software Foundation, either version 3 of the License, or
     12 ;; (at your option) any later version.
     13 
     14 ;; GNU Emacs is distributed in the hope that it will be useful,
     15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
     16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     17 ;; GNU General Public License for more details.
     18 
     19 ;; You should have received a copy of the GNU General Public License
     20 ;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
     21 
     22 ;;; Commentary:
     23 
     24 ;; This program allow configurable publishing of related sets of
     25 ;; Org mode files as a complete website.
     26 ;;
     27 ;; ox-publish.el can do the following:
     28 ;;
     29 ;; + Publish all one's Org files to a given export backend
     30 ;; + Upload HTML, images, attachments and other files to a web server
     31 ;; + Exclude selected private pages from publishing
     32 ;; + Publish a clickable sitemap of pages
     33 ;; + Manage local timestamps for publishing only changed files
     34 ;; + Accept plugin functions to extend range of publishable content
     35 ;;
     36 ;; Documentation for publishing is in the manual.
     37 
     38 ;;; Code:
     39 
     40 (require 'org-macs)
     41 (org-assert-version)
     42 
     43 (require 'cl-lib)
     44 (require 'format-spec)
     45 (require 'ox)
     46 
     47 (declare-function org-at-heading-p "org" (&optional _))
     48 (declare-function org-back-to-heading "org" (&optional invisible-ok))
     49 (declare-function org-next-visible-heading "org" (arg))
     50 
     51 
     52 ;;; Variables
     53 
     54 ;; Here, so you find the variable right before it's used the first time:
     55 (defvar org-publish-cache nil
     56   "This will cache timestamps and titles for files in publishing projects.
     57 Blocks could hash sha1 values here.")
     58 
     59 (defvar org-publish-transient-cache nil
     60   "This will cache information during publishing process.")
     61 
     62 (defvar org-publish-after-publishing-hook nil
     63   "Hook run each time a file is published.
     64 Every function in this hook will be called with two arguments:
     65 the name of the original file and the name of the file
     66 produced.")
     67 
     68 (defgroup org-export-publish nil
     69   "Options for publishing a set of files."
     70   :tag "Org Publishing"
     71   :group 'org-export)
     72 
     73 (defcustom org-publish-project-alist nil
     74   "Association list to control publishing behavior.
     75 \\<org-mode-map>
     76 Each element of the alist is a publishing project.  The car of
     77 each element is a string, uniquely identifying the project.  The
     78 cdr of each element is in one of the following forms:
     79 
     80 1. A well-formed property list with an even number of elements,
     81    alternating keys and values, specifying parameters for the
     82    publishing process.
     83 
     84      (:property value :property value ... )
     85 
     86 2. A meta-project definition, specifying of a list of
     87    sub-projects:
     88 
     89      (:components (\"project-1\" \"project-2\" ...))
     90 
     91 When the CDR of an element of `org-publish-project-alist' is in
     92 this second form, the elements of the list after `:components'
     93 are taken to be components of the project, which group together
     94 files requiring different publishing options.  When you publish
     95 such a project with `\\[org-publish]', the components all publish.
     96 
     97 When a property is given a value in `org-publish-project-alist',
     98 its setting overrides the value of the corresponding user
     99 variable (if any) during publishing.  However, options set within
    100 a file override everything.
    101 
    102 Most properties are optional, but some should always be set:
    103 
    104   `:base-directory'
    105 
    106     Directory containing publishing source files.
    107 
    108   `:base-extension'
    109 
    110     Extension (without the dot!) of source files.  This can be
    111     a regular expression.  If not given, \"org\" will be used as
    112     default extension.  If it is `any', include all the files,
    113     even without extension.
    114 
    115   `:publishing-directory'
    116 
    117     Directory (possibly remote) where output files will be
    118     published.
    119 
    120 If `:recursive' is non-nil files in sub-directories of
    121 `:base-directory' are considered.
    122 
    123 The `:exclude' property may be used to prevent certain files from
    124 being published.  Its value may be a string or regexp matching
    125 file names you don't want to be published.
    126 
    127 The `:include' property may be used to include extra files.  Its
    128 value may be a list of filenames to include.  The filenames are
    129 considered relative to the base directory.
    130 
    131 When both `:include' and `:exclude' properties are given values,
    132 the exclusion step happens first.
    133 
    134 One special property controls which backend function to use for
    135 publishing files in the project.  This can be used to extend the
    136 set of file types publishable by `org-publish', as well as the
    137 set of output formats.
    138 
    139   `:publishing-function'
    140 
    141     Function to publish file.  Each backend may define its
    142     own (i.e. `org-latex-publish-to-pdf',
    143     `org-html-publish-to-html').  May be a list of functions, in
    144     which case each function in the list is invoked in turn.
    145 
    146 Another property allows you to insert code that prepares
    147 a project for publishing.  For example, you could call GNU Make
    148 on a certain makefile, to ensure published files are built up to
    149 date.
    150 
    151   `:preparation-function'
    152 
    153     Function to be called before publishing this project.  This
    154     may also be a list of functions.  Preparation functions are
    155     called with the project properties list as their sole
    156     argument.
    157 
    158   `:completion-function'
    159 
    160     Function to be called after publishing this project.  This
    161     may also be a list of functions.  Completion functions are
    162     called with the project properties list as their sole
    163     argument.
    164 
    165 Some properties control details of the Org publishing process,
    166 and are equivalent to the corresponding user variables listed in
    167 the right column.  Backend specific properties may also be
    168 included.  See the backend documentation for more information.
    169 
    170   :author                   variable `user-full-name'
    171   :creator                  `org-export-creator-string'
    172   :email                    `user-mail-address'
    173   :exclude-tags             `org-export-exclude-tags'
    174   :headline-levels          `org-export-headline-levels'
    175   :language                 `org-export-default-language'
    176   :preserve-breaks          `org-export-preserve-breaks'
    177   :section-numbers          `org-export-with-section-numbers'
    178   :select-tags              `org-export-select-tags'
    179   :time-stamp-file          `org-export-timestamp-file'
    180   :with-archived-trees      `org-export-with-archived-trees'
    181   :with-author              `org-export-with-author'
    182   :with-creator             `org-export-with-creator'
    183   :with-date                `org-export-with-date'
    184   :with-drawers             `org-export-with-drawers'
    185   :with-email               `org-export-with-email'
    186   :with-emphasize           `org-export-with-emphasize'
    187   :with-entities            `org-export-with-entities'
    188   :with-fixed-width         `org-export-with-fixed-width'
    189   :with-footnotes           `org-export-with-footnotes'
    190   :with-inlinetasks         `org-export-with-inlinetasks'
    191   :with-latex               `org-export-with-latex'
    192   :with-planning            `org-export-with-planning'
    193   :with-priority            `org-export-with-priority'
    194   :with-properties          `org-export-with-properties'
    195   :with-smart-quotes        `org-export-with-smart-quotes'
    196   :with-special-strings     `org-export-with-special-strings'
    197   :with-statistics-cookies' `org-export-with-statistics-cookies'
    198   :with-sub-superscript     `org-export-with-sub-superscripts'
    199   :with-toc                 `org-export-with-toc'
    200   :with-tables              `org-export-with-tables'
    201   :with-tags                `org-export-with-tags'
    202   :with-tasks               `org-export-with-tasks'
    203   :with-timestamps          `org-export-with-timestamps'
    204   :with-title               `org-export-with-title'
    205   :with-todo-keywords       `org-export-with-todo-keywords'
    206 
    207 The following properties may be used to control publishing of
    208 a site-map of files or summary page for a given project.
    209 
    210   `:auto-sitemap'
    211 
    212     Whether to publish a site-map during
    213     `org-publish-current-project' or `org-publish-all'.
    214 
    215   `:sitemap-filename'
    216 
    217     Filename for output of site-map.  Defaults to \"sitemap.org\".
    218 
    219   `:sitemap-title'
    220 
    221     Title of site-map page.  Defaults to name of file.
    222 
    223   `:sitemap-style'
    224 
    225     Can be `list' (site-map is just an itemized list of the
    226     titles of the files involved) or `tree' (the directory
    227     structure of the source files is reflected in the site-map).
    228     Defaults to `tree'.
    229 
    230   `:sitemap-format-entry'
    231 
    232     Plugin function used to format entries in the site-map.  It
    233     is called with three arguments: the file or directory name
    234     relative to base directory, the site map style and the
    235     current project.  It has to return a string.
    236 
    237     Defaults to `org-publish-sitemap-default-entry', which turns
    238     file names into links and use document titles as
    239     descriptions.  For specific formatting needs, one can use
    240     `org-publish-find-date', `org-publish-find-title' and
    241     `org-publish-find-property', to retrieve additional
    242     information about published documents.
    243 
    244   `:sitemap-function'
    245 
    246     Plugin function to use for generation of site-map.  It is
    247     called with two arguments: the title of the site-map, as
    248     a string, and a representation of the files involved in the
    249     project, as returned by `org-list-to-lisp'.  The latter can
    250     further be transformed using `org-list-to-generic',
    251     `org-list-to-subtree' and alike.  It has to return a string.
    252 
    253     Defaults to `org-publish-sitemap-default', which generates
    254     a plain list of links to all files in the project.
    255 
    256 If you create a site-map file, adjust the sorting like this:
    257 
    258   `:sitemap-sort-folders'
    259 
    260     Where folders should appear in the site-map.  Set this to
    261     `first' or `last' to display folders first or last,
    262     respectively.  When set to `ignore' (default), folders are
    263     ignored altogether.  Any other value will mix files and
    264     folders.  This variable has no effect when site-map style is
    265     `tree'.
    266 
    267   `:sitemap-sort-files'
    268 
    269     The site map is normally sorted alphabetically.  You can
    270     change this behavior setting this to `anti-chronologically',
    271     `chronologically', or nil.
    272 
    273   `:sitemap-ignore-case'
    274 
    275     Should sorting be case-sensitive?  Default nil.
    276 
    277 The following property control the creation of a concept index.
    278 
    279   `:makeindex'
    280 
    281     Create a concept index.  The file containing the index has to
    282     be called \"theindex.org\".  If it doesn't exist in the
    283     project, it will be generated.  Contents of the index are
    284     stored in the file \"theindex.inc\", which can be included in
    285     \"theindex.org\".
    286 
    287 Other properties affecting publication.
    288 
    289   `:body-only'
    290 
    291     Set this to t to publish only the body of the documents."
    292   :group 'org-export-publish
    293   :type 'alist)
    294 
    295 (defcustom org-publish-use-timestamps-flag t
    296   "Non-nil means use timestamp checking to publish only changed files.
    297 When nil, do no timestamp checking and always publish all files."
    298   :group 'org-export-publish
    299   :type 'boolean)
    300 
    301 (defcustom org-publish-timestamp-directory
    302   (convert-standard-filename "~/.org-timestamps/")
    303   "Name of directory in which to store publishing timestamps."
    304   :group 'org-export-publish
    305   :type 'directory)
    306 
    307 (defcustom org-publish-list-skipped-files t
    308   "Non-nil means show message about files *not* published."
    309   :group 'org-export-publish
    310   :type 'boolean)
    311 
    312 (defcustom org-publish-sitemap-sort-files 'alphabetically
    313   "Method to sort files in site-maps.
    314 Possible values are `alphabetically', `chronologically',
    315 `anti-chronologically' and nil.
    316 
    317 If `alphabetically', files will be sorted alphabetically.  If
    318 `chronologically', files will be sorted with older modification
    319 time first.  If `anti-chronologically', files will be sorted with
    320 newer modification time first.  nil won't sort files.
    321 
    322 You can overwrite this default per project in your
    323 `org-publish-project-alist', using `:sitemap-sort-files'."
    324   :group 'org-export-publish
    325   :type 'symbol)
    326 
    327 (defcustom org-publish-sitemap-sort-folders 'ignore
    328   "A symbol, denoting if folders are sorted first in site-maps.
    329 
    330 Possible values are `first', `last', `ignore' and nil.
    331 If `first', folders will be sorted before files.
    332 If `last', folders are sorted to the end after the files.
    333 If `ignore', folders do not appear in the site-map.
    334 Any other value will mix files and folders.
    335 
    336 You can overwrite this default per project in your
    337 `org-publish-project-alist', using `:sitemap-sort-folders'.
    338 
    339 This variable is ignored when site-map style is `tree'."
    340   :group 'org-export-publish
    341   :type '(choice
    342 	  (const :tag "Folders before files" first)
    343 	  (const :tag "Folders after files" last)
    344 	  (const :tag "No folder in site-map" ignore)
    345 	  (const :tag "Mix folders and files" nil))
    346   :version "26.1"
    347   :package-version '(Org . "9.1")
    348   :safe #'symbolp)
    349 
    350 (defcustom org-publish-sitemap-sort-ignore-case nil
    351   "Non-nil when site-map sorting should ignore case.
    352 
    353 You can overwrite this default per project in your
    354 `org-publish-project-alist', using `:sitemap-ignore-case'."
    355   :group 'org-export-publish
    356   :type 'boolean)
    357 
    358 
    359 
    360 ;;; Timestamp-related functions
    361 
    362 (defun org-publish-timestamp-filename (filename &optional pub-dir pub-func)
    363   "Return path to timestamp file for filename FILENAME.
    364 The timestamp file name is constructed using FILENAME, publishing
    365 directory PUB-DIR, and PUB-FUNC publishing function."
    366   (setq filename (concat filename "::" (or pub-dir "") "::"
    367 			 (format "%s" (or pub-func ""))))
    368   (concat "X" (if (fboundp 'sha1) (sha1 filename) (md5 filename))))
    369 
    370 (defun org-publish-needed-p
    371     (filename &optional pub-dir pub-func _true-pub-dir base-dir)
    372   "Non-nil if FILENAME should be published in PUB-DIR using PUB-FUNC.
    373 TRUE-PUB-DIR is where the file will truly end up.  Currently we
    374 are not using this - maybe it can eventually be used to check if
    375 the file is present at the target location, and how old it is.
    376 Right now we cannot do this, because we do not know under what
    377 file name the file will be stored - the publishing function can
    378 still decide about that independently."
    379   (let ((rtn (if (not org-publish-use-timestamps-flag) t
    380 	       (org-publish-cache-file-needs-publishing
    381 		filename pub-dir pub-func base-dir))))
    382     (if rtn (message "Publishing file %s using `%s'" filename pub-func)
    383       (when org-publish-list-skipped-files
    384 	(message "Skipping unmodified file %s" filename)))
    385     rtn))
    386 
    387 (defun org-publish-update-timestamp
    388     (filename &optional pub-dir pub-func _base-dir)
    389   "Update publishing timestamp for file FILENAME.
    390 If there is no timestamp, create one."
    391   (let ((key (org-publish-timestamp-filename filename pub-dir pub-func))
    392 	(stamp (org-publish-cache-mtime-of-src filename)))
    393     (org-publish-cache-set key stamp)))
    394 
    395 (defun org-publish-remove-all-timestamps ()
    396   "Remove all files in the timestamp directory."
    397   (let ((dir org-publish-timestamp-directory))
    398     (when (and (file-exists-p dir) (file-directory-p dir))
    399       (mapc #'delete-file (directory-files dir 'full "[^.]\\'"))
    400       (org-publish-reset-cache))))
    401 
    402 
    403 
    404 ;;; Getting project information out of `org-publish-project-alist'
    405 
    406 (defun org-publish-property (property project &optional default)
    407   "Return value PROPERTY, as a symbol, in PROJECT.
    408 DEFAULT is returned when PROPERTY is not actually set in PROJECT
    409 definition."
    410   (let ((properties (cdr project)))
    411     (if (plist-member properties property)
    412 	(plist-get properties property)
    413       default)))
    414 
    415 (defun org-publish--expand-file-name (file project)
    416   "Return full file name for FILE in PROJECT.
    417 When FILE is a relative file name, it is expanded according to
    418 project base directory."
    419   (if (file-name-absolute-p file) file
    420     (expand-file-name file (org-publish-property :base-directory project))))
    421 
    422 (defun org-publish-expand-projects (projects-alist)
    423   "Expand projects in PROJECTS-ALIST.
    424 This splices all the components into the list."
    425   (let ((rest projects-alist) rtn p components)
    426     (while (setq p (pop rest))
    427       (if (setq components (plist-get (cdr p) :components))
    428 	  (setq rest (append
    429 		      (mapcar
    430 		       (lambda (x)
    431 			 (or (assoc x org-publish-project-alist)
    432 			     (user-error "Unknown component %S in project %S"
    433 					 x (car p))))
    434 		       components)
    435 		      rest))
    436 	(push p rtn)))
    437     (nreverse (delete-dups (delq nil rtn)))))
    438 
    439 (defun org-publish-get-base-files (project)
    440   "Return a list of all files in PROJECT."
    441   (let* ((base-dir (file-name-as-directory
    442 		    (org-publish-property :base-directory project)))
    443 	 (extension (or (org-publish-property :base-extension project) "org"))
    444 	 (match (if (eq extension 'any) ""
    445 		  (format "^[^\\.].*\\.\\(%s\\)$" extension)))
    446 	 (base-files
    447 	  (cond ((not (file-exists-p base-dir)) nil)
    448 		((not (org-publish-property :recursive project))
    449 		 (cl-remove-if #'file-directory-p
    450 			       (directory-files base-dir t match t)))
    451 		(t
    452 		 ;; Find all files recursively.  Unlike to
    453 		 ;; `directory-files-recursively', we follow symlinks
    454 		 ;; to other directories.
    455 		 (letrec ((files nil)
    456 			  (walk-tree
    457 			   (lambda (dir depth)
    458 			     (when (> depth 100)
    459 			       (error "Apparent cycle of symbolic links for %S"
    460 				      base-dir))
    461 			     (dolist (f (file-name-all-completions "" dir))
    462 			       (pcase f
    463 				 ((or "./" "../") nil)
    464 				 ((pred directory-name-p)
    465 				  (funcall walk-tree
    466 					   (expand-file-name f dir)
    467 					   (1+ depth)))
    468 				 ((pred (string-match match))
    469 				  (push (expand-file-name f dir) files))
    470 				 (_ nil)))
    471 			     files)))
    472 		   (funcall walk-tree base-dir 0))))))
    473     (org-uniquify
    474      (append
    475       ;; Files from BASE-DIR.  Apply exclusion filter before adding
    476       ;; included files.
    477       (let ((exclude-regexp (org-publish-property :exclude project)))
    478 	(if exclude-regexp
    479 	    (cl-remove-if
    480 	     (lambda (f)
    481 	       ;; Match against relative names, yet BASE-DIR file
    482 	       ;; names are absolute.
    483 	       (string-match exclude-regexp
    484 			     (file-relative-name f base-dir)))
    485 	     base-files)
    486 	  base-files))
    487       ;; Sitemap file.
    488       (and (org-publish-property :auto-sitemap project)
    489 	   (list (expand-file-name
    490 		  (or (org-publish-property :sitemap-filename project)
    491 		      "sitemap.org")
    492 		  base-dir)))
    493       ;; Included files.
    494       (mapcar (lambda (f) (expand-file-name f base-dir))
    495 	      (org-publish-property :include project))))))
    496 
    497 (defun org-publish-get-project-from-filename (filename &optional up)
    498   "Return a project that FILENAME belongs to.
    499 When UP is non-nil, return a meta-project (i.e., with a :components part)
    500 publishing FILENAME."
    501   (let* ((filename (expand-file-name filename))
    502 	 (project
    503 	  (cl-some
    504 	   (lambda (p)
    505 	     ;; Ignore meta-projects.
    506 	     (unless (org-publish-property :components p)
    507 	       (let ((base (expand-file-name
    508 			    (org-publish-property :base-directory p))))
    509 		 (cond
    510 		  ;; Check if FILENAME is explicitly included in one
    511 		  ;; project.
    512 		  ((cl-some (lambda (f) (file-equal-p f filename))
    513 			    (mapcar (lambda (f) (expand-file-name f base))
    514 				    (org-publish-property :include p)))
    515 		   p)
    516 		  ;; Exclude file names matching :exclude property.
    517 		  ((let ((exclude-re (org-publish-property :exclude p)))
    518 		     (and exclude-re
    519 			  (string-match-p exclude-re
    520 					  (file-relative-name filename base))))
    521 		   nil)
    522 		  ;; Check :extension.  Handle special `any'
    523 		  ;; extension.
    524 		  ((let ((extension (org-publish-property :base-extension p)))
    525 		     (not (or (eq extension 'any)
    526 			      (string= (or extension "org")
    527 				       (file-name-extension filename)))))
    528 		   nil)
    529 		  ;; Check if FILENAME belong to project's base
    530 		  ;; directory, or some of its sub-directories
    531 		  ;; if :recursive in non-nil.
    532 		  ((member filename (org-publish-get-base-files p)) p)
    533 		  (t nil)))))
    534 	   org-publish-project-alist)))
    535     (cond
    536      ((not project) nil)
    537      ((not up) project)
    538      ;; When optional argument UP is non-nil, return the top-most
    539      ;; meta-project effectively publishing FILENAME.
    540      (t
    541       (letrec ((find-parent-project
    542 		(lambda (project)
    543 		  (or (cl-some
    544 		       (lambda (p)
    545 			 (and (member (car project)
    546 				      (org-publish-property :components p))
    547 			      (funcall find-parent-project p)))
    548 		       org-publish-project-alist)
    549 		      project))))
    550 	(funcall find-parent-project project))))))
    551 
    552 
    553 
    554 ;;; Tools for publishing functions in backends
    555 
    556 (defun org-publish-org-to (backend filename extension plist &optional pub-dir)
    557   "Publish an Org file to a specified backend.
    558 
    559 BACKEND is a symbol representing the backend used for
    560 transcoding.  FILENAME is the filename of the Org file to be
    561 published.  EXTENSION is the extension used for the output
    562 string, with the leading dot.  PLIST is the property list for the
    563 given project.
    564 
    565 Optional argument PUB-DIR, when non-nil is the publishing
    566 directory.
    567 
    568 Return output file name."
    569   (unless (or (not pub-dir) (file-exists-p pub-dir)) (make-directory pub-dir t))
    570   ;; Check if a buffer visiting FILENAME is already open.
    571   (let* ((org-inhibit-startup t))
    572     (org-with-file-buffer filename
    573       (let ((output (org-export-output-file-name extension nil pub-dir)))
    574 	(org-export-to-file backend output
    575 	  nil nil nil (plist-get plist :body-only)
    576 	  ;; Add `org-publish--store-crossrefs' and
    577 	  ;; `org-publish-collect-index' to final output filters.
    578 	  ;; The latter isn't dependent on `:makeindex', since we
    579 	  ;; want to keep it up-to-date in cache anyway.
    580 	  (org-combine-plists
    581 	   plist
    582 	   `(:crossrefs
    583 	     ,(org-publish-cache-get-file-property
    584 	       ;; Normalize file names in cache.
    585 	       (file-truename filename) :crossrefs nil t)
    586 	     :filter-final-output
    587 	     (org-publish--store-crossrefs
    588 	      org-publish-collect-index
    589 	      ,@(plist-get plist :filter-final-output)))))))))
    590 
    591 (defun org-publish-attachment (_plist filename pub-dir)
    592   "Publish a file with no transformation of any kind.
    593 
    594 FILENAME is the filename of the Org file to be published.  PLIST
    595 is the property list for the given project.  PUB-DIR is the
    596 publishing directory.
    597 
    598 Return output file name."
    599   (unless (file-directory-p pub-dir)
    600     (make-directory pub-dir t))
    601   (let ((output (expand-file-name (file-name-nondirectory filename) pub-dir)))
    602     (unless (file-equal-p (expand-file-name (file-name-directory filename))
    603 			  (file-name-as-directory (expand-file-name pub-dir)))
    604       (copy-file filename output t))
    605     ;; Return file name.
    606     output))
    607 
    608 
    609 
    610 ;;; Publishing files, sets of files
    611 
    612 (defun org-publish-file (filename &optional project no-cache)
    613   "Publish file FILENAME from PROJECT.
    614 If NO-CACHE is not nil, do not initialize `org-publish-cache'.
    615 This is needed, since this function is used to publish single
    616 files, when entire projects are published (see
    617 `org-publish-projects')."
    618   (let* ((project
    619 	  (or project
    620 	      (org-publish-get-project-from-filename filename)
    621 	      (user-error "File %S is not part of any known project"
    622 			  (abbreviate-file-name filename))))
    623 	 (project-plist (cdr project))
    624 	 (publishing-function
    625 	  (pcase (org-publish-property :publishing-function project
    626                                        'org-html-publish-to-html)
    627 	    (`nil (user-error "No publishing function chosen"))
    628 	    ((and f (pred listp)) f)
    629 	    (f (list f))))
    630 	 (base-dir
    631 	  (file-name-as-directory
    632 	   (or (org-publish-property :base-directory project)
    633 	       (user-error "Project %S does not have :base-directory defined"
    634 			   (car project)))))
    635 	 (pub-base-dir
    636 	  (file-name-as-directory
    637 	   (or (org-publish-property :publishing-directory project)
    638 	       (user-error
    639 		"Project %S does not have :publishing-directory defined"
    640 		(car project)))))
    641 	 (pub-dir
    642 	  (file-name-directory
    643 	   (expand-file-name (file-relative-name filename base-dir)
    644 			     pub-base-dir))))
    645 
    646     (unless no-cache (org-publish-initialize-cache (car project)))
    647 
    648     ;; Allow chain of publishing functions.
    649     (dolist (f publishing-function)
    650       (when (org-publish-needed-p filename pub-base-dir f pub-dir base-dir)
    651 	(let ((output (funcall f project-plist filename pub-dir)))
    652 	  (org-publish-update-timestamp filename pub-base-dir f base-dir)
    653 	  (run-hook-with-args 'org-publish-after-publishing-hook
    654 			      filename
    655 			      output))))
    656     ;; Make sure to write cache to file after successfully publishing
    657     ;; a file, so as to minimize impact of a publishing failure.
    658     (org-publish-write-cache-file)))
    659 
    660 (defun org-publish-projects (projects)
    661   "Publish all files belonging to the PROJECTS alist.
    662 If `:auto-sitemap' is set, publish the sitemap too.  If
    663 `:makeindex' is set, also produce a file \"theindex.org\"."
    664   (dolist (project (org-publish-expand-projects projects))
    665     (let ((plist (cdr project)))
    666       (let ((fun (org-publish-property :preparation-function project)))
    667 	(cond
    668 	 ((functionp fun) (funcall fun plist))
    669 	 ((consp fun) (dolist (f fun) (funcall f plist)))))
    670       ;; Each project uses its own cache file.
    671       (org-publish-initialize-cache (car project))
    672       (when (org-publish-property :auto-sitemap project)
    673 	(let ((sitemap-filename
    674 	       (or (org-publish-property :sitemap-filename project)
    675 		   "sitemap.org")))
    676 	  (org-publish-sitemap project sitemap-filename)))
    677       ;; Publish all files from PROJECT except "theindex.org".  Its
    678       ;; publishing will be deferred until "theindex.inc" is
    679       ;; populated.
    680       (let ((theindex
    681 	     (expand-file-name "theindex.org"
    682 			       (org-publish-property :base-directory project))))
    683 	(dolist (file (org-publish-get-base-files project))
    684 	  (unless (file-equal-p file theindex)
    685 	    (org-publish-file file project t)))
    686 	;; Populate "theindex.inc", if needed, and publish
    687 	;; "theindex.org".
    688 	(when (org-publish-property :makeindex project)
    689 	  (org-publish-index-generate-theindex
    690 	   project (org-publish-property :base-directory project))
    691 	  (org-publish-file theindex project t)))
    692       (let ((fun (org-publish-property :completion-function project)))
    693 	(cond
    694 	 ((functionp fun) (funcall fun plist))
    695 	 ((consp fun) (dolist (f fun) (funcall f plist))))))
    696     (org-publish-write-cache-file)))
    697 
    698 
    699 ;;; Site map generation
    700 
    701 (defun org-publish--sitemap-files-to-lisp (files project style format-entry)
    702   "Represent FILES as a parsed plain list.
    703 FILES is the list of files in the site map.  PROJECT is the
    704 current project.  STYLE determines is either `list' or `tree'.
    705 FORMAT-ENTRY is a function called on each file which should
    706 return a string.  Return value is a list as returned by
    707 `org-list-to-lisp'."
    708   (let ((root (expand-file-name
    709 	       (file-name-as-directory
    710 		(org-publish-property :base-directory project)))))
    711     (pcase style
    712       (`list
    713        (cons 'unordered
    714 	     (mapcar
    715 	      (lambda (f)
    716 		(list (funcall format-entry
    717 			       (file-relative-name f root)
    718 			       style
    719 			       project)))
    720 	      files)))
    721       (`tree
    722        (letrec ((files-only (cl-remove-if #'directory-name-p files))
    723 		(directories (cl-remove-if-not #'directory-name-p files))
    724 		(subtree-to-list
    725 		 (lambda (dir)
    726 		   (cons 'unordered
    727 			 (nconc
    728 			  ;; Files in DIR.
    729 			  (mapcar
    730 			   (lambda (f)
    731 			     (list (funcall format-entry
    732 					    (file-relative-name f root)
    733 					    style
    734 					    project)))
    735 			   (cl-remove-if-not
    736 			    (lambda (f) (string= dir (file-name-directory f)))
    737 			    files-only))
    738 			  ;; Direct sub-directories.
    739 			  (mapcar
    740 			   (lambda (sub)
    741 			     (list (funcall format-entry
    742 					    (file-relative-name sub root)
    743 					    style
    744 					    project)
    745 				   (funcall subtree-to-list sub)))
    746 			   (cl-remove-if-not
    747 			    (lambda (f)
    748 			      (string=
    749 			       dir
    750 			       ;; Parent directory.
    751 			       (file-name-directory (directory-file-name f))))
    752 			    directories)))))))
    753 	 (funcall subtree-to-list root)))
    754       (_ (user-error "Unknown site-map style: `%s'" style)))))
    755 
    756 (defun org-publish-sitemap (project &optional sitemap-filename)
    757   "Create a sitemap of pages in set defined by PROJECT.
    758 Optionally set the filename of the sitemap with SITEMAP-FILENAME.
    759 Default for SITEMAP-FILENAME is `sitemap.org'."
    760   (let* ((root (expand-file-name
    761 		(file-name-as-directory
    762 		 (org-publish-property :base-directory project))))
    763 	 (sitemap-filename (expand-file-name (or sitemap-filename "sitemap.org")
    764 					     root))
    765 	 (title (or (org-publish-property :sitemap-title project)
    766 		    (concat "Sitemap for project " (car project))))
    767 	 (style (or (org-publish-property :sitemap-style project)
    768 		    'tree))
    769 	 (sitemap-builder (or (org-publish-property :sitemap-function project)
    770 			      #'org-publish-sitemap-default))
    771 	 (format-entry (or (org-publish-property :sitemap-format-entry project)
    772 			   #'org-publish-sitemap-default-entry))
    773 	 (sort-folders
    774 	  (org-publish-property :sitemap-sort-folders project
    775 				org-publish-sitemap-sort-folders))
    776 	 (sort-files
    777 	  (org-publish-property :sitemap-sort-files project
    778 				org-publish-sitemap-sort-files))
    779 	 (ignore-case
    780 	  (org-publish-property :sitemap-ignore-case project
    781 				org-publish-sitemap-sort-ignore-case))
    782 	 (org-file-p (lambda (f) (equal "org" (file-name-extension f))))
    783 	 (sort-predicate
    784 	  (lambda (a b)
    785 	    (let ((retval t))
    786 	      ;; First we sort files:
    787 	      (pcase sort-files
    788 		(`alphabetically
    789 		 (let ((A (if (funcall org-file-p a)
    790 			      (concat (file-name-directory a)
    791 				      (org-publish-find-title a project))
    792 			    a))
    793 		       (B (if (funcall org-file-p b)
    794 			      (concat (file-name-directory b)
    795 				      (org-publish-find-title b project))
    796 			    b)))
    797 		   (setq retval (org-string<= A B nil ignore-case))))
    798 		((or `anti-chronologically `chronologically)
    799 		 (let* ((adate (org-publish-find-date a project))
    800 			(bdate (org-publish-find-date b project)))
    801 		   (setq retval
    802 			 (not (if (eq sort-files 'chronologically)
    803 				(time-less-p bdate adate)
    804 			      (time-less-p adate bdate))))))
    805 		(`nil nil)
    806 		(_ (user-error "Invalid sort value %s" sort-files)))
    807 	      ;; Directory-wise wins:
    808 	      (when (memq sort-folders '(first last))
    809 		;; a is directory, b not:
    810 		(cond
    811 		 ((and (file-directory-p a) (not (file-directory-p b)))
    812 		  (setq retval (eq sort-folders 'first)))
    813 		 ;; a is not a directory, but b is:
    814 		 ((and (not (file-directory-p a)) (file-directory-p b))
    815 		  (setq retval (eq sort-folders 'last)))))
    816 	      retval))))
    817     (message "Generating sitemap for %s" title)
    818     (with-temp-file sitemap-filename
    819       (insert
    820        (let ((files (remove sitemap-filename
    821 			    (org-publish-get-base-files project))))
    822 	 ;; Add directories, if applicable.
    823 	 (unless (and (eq style 'list) (eq sort-folders 'ignore))
    824 	   (setq files
    825 		 (nconc (remove root (org-uniquify
    826 				      (mapcar #'file-name-directory files)))
    827 			files)))
    828 	 ;; Eventually sort all entries.
    829 	 (when (or sort-files (not (memq sort-folders 'ignore)))
    830 	   (setq files (sort files sort-predicate)))
    831 	 (funcall sitemap-builder
    832 		  title
    833 		  (org-publish--sitemap-files-to-lisp
    834 		   files project style format-entry)))))))
    835 
    836 (defun org-publish-find-property (file property project &optional backend)
    837   "Find the PROPERTY of FILE in project.
    838 
    839 PROPERTY is a keyword referring to an export option, as defined
    840 in `org-export-options-alist' or in export backends.  In the
    841 latter case, optional argument BACKEND has to be set to the
    842 backend where the option is defined, e.g.,
    843 
    844   (org-publish-find-property file :subtitle \\='latex)
    845 
    846 Return value may be a string or a list, depending on the type of
    847 PROPERTY, i.e. \"behavior\" parameter from `org-export-options-alist'."
    848   (let ((file (org-publish--expand-file-name file project)))
    849     (when (and (file-readable-p file) (not (directory-name-p file)))
    850       (let* ((org-inhibit-startup t))
    851 	(plist-get (org-with-file-buffer file
    852 		     (if (not org-file-buffer-created) (org-export-get-environment backend)
    853 		       ;; Protect local variables in open buffers.
    854 		       (org-export-with-buffer-copy
    855 		        (org-export-get-environment backend))))
    856 		   property)))))
    857 
    858 (defun org-publish-find-title (file project)
    859   "Find the title of FILE in PROJECT."
    860   (let ((file (org-publish--expand-file-name file project)))
    861     (or (org-publish-cache-get-file-property file :title nil t)
    862 	(let* ((parsed-title (org-publish-find-property file :title project))
    863 	       (title
    864 		(if parsed-title
    865 		    ;; Remove property so that the return value is
    866 		    ;; cache-able (i.e., it can be `read' back).
    867 		    (org-no-properties
    868 		     (org-element-interpret-data parsed-title))
    869 		  (file-name-nondirectory (file-name-sans-extension file)))))
    870 	  (org-publish-cache-set-file-property file :title title nil 'transient)))))
    871 
    872 (defun org-publish-find-date (file project)
    873   "Find the date of FILE in PROJECT.
    874 This function assumes FILE is either a directory or an Org file.
    875 If FILE is an Org file and provides a DATE keyword use it.  In
    876 any other case use the file system's modification time.  Return
    877 time in `current-time' format."
    878   (let ((file (org-publish--expand-file-name file project)))
    879     (or (org-publish-cache-get-file-property file :date nil t)
    880 	(org-publish-cache-set-file-property
    881 	 file :date
    882 	 (if (file-directory-p file)
    883 	     (file-attribute-modification-time (file-attributes file))
    884 	   (let ((date (org-publish-find-property file :date project)))
    885 	     ;; DATE is a secondary string.  If it contains
    886 	     ;; a timestamp, convert it to internal format.
    887 	     ;; Otherwise, use FILE modification time.
    888 	     (cond ((let ((ts (and (consp date) (assq 'timestamp date))))
    889 		      (and ts
    890 			   (let ((value (org-element-interpret-data ts)))
    891 			     (and (org-string-nw-p value)
    892 				  (org-time-string-to-time value))))))
    893 		   ((file-exists-p file)
    894 		    (file-attribute-modification-time (file-attributes file)))
    895 		   (t (error "No such file: \"%s\"" file)))))
    896          nil 'transient))))
    897 
    898 (defun org-publish-sitemap-default-entry (entry style project)
    899   "Default format for site map ENTRY, as a string.
    900 ENTRY is a file name.  STYLE is the style of the sitemap.
    901 PROJECT is the current project."
    902   (cond ((not (directory-name-p entry))
    903 	 (format "[[file:%s][%s]]"
    904 		 entry
    905 		 (org-publish-find-title entry project)))
    906 	((eq style 'tree)
    907 	 ;; Return only last subdir.
    908 	 (file-name-nondirectory (directory-file-name entry)))
    909 	(t entry)))
    910 
    911 (defun org-publish-sitemap-default (title list)
    912   "Default site map, as a string.
    913 TITLE is the title of the site map.  LIST is an internal
    914 representation for the files to include, as returned by
    915 `org-list-to-lisp'.  PROJECT is the current project."
    916   (concat "#+TITLE: " title "\n\n"
    917 	  (org-list-to-org list)))
    918 
    919 
    920 ;;; Interactive publishing functions
    921 
    922 ;;;###autoload
    923 (defalias 'org-publish-project 'org-publish)
    924 
    925 ;;;###autoload
    926 (defun org-publish (project &optional force async)
    927   "Publish PROJECT.
    928 
    929 PROJECT is either a project name, as a string, or a project
    930 alist (see `org-publish-project-alist' variable).
    931 
    932 When optional argument FORCE is non-nil, force publishing all
    933 files in PROJECT.  With a non-nil optional argument ASYNC,
    934 publishing will be done asynchronously, in another process."
    935   (interactive
    936    (list (assoc (completing-read "Publish project: "
    937 				 org-publish-project-alist nil t)
    938 		org-publish-project-alist)
    939 	 current-prefix-arg))
    940   (let ((project (if (not (stringp project)) project
    941 		   ;; If this function is called in batch mode,
    942 		   ;; PROJECT is still a string here.
    943 		   (assoc project org-publish-project-alist))))
    944     (cond
    945      ((not project))
    946      (async
    947       (org-export-async-start (lambda (_) nil)
    948 	`(let ((org-publish-use-timestamps-flag
    949 		,(and (not force) org-publish-use-timestamps-flag)))
    950 	   ;; Expand components right now as external process may not
    951 	   ;; be aware of complete `org-publish-project-alist'.
    952 	   (org-publish-projects
    953 	    ',(org-publish-expand-projects (list project))))))
    954      (t (save-window-excursion
    955 	  (let ((org-publish-use-timestamps-flag
    956 		 (and (not force) org-publish-use-timestamps-flag)))
    957 	    (org-publish-projects (list project))))))))
    958 
    959 ;;;###autoload
    960 (defun org-publish-all (&optional force async)
    961   "Publish all projects.
    962 With prefix argument FORCE, remove all files in the timestamp
    963 directory and force publishing all projects.  With a non-nil
    964 optional argument ASYNC, publishing will be done asynchronously,
    965 in another process."
    966   (interactive "P")
    967   (if async
    968       (org-export-async-start (lambda (_) nil)
    969 	`(progn
    970 	   (when ',force (org-publish-remove-all-timestamps))
    971 	   (let ((org-publish-use-timestamps-flag
    972 		  (if ',force nil ,org-publish-use-timestamps-flag)))
    973 	     (org-publish-projects ',org-publish-project-alist))))
    974     (when force (org-publish-remove-all-timestamps))
    975     (save-window-excursion
    976       (let ((org-publish-use-timestamps-flag
    977 	     (if force nil org-publish-use-timestamps-flag)))
    978 	(org-publish-projects org-publish-project-alist)))))
    979 
    980 
    981 ;;;###autoload
    982 (defun org-publish-current-file (&optional force async)
    983   "Publish the current file.
    984 With prefix argument FORCE, force publish the file.  When
    985 optional argument ASYNC is non-nil, publishing will be done
    986 asynchronously, in another process."
    987   (interactive "P")
    988   (let ((file (buffer-file-name (buffer-base-buffer))))
    989     (if async
    990 	(org-export-async-start (lambda (_) nil)
    991 	  `(let ((org-publish-use-timestamps-flag
    992 		  (if ',force nil ,org-publish-use-timestamps-flag)))
    993 	     (org-publish-file ,file)))
    994       (save-window-excursion
    995 	(let ((org-publish-use-timestamps-flag
    996 	       (if force nil org-publish-use-timestamps-flag)))
    997 	  (org-publish-file file))))))
    998 
    999 ;;;###autoload
   1000 (defun org-publish-current-project (&optional force async)
   1001   "Publish the project associated with the current file.
   1002 With a prefix argument, force publishing of all files in
   1003 the project."
   1004   (interactive "P")
   1005   (save-window-excursion
   1006     (let ((project (org-publish-get-project-from-filename
   1007 		    (buffer-file-name (buffer-base-buffer)) 'up)))
   1008       (if project (org-publish project force async)
   1009 	(error "File %s is not part of any known project"
   1010 	       (buffer-file-name (buffer-base-buffer)))))))
   1011 
   1012 
   1013 
   1014 ;;; Index generation
   1015 
   1016 (defun org-publish-collect-index (output _backend info)
   1017   "Update index for a file in cache.
   1018 
   1019 OUTPUT is the output from transcoding current file.  BACKEND is
   1020 the backend that was used for transcoding.  INFO is a plist
   1021 containing publishing and export options.
   1022 
   1023 The index relative to current file is stored as an alist.  An
   1024 association has the following shape: (TERM FILE-NAME PARENT),
   1025 where TERM is the indexed term, as a string, FILE-NAME is the
   1026 original full path of the file where the term in encountered, and
   1027 PARENT is a reference to the headline, if any, containing the
   1028 original index keyword.  When non-nil, this reference is a cons
   1029 cell.  Its CAR is a symbol among `id', `custom-id' and `name' and
   1030 its CDR is a string."
   1031   (let ((file (file-truename (plist-get info :input-file))))
   1032     (org-publish-cache-set-file-property
   1033      file :index
   1034      (delete-dups
   1035       (org-element-map (plist-get info :parse-tree) 'keyword
   1036 	(lambda (k)
   1037 	  (when (equal (org-element-property :key k) "INDEX")
   1038 	    (let ((parent (org-element-lineage k 'headline)))
   1039 	      (list (org-element-property :value k)
   1040 		    file
   1041 		    (cond
   1042 		     ((not parent) nil)
   1043 		     ((let ((id (org-element-property :ID parent)))
   1044 			(and id (cons 'id id))))
   1045 		     ((let ((id (org-element-property :CUSTOM_ID parent)))
   1046 			(and id (cons 'custom-id id))))
   1047 		     (t (cons 'name
   1048 			      ;; Remove statistics cookie.
   1049 			      (replace-regexp-in-string
   1050 			       "\\[[0-9]+%\\]\\|\\[[0-9]+/[0-9]+\\]" ""
   1051 			       (org-element-property :raw-value parent)))))))))
   1052 	info))
   1053      nil 'transient))
   1054   ;; Return output unchanged.
   1055   output)
   1056 
   1057 (defun org-publish-index-generate-theindex (project directory)
   1058   "Retrieve full index from cache and build \"theindex.org\".
   1059 PROJECT is the project the index relates to.  DIRECTORY is the
   1060 publishing directory."
   1061   (let ((all-files (org-publish-get-base-files project))
   1062 	full-index)
   1063     ;; Compile full index and sort it alphabetically.
   1064     (dolist (file all-files
   1065 		  (setq full-index
   1066 			(sort (nreverse full-index)
   1067 			      (lambda (a b) (string< (downcase (car a))
   1068 						     (downcase (car b)))))))
   1069       (let ((index (org-publish-cache-get-file-property file :index)))
   1070 	(dolist (term index)
   1071 	  (unless (member term full-index) (push term full-index)))))
   1072     ;; Write "theindex.inc" in DIRECTORY.
   1073     (with-temp-file (expand-file-name "theindex.inc" directory)
   1074       (let ((current-letter nil) (last-entry nil))
   1075 	(dolist (idx full-index)
   1076 	  (let* ((entry (org-split-string (car idx) "!"))
   1077 		 (letter (upcase (substring (car entry) 0 1)))
   1078 		 ;; Transform file into a path relative to publishing
   1079 		 ;; directory.
   1080 		 (file (file-relative-name
   1081 			(nth 1 idx)
   1082 			(plist-get (cdr project) :base-directory))))
   1083 	    ;; Check if another letter has to be inserted.
   1084 	    (unless (string= letter current-letter)
   1085 	      (insert (format "* %s\n" letter)))
   1086 	    ;; Compute the first difference between last entry and
   1087 	    ;; current one: it tells the level at which new items
   1088 	    ;; should be added.
   1089 	    (let* ((rank
   1090 		    (if (equal entry last-entry) (1- (length entry))
   1091 		      (cl-loop for n from 0 to (length entry)
   1092 			       unless (equal (nth n entry) (nth n last-entry))
   1093 			       return n)))
   1094 		   (len (length (nthcdr rank entry))))
   1095 	      ;; For each term after the first difference, create
   1096 	      ;; a new sub-list with the term as body.  Moreover,
   1097 	      ;; linkify the last term.
   1098 	      (dotimes (n len)
   1099 		(insert
   1100 		 (concat
   1101 		  (make-string (* (+ rank n) 2) ?\s) "  - "
   1102 		  (if (not (= (1- len) n)) (nth (+ rank n) entry)
   1103 		    ;; Last term: Link it to TARGET, if possible.
   1104 		    (let ((target (nth 2 idx)))
   1105 		      (format
   1106 		       "[[%s][%s]]"
   1107 		       ;; Destination.
   1108 		       (pcase (car target)
   1109 			 (`nil (format "file:%s" file))
   1110 			 (`id (format "id:%s" (cdr target)))
   1111 			 (`custom-id (format "file:%s::#%s" file (cdr target)))
   1112 			 (_ (format "file:%s::*%s" file (cdr target))))
   1113 		       ;; Description.
   1114 		       (car (last entry)))))
   1115 		  "\n"))))
   1116 	    (setq current-letter letter last-entry entry))))
   1117       ;; Create "theindex.org", if it doesn't exist yet, and provide
   1118       ;; a default index file.
   1119       (let ((index.org (expand-file-name "theindex.org" directory)))
   1120 	(unless (file-exists-p index.org)
   1121 	  (with-temp-file index.org
   1122 	    (insert "#+TITLE: Index\n\n#+INCLUDE: \"theindex.inc\"\n\n")))))))
   1123 
   1124 
   1125 
   1126 ;;; External Fuzzy Links Resolution
   1127 ;;
   1128 ;; This part implements tools to resolve [[file.org::*Some headline]]
   1129 ;; links, where "file.org" belongs to the current project.
   1130 
   1131 (defun org-publish--store-crossrefs (output _backend info)
   1132   "Store cross-references for current published file.
   1133 
   1134 OUTPUT is the produced output, as a string.  BACKEND is the export
   1135 backend used, as a symbol.  INFO is the final export state, as
   1136 a plist.
   1137 
   1138 This function is meant to be used as a final output filter.  See
   1139 `org-publish-org-to'."
   1140   (org-publish-cache-set-file-property
   1141    (file-truename (plist-get info :input-file))
   1142    :crossrefs
   1143    ;; Update `:crossrefs' so as to remove unused references and search
   1144    ;; cells.  Actually used references are extracted from
   1145    ;; `:internal-references', with references as strings removed.  See
   1146    ;; `org-export-get-reference' for details.
   1147    (cl-remove-if (lambda (pair) (stringp (car pair)))
   1148 		 (plist-get info :internal-references)))
   1149   ;; Return output unchanged.
   1150   output)
   1151 
   1152 (defun org-publish-resolve-external-link (search file &optional prefer-custom)
   1153   "Return reference for element matching string SEARCH in FILE.
   1154 
   1155 Return value is an internal reference, as a string.
   1156 
   1157 This function allows resolving external links with a search
   1158 option, e.g.,
   1159 
   1160   [[file:file.org::*heading][description]]
   1161   [[file:file.org::#custom-id][description]]
   1162   [[file:file.org::fuzzy][description]]
   1163 
   1164 When PREFER-CUSTOM is non-nil, and SEARCH targets a headline in
   1165 FILE, return its custom ID, if any.
   1166 
   1167 It only makes sense to use this if export backend builds
   1168 references with `org-export-get-reference'."
   1169   (cond
   1170    ((and prefer-custom
   1171 	 (if (string-prefix-p "#" search)
   1172 	     (substring search 1)
   1173 	   (with-current-buffer (find-file-noselect file)
   1174 	     (org-with-point-at 1
   1175 	       (let ((org-link-search-must-match-exact-headline t))
   1176 		 (condition-case err
   1177 		     (org-link-search search nil t)
   1178 		   (error
   1179 		    (signal 'org-link-broken (cdr err)))))
   1180 	       (and (derived-mode-p 'org-mode)
   1181                     (org-at-heading-p)
   1182 		    (org-string-nw-p (org-entry-get (point) "CUSTOM_ID"))))))))
   1183    ((not org-publish-cache)
   1184     (progn
   1185       (message "Reference %S in file %S cannot be resolved without publishing"
   1186 	       search
   1187 	       file)
   1188       "MissingReference"))
   1189    (t
   1190     (let* ((filename (file-truename file))
   1191 	   (crossrefs
   1192 	    (org-publish-cache-get-file-property filename :crossrefs nil t))
   1193 	   (cells (org-export-string-to-search-cell search)))
   1194       (or
   1195        ;; Look for reference associated to search cells triggered by
   1196        ;; LINK.  It can match when targeted file has been published
   1197        ;; already.
   1198        (let ((known (cdr (cl-some (lambda (c) (assoc c crossrefs)) cells))))
   1199 	 (and known (org-export-format-reference known)))
   1200        ;; Search cell is unknown so far.  Generate a new internal
   1201        ;; reference that will be used when the targeted file will be
   1202        ;; published.
   1203        (let ((new (org-export-new-reference crossrefs)))
   1204 	 (dolist (cell cells) (push (cons cell new) crossrefs))
   1205 	 (org-publish-cache-set-file-property filename :crossrefs crossrefs)
   1206 	 (org-export-format-reference new)))))))
   1207 
   1208 (defun org-publish-file-relative-name (filename info)
   1209   "Convert FILENAME to be relative to current project's base directory.
   1210 INFO is the plist containing the current export state.  The
   1211 function does not change relative file names."
   1212   (let ((base (plist-get info :base-directory)))
   1213     (if (and base
   1214 	     (file-name-absolute-p filename)
   1215 	     (file-in-directory-p filename base))
   1216 	(file-relative-name filename base)
   1217       filename)))
   1218 
   1219 
   1220 
   1221 ;;; Caching functions
   1222 
   1223 (defun org-publish-write-cache-file (&optional free-cache)
   1224   "Write `org-publish-cache' to file.
   1225 If FREE-CACHE, empty the cache."
   1226   (unless org-publish-cache
   1227     (error "`org-publish-write-cache-file' called, but no cache present"))
   1228 
   1229   (let ((cache-file (org-publish-cache-get ":cache-file:")))
   1230     (unless cache-file
   1231       (error "Cannot find cache-file name in `org-publish-write-cache-file'"))
   1232     (with-temp-file cache-file
   1233       (let (print-level print-length)
   1234 	(insert "(setq org-publish-cache \
   1235 \(make-hash-table :test 'equal :weakness nil :size 100))\n")
   1236 	(maphash (lambda (k v)
   1237 		   (insert
   1238 		    (format "(puthash %S %s%S org-publish-cache)\n"
   1239 			    k (if (or (listp v) (symbolp v)) "'" "") v)))
   1240 		 org-publish-cache)))
   1241     (when free-cache (org-publish-reset-cache))))
   1242 
   1243 (defun org-publish-initialize-cache (project-name)
   1244   "Initialize the projects cache if not initialized yet and return it."
   1245 
   1246   (unless project-name
   1247     (error "Cannot initialize `org-publish-cache' without projects name in \
   1248 `org-publish-initialize-cache'"))
   1249 
   1250   (unless (file-exists-p org-publish-timestamp-directory)
   1251     (make-directory org-publish-timestamp-directory t))
   1252   (unless (file-directory-p org-publish-timestamp-directory)
   1253     (error "Org publish timestamp: %s is not a directory"
   1254 	   org-publish-timestamp-directory))
   1255 
   1256   (unless org-publish-transient-cache
   1257     (setq org-publish-transient-cache (make-hash-table :test #'equal)))
   1258 
   1259   (unless (and org-publish-cache
   1260 	       (string= (org-publish-cache-get ":project:") project-name))
   1261     (let* ((cache-file
   1262 	    (concat
   1263 	     (expand-file-name org-publish-timestamp-directory)
   1264 	     project-name ".cache"))
   1265 	   (cexists (file-exists-p cache-file)))
   1266 
   1267       (when org-publish-cache (org-publish-reset-cache))
   1268 
   1269       (if cexists (load-file cache-file)
   1270 	(setq org-publish-cache
   1271 	      (make-hash-table :test 'equal :weakness nil :size 100))
   1272 	(org-publish-cache-set ":project:" project-name)
   1273 	(org-publish-cache-set ":cache-file:" cache-file))
   1274       (unless cexists (org-publish-write-cache-file nil))))
   1275   org-publish-cache)
   1276 
   1277 (defun org-publish-reset-cache ()
   1278   "Empty `org-publish-cache' and reset it nil."
   1279   (message "%s" "Resetting org-publish-cache")
   1280   (when (hash-table-p org-publish-cache)
   1281     (clrhash org-publish-cache))
   1282   (when (hash-table-p org-publish-transient-cache)
   1283     (clrhash org-publish-transient-cache))
   1284   (setq org-publish-cache nil))
   1285 
   1286 (defun org-publish-cache-file-needs-publishing
   1287     (filename &optional pub-dir pub-func _base-dir)
   1288   "Check the timestamp of the last publishing of FILENAME.
   1289 Return non-nil if the file needs publishing.  Also check if
   1290 any included files have been more recently published, so that
   1291 the file including them will be republished as well."
   1292   (unless org-publish-cache
   1293     (error
   1294      "`org-publish-cache-file-needs-publishing' called, but no cache present"))
   1295   (let* ((key (org-publish-timestamp-filename filename pub-dir pub-func))
   1296 	 (pstamp (org-publish-cache-get key))
   1297 	 (org-inhibit-startup t)
   1298 	 included-files-mtime)
   1299     (when (equal (file-name-extension filename) "org")
   1300       (let ((case-fold-search t))
   1301 	(with-temp-buffer
   1302           (delay-mode-hooks
   1303             (org-mode)
   1304             (insert-file-contents filename)
   1305 	    (goto-char (point-min))
   1306 	    (while (re-search-forward "^[ \t]*#\\+INCLUDE:" nil t)
   1307 	      (let ((element (org-element-at-point)))
   1308 	        (when (org-element-type-p element 'keyword)
   1309 		  (let* ((value (org-element-property :value element))
   1310 		         (include-filename
   1311 			  (and (string-match "\\`\\(\".+?\"\\|\\S-+\\)" value)
   1312 			       (let ((m (org-strip-quotes
   1313 				         (match-string 1 value))))
   1314 			         ;; Ignore search suffix.
   1315 			         (if (string-match "::.*?\\'" m)
   1316 				     (substring m 0 (match-beginning 0))
   1317 				   m)))))
   1318 		    (when include-filename
   1319 		      (push (org-publish-cache-mtime-of-src
   1320 			     (expand-file-name include-filename (file-name-directory filename)))
   1321 			    included-files-mtime))))))))))
   1322     (or (null pstamp)
   1323 	(let ((mtime (org-publish-cache-mtime-of-src filename)))
   1324 	  (or (time-less-p pstamp mtime)
   1325 	      (cl-some (lambda (ct) (time-less-p mtime ct))
   1326 		       included-files-mtime))))))
   1327 
   1328 (defun org-publish-cache-set-file-property
   1329     (filename property value &optional project-name transient)
   1330   "Set the VALUE for a PROPERTY of file FILENAME in publishing cache to VALUE.
   1331 Use cache file of PROJECT-NAME.  If the entry does not exist, it
   1332 will be created.  Return VALUE.
   1333 
   1334 When TRANSIENT is non-nil, store value in transient cache that is only
   1335 maintained during the current publish process."
   1336   ;; Evtl. load the requested cache file:
   1337   (when project-name (org-publish-initialize-cache project-name))
   1338   (if transient
   1339       (puthash (cons filename property) value
   1340                org-publish-transient-cache)
   1341     (let ((pl (org-publish-cache-get filename)))
   1342       (if pl (progn (plist-put pl property value) value)
   1343         (org-publish-cache-get-file-property
   1344          filename property value nil project-name)))))
   1345 
   1346 (defun org-publish-cache-get-file-property
   1347     (filename property &optional default no-create project-name)
   1348   "Return the value for a PROPERTY of file FILENAME in publishing cache.
   1349 Use cache file of PROJECT-NAME.  Return the value of that PROPERTY,
   1350 or DEFAULT, if the value does not yet exist.  Create the entry,
   1351 if necessary, unless NO-CREATE is non-nil."
   1352   (when project-name (org-publish-initialize-cache project-name))
   1353   (or (gethash (cons filename property) org-publish-transient-cache)
   1354       (let ((properties (org-publish-cache-get filename)))
   1355         (cond ((null properties)
   1356 	       (unless no-create
   1357 	         (org-publish-cache-set filename (list property default)))
   1358 	       default)
   1359 	      ((plist-member properties property) (plist-get properties property))
   1360 	      (t default)))))
   1361 
   1362 (defun org-publish-cache-get (key)
   1363   "Return the value stored in `org-publish-cache' for key KEY.
   1364 Return nil, if no value or nil is found.  Raise an error if the
   1365 cache does not exist."
   1366   (unless org-publish-cache
   1367     (error "`org-publish-cache-get' called, but no cache present"))
   1368   (gethash key org-publish-cache))
   1369 
   1370 (defun org-publish-cache-set (key value)
   1371   "Store KEY VALUE pair in `org-publish-cache'.
   1372 Returns value on success, else nil.  Raise an error if the cache
   1373 does not exist."
   1374   (unless org-publish-cache
   1375     (error "`org-publish-cache-set' called, but no cache present"))
   1376   (puthash key value org-publish-cache))
   1377 
   1378 (defun org-publish-cache-mtime-of-src (file)
   1379   "Get the mtime of FILE as an integer."
   1380   (let ((attr (file-attributes
   1381 	       (expand-file-name (or (file-symlink-p file) file)
   1382 				 (file-name-directory file)))))
   1383     (if attr (file-attribute-modification-time attr)
   1384       (error "No such file: %S" file))))
   1385 
   1386 
   1387 (provide 'ox-publish)
   1388 
   1389 ;; Local variables:
   1390 ;; generated-autoload-file: "org-loaddefs.el"
   1391 ;; End:
   1392 
   1393 ;;; ox-publish.el ends here