summaryrefslogtreecommitdiff
path: root/lisp/org/ox-texinfo.el
diff options
context:
space:
mode:
authorKyle Meyer <kyle@kyleam.com>2022-11-29 23:05:53 -0500
committerKyle Meyer <kyle@kyleam.com>2022-11-29 23:05:53 -0500
commit0625651e8a61c9effc31ff771f15885a3a37c6e6 (patch)
treedb4c09e8ef119ad4a9a4028c5e615fd58d2dee69 /lisp/org/ox-texinfo.el
parentedd64e64a389e0f0e6ce670846d4fae79a9d8b35 (diff)
downloademacs-0625651e8a61c9effc31ff771f15885a3a37c6e6.tar.gz
Update to Org 9.6-3-ga4d38e
Diffstat (limited to 'lisp/org/ox-texinfo.el')
-rw-r--r--lisp/org/ox-texinfo.el346
1 files changed, 321 insertions, 25 deletions
diff --git a/lisp/org/ox-texinfo.el b/lisp/org/ox-texinfo.el
index 9a8217583e9..73ba349b1bb 100644
--- a/lisp/org/ox-texinfo.el
+++ b/lisp/org/ox-texinfo.el
@@ -2,7 +2,7 @@
;; Copyright (C) 2012-2022 Free Software Foundation, Inc.
;; Author: Jonathan Leech-Pepin <jonathan.leechpepin at gmail dot com>
-;; Maintainer: Nicolas Goaziou <n.goaziou at gmail dot com>
+;; Maintainer: Nicolas Goaziou <mail@nicolasgoaziou.fr>
;; Keywords: outlines, hypermedia, calendar, wp
;; This file is part of GNU Emacs.
@@ -26,11 +26,14 @@
;;; Code:
+(require 'org-macs)
+(org-assert-version)
+
(require 'cl-lib)
(require 'ox)
(defvar orgtbl-exp-regexp)
-
+(defvar org-texinfo-supports-math--cache)
;;; Define Back-End
@@ -55,6 +58,8 @@
(italic . org-texinfo-italic)
(item . org-texinfo-item)
(keyword . org-texinfo-keyword)
+ (latex-environment . org-texinfo-latex-environment)
+ (latex-fragment . org-texinfo-latex-fragment)
(line-break . org-texinfo-line-break)
(link . org-texinfo-link)
(node-property . org-texinfo-node-property)
@@ -83,7 +88,8 @@
(verse-block . org-texinfo-verse-block))
:filters-alist
'((:filter-headline . org-texinfo--filter-section-blank-lines)
- (:filter-parse-tree . org-texinfo--normalize-headlines)
+ (:filter-parse-tree . (org-texinfo--normalize-headlines
+ org-texinfo--separate-definitions))
(:filter-section . org-texinfo--filter-section-blank-lines)
(:filter-final-output . org-texinfo--untabify))
:menu-entry
@@ -118,8 +124,10 @@
(:texinfo-table-default-markup nil nil org-texinfo-table-default-markup)
(:texinfo-text-markup-alist nil nil org-texinfo-text-markup-alist)
(:texinfo-format-drawer-function nil nil org-texinfo-format-drawer-function)
- (:texinfo-format-inlinetask-function nil nil org-texinfo-format-inlinetask-function)))
-
+ (:texinfo-format-inlinetask-function nil nil org-texinfo-format-inlinetask-function)
+ (:texinfo-compact-itemx nil "compact-itemx" org-texinfo-compact-itemx)
+ ;; Redefine regular options.
+ (:with-latex nil "tex" org-texinfo-with-latex)))
;;; User Configurable Variables
@@ -354,6 +362,37 @@ The function should return the string to be exported."
:group 'org-export-texinfo
:type 'function)
+;;;; LaTeX
+
+(defcustom org-texinfo-with-latex (and org-export-with-latex 'detect)
+ "When non-nil, the Texinfo exporter attempts to process LaTeX math.
+
+When set to t, the exporter will process LaTeX environments and
+fragments as Texinfo \"@displaymath\" and \"@math\" commands
+respectively. Alternatively, when set to `detect', the exporter
+does so only if the installed version of Texinfo supports the
+necessary commands."
+ :group 'org-export-texinfo
+ :package-version '(Org . "9.6")
+ :type '(choice
+ (const :tag "Detect" detect)
+ (const :tag "Yes" t)
+ (const :tag "No" nil)))
+
+;;;; Itemx
+
+(defcustom org-texinfo-compact-itemx nil
+ "Non-nil means certain items in description list become `@itemx'.
+
+If this is non-nil and an item in a description list has no
+body but is followed by another item, then the second item is
+transcoded to `@itemx'. See info node `(org)Plain lists in
+Texinfo export' for how to enable this for individual lists."
+ :package-version '(Org . "9.6")
+ :group 'org-export-texinfo
+ :type 'boolean
+ :safe t)
+
;;;; Compilation
(defcustom org-texinfo-info-process '("makeinfo --no-split %f")
@@ -407,6 +446,30 @@ If two strings share the same prefix (e.g. \"ISO-8859-1\" and
(regexp-opt '("eps" "pdf" "png" "jpg" "jpeg" "gif" "svg"))))
"Rules characterizing image files that can be inlined.")
+(defvar org-texinfo--quoted-keys-regexp
+ (regexp-opt '("BS" "TAB" "RET" "ESC" "SPC" "DEL"
+ "LFD" "DELETE" "SHIFT" "Ctrl" "Meta" "Alt"
+ "Cmd" "Super" "UP" "LEFT" "RIGHT" "DOWN")
+ 'words)
+ "Regexp matching keys that have to be quoted using @key{KEY}.")
+
+(defconst org-texinfo--definition-command-alist
+ '(("deffn Command" . "Command")
+ ("defun" . "Function")
+ ("defmac" . "Macro")
+ ("defspec" . "Special Form")
+ ("defvar" . "Variable")
+ ("defopt" . "User Option")
+ (nil . "Key"))
+ "Alist mapping Texinfo definition commands to output in Info files.")
+
+(defconst org-texinfo--definition-command-regexp
+ (format "\\`%s: \\(.+\\)"
+ (regexp-opt
+ (delq nil (mapcar #'cdr org-texinfo--definition-command-alist))
+ t))
+ "Regexp used to match definition commands in descriptive lists.")
+
;;; Internal Functions
@@ -570,6 +633,130 @@ INFO is a plist holding export options."
(`(,_ ,_ . ,sections) sections)
(_ (user-error "Unknown Texinfo class: %S" class)))))
+(defun org-texinfo--separate-definitions (tree _backend info)
+ "Split up descriptive lists in TREE that contain Texinfo definition commands.
+INFO is a plist used as a communication channel.
+Return new tree."
+ (org-element-map tree 'plain-list
+ (lambda (plain-list)
+ (when (eq (org-element-property :type plain-list) 'descriptive)
+ (let ((contents (org-element-contents plain-list))
+ (items nil))
+ (dolist (item contents)
+ (pcase-let ((`(,cmd . ,args) (org-texinfo--match-definition item)))
+ (cond
+ (cmd
+ (when items
+ (org-texinfo--split-plain-list plain-list (nreverse items))
+ (setq items nil))
+ (org-texinfo--split-definition plain-list item cmd args))
+ (t
+ (when args
+ (org-texinfo--massage-key-item plain-list item args info))
+ (push item items)))))
+ (unless (org-element-contents plain-list)
+ (org-element-extract-element plain-list)))))
+ info)
+ tree)
+
+(defun org-texinfo--match-definition (item)
+ "Return a cons-cell if ITEM specifies a Texinfo definition command.
+The car is the command and the cdr is its arguments."
+ (let ((tag (car-safe (org-element-property :tag item))))
+ (and tag
+ (stringp tag)
+ (string-match org-texinfo--definition-command-regexp tag)
+ (pcase-let*
+ ((cmd (car (rassoc (match-string-no-properties 1 tag)
+ org-texinfo--definition-command-alist)))
+ (`(,cmd ,category)
+ (and cmd (save-match-data (split-string cmd " "))))
+ (args (match-string-no-properties 2 tag)))
+ (cons cmd (if category (concat category " " args) args))))))
+
+(defun org-texinfo--split-definition (plain-list item cmd args)
+ "Insert a definition command before list PLAIN-LIST.
+Replace list item ITEM with a special-block that inherits the
+contents of ITEM and whose type and Texinfo attributes are
+specified by CMD and ARGS."
+ (let ((contents (org-element-contents item)))
+ (org-element-insert-before
+ (apply #'org-element-create 'special-block
+ (list :type cmd
+ :attr_texinfo (list (format ":options %s" args))
+ :post-blank (if contents 1 0))
+ (mapc #'org-element-extract-element contents))
+ plain-list))
+ (org-element-extract-element item))
+
+(defun org-texinfo--split-plain-list (plain-list items)
+ "Insert a new plain list before the plain list PLAIN-LIST.
+Remove ITEMS from PLAIN-LIST and use them as the contents of the
+new plain list."
+ (org-element-insert-before
+ (apply #'org-element-create 'plain-list
+ (list :type 'descriptive
+ :attr_texinfo (org-element-property :attr_texinfo plain-list)
+ :post-blank 1)
+ (mapc #'org-element-extract-element items))
+ plain-list))
+
+(defun org-texinfo--massage-key-item (plain-list item args info)
+ "In PLAIN-LIST modify ITEM based on ARGS.
+
+Reformat ITEM's tag property and determine the arguments for the
+`@findex' and `@kindex' commands for ITEM and store them in ITEM
+using the `:findex' and `:kindex' properties.
+
+If PLAIN-LIST is a description list whose `:compact' attribute is
+non-nil and ITEM has no content but is followed by another item,
+then store the `@findex' and `@kindex' values in the next item.
+If the previous item stored its respective values in this item,
+then move them to the next item.
+
+INFO is a plist used as a communication channel."
+ (let ((key nil)
+ (cmd nil))
+ (if (string-match (rx (+ " ")
+ "(" (group (+ (not (any "()")))) ")"
+ (* " ")
+ eos)
+ args)
+ (setq key (substring args 0 (match-beginning 0))
+ cmd (match-string 1 args))
+ (setq key args))
+ (org-element-put-property
+ item :tag
+ (cons (org-export-raw-string (org-texinfo-kbd-macro key t))
+ (and cmd `(" (" (code (:value ,cmd :post-blank 0)) ")"))))
+ (let ((findex (org-element-property :findex item))
+ (kindex (org-element-property :kindex item))
+ (next-item (org-export-get-next-element item nil))
+ (mx (string-prefix-p "M-x " key)))
+ (when (and (not cmd) mx)
+ (setq cmd (substring key 4)))
+ (when (and cmd (not (member cmd findex)))
+ (setq findex (nconc findex (list cmd))))
+ (unless mx
+ (setq kindex (nconc kindex (list key))))
+ (cond
+ ((and next-item
+ (or (plist-get info :texinfo-compact-itemx)
+ (org-not-nil
+ (org-export-read-attribute :attr_texinfo plain-list :compact)))
+ (not (org-element-contents item))
+ (eq 1 (org-element-property :post-blank item)))
+ (org-element-put-property next-item :findex findex)
+ (org-element-put-property next-item :kindex kindex)
+ (org-element-put-property item :findex nil)
+ (org-element-put-property item :kindex nil))
+ (t
+ (org-element-set-contents
+ item
+ (nconc (mapcar (lambda (key) `(keyword (:key "KINDEX" :value ,key))) kindex)
+ (mapcar (lambda (cmd) `(keyword (:key "FINDEX" :value ,cmd))) findex)
+ (org-element-contents item))))))))
+
;;; Template
(defun org-texinfo-template (contents info)
@@ -990,25 +1177,42 @@ contextual information."
CONTENTS holds the contents of the item. INFO is a plist holding
contextual information."
(let* ((tag (org-element-property :tag item))
- (split (org-string-nw-p
- (org-export-read-attribute :attr_texinfo
- (org-element-property :parent item)
- :sep)))
- (items (and tag
- (let ((tag (org-export-data tag info)))
- (if split
- (split-string tag (regexp-quote split) t "[ \t\n]+")
- (list tag))))))
- (format "%s\n%s"
- (pcase items
- (`nil "@item")
- (`(,item) (concat "@item " item))
- (`(,item . ,items)
- (concat "@item " item "\n"
- (mapconcat (lambda (i) (concat "@itemx " i))
- items
- "\n"))))
- (or contents ""))))
+ (plain-list (org-element-property :parent item))
+ (compact (and (eq (org-element-property :type plain-list) 'descriptive)
+ (or (plist-get info :texinfo-compact-itemx)
+ (org-not-nil (org-export-read-attribute
+ :attr_texinfo plain-list :compact)))))
+ (previous-item nil))
+ (when (and compact
+ (org-export-get-next-element item info)
+ (not (org-element-contents item))
+ (eq 1 (org-element-property :post-blank item)))
+ (org-element-put-property item :post-blank 0))
+ (if (and compact
+ (setq previous-item (org-export-get-previous-element item info))
+ (not (org-element-contents previous-item))
+ (eq 0 (org-element-property :post-blank previous-item)))
+ (format "@itemx%s\n%s"
+ (if tag (concat " " (org-export-data tag info)) "")
+ (or contents ""))
+ (let* ((split (org-string-nw-p (org-export-read-attribute
+ :attr_texinfo plain-list :sep)))
+ (items (and tag
+ (let ((tag (org-export-data tag info)))
+ (if split
+ (split-string tag (regexp-quote split)
+ t "[ \t\n]+")
+ (list tag))))))
+ (format "%s\n%s"
+ (pcase items
+ (`nil "@item")
+ (`(,item) (concat "@item " item))
+ (`(,item . ,items)
+ (concat "@item " item "\n"
+ (mapconcat (lambda (i) (concat "@itemx " i))
+ items
+ "\n"))))
+ (or contents ""))))))
;;;; Keyword
@@ -1032,6 +1236,52 @@ CONTENTS is nil. INFO is a plist holding contextual information."
(concat "@listoffloats "
(org-export-translate "Listing" :utf-8 info))))))))
+;;;; LaTeX Environment
+
+(defun org-texinfo-latex-environment (environment _contents info)
+ "Transcode a LaTeX ENVIRONMENT from Org to Texinfo.
+CONTENTS is ignored. INFO is a plist holding contextual information."
+ (let ((with-latex (plist-get info :with-latex)))
+ (when (or (eq with-latex t)
+ (and (eq with-latex 'detect)
+ (org-texinfo-supports-math-p)))
+ (let ((value (org-element-property :value environment)))
+ (string-join (list "@displaymath"
+ (string-trim (org-remove-indentation value))
+ "@end displaymath")
+ "\n")))))
+
+;;;; LaTeX Fragment
+
+(defun org-texinfo-latex-fragment (fragment _contents info)
+ "Transcode a LaTeX FRAGMENT from Org to Texinfo.
+INFO is a plist holding contextual information."
+ (let ((with-latex (plist-get info :with-latex)))
+ (when (or (eq with-latex t)
+ (and (eq with-latex 'detect)
+ (org-texinfo-supports-math-p)))
+ (let ((value (org-remove-indentation
+ (org-element-property :value fragment))))
+ (cond
+ ((or (string-match-p "^\\\\\\[" value)
+ (string-match-p "^\\$\\$" value))
+ (concat "\n"
+ "@displaymath"
+ "\n"
+ (string-trim (substring value 2 -2))
+ "\n"
+ "@end displaymath"
+ "\n"))
+ ((string-match-p "^\\$" value)
+ (concat "@math{"
+ (string-trim (substring value 1 -1))
+ "}"))
+ ((string-match-p "^\\\\(" value)
+ (concat "@math{"
+ (string-trim (substring value 2 -2))
+ "}"))
+ (t value))))))
+
;;;; Line Break
(defun org-texinfo-line-break (_line-break _contents _info)
@@ -1611,7 +1861,28 @@ contextual information."
(format "@display\n%s@end display" contents))
-;;; Interactive functions
+;;; Public Functions
+
+(defun org-texinfo-kbd-macro (key &optional noquote)
+ "Quote KEY using @kbd{...} and if necessary @key{...}.
+
+This is intended to be used as an Org macro like so:
+
+ #+macro: kbd (eval (org-texinfo-kbd-macro $1))
+ Type {{{kbd(C-c SPC)}}}.
+
+Also see info node `(org)Key bindings in Texinfo export'.
+
+If optional NOQOUTE is non-nil, then do not add the quoting
+that is necessary when using this in an Org macro."
+ (format (if noquote "@kbd{%s}" "@@texinfo:@kbd{@@%s@@texinfo:}@@")
+ (let ((case-fold-search nil))
+ (replace-regexp-in-string
+ org-texinfo--quoted-keys-regexp
+ (if noquote "@key{\\&}" "@@texinfo:@key{@@\\&@@texinfo:}@@")
+ key t))))
+
+;;; Interactive Functions
;;;###autoload
(defun org-texinfo-export-to-texinfo
@@ -1747,6 +2018,31 @@ Return INFO file name or an error if it couldn't be produced."
(message "Process completed.")
output))
+(defun org-texinfo-supports-math-p ()
+ "Return t if the installed version of Texinfo supports \"@math\".
+
+Once computed, the results remain cached."
+ (unless (boundp 'org-texinfo-supports-math--cache)
+ (setq org-texinfo-supports-math--cache
+ (let ((math-example "1 + 1 = 2"))
+ (let* ((input-file
+ (make-temp-file "test" nil ".info"))
+ (input-content
+ (concat (format "@setfilename %s" input-file) "\n"
+ "@node Top" "\n"
+ (format "@displaymath{%s}" math-example) "\n")))
+ (with-temp-file input-file
+ (insert input-content))
+ (let* ((output-file (org-texinfo-compile input-file))
+ (output-content (with-temp-buffer
+ (insert-file-contents output-file)
+ (buffer-string))))
+ (let ((result (string-match-p (regexp-quote math-example)
+ output-content)))
+ (delete-file input-file)
+ (delete-file output-file)
+ (if result t nil)))))))
+ org-texinfo-supports-math--cache)
(provide 'ox-texinfo)