Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add yaml metadata parsing #91

Merged
merged 1 commit into from
Feb 10, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 127 additions & 20 deletions markdown-mode.el
Original file line number Diff line number Diff line change
Expand Up @@ -1083,6 +1083,12 @@ completion."
:group 'markdown
:type 'boolean)

(defcustom markdown-use-pandoc-style-yaml-metadata nil
"When non-nil, allow yaml metadata anywhere in the document, and allow
ellipses to close a metadata region."
:group 'markdown
:type 'boolean)

(defcustom markdown-live-preview-window-function
'markdown-live-preview-window-eww
"Function to display preview of Markdown output within Emacs. Function must
Expand Down Expand Up @@ -1374,17 +1380,25 @@ Group 3 matches the mathematical expression contained within.")
(defconst markdown-regex-math-display
"^\\(\\\\\\[\\)\\(\\(?:.\\|\n\\)*\\)?\\(\\\\\\]\\)$"
"Regular expression for itex \[..\] display mode expressions.
Groups 1 and 3 matche the opening and closing delimiters.
Groups 1 and 3 match the opening and closing delimiters.
Group 2 matches the mathematical expression contained within.")

(defconst markdown-regex-multimarkdown-metadata
"^\\([[:alpha:]][[:alpha:] _-]*?\\)\\(:[ \t]*\\)\\(.*\\)$"
"Regular expression for matching MultiMarkdown metadata.")

(defconst markdown-regex-pandoc-metadata
"^\\(%\\)\\([ \t]*\\)\\(.*\\)$"
"^\\(%\\)\\([ \t]*\\)\\(.*\\(?:\n[ \t]+.*\\)*\\)"
"Regular expression for matching Pandoc metadata.")

(defconst markdown-regex-yaml-metadata-border
"\\(\\-\\{3\\}\\)$"
"Regular expression for matching yaml metadata.")

(defconst markdown-regex-yaml-pandoc-metadata-end-border
"\\(\\.\\{3\\}\\|\\-\\{3\\}\\)$"
"Regular expression for matching yaml metadata end borders.")


;;; Syntax ====================================================================

Expand Down Expand Up @@ -1496,6 +1510,17 @@ Function is called repeatedly until it returns nil. For details, see
'markdown-blockquote
(match-data t)))))

(defun markdown-syntax-propertize-yaml-metadata (start end)
(save-excursion
(goto-char start)
(while (markdown-match-yaml-metadata end)
(put-text-property (match-beginning 1) (match-end 1)
'markdown-metadata-key (match-data t))
(put-text-property (match-beginning 2) (match-end 2)
'markdown-metadata-markup (match-data t))
(put-text-property (match-beginning 3) (match-end 3)
'markdown-metadata-value (match-data t)))))

(defun markdown-syntax-propertize-headings-generic (symbol regex start end)
"Match headings of type SYMBOL with REGEX from START to END."
(save-excursion
Expand Down Expand Up @@ -1546,10 +1571,14 @@ Function is called repeatedly until it returns nil. For details, see
(remove-text-properties start end '(markdown-heading-4-atx))
(remove-text-properties start end '(markdown-heading-5-atx))
(remove-text-properties start end '(markdown-heading-6-atx))
(remove-text-properties start end '(markdown-metadata-key))
(remove-text-properties start end '(markdown-metadata-value))
(remove-text-properties start end '(markdown-metadata-markup))
(markdown-syntax-propertize-gfm-code-blocks start end)
(markdown-syntax-propertize-fenced-code-blocks start end)
(markdown-syntax-propertize-pre-blocks start end)
(markdown-syntax-propertize-blockquotes start end)
(markdown-syntax-propertize-yaml-metadata start end)
(markdown-syntax-propertize-headings-generic
'markdown-heading-1-setext markdown-regex-header-1-setext start end)
(markdown-syntax-propertize-headings-generic
Expand Down Expand Up @@ -1836,6 +1865,12 @@ See `font-lock-syntactic-face-function' for details."

(defvar markdown-mode-font-lock-keywords-basic
(list
(cons 'markdown-match-yaml-metadata-border
'((1 markdown-markup-face)
(2 markdown-markup-face)))
(cons 'markdown-match-yaml-metadata '((1 markdown-metadata-key-face)
(2 markdown-markup-face)
(3 markdown-metadata-value-face)))
(cons 'markdown-match-gfm-code-blocks '((1 markdown-markup-face)
(2 markdown-language-keyword-face nil t)
(3 markdown-pre-face)
Expand Down Expand Up @@ -1923,8 +1958,7 @@ See `font-lock-syntactic-face-function' for details."
(3 markdown-markup-face prepend)))
(cons markdown-regex-uri 'markdown-link-face)
(cons markdown-regex-email 'markdown-link-face)
(cons markdown-regex-line-break '(1 markdown-line-break-face prepend))
)
(cons markdown-regex-line-break '(1 markdown-line-break-face prepend)))
"Syntax highlighting for Markdown files.")

(defconst markdown-mode-font-lock-keywords-math
Expand Down Expand Up @@ -2599,30 +2633,60 @@ analysis."
(set-match-data (list beg (point)))
t))))

(defun markdown-match-generic-metadata (regexp last)
(defun markdown-get-match-boundaries (start-header end-header last &optional pos)
(save-excursion
(goto-char (or pos (point-min)))
(cl-loop
with cur-result = nil
and st-hdr = (or start-header "\\`")
and end-hdr = (or end-header "\n\n\\|\n\\'\\|\\'")
while (and (< (point) last)
(re-search-forward st-hdr last t)
(progn
(setq cur-result (match-data))
(re-search-forward end-hdr nil t)))
collect (list cur-result (match-data)))))

(defvar markdown-conditional-search-function #'re-search-forward
"Conditional search function used in `markdown-search-until-condition'. Made
into a variable to allow for dynamic let-binding.")
(defun markdown-search-until-condition (condition &rest args)
(let (ret)
(while (and (not ret) (apply markdown-conditional-search-function args))
(setq ret (funcall condition)))
ret))

(defun markdown-match-generic-metadata
(regexp last &optional start-header end-header)
"Match generic metadata specified by REGEXP from the point to LAST."
(let ((header-end (save-excursion
(goto-char (point-min))
(if (re-search-forward "\n\n" (point-max) t)
(match-beginning 0)
(point-max)))))
(cond ((>= (point) header-end)
;; Don't match anything outside of the header.
;; if start-header is nil, we assume metadata can only occur at the very top
;; of a file ("\\`"). if end-header is nil, we assume it is "\n\n"
(let* ((header-bounds
(markdown-get-match-boundaries start-header end-header last))
(enclosing-header
(cl-find-if ; just take first if multiple
(lambda (match-bounds)
(cl-destructuring-bind (start-match end-match) match-bounds
(and
(< (point) (cl-first end-match))
(save-excursion
(re-search-forward regexp (cl-second end-match) t)))))
header-bounds))
(header-begin
(when enclosing-header (cl-second (cl-first enclosing-header))))
(header-end
(when enclosing-header (cl-first (cl-second enclosing-header)))))
(cond ((null enclosing-header)
;; Don't match anything outside of a header.
nil)
((re-search-forward regexp (min last header-end) t)
((markdown-search-until-condition
(lambda () (> (point) header-begin)) regexp (min last header-end) t)
;; If a metadata item is found, it may span several lines.
(let ((key-beginning (match-beginning 1))
(key-end (match-end 1))
(markup-begin (match-beginning 2))
(markup-end (match-end 2))
(value-beginning (match-beginning 3)))
(while (and (not (looking-at regexp))
(not (> (point) (min last header-end)))
(not (eobp)))
(forward-line))
(unless (eobp)
(forward-line -1)
(end-of-line))
(set-match-data (list key-beginning (point) ; complete metadata
key-beginning key-end ; key
markup-begin markup-end ; markup
Expand All @@ -2638,6 +2702,49 @@ analysis."
"Match Pandoc metadata from the point to LAST."
(markdown-match-generic-metadata markdown-regex-pandoc-metadata last))

(defun markdown-match-yaml-metadata (last)
"Match yaml metadata from the point to LAST."
(markdown-match-generic-metadata
markdown-regex-multimarkdown-metadata last
(concat
(if markdown-use-pandoc-style-yaml-metadata "^" "\\`")
markdown-regex-yaml-metadata-border)
(concat
"^"
(if markdown-use-pandoc-style-yaml-metadata
markdown-regex-yaml-pandoc-metadata-end-border
markdown-regex-yaml-metadata-border))))

(defun markdown-match-yaml-metadata-border (last)
(let ((res
(cl-first
(markdown-get-match-boundaries
(concat
(if markdown-use-pandoc-style-yaml-metadata "^" "\\`")
markdown-regex-yaml-metadata-border)
(concat
"^"
(if markdown-use-pandoc-style-yaml-metadata
markdown-regex-yaml-pandoc-metadata-end-border
markdown-regex-yaml-metadata-border))
last (point)))))
(when res
(cl-destructuring-bind (start-header end-header) res
(set-match-data
(list (cl-third start-header) (cl-fourth end-header)
(cl-third start-header) (cl-fourth start-header)
(cl-third end-header) (cl-fourth end-header)))
t))))

(defun markdown-match-yaml-metadata-key (last)
(markdown-match-propertized-text 'markdown-metadata-key last))

(defun markdown-match-yaml-metadata-markup (last)
(markdown-match-propertized-text 'markdown-metadata-markup last))

(defun markdown-match-yaml-metadata-value (last)
(markdown-match-propertized-text 'markdown-metadata-value last))


;;; Syntax Table ==============================================================

Expand Down
54 changes: 54 additions & 0 deletions tests/markdown-test.el
Original file line number Diff line number Diff line change
Expand Up @@ -2297,6 +2297,60 @@ body"
(markdown-test-range-has-face 60 63 markdown-metadata-value-face)
(markdown-test-range-has-face 64 69 nil)))

(ert-deftest test-markdown-font-lock/yaml-metadata ()
"Basic yaml metadata tests."
(markdown-test-string
"---
layout: post
date: 2015-08-13 11:35:25 EST
---
"
;; first section
(markdown-test-range-has-face 1 3 markdown-markup-face)
(markdown-test-range-has-face 5 10 markdown-metadata-key-face)
(markdown-test-range-has-face 11 11 markdown-markup-face)
(markdown-test-range-has-face 13 16 markdown-metadata-value-face)
(markdown-test-range-has-face 18 21 markdown-metadata-key-face)
(markdown-test-range-has-face 22 22 markdown-markup-face)
(markdown-test-range-has-face 24 46 markdown-metadata-value-face)
(markdown-test-range-has-face 48 50 markdown-markup-face)))

(ert-deftest test-markdown-font-lock/pandoc-yaml-metadata ()
"Basic yaml metadata tests, with pandoc syntax."
(let ((markdown-use-pandoc-style-yaml-metadata t))
(markdown-test-string
"some text

---
layout: post
date: 2015-08-13 11:35:25 EST
...

more text

---
layout: post
date: 2015-08-13 11:35:25 EST
---"
;; first section
(markdown-test-range-has-face 12 14 markdown-markup-face)
(markdown-test-range-has-face 16 21 markdown-metadata-key-face)
(markdown-test-range-has-face 22 22 markdown-markup-face)
(markdown-test-range-has-face 24 27 markdown-metadata-value-face)
(markdown-test-range-has-face 29 32 markdown-metadata-key-face)
(markdown-test-range-has-face 33 33 markdown-markup-face)
(markdown-test-range-has-face 35 57 markdown-metadata-value-face)
(markdown-test-range-has-face 59 61 markdown-markup-face)
;; second section
(markdown-test-range-has-face 75 77 markdown-markup-face)
(markdown-test-range-has-face 79 84 markdown-metadata-key-face)
(markdown-test-range-has-face 85 85 markdown-markup-face)
(markdown-test-range-has-face 87 90 markdown-metadata-value-face)
(markdown-test-range-has-face 92 95 markdown-metadata-key-face)
(markdown-test-range-has-face 96 96 markdown-markup-face)
(markdown-test-range-has-face 98 120 markdown-metadata-value-face)
(markdown-test-range-has-face 122 124 markdown-markup-face))))

(ert-deftest test-markdown-font-lock/line-break ()
"Basic line break tests."
(markdown-test-string " \nasdf \n"
Expand Down