From ec84430cf4e09ba25ec675debdf802bc28111e06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Delafond?= Date: Mon, 7 Nov 2016 10:41:54 +0100 Subject: Imported Upstream version 9.0 --- lisp/ox-md.el | 161 +++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 113 insertions(+), 48 deletions(-) (limited to 'lisp/ox-md.el') 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 "%s" + "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)) -- cgit v1.2.3