aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/extensions/meta_data.txt32
-rw-r--r--markdown/extensions/meta.py48
-rw-r--r--tests/test_extensions.py42
3 files changed, 109 insertions, 13 deletions
diff --git a/docs/extensions/meta_data.txt b/docs/extensions/meta_data.txt
index 406755b..517d3e6 100644
--- a/docs/extensions/meta_data.txt
+++ b/docs/extensions/meta_data.txt
@@ -44,9 +44,16 @@ If a line is indented by 4 or more spaces, that line is assumed to be an
additional line of the value for the previous keyword. A keyword may have as
many lines as desired.
-The first blank line ends all meta-data for the document. Therefore, the first
-line of a document must not be blank. All meta-data is stripped from the
-document prior to any further processing by Markdown.
+The first blank line ends all meta-data for the document. Therefore, the first
+line of a document must not be blank.
+
+Alternatively, if the first line in the document is `---`, a YAML document
+separator, then the meta-data is searched for between it and the next `---`
+(or `...`) line. Even though YAML delimitors are supported, meta-data is
+not parsed as YAML unless the `yaml` option is set (see below).
+
+All meta-data is stripped from the document prior to any further processing
+by Markdown.
Usage
-----
@@ -54,7 +61,15 @@ Usage
See [Extensions](index.html) for general extension usage, specify `markdown.extensions.meta`
as the name of the extension.
-This extension does not accept any special configuration options.
+The following options are provided to configure the output:
+
+* **`yaml`**: Support meta-data specified in YAML format.
+
+ Default: `False`
+
+ If `yaml` is set to `True`, the lines between `---` separators are parsed
+ as a full YAML object. PyYAML is required for this, and a warning is
+ issued if PyYAML (or equivalent) isn't available.
Accessing the Meta-Data
-----------------------
@@ -85,8 +100,13 @@ line breaks if desired. Or the items could be joined where appropriate. No
assumptions are made regarding the data. It is simply passed as found to the
`Meta` attribute.
-Perhaps the meta-data could be passed into a template system, or used by
-various Markdown extensions. The possibilities are left to the imagination of
+Note, if `yaml` option is set, the resulting `Meta` attribute is the object as
+returned by `yaml.load()` and may deviate significantly from the above
+description (e.g. may be a list of dicts, with value objects other than
+strings, ...).
+
+Perhaps the meta-data could be passed into a template system, or used by
+various Markdown extensions. The possibilities are left to the imagination of
the developer.
Compatible Extensions
diff --git a/markdown/extensions/meta.py b/markdown/extensions/meta.py
index a733a8a..cf8074f 100644
--- a/markdown/extensions/meta.py
+++ b/markdown/extensions/meta.py
@@ -20,36 +20,68 @@ from __future__ import unicode_literals
from . import Extension
from ..preprocessors import Preprocessor
import re
+import logging
+
+try: # pragma: no cover
+ import yaml
+ try:
+ from yaml import CSafeLoader as SafeLoader
+ except ImportError:
+ from yaml import SafeLoader
+except ImportError:
+ yaml = None
+
+log = logging.getLogger('MARKDOWN')
# Global Vars
META_RE = re.compile(r'^[ ]{0,3}(?P<key>[A-Za-z0-9_-]+):\s*(?P<value>.*)')
META_MORE_RE = re.compile(r'^[ ]{4,}(?P<value>.*)')
+YAML_BEGIN_RE = re.compile(r'^-{3}(\s.*)?')
+YAML_END_RE = re.compile(r'^(-{3}|\.{3})(\s.*)?')
class MetaExtension (Extension):
""" Meta-Data extension for Python-Markdown. """
+ def __init__(self, *args, **kwargs):
+ self.config = {
+ 'yaml': [False, "Parse meta data specified as a "
+ "'---' delimited YAML front matter"],
+ }
+ super(MetaExtension, self).__init__(*args, **kwargs)
def extendMarkdown(self, md, md_globals):
""" Add MetaPreprocessor to Markdown instance. """
-
- md.preprocessors.add(
- "meta", MetaPreprocessor(md), ">normalize_whitespace"
- )
+ md.preprocessors.add("meta",
+ MetaPreprocessor(md, self.getConfigs()),
+ ">normalize_whitespace")
class MetaPreprocessor(Preprocessor):
""" Get Meta-Data. """
+ def __init__(self, md, config):
+ self.config = config
+ super(MetaPreprocessor, self).__init__(md)
+
def run(self, lines):
""" Parse Meta-Data and store in Markdown.Meta. """
meta = {}
key = None
+ yaml_block = []
+ have_yaml = False
+ if lines and YAML_BEGIN_RE.match(lines[0]):
+ have_yaml = True
+ lines.pop(0)
+ if self.config['yaml'] and not yaml: # pragma: no cover
+ log.warning('Document with YAML header, but PyYAML unavailable')
while lines:
line = lines.pop(0)
- if line.strip() == '':
- break # blank line - done
m1 = META_RE.match(line)
- if m1:
+ if line.strip() == '' or have_yaml and YAML_END_RE.match(line):
+ break # blank line or end of YAML header - done
+ elif have_yaml and self.config['yaml'] and yaml:
+ yaml_block.append(line)
+ elif m1:
key = m1.group('key').lower().strip()
value = m1.group('value').strip()
try:
@@ -64,6 +96,8 @@ class MetaPreprocessor(Preprocessor):
else:
lines.insert(0, line)
break # no meta data - done
+ if yaml_block:
+ meta = yaml.load('\n'.join(yaml_block), SafeLoader)
self.markdown.Meta = meta
return lines
diff --git a/tests/test_extensions.py b/tests/test_extensions.py
index a037915..e24118f 100644
--- a/tests/test_extensions.py
+++ b/tests/test_extensions.py
@@ -8,6 +8,7 @@ continue to work as advertised. This used to be accomplished by doctests.
"""
from __future__ import unicode_literals
+import datetime
import unittest
import markdown
@@ -513,6 +514,28 @@ The body. This is paragraph one.'''
}
)
+ def testYamlMetaData(self):
+ """ Test metadata specified as simple YAML. """
+
+ text = '''---
+Title: A Test Doc.
+Author: [Waylan Limberg, John Doe]
+Blank_Data:
+---
+
+The body. This is paragraph one.'''
+ self.assertEqual(
+ self.md.convert(text),
+ '<p>The body. This is paragraph one.</p>'
+ )
+ self.assertEqual(
+ self.md.Meta, {
+ 'author': ['[Waylan Limberg, John Doe]'],
+ 'blank_data': [''],
+ 'title': ['A Test Doc.']
+ }
+ )
+
def testMissingMetaData(self):
""" Test document without Meta Data. """
@@ -530,6 +553,25 @@ The body. This is paragraph one.'''
self.assertEqual(self.md.convert(text), '')
self.assertEqual(self.md.Meta, {'title': ['No newline']})
+ def testYamlObjectMetaData(self):
+ """ Test metadata specified as a complex YAML object. """
+ md = markdown.Markdown(extensions=[markdown.extensions.meta.MetaExtension(yaml=True)])
+ text = '''---
+Author: John Doe
+Date: 2014-11-29 14:15:16
+Integer: 0x16
+---
+
+Some content.'''
+ self.assertEqual(md.convert(text), '<p>Some content.</p>')
+ self.assertEqual(
+ md.Meta, {
+ 'Author': 'John Doe',
+ 'Date': datetime.datetime(2014, 11, 29, 14, 15, 16),
+ 'Integer': 22
+ }
+ )
+
class TestWikiLinks(unittest.TestCase):
""" Test Wikilinks Extension. """