diff options
author | mattkae <mattkae@protonmail.com> | 2022-05-11 09:23:58 -0400 |
---|---|---|
committer | mattkae <mattkae@protonmail.com> | 2022-05-11 09:23:58 -0400 |
commit | 3f4a0d5370ae6c34afe180df96add3b8522f4af1 (patch) | |
tree | ae901409e02bde8ee278475f8cf6818f8f680a60 /elpa/org-9.5.2/ox-texinfo.el |
initial commit
Diffstat (limited to 'elpa/org-9.5.2/ox-texinfo.el')
-rw-r--r-- | elpa/org-9.5.2/ox-texinfo.el | 1757 |
1 files changed, 1757 insertions, 0 deletions
diff --git a/elpa/org-9.5.2/ox-texinfo.el b/elpa/org-9.5.2/ox-texinfo.el new file mode 100644 index 0000000..46077ec --- /dev/null +++ b/elpa/org-9.5.2/ox-texinfo.el @@ -0,0 +1,1757 @@ +;;; ox-texinfo.el --- Texinfo Back-End for Org Export Engine -*- lexical-binding: t; -*- + +;; Copyright (C) 2012-2021 Free Software Foundation, Inc. +;; Author: Jonathan Leech-Pepin <jonathan.leechpepin at gmail dot com> +;; Maintainer: Nicolas Goaziou <n.goaziou at gmail dot com> +;; Keywords: outlines, hypermedia, calendar, wp + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: +;; +;; See Org manual for details. + +;;; Code: + +(require 'cl-lib) +(require 'ox) + +(defvar orgtbl-exp-regexp) + + + +;;; Define Back-End + +(org-export-define-backend 'texinfo + '((bold . org-texinfo-bold) + (center-block . org-texinfo-center-block) + (clock . org-texinfo-clock) + (code . org-texinfo-code) + (drawer . org-texinfo-drawer) + (dynamic-block . org-texinfo-dynamic-block) + (entity . org-texinfo-entity) + (example-block . org-texinfo-example-block) + (export-block . org-texinfo-export-block) + (export-snippet . org-texinfo-export-snippet) + (fixed-width . org-texinfo-fixed-width) + (footnote-definition . org-texinfo-footnote-definition) + (footnote-reference . org-texinfo-footnote-reference) + (headline . org-texinfo-headline) + (inline-src-block . org-texinfo-inline-src-block) + (inlinetask . org-texinfo-inlinetask) + (italic . org-texinfo-italic) + (item . org-texinfo-item) + (keyword . org-texinfo-keyword) + (line-break . org-texinfo-line-break) + (link . org-texinfo-link) + (node-property . org-texinfo-node-property) + (paragraph . org-texinfo-paragraph) + (plain-list . org-texinfo-plain-list) + (plain-text . org-texinfo-plain-text) + (planning . org-texinfo-planning) + (property-drawer . org-texinfo-property-drawer) + (quote-block . org-texinfo-quote-block) + (radio-target . org-texinfo-radio-target) + (section . org-texinfo-section) + (special-block . org-texinfo-special-block) + (src-block . org-texinfo-src-block) + (statistics-cookie . org-texinfo-statistics-cookie) + (strike-through . org-texinfo-strike-through) + (subscript . org-texinfo-subscript) + (superscript . org-texinfo-superscript) + (table . org-texinfo-table) + (table-cell . org-texinfo-table-cell) + (table-row . org-texinfo-table-row) + (target . org-texinfo-target) + (template . org-texinfo-template) + (timestamp . org-texinfo-timestamp) + (underline . org-texinfo-underline) + (verbatim . org-texinfo-verbatim) + (verse-block . org-texinfo-verse-block)) + :filters-alist + '((:filter-headline . org-texinfo--filter-section-blank-lines) + (:filter-parse-tree . org-texinfo--normalize-headlines) + (:filter-section . org-texinfo--filter-section-blank-lines) + (:filter-final-output . org-texinfo--untabify)) + :menu-entry + '(?i "Export to Texinfo" + ((?t "As TEXI file" org-texinfo-export-to-texinfo) + (?i "As INFO file" org-texinfo-export-to-info) + (?o "As INFO file and open" + (lambda (a s v b) + (if a (org-texinfo-export-to-info t s v b) + (org-open-file (org-texinfo-export-to-info nil s v b))))))) + :options-alist + '((:texinfo-filename "TEXINFO_FILENAME" nil nil t) + (:texinfo-class "TEXINFO_CLASS" nil org-texinfo-default-class t) + (:texinfo-header "TEXINFO_HEADER" nil nil newline) + (:texinfo-post-header "TEXINFO_POST_HEADER" nil nil newline) + (:subtitle "SUBTITLE" nil nil parse) + (:subauthor "SUBAUTHOR" nil nil newline) + (:texinfo-dircat "TEXINFO_DIR_CATEGORY" nil nil t) + (:texinfo-dirtitle "TEXINFO_DIR_TITLE" nil nil t) + (:texinfo-dirdesc "TEXINFO_DIR_DESC" nil nil t) + (:texinfo-printed-title "TEXINFO_PRINTED_TITLE" nil nil t) + ;; Other variables. + (:texinfo-classes nil nil org-texinfo-classes) + (:texinfo-format-headline-function nil nil org-texinfo-format-headline-function) + (:texinfo-node-description-column nil nil org-texinfo-node-description-column) + (:texinfo-active-timestamp-format nil nil org-texinfo-active-timestamp-format) + (:texinfo-inactive-timestamp-format nil nil org-texinfo-inactive-timestamp-format) + (:texinfo-diary-timestamp-format nil nil org-texinfo-diary-timestamp-format) + (:texinfo-link-with-unknown-path-format nil nil org-texinfo-link-with-unknown-path-format) + (:texinfo-tables-verbatim nil nil org-texinfo-tables-verbatim) + (:texinfo-table-scientific-notation nil nil org-texinfo-table-scientific-notation) + (: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))) + + + +;;; User Configurable Variables + +(defgroup org-export-texinfo nil + "Options for exporting Org mode files to Texinfo." + :tag "Org Export Texinfo" + :version "24.4" + :package-version '(Org . "8.0") + :group 'org-export) + +;;;; Preamble + +(defcustom org-texinfo-coding-system nil + "Default document encoding for Texinfo output. + +If nil it will default to `buffer-file-coding-system'." + :group 'org-export-texinfo + :type 'coding-system) + +(defcustom org-texinfo-default-class "info" + "The default Texinfo class." + :group 'org-export-texinfo + :type '(string :tag "Texinfo class")) + +(defcustom org-texinfo-classes + '(("info" + "@documentencoding AUTO\n@documentlanguage AUTO" + ("@chapter %s" "@unnumbered %s" "@chapheading %s" "@appendix %s") + ("@section %s" "@unnumberedsec %s" "@heading %s" "@appendixsec %s") + ("@subsection %s" "@unnumberedsubsec %s" "@subheading %s" + "@appendixsubsec %s") + ("@subsubsection %s" "@unnumberedsubsubsec %s" "@subsubheading %s" + "@appendixsubsubsec %s"))) + "Alist of Texinfo classes and associated header and structure. +If #+TEXINFO_CLASS is set in the buffer, use its value and the +associated information. Here is the structure of a class +definition: + + (class-name + header-string + (numbered-1 unnumbered-1 unnumbered-no-toc-1 appendix-1) + (numbered-2 unnumbered-2 unnumbered-no-toc-2 appendix-2) + ...) + + +The header string +----------------- + +The header string is inserted in the header of the generated +document, right after \"@setfilename\" and \"@settitle\" +commands. + +If it contains the special string + + \"@documentencoding AUTO\" + +\"AUTO\" will be replaced with an appropriate coding system. See +`org-texinfo-coding-system' for more information. Likewise, if +the string contains the special string + + \"@documentlanguage AUTO\" + +\"AUTO\" will be replaced with the language defined in the +buffer, through #+LANGUAGE keyword, or globally, with +`org-export-default-language', which see. + + +The sectioning structure +------------------------ + +The sectioning structure of the class is given by the elements +following the header string. For each sectioning level, a number +of strings is specified. A %s formatter is mandatory in each +section string and will be replaced by the title of the section." + :group 'org-export-texinfo + :version "27.1" + :package-version '(Org . "9.2") + :type '(repeat + (list (string :tag "Texinfo class") + (string :tag "Texinfo header") + (repeat :tag "Levels" :inline t + (choice + (list :tag "Heading" + (string :tag " numbered") + (string :tag " unnumbered") + (string :tag "unnumbered-no-toc") + (string :tag " appendix"))))))) + +;;;; Headline + +(defcustom org-texinfo-format-headline-function + 'org-texinfo-format-headline-default-function + "Function to format headline text. + +This function will be called with 5 arguments: +TODO the todo keyword (string or nil). +TODO-TYPE the type of todo (symbol: `todo', `done', nil) +PRIORITY the priority of the headline (integer or nil) +TEXT the main headline text (string). +TAGS the tags as a list of strings (list of strings or nil). + +The function result will be used in the section format string." + :group 'org-export-texinfo + :type 'function + :version "26.1" + :package-version '(Org . "8.3")) + +;;;; Node listing (menu) + +(defcustom org-texinfo-node-description-column 32 + "Column at which to start the description in the node listings. +If a node title is greater than this length, the description will +be placed after the end of the title." + :group 'org-export-texinfo + :type 'integer) + +;;;; Timestamps + +(defcustom org-texinfo-active-timestamp-format "@emph{%s}" + "A printf format string to be applied to active timestamps." + :group 'org-export-texinfo + :type 'string) + +(defcustom org-texinfo-inactive-timestamp-format "@emph{%s}" + "A printf format string to be applied to inactive timestamps." + :group 'org-export-texinfo + :type 'string) + +(defcustom org-texinfo-diary-timestamp-format "@emph{%s}" + "A printf format string to be applied to diary timestamps." + :group 'org-export-texinfo + :type 'string) + +;;;; Links + +(defcustom org-texinfo-link-with-unknown-path-format "@indicateurl{%s}" + "Format string for links with unknown path type." + :group 'org-export-texinfo + :type 'string) + +;;;; Tables + +(defcustom org-texinfo-tables-verbatim nil + "When non-nil, tables are exported verbatim." + :group 'org-export-texinfo + :type 'boolean) + +(defcustom org-texinfo-table-scientific-notation nil + "Format string to display numbers in scientific notation. + +The format should have \"%s\" twice, for mantissa and exponent +\(i.e. \"%s\\\\times10^{%s}\"). + +When nil, no transformation is made." + :group 'org-export-texinfo + :type '(choice + (string :tag "Format string") + (const :tag "No formatting" nil))) + +(defcustom org-texinfo-table-default-markup "@asis" + "Default markup for first column in two-column tables. + +This should an indicating command, e.g., \"@code\", \"@kbd\" or +\"@samp\". + +It can be overridden locally using the \":indic\" attribute." + :group 'org-export-texinfo + :type 'string + :version "26.1" + :package-version '(Org . "9.1") + :safe #'stringp) + +;;;; Text markup + +(defcustom org-texinfo-text-markup-alist '((bold . "@strong{%s}") + (code . code) + (italic . "@emph{%s}") + (verbatim . samp)) + "Alist of Texinfo expressions to convert text markup. + +The key must be a symbol among `bold', `code', `italic', +`strike-through', `underscore' and `verbatim'. The value is +a formatting string to wrap fontified text with. + +Value can also be set to the following symbols: `verb', `samp' +and `code'. With the first one, Org uses \"@verb\" to create +a format string and selects a delimiter character that isn't in +the string. For the other two, Org uses \"@samp\" or \"@code\" +to typeset and protects special characters. + +When no association is found for a given markup, text is returned +as-is." + :group 'org-export-texinfo + :version "26.1" + :package-version '(Org . "9.1") + :type 'alist + :options '(bold code italic strike-through underscore verbatim)) + +;;;; Drawers + +(defcustom org-texinfo-format-drawer-function (lambda (_name contents) contents) + "Function called to format a drawer in Texinfo code. + +The function must accept two parameters: + NAME the drawer name, like \"LOGBOOK\" + CONTENTS the contents of the drawer. + +The function should return the string to be exported. + +The default function simply returns the value of CONTENTS." + :group 'org-export-texinfo + :version "24.4" + :package-version '(Org . "8.2") + :type 'function) + +;;;; Inlinetasks + +(defcustom org-texinfo-format-inlinetask-function + 'org-texinfo-format-inlinetask-default-function + "Function called to format an inlinetask in Texinfo code. + +The function must accept six parameters: + TODO the todo keyword, as a string + TODO-TYPE the todo type, a symbol among `todo', `done' and nil. + PRIORITY the inlinetask priority, as a string + NAME the inlinetask name, as a string. + TAGS the inlinetask tags, as a list of strings. + CONTENTS the contents of the inlinetask, as a string. + +The function should return the string to be exported." + :group 'org-export-texinfo + :type 'function) + +;;;; Compilation + +(defcustom org-texinfo-info-process '("makeinfo --no-split %f") + "Commands to process a Texinfo file to an INFO file. + +This is a list of strings, each of them will be given to the +shell as a command. %f in the command will be replaced by the +relative file name, %F by the absolute file name, %b by the file +base name (i.e. without directory and extension parts), %o by the +base directory of the file and %O by the absolute file name of +the output file." + :group 'org-export-texinfo + :version "26.1" + :package-version '(Org . "9.1") + :type '(repeat :tag "Shell command sequence" + (string :tag "Shell command"))) + +(defcustom org-texinfo-logfiles-extensions + '("aux" "toc" "cp" "fn" "ky" "pg" "tp" "vr") + "The list of file extensions to consider as Texinfo logfiles. +The logfiles will be remove if `org-texinfo-remove-logfiles' is +non-nil." + :group 'org-export-texinfo + :type '(repeat (string :tag "Extension"))) + +(defcustom org-texinfo-remove-logfiles t + "Non-nil means remove the logfiles produced by compiling a Texinfo file. +By default, logfiles are files with these extensions: .aux, .toc, +.cp, .fn, .ky, .pg and .tp. To define the set of logfiles to remove, +set `org-texinfo-logfiles-extensions'." + :group 'org-export-latex + :type 'boolean) + +;;; Constants + +(defconst org-texinfo-max-toc-depth 4 + "Maximum depth for creation of detailed menu listings. +Beyond this depth, Texinfo will not recognize the nodes and will +cause errors. Left as a constant in case this value ever +changes.") + +(defconst org-texinfo-supported-coding-systems + '("US-ASCII" "UTF-8" "ISO-8859-15" "ISO-8859-1" "ISO-8859-2" "koi8-r" "koi8-u") + "List of coding systems supported by Texinfo, as strings. +Specified coding system will be matched against these strings. +If two strings share the same prefix (e.g. \"ISO-8859-1\" and +\"ISO-8859-15\"), the most specific one has to be listed first.") + +(defconst org-texinfo-inline-image-rules + (list (cons "file" + (regexp-opt '("eps" "pdf" "png" "jpg" "jpeg" "gif" "svg")))) + "Rules characterizing image files that can be inlined.") + + +;;; Internal Functions + +(defun org-texinfo--untabify (s _backend _info) + "Remove TAB characters in string S." + (replace-regexp-in-string "\t" (make-string tab-width ?\s) s)) + +(defun org-texinfo--filter-section-blank-lines (headline _backend _info) + "Filter controlling number of blank lines after a section." + (replace-regexp-in-string "\n\\(?:\n[ \t]*\\)*\\'" "\n\n" headline)) + +(defun org-texinfo--normalize-headlines (tree _backend info) + "Normalize headlines in TREE. + +BACK-END is the symbol specifying back-end used for export. +INFO is a plist used as a communication channel. + +Make sure every headline in TREE contains a section, since those +are required to install a menu. Also put exactly one blank line +at the end of each section. + +Return new tree." + (org-element-map tree 'headline + (lambda (hl) + (org-element-put-property hl :post-blank 1) + (let ((contents (org-element-contents hl))) + (when contents + (let ((first (org-element-map contents '(headline section) + #'identity info t))) + (unless (eq (org-element-type first) 'section) + (apply #'org-element-set-contents + hl + (cons `(section (:parent ,hl)) contents))))))) + info) + tree) + +(defun org-texinfo--find-verb-separator (s) + "Return a character not used in string S. +This is used to choose a separator for constructs like \\verb." + (let ((ll "~,./?;':\"|!@#%^&-_=+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ<>()[]{}")) + (cl-loop for c across ll + when (not (string-match (regexp-quote (char-to-string c)) s)) + return (char-to-string c)))) + +(defun org-texinfo--text-markup (text markup _info) + "Format TEXT depending on MARKUP text markup. +INFO is a plist used as a communication channel. See +`org-texinfo-text-markup-alist' for details." + (pcase (cdr (assq markup org-texinfo-text-markup-alist)) + (`nil text) ;no markup: return raw text + (`code (format "@code{%s}" (org-texinfo--sanitize-content text))) + (`samp (format "@samp{%s}" (org-texinfo--sanitize-content text))) + (`verb + (let ((separator (org-texinfo--find-verb-separator text))) + (format "@verb{%s%s%s}" separator text separator))) + ;; Else use format string. + (fmt (format fmt text)))) + +(defun org-texinfo--get-node (datum info) + "Return node or anchor associated to DATUM. +DATUM is a headline, a radio-target or a target. INFO is a plist +used as a communication channel. The function guarantees the +node or anchor name is unique." + (let ((cache (plist-get info :texinfo-node-cache))) + (or (cdr (assq datum cache)) + (let* ((salt 0) + (basename + (org-texinfo--sanitize-node + (pcase (org-element-type datum) + (`headline + (org-texinfo--sanitize-title + (org-export-get-alt-title datum info) info)) + (`radio-target + (org-export-data (org-element-contents datum) info)) + (`target + (org-element-property :value datum)) + (_ + (or (org-element-property :name datum) + (org-export-get-reference datum info)))))) + (name basename)) + ;; Org exports deeper elements before their parents. If two + ;; node names collide -- e.g., they have the same title -- + ;; within the same hierarchy, the second one would get the + ;; smaller node name. This is counter-intuitive. + ;; Consequently, we ensure that every parent headline gets + ;; its node beforehand. As a recursive operation, this + ;; achieves the desired effect. + (let ((parent (org-element-lineage datum '(headline)))) + (when (and parent (not (assq parent cache))) + (org-texinfo--get-node parent info) + (setq cache (plist-get info :texinfo-node-cache)))) + ;; Ensure NAME is unique and not reserved node name "Top", + ;; no matter what case is used. + (while (or (string-equal "Top" (capitalize name)) + (rassoc name cache)) + (setq name (concat basename (format " (%d)" (cl-incf salt))))) + (plist-put info :texinfo-node-cache (cons (cons datum name) cache)) + name)))) + +(defun org-texinfo--sanitize-node (title) + "Bend string TITLE to node line requirements. +Trim string and collapse multiple whitespace characters as they +are not significant. Replace leading left parenthesis, when +followed by a right parenthesis, with a square bracket. Remove +periods, commas and colons." + (org-trim + (replace-regexp-in-string + "[ \t]+" " " + (replace-regexp-in-string + "[:,.]" "" + (replace-regexp-in-string "\\`(\\(.*?)\\)" "[\\1" title))))) + +(defun org-texinfo--sanitize-title (title info) + "Make TITLE suitable as a section name. +TITLE is a string or a secondary string. INFO is the current +export state, as a plist." + (org-export-data-with-backend + title (org-export-toc-entry-backend 'texinfo) info)) + +(defun org-texinfo--sanitize-content (text) + "Escape special characters in string TEXT. +Special characters are: @ { }" + (replace-regexp-in-string "[@{}]" "@\\&" text)) + +(defun org-texinfo--wrap-float (value info &optional type label caption short) + "Wrap string VALUE within a @float command. +INFO is the current export state, as a plist. TYPE is float +type, as a string. LABEL is the cross reference label for the +float, as a string. CAPTION and SHORT are, respectively, the +caption and shortcaption used for the float, as secondary +strings (e.g., returned by `org-export-get-caption')." + (let* ((backend + (org-export-toc-entry-backend 'texinfo + (cons 'footnote-reference + (lambda (f c i) (org-export-with-backend 'texinfo f c i))))) + (short-backend + (org-export-toc-entry-backend 'texinfo + '(inline-src-block . ignore) + '(verbatim . ignore))) + (short-str + (if (and short caption) + (format "@shortcaption{%s}\n" + (org-export-data-with-backend short short-backend info)) + "")) + (caption-str + (if (or short caption) + (format "@caption{%s}\n" + (org-export-data-with-backend + (or caption short) + (if (equal short-str "") short-backend backend) + info)) + ""))) + (format "@float %s%s\n%s\n%s%s@end float" + type (if label (concat "," label) "") value caption-str short-str))) + +(defun org-texinfo--sectioning-structure (info) + "Return sectioning structure used in the document. +INFO is a plist holding export options." + (let ((class (plist-get info :texinfo-class))) + (pcase (assoc class (plist-get info :texinfo-classes)) + (`(,_ ,_ . ,sections) sections) + (_ (user-error "Unknown Texinfo class: %S" class))))) + +;;; Template + +(defun org-texinfo-template (contents info) + "Return complete document string after Texinfo conversion. +CONTENTS is the transcoded contents string. INFO is a plist +holding export options." + (let ((title (org-export-data (plist-get info :title) info)) + ;; Copying data is the contents of the first headline in + ;; parse tree with a non-nil copying property. + (copying (org-element-map (plist-get info :parse-tree) 'headline + (lambda (hl) + (and (org-not-nil (org-element-property :COPYING hl)) + (org-element-contents hl))) + info t))) + (concat + "\\input texinfo @c -*- texinfo -*-\n" + "@c %**start of header\n" + (let ((file (or (org-strip-quotes (plist-get info :texinfo-filename)) + (let ((f (plist-get info :output-file))) + (and f (concat (file-name-sans-extension f) ".info")))))) + (and file (format "@setfilename %s\n" file))) + (format "@settitle %s\n" title) + ;; Insert class-defined header. + (org-element-normalize-string + (let ((header (nth 1 (assoc (plist-get info :texinfo-class) + org-texinfo-classes))) + (coding + (catch 'coding-system + (let ((case-fold-search t) + (name (symbol-name (or org-texinfo-coding-system + buffer-file-coding-system)))) + (dolist (system org-texinfo-supported-coding-systems "UTF-8") + (when (string-match-p (regexp-quote system) name) + (throw 'coding-system system)))))) + (language (plist-get info :language)) + (case-fold-search nil)) + ;; Auto coding system. + (replace-regexp-in-string + "^@documentencoding \\(AUTO\\)$" + coding + (replace-regexp-in-string + "^@documentlanguage \\(AUTO\\)$" language header t nil 1) + t nil 1))) + ;; Additional header options set by #+TEXINFO_HEADER. + (let ((texinfo-header (plist-get info :texinfo-header))) + (and texinfo-header (org-element-normalize-string texinfo-header))) + "@c %**end of header\n\n" + ;; Additional options set by #+TEXINFO_POST_HEADER. + (let ((texinfo-post-header (plist-get info :texinfo-post-header))) + (and texinfo-post-header + (org-element-normalize-string texinfo-post-header))) + ;; Copying. + (and copying + (format "@copying\n%s@end copying\n\n" + (org-element-normalize-string + (org-export-data copying info)))) + ;; Info directory information. Only supply if both title and + ;; category are provided. + (let ((dircat (plist-get info :texinfo-dircat)) + (dirtitle + (let ((title (plist-get info :texinfo-dirtitle))) + (and title + (string-match "^\\(?:\\* \\)?\\(.*?\\)\\(\\.\\)?$" title) + (format "* %s." (match-string 1 title)))))) + (when (and dircat dirtitle) + (concat "@dircategory " dircat "\n" + "@direntry\n" + (let ((dirdesc + (let ((desc (plist-get info :texinfo-dirdesc))) + (cond ((not desc) nil) + ((string-suffix-p "." desc) desc) + (t (concat desc ".")))))) + (if dirdesc (format "%-23s %s" dirtitle dirdesc) dirtitle)) + "\n" + "@end direntry\n\n"))) + ;; Title + "@finalout\n" + "@titlepage\n" + (when (plist-get info :with-title) + (concat + (format "@title %s\n" + (or (plist-get info :texinfo-printed-title) title "")) + (let ((subtitle (plist-get info :subtitle))) + (when subtitle + (format "@subtitle %s\n" + (org-export-data subtitle info)))))) + (when (plist-get info :with-author) + (concat + ;; Primary author. + (let ((author (org-string-nw-p + (org-export-data (plist-get info :author) info))) + (email (and (plist-get info :with-email) + (org-string-nw-p + (org-export-data (plist-get info :email) info))))) + (cond ((and author email) + (format "@author %s (@email{%s})\n" author email)) + (author (format "@author %s\n" author)) + (email (format "@author @email{%s}\n" email)))) + ;; Other authors. + (let ((subauthor (plist-get info :subauthor))) + (and subauthor + (org-element-normalize-string + (replace-regexp-in-string "^" "@author " subauthor)))))) + (and copying "@page\n@vskip 0pt plus 1filll\n@insertcopying\n") + "@end titlepage\n\n" + ;; Table of contents. + (and (plist-get info :with-toc) "@contents\n\n") + ;; Configure Top Node when not for TeX. Also include contents + ;; from the first section of the document. + "@ifnottex\n" + "@node Top\n" + (format "@top %s\n" title) + (let* ((first-section + (org-element-map (plist-get info :parse-tree) 'section + #'identity info t '(headline))) + (top-contents + (org-export-data (org-element-contents first-section) info))) + (and (org-string-nw-p top-contents) (concat "\n" top-contents))) + "@end ifnottex\n\n" + ;; Menu. + (org-texinfo-make-menu (plist-get info :parse-tree) info 'master) + "\n" + ;; Document's body. + contents "\n" + ;; Creator. + (and (plist-get info :with-creator) + (concat (plist-get info :creator) "\n")) + ;; Document end. + "@bye"))) + + + +;;; Transcode Functions + +;;;; Bold + +(defun org-texinfo-bold (_bold contents info) + "Transcode BOLD from Org to Texinfo. +CONTENTS is the text with bold markup. INFO is a plist holding +contextual information." + (org-texinfo--text-markup contents 'bold info)) + +;;;; Center Block + +(defun org-texinfo-center-block (_center-block contents _info) + "Transcode a CENTER-BLOCK element from Org to Texinfo. +CONTENTS holds the contents of the block. INFO is a plist used +as a communication channel." + (replace-regexp-in-string "\\(^\\).*?\\S-" "@center " contents nil nil 1)) + +;;;; Clock + +(defun org-texinfo-clock (clock _contents info) + "Transcode a CLOCK element from Org to Texinfo. +CONTENTS is nil. INFO is a plist holding contextual +information." + (concat + "@noindent" + (format "@strong{%s} " org-clock-string) + (format (plist-get info :texinfo-inactive-timestamp-format) + (concat (org-timestamp-translate (org-element-property :value clock)) + (let ((time (org-element-property :duration clock))) + (and time (format " (%s)" time))))) + "@*")) + +;;;; Code + +(defun org-texinfo-code (code _contents info) + "Transcode a CODE object from Org to Texinfo. +CONTENTS is nil. INFO is a plist used as a communication +channel." + (org-texinfo--text-markup (org-element-property :value code) 'code info)) + +;;;; Drawer + +(defun org-texinfo-drawer (drawer contents info) + "Transcode a DRAWER element from Org to Texinfo. +CONTENTS holds the contents of the block. INFO is a plist +holding contextual information." + (let* ((name (org-element-property :drawer-name drawer)) + (output (funcall (plist-get info :texinfo-format-drawer-function) + name contents))) + output)) + +;;;; Dynamic Block + +(defun org-texinfo-dynamic-block (_dynamic-block contents _info) + "Transcode a DYNAMIC-BLOCK element from Org to Texinfo. +CONTENTS holds the contents of the block. INFO is a plist +holding contextual information." + contents) + +;;;; Entity + +(defun org-texinfo-entity (entity _contents _info) + "Transcode an ENTITY object from Org to Texinfo." + ;; Since there is not specific Texinfo entry in entities, use + ;; Texinfo-specific commands whenever possible, and fallback to + ;; UTF-8 otherwise. + (pcase (org-element-property :name entity) + ("AElig" "@AE{}") + ("aelig" "@ae{}") + ((or "bull" "bullet") "@bullet{}") + ("copy" "@copyright{}") + ("deg" "@textdegree{}") + ((or "dots" "hellip") "@dots{}") + ("equiv" "@equiv{}") + ((or "euro" "EUR") "@euro{}") + ((or "ge" "geq") "@geq{}") + ("laquo" "@guillemetleft{}") + ("iexcl" "@exclamdown{}") + ("imath" "@dotless{i}") + ("iquest" "@questiondown{}") + ("jmath" "@dotless{j}") + ((or "le" "leq") "@leq{}") + ("lsaquo" "@guilsinglleft{}") + ("mdash" "---") + ("minus" "@minus{}") + ("nbsp" "@tie{}") + ("ndash" "--") + ("OElig" "@OE{}") + ("oelig" "@oe{}") + ("ordf" "@ordf{}") + ("ordm" "@ordm{}") + ("pound" "@pound{}") + ("raquo" "@guillemetright{}") + ((or "rArr" "Rightarrow") "@result{}") + ("reg" "@registeredsymbol{}") + ((or "rightarrow" "to" "rarr") "@arrow{}") + ("rsaquo" "@guilsinglright{}") + ("thorn" "@th{}") + ("THORN" "@TH{}") + ((and (pred (string-prefix-p "_")) name) ;spacing entities + (format "@w{%s}" (substring name 1))) + (_ (org-element-property :utf-8 entity)))) + +;;;; Example Block + +(defun org-texinfo-example-block (example-block _contents info) + "Transcode an EXAMPLE-BLOCK element from Org to Texinfo. +CONTENTS is nil. INFO is a plist holding contextual +information." + (format "@example\n%s@end example" + (org-texinfo--sanitize-content + (org-export-format-code-default example-block info)))) + +;;; Export Block + +(defun org-texinfo-export-block (export-block _contents _info) + "Transcode a EXPORT-BLOCK element from Org to Texinfo. +CONTENTS is nil. INFO is a plist holding contextual information." + (when (string= (org-element-property :type export-block) "TEXINFO") + (org-remove-indentation (org-element-property :value export-block)))) + +;;; Export Snippet + +(defun org-texinfo-export-snippet (export-snippet _contents _info) + "Transcode a EXPORT-SNIPPET object from Org to Texinfo. +CONTENTS is nil. INFO is a plist holding contextual information." + (when (eq (org-export-snippet-backend export-snippet) 'texinfo) + (org-element-property :value export-snippet))) + +;;;; Fixed Width + +(defun org-texinfo-fixed-width (fixed-width _contents _info) + "Transcode a FIXED-WIDTH element from Org to Texinfo. +CONTENTS is nil. INFO is a plist holding contextual information." + (format "@example\n%s\n@end example" + (org-remove-indentation + (org-texinfo--sanitize-content + (org-element-property :value fixed-width))))) + +;;;; Footnote Reference + +(defun org-texinfo-footnote-reference (footnote _contents info) + "Create a footnote reference for FOOTNOTE. + +FOOTNOTE is the footnote to define. CONTENTS is nil. INFO is a +plist holding contextual information." + (let* ((contents (org-export-get-footnote-definition footnote info)) + (data (org-export-data contents info))) + (format "@footnote{%s}" + ;; It is invalid to close a footnote on a line starting + ;; with "@end". As a safety net, we leave a newline + ;; character before the closing brace. However, when the + ;; footnote ends with a paragraph, it is visually pleasing + ;; to move the brace right after its end. + (if (eq 'paragraph (org-element-type (org-last contents))) + (org-trim data) + data)))) + +;;;; Headline + +(defun org-texinfo-headline (headline contents info) + "Transcode a HEADLINE element from Org to Texinfo. +CONTENTS holds the contents of the headline. INFO is a plist +holding contextual information." + (cond + ((org-element-property :footnote-section-p headline) nil) + ((org-not-nil (org-export-get-node-property :COPYING headline t)) nil) + (t + (let* ((index (let ((i (org-export-get-node-property :INDEX headline t))) + (and (member i '("cp" "fn" "ky" "pg" "tp" "vr")) i))) + (numbered? (org-export-numbered-headline-p headline info)) + (notoc? (org-export-excluded-from-toc-p headline info)) + (command + (and + (not (org-export-low-level-p headline info)) + (let ((sections (org-texinfo--sectioning-structure info))) + (pcase (nth (1- (org-export-get-relative-level headline info)) + sections) + (`(,numbered ,unnumbered ,unnumbered-no-toc ,appendix) + (cond + ((org-not-nil + (org-export-get-node-property :APPENDIX headline t)) + appendix) + (numbered? numbered) + (index unnumbered) + (notoc? unnumbered-no-toc) + (t unnumbered))) + (`nil nil) + (_ (user-error "Invalid Texinfo class specification: %S" + (plist-get info :texinfo-class))))))) + (todo + (and (plist-get info :with-todo-keywords) + (let ((todo (org-element-property :todo-keyword headline))) + (and todo (org-export-data todo info))))) + (todo-type (and todo (org-element-property :todo-type headline))) + (tags (and (plist-get info :with-tags) + (org-export-get-tags headline info))) + (priority (and (plist-get info :with-priority) + (org-element-property :priority headline))) + (text (org-texinfo--sanitize-title + (org-element-property :title headline) info)) + (full-text + (funcall (plist-get info :texinfo-format-headline-function) + todo todo-type priority text tags)) + (contents + (concat "\n" + (if (org-string-nw-p contents) (concat "\n" contents) "") + (and index (format "\n@printindex %s\n" index)))) + (node (org-texinfo--get-node headline info))) + (if (not command) + (concat (and (org-export-first-sibling-p headline info) + (format "@%s\n" (if numbered? 'enumerate 'itemize))) + (format "@item\n@anchor{%s}%s\n" node full-text) + contents + (if (org-export-last-sibling-p headline info) + (format "@end %s" (if numbered? 'enumerate 'itemize)) + "\n")) + (concat + ;; Even if HEADLINE is using @subheading and al., leave an + ;; anchor so cross-references in the Org document still work. + (format (if notoc? "@anchor{%s}\n" "@node %s\n") node) + (format command full-text) + contents)))))) + +(defun org-texinfo-format-headline-default-function + (todo _todo-type priority text tags) + "Default format function for a headline. +See `org-texinfo-format-headline-function' for details." + (concat (and todo (format "@strong{%s} " todo)) + (and priority (format "@emph{#%s} " priority)) + text + (and tags (concat " " (org-make-tag-string tags))))) + +;;;; Inline Src Block + +(defun org-texinfo-inline-src-block (inline-src-block _contents _info) + "Transcode an INLINE-SRC-BLOCK element from Org to Texinfo. +CONTENTS holds the contents of the item. INFO is a plist holding +contextual information." + (format "@code{%s}" + (org-texinfo--sanitize-content + (org-element-property :value inline-src-block)))) + +;;;; Inlinetask + +(defun org-texinfo-inlinetask (inlinetask contents info) + "Transcode an INLINETASK element from Org to Texinfo. +CONTENTS holds the contents of the block. INFO is a plist +holding contextual information." + (let ((title (org-export-data (org-element-property :title inlinetask) info)) + (todo (and (plist-get info :with-todo-keywords) + (let ((todo (org-element-property :todo-keyword inlinetask))) + (and todo (org-export-data todo info))))) + (todo-type (org-element-property :todo-type inlinetask)) + (tags (and (plist-get info :with-tags) + (org-export-get-tags inlinetask info))) + (priority (and (plist-get info :with-priority) + (org-element-property :priority inlinetask)))) + (funcall (plist-get info :texinfo-format-inlinetask-function) + todo todo-type priority title tags contents))) + +(defun org-texinfo-format-inlinetask-default-function + (todo _todo-type priority title tags contents) + "Default format function for inlinetasks. +See `org-texinfo-format-inlinetask-function' for details." + (let ((full-title + (concat (when todo (format "@strong{%s} " todo)) + (when priority (format "#%c " priority)) + title + (when tags (org-make-tag-string tags))))) + (format "@center %s\n\n%s\n" full-title contents))) + +;;;; Italic + +(defun org-texinfo-italic (_italic contents info) + "Transcode ITALIC from Org to Texinfo. +CONTENTS is the text with italic markup. INFO is a plist holding +contextual information." + (org-texinfo--text-markup contents 'italic info)) + +;;;; Item + +(defun org-texinfo-item (item contents info) + "Transcode an ITEM element from Org to Texinfo. +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 "")))) + +;;;; Keyword + +(defun org-texinfo-keyword (keyword _contents info) + "Transcode a KEYWORD element from Org to Texinfo. +CONTENTS is nil. INFO is a plist holding contextual information." + (let ((value (org-element-property :value keyword))) + (pcase (org-element-property :key keyword) + ("TEXINFO" value) + ("CINDEX" (format "@cindex %s" value)) + ("FINDEX" (format "@findex %s" value)) + ("KINDEX" (format "@kindex %s" value)) + ("PINDEX" (format "@pindex %s" value)) + ("TINDEX" (format "@tindex %s" value)) + ("VINDEX" (format "@vindex %s" value)) + ("TOC" + (cond ((string-match-p "\\<tables\\>" value) + (concat "@listoffloats " + (org-export-translate "Table" :utf-8 info))) + ((string-match-p "\\<listings\\>" value) + (concat "@listoffloats " + (org-export-translate "Listing" :utf-8 info)))))))) + +;;;; Line Break + +(defun org-texinfo-line-break (_line-break _contents _info) + "Transcode a LINE-BREAK object from Org to Texinfo. +CONTENTS is nil. INFO is a plist holding contextual information." + "@*\n") + +;;;; Link + +(defun org-texinfo--@ref (datum description info) + "Return @ref command for element or object DATUM. +DESCRIPTION is the printed name of the section, as a string, or +nil." + (let ((node-name (org-texinfo--get-node datum info)) + ;; Sanitize DESCRIPTION for cross-reference use. In + ;; particular, remove colons as they seem to cause pain (even + ;; within @asis{...}) to the Texinfo reader. + (title (and description + (replace-regexp-in-string + "[ \t]*:+" "" + (replace-regexp-in-string "," "@comma{}" description))))) + (if (or (not title) (equal title node-name)) + (format "@ref{%s}" node-name) + (format "@ref{%s, , %s}" node-name title)))) + +(defun org-texinfo-link (link desc info) + "Transcode a LINK object from Org to Texinfo. +DESC is the description part of the link, or the empty string. +INFO is a plist holding contextual information. See +`org-export-data'." + (let* ((type (org-element-property :type link)) + (raw-path (org-element-property :path link)) + ;; Ensure DESC really exists, or set it to nil. + (desc (and (not (string= desc "")) desc)) + (path (org-texinfo--sanitize-content + (cond + ((member type '("http" "https" "ftp")) + (concat type ":" raw-path)) + ((string-equal type "file") + (org-export-file-uri raw-path)) + (t raw-path))))) + (cond + ((org-export-custom-protocol-maybe link desc 'texinfo info)) + ((org-export-inline-image-p link org-texinfo-inline-image-rules) + (org-texinfo--inline-image link info)) + ((equal type "radio") + (let ((destination (org-export-resolve-radio-link link info))) + (if (not destination) desc + (org-texinfo--@ref destination desc info)))) + ((member type '("custom-id" "id" "fuzzy")) + (let ((destination + (if (equal type "fuzzy") + (org-export-resolve-fuzzy-link link info) + (org-export-resolve-id-link link info)))) + (pcase (org-element-type destination) + (`nil + (format org-texinfo-link-with-unknown-path-format path)) + ;; Id link points to an external file. + (`plain-text + (if desc (format "@uref{file://%s,%s}" destination desc) + (format "@uref{file://%s}" destination))) + ((or `headline + ;; Targets within headlines cannot be turned into + ;; @anchor{}, so we refer to the headline parent + ;; directly. + (and `target + (guard (eq 'headline + (org-element-type + (org-element-property :parent destination)))))) + (let ((headline (org-element-lineage destination '(headline) t))) + (org-texinfo--@ref headline desc info))) + (_ (org-texinfo--@ref destination desc info))))) + ((string= type "mailto") + (format "@email{%s}" + (concat path (and desc (concat ", " desc))))) + ;; External link with a description part. + ((and path desc) (format "@uref{%s, %s}" path desc)) + ;; External link without a description part. + (path (format "@uref{%s}" path)) + ;; No path, only description. Try to do something useful. + (t + (format (plist-get info :texinfo-link-with-unknown-path-format) desc))))) + +(defun org-texinfo--inline-image (link info) + "Return Texinfo code for an inline image. +LINK is the link pointing to the inline image. INFO is the +current state of the export, as a plist." + (let* ((parent (org-export-get-parent-element link)) + (label (and (org-element-property :name parent) + (org-texinfo--get-node parent info))) + (caption (org-export-get-caption parent)) + (shortcaption (org-export-get-caption parent t)) + (path (org-element-property :path link)) + (filename + (file-name-sans-extension + (if (file-name-absolute-p path) + (expand-file-name path) + (file-relative-name path)))) + (extension (file-name-extension path)) + (attributes (org-export-read-attribute :attr_texinfo parent)) + (height (or (plist-get attributes :height) "")) + (width (or (plist-get attributes :width) "")) + (alt (or (plist-get attributes :alt) "")) + (image (format "@image{%s,%s,%s,%s,%s}" + filename width height alt extension))) + (cond ((or caption shortcaption) + (org-texinfo--wrap-float image + info + (org-export-translate "Figure" :utf-8 info) + label + caption + shortcaption)) + (label (concat "@anchor{" label "}\n" image)) + (t image)))) + + +;;;; Menu + +(defun org-texinfo-make-menu (scope info &optional master) + "Create the menu for inclusion in the Texinfo document. + +SCOPE is a headline or a full parse tree. INFO is the +communication channel, as a plist. + +When optional argument MASTER is non-nil, generate a master menu, +including detailed node listing." + (let ((menu (org-texinfo--build-menu scope info))) + (when (org-string-nw-p menu) + (org-element-normalize-string + (format + "@menu\n%s@end menu" + (concat menu + (when master + (let ((detailmenu + (org-texinfo--build-menu + scope info + (let ((toc-depth (plist-get info :with-toc))) + (if (wholenump toc-depth) toc-depth + org-texinfo-max-toc-depth))))) + (when (org-string-nw-p detailmenu) + (concat "\n@detailmenu\n" + "--- The Detailed Node Listing ---\n\n" + detailmenu + "@end detailmenu\n")))))))))) + +(defun org-texinfo--build-menu (scope info &optional level) + "Build menu for entries within SCOPE. +SCOPE is a headline or a full parse tree. INFO is a plist +containing contextual information. When optional argument LEVEL +is an integer, build the menu recursively, down to this depth." + (cond + ((not level) + (org-texinfo--format-entries (org-texinfo--menu-entries scope info) info)) + ((zerop level) "\n") + (t + (mapconcat + (lambda (h) + (let ((entries (org-texinfo--menu-entries h info))) + (when entries + (concat + (format "%s\n\n%s\n" + (org-export-data (org-export-get-alt-title h info) info) + (org-texinfo--format-entries entries info)) + (org-texinfo--build-menu h info (1- level)))))) + (org-texinfo--menu-entries scope info) + "")))) + +(defun org-texinfo--format-entries (entries info) + "Format all direct menu entries in SCOPE, as a string. +SCOPE is either a headline or a full Org document. INFO is +a plist containing contextual information." + (org-element-normalize-string + (mapconcat + (lambda (h) + (let* ((title + ;; Colons are used as a separator between title and node + ;; name. Remove them. + (replace-regexp-in-string + "[ \t]*:+" "" + (org-texinfo--sanitize-title + (org-export-get-alt-title h info) info))) + (node (org-texinfo--get-node h info)) + (entry (concat "* " title ":" + (if (string= title node) ":" + (concat " " node ". ")))) + (desc (org-element-property :DESCRIPTION h))) + (if (not desc) entry + (format (format "%%-%ds %%s" org-texinfo-node-description-column) + entry desc)))) + entries "\n"))) + +(defun org-texinfo--menu-entries (scope info) + "List direct children in SCOPE needing a menu entry. +SCOPE is a headline or a full parse tree. INFO is a plist +holding contextual information." + (let* ((cache (or (plist-get info :texinfo-entries-cache) + (plist-get (plist-put info :texinfo-entries-cache + (make-hash-table :test #'eq)) + :texinfo-entries-cache))) + (cached-entries (gethash scope cache 'no-cache))) + (if (not (eq cached-entries 'no-cache)) cached-entries + (let* ((sections (org-texinfo--sectioning-structure info)) + (max-depth (length sections))) + (puthash scope + (cl-remove-if + (lambda (h) + (or (org-not-nil (org-export-get-node-property :COPYING h t)) + (< max-depth (org-export-get-relative-level h info)))) + (org-export-collect-headlines info 1 scope)) + cache))))) + +;;;; Node Property + +(defun org-texinfo-node-property (node-property _contents _info) + "Transcode a NODE-PROPERTY element from Org to Texinfo. +CONTENTS is nil. INFO is a plist holding contextual +information." + (format "%s:%s" + (org-element-property :key node-property) + (let ((value (org-element-property :value node-property))) + (if value (concat " " value) "")))) + +;;;; Paragraph + +(defun org-texinfo-paragraph (_paragraph contents _info) + "Transcode a PARAGRAPH element from Org to Texinfo. +CONTENTS is the contents of the paragraph, as a string. INFO is +the plist used as a communication channel." + contents) + +;;;; Plain List + +(defun org-texinfo-plain-list (plain-list contents info) + "Transcode a PLAIN-LIST element from Org to Texinfo. +CONTENTS is the contents of the list. INFO is a plist holding +contextual information." + (let* ((attr (org-export-read-attribute :attr_texinfo plain-list)) + (indic (let ((i (or (plist-get attr :indic) + (plist-get info :texinfo-table-default-markup)))) + ;; Allow indicating commands with missing @ sign. + (if (string-prefix-p "@" i) i (concat "@" i)))) + (table-type (plist-get attr :table-type)) + (type (org-element-property :type plain-list)) + (enum + (cond ((not (eq type 'ordered)) nil) + ((plist-member attr :enum) (plist-get attr :enum)) + (t + ;; Texinfo only supports initial counters, i.e., it + ;; cannot change the numbering mid-list. + (let ((first-item (car (org-element-contents plain-list)))) + (org-element-property :counter first-item))))) + (list-type (cond + ((eq type 'ordered) "enumerate") + ((eq type 'unordered) "itemize") + ((member table-type '("ftable" "vtable")) table-type) + (t "table")))) + (format "@%s\n%s@end %s" + (cond ((eq type 'descriptive) (concat list-type " " indic)) + (enum (format "%s %s" list-type enum)) + (t list-type)) + contents + list-type))) + +;;;; Plain Text + +(defun org-texinfo-plain-text (text info) + "Transcode a TEXT string from Org to Texinfo. +TEXT is the string to transcode. INFO is a plist holding +contextual information." + ;; First protect @, { and }. + (let ((output (org-texinfo--sanitize-content text))) + ;; Activate smart quotes. Be sure to provide original TEXT string + ;; since OUTPUT may have been modified. + (when (plist-get info :with-smart-quotes) + (setq output + (org-export-activate-smart-quotes output :texinfo info text))) + ;; LaTeX into @LaTeX{} and TeX into @TeX{} + (let ((case-fold-search nil)) + (setq output (replace-regexp-in-string "\\(?:La\\)?TeX" "@\\&{}" output))) + ;; Convert special strings. + (when (plist-get info :with-special-strings) + (setq output + (replace-regexp-in-string + "\\.\\.\\." "@dots{}" + (replace-regexp-in-string "\\\\-" "@-" output)))) + ;; Handle break preservation if required. + (when (plist-get info :preserve-breaks) + (setq output (replace-regexp-in-string + "\\(\\\\\\\\\\)?[ \t]*\n" " @*\n" output))) + ;; Reverse sentence ending. A sentence can end with a capital + ;; letter. Use non-breaking space if it shouldn't. + (let ((case-fold-search nil)) + (replace-regexp-in-string + "[A-Z]\\([.?!]\\)\\(?:[])]\\|'\\{1,2\\}\\)?\\(?: \\|$\\)" + "@\\1" + output nil nil 1)))) + +;;;; Planning + +(defun org-texinfo-planning (planning _contents info) + "Transcode a PLANNING element from Org to Texinfo. +CONTENTS is nil. INFO is a plist holding contextual +information." + (concat + "@noindent" + (mapconcat + 'identity + (delq nil + (list + (let ((closed (org-element-property :closed planning))) + (when closed + (concat + (format "@strong{%s} " org-closed-string) + (format (plist-get info :texinfo-inactive-timestamp-format) + (org-timestamp-translate closed))))) + (let ((deadline (org-element-property :deadline planning))) + (when deadline + (concat + (format "@strong{%s} " org-deadline-string) + (format (plist-get info :texinfo-active-timestamp-format) + (org-timestamp-translate deadline))))) + (let ((scheduled (org-element-property :scheduled planning))) + (when scheduled + (concat + (format "@strong{%s} " org-scheduled-string) + (format (plist-get info :texinfo-active-timestamp-format) + (org-timestamp-translate scheduled))))))) + " ") + "@*")) + +;;;; Property Drawer + +(defun org-texinfo-property-drawer (_property-drawer contents _info) + "Transcode a PROPERTY-DRAWER element from Org to Texinfo. +CONTENTS holds the contents of the drawer. INFO is a plist +holding contextual information." + (and (org-string-nw-p contents) + (format "@verbatim\n%s@end verbatim" contents))) + +;;;; Quote Block + +(defun org-texinfo-quote-block (quote-block contents _info) + "Transcode a QUOTE-BLOCK element from Org to Texinfo. +CONTENTS holds the contents of the block. INFO is a plist +holding contextual information." + (let ((tag (org-export-read-attribute :attr_texinfo quote-block :tag)) + (author (org-export-read-attribute :attr_texinfo quote-block :author))) + (format "@quotation%s\n%s%s\n@end quotation" + (if tag (concat " " tag) "") + contents + (if author (concat "\n@author " author) "")))) + +;;;; Radio Target + +(defun org-texinfo-radio-target (radio-target text info) + "Transcode a RADIO-TARGET object from Org to Texinfo. +TEXT is the text of the target. INFO is a plist holding +contextual information." + (format "@anchor{%s}%s" + (org-texinfo--get-node radio-target info) + text)) + +;;;; Section + +(defun org-texinfo-section (section contents info) + "Transcode a SECTION element from Org to Texinfo. +CONTENTS holds the contents of the section. INFO is a plist +holding contextual information." + (let ((parent (org-export-get-parent-headline section))) + (when parent ;first section is handled in `org-texinfo-template' + (org-trim + (concat contents + "\n" + (and (not (org-export-excluded-from-toc-p parent info)) + (org-texinfo-make-menu parent info))))))) + +;;;; Special Block + +(defun org-texinfo-special-block (special-block contents _info) + "Transcode a SPECIAL-BLOCK element from Org to Texinfo. +CONTENTS holds the contents of the block. INFO is a plist used +as a communication channel." + (let ((opt (org-export-read-attribute :attr_texinfo special-block :options)) + (type (org-element-property :type special-block))) + (format "@%s%s\n%s@end %s" + type + (if opt (concat " " opt) "") + (or contents "") + type))) + +;;;; Src Block + +(defun org-texinfo-src-block (src-block _contents info) + "Transcode a SRC-BLOCK element from Org to Texinfo. +CONTENTS holds the contents of the item. INFO is a plist holding +contextual information." + (let* ((lisp (string-match-p "lisp" + (org-element-property :language src-block))) + (code (org-texinfo--sanitize-content + (org-export-format-code-default src-block info))) + (value (format + (if lisp "@lisp\n%s@end lisp" "@example\n%s@end example") + code)) + (caption (org-export-get-caption src-block)) + (shortcaption (org-export-get-caption src-block t))) + (if (not (or caption shortcaption)) value + (org-texinfo--wrap-float value + info + (org-export-translate "Listing" :utf-8 info) + (org-texinfo--get-node src-block info) + caption + shortcaption)))) + +;;;; Statistics Cookie + +(defun org-texinfo-statistics-cookie (statistics-cookie _contents _info) + "Transcode a STATISTICS-COOKIE object from Org to Texinfo. +CONTENTS is nil. INFO is a plist holding contextual information." + (org-element-property :value statistics-cookie)) + + +;;;; Strike-through + +(defun org-texinfo-strike-through (_strike-through contents info) + "Transcode STRIKE-THROUGH from Org to Texinfo. +CONTENTS is the text with strike-through markup. INFO is a plist +holding contextual information." + (org-texinfo--text-markup contents 'strike-through info)) + +;;;; Subscript + +(defun org-texinfo-subscript (_subscript contents _info) + "Transcode a SUBSCRIPT object from Org to Texinfo. +CONTENTS is the contents of the object. INFO is a plist holding +contextual information." + (format "@math{_%s}" contents)) + +;;;; Superscript + +(defun org-texinfo-superscript (_superscript contents _info) + "Transcode a SUPERSCRIPT object from Org to Texinfo. +CONTENTS is the contents of the object. INFO is a plist holding +contextual information." + (format "@math{^%s}" contents)) + +;;;; Table + +(defun org-texinfo-table (table contents info) + "Transcode a TABLE element from Org to Texinfo. +CONTENTS is the contents of the table. INFO is a plist holding +contextual information." + (if (eq (org-element-property :type table) 'table.el) + (format "@verbatim\n%s@end verbatim" + (org-element-normalize-string + (org-element-property :value table))) + (let* ((col-width (org-export-read-attribute :attr_texinfo table :columns)) + (columns + (if col-width (format "@columnfractions %s" col-width) + (org-texinfo-table-column-widths table info))) + (caption (org-export-get-caption table)) + (shortcaption (org-export-get-caption table t)) + (table-str (format "@multitable %s\n%s@end multitable" + columns + contents))) + (if (not (or caption shortcaption)) table-str + (org-texinfo--wrap-float table-str + info + (org-export-translate "Table" :utf-8 info) + (org-texinfo--get-node table info) + caption + shortcaption))))) + +(defun org-texinfo-table-column-widths (table info) + "Determine the largest table cell in each column to process alignment. +TABLE is the table element to transcode. INFO is a plist used as +a communication channel." + (let ((widths (make-vector (cdr (org-export-table-dimensions table info)) 0))) + (org-element-map table 'table-row + (lambda (row) + (let ((idx 0)) + (org-element-map row 'table-cell + (lambda (cell) + ;; Length of the cell in the original buffer is only an + ;; approximation of the length of the cell in the + ;; output. It can sometimes fail (e.g. it considers + ;; "/a/" being larger than "ab"). + (let ((w (- (org-element-property :contents-end cell) + (org-element-property :contents-begin cell)))) + (aset widths idx (max w (aref widths idx)))) + (cl-incf idx)) + info))) + info) + (format "{%s}" (mapconcat (lambda (w) (make-string w ?a)) widths "} {")))) + +;;;; Table Cell + +(defun org-texinfo-table-cell (table-cell contents info) + "Transcode a TABLE-CELL element from Org to Texinfo. +CONTENTS is the cell contents. INFO is a plist used as +a communication channel." + (concat + (let ((scientific-notation + (plist-get info :texinfo-table-scientific-notation))) + (if (and contents + scientific-notation + (string-match orgtbl-exp-regexp contents)) + ;; Use appropriate format string for scientific notation. + (format scientific-notation + (match-string 1 contents) + (match-string 2 contents)) + contents)) + (when (org-export-get-next-element table-cell info) "\n@tab "))) + +;;;; Table Row + +(defun org-texinfo-table-row (table-row contents info) + "Transcode a TABLE-ROW element from Org to Texinfo. +CONTENTS is the contents of the row. INFO is a plist used as +a communication channel." + ;; Rules are ignored since table separators are deduced from + ;; borders of the current row. + (when (eq (org-element-property :type table-row) 'standard) + (let ((rowgroup-tag + (if (and (= 1 (org-export-table-row-group table-row info)) + (org-export-table-has-header-p + (org-export-get-parent-table table-row) info)) + "@headitem " + "@item "))) + (concat rowgroup-tag contents "\n")))) + +;;;; Target + +(defun org-texinfo-target (target _contents info) + "Transcode a TARGET object from Org to Texinfo. +CONTENTS is nil. INFO is a plist holding contextual +information." + (format "@anchor{%s}" (org-texinfo--get-node target info))) + +;;;; Timestamp + +(defun org-texinfo-timestamp (timestamp _contents info) + "Transcode a TIMESTAMP object from Org to Texinfo. +CONTENTS is nil. INFO is a plist holding contextual +information." + (let ((value (org-texinfo-plain-text + (org-timestamp-translate timestamp) info))) + (pcase (org-element-property :type timestamp) + ((or `active `active-range) + (format (plist-get info :texinfo-active-timestamp-format) value)) + ((or `inactive `inactive-range) + (format (plist-get info :texinfo-inactive-timestamp-format) value)) + (_ (format (plist-get info :texinfo-diary-timestamp-format) value))))) + +;;;; Underline + +(defun org-texinfo-underline (_underline contents info) + "Transcode UNDERLINE from Org to Texinfo. +CONTENTS is the text with underline markup. INFO is a plist +holding contextual information." + (org-texinfo--text-markup contents 'underline info)) + +;;;; Verbatim + +(defun org-texinfo-verbatim (verbatim _contents info) + "Transcode a VERBATIM object from Org to Texinfo. +CONTENTS is nil. INFO is a plist used as a communication +channel." + (org-texinfo--text-markup + (org-element-property :value verbatim) 'verbatim info)) + +;;;; Verse Block + +(defun org-texinfo-verse-block (_verse-block contents _info) + "Transcode a VERSE-BLOCK element from Org to Texinfo. +CONTENTS is verse block contents. INFO is a plist holding +contextual information." + (format "@display\n%s@end display" contents)) + + +;;; Interactive functions + +;;;###autoload +(defun org-texinfo-export-to-texinfo + (&optional async subtreep visible-only body-only ext-plist) + "Export current buffer to a Texinfo file. + +If narrowing is active in the current buffer, only export its +narrowed part. + +If a region is active, export that region. + +A non-nil optional argument ASYNC means the process should happen +asynchronously. The resulting file should be accessible through +the `org-export-stack' interface. + +When optional argument SUBTREEP is non-nil, export the sub-tree +at point, extracting information from the headline properties +first. + +When optional argument VISIBLE-ONLY is non-nil, don't export +contents of hidden elements. + +When optional argument BODY-ONLY is non-nil, only write code +between \"\\begin{document}\" and \"\\end{document}\". + +EXT-PLIST, when provided, is a property list with external +parameters overriding Org default settings, but still inferior to +file-local settings. + +Return output file's name." + (interactive) + (let ((outfile (org-export-output-file-name ".texi" subtreep)) + (org-export-coding-system org-texinfo-coding-system)) + (org-export-to-file 'texinfo outfile + async subtreep visible-only body-only ext-plist))) + +(defun org-texinfo-export-to-texinfo-batch () + "Export Org file INFILE to Texinfo file OUTFILE, in batch mode. +Overwrites existing output file. +Usage: emacs -batch -f org-texinfo-export-to-texinfo-batch INFILE OUTFILE" + (or noninteractive (user-error "Batch mode use only")) + (let ((infile (pop command-line-args-left)) + (outfile (pop command-line-args-left)) + (org-export-coding-system org-texinfo-coding-system) + (make-backup-files nil)) + (unless (file-readable-p infile) + (message "File `%s' not readable" infile) + (kill-emacs 1)) + (with-temp-buffer + (insert-file-contents infile) + (org-export-to-file 'texinfo outfile)))) + +;;;###autoload +(defun org-texinfo-export-to-info + (&optional async subtreep visible-only body-only ext-plist) + "Export current buffer to Texinfo then process through to INFO. + +If narrowing is active in the current buffer, only export its +narrowed part. + +If a region is active, export that region. + +A non-nil optional argument ASYNC means the process should happen +asynchronously. The resulting file should be accessible through +the `org-export-stack' interface. + +When optional argument SUBTREEP is non-nil, export the sub-tree +at point, extracting information from the headline properties +first. + +When optional argument VISIBLE-ONLY is non-nil, don't export +contents of hidden elements. + +When optional argument BODY-ONLY is non-nil, only write code +between \"\\begin{document}\" and \"\\end{document}\". + +EXT-PLIST, when provided, is a property list with external +parameters overriding Org default settings, but still inferior to +file-local settings. + +When optional argument PUB-DIR is set, use it as the publishing +directory. + +Return INFO file's name." + (interactive) + (let ((outfile (org-export-output-file-name ".texi" subtreep)) + (org-export-coding-system org-texinfo-coding-system)) + (org-export-to-file 'texinfo outfile + async subtreep visible-only body-only ext-plist + #'org-texinfo-compile))) + +;;;###autoload +(defun org-texinfo-publish-to-texinfo (plist filename pub-dir) + "Publish an org file to Texinfo. + +FILENAME is the filename of the Org file to be published. PLIST +is the property list for the given project. PUB-DIR is the +publishing directory. + +Return output file name." + (org-publish-org-to 'texinfo filename ".texi" plist pub-dir)) + +;;;###autoload +(defun org-texinfo-convert-region-to-texinfo () + "Assume the current region has Org syntax, and convert it to Texinfo. +This can be used in any buffer. For example, you can write an +itemized list in Org syntax in an Texinfo buffer and use this +command to convert it." + (interactive) + (org-export-replace-region-by 'texinfo)) + +(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', +which see. Output is redirected to \"*Org INFO Texinfo Output*\" +buffer. + +Return INFO file name or an error if it couldn't be produced." + (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) + +;; Local variables: +;; generated-autoload-file: "org-loaddefs.el" +;; End: + +;;; ox-texinfo.el ends here |