Merge branch 'maint'

Conflicts:
	doc/org.texi
	lisp/ox-texinfo.el
This commit is contained in:
Nicolas Goaziou 2014-08-11 15:00:57 +02:00
commit 6400c9a8e5
2 changed files with 183 additions and 321 deletions

View file

@ -13098,6 +13098,12 @@ Texinfo output.
As an exception, a headline with a non-nil @code{:APPENDIX:} property becomes
an appendix, independently on its level and the class used.
@cindex property, DESCRIPTION
Each regular sectioning structure creates a menu entry, named after the
heading. You can provide a different, e.g., shorter, title in
@code{:ALT_TITLE:} property (@pxref{Table of contents}). Optionally, you can
specify a description for the item in @code{:DESCRIPTION:} property.
@node Indices
@subsection Indices

View file

@ -82,6 +82,7 @@
:export-block "TEXINFO"
: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))
:menu-entry
'(?i "Export to Texinfo"
@ -340,12 +341,11 @@ The function should return the string to be exported."
;;;; Compilation
(defcustom org-texinfo-info-process
'("makeinfo %f")
(defcustom org-texinfo-info-process '("makeinfo %f")
"Commands to process a Texinfo file to an INFO file.
This is list of strings, each of them will be given to the shell
as a command. %f in the command will be replaced by the full
file name, %b by the file base name \(i.e without extension) and
file name, %b by the file base name (i.e without extension) and
%o by the base directory of the file."
:group 'org-export-texinfo
:type '(repeat :tag "Shell command sequence"
@ -370,9 +370,10 @@ set `org-texinfo-logfiles-extensions'."
;;; 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.")
"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")
@ -389,6 +390,31 @@ If two strings share the same prefix (e.g. \"ISO-8859-1\" and
(let ((blanks (make-string 2 ?\n)))
(replace-regexp-in-string "\n\\(?:\n[ \t]*\\)*\\'" blanks headline)))
(defun org-texinfo--normalize-headlines (tree back-end 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 beginning and the end of each section.
Return new tree."
(org-element-map tree 'headline
(lambda (hl)
(org-element-put-property hl :pre-blank 1)
(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)
(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."
@ -397,18 +423,6 @@ This is used to choose a separator for constructs like \\verb."
when (not (string-match (regexp-quote (char-to-string c)) s))
return (char-to-string c))))
(defun org-texinfo--make-option-string (options)
"Return a comma separated string of keywords and values.
OPTIONS is an alist where the key is the options keyword as
a string, and the value a list containing the keyword value, or
nil."
(mapconcat (lambda (pair)
(concat (first pair)
(when (> (length (second pair)) 0)
(concat "=" (second pair)))))
options
","))
(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
@ -439,200 +453,39 @@ INFO is a plist used as a communication channel. See
(defun org-texinfo--get-node (headline info)
"Return node entry associated to HEADLINE.
INFO is a plist used as a communication channel."
(let ((menu-title (org-export-get-alt-title headline info)))
(org-texinfo--sanitize-menu
(replace-regexp-in-string
"%" "%%"
(if menu-title (org-export-data menu-title info)
(org-texinfo--sanitize-headline
(org-element-property :title headline) info))))))
;;;; Headline sanitizing
(defun org-texinfo--sanitize-headline (headline info)
"Remove all formatting from the text of a headline for use in
node and menu listing."
(mapconcat 'identity
(org-texinfo--sanitize-headline-contents headline info) " "))
(defun org-texinfo--sanitize-headline-contents (headline info)
"Retrieve the content of the headline.
Any content that can contain further formatting is checked
recursively, to ensure that nested content is also properly
retrieved."
(loop for contents in headline append
(cond
;; already a string
((stringp contents)
(list (replace-regexp-in-string " $" "" contents)))
;; Is exported as-is (value)
((org-element-map contents '(verbatim code)
(lambda (value) (org-element-property :value value)) info))
;; Has content and recurse into the content
((org-element-contents contents)
(org-texinfo--sanitize-headline-contents
(org-element-contents contents) info)))))
INFO is a plist used as a communication channel. The function
guarantees the node name is unique."
(let ((cache (plist-get info :texinfo-node-cache)))
(or (cdr (assq headline cache))
(let ((name (org-texinfo--sanitize-node
(org-export-data
(org-export-get-alt-title headline info) info))))
;; Ensure NAME is unique.
(while (rassoc name cache) (setq name (concat name "x")))
(plist-put info :texinfo-node-cache (cons (cons headline name) cache))
name))))
;;;; Menu sanitizing
(defun org-texinfo--sanitize-menu (title)
"Remove invalid characters from TITLE for use in menus and
nodes.
Based on Texinfo specifications, the following must be removed:
@ { } ( ) : . ,"
(replace-regexp-in-string "[@{}():,.]" "" title))
(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. Also remove the following characters: @
{ } ( ) : . ,"
(org-trim
(replace-regexp-in-string
"[:,.]" ""
(replace-regexp-in-string
"\\`(\\(.*)\\)" "[\\1"
(replace-regexp-in-string "[ \t]\\{2,\\}" " " title)))))
;;;; Content sanitizing
(defun org-texinfo--sanitize-content (text)
"Ensure characters are properly escaped when used in headlines or blocks.
Escape characters are: @ { }"
"Escape special characters in string TEXT.
Special characters are: @ { }"
(replace-regexp-in-string "\\\([@{}]\\\)" "@\\1" text))
;;;; Menu creation
(defun org-texinfo--build-menu (tree level info &optional detailed)
"Create the @menu/@end menu information from TREE at headline
level LEVEL.
TREE contains the parse-tree to work with, either of the entire
document or of a specific parent headline. LEVEL indicates what
level of headlines to look at when generating the menu. INFO is
a plist containing contextual information.
Detailed determines whether to build a single level of menu, or
recurse into all children as well."
(let ((menu (org-texinfo--generate-menu-list tree level info))
output text-menu)
(cond
(detailed
;; Looping is done within the menu generation.
(setq text-menu (org-texinfo--generate-detailed menu level info)))
(t
(setq text-menu (org-texinfo--generate-menu-items menu info))))
(when text-menu
(setq output (org-texinfo--format-menu text-menu info))
(mapconcat 'identity output "\n"))))
(defun org-texinfo--generate-detailed (menu level info)
"Generate a detailed listing of all subheadings within MENU starting at LEVEL.
MENU is the parse-tree to work with. LEVEL is the starting level
for the menu headlines and from which recursion occurs. INFO is
a plist containing contextual information."
(when level
(let ((max-depth (min org-texinfo-max-toc-depth
(plist-get info :headline-levels))))
(when (> max-depth level)
(loop for headline in menu append
(let* ((title (org-texinfo--menu-headlines headline info))
;; Create list of menu entries for the next level
(sublist (org-texinfo--generate-menu-list
headline (1+ level) info))
;; Generate the menu items for that level. If
;; there are none omit that heading completely,
;; otherwise join the title to it's related entries.
(submenu (if (org-texinfo--generate-menu-items sublist info)
(append (list title)
(org-texinfo--generate-menu-items sublist info))
'nil))
;; Start the process over the next level down.
(recursion (org-texinfo--generate-detailed sublist (1+ level) info)))
(setq recursion (append submenu recursion))
recursion))))))
(defun org-texinfo--generate-menu-list (tree level info)
"Generate the list of headlines that are within a given level
of the tree for further formatting.
TREE is the parse-tree containing the headlines. LEVEL is the
headline level to generate a list of. INFO is a plist holding
contextual information."
(org-element-map tree 'headline
(lambda (head)
(and (= (org-export-get-relative-level head info) level)
;; Do not take note of footnotes or copying headlines.
(not (org-not-nil (org-element-property :COPYING head)))
(not (org-element-property :footnote-section-p head))
;; Collect headline.
head))
info))
(defun org-texinfo--generate-menu-items (items info)
"Generate a list of headline information from the listing ITEMS.
ITEMS is a list of the headlines to be converted into entries.
INFO is a plist containing contextual information.
Returns a list containing the following information from each
headline: length, title, description. This is used to format the
menu using `org-texinfo--format-menu'."
(loop for headline in items collect
(let* ((menu-title (org-texinfo--sanitize-menu
(org-export-data
(org-export-get-alt-title headline info)
info)))
(title (org-texinfo--sanitize-menu
(org-texinfo--sanitize-headline
(org-element-property :title headline) info)))
(descr (org-export-data
(org-element-property :DESCRIPTION headline)
info))
(menu-entry (if (string= "" menu-title) title menu-title))
(len (length menu-entry))
(output (list len menu-entry descr)))
output)))
(defun org-texinfo--menu-headlines (headline info)
"Retrieve the title from HEADLINE.
INFO is a plist holding contextual information.
Return the headline as a list of (length title description) with
length of -1 and nil description. This is used in
`org-texinfo--format-menu' to identify headlines as opposed to
entries."
(let ((title (org-export-data
(org-element-property :title headline) info)))
(list -1 title 'nil)))
(defun org-texinfo--format-menu (text-menu info)
"Format the TEXT-MENU items to be properly printed in the menu.
INFO is a plist containing contextual information.
Each entry in the menu should be provided as (length title
description).
Headlines in the detailed menu are given length -1 to ensure they
are never confused with other entries. They also have no
description.
Other menu items are output as:
Title:: description
With the spacing between :: and description based on the length
of the longest menu entry."
(mapcar
(lambda (name)
(let* ((title (nth 1 name))
(desc (nth 2 name))
(length (nth 0 name))
(column (max
length
;; 6 is "* " ":: " for inserted text
(- (plist-get info :texinfo-node-description-column) 6)))
(spacing (- column length)))
(if (> length -1)
(concat "* " title ":: "
(make-string spacing ?\s)
(and desc (concat desc)))
(concat "\n" title "\n"))))
text-menu))
;;; Template
(defun org-texinfo-template (contents info)
@ -744,17 +597,8 @@ holding export options."
(and copying "@insertcopying\n")
"@end ifnottex\n\n"
;; Menu.
(let ((menu (org-texinfo-make-menu info 'main))
(detail-menu (org-texinfo-make-menu info 'detailed)))
(and menu
(concat "@menu\n"
menu "\n"
(and detail-menu
(concat "\n@detailmenu\n"
" --- The Detailed Node Listing ---\n"
detail-menu "\n"
"@end detailmenu\n"))
"@end menu\n\n")))
(org-texinfo-make-menu (plist-get info :parse-tree) info 'master)
"\n"
;; Document's body.
contents "\n"
;; Creator.
@ -886,41 +730,16 @@ holding contextual information."
(level (org-export-get-relative-level headline info))
(numberedp (org-export-numbered-headline-p headline info))
(class-sectioning (assoc class (plist-get info :texinfo-classes)))
;; Find the index type, if any
;; Find the index type, if any.
(index (org-element-property :INDEX headline))
;; Retrieve headline text
(text (org-texinfo--sanitize-headline
(org-element-property :title headline) info))
;; Create node info, to insert it before section formatting.
;; Use custom menu title if present
;; Use custom menu title if present.
(node (format "@node %s\n" (org-texinfo--get-node headline info)))
;; Menus must be generated with first child, otherwise they
;; will not nest properly
(menu (let* ((first (org-export-first-sibling-p headline info))
(parent (org-export-get-parent-headline headline))
(title (org-texinfo--sanitize-headline
(org-element-property :title parent) info))
heading listing
(tree (plist-get info :parse-tree)))
(if first
(org-element-map (plist-get info :parse-tree) 'headline
(lambda (ref)
(if (member title (org-element-property :title ref))
(push ref heading)))
info t))
(setq listing (org-texinfo--build-menu
(car heading) level info))
(if listing
(setq listing (replace-regexp-in-string
"%" "%%" listing)
listing (format
"\n@menu\n%s\n@end menu\n\n" listing))
'nil)))
;; Section formatting will set two placeholders: one for the
;; title and the other for the contents.
(section-fmt
(if (org-not-nil (org-element-property :APPENDIX headline))
(concat menu node "appendix\n%s")
"@appendix %s\n%s"
(let ((sec (if (and (symbolp (nth 2 class-sectioning))
(fboundp (nth 2 class-sectioning)))
(funcall (nth 2 class-sectioning) level numberedp)
@ -932,10 +751,7 @@ holding contextual information."
((stringp sec) sec)
;; (numbered-section . unnumbered-section)
((not (consp (cdr sec)))
(concat menu
node
;; An index is always unnumbered.
(if (or index (not numberedp)) (cdr sec) (car sec))
(concat (if (or index (not numberedp)) (cdr sec) (car sec))
"\n%s"))))))
(todo
(and (plist-get info :with-todo-keywords)
@ -946,17 +762,15 @@ holding contextual information."
(org-export-get-tags headline info)))
(priority (and (plist-get info :with-priority)
(org-element-property :priority headline)))
;; Retrieve headline text for structuring command.
(text (org-export-data (org-element-property :title headline) info))
;; Create the headline text along with a no-tag version. The
;; latter is required to remove tags from table of contents.
(full-text (org-texinfo--sanitize-content
(funcall (plist-get info :texinfo-format-headline-function)
todo todo-type priority text tags)))
(full-text-no-tag
(org-texinfo--sanitize-content
(funcall (plist-get info :texinfo-format-headline-function)
todo todo-type priority text nil)))
(pre-blanks
(make-string (org-element-property :pre-blank headline) 10)))
(make-string (org-element-property :pre-blank headline) ?\n)))
(cond
;; Case 1: This is a footnote section: ignore it.
((org-element-property :footnote-section-p headline) nil)
@ -967,11 +781,13 @@ holding contextual information."
;; print it as such following the contents, otherwise
;; print the contents and leave the index up to the user.
(index
(format
section-fmt full-text
(concat pre-blanks contents "\n"
(if (member index '("cp" "fn" "ky" "pg" "tp" "vr"))
(concat "@printindex " index)))))
(concat node
(format
section-fmt
full-text
(concat pre-blanks contents (and (org-string-nw-p contents) "\n")
(if (member index '("cp" "fn" "ky" "pg" "tp" "vr"))
(concat "@printindex " index))))))
;; Case 4: This is a deep sub-tree: export it as a list item.
;; Also export as items headlines for which no section
;; format has been found.
@ -994,32 +810,8 @@ holding contextual information."
low-level-body))))
;; Case 5: Standard headline. Export it as a section.
(t
(cond
((not (and tags (eq (plist-get info :with-tags) 'not-in-toc)))
;; Regular section. Use specified format string.
(format (replace-regexp-in-string "%]" "%%]" section-fmt) full-text
(concat pre-blanks contents)))
((string-match "\\`@\\(.*?\\){" section-fmt)
;; If tags should be removed from table of contents, insert
;; title without tags as an alternative heading in sectioning
;; command.
(format (replace-match (concat (match-string 1 section-fmt) "[%s]")
nil nil section-fmt 1)
;; Replace square brackets with parenthesis since
;; square brackets are not supported in optional
;; arguments.
(replace-regexp-in-string
"\\[" "("
(replace-regexp-in-string
"\\]" ")"
full-text-no-tag))
full-text
(concat pre-blanks contents)))
(t
;; Impossible to add an alternative heading. Fallback to
;; regular sectioning format string.
(format (replace-regexp-in-string "%]" "%%]" section-fmt) full-text
(concat pre-blanks contents))))))))
(concat node
(format section-fmt full-text (concat pre-blanks contents)))))))
(defun org-texinfo-format-headline-default-function
(todo todo-type priority text tags)
@ -1189,23 +981,93 @@ INFO is a plist holding contextual information. See
;;;; Menu
(defun org-texinfo-make-menu (info level)
"Create the menu for inclusion in the texifo document.
(defun org-texinfo-make-menu (scope info &optional master)
"Create the menu for inclusion in the Texinfo document.
INFO is the parsed buffer that contains the headlines. LEVEL
determines whether to make the main menu, or the detailed menu.
SCOPE is a headline or a full parse tree. INFO is the
communication channel, as a plist.
This is only used for generating the primary menu. In-Node menus
are generated directly."
(let ((parse (plist-get info :parse-tree)))
(cond
;; Generate the main menu
((eq level 'main) (org-texinfo--build-menu parse 1 info))
;; Generate the detailed (recursive) menu
((eq level 'detailed)
;; Requires recursion
;;(org-texinfo--build-detailed-menu parse top info)
(org-texinfo--build-menu parse 1 info 'detailed)))))
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) nil)
(t
(org-element-normalize-string
(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 (org-export-data
(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
(puthash scope
(org-element-map (org-element-contents scope) 'headline
(lambda (h)
(and (not (org-not-nil (org-element-property :COPYING h)))
(not (org-element-property :footnote-section-p h))
(not (org-export-low-level-p h info))
h))
info nil 'headline)
cache))))
;;;; Node Property
@ -1352,7 +1214,9 @@ contextual information."
"Transcode a SECTION element from Org to Texinfo.
CONTENTS holds the contents of the section. INFO is a plist
holding contextual information."
contents)
(concat contents
(let ((parent (org-export-get-parent-headline section)))
(and parent (org-texinfo-make-menu parent info)))))
;;;; Special Block
@ -1636,29 +1500,21 @@ Return INFO file name or an error if it couldn't be produced."
errors)
(message (format "Processing Texinfo file %s..." file))
(save-window-excursion
(cond
;; A function is provided: Apply it.
((functionp org-texinfo-info-process)
(funcall org-texinfo-info-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 INFO Texinfo Output*" buffer.
((consp org-texinfo-info-process)
(let ((outbuf (get-buffer-create "*Org INFO Texinfo Output*")))
(mapc
(lambda (command)
(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))
org-texinfo-info-process)
;; Collect standard errors from output buffer.
(setq errors (org-texinfo-collect-errors outbuf))))
(t (error "No valid command to process to Info")))
;; 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*")))
(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.