Add `org-compile-file' and `org-file-newer-than-p'

* lisp/org.el (org-compile-file):
(org-file-newer-than-p): New functions.

* lisp/ox-latex.el (org-latex-compile): Use new functions.

* lisp/ox-man.el (org-man-compile): Use new functions.
(org-man-collect-errors): Remove it.

* lisp/ox-texinfo.el (org-texinfo-compile): Use new functions.
(org-texinfo-collect-errors): Remove function as it is not accurate
enough (e.g., it doesn't handle internationalization).
This commit is contained in:
Nicolas Goaziou 2016-05-17 23:37:18 +02:00
parent 14f853dceb
commit ea1147a479
4 changed files with 143 additions and 232 deletions

View File

@ -22852,6 +22852,73 @@ hierarchy of headlines by UP levels before marking the subtree."
(call-interactively 'org-mark-element)
(org-mark-element)))
(defun org-file-newer-than-p (file time)
"Non-nil if FILE is newer than TIME.
FILE is a filename, as a string, TIME is a list of integers, as
returned by, e.g., `current-time'."
(and (file-exists-p file)
;; Only compare times up to whole seconds as some file-systems
;; (e.g. HFS+) do not retain any finer granularity. As
;; a consequence, make sure we return non-nil when the two
;; times are equal.
(not (time-less-p (cl-subseq (nth 5 (file-attributes file)) 0 2)
(cl-subseq time 0 2)))))
(defun org-compile-file (source process ext &optional err-msg log-buf spec)
"Compile a SOURCE file using PROCESS.
PROCESS is either a function or a list of shell commands, as
strings. EXT is a file extension, without the leading dot, as
a string. It is used to check if the process actually succeeded.
PROCESS must create a file with the same base name and directory
as SOURCE, but ending with EXT. The function then returns its
filename. Otherwise, it raises an error. The error message can
then be refined by providing string ERR-MSG, which is appended to
the standard message.
If PROCESS is a function, it is called with a single argument:
the SOURCE file.
If it is a list of commands, each of them is called using
`shell-command'. By default, in each command, %b, %f and %o are
replaced, respectively, with SOURCE base name, SOURCE full name
and SOURCE directory. It is possible, however, to use more
place-holders by specifying them in optional argument SPEC, as an
alist following the pattern (CHARACTER . REPLACEMENT-STRING).
When PROCESS is a list of commands, optional argument LOG-BUF can
be set to a buffer or a buffer name. `shell-command' then uses
it for output.
`default-directory' is set to SOURCE directory during the whole
process."
(let* ((base-name (file-name-sans-extension (file-name-nondirectory source)))
(full-name (file-truename source))
(out-dir (file-name-directory source))
;; Properly set working directory for compilation.
(default-directory (if (file-name-absolute-p source)
(file-name-directory full-name)
default-directory))
(time (current-time))
(err-msg (if (stringp err-msg) (concat ". " err-msg) "")))
(save-window-excursion
(pcase process
((pred functionp) (funcall process (shell-quote-argument source)))
((pred consp)
(let ((log-buf (and log-buf (get-buffer-create log-buf)))
(spec (append spec
`((?b . ,(shell-quote-argument base-name))
(?f . ,(shell-quote-argument full-name))
(?o . ,(shell-quote-argument out-dir))))))
(dolist (command process)
(shell-command (format-spec command spec) log-buf))))
(_ (error "No valid command to process %S%s" source err-msg)))
;; Check for process failure.
(let ((output (concat out-dir base-name "." ext)))
(unless (org-file-newer-than-p output time)
(error (format "File %S wasn't produced%s" output err-msg)))
output))))
;;; Indentation

View File

@ -3519,87 +3519,59 @@ Return PDF file's name."
"Compile a TeX file.
TEXFILE is the name of the file being compiled. Processing is
done through the command specified in `org-latex-pdf-process'.
done through the command specified in `org-latex-pdf-process',
which see. Output is redirected to \"*Org PDF LaTeX Output*\"
buffer.
When optional argument SNIPPET is non-nil, TEXFILE is a temporary
file used to preview a LaTeX snippet. In this case, do not
create a log buffer and do not bother removing log files.
create a log buffer and do not remove log files.
Return PDF file name or an error if it couldn't be produced."
(let* ((base-name (file-name-sans-extension (file-name-nondirectory texfile)))
(full-name (file-truename texfile))
(compiler (or (with-temp-buffer
(save-excursion (insert-file-contents full-name))
(when (and (search-forward-regexp
(regexp-opt org-latex-compilers) (line-end-position 2) t)
(progn (beginning-of-line)
(looking-at-p "%")))
(match-string 0)))
"pdflatex"))
(out-dir (file-name-directory texfile))
;; Properly set working directory for compilation.
(default-directory (if (file-name-absolute-p texfile)
(file-name-directory full-name)
default-directory))
(time (current-time))
warnings)
(unless snippet (message "Processing LaTeX file %s..." texfile))
(save-window-excursion
(cond
;; A function is provided: Apply it.
((functionp org-latex-pdf-process)
(funcall org-latex-pdf-process (shell-quote-argument texfile)))
;; A list is provided: Replace %b, %f and %o with appropriate
;; values in each command before applying it. Note that while
;; "%latex" and "%bibtex" is used in `org-latex-pdf-process',
;; they are replaced with "%L" and "%B" to adhere to
;; format-spec. Output is redirected to "*Org PDF LaTeX
;; Output*" buffer.
((consp org-latex-pdf-process)
(let ((outbuf (and (not snippet)
(get-buffer-create "*Org PDF LaTeX Output*")))
(spec (list (cons ?B (shell-quote-argument org-latex-bib-compiler))
(cons ?L (shell-quote-argument compiler))
(cons ?b (shell-quote-argument base-name))
(cons ?f (shell-quote-argument full-name))
(cons ?o (shell-quote-argument out-dir)))))
(dolist (command org-latex-pdf-process)
(let ((c (replace-regexp-in-string
"%\\(latex\\|bibtex\\)\\>"
(lambda (str) (upcase (substring str 0 2)))
command)))
(shell-command (format-spec c spec) outbuf)))
;; Collect standard errors from output buffer.
(setq warnings (and (not snippet)
(org-latex--collect-warnings outbuf)))))
(t (error "No valid command to process to PDF")))
(let ((pdffile (concat out-dir base-name ".pdf")))
;; Check for process failure. Provide collected errors if
;; possible.
(if (or (not (file-exists-p pdffile))
;; Only compare times up to whole seconds as some filesystems
;; (e.g. HFS+) do not retain any finer granularity.
(time-less-p (cl-subseq (nth 5 (file-attributes pdffile)) 0 2)
(cl-subseq time 0 2)))
(error (format "PDF file %s wasn't produced" pdffile))
;; Else remove log files, when specified, and signal end of
;; process to user, along with any error encountered.
(unless snippet
(when org-latex-remove-logfiles
(dolist (file (directory-files
out-dir t
(concat (regexp-quote base-name)
"\\(?:\\.[0-9]+\\)?"
"\\."
(regexp-opt org-latex-logfiles-extensions))))
(delete-file file)))
(message (concat "PDF file produced"
(cond
((eq warnings 'error) " with errors.")
(warnings (concat " with warnings: " warnings))
(t "."))))))
;; Return output file name.
pdffile))))
Return PDF file name or raise an error if it couldn't be
produced."
(unless snippet (message "Processing LaTeX file %s..." texfile))
(let* ((compiler
(or (with-temp-buffer
(save-excursion (insert-file-contents texfile))
(and (search-forward-regexp (regexp-opt org-latex-compilers)
(line-end-position 2)
t)
(progn (beginning-of-line) (looking-at-p "%"))
(match-string 0)))
"pdflatex"))
(process (if (functionp org-latex-pdf-process) org-latex-pdf-process
;; Replace "%latex" and "%bibtex" with,
;; respectively, "%L" and "%B" so as to adhere to
;; `format-spec' specifications.
(mapcar (lambda (command)
(replace-regexp-in-string
"%\\(?:bib\\|la\\)tex\\>"
(lambda (m) (upcase (substring m 0 2)))
command))
org-latex-pdf-process)))
(spec `((?B . ,(shell-quote-argument org-latex-bib-compiler))
(?L . ,(shell-quote-argument compiler))))
(log-buf-name "*Org PDF LaTeX Output*")
(log-buf (and (not snippet) (get-buffer-create log-buf-name)))
(outfile (org-compile-file texfile process "pdf"
(format "See %S for details" log-buf-name)
log-buf spec)))
(unless snippet
(when org-latex-remove-logfiles
(mapc #'delete-file
(directory-files
(file-name-directory texfile) t
(concat (regexp-quote (file-name-base outfile))
"\\(?:\\.[0-9]+\\)?\\."
(regexp-opt org-latex-logfiles-extensions)))))
(let ((warnings (org-latex--collect-warnings log-buf)))
(message (concat "PDF file produced"
(cond
((eq warnings 'error) " with errors.")
(warnings (concat " with warnings: " warnings))
(t "."))))))
;; Return output file name.
outfile))
(defun org-latex--collect-warnings (buffer)
"Collect some warnings from \"pdflatex\" command output.

View File

@ -1127,73 +1127,15 @@ FILE is the name of the file being compiled. Processing is done
through the command specified in `org-man-pdf-process'.
Return PDF file name or an error if it couldn't be produced."
(let* ((base-name (file-name-sans-extension (file-name-nondirectory file)))
(full-name (file-truename file))
(out-dir (file-name-directory file))
(time (current-time))
;; Properly set working directory for compilation.
(default-directory (if (file-name-absolute-p file)
(file-name-directory full-name)
default-directory))
errors)
(message "Processing Groff file %s..." file)
(save-window-excursion
(cond
;; A function is provided: Apply it.
((functionp org-man-pdf-process)
(funcall org-man-pdf-process (shell-quote-argument file)))
;; A list is provided: Replace %b, %f and %o with appropriate
;; values in each command before applying it. Output is
;; redirected to "*Org PDF Groff Output*" buffer.
((consp org-man-pdf-process)
(let ((outbuf (get-buffer-create "*Org PDF Groff Output*")))
(dolist (command org-man-pdf-process)
(shell-command
(replace-regexp-in-string
"%b" (shell-quote-argument base-name)
(replace-regexp-in-string
"%f" (shell-quote-argument full-name)
(replace-regexp-in-string
"%o" (shell-quote-argument out-dir) command t t) t t) t t)
outbuf))
;; Collect standard errors from output buffer.
(setq errors (org-man-collect-errors outbuf))))
(t (error "No valid command to process to PDF")))
(let ((pdffile (concat out-dir base-name ".pdf")))
;; Check for process failure. Provide collected errors if
;; possible.
(if (or (not (file-exists-p pdffile))
;; Only compare times up to whole seconds as some
;; filesystems (e.g. HFS+) do not retain any finer
;; granularity.
(time-less-p (cl-subseq (nth 5 (file-attributes pdffile)) 0 2)
(cl-subseq time 0 2)))
(error "PDF file %s wasn't produced%s"
pdffile
(if errors (concat ": " errors) ""))
;; Else remove log files, when specified, and signal end of
;; process to user, along with any error encountered.
(when org-man-remove-logfiles
(dolist (ext org-man-logfiles-extensions)
(let ((file (concat out-dir base-name "." ext)))
(when (file-exists-p file) (delete-file file)))))
(message (concat "Process completed"
(if (not errors) "."
(concat " with errors: " errors)))))
;; Return output file name.
pdffile))))
(defun org-man-collect-errors (buffer)
"Collect some kind of errors from \"groff\" output
BUFFER is the buffer containing output.
Return collected error types as a string, or nil if there was
none."
(with-current-buffer buffer
(save-excursion
(goto-char (point-max))
;; Find final run
nil )))
(message "Processing Groff file %s..." file)
(let ((output (org-compile-file file org-man-pdf-process "pdf")))
(when org-man-remove-logfiles
(let ((base (file-name-sans-extension output)))
(dolist (ext org-man-logfiles-extensions)
(let ((file (concat base "." ext)))
(when (file-exists-p file) (delete-file file))))))
(message "Process completed.")
output))
(provide 'ox-man)

View File

@ -1563,96 +1563,26 @@ this command to convert it."
(defun org-texinfo-compile (file)
"Compile a texinfo file.
FILE is the name of the file being compiled. Processing is
done through the command specified in `org-texinfo-info-process'.
FILE is the name of the file being compiled. Processing is done
through the command specified in `org-texinfo-info-process',
which see. Output is redirected to \"*Org INFO Texinfo Output*\"
buffer.
Return INFO file name or an error if it couldn't be produced."
(let* ((base-name (file-name-sans-extension (file-name-nondirectory file)))
(full-name (file-truename file))
(out-dir (file-name-directory file))
(time (current-time))
;; Properly set working directory for compilation.
(default-directory (if (file-name-absolute-p file)
(file-name-directory full-name)
default-directory))
errors)
(message "Processing Texinfo file %s..." file)
(save-window-excursion
;; Replace %b, %f and %o with appropriate values in each command
;; before applying it. Output is redirected to "*Org INFO
;; Texinfo Output*" buffer.
(let ((outbuf (get-buffer-create "*Org INFO Texinfo Output*")))
(with-current-buffer outbuf (compilation-mode))
(dolist (command org-texinfo-info-process)
(shell-command
(replace-regexp-in-string
"%b" (shell-quote-argument base-name)
(replace-regexp-in-string
"%f" (shell-quote-argument full-name)
(replace-regexp-in-string
"%o" (shell-quote-argument out-dir) command t t) t t) t t)
outbuf))
;; Collect standard errors from output buffer.
(setq errors (org-texinfo-collect-errors outbuf)))
(let ((infofile (concat out-dir base-name ".info")))
;; Check for process failure. Provide collected errors if
;; possible.
(if (or (not (file-exists-p infofile))
;; Only compare times up to whole seconds as some
;; filesystems (e.g. HFS+) do not retain any finer
;; granularity.
(time-less-p (cl-subseq (nth 5 (file-attributes infofile)) 0 2)
(cl-subseq time 0 2)))
(error "INFO file %s wasn't produced%s" infofile
(if errors (concat ": " errors) ""))
;; Else remove log files, when specified, and signal end of
;; process to user, along with any error encountered.
(when org-texinfo-remove-logfiles
(dolist (ext org-texinfo-logfiles-extensions)
(let ((file (concat out-dir base-name "." ext)))
(when (file-exists-p file) (delete-file file)))))
(message (concat "Process completed"
(if (not errors) "."
(concat " with errors: " errors)))))
;; Return output file name.
infofile))))
(defun org-texinfo-collect-errors (buffer)
"Collect some kind of errors from \"makeinfo\" command output.
BUFFER is the buffer containing output.
Return collected error types as a string, or nil if there was
none."
(with-current-buffer buffer
(save-excursion
(goto-char (point-min))
;; Find final "makeinfo" run.
(when t
(let ((case-fold-search t)
(errors ""))
(when (save-excursion
(re-search-forward "perhaps incorrect sectioning?" nil t))
(setq errors (concat errors " [incorrect sectioning]")))
(when (save-excursion
(re-search-forward "missing close brace" nil t))
(setq errors (concat errors " [syntax error]")))
(when (save-excursion
(re-search-forward "Unknown command" nil t))
(setq errors (concat errors " [undefined @command]")))
(when (save-excursion
(re-search-forward "No matching @end" nil t))
(setq errors (concat errors " [block incomplete]")))
(when (save-excursion
(re-search-forward "requires a sectioning" nil t))
(setq errors (concat errors " [invalid section command]")))
(when (save-excursion
(re-search-forward "\\[unexpected\ ]" nil t))
(setq errors (concat errors " [unexpected error]")))
(when (save-excursion
(re-search-forward "misplaced " nil t))
(setq errors (concat errors " [syntax error]")))
(and (org-string-nw-p errors) (org-trim errors)))))))
(message "Processing Texinfo file %s..." file)
(let* ((log-name "*Org INFO Texinfo Output*")
(log (get-buffer-create log-name))
(output
(org-compile-file file org-texinfo-info-process "info"
(format "See %S for details" log-name)
log)))
(when org-texinfo-remove-logfiles
(let ((base (file-name-sans-extension output)))
(dolist (ext org-texinfo-logfiles-extensions)
(let ((file (concat base "." ext)))
(when (file-exists-p file) (delete-file file))))))
(message "Process completed.")
output))
(provide 'ox-texinfo)