summaryrefslogtreecommitdiff
path: root/lisp/ox-md.el
diff options
context:
space:
mode:
authorSébastien Delafond <sdelafond@gmail.com>2016-11-07 10:41:54 +0100
committerSébastien Delafond <sdelafond@gmail.com>2016-11-07 10:41:54 +0100
commit1a7cd65ca362047cd97d4127d49108994baebc6c (patch)
treef18735f3b77120ef2e91043f2f662b03e47ccbb0 /lisp/ox-md.el
parent55074078ca876273e3fa58ee6838cba90d2b6100 (diff)
parentec84430cf4e09ba25ec675debdf802bc28111e06 (diff)
Merge tag 'upstream/9.0'
Upstream version 9.0
Diffstat (limited to 'lisp/ox-md.el')
-rw-r--r--lisp/ox-md.el161
1 files changed, 113 insertions, 48 deletions
diff --git a/lisp/ox-md.el b/lisp/ox-md.el
index 0aaade6..b8c4704 100644
--- a/lisp/ox-md.el
+++ b/lisp/ox-md.el
@@ -1,4 +1,4 @@
-;;; ox-md.el --- Markdown Back-End for Org Export Engine
+;;; ox-md.el --- Markdown Back-End for Org Export Engine -*- lexical-binding: t; -*-
;; Copyright (C) 2012-2016 Free Software Foundation, Inc.
@@ -28,7 +28,7 @@
;;; Code:
-(eval-when-compile (require 'cl))
+(require 'cl-lib)
(require 'ox-html)
(require 'ox-publish)
@@ -51,11 +51,29 @@ This variable can be set to either `atx' or `setext'."
(const :tag "Use \"Setext\" style" setext)))
+;;;; Footnotes
+
+(defcustom org-md-footnotes-section "%s%s"
+ "Format string for the footnotes section.
+The first %s placeholder will be replaced with the localized Footnotes section
+heading, the second with the contents of the Footnotes section."
+ :group 'org-export-md
+ :type 'string
+ :version "25.2"
+ :package-version '(Org . "9.0"))
+
+(defcustom org-md-footnote-format "<sup>%s</sup>"
+ "Format string for the footnote reference.
+The %s will be replaced by the footnote reference itself."
+ :group 'org-export-md
+ :type 'string
+ :version "25.2"
+ :package-version '(Org . "9.0"))
+
;;; Define Back-End
(org-export-define-derived-backend 'md 'html
- :export-block '("MD" "MARKDOWN")
:filters-alist '((:filter-parse-tree . org-md-separate-elements))
:menu-entry
'(?m "Export to Markdown"
@@ -90,12 +108,15 @@ This variable can be set to either `atx' or `setext'."
(src-block . org-md-example-block)
(template . org-md-template)
(verbatim . org-md-verbatim))
- :options-alist '((:md-headline-style nil nil org-md-headline-style)))
+ :options-alist
+ '((:md-footnote-format nil nil org-md-footnote-format)
+ (:md-footnotes-section nil nil org-md-footnotes-section)
+ (:md-headline-style nil nil org-md-headline-style)))
;;; Filters
-(defun org-md-separate-elements (tree backend info)
+(defun org-md-separate-elements (tree _backend info)
"Fix blank lines between elements.
TREE is the parse tree being exported. BACKEND is the export
@@ -132,7 +153,7 @@ Assume BACKEND is `md'."
;;;; Bold
-(defun org-md-bold (bold contents info)
+(defun org-md-bold (_bold contents _info)
"Transcode BOLD object into Markdown format.
CONTENTS is the text within bold markup. INFO is a plist used as
a communication channel."
@@ -141,14 +162,14 @@ a communication channel."
;;;; Code and Verbatim
-(defun org-md-verbatim (verbatim contents info)
+(defun org-md-verbatim (verbatim _contents _info)
"Transcode VERBATIM object into Markdown format.
CONTENTS is nil. INFO is a plist used as a communication
channel."
(let ((value (org-element-property :value verbatim)))
(format (cond ((not (string-match "`" value)) "`%s`")
- ((or (string-match "\\``" value)
- (string-match "`\\'" value))
+ ((or (string-prefix-p "`" value)
+ (string-suffix-p "`" value))
"`` %s ``")
(t "``%s``"))
value)))
@@ -156,7 +177,7 @@ channel."
;;;; Example Block, Src Block and export Block
-(defun org-md-example-block (example-block contents info)
+(defun org-md-example-block (example-block _contents info)
"Transcode EXAMPLE-BLOCK element into Markdown format.
CONTENTS is nil. INFO is a plist used as a communication
channel."
@@ -216,24 +237,33 @@ a communication channel."
(car (last (org-export-get-headline-number
headline info))))
"."))))
- (concat bullet (make-string (- 4 (length bullet)) ?\s) heading tags
- "\n\n"
- (and contents
- (replace-regexp-in-string "^" " " contents)))))
- ;; Use "Setext" style.
- ((eq style 'setext)
- (concat heading tags anchor "\n"
- (make-string (length heading) (if (= level 1) ?= ?-))
- "\n\n"
- contents))
- ;; Use "atx" style.
- (t (concat (make-string level ?#) " " heading tags anchor "\n\n"
- contents))))))
-
+ (concat bullet (make-string (- 4 (length bullet)) ?\s) heading tags "\n\n"
+ (and contents (replace-regexp-in-string "^" " " contents)))))
+ (t (concat (org-md--headline-title style level title anchor tags) contents))))))
+
+
+;; Headline Title
+
+(defun org-md--headline-title (style level title &optional anchor tags)
+ "Generate a headline title in the preferred Markdown headline style.
+STYLE is the preferred style (`atx' or `setext'). LEVEL is the
+header level. TITLE is the headline title. ANCHOR is the HTML
+anchor tag for the section as a string. TAGS are the tags set on
+the section."
+ (let ((anchor-lines (and anchor (concat anchor "\n\n"))))
+ ;; Use "Setext" style
+ (if (and (eq style 'setext) (< level 3))
+ (let* ((underline-char (if (= level 1) ?= ?-))
+ (underline (concat (make-string (length title) underline-char)
+ "\n")))
+ (concat "\n" anchor-lines title tags "\n" underline "\n"))
+ ;; Use "Atx" style
+ (let ((level-mark (make-string level ?#)))
+ (concat "\n" anchor-lines level-mark " " title tags "\n\n")))))
;;;; Horizontal Rule
-(defun org-md-horizontal-rule (horizontal-rule contents info)
+(defun org-md-horizontal-rule (_horizontal-rule _contents _info)
"Transcode HORIZONTAL-RULE element into Markdown format.
CONTENTS is the horizontal rule contents. INFO is a plist used
as a communication channel."
@@ -242,7 +272,7 @@ as a communication channel."
;;;; Italic
-(defun org-md-italic (italic contents info)
+(defun org-md-italic (_italic contents _info)
"Transcode ITALIC object into Markdown format.
CONTENTS is the text within italic markup. INFO is a plist used
as a communication channel."
@@ -267,10 +297,10 @@ a communication channel."
"."))))
(concat bullet
(make-string (- 4 (length bullet)) ? )
- (case (org-element-property :checkbox item)
- (on "[X] ")
- (trans "[-] ")
- (off "[ ] "))
+ (pcase (org-element-property :checkbox item)
+ (`on "[X] ")
+ (`trans "[-] ")
+ (`off "[ ] "))
(let ((tag (org-element-property :tag item)))
(and tag (format "**%s:** "(org-export-data tag info))))
(and contents
@@ -291,7 +321,7 @@ channel."
;;;; Line Break
-(defun org-md-line-break (line-break contents info)
+(defun org-md-line-break (_line-break _contents _info)
"Transcode LINE-BREAK object into Markdown format.
CONTENTS is nil. INFO is a plist used as a communication
channel."
@@ -318,12 +348,12 @@ a communication channel."
(let ((destination (if (string= type "fuzzy")
(org-export-resolve-fuzzy-link link info)
(org-export-resolve-id-link link info))))
- (case (org-element-type destination)
- (plain-text ; External file.
+ (pcase (org-element-type destination)
+ (`plain-text ; External file.
(let ((path (funcall link-org-files-as-md destination)))
(if (not contents) (format "<%s>" path)
(format "[%s](%s)" contents path))))
- (headline
+ (`headline
(format
"[%s](#%s)"
;; Description.
@@ -337,7 +367,7 @@ a communication channel."
;; Reference.
(or (org-element-property :CUSTOM_ID destination)
(org-export-get-reference destination info))))
- (t
+ (_
(let ((description
(or (org-string-nw-p contents)
(let ((number (org-export-get-ordinal destination info)))
@@ -378,7 +408,7 @@ a communication channel."
;;;; Node Property
-(defun org-md-node-property (node-property contents info)
+(defun org-md-node-property (node-property _contents _info)
"Transcode a NODE-PROPERTY element into Markdown syntax.
CONTENTS is nil. INFO is a plist holding contextual
information."
@@ -390,20 +420,20 @@ information."
;;;; Paragraph
-(defun org-md-paragraph (paragraph contents info)
+(defun org-md-paragraph (paragraph contents _info)
"Transcode PARAGRAPH element into Markdown format.
CONTENTS is the paragraph contents. INFO is a plist used as
a communication channel."
(let ((first-object (car (org-element-contents paragraph))))
;; If paragraph starts with a #, protect it.
- (if (and (stringp first-object) (string-match "\\`#" first-object))
- (replace-regexp-in-string "\\`#" "\\#" contents nil t)
+ (if (and (stringp first-object) (string-prefix-p "#" first-object))
+ (concat "\\" contents)
contents)))
;;;; Plain List
-(defun org-md-plain-list (plain-list contents info)
+(defun org-md-plain-list (_plain-list contents _info)
"Transcode PLAIN-LIST element into Markdown format.
CONTENTS is the plain-list contents. INFO is a plist used as
a communication channel."
@@ -438,7 +468,7 @@ contextual information."
;;;; Property Drawer
-(defun org-md-property-drawer (property-drawer contents info)
+(defun org-md-property-drawer (_property-drawer contents _info)
"Transcode a PROPERTY-DRAWER element into Markdown format.
CONTENTS holds the contents of the drawer. INFO is a plist
holding contextual information."
@@ -448,7 +478,7 @@ holding contextual information."
;;;; Quote Block
-(defun org-md-quote-block (quote-block contents info)
+(defun org-md-quote-block (_quote-block contents _info)
"Transcode QUOTE-BLOCK element into Markdown format.
CONTENTS is the quote-block contents. INFO is a plist used as
a communication channel."
@@ -459,7 +489,7 @@ a communication channel."
;;;; Section
-(defun org-md-section (section contents info)
+(defun org-md-section (_section contents _info)
"Transcode SECTION element into Markdown format.
CONTENTS is the section contents. INFO is a plist used as
a communication channel."
@@ -468,15 +498,50 @@ a communication channel."
;;;; Template
+(defun org-md--footnote-formatted (footnote info)
+ "Formats a single footnote entry FOOTNOTE.
+FOOTNOTE is a cons cell of the form (number . definition).
+INFO is a plist with contextual information."
+ (let* ((fn-num (car footnote))
+ (fn-text (cdr footnote))
+ (fn-format (plist-get info :md-footnote-format))
+ (fn-anchor (format "fn.%d" fn-num))
+ (fn-href (format " href=\"#fnr.%d\"" fn-num))
+ (fn-link-to-ref (org-html--anchor fn-anchor fn-num fn-href info)))
+ (concat (format fn-format fn-link-to-ref) " " fn-text "\n")))
+
+(defun org-md--footnote-section (info)
+ "Format the footnote section.
+INFO is a plist used as a communication channel."
+ (let* ((fn-alist (org-export-collect-footnote-definitions info))
+ (fn-alist (cl-loop for (n _type raw) in fn-alist collect
+ (cons n (org-trim (org-export-data raw info)))))
+ (headline-style (plist-get info :md-headline-style))
+ (section-title (org-html--translate "Footnotes" info)))
+ (when fn-alist
+ (format (plist-get info :md-footnotes-section)
+ (org-md--headline-title headline-style 1 section-title)
+ (mapconcat (lambda (fn) (org-md--footnote-formatted fn info))
+ fn-alist
+ "\n")))))
+
(defun org-md-inner-template (contents info)
"Return body of document after converting it to Markdown syntax.
CONTENTS is the transcoded contents string. INFO is a plist
holding export options."
;; Make sure CONTENTS is separated from table of contents and
;; footnotes with at least a blank line.
- (org-trim (org-html-inner-template (concat "\n" contents "\n") info)))
-
-(defun org-md-template (contents info)
+ (concat
+ ;; Table of contents.
+ (let ((depth (plist-get info :with-toc)))
+ (when depth (org-html-toc depth info)))
+ ;; Document contents.
+ contents
+ "\n"
+ ;; Footnotes section.
+ (org-md--footnote-section info)))
+
+(defun org-md-template (contents _info)
"Return complete document string after Markdown conversion.
CONTENTS is the transcoded contents string. INFO is a plist used
as a communication channel."
@@ -515,9 +580,9 @@ non-nil."
;;;###autoload
(defun org-md-convert-region-to-md ()
- "Assume the current region has org-mode syntax, and convert it to Markdown.
+ "Assume the current region has Org syntax, and convert it to Markdown.
This can be used in any buffer. For example, you can write an
-itemized list in org-mode syntax in a Markdown buffer and use
+itemized list in Org syntax in a Markdown buffer and use
this command to convert it."
(interactive)
(org-export-replace-region-by 'md))