Skip to content
Closed
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
32 changes: 26 additions & 6 deletions docs/extensions/meta_data.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,32 @@ 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
-----

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
-----------------------
Expand Down Expand Up @@ -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
Expand Down
48 changes: 41 additions & 7 deletions markdown/extensions/meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,36 +20,68 @@
from . import Extension
from ..preprocessors import Preprocessor
import re
import logging

try:
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:
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:
Expand All @@ -64,6 +96,8 @@ def run(self, lines):
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

Expand Down
42 changes: 42 additions & 0 deletions tests/test_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"""

from __future__ import unicode_literals
import datetime
import unittest
import markdown

Expand Down Expand Up @@ -513,6 +514,28 @@ def testBasicMetaData(self):
}
)

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. """

Expand All @@ -530,6 +553,25 @@ def testMetaDataWithoutNewline(self):
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. """
Expand Down