1744 lines
61 KiB
EmacsLisp
1744 lines
61 KiB
EmacsLisp
![]() |
;;; ox-rst.el --- Export reStructuredText using org-mode. -*- lexical-binding: t; -*-
|
|||
|
|
|||
|
;; Copyright (C) 2015-2019 Masanao Igarashi
|
|||
|
|
|||
|
;; This program 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 2 of the License, or
|
|||
|
;; (at your option) any later version.
|
|||
|
|
|||
|
;; This program 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 this program; if not, write to the Free Software
|
|||
|
;; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|||
|
|
|||
|
;; Author: Masanao Igarashi <syoux2@gmail.com>
|
|||
|
;; Keywords: org, rst, reST, reStructuredText
|
|||
|
;; Package-Version: 20191013.551
|
|||
|
;; Version: 0.3
|
|||
|
;; URL: https://github.com/msnoigrs/ox-rst
|
|||
|
;; Package-Requires: ((emacs "25.1") (org "8.3"))
|
|||
|
|
|||
|
;;; Commentary:
|
|||
|
;; This library implements an reStructuredText back-end for
|
|||
|
;; Org generic exporter.
|
|||
|
|
|||
|
;;; Code:
|
|||
|
|
|||
|
(require 'cl-lib)
|
|||
|
(require 'ox)
|
|||
|
(require 'ox-publish)
|
|||
|
|
|||
|
|
|||
|
;;; Define Back-End
|
|||
|
(org-export-define-backend 'rst
|
|||
|
'((bold . org-rst-bold)
|
|||
|
(center-block . org-rst-center-block)
|
|||
|
(clock . org-rst-clock)
|
|||
|
(code . org-rst-code)
|
|||
|
(drawer . org-rst-drawer)
|
|||
|
(dynamic-block . org-rst-dynamic-block)
|
|||
|
(entity . org-rst-entity)
|
|||
|
(example-block . org-rst-example-block)
|
|||
|
(export-block . org-rst-export-block)
|
|||
|
(export-snippet . org-rst-export-snippet)
|
|||
|
(fixed-width . org-rst-example-block)
|
|||
|
(footnote-reference . org-rst-footnote-reference)
|
|||
|
(headline . org-rst-headline)
|
|||
|
(horizontal-rule . org-rst-horizontal-rule)
|
|||
|
(inline-src-block . org-rst-inline-src-block)
|
|||
|
(inlinetask . org-rst-inlinetask)
|
|||
|
(inner-template . org-rst-inner-template)
|
|||
|
(italic . org-rst-italic)
|
|||
|
(item . org-rst-item)
|
|||
|
(keyword . org-rst-keyword)
|
|||
|
(latex-environment . org-rst-latex-environment)
|
|||
|
(latex-fragment . org-rst-latex-fragment)
|
|||
|
(line-break . org-rst-line-break)
|
|||
|
(link . org-rst-link)
|
|||
|
(node-property . org-rst-node-property)
|
|||
|
(paragraph . org-rst-paragraph)
|
|||
|
(plain-list . org-rst-plain-list)
|
|||
|
(plain-text . org-rst-plain-text)
|
|||
|
(planning . org-rst-planning)
|
|||
|
(property-drawer . org-rst-property-drawer)
|
|||
|
(quote-block . org-rst-quote-block)
|
|||
|
(radio-target . org-rst-radio-target)
|
|||
|
(section . org-rst-section)
|
|||
|
(special-block . org-rst-special-block)
|
|||
|
(src-block . org-rst-src-block)
|
|||
|
(statistics-cookie . org-rst-statistics-cookie)
|
|||
|
(strike-through . org-rst-strike-through)
|
|||
|
(subscript . org-rst-subscript)
|
|||
|
(superscript . org-rst-superscript)
|
|||
|
(table . org-rst-table)
|
|||
|
(table-cell . org-rst-table-cell)
|
|||
|
(table-row . org-rst-table-row)
|
|||
|
(target . org-rst-target)
|
|||
|
(template . org-rst-template)
|
|||
|
(timestamp . org-rst-timestamp)
|
|||
|
(underline . org-rst-underline)
|
|||
|
(verbatim . org-rst-verbatim)
|
|||
|
(verse-block . org-rst-verse-block)
|
|||
|
;; Pseudo objects and elements.
|
|||
|
(latex-math-block . org-rst-math-block))
|
|||
|
:menu-entry
|
|||
|
'(?r "Export to reStructuredText"
|
|||
|
((?R "As reStructuredText buffer" org-rst-export-as-rst)
|
|||
|
(?r "As reStructuredText file" org-rst-export-to-rst)))
|
|||
|
:options-alist
|
|||
|
'((:subtitle "SUBTITLE" nil nil parse)
|
|||
|
(:rst-link-use-abs-url nil "rst-link-use-abs-url" org-rst-link-use-abs-url)
|
|||
|
(:rst-inline-images nil nil org-rst-inline-images)
|
|||
|
(:rst-inline-image-rules nil nil org-rst-inline-image-rules)
|
|||
|
(:rst-link-org-files-as-rst nil nil org-rst-link-org-files-as-rst)
|
|||
|
(:rst-link-home "RST_LINK_HOME" nil org-rst-link-home)
|
|||
|
(:rst-link-use-ref-role nil nil org-rst-link-use-ref-role)
|
|||
|
(:rst-extension nil nil org-rst-extension)
|
|||
|
(:rst-file-link-use-ref-role nil nil org-rst-file-link-use-ref-role)
|
|||
|
(:rst-text-markup-alist nil nil org-rst-text-markup-alist)
|
|||
|
(:rst-quote-margin nil nil org-rst-quote-margin)
|
|||
|
(:rst-headline-underline-characters nil nil org-rst-headline-underline-characters)
|
|||
|
(:rst-headline-spacing nil nil org-rst-headline-spacing)
|
|||
|
(:rst-paragraph-spacing nil nil org-rst-paragraph-spacing)
|
|||
|
(:rst-format-drawer-function nil nil org-rst-format-drawer-function)
|
|||
|
(:rst-format-inlinetask-function nil nil org-rst-format-inlinetask-function)
|
|||
|
(:rst-code-block nil nil org-rst-code-block)
|
|||
|
(:rst-pygments-langs nil nil org-rst-pygments-langs))
|
|||
|
:filters-alist '((:filter-options . org-rst-math-block-options-filter)
|
|||
|
(:filter-headline . org-rst-filter-headline-blank-lines)
|
|||
|
(:filter-parse-tree org-rst-math-block-tree-filter
|
|||
|
org-rst-separate-elements
|
|||
|
org-rst-filter-paragraph-spacing)
|
|||
|
(:filter-section . org-rst-filter-headline-blank-lines)))
|
|||
|
|
|||
|
|
|||
|
;;; Internal Variables
|
|||
|
|
|||
|
|
|||
|
|
|||
|
;;; User Configurable Variables
|
|||
|
|
|||
|
(defgroup org-export-rst nil
|
|||
|
"Options for exporting Org mode files to reStructuredText."
|
|||
|
:tag "Org RST"
|
|||
|
:group 'org-export)
|
|||
|
|
|||
|
|
|||
|
(defcustom org-rst-extension "rst"
|
|||
|
"The extension for exported reStructuredText files."
|
|||
|
:group 'org-export-rst
|
|||
|
:type 'string)
|
|||
|
|
|||
|
|
|||
|
(defcustom org-rst-link-org-files-as-rst t
|
|||
|
"Non-nil means make file links to `file.org' point to `file.rst'.
|
|||
|
When `org-mode' is exporting an `org-mode' file to reStructuredText,
|
|||
|
links to non-rst files are directly put into a href tag in
|
|||
|
reStructuredText.
|
|||
|
However, links to other Org mode files (recognized by the extension
|
|||
|
`.org.) should become links to the corresponding reStructuredText
|
|||
|
file, assuming that the linked `org-mode' file will also be
|
|||
|
converted to reStructuredText.
|
|||
|
When nil, the links still point to the plain \".org\" file."
|
|||
|
:group 'org-export-rst
|
|||
|
:type 'boolean)
|
|||
|
|
|||
|
|
|||
|
(defcustom org-rst-link-home ""
|
|||
|
"Where should the \"HOME\" link of exported rst files lead?"
|
|||
|
:group 'org-export-rst
|
|||
|
:type '(string :tag "File or URL"))
|
|||
|
|
|||
|
|
|||
|
(defcustom org-rst-link-use-abs-url nil
|
|||
|
"Should we prepend relative links with RST_LINK_HOME?"
|
|||
|
:group 'org-export-rst
|
|||
|
:type 'boolean)
|
|||
|
|
|||
|
;;;; Links :: Inline images
|
|||
|
|
|||
|
(defcustom org-rst-inline-images t
|
|||
|
"Non-nil means inline images into exported reStructuredText.
|
|||
|
This is done using an image directive or an figure directive.
|
|||
|
When nil, an anchor with reference is used to link to the image."
|
|||
|
:group 'org-export-rst
|
|||
|
:type 'boolean)
|
|||
|
|
|||
|
(defcustom org-rst-inline-image-rules
|
|||
|
'(("file" . "\\.\\(jpeg\\|jpg\\|png\\|gif\\|svg\\|svgz\\|swf\\)\\'")
|
|||
|
("attachment" . "\\.\\(jpeg\\|jpg\\|png\\|gif\\|svg\\|svgz\\|swf\\)\\'")
|
|||
|
("http" . "\\.\\(jpeg\\|jpg\\|png\\|gif\\|svg\\|svgz\\|swf\\)\\'")
|
|||
|
("https" . "\\.\\(jpeg\\|jpg\\|png\\|gif\\|svg\\|svgz\\|swf\\)\\'"))
|
|||
|
"Rules characterizing image files that can be inlined into reStructuredText.
|
|||
|
A rule consists in an association whose key is the type of link
|
|||
|
to consider, and value is a regexp that will be matched against
|
|||
|
link's path."
|
|||
|
:group 'org-export-rst
|
|||
|
:type '(alist :key-type (string :tag "Type")
|
|||
|
:value-type (regexp :tag "Path")))
|
|||
|
|
|||
|
(defcustom org-rst-link-use-ref-role nil
|
|||
|
"Non-nil means export internal links using :ref: role."
|
|||
|
:group 'org-export-rst
|
|||
|
:type 'boolean)
|
|||
|
|
|||
|
(defcustom org-rst-file-link-use-ref-role nil
|
|||
|
"Non-nil means export internal file links using :ref: role."
|
|||
|
:group 'org-export-rst
|
|||
|
:type 'boolean)
|
|||
|
|
|||
|
(defcustom org-rst-text-markup-alist '((bold . "**%s**")
|
|||
|
(code . verb)
|
|||
|
(italic . "*%s*")
|
|||
|
(verbatim . verb))
|
|||
|
"Alist of reStructredText expressions to convert text markup.
|
|||
|
|
|||
|
The key must be a symbol among `bold', `code', `italic',
|
|||
|
`verbatim'. The value is a formatting string to
|
|||
|
wrap fontified text with.
|
|||
|
|
|||
|
Value can also be set to the following symbols: `verb'.
|
|||
|
|
|||
|
If no association can be found for a given markup, text will be
|
|||
|
returned as-is."
|
|||
|
:group 'org-export-rst
|
|||
|
:type 'alist
|
|||
|
:options '(bold code italic verbatim))
|
|||
|
|
|||
|
|
|||
|
(defcustom org-rst-quote-margin 4
|
|||
|
"Width of margin used for quoting text, in characters.
|
|||
|
This margin is applied on left side of the text."
|
|||
|
:group 'org-export-rst
|
|||
|
:type 'integer)
|
|||
|
|
|||
|
|
|||
|
(defcustom org-rst-headline-spacing '(1 . 1)
|
|||
|
"Number of blank lines inserted around headlines.
|
|||
|
|
|||
|
This variable can be set to a cons cell. In that case, its car
|
|||
|
represents the number of blank lines present before headline
|
|||
|
contents whereas its cdr reflects the number of blank lines after
|
|||
|
contents.
|
|||
|
|
|||
|
A nil value replicates the number of blank lines found in the
|
|||
|
original Org buffer at the same place."
|
|||
|
:group 'org-export-rst
|
|||
|
:type '(choice
|
|||
|
(const :tag "Replicate original spacing" nil)
|
|||
|
(cons :tag "Set a uniform spacing"
|
|||
|
(integer :tag "Number of blank lines before contents")
|
|||
|
(integer :tag "Number of blank lines after contents"))))
|
|||
|
|
|||
|
|
|||
|
(defcustom org-rst-paragraph-spacing 'auto
|
|||
|
"Number of white lines between paragraphs.
|
|||
|
If the value is an integer, add this number of blank lines
|
|||
|
between contiguous paragraphs. If is it the symbol `auto', keep
|
|||
|
the same number of blank lines as in the original document."
|
|||
|
:group 'org-export-rst
|
|||
|
:type '(choice
|
|||
|
(integer :tag "Number of blank lines")
|
|||
|
(const :tag "Preserve original spacing" auto)))
|
|||
|
|
|||
|
|
|||
|
(defcustom org-rst-headline-underline-characters '(?- ?~ ?^ ?: ?' ?\ ?_)
|
|||
|
"List of underline characters for each headline level."
|
|||
|
:group 'org-export-rst
|
|||
|
:type 'list)
|
|||
|
|
|||
|
;;;; Drawers
|
|||
|
|
|||
|
(defcustom org-rst-format-drawer-function nil
|
|||
|
"Function called to format a drawer in reStructuredText 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.
|
|||
|
|
|||
|
For example, the variable could be set to the following function
|
|||
|
in order to mimic default behaviour:
|
|||
|
|
|||
|
\(defun org-rst-format-drawer-default \(name contents\)
|
|||
|
\"Format a drawer element for reStructuredText export.\"
|
|||
|
contents\)"
|
|||
|
:group 'org-export-rst
|
|||
|
:type 'function)
|
|||
|
|
|||
|
|
|||
|
;;;; Inlinetasks
|
|||
|
|
|||
|
(defcustom org-rst-format-inlinetask-function nil
|
|||
|
"Function called to format an inlinetask in reStructuredText 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-rst
|
|||
|
:type 'function)
|
|||
|
|
|||
|
|
|||
|
;;;; Src blocks
|
|||
|
|
|||
|
(defcustom org-rst-code-block 'code
|
|||
|
"The directive used to export SRC-BLOCKs."
|
|||
|
:group 'org-export-rst
|
|||
|
:type '(choice
|
|||
|
(const :tag "Use a code-block directive" code-block)
|
|||
|
(const :tag "Use a code directive" code)
|
|||
|
(const :tag "Use a literal block" nil))
|
|||
|
:safe (lambda (s) (memq s '(code-block code nil)))
|
|||
|
)
|
|||
|
|
|||
|
(defcustom org-rst-pygments-langs
|
|||
|
'((emacs-lisp "common-lisp") (lisp "common-lisp")
|
|||
|
(cc "c++")
|
|||
|
(cperl "perl")
|
|||
|
(latex "tex")
|
|||
|
(shell-script "bash")
|
|||
|
(caml "ocaml")
|
|||
|
(sqlite3 "sqlite"))
|
|||
|
"Alist mapping languages to their listing language counterpart.
|
|||
|
The key is a symbol, the major mode symbol without the \"-mode\".
|
|||
|
The value is the string that should be inserted as the language
|
|||
|
parameter for the listings package. If the mode name and the
|
|||
|
listings name are the same, the language does not need an entry
|
|||
|
in this list - but it does not hurt if it is present."
|
|||
|
:group 'org-export-rst
|
|||
|
:type '(repeat
|
|||
|
(list
|
|||
|
(symbol :tag "Major mode ")
|
|||
|
(string :tag "Pygments language"))))
|
|||
|
|
|||
|
|
|||
|
|
|||
|
;;; Internal Functions
|
|||
|
|
|||
|
(defun org-rst--justify-lines (s text-width how)
|
|||
|
"Justify all lines in string S.
|
|||
|
TEXT-WIDTH is an integer specifying maximum length of a line.
|
|||
|
HOW determines the type of justification: it can be `left',
|
|||
|
`right', `full' or `center'."
|
|||
|
(with-temp-buffer
|
|||
|
(insert s)
|
|||
|
(goto-char (point-min))
|
|||
|
(let ((fill-column text-width)
|
|||
|
;; Disable `adaptive-fill-mode' so it doesn't prevent
|
|||
|
;; filling lines matching `adaptive-fill-regexp'.
|
|||
|
(adaptive-fill-mode nil))
|
|||
|
(while (< (point) (point-max))
|
|||
|
(justify-current-line how)
|
|||
|
(forward-line)))
|
|||
|
(buffer-string)))
|
|||
|
|
|||
|
|
|||
|
(defun org-rst--indent-string (s width)
|
|||
|
"Indent string S by WIDTH white spaces.
|
|||
|
Empty lines are not indented."
|
|||
|
(when (stringp s)
|
|||
|
(replace-regexp-in-string
|
|||
|
"\\(^\\)\\(?:.*\\S-\\)" (make-string width ? ) s nil nil 1)))
|
|||
|
|
|||
|
|
|||
|
(defun org-rst--make-attribute-string (attributes)
|
|||
|
"Return a list of attributes, as a string.
|
|||
|
ATTRIBUTES is a plist where values are either strings or nil. An
|
|||
|
attributes with a nil value will be omitted from the result."
|
|||
|
(let (output)
|
|||
|
(dolist (item attributes (mapconcat 'identity (nreverse output) "\n"))
|
|||
|
(cond ((null item) (pop output))
|
|||
|
((symbolp item) (push (substring (symbol-name item) 1) output))
|
|||
|
(t (let ((key (org-trim (car output)))
|
|||
|
(value (replace-regexp-in-string "\"" "\\\""
|
|||
|
(replace-regexp-in-string
|
|||
|
"\\\\" "\\\\" (org-trim item)))))
|
|||
|
(setcar output (format " :%s: %s" key value))))))))
|
|||
|
|
|||
|
|
|||
|
(defun org-rst--build-title
|
|||
|
(element info &optional underline notags toc)
|
|||
|
"Format ELEMENT title and return it.
|
|||
|
|
|||
|
ELEMENT is either an `headline' or `inlinetask' element. INFO is
|
|||
|
a plist used as a communication channel.
|
|||
|
|
|||
|
When optional argument UNDERLINE is non-nil, underline title,
|
|||
|
without the tags, according to `org-rst-underline'
|
|||
|
specifications.
|
|||
|
|
|||
|
If optional argument NOTAGS is non-nil, no tags will be added to
|
|||
|
the title.
|
|||
|
|
|||
|
When optional argument TOC is non-nil, use optional title if
|
|||
|
possible. It doesn't apply to `inlinetask' elements."
|
|||
|
(let* ((headlinep (eq (org-element-type element) 'headline))
|
|||
|
(numbers
|
|||
|
;; Numbering is specific to headlines.
|
|||
|
(and headlinep (org-export-numbered-headline-p element info)
|
|||
|
;; All tests passed: build numbering string.
|
|||
|
(concat
|
|||
|
(mapconcat
|
|||
|
'number-to-string
|
|||
|
(org-export-get-headline-number element info) ".")
|
|||
|
" ")))
|
|||
|
(text
|
|||
|
(org-trim
|
|||
|
(org-export-data
|
|||
|
(if (and toc headlinep) (org-export-get-alt-title element info)
|
|||
|
(org-element-property :title element))
|
|||
|
info)))
|
|||
|
(todo
|
|||
|
(and (plist-get info :with-todo-keywords)
|
|||
|
(let ((todo (org-element-property :todo-keyword element)))
|
|||
|
(and todo (concat (org-export-data todo info) " ")))))
|
|||
|
(tags (and (not notags)
|
|||
|
(plist-get info :with-tags)
|
|||
|
(let ((tag-list (org-export-get-tags element info)))
|
|||
|
(and tag-list
|
|||
|
(org-make-tag-string tag-list)))))
|
|||
|
(priority
|
|||
|
(and (plist-get info :with-priority)
|
|||
|
(let ((char (org-element-property :priority element)))
|
|||
|
(and char (format "(#%c) " char)))))
|
|||
|
(first-part (concat numbers todo priority text)))
|
|||
|
(concat
|
|||
|
first-part
|
|||
|
;; Align tags, if any.
|
|||
|
(when tags
|
|||
|
(format
|
|||
|
(format " %%%ds" (string-width tags))
|
|||
|
tags))
|
|||
|
;; Maybe underline text, if ELEMENT type is `headline' and an
|
|||
|
;; underline character has been defined.
|
|||
|
(when (and underline headlinep)
|
|||
|
(let ((under-char
|
|||
|
(nth (1- (org-export-get-relative-level element info))
|
|||
|
org-rst-headline-underline-characters)))
|
|||
|
(and under-char
|
|||
|
(concat "\n"
|
|||
|
(make-string (string-width first-part) under-char))))))))
|
|||
|
|
|||
|
|
|||
|
(defun org-rst--text-markup (text markup info)
|
|||
|
"Format TEXT depending on MARKUP text markup.
|
|||
|
See `org-rst-text-markup-alist' for details."
|
|||
|
(let ((fmt (cdr (assq markup (plist-get info :rst-text-markup-alist))))
|
|||
|
(text (replace-regexp-in-string "[ \t\n]+" " " text)))
|
|||
|
(cond
|
|||
|
;; No format string: Return raw text.
|
|||
|
((not fmt) text)
|
|||
|
;; Handle the `verb' special case: Protect some
|
|||
|
;; special chars and use "\\" escape.
|
|||
|
((eq 'verb fmt)
|
|||
|
(let ((rtn "")
|
|||
|
char)
|
|||
|
(while (string-match "\\`*" text)
|
|||
|
(setq char (match-string 0 text))
|
|||
|
(when (> (match-beginning 0) 0)
|
|||
|
(setq rtn (concat rtn (substring text 0 (match-beginning 0)))))
|
|||
|
(setq text (substring text (1+ (match-beginning 0))))
|
|||
|
(setq char (concat "\\" char)
|
|||
|
rtn (concat rtn char)))
|
|||
|
(setq text (concat rtn text)
|
|||
|
fmt "``%s``")
|
|||
|
(format fmt text)))
|
|||
|
;; Else use format string.
|
|||
|
(t (format fmt text)))))
|
|||
|
|
|||
|
|
|||
|
(defun org-rst--checkbox (item _info)
|
|||
|
"Return checkbox string for ITEM or nil.
|
|||
|
INFO is a plist used as a communication channel."
|
|||
|
;(let ((utf8p (eq (plist-get info :ascii-charset) 'utf-8)))
|
|||
|
; (case (org-element-property :checkbox item)
|
|||
|
; (on (if utf8p "☑ " "[X] "))
|
|||
|
; (off (if utf8p "☐ " "[ ] "))
|
|||
|
; (trans (if utf8p "☒ " "[-] ")))))
|
|||
|
(cl-case (org-element-property :checkbox item)
|
|||
|
(on "☑ ")
|
|||
|
(off "☐ ")
|
|||
|
(trans "☒ ")))
|
|||
|
|
|||
|
|
|||
|
|
|||
|
;;; Template
|
|||
|
|
|||
|
(defun org-rst-template--document-title (info)
|
|||
|
"Return document title, as a string.
|
|||
|
INFO is a plist used as a communication channel."
|
|||
|
(let* (;; Links in the title will not be resolved later, so we make
|
|||
|
;; sure their path is located right after them.
|
|||
|
(with-title (plist-get info :with-title))
|
|||
|
(title (if with-title
|
|||
|
(org-export-data (plist-get info :title) info)
|
|||
|
""))
|
|||
|
(subtitle (if with-title
|
|||
|
(org-export-data (plist-get info :subtitle) info)
|
|||
|
""))
|
|||
|
(author (and (plist-get info :with-author)
|
|||
|
(let ((auth (plist-get info :author)))
|
|||
|
(and auth (org-export-data auth info)))))
|
|||
|
(email (and (plist-get info :with-email)
|
|||
|
(org-export-data (plist-get info :email) info)))
|
|||
|
(date (and (plist-get info :with-date)
|
|||
|
(org-export-data (org-export-get-date info) info)))
|
|||
|
(titleline (make-string (string-width title) ?=))
|
|||
|
(subtitleline (make-string (string-width subtitle) ?-))
|
|||
|
(title (if (not (string= title ""))
|
|||
|
(concat titleline "\n"
|
|||
|
title "\n"
|
|||
|
titleline "\n") ""))
|
|||
|
(subtitle (if (not (string= subtitle ""))
|
|||
|
(concat subtitleline "\n"
|
|||
|
subtitle "\n"
|
|||
|
subtitleline "\n") "")))
|
|||
|
(cond
|
|||
|
((string= title "")
|
|||
|
(concat
|
|||
|
(when (org-string-nw-p author) (concat " :Author: " author "\n"))
|
|||
|
(when (org-string-nw-p email) (concat " :Contact: " email "\n"))
|
|||
|
(when (org-string-nw-p date) (concat " :Date: " date "\n"))))
|
|||
|
(t
|
|||
|
(concat
|
|||
|
title
|
|||
|
subtitle
|
|||
|
(when (org-string-nw-p author) (concat "\n :Author: " author))
|
|||
|
(when (org-string-nw-p email) (concat "\n :Contact: " email))
|
|||
|
(when (org-string-nw-p date) (concat "\n :Date: " date))
|
|||
|
"\n")))))
|
|||
|
|
|||
|
|
|||
|
(defun org-rst-template (contents info)
|
|||
|
"Return complete document string after reStructuredText conversion.
|
|||
|
CONTENTS is the transcoded contents string. INFO is a plist
|
|||
|
holding export options."
|
|||
|
(concat
|
|||
|
;; Build title block.
|
|||
|
(concat (org-rst-template--document-title info)
|
|||
|
"\n"
|
|||
|
;; 2. Table of contents.
|
|||
|
(let ((depth (plist-get info :with-toc)))
|
|||
|
(when depth ".. contents::\n\n")))
|
|||
|
;; Document's body.
|
|||
|
contents
|
|||
|
;; Creator. Justify it to the bottom right.
|
|||
|
(and (plist-get info :with-creater)
|
|||
|
(concat
|
|||
|
"\n :Creator: "
|
|||
|
(plist-get info :creator) "\n"))))
|
|||
|
|
|||
|
|
|||
|
|
|||
|
;;; Transcode Functions
|
|||
|
|
|||
|
;;;; Bold
|
|||
|
|
|||
|
(defun org-rst-bold (_bold contents info)
|
|||
|
"Transcode BOLD from Org to reStructuredText.
|
|||
|
CONTENTS is the text with bold markup. INFO is a plist holding
|
|||
|
contextual information."
|
|||
|
(org-rst--text-markup contents 'bold info))
|
|||
|
|
|||
|
|
|||
|
;;;; Center Block
|
|||
|
|
|||
|
(defun org-rst-center-block (_center-block contents _info)
|
|||
|
"Transcode a CENTER-BLOCK element from Org to reStructuredText.
|
|||
|
CONTENTS holds the contents of the center block. INFO is a plist
|
|||
|
holding contextual information."
|
|||
|
contents)
|
|||
|
|
|||
|
|
|||
|
;;;; Clock
|
|||
|
|
|||
|
(defun org-rst-clock (clock _contents _info)
|
|||
|
"Transcode a CLOCK object from Org to reStructuredText.
|
|||
|
CONTENTS is nil. INFO is a plist holding contextual
|
|||
|
information."
|
|||
|
(concat org-clock-string " "
|
|||
|
(org-translate-time
|
|||
|
(org-element-property :raw-value
|
|||
|
(org-element-property :value clock)))
|
|||
|
(let ((time (org-element-property :duration clock)))
|
|||
|
(and time
|
|||
|
(concat " => "
|
|||
|
(apply 'format
|
|||
|
"%2s:%02s"
|
|||
|
(org-split-string time ":")))))))
|
|||
|
|
|||
|
|
|||
|
;;;; Code
|
|||
|
|
|||
|
(defun org-rst-code (code _contents info)
|
|||
|
"Transcode a CODE object from Org to reStructuredText.
|
|||
|
CONTENTS is nil. INFO is a plist used as a communication
|
|||
|
channel."
|
|||
|
(org-rst--text-markup (org-element-property :value code) 'code info))
|
|||
|
|
|||
|
|
|||
|
;;;; Drawer
|
|||
|
|
|||
|
(defun org-rst-drawer (drawer contents _info)
|
|||
|
"Transcode a DRAWER element from Org to reStructuredText.
|
|||
|
CONTENTS holds the contents of the block. INFO is a plist
|
|||
|
holding contextual information."
|
|||
|
(let* ((name (org-element-property :drawer-name drawer))
|
|||
|
(output (if (functionp org-rst-format-drawer-function)
|
|||
|
(funcall org-rst-format-drawer-function
|
|||
|
name contents)
|
|||
|
;; If there's no user defined function: simply
|
|||
|
;; display contents of the drawer.
|
|||
|
contents)))
|
|||
|
output))
|
|||
|
|
|||
|
|
|||
|
;;;; Dynamic Block
|
|||
|
|
|||
|
(defun org-rst-dynamic-block (_dynamic-block contents _info)
|
|||
|
"Transcode a DYNAMIC-BLOCK element from Org to reStructuredText.
|
|||
|
CONTENTS holds the contents of the block. INFO is a plist
|
|||
|
holding contextual information."
|
|||
|
contents)
|
|||
|
|
|||
|
|
|||
|
;;;; Entity
|
|||
|
|
|||
|
(defun org-rst-entity (entity _contents _info)
|
|||
|
"Transcode an ENTITY object from Org to reStructuredText.
|
|||
|
CONTENTS are the definition itself. INFO is a plist holding
|
|||
|
contextual information."
|
|||
|
(let ((ent (org-element-property :latex entity)))
|
|||
|
(if (org-element-property :latex-math-p entity)
|
|||
|
(format ":math:`%s`" ent)
|
|||
|
(org-element-property :utf-8 entity))))
|
|||
|
|
|||
|
|
|||
|
;;;; Example Block
|
|||
|
|
|||
|
(defun org-rst-example-block (example-block _contents _info)
|
|||
|
"Transcode an EXAMPLE-BLOCK element from Org to reStructuredText.
|
|||
|
CONTENTS is nil. INFO is a plist holding contextual
|
|||
|
information."
|
|||
|
(let* ((example (org-remove-indentation
|
|||
|
(org-element-property :value example-block)))
|
|||
|
(label (org-element-property :name example-block))
|
|||
|
(attributes
|
|||
|
(org-export-read-attribute :attr_rst example-block))
|
|||
|
(class (plist-get attributes :class)))
|
|||
|
(when example
|
|||
|
(concat
|
|||
|
"::\n"
|
|||
|
(when class (format " :class: %s\n" class))
|
|||
|
(when label (format " :name: %s\n" label))
|
|||
|
"\n"
|
|||
|
(org-rst--indent-string example org-rst-quote-margin)))))
|
|||
|
|
|||
|
|
|||
|
;;;; Export Block
|
|||
|
|
|||
|
(defun org-rst-export-block (export-block _contents _info)
|
|||
|
"Transcode a EXPORT-BLOCK element from Org to reStructuredText.
|
|||
|
CONTENTS is nil. INFO is a plist holding contextual information."
|
|||
|
(when (member (org-element-property :type export-block) '("RST" "REST" "RESTRUCTUREDTEXT"))
|
|||
|
(org-element-property :value export-block)))
|
|||
|
|
|||
|
|
|||
|
;;;; Export Snippet
|
|||
|
|
|||
|
(defun org-rst-export-snippet (export-snippet _contents _info)
|
|||
|
"Transcode a EXPORT-SNIPPET object from Org to reStructuredText.
|
|||
|
CONTENTS is nil. INFO is a plist holding contextual information."
|
|||
|
(when (eq (org-export-snippet-backend export-snippet) 'rst)
|
|||
|
(org-element-property :value export-snippet)))
|
|||
|
|
|||
|
|
|||
|
;;;; Footnote Definition
|
|||
|
|
|||
|
;(defun org-rst-footnote-definition (footnote-definition contents info)
|
|||
|
; "Transcode a FOOTNOTE-DEFINITION element from Org to reStructuredText.
|
|||
|
;CONTENTS is nil. INFO is a plist holding contextual information."
|
|||
|
; (replace-regexp-in-string
|
|||
|
; "^" ".. "
|
|||
|
; (org-remove-indentation
|
|||
|
; (org-element-property :value footnote-definition))))
|
|||
|
|
|||
|
|
|||
|
;;;; Footnote Reference
|
|||
|
|
|||
|
(defun org-rst-footnote-reference (footnote-reference _contents info)
|
|||
|
"Transcode a FOOTNOTE-REFERENCE element from Org to reStructuredText.
|
|||
|
CONTENTS is nil. INFO is a plist holding contextual information."
|
|||
|
(format " [%s]_ " (org-export-get-footnote-number footnote-reference info)))
|
|||
|
|
|||
|
|
|||
|
;;;; Headline
|
|||
|
|
|||
|
(defun org-rst-headline (headline contents info)
|
|||
|
"Transcode a HEADLINE element from Org to reStructuredText.
|
|||
|
CONTENTS holds the contents of the headline. INFO is a plist
|
|||
|
holding contextual information."
|
|||
|
;; Don't export footnote section, which will be handled at the end
|
|||
|
;; of the template.
|
|||
|
(unless (org-element-property :footnote-section-p headline)
|
|||
|
(let* (;; Blank lines between headline and its contents.
|
|||
|
;; `org-rst-headline-spacing', when set, overwrites
|
|||
|
;; original buffer's spacing.
|
|||
|
(pre-blanks
|
|||
|
(make-string
|
|||
|
(if org-rst-headline-spacing (car org-rst-headline-spacing)
|
|||
|
(org-element-property :pre-blank headline)) ?\n))
|
|||
|
(id (org-element-property :ID headline))
|
|||
|
(customid (org-element-property :CUSTOM_ID headline)))
|
|||
|
(concat
|
|||
|
(if customid
|
|||
|
(format ".. _%s:\n\n" customid)
|
|||
|
(if id (format ".. _%s:\n\n" id) ""))
|
|||
|
(org-rst--build-title headline info 'underline)
|
|||
|
"\n" pre-blanks
|
|||
|
contents))))
|
|||
|
|
|||
|
|
|||
|
;;;; Horizontal Rule
|
|||
|
|
|||
|
(defun org-rst-horizontal-rule (_horizontal-rule _contents _info)
|
|||
|
"Transcode an HORIZONTAL-RULE object from Org to reStructuredText.
|
|||
|
CONTENTS is nil. INFO is a plist holding contextual information."
|
|||
|
"\n------------\n")
|
|||
|
|
|||
|
;;;; Inline Src Block
|
|||
|
|
|||
|
(defun org-rst-inline-src-block (inline-src-block _contents info)
|
|||
|
"Transcode an INLINE-SRC-BLOCK element from Org to reStructuredText.
|
|||
|
CONTENTS holds the contents of the item. INFO is a plist holding
|
|||
|
contextual information."
|
|||
|
(org-rst--text-markup
|
|||
|
(org-element-property :value inline-src-block) 'verbatim info))
|
|||
|
|
|||
|
|
|||
|
;;;; Inlinetask
|
|||
|
|
|||
|
(defun org-rst-inlinetask (inlinetask contents info)
|
|||
|
"Transcode an INLINETASK element from Org to reStructuredText.
|
|||
|
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))))
|
|||
|
;; If `org-rst-format-inlinetask-function' is provided, call it
|
|||
|
;; with appropriate arguments.
|
|||
|
(if (functionp org-rst-format-inlinetask-function)
|
|||
|
(funcall org-rst-format-inlinetask-function
|
|||
|
todo todo-type priority title tags contents)
|
|||
|
;; Otherwise, use a default template.
|
|||
|
(let ((full-title
|
|||
|
(concat
|
|||
|
(when todo (format "%s" todo))
|
|||
|
(when priority (format "\#%c " priority))
|
|||
|
title
|
|||
|
(when tags (format ":%s:"
|
|||
|
(mapconcat 'identity tags ":"))))))
|
|||
|
(format (concat "%s\n\n"
|
|||
|
"%s\n")
|
|||
|
full-title contents)))))
|
|||
|
|
|||
|
|
|||
|
;;;; Inner template
|
|||
|
|
|||
|
(defun org-rst-inner-template (contents info)
|
|||
|
"Return complete document string after reStructuredText conversion.
|
|||
|
CONTENTS is the transcoded contents string. INFO is a plist
|
|||
|
holding export options."
|
|||
|
(org-element-normalize-string
|
|||
|
(concat
|
|||
|
;; 1. Document's body.
|
|||
|
contents
|
|||
|
;; 2. Footnote definitions.
|
|||
|
(let ((definitions (org-export-collect-footnote-definitions info)))
|
|||
|
(when definitions
|
|||
|
(concat
|
|||
|
"\n\n"
|
|||
|
(mapconcat
|
|||
|
(lambda (ref)
|
|||
|
(let* ((id (format ".. [%s] " (car ref)))
|
|||
|
(def (nth 2 ref))
|
|||
|
(lines (split-string (org-export-data def info) "\n+[ \t\n]*"))
|
|||
|
(fntext (concat (car lines) "\n"
|
|||
|
(apply 'concat (mapcar
|
|||
|
'(lambda (x) (if (> (length x) 0)
|
|||
|
(concat (org-rst--indent-string x org-rst-quote-margin) "\n")))
|
|||
|
(cdr lines)))))
|
|||
|
)
|
|||
|
(concat id fntext)))
|
|||
|
definitions "\n")))))))
|
|||
|
|
|||
|
|
|||
|
;;;; Italic
|
|||
|
|
|||
|
(defun org-rst-italic (_italic contents info)
|
|||
|
"Transcode ITALIC from Org to reStructuredText.
|
|||
|
CONTENTS is the text with italic markup. INFO is a plist holding
|
|||
|
contextual information."
|
|||
|
(org-rst--text-markup contents 'italic info))
|
|||
|
|
|||
|
|
|||
|
;;;; Item
|
|||
|
|
|||
|
(defun org-rst-item (item contents info)
|
|||
|
"Transcode ITEM element into reStructuredText format.
|
|||
|
CONTENTS is the item contents. INFO is a plist used as
|
|||
|
a communication channel."
|
|||
|
(let* ((checkbox (org-rst--checkbox item info))
|
|||
|
(list-type (org-element-property :type (org-export-get-parent item)))
|
|||
|
(tag (let
|
|||
|
((tag (org-element-property :tag item)))
|
|||
|
(and tag (concat (org-export-data tag info) checkbox))))
|
|||
|
(bullet
|
|||
|
;; First parent of ITEM is always the plain-list. Get
|
|||
|
;; `:type' property from it.
|
|||
|
(org-list-bullet-string
|
|||
|
(cond
|
|||
|
((eq list-type 'ordered)
|
|||
|
;; Return correct number for ITEM, paying attention to
|
|||
|
;; counters.
|
|||
|
(let* ((struct (org-element-property :structure item))
|
|||
|
(bul (org-element-property :bullet item))
|
|||
|
(num (number-to-string
|
|||
|
(car (last (org-list-get-item-number
|
|||
|
(org-element-property :begin item)
|
|||
|
struct
|
|||
|
(org-list-prevs-alist struct)
|
|||
|
(org-list-parents-alist struct)))))))
|
|||
|
(replace-regexp-in-string "[0-9]+" num bul)))
|
|||
|
(tag "")
|
|||
|
(t "-"))))
|
|||
|
(width (if tag 4 (string-width bullet)))
|
|||
|
)
|
|||
|
(concat
|
|||
|
(if tag tag (concat bullet checkbox))
|
|||
|
(let ((contents (org-rst--indent-string contents width)))
|
|||
|
(if (and (not tag)
|
|||
|
(eq (org-element-type (car (org-element-contents item))) 'paragraph))
|
|||
|
(org-trim contents)
|
|||
|
(concat "\n" contents))))))
|
|||
|
|
|||
|
|
|||
|
;;;; Keyword
|
|||
|
|
|||
|
(defun org-rst-keyword (keyword _contents _info)
|
|||
|
"Transcode a KEYWORD element from Org to reStructuredText.
|
|||
|
CONTENTS is nil. INFO is a plist holding contextual information."
|
|||
|
(let ((key (org-element-property :key keyword))
|
|||
|
(value (org-element-property :value keyword)))
|
|||
|
(cond
|
|||
|
((string= key "RST") value))))
|
|||
|
|
|||
|
|
|||
|
;;;; Latex Environment
|
|||
|
|
|||
|
(defun org-rst-latex-environment (latex-environment _contents info)
|
|||
|
"Transcode a LATEX-ENVIRONMENT element from Org to reStructuredText.
|
|||
|
CONTENTS is nil. INFO is a plist holding contextual
|
|||
|
information."
|
|||
|
(when (plist-get info :with-latex)
|
|||
|
(org-remove-indentation (org-element-property :value latex-environment))))
|
|||
|
|
|||
|
|
|||
|
;;;; Latex Fragment
|
|||
|
|
|||
|
(defun org-rst-latex-fragment (latex-fragment _contents _info)
|
|||
|
"Transcode a LATEX-FRAGMENT object from Org to reStructuredText.
|
|||
|
CONTENTS is nil. INFO is a plist holding contextual
|
|||
|
information."
|
|||
|
(let ((value (org-element-property :value latex-fragment)))
|
|||
|
(cond
|
|||
|
((string-match "\\`\\(\\$\\{2\\}\\)\\([^\000]*\\)\\1\\'" value)
|
|||
|
(format ".. math::\n\n%s"
|
|||
|
(org-rst--indent-string
|
|||
|
(org-trim (match-string 2 value)) org-rst-quote-margin)))
|
|||
|
((string-match "\\`\\(\\$\\{1\\}\\)\\([^\000]*\\)\\1\\'" value)
|
|||
|
(format ":math:`%s`" (org-trim (match-string 2 value))))
|
|||
|
((string-match "\\`\\\\(\\([^\000]*\\)\\\\)\\'" value)
|
|||
|
(format ":math:`%s`" (org-trim (match-string 1 value))))
|
|||
|
((string-match "\\`\\\\\\[\\([^\000]*\\)\\\\\\]\\'" value)
|
|||
|
(format "\.. math::\n\n%s"
|
|||
|
(org-rst--indent-string
|
|||
|
(org-trim (match-string 1 value)) org-rst-quote-margin)))
|
|||
|
(t value))))
|
|||
|
|
|||
|
|
|||
|
;;;; Line Break
|
|||
|
|
|||
|
(defun org-rst-line-break (_line-break _contents _info)
|
|||
|
"Transcode a LINE-BREAK object from Org to reStructuredText.
|
|||
|
CONTENTS is nil. INFO is a plist holding contextual
|
|||
|
information."
|
|||
|
hard-newline)
|
|||
|
|
|||
|
|
|||
|
;;;; Link
|
|||
|
|
|||
|
(defun org-rst-inline-image-p (link info)
|
|||
|
"Non-nil when LINK is meant to appear as an image.
|
|||
|
INFO is a plist used as a communication channel. LINK is an
|
|||
|
inline image when it has no description and targets an image
|
|||
|
file (see `org-rst-inline-image-rules' for more information), or
|
|||
|
if its description is a single link targeting an image file."
|
|||
|
(if (not (org-element-contents link))
|
|||
|
(org-export-inline-image-p
|
|||
|
link (plist-get info :rst-inline-image-rules))
|
|||
|
(not
|
|||
|
(let ((link-count 0))
|
|||
|
(org-element-map (org-element-contents link)
|
|||
|
(cons 'plain-text org-element-all-objects)
|
|||
|
(lambda (obj)
|
|||
|
(cl-case (org-element-type obj)
|
|||
|
(plain-text (org-string-nw-p obj))
|
|||
|
(link (if (= link-count 1) t
|
|||
|
(cl-incf link-count)
|
|||
|
(not (org-export-inline-image-p
|
|||
|
obj (plist-get info :rst-inline-image-rules)))))
|
|||
|
(otherwise t)))
|
|||
|
info t)))))
|
|||
|
|
|||
|
|
|||
|
(defun my-org-export-inline-image-p (link &optional rules)
|
|||
|
(let ((case-fold-search t)
|
|||
|
(rules (or rules org-export-default-inline-image-rule)))
|
|||
|
(catch 'exit
|
|||
|
(mapc
|
|||
|
(lambda (rule)
|
|||
|
(if (string-match (cdr rule) link)
|
|||
|
(throw 'exit t)))
|
|||
|
rules)
|
|||
|
;; Return nil if no rule matched.
|
|||
|
nil)))
|
|||
|
|
|||
|
|
|||
|
(defun org-rst-link (link desc info)
|
|||
|
"Transcode a LINK object from Org to reStructuredText.
|
|||
|
|
|||
|
DESC is the description part of the link, or the empty string.
|
|||
|
INFO is a plist holding contextual information."
|
|||
|
(let* ((home (when (plist-get info :rst-link-home)
|
|||
|
(org-trim (plist-get info :rst-link-home))))
|
|||
|
(use-abs-url (plist-get info :rst-link-use-abs-url))
|
|||
|
(link-org-files-as-rst-maybe
|
|||
|
(function
|
|||
|
(lambda (raw-path info)
|
|||
|
"Treat links to `file.org' as links to `file.rst', if needed.
|
|||
|
See `org-rst-link-org-files-as-rst'."
|
|||
|
(cond
|
|||
|
((and (plist-get info :rst-link-org-files-as-rst)
|
|||
|
(string= ".org"
|
|||
|
(downcase (file-name-extension raw-path "."))))
|
|||
|
(concat (file-name-sans-extension raw-path) "."
|
|||
|
(plist-get info :rst-extension)))
|
|||
|
(t raw-path)))))
|
|||
|
(type (org-element-property :type link))
|
|||
|
(search-option (org-element-property :search-option link))
|
|||
|
(raw-path (org-element-property :path link))
|
|||
|
;; Ensure DESC really exists, or set it to nil.
|
|||
|
(desc (and (not (string= desc "")) desc))
|
|||
|
(path (cond
|
|||
|
((member type '("http" "https" "ftp" "mailto"))
|
|||
|
(url-encode-url
|
|||
|
(concat type ":" raw-path)))
|
|||
|
((string= type "file")
|
|||
|
;; Treat links to ".org" files as ".rst", if needed.
|
|||
|
(setq raw-path
|
|||
|
(funcall link-org-files-as-rst-maybe raw-path info))
|
|||
|
(cond ((and home use-abs-url)
|
|||
|
(setq raw-path
|
|||
|
(concat (file-name-as-directory home) raw-path)))
|
|||
|
(t raw-path)))
|
|||
|
(t raw-path)))
|
|||
|
(attributes-plist
|
|||
|
(org-combine-plists
|
|||
|
;; Extract attributes from parent's paragraph. HACK: Only
|
|||
|
;; do this for the first link in parent (inner image link
|
|||
|
;; for inline images). This is needed as long as
|
|||
|
;; attributes cannot be set on a per link basis.
|
|||
|
(let* ((parent (org-export-get-parent-element link))
|
|||
|
(link (let ((container (org-export-get-parent link)))
|
|||
|
(if (and (eq 'link (org-element-type container))
|
|||
|
(org-rst-inline-image-p link info))
|
|||
|
container
|
|||
|
link))))
|
|||
|
(and (eq link (org-element-map parent 'link #'identity info t))
|
|||
|
(org-export-read-attribute :attr_rst parent)))
|
|||
|
;; Also add attributes from link itself. Currently, those
|
|||
|
;; need to be added programmatically before `org-rst-link'
|
|||
|
;; is invoked, for example, by backends building upon HTML
|
|||
|
;; export.
|
|||
|
(org-export-read-attribute :attr_rst link)))
|
|||
|
(attributes
|
|||
|
(let ((attr (org-rst--make-attribute-string attributes-plist)))
|
|||
|
(if (org-string-nw-p attr) (concat "\n" attr "\n") ""))))
|
|||
|
(cond
|
|||
|
;; Link type is handled by a special function.
|
|||
|
((org-export-custom-protocol-maybe link desc 'rst))
|
|||
|
;; Image file.
|
|||
|
((and (plist-get info :rst-inline-images)
|
|||
|
(org-export-inline-image-p
|
|||
|
link (plist-get info :rst-inline-image-rules)))
|
|||
|
(let* ((ipath (if (not (file-name-absolute-p raw-path)) raw-path
|
|||
|
(expand-file-name raw-path)))
|
|||
|
(caption (org-export-get-caption
|
|||
|
(org-export-get-parent-element link)))
|
|||
|
(linkname
|
|||
|
(org-element-property :name (org-export-get-parent-element link)))
|
|||
|
(label (if linkname (format ".. _%s:\n\n" linkname) "")))
|
|||
|
(if caption (format "%s.. figure:: %s%s\n\n %s\n"
|
|||
|
label ipath attributes
|
|||
|
(org-export-data caption info))
|
|||
|
(format "%s.. image:: %s%s\n" label ipath attributes))))
|
|||
|
((and (plist-get info :rst-inline-images)
|
|||
|
desc
|
|||
|
(my-org-export-inline-image-p
|
|||
|
desc (plist-get info :rst-inline-image-rules)))
|
|||
|
(format ".. image:: %s\n :target: %s%s" desc path attributes))
|
|||
|
;; Radio link: Transcode target's contents and use them as link's
|
|||
|
;; description.
|
|||
|
((string= type "radio")
|
|||
|
(let ((destination (org-export-resolve-radio-link link info)))
|
|||
|
(when destination
|
|||
|
(format "`%s <%s>`_"
|
|||
|
path
|
|||
|
(org-export-data (org-element-contents destination) info)))))
|
|||
|
;; Links pointing to a headline: Find destination and build
|
|||
|
;; appropriate referencing command.
|
|||
|
((member type '("custom-id" "fuzzy" "id"))
|
|||
|
(let ((destination (if (string= type "fuzzy")
|
|||
|
(org-export-resolve-fuzzy-link link info)
|
|||
|
(org-export-resolve-id-link link info))))
|
|||
|
(cl-case (org-element-type destination)
|
|||
|
;; Id link points to an external file.
|
|||
|
(plain-text
|
|||
|
(if desc (format "`%s <%s>`_" desc destination)
|
|||
|
(format "`%s`_" destination)))
|
|||
|
;; Fuzzy link points nowhere.
|
|||
|
('nil
|
|||
|
(let ((rawlink
|
|||
|
(org-export-data (org-element-property :raw-link link) info)))
|
|||
|
(if desc (format "`%s <%s>`_" desc rawlink)
|
|||
|
(format "`%s`_" rawlink))))
|
|||
|
;; LINK points to a headline.
|
|||
|
(headline
|
|||
|
(if (member type '("custom-id" "id"))
|
|||
|
(if (plist-get info :rst-link-use-ref-role)
|
|||
|
(if desc (format " :ref:`%s <%s>`" desc raw-path)
|
|||
|
(format " :ref:`%s`" raw-path))
|
|||
|
(format "`%s`_" raw-path))
|
|||
|
(format "`%s`_" (org-rst--build-title destination info nil))))
|
|||
|
;; Fuzzy link points to a target.
|
|||
|
(otherwise
|
|||
|
(if (not desc) (format "`%s`_" path)
|
|||
|
(format "`%s <%s>`_" desc path))))))
|
|||
|
;; Coderef: replace link with the reference name or the
|
|||
|
;; equivalent line number. It is not supported in ReST.
|
|||
|
((string= type "coderef")
|
|||
|
(format (org-export-get-coderef-format path desc)
|
|||
|
(org-export-resolve-coderef path info)))
|
|||
|
((and (plist-get info :rst-file-link-use-ref-role)
|
|||
|
(string= type "file")
|
|||
|
search-option)
|
|||
|
(let ((ref (replace-regexp-in-string "^#" "" search-option)))
|
|||
|
(if desc
|
|||
|
(format ":ref:`%s <%s>`" desc ref)
|
|||
|
(format ":ref:`%s`" ref))))
|
|||
|
;; Link type is handled by a special function.
|
|||
|
;((functionp (setq protocol (nth 2 (assoc type org-link-protocols))))
|
|||
|
; (funcall protocol (org-link-unescape path) desc 'latex))
|
|||
|
;; External link with a description part.
|
|||
|
((and path desc) (format "`%s <%s>`_" desc path))
|
|||
|
;; External link without a description part.
|
|||
|
(path (format "`%s <%s>`_"
|
|||
|
(replace-regexp-in-string "^//" "" path) path))
|
|||
|
;; No path, only description. Try to do something useful.
|
|||
|
(t (format "`%s <%s>`_" desc desc)))))
|
|||
|
|
|||
|
|
|||
|
;;;; Node Property
|
|||
|
|
|||
|
(defun org-rst-node-property (node-property _contents _info)
|
|||
|
"Transcode a NODE-PROPERTY element from Org to reStructuredText.
|
|||
|
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-rst-paragraph (_paragraph contents info)
|
|||
|
"Transcode a PARAGRAPH element from Org to reStructuredText.
|
|||
|
CONTENTS is the contents of the paragraph, as a string. INFO is
|
|||
|
the plist used as a communication channel."
|
|||
|
(when (plist-get info :preserve-breaks)
|
|||
|
(let ((lines (split-string contents "\n+[ \t\n]*")))
|
|||
|
(cond ((> (length lines) 2)
|
|||
|
(setq contents (apply 'concat (mapcar
|
|||
|
'(lambda (x) (if (> (length x) 0) (concat "| " x "\n") x))
|
|||
|
lines)))))))
|
|||
|
contents)
|
|||
|
|
|||
|
|
|||
|
;;;; Plain List
|
|||
|
|
|||
|
(defun org-rst-plain-list (_plain-list contents _info)
|
|||
|
"Transcode a PLAIN-LIST element from Org to reStructuredText.
|
|||
|
CONTENTS is the contents of the list. INFO is a plist holding
|
|||
|
contextual information."
|
|||
|
contents)
|
|||
|
|
|||
|
|
|||
|
;;;; Plain Text
|
|||
|
|
|||
|
(defun org-rst-plain-text (text info)
|
|||
|
"Transcode a TEXT string from Org to reStructuredText.
|
|||
|
TEXT is the string to transcode. INFO is a plist holding
|
|||
|
contextual information."
|
|||
|
(when (plist-get info :with-smart-quotes)
|
|||
|
(setq text (org-export-activate-smart-quotes text :utf-8 info)))
|
|||
|
;; Protect `, *, _ and \
|
|||
|
(setq text (replace-regexp-in-string "[`*_\\]" "\\\\\\&" text))
|
|||
|
;; Protect ..
|
|||
|
(setq text (replace-regexp-in-string "^[\s-]*\\.\\. [^\\[]" "\\\\\\&" text))
|
|||
|
;; Protect ::
|
|||
|
(setq text (replace-regexp-in-string "::" "\\\\:\\\\:" text))
|
|||
|
;; Return value.
|
|||
|
text)
|
|||
|
|
|||
|
|
|||
|
;;;; Planning
|
|||
|
|
|||
|
(defun org-rst-planning (planning _contents _info)
|
|||
|
"Transcode a PLANNING element from Org to reStructuredText.
|
|||
|
CONTENTS is nil. INFO is a plist used as a communication
|
|||
|
channel."
|
|||
|
(mapconcat
|
|||
|
'identity
|
|||
|
(delq nil
|
|||
|
(list (let ((closed (org-element-property :closed planning)))
|
|||
|
(when closed
|
|||
|
(concat org-closed-string " "
|
|||
|
(org-translate-time
|
|||
|
(org-element-property :raw-value closed)))))
|
|||
|
(let ((deadline (org-element-property :deadline planning)))
|
|||
|
(when deadline
|
|||
|
(concat org-deadline-string " "
|
|||
|
(org-translate-time
|
|||
|
(org-element-property :raw-value deadline)))))
|
|||
|
(let ((scheduled (org-element-property :scheduled planning)))
|
|||
|
(when scheduled
|
|||
|
(concat org-scheduled-string " "
|
|||
|
(org-translate-time
|
|||
|
(org-element-property :raw-value scheduled)))))))
|
|||
|
" "))
|
|||
|
|
|||
|
|
|||
|
;;;; Property Drawer
|
|||
|
|
|||
|
(defun org-rst-property-drawer (_property-drawer contents _info)
|
|||
|
"Transcode a PROPERTY-DRAWER element from Org to reStructuredText.
|
|||
|
CONTENTS holds the contents of the drawer. INFO is a plist
|
|||
|
holding contextual information."
|
|||
|
(when (org-string-nw-p contents)
|
|||
|
(concat
|
|||
|
"::\n\n"
|
|||
|
(org-rst--indent-string contents org-rst-quote-margin))))
|
|||
|
|
|||
|
|
|||
|
;;;; Pseudo Object: LaTeX Math Block
|
|||
|
|
|||
|
;; `latex-math-block' objects have the following property:
|
|||
|
;; `:post-blank'.
|
|||
|
|
|||
|
(defun org-rst--wrap-latex-math-block (data info)
|
|||
|
"Merge contiguous math objects in a pseudo-object container.
|
|||
|
DATA is a parse tree or a secondary string. INFO is a plist
|
|||
|
containing export options. Modify DATA by side-effect and return it."
|
|||
|
(let ((valid-object-p
|
|||
|
(function
|
|||
|
;; Non-nil when OBJ can be added to the latex math block.
|
|||
|
(lambda (obj)
|
|||
|
(cl-case (org-element-type obj)
|
|||
|
(entity (org-element-property :latex-math-p obj))
|
|||
|
(latex-fragment
|
|||
|
(let ((value (org-element-property :value obj)))
|
|||
|
(or (string-match-p "\\`\\\\([^\000]*\\\\)\\'" value)
|
|||
|
(string-match-p "\\`\\$[^\000]*\\$\\'" value)
|
|||
|
(string-match-p "\\`\\\\\\[[^\000]*\\\\\\]\\'" value))))
|
|||
|
((subscript superscript) t))))))
|
|||
|
(org-element-map data '(entity latex-fragment subscript superscript)
|
|||
|
(lambda (object)
|
|||
|
;; Skip objects already wrapped.
|
|||
|
(when (and (not (eq (org-element-type
|
|||
|
(org-element-property :parent object))
|
|||
|
'latex-math-block))
|
|||
|
(funcall valid-object-p object))
|
|||
|
(let ((math-block (list 'latex-math-block nil))
|
|||
|
(next-elements (org-export-get-next-element object info t))
|
|||
|
(last object))
|
|||
|
;; Wrap MATH-BLOCK around OBJECT in DATA.
|
|||
|
(org-element-insert-before math-block object)
|
|||
|
(org-element-extract-element object)
|
|||
|
(org-element-adopt-elements math-block object)
|
|||
|
(when (zerop (or (org-element-property :post-blank object) 0))
|
|||
|
;; MATH-BLOCK swallows consecutive math objects.
|
|||
|
(catch 'exit
|
|||
|
(dolist (next next-elements)
|
|||
|
(if (not (funcall valid-object-p next)) (throw 'exit nil)
|
|||
|
(org-element-extract-element next)
|
|||
|
(org-element-adopt-elements math-block next)
|
|||
|
;; Eschew the case: \beta$x$ -> \(\betax\).
|
|||
|
(unless (memq (org-element-type next)
|
|||
|
'(subscript superscript))
|
|||
|
(org-element-put-property last :post-blank 1))
|
|||
|
(setq last next)
|
|||
|
(when (> (or (org-element-property :post-blank next) 0) 0)
|
|||
|
(throw 'exit nil))))))
|
|||
|
(org-element-put-property
|
|||
|
math-block :post-blank (org-element-property :post-blank last)))))
|
|||
|
info nil '(subscript superscript latex-math-block) t)
|
|||
|
;; Return updated DATA.
|
|||
|
data))
|
|||
|
|
|||
|
(defun org-rst-math-block-tree-filter (tree _backend info)
|
|||
|
(org-rst--wrap-latex-math-block tree info))
|
|||
|
|
|||
|
(defun org-rst-math-block-options-filter (info _backend)
|
|||
|
(dolist (prop '(:author :date :title) info)
|
|||
|
(plist-put info prop
|
|||
|
(org-rst--wrap-latex-math-block (plist-get info prop) info))))
|
|||
|
|
|||
|
(defun org-rst-math-block (_math-block contents _info)
|
|||
|
"Transcode a MATH-BLOCK object from Org to reStructuredText.
|
|||
|
CONTENTS is a string. INFO is a plist used as a communication
|
|||
|
channel."
|
|||
|
(let* ((value (org-trim contents))
|
|||
|
(value
|
|||
|
(if (> (string-width value) 2)
|
|||
|
(if (string= ".." (substring value 0 2))
|
|||
|
(format "\n\n%s\n\n" value)
|
|||
|
value))))
|
|||
|
(format "%s" value)))
|
|||
|
|
|||
|
|
|||
|
;;;; Quote Block
|
|||
|
|
|||
|
(defun org-rst-quote-block (quote-block contents _info)
|
|||
|
"Transcode a QUOTE-BLOCK element from Org to reStructuredText.
|
|||
|
CONTENTS holds the contents of the block. INFO is a plist
|
|||
|
holding contextual information."
|
|||
|
(let* ((attributes
|
|||
|
(org-export-read-attribute :attr_rst quote-block))
|
|||
|
(directive (plist-get attributes :directive))
|
|||
|
(title (plist-get attributes :title))
|
|||
|
(subtitle (plist-get attributes :subtitle))
|
|||
|
(margin (plist-get attributes :margin))
|
|||
|
(class (plist-get attributes :class))
|
|||
|
(label (org-element-property :name quote-block)))
|
|||
|
(cond ((and margin contents)
|
|||
|
(org-rst--indent-string contents (string-to-number margin)))
|
|||
|
(directive
|
|||
|
(concat
|
|||
|
(format ".. %s::" directive)
|
|||
|
(when title (format " %s" title))
|
|||
|
"\n"
|
|||
|
(when (and subtitle (string= "sidebar" directive))
|
|||
|
(format " :subtitle: %s\n" subtitle))
|
|||
|
(when (and class (not (string= "container" directive)))
|
|||
|
(format " :class: %s\n" class))
|
|||
|
(when label (format " :name: %s\n" label))
|
|||
|
"\n"
|
|||
|
(when contents
|
|||
|
(org-rst--indent-string contents org-rst-quote-margin))))
|
|||
|
(t
|
|||
|
(concat
|
|||
|
"::\n"
|
|||
|
(when class (format " :class: %s\n" class))
|
|||
|
(when label (format " :name: %s\n" label))
|
|||
|
"\n"
|
|||
|
(when contents
|
|||
|
(org-rst--indent-string contents org-rst-quote-margin)))))))
|
|||
|
|
|||
|
|
|||
|
;;;; Radio Target
|
|||
|
|
|||
|
(defun org-rst-radio-target (_radio-target contents _info)
|
|||
|
"Transcode a RADIO-TARGET object from Org to reStructuredText.
|
|||
|
CONTENTS is the contents of the target. INFO is a plist holding
|
|||
|
contextual information."
|
|||
|
contents)
|
|||
|
|
|||
|
|
|||
|
;;;; Section
|
|||
|
|
|||
|
(defun org-rst-section (_section contents _info)
|
|||
|
"Transcode a SECTION element from Org to reStructuredText.
|
|||
|
CONTENTS holds the contents of the section. INFO is a plist
|
|||
|
holding contextual information."
|
|||
|
contents)
|
|||
|
|
|||
|
|
|||
|
;;;; Special Block
|
|||
|
|
|||
|
(defun org-rst-special-block (special-block contents _info)
|
|||
|
"Transcode a SPECIAL-BLOCK element from Org to reStructuredText.
|
|||
|
CONTENTS holds the contents of the block. INFO is a plist
|
|||
|
holding contextual information."
|
|||
|
(let* ((attributes
|
|||
|
(org-export-read-attribute :attr_rst special-block))
|
|||
|
(title (plist-get attributes :title))
|
|||
|
(class (plist-get attributes :class))
|
|||
|
(label (org-element-property :name special-block))
|
|||
|
(type (org-element-property :type special-block)))
|
|||
|
(concat
|
|||
|
(format ".. %s::" type)
|
|||
|
(when title (format " %s" title))
|
|||
|
"\n"
|
|||
|
(when class (format " :class: %s\n" class))
|
|||
|
(when label (format " :name: %s\n" label))
|
|||
|
"\n"
|
|||
|
(when contents
|
|||
|
(org-rst--indent-string contents org-rst-quote-margin)))))
|
|||
|
|
|||
|
;;;; Src Block
|
|||
|
|
|||
|
(defun org-rst-src-block (src-block _contents info)
|
|||
|
"Transcode a SRC-BLOCK element from Org to reStructuredText.
|
|||
|
CONTENTS holds the contents of the item. INFO is a plist holding
|
|||
|
contextual information."
|
|||
|
(when (org-string-nw-p (org-element-property :value src-block))
|
|||
|
(let* ((lang (org-element-property :language src-block))
|
|||
|
(label (org-element-property :name src-block))
|
|||
|
(value (org-remove-indentation
|
|||
|
(org-element-property :value src-block)))
|
|||
|
(num-start (org-export-get-loc src-block info))
|
|||
|
(codeblockd (plist-get info :rst-code-block))
|
|||
|
(attributes
|
|||
|
(org-export-read-attribute :attr_rst src-block))
|
|||
|
(class (plist-get attributes :class)))
|
|||
|
(cond
|
|||
|
;; Case 1.
|
|||
|
((eq codeblockd 'code-block)
|
|||
|
(let ((lst-lang
|
|||
|
(or (cadr (assq (intern lang) org-rst-pygments-langs)) lang)))
|
|||
|
(concat
|
|||
|
(format ".. code-block:: %s\n" lst-lang)
|
|||
|
(when num-start (format " :lineno-start: %s\n" (1+ num-start)))
|
|||
|
(when class (format " :class: %s\n" class))
|
|||
|
(when label (format " :name: %s\n" label))
|
|||
|
"\n"
|
|||
|
(org-rst--indent-string value org-rst-quote-margin))))
|
|||
|
;; Case 2. code.
|
|||
|
((eq codeblockd 'code)
|
|||
|
(let ((lst-lang
|
|||
|
(or (cadr (assq (intern lang) org-rst-pygments-langs)) lang)))
|
|||
|
(concat
|
|||
|
(format ".. code:: %s\n" lst-lang)
|
|||
|
(when num-start (format " :number-lines: %s\n" (1+ num-start)))
|
|||
|
(when class (format " :class: %s\n" class))
|
|||
|
(when label (format " :name: %s\n" label))
|
|||
|
"\n"
|
|||
|
(org-rst--indent-string value org-rst-quote-margin))))
|
|||
|
(t
|
|||
|
(concat
|
|||
|
"::\n"
|
|||
|
(when class (format " :class: %s\n" class))
|
|||
|
(when label (format " :name: %s\n" label))
|
|||
|
"\n"
|
|||
|
(org-rst--indent-string value org-rst-quote-margin)))))))
|
|||
|
|
|||
|
|
|||
|
;;;; Statistics Cookie
|
|||
|
|
|||
|
(defun org-rst-statistics-cookie (statistics-cookie _contents _info)
|
|||
|
"Transcode a STATISTICS-COOKIE object from Org to reStructuredText.
|
|||
|
CONTENTS is nil. INFO is a plist holding contextual information."
|
|||
|
(org-element-property :value statistics-cookie))
|
|||
|
|
|||
|
|
|||
|
;;;; Strike-Through
|
|||
|
|
|||
|
(defun org-rst-strike-through (_strike-through contents _info)
|
|||
|
"Transcode STRIKE-THROUGH from Org to reStructuredText.
|
|||
|
CONTENTS is the text with strike-through markup. INFO is a plist
|
|||
|
holding contextual information."
|
|||
|
contents)
|
|||
|
|
|||
|
|
|||
|
;;;; Subscript
|
|||
|
|
|||
|
(defun org-rst-subscript (_subscript contents _info)
|
|||
|
"Transcode a SUBSCRIPT object from Org to reStructuredText.
|
|||
|
CONTENTS is the contents of the object. INFO is a plist holding
|
|||
|
contextual information."
|
|||
|
(format "\\ :sub:`%s`\\ " contents))
|
|||
|
|
|||
|
|
|||
|
;;;; Superscript
|
|||
|
|
|||
|
(defun org-rst-superscript (_superscript contents _info)
|
|||
|
"Transcode a SUPERSCRIPT object from Org to reStructuredText.
|
|||
|
CONTENTS is the contents of the object. INFO is a plist holding
|
|||
|
contextual information."
|
|||
|
(format "\\ :sup:`%s`\\ " contents))
|
|||
|
|
|||
|
|
|||
|
;;;; Table
|
|||
|
|
|||
|
(defun org-rst-table-first-row-data-cells (table info)
|
|||
|
"Transcode the first row of TABLE.
|
|||
|
INFO is a plist used as a communication channel."
|
|||
|
(let ((table-row
|
|||
|
(org-element-map table 'table-row
|
|||
|
(lambda (row)
|
|||
|
(unless (eq (org-element-property :type row) 'rule) row))
|
|||
|
info 'first-match))
|
|||
|
(special-column-p (org-export-table-has-special-column-p table)))
|
|||
|
(if (not special-column-p) (org-element-contents table-row)
|
|||
|
(cdr (org-element-contents table-row)))))
|
|||
|
|
|||
|
(defun org-rst-table (table contents info)
|
|||
|
"Transcode a TABLE element from Org to reStructuredText.
|
|||
|
CONTENTS is the contents of the table. INFO is a plist holding
|
|||
|
contextual information."
|
|||
|
(let* ((caption (org-export-get-caption table))
|
|||
|
(attributes
|
|||
|
(org-export-read-attribute :attr_rst table))
|
|||
|
(class (plist-get attributes :class))
|
|||
|
(label (org-element-property :name table)))
|
|||
|
(concat
|
|||
|
(if caption (format ".. table:: %s\n" (org-export-data caption info))
|
|||
|
".. table::\n")
|
|||
|
(when class (format " :class: %s\n" class))
|
|||
|
(when label (format " :name: %s\n" label))
|
|||
|
"\n"
|
|||
|
(org-rst--indent-string contents org-rst-quote-margin))))
|
|||
|
|
|||
|
|
|||
|
;;;; Table Cell
|
|||
|
|
|||
|
(defun org-rst--table-cell-width (table-cell info)
|
|||
|
"Return width of TABLE-CELL.
|
|||
|
|
|||
|
INFO is a plist used as a communication channel.
|
|||
|
|
|||
|
Width of a cell is determined either by a width cookie in the
|
|||
|
same column as the cell, or by the maximum cell's length in that
|
|||
|
column."
|
|||
|
(let* ((row (org-export-get-parent table-cell))
|
|||
|
(table (org-export-get-parent row))
|
|||
|
(col (let ((cells (org-element-contents row)))
|
|||
|
(- (length cells) (length (memq table-cell cells)))))
|
|||
|
(cache
|
|||
|
(or (plist-get info :rst-table-cell-width-cache)
|
|||
|
(plist-get (setq info
|
|||
|
(plist-put info :rst-table-cell-width-cache
|
|||
|
(make-hash-table :test 'equal)))
|
|||
|
:rst-table-cell-width-cache)))
|
|||
|
(key (cons table col)))
|
|||
|
(or (gethash key cache)
|
|||
|
(puthash
|
|||
|
key
|
|||
|
(let ((cookie-width (org-export-table-cell-width table-cell info)))
|
|||
|
(or cookie-width
|
|||
|
(let ((contents-width
|
|||
|
(let ((max-width 0))
|
|||
|
(org-element-map table 'table-row
|
|||
|
(lambda (row)
|
|||
|
(setq max-width
|
|||
|
(max (string-width
|
|||
|
(org-export-data
|
|||
|
(org-element-contents
|
|||
|
(elt (org-element-contents row) col))
|
|||
|
info))
|
|||
|
max-width)))
|
|||
|
info)
|
|||
|
max-width)))
|
|||
|
(cond ((not cookie-width) contents-width)
|
|||
|
(t cookie-width)))))
|
|||
|
cache))))
|
|||
|
|
|||
|
|
|||
|
(defun org-rst-table-cell (table-cell contents info)
|
|||
|
"Transcode a TABLE-CELL object from Org to reStructuredText.
|
|||
|
CONTENTS is the cell contents. INFO is a plist used as
|
|||
|
a communication channel."
|
|||
|
(let ((width (org-rst--table-cell-width table-cell info)))
|
|||
|
;; Align contents correctly within the cell.
|
|||
|
(let* ((indent-tabs-mode nil)
|
|||
|
(data
|
|||
|
(if contents
|
|||
|
(org-rst--justify-lines
|
|||
|
contents width
|
|||
|
(org-export-table-cell-alignment table-cell info)) "\\")))
|
|||
|
(setq contents (concat data
|
|||
|
(make-string (- width (string-width data)) ? ))))
|
|||
|
;; Return cell.
|
|||
|
(concat (format " %s " contents)
|
|||
|
(when (org-export-get-next-element table-cell info) "|"))))
|
|||
|
|
|||
|
|
|||
|
;;;; Table Row
|
|||
|
|
|||
|
(defun org-rst-table-row (table-row contents info)
|
|||
|
"Transcode a TABLE-ROW element from Org to reStructuredText.
|
|||
|
CONTENTS is the row contents. INFO is a plist used as
|
|||
|
a communication channel."
|
|||
|
(let ((borders (org-export-table-cell-borders
|
|||
|
(org-element-map table-row 'table-cell 'identity info t)
|
|||
|
info)))
|
|||
|
(if (not (and (memq 'bottom borders) (memq 'top borders)))
|
|||
|
(let* ((rowgroup-number (org-export-table-row-group table-row info))
|
|||
|
(row-number (org-export-table-row-number table-row info))
|
|||
|
(line-bit
|
|||
|
(cond
|
|||
|
((not (= 1 rowgroup-number))
|
|||
|
?-)
|
|||
|
((org-export-table-has-header-p
|
|||
|
(org-export-get-parent-table table-row) info)
|
|||
|
?=)
|
|||
|
(t ?-)))
|
|||
|
(makeline
|
|||
|
(function
|
|||
|
(lambda (_rowcontents linebit)
|
|||
|
(format "+%s+"
|
|||
|
(mapconcat
|
|||
|
'identity
|
|||
|
(mapcar
|
|||
|
(lambda (table-cell)
|
|||
|
(make-string (string-width table-cell)
|
|||
|
linebit))
|
|||
|
(split-string contents "|"))
|
|||
|
"+")))))
|
|||
|
(hline (format "+%s+"
|
|||
|
(mapconcat
|
|||
|
'identity
|
|||
|
(mapcar
|
|||
|
(lambda (table-cell)
|
|||
|
(make-string (string-width table-cell)
|
|||
|
line-bit))
|
|||
|
(split-string contents "|"))
|
|||
|
"+"))))
|
|||
|
(concat
|
|||
|
(when (= 0 row-number)
|
|||
|
(concat (funcall makeline contents ?-) "\n"))
|
|||
|
"|" contents "|\n" hline))
|
|||
|
nil
|
|||
|
)))
|
|||
|
|
|||
|
|
|||
|
;;;; Target
|
|||
|
|
|||
|
(defun org-rst-target (target _contents _info)
|
|||
|
"Transcode a TARGET object from Org to reStructuredText.
|
|||
|
CONTENTS is nil. INFO is a plist holding contextual
|
|||
|
information."
|
|||
|
(format " _`%s` " (org-element-property :value target)))
|
|||
|
|
|||
|
|
|||
|
;;;; Timestamp
|
|||
|
|
|||
|
(defun org-rst-timestamp (timestamp _contents info)
|
|||
|
"Transcode a TIMESTAMP object from Org to reStructuredText.
|
|||
|
CONTENTS is nil. INFO is a plist holding contextual
|
|||
|
information."
|
|||
|
(org-rst-plain-text (org-timestamp-translate timestamp) info))
|
|||
|
|
|||
|
|
|||
|
;;;; Underline
|
|||
|
|
|||
|
(defun org-rst-underline (_underline contents _info)
|
|||
|
"Transcode UNDERLINE from Org to reStructuredText.
|
|||
|
CONTENTS is the text with underline markup. INFO is a plist
|
|||
|
holding contextual information."
|
|||
|
contents)
|
|||
|
|
|||
|
|
|||
|
;;;; Verbatim
|
|||
|
|
|||
|
(defun org-rst-verbatim (verbatim _contents info)
|
|||
|
"Transcode a VERBATIM object from Org to reStructredText.
|
|||
|
CONTENTS is nil. INFO is a plist used as a communication
|
|||
|
channel."
|
|||
|
(org-rst--text-markup (org-element-property :value verbatim) 'verbatim info))
|
|||
|
|
|||
|
|
|||
|
;;;; Verse Block
|
|||
|
|
|||
|
(defun org-rst-verse-block (_verse-block contents _info)
|
|||
|
"Transcode a VERSE-BLOCK element from Org to reStructuredText.
|
|||
|
CONTENTS is verse block contents. INFO is a plist holding
|
|||
|
contextual information."
|
|||
|
(let ((lines (split-string contents "\n")))
|
|||
|
(cond ((> (length lines) 0)
|
|||
|
(mapconcat
|
|||
|
(function (lambda (x) (if (> (string-width x) 0)
|
|||
|
(concat "| " x "\n") ""))) lines "")))))
|
|||
|
|
|||
|
|
|||
|
;;; Filters
|
|||
|
|
|||
|
(defun org-rst-separate-elements (tree _backend _info)
|
|||
|
"Make sure elements are separated by at least one blank line.
|
|||
|
|
|||
|
TREE is the parse tree being exported. BACKEND is the export
|
|||
|
back-end used. INFO is a plist used as a communication channel.
|
|||
|
|
|||
|
Assume BACKEND is `rst'."
|
|||
|
(org-element-map tree org-element-all-elements
|
|||
|
(lambda (elem)
|
|||
|
(unless (or (eq (org-element-type elem) 'org-data)
|
|||
|
(eq (org-element-type elem) 'table-row))
|
|||
|
(org-element-put-property
|
|||
|
elem :post-blank
|
|||
|
(let ((post-blank (org-element-property :post-blank elem)))
|
|||
|
(if (not post-blank) 1 (max 1 post-blank)))))))
|
|||
|
;; Return updated tree.
|
|||
|
tree)
|
|||
|
|
|||
|
(defun org-rst-filter-headline-blank-lines (headline _back-end _info)
|
|||
|
"Filter controlling number of blank lines after a headline.
|
|||
|
|
|||
|
HEADLINE is a string representing a transcoded headline.
|
|||
|
BACK-END is symbol specifying back-end used for export. INFO is
|
|||
|
plist containing the communication channel.
|
|||
|
|
|||
|
This function only applies to `rst' back-end. See
|
|||
|
`org-rst-headline-spacing' for information."
|
|||
|
(if (not org-rst-headline-spacing) headline
|
|||
|
(let ((blanks (make-string (1+ (cdr org-rst-headline-spacing)) ?\n)))
|
|||
|
(replace-regexp-in-string "\n\\(?:\n[ \t]*\\)*\\'" blanks headline))))
|
|||
|
|
|||
|
(defun org-rst-filter-paragraph-spacing (tree _back-end info)
|
|||
|
"Filter controlling number of blank lines between paragraphs.
|
|||
|
|
|||
|
TREE is the parse tree. BACK-END is the symbol specifying
|
|||
|
back-end used for export. INFO is a plist used as
|
|||
|
a communication channel.
|
|||
|
|
|||
|
See `org-rst-paragraph-spacing' for information."
|
|||
|
(when (wholenump org-rst-paragraph-spacing)
|
|||
|
(org-element-map tree 'paragraph
|
|||
|
(lambda (p)
|
|||
|
(when (eq (org-element-type (org-export-get-next-element p info))
|
|||
|
'paragraph)
|
|||
|
(org-element-put-property
|
|||
|
p :post-blank org-rst-paragraph-spacing)))))
|
|||
|
tree)
|
|||
|
|
|||
|
|
|||
|
|
|||
|
;;; End-user functions
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun org-rst-export-as-rst
|
|||
|
(&optional async subtreep visible-only body-only ext-plist)
|
|||
|
"Export current buffer to a reStructuredText buffer.
|
|||
|
|
|||
|
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 buffer 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.
|
|||
|
|
|||
|
Export is done in a buffer named \"*Org RST Export*\", which will
|
|||
|
be displayed when `org-export-show-temporary-export-buffer' is
|
|||
|
non-nil."
|
|||
|
(interactive)
|
|||
|
(org-export-to-buffer 'rst "*Org RST Export*"
|
|||
|
async subtreep visible-only body-only ext-plist (lambda () (rst-mode))))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun org-rst-convert-region-to-rst ()
|
|||
|
"Assume the current region has Org syntax, and convert it to
|
|||
|
reStructuredText.
|
|||
|
This can be used in any buffer. For example, you can write an
|
|||
|
itemized list in Org syntax in a Markdown buffer and use this command
|
|||
|
to convert it."
|
|||
|
(interactive)
|
|||
|
(org-export-replace-region-by 'rst))
|
|||
|
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun org-rst-export-to-rst
|
|||
|
(&optional async subtreep visible-only body-only ext-plist)
|
|||
|
"Export current buffer to a reStructuredText 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.
|
|||
|
|
|||
|
Return output file's name."
|
|||
|
(interactive)
|
|||
|
(let* ((extension (concat "." (or (plist-get ext-plist :rst-extension)
|
|||
|
org-rst-extension
|
|||
|
"rst")))
|
|||
|
(file (org-export-output-file-name extension subtreep)))
|
|||
|
(org-export-to-file 'rst file
|
|||
|
async subtreep visible-only body-only ext-plist)))
|
|||
|
|
|||
|
;;;###autoload
|
|||
|
(defun org-rst-publish-to-rst (plist filename pub-dir)
|
|||
|
"Publish an org file to reStructuredText.
|
|||
|
|
|||
|
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 'rst filename ".rst" plist pub-dir))
|
|||
|
|
|||
|
|
|||
|
;;; provide
|
|||
|
|
|||
|
(provide 'ox-rst)
|
|||
|
|
|||
|
;;; ox-rst.el ends here
|