Compare commits

..

No commits in common. "master" and "v1.0" have entirely different histories.
master ... v1.0

110
ox-gfm.el
View File

@ -1,4 +1,4 @@
;;; ox-gfm.el --- Github Flavored Markdown Back-End for Org Export Engine -*- lexical-binding: t; -*- ;;; ox-gfm.el --- Github Flavored Markdown Back-End for Org Export Engine
;; Copyright (C) 2014-2017 Lars Tveito ;; Copyright (C) 2014-2017 Lars Tveito
@ -29,7 +29,6 @@
(require 'ox-md) (require 'ox-md)
(require 'ox-publish) (require 'ox-publish)
;;; User-Configurable Variables ;;; User-Configurable Variables
@ -57,12 +56,12 @@
:translate-alist '((inner-template . org-gfm-inner-template) :translate-alist '((inner-template . org-gfm-inner-template)
(paragraph . org-gfm-paragraph) (paragraph . org-gfm-paragraph)
(strike-through . org-gfm-strike-through) (strike-through . org-gfm-strike-through)
(example-block . org-gfm-example-block)
(src-block . org-gfm-src-block) (src-block . org-gfm-src-block)
(table-cell . org-gfm-table-cell) (table-cell . org-gfm-table-cell)
(table-row . org-gfm-table-row) (table-row . org-gfm-table-row)
(table . org-gfm-table))) (table . org-gfm-table)))
;;; Transcode Functions ;;; Transcode Functions
@ -80,11 +79,12 @@ communication channel."
(replace-regexp-in-string "\\`#" "\\#" contents nil t) (replace-regexp-in-string "\\`#" "\\#" contents nil t)
contents))) contents)))
;;;; Src Block ;;;; Src Block
(defun org-gfm-src-block (src-block _contents info) (defun org-gfm-src-block (src-block contents info)
"Transcode SRC-BLOCK element into Github Flavored Markdown format. "Transcode SRC-BLOCK element into Github Flavored Markdown
_CONTENTS is nil. INFO is a plist used as a communication format. CONTENTS is nil. INFO is a plist used as a communication
channel." channel."
(let* ((lang (org-element-property :language src-block)) (let* ((lang (org-element-property :language src-block))
(code (org-export-format-code-default src-block info)) (code (org-export-format-code-default src-block info))
@ -92,18 +92,16 @@ channel."
(suffix "```")) (suffix "```"))
(concat prefix code suffix))) (concat prefix code suffix)))
;;;; Example Block
(defalias 'org-gfm-example-block #'org-gfm-src-block)
;;;; Strike-Through ;;;; Strike-Through
(defun org-gfm-strike-through (_strike-through contents _info) (defun org-gfm-strike-through (strike-through contents info)
"Transcode _STRIKE-THROUGH from Org to Markdown (GFM). "Transcode STRIKE-THROUGH from Org to Markdown (GFM).
CONTENTS is the text with strike-through markup. _INFO is a plist CONTENTS is the text with strike-through markup. INFO is a plist
holding contextual information." holding contextual information."
(format "~~%s~~" contents)) (format "~~%s~~" contents))
;;;; Table-Common ;;;; Table-Common
(defvar width-cookies nil) (defvar width-cookies nil)
@ -114,10 +112,10 @@ holding contextual information."
(defconst gfm-table-separator " |") (defconst gfm-table-separator " |")
(defun org-gfm-table-col-width (table column info) (defun org-gfm-table-col-width (table column info)
"Return width of TABLE at given COLUMN. "Return width of TABLE at given COLUMN. INFO is a plist used as
INFO is a plist used as communication channel. Width of a column communication channel. Width of a column is determined either by
is determined either by inquerying `width-cookies' in the column, inquerying `width-cookies' in the column, or by the maximum cell with in
or by the maximum cell with in the column." the column."
(let ((cookie (when (hash-table-p width-cookies) (let ((cookie (when (hash-table-p width-cookies)
(gethash column width-cookies)))) (gethash column width-cookies))))
(if (and (eq table width-cookies-table) (if (and (eq table width-cookies-table)
@ -146,21 +144,22 @@ or by the maximum cell with in the column."
info) info)
(puthash column max-width width-cookies)))))) (puthash column max-width width-cookies))))))
(defun org-gfm-make-hline-builder (table info char) (defun org-gfm-make-hline-builder (table info char)
"Return a function to build horizontal line in TABLE with given CHAR. "Return a function to build horizontal line in TABLE with given
INFO is a plist used as a communication channel." CHAR. INFO is a plist used as a communication channel."
(lambda (col) `(lambda (col)
(let ((max-width (max 3 (org-gfm-table-col-width table col info)))) (let ((max-width (max 3 (org-gfm-table-col-width table col info))))
(when (< max-width 1) (when (< max-width 1)
(setq max-width 1)) (setq max-width 1))
(make-string max-width char)))) (make-string max-width ,char))))
;;;; Table-Cell ;;;; Table-Cell
(defun org-gfm-table-cell (table-cell contents info) (defun org-gfm-table-cell (table-cell contents info)
"Transcode TABLE-CELL element from Org into GFM. "Transcode TABLE-CELL element from Org into GFM. CONTENTS is content
CONTENTS is content of the cell. INFO is a plist used as a of the cell. INFO is a plist used as a communication channel."
communication channel."
(let* ((table (org-export-get-parent-table table-cell)) (let* ((table (org-export-get-parent-table table-cell))
(column (cdr (org-export-table-cell-address table-cell info))) (column (cdr (org-export-table-cell-address table-cell info)))
(width (org-gfm-table-col-width table column info)) (width (org-gfm-table-col-width table column info))
@ -173,12 +172,13 @@ communication channel."
?\s))) ?\s)))
(concat left-border contents right-border))) (concat left-border contents right-border)))
;;;; Table-Row ;;;; Table-Row
(defun org-gfm-table-row (table-row contents info) (defun org-gfm-table-row (table-row contents info)
"Transcode TABLE-ROW element from Org into GFM. "Transcode TABLE-ROW element from Org into GFM. CONTENTS is cell
CONTENTS is cell contents of TABLE-ROW. INFO is a plist used as a contents of TABLE-ROW. INFO is a plist used as a communication
communication channel." channel."
(let ((table (org-export-get-parent-table table-row))) (let ((table (org-export-get-parent-table table-row)))
(when (and (eq 'rule (org-element-property :type table-row)) (when (and (eq 'rule (org-element-property :type table-row))
;; In GFM, rule is valid only at second row. ;; In GFM, rule is valid only at second row.
@ -186,6 +186,7 @@ communication channel."
table-row table-row
(org-element-map table 'table-row 'identity info)))) (org-element-map table 'table-row 'identity info))))
(let* ((table (org-export-get-parent-table table-row)) (let* ((table (org-export-get-parent-table table-row))
(header-p (org-export-table-row-starts-header-p table-row info))
(build-rule (org-gfm-make-hline-builder table info ?-)) (build-rule (org-gfm-make-hline-builder table info ?-))
(cols (cdr (org-export-table-dimensions table info)))) (cols (cdr (org-export-table-dimensions table info))))
(setq contents (setq contents
@ -196,6 +197,8 @@ communication channel."
gfm-table-right-border)))) gfm-table-right-border))))
contents)) contents))
;;;; Table ;;;; Table
(defun org-gfm-table (table contents info) (defun org-gfm-table (table contents info)
@ -206,6 +209,7 @@ contextual information."
(no-header (or (<= (length rows) 1))) (no-header (or (<= (length rows) 1)))
(cols (cdr (org-export-table-dimensions table info))) (cols (cdr (org-export-table-dimensions table info)))
(build-dummy-header (build-dummy-header
(function
(lambda () (lambda ()
(let ((build-empty-cell (org-gfm-make-hline-builder table info ?\s)) (let ((build-empty-cell (org-gfm-make-hline-builder table info ?\s))
(build-rule (org-gfm-make-hline-builder table info ?-)) (build-rule (org-gfm-make-hline-builder table info ?-))
@ -218,14 +222,16 @@ contextual information."
(mapconcat (lambda (col) (funcall build-rule col)) (mapconcat (lambda (col) (funcall build-rule col))
columns columns
gfm-table-separator) gfm-table-separator)
gfm-table-right-border "\n"))))) gfm-table-right-border "\n"))))))
(concat (and no-header (funcall build-dummy-header)) (concat (when no-header (funcall build-dummy-header))
(replace-regexp-in-string "\n\n" "\n" contents)))) (replace-regexp-in-string "\n\n" "\n" contents))))
;;;; Table of contents ;;;; Table of contents
(defun org-gfm-format-toc (headline info) (defun org-gfm-format-toc (headline)
"Return an appropriate table of contents entry for HEADLINE." "Return an appropriate table of contents entry for HEADLINE. INFO is a
plist used as a communication channel."
(let* ((title (org-export-data (let* ((title (org-export-data
(org-export-get-alt-title headline info) info)) (org-export-get-alt-title headline info) info))
(level (1- (org-element-property :level headline))) (level (1- (org-element-property :level headline)))
@ -234,25 +240,38 @@ contextual information."
(org-export-get-reference headline info)))) (org-export-get-reference headline info))))
(concat indent "- [" title "]" "(#" anchor ")"))) (concat indent "- [" title "]" "(#" anchor ")")))
;;;; Footnote section ;;;; Footnote section
(defun org-gfm-footnote-section (info) (defun org-gfm-footnote-section (info)
"Format the footnote section. "Format the footnote section.
INFO is a plist used as a communication channel." INFO is a plist used as a communication channel."
(and-let* ((fn-alist (org-export-collect-footnote-definitions info))) (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))))))
(when fn-alist
(format (format
"## Footnotes\n\n%s\n" "## %s\n%s"
(mapconcat (pcase-lambda (`(,n ,_type ,def)) "Footnotes"
(format
"\n%s\n"
(mapconcat
(lambda (fn)
(let ((n (car fn)) (def (cdr fn)))
(format (format
"%s %s\n" "%s %s\n"
(format (plist-get info :html-footnote-format) (format
(plist-get info :html-footnote-format)
(org-html--anchor (org-html--anchor
(format "fn.%d" n) (format "fn.%d" n)
n n
(format " class=\"footnum\" href=\"#fnr.%d\"" n) (format " class=\"footnum\" href=\"#fnr.%d\"" n)
info)) info))
(org-trim (org-export-data def info)))) def)))
fn-alist "\n")))) fn-alist
"\n"))))))
;;;; Template ;;;; Template
@ -262,13 +281,13 @@ CONTENTS is the transcoded contents string. INFO is a plist
holding export options." holding export options."
(let* ((depth (plist-get info :with-toc)) (let* ((depth (plist-get info :with-toc))
(headlines (and depth (org-export-collect-headlines info depth))) (headlines (and depth (org-export-collect-headlines info depth)))
(toc-string (or (mapconcat (lambda (headline) (toc-string (or (mapconcat 'org-gfm-format-toc headlines "\n") ""))
(org-gfm-format-toc headline info))
headlines "\n")
""))
(toc-tail (if headlines "\n\n" ""))) (toc-tail (if headlines "\n\n" "")))
(org-trim (concat toc-string toc-tail contents "\n" (org-gfm-footnote-section info))))) (org-trim (concat toc-string toc-tail contents "\n" (org-gfm-footnote-section info)))))
;;; Interactive function ;;; Interactive function
@ -299,16 +318,17 @@ non-nil."
(org-export-to-buffer 'gfm "*Org GFM Export*" (org-export-to-buffer 'gfm "*Org GFM Export*"
async subtreep visible-only nil nil (lambda () (text-mode)))) async subtreep visible-only nil nil (lambda () (text-mode))))
;;;###autoload ;;;###autoload
(defun org-gfm-convert-region-to-md () (defun org-gfm-convert-region-to-md ()
"Convert the region to Github Flavored Markdown. "Assume the current region has org-mode syntax, and convert it
This can be used in any buffer, this function assume that the to Github Flavored Markdown. This can be used in any buffer.
current region has org-mode syntax. For example, you can write For example, you can write an itemized list in org-mode syntax in
an itemized list in org-mode syntax in a Markdown buffer and use a Markdown buffer and use this command to convert it."
this command to convert it."
(interactive) (interactive)
(org-export-replace-region-by 'gfm)) (org-export-replace-region-by 'gfm))
;;;###autoload ;;;###autoload
(defun org-gfm-export-to-markdown (&optional async subtreep visible-only) (defun org-gfm-export-to-markdown (&optional async subtreep visible-only)
"Export current buffer to a Github Flavored Markdown file. "Export current buffer to a Github Flavored Markdown file.