Browse Source

Merge branch 'maint'

Conflicts:
	doc/org.texi
	lisp/ox-texinfo.el
Nicolas Goaziou 10 years ago
parent
commit
6400c9a8e5
2 changed files with 185 additions and 323 deletions
  1. 6 0
      doc/org.texi
  2. 179 323
      lisp/ox-texinfo.el

+ 6 - 0
doc/org.texi

@@ -13098,6 +13098,12 @@ Texinfo output.
 As an exception, a headline with a non-nil @code{:APPENDIX:} property becomes
 As an exception, a headline with a non-nil @code{:APPENDIX:} property becomes
 an appendix, independently on its level and the class used.
 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
 @node Indices
 @subsection Indices
 @subsection Indices
 
 

+ 179 - 323
lisp/ox-texinfo.el

@@ -82,6 +82,7 @@
   :export-block "TEXINFO"
   :export-block "TEXINFO"
   :filters-alist
   :filters-alist
   '((:filter-headline . org-texinfo-filter-section-blank-lines)
   '((:filter-headline . org-texinfo-filter-section-blank-lines)
+    (:filter-parse-tree . org-texinfo--normalize-headlines)
     (:filter-section . org-texinfo-filter-section-blank-lines))
     (:filter-section . org-texinfo-filter-section-blank-lines))
   :menu-entry
   :menu-entry
   '(?i "Export to Texinfo"
   '(?i "Export to Texinfo"
@@ -340,12 +341,11 @@ The function should return the string to be exported."
 
 
 ;;;; Compilation
 ;;;; 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.
   "Commands to process a Texinfo file to an INFO file.
 This is list of strings, each of them will be given to the shell
 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
 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."
 %o by the base directory of the file."
   :group 'org-export-texinfo
   :group 'org-export-texinfo
   :type '(repeat :tag "Shell command sequence"
   :type '(repeat :tag "Shell command sequence"
@@ -370,9 +370,10 @@ set `org-texinfo-logfiles-extensions'."
 ;;; Constants
 ;;; Constants
 
 
 (defconst org-texinfo-max-toc-depth 4
 (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
 (defconst org-texinfo-supported-coding-systems
   '("US-ASCII" "UTF-8" "ISO-8859-15" "ISO-8859-1" "ISO-8859-2" "koi8-r" "koi8-u")
   '("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)))
   (let ((blanks (make-string 2 ?\n)))
     (replace-regexp-in-string "\n\\(?:\n[ \t]*\\)*\\'" blanks headline)))
     (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)
 (defun org-texinfo--find-verb-separator (s)
   "Return a character not used in string S.
   "Return a character not used in string S.
 This is used to choose a separator for constructs like \\verb."
 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))
 	  when (not (string-match (regexp-quote (char-to-string c)) s))
 	  return (char-to-string c))))
 	  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)
 (defun org-texinfo--text-markup (text markup info)
   "Format TEXT depending on MARKUP text markup.
   "Format TEXT depending on MARKUP text markup.
 INFO is a plist used as a communication channel.  See
 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)
 (defun org-texinfo--get-node (headline info)
   "Return node entry associated to HEADLINE.
   "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
 ;;;; 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
 ;;;; Content sanitizing
 
 
 (defun org-texinfo--sanitize-content (text)
 (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))
   (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
 ;;; Template
 
 
 (defun org-texinfo-template (contents info)
 (defun org-texinfo-template (contents info)
@@ -744,17 +597,8 @@ holding export options."
      (and copying "@insertcopying\n")
      (and copying "@insertcopying\n")
      "@end ifnottex\n\n"
      "@end ifnottex\n\n"
      ;; Menu.
      ;; 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.
      ;; Document's body.
      contents "\n"
      contents "\n"
      ;; Creator.
      ;; Creator.
@@ -886,41 +730,16 @@ holding contextual information."
 	 (level (org-export-get-relative-level headline info))
 	 (level (org-export-get-relative-level headline info))
 	 (numberedp (org-export-numbered-headline-p headline info))
 	 (numberedp (org-export-numbered-headline-p headline info))
 	 (class-sectioning (assoc class (plist-get info :texinfo-classes)))
 	 (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))
 	 (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.
 	 ;; 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)))
 	 (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
 	 ;; Section formatting will set two placeholders: one for the
 	 ;; title and the other for the contents.
 	 ;; title and the other for the contents.
 	 (section-fmt
 	 (section-fmt
 	  (if (org-not-nil (org-element-property :APPENDIX headline))
 	  (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))
 	    (let ((sec (if (and (symbolp (nth 2 class-sectioning))
 				(fboundp (nth 2 class-sectioning)))
 				(fboundp (nth 2 class-sectioning)))
 			   (funcall (nth 2 class-sectioning) level numberedp)
 			   (funcall (nth 2 class-sectioning) level numberedp)
@@ -932,10 +751,7 @@ holding contextual information."
 	       ((stringp sec) sec)
 	       ((stringp sec) sec)
 	       ;; (numbered-section . unnumbered-section)
 	       ;; (numbered-section . unnumbered-section)
 	       ((not (consp (cdr sec)))
 	       ((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"))))))
 			"\n%s"))))))
 	 (todo
 	 (todo
 	  (and (plist-get info :with-todo-keywords)
 	  (and (plist-get info :with-todo-keywords)
@@ -946,17 +762,15 @@ holding contextual information."
 		    (org-export-get-tags headline info)))
 		    (org-export-get-tags headline info)))
 	 (priority (and (plist-get info :with-priority)
 	 (priority (and (plist-get info :with-priority)
 			(org-element-property :priority headline)))
 			(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
 	 ;; Create the headline text along with a no-tag version.  The
 	 ;; latter is required to remove tags from table of contents.
 	 ;; latter is required to remove tags from table of contents.
 	 (full-text (org-texinfo--sanitize-content
 	 (full-text (org-texinfo--sanitize-content
 		     (funcall (plist-get info :texinfo-format-headline-function)
 		     (funcall (plist-get info :texinfo-format-headline-function)
 			      todo todo-type priority text tags)))
 			      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
 	 (pre-blanks
-	  (make-string (org-element-property :pre-blank headline) 10)))
+	  (make-string (org-element-property :pre-blank headline) ?\n)))
     (cond
     (cond
      ;; Case 1: This is a footnote section: ignore it.
      ;; Case 1: This is a footnote section: ignore it.
      ((org-element-property :footnote-section-p headline) nil)
      ((org-element-property :footnote-section-p headline) nil)
@@ -967,11 +781,13 @@ holding contextual information."
      ;;         print it as such following the contents, otherwise
      ;;         print it as such following the contents, otherwise
      ;;         print the contents and leave the index up to the user.
      ;;         print the contents and leave the index up to the user.
      (index
      (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.
      ;; Case 4: This is a deep sub-tree: export it as a list item.
      ;;         Also export as items headlines for which no section
      ;;         Also export as items headlines for which no section
      ;;         format has been found.
      ;;         format has been found.
@@ -994,32 +810,8 @@ holding contextual information."
 	   low-level-body))))
 	   low-level-body))))
      ;; Case 5: Standard headline.  Export it as a section.
      ;; Case 5: Standard headline.  Export it as a section.
      (t
      (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
 (defun org-texinfo-format-headline-default-function
   (todo todo-type priority text tags)
   (todo todo-type priority text tags)
@@ -1189,23 +981,93 @@ INFO is a plist holding contextual information.  See
 
 
 ;;;; Menu
 ;;;; Menu
 
 
-(defun org-texinfo-make-menu (info level)
-  "Create the menu for inclusion in the texifo document.
-
-INFO is the parsed buffer that contains the headlines.  LEVEL
-determines whether to make the main menu, or the detailed menu.
-
-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)))))
+(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) 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
 ;;;; Node Property
 
 
@@ -1352,7 +1214,9 @@ contextual information."
   "Transcode a SECTION element from Org to Texinfo.
   "Transcode a SECTION element from Org to Texinfo.
 CONTENTS holds the contents of the section.  INFO is a plist
 CONTENTS holds the contents of the section.  INFO is a plist
 holding contextual information."
 holding contextual information."
-  contents)
+  (concat contents
+	  (let ((parent (org-export-get-parent-headline section)))
+	    (and parent (org-texinfo-make-menu parent info)))))
 
 
 ;;;; Special Block
 ;;;; Special Block
 
 
@@ -1636,29 +1500,21 @@ Return INFO file name or an error if it couldn't be produced."
 	 errors)
 	 errors)
     (message (format "Processing Texinfo file %s..." file))
     (message (format "Processing Texinfo file %s..." file))
     (save-window-excursion
     (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")))
       (let ((infofile (concat out-dir base-name ".info")))
 	;; Check for process failure.  Provide collected errors if
 	;; Check for process failure.  Provide collected errors if
 	;; possible.
 	;; possible.