Compare commits

..

7 Commits
v1.0 ... master

Author SHA1 Message Date
Jonas Bernoulli
4f774f13d3 org-gfm-footnote-section: Cosmetics 2023-12-15 20:01:18 +01:00
Jonas Bernoulli
a971997f8f org-gfm-table: Cosmetics
- It is redundant to use `function' around `lambda'.
- It is semantically better to use `and' if the returned value
  matters.  `when' should be used if we only care about the
  side-effects.
2023-12-15 20:00:13 +01:00
Jonas Bernoulli
4aabaa51b1 Use lexical-binding and fix uncovered bugs
Also silence false-positives.
2023-12-15 19:58:43 +01:00
Jonas Bernoulli
1bfc320f94
End first line of each docstring with a period
The convention calls for the first line to form a complete sentence.
Here we can achieve that in most cases simply by not beginning the
second sentence on the first line.
2023-06-26 14:27:56 +02:00
Jonas Bernoulli
4eaf2adf29
Improve consistency in the usage of empty lines 2023-06-26 14:07:35 +02:00
46faa67dbb
Merge pull request #39 from benley/example-block-src
Use triple-backtick block syntax for example blocks too
2022-09-10 15:21:35 +02:00
Benjamin Staffin
bd85f6a56f Use triple-backtick block syntax for example blocks too
org-mode `#+BEGIN_EXAMPLE` blocks can (optionally) be tagged with a
language, so this will give them proper syntax highlighting on GitHub.
It still works when the language is unset, of course.
2019-05-02 15:38:12 -04:00

158
ox-gfm.el
View File

@ -1,4 +1,4 @@
;;; ox-gfm.el --- Github Flavored Markdown Back-End for Org Export Engine ;;; ox-gfm.el --- Github Flavored Markdown Back-End for Org Export Engine -*- lexical-binding: t; -*-
;; Copyright (C) 2014-2017 Lars Tveito ;; Copyright (C) 2014-2017 Lars Tveito
@ -29,6 +29,7 @@
(require 'ox-md) (require 'ox-md)
(require 'ox-publish) (require 'ox-publish)
;;; User-Configurable Variables ;;; User-Configurable Variables
@ -56,12 +57,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
@ -79,12 +80,11 @@ 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 "Transcode SRC-BLOCK element into Github Flavored Markdown format.
format. CONTENTS is nil. INFO is a plist used as a communication _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,16 +92,18 @@ 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)
@ -112,10 +114,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. INFO is a plist used as "Return width of TABLE at given COLUMN.
communication channel. Width of a column is determined either by INFO is a plist used as communication channel. Width of a column
inquerying `width-cookies' in the column, or by the maximum cell with in is determined either by inquerying `width-cookies' in the column,
the column." or by the maximum cell with in 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)
@ -144,22 +146,21 @@ 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 "Return a function to build horizontal line in TABLE with given CHAR.
CHAR. INFO is a plist used as a communication channel." 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. CONTENTS is content "Transcode TABLE-CELL element from Org into GFM.
of the cell. INFO is a plist used as a communication channel." CONTENTS is content of the cell. INFO is a plist used as a
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))
@ -172,13 +173,12 @@ of the cell. INFO is a plist used as a 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. CONTENTS is cell "Transcode TABLE-ROW element from Org into GFM.
contents of TABLE-ROW. INFO is a plist used as a communication CONTENTS is cell contents of TABLE-ROW. INFO is a plist used as a
channel." communication 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,7 +186,6 @@ 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
@ -197,41 +196,36 @@ 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)
"Transcode TABLE element into Github Flavored Markdown table. "Transcode TABLE element into Github Flavored Markdown table.
CONTENTS is the contents of the table. INFO is a plist holding CONTENTS is the contents of the table. INFO is a plist holding
contextual information." contextual information."
(let* ((rows (org-element-map table 'table-row 'identity info)) (let* ((rows (org-element-map table 'table-row 'identity info))
(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 ?-)) (columns (number-sequence 0 (- cols 1))))
(columns (number-sequence 0 (- cols 1)))) (concat gfm-table-left-border
(concat gfm-table-left-border (mapconcat (lambda (col) (funcall build-empty-cell col))
(mapconcat (lambda (col) (funcall build-empty-cell col)) columns
columns gfm-table-separator)
gfm-table-separator) gfm-table-right-border "\n" gfm-table-left-border
gfm-table-right-border "\n" gfm-table-left-border (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) (defun org-gfm-format-toc (headline info)
"Return an appropriate table of contents entry for HEADLINE. INFO is a "Return an appropriate table of contents entry for HEADLINE."
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)))
@ -240,38 +234,25 @@ plist used as a communication channel."
(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."
(let* ((fn-alist (org-export-collect-footnote-definitions info)) (and-let* ((fn-alist (org-export-collect-footnote-definitions info)))
(fn-alist (format
(cl-loop for (n type raw) in fn-alist collect "## Footnotes\n\n%s\n"
(cons n (org-trim (org-export-data raw info)))))) (mapconcat (pcase-lambda (`(,n ,_type ,def))
(when fn-alist (format
(format "%s %s\n"
"## %s\n%s" (format (plist-get info :html-footnote-format)
"Footnotes" (org-html--anchor
(format (format "fn.%d" n)
"\n%s\n" n
(mapconcat (format " class=\"footnum\" href=\"#fnr.%d\"" n)
(lambda (fn) info))
(let ((n (car fn)) (def (cdr fn))) (org-trim (org-export-data def info))))
(format fn-alist "\n"))))
"%s %s\n"
(format
(plist-get info :html-footnote-format)
(org-html--anchor
(format "fn.%d" n)
n
(format " class=\"footnum\" href=\"#fnr.%d\"" n)
info))
def)))
fn-alist
"\n"))))))
;;;; Template ;;;; Template
@ -281,13 +262,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 'org-gfm-format-toc headlines "\n") "")) (toc-string (or (mapconcat (lambda (headline)
(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
@ -318,17 +299,16 @@ 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 ()
"Assume the current region has org-mode syntax, and convert it "Convert the region to Github Flavored Markdown.
to Github Flavored Markdown. This can be used in any buffer. This can be used in any buffer, this function assume that the
For example, you can write an itemized list in org-mode syntax in current region has org-mode syntax. For example, you can write
a Markdown buffer and use this command to convert it." an itemized list in org-mode syntax in a Markdown buffer and use
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.