Skip to content

Commit 4cac4ff

Browse files
committed
Merge branch 'kernc-meta_yaml'
2 parents 4732926 + ef6eb03 commit 4cac4ff

File tree

4 files changed

+110
-14
lines changed

4 files changed

+110
-14
lines changed

docs/extensions/meta_data.txt

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,17 +44,32 @@ If a line is indented by 4 or more spaces, that line is assumed to be an
4444
additional line of the value for the previous keyword. A keyword may have as
4545
many lines as desired.
4646

47-
The first blank line ends all meta-data for the document. Therefore, the first
48-
line of a document must not be blank. All meta-data is stripped from the
49-
document prior to any further processing by Markdown.
47+
The first blank line ends all meta-data for the document. Therefore, the first
48+
line of a document must not be blank.
49+
50+
Alternatively, if the first line in the document is `---`, a YAML document
51+
separator, then the meta-data is searched for between it and the next `---`
52+
(or `...`) line. Even though YAML delimitors are supported, meta-data is
53+
not parsed as YAML unless the `yaml` option is set (see below).
54+
55+
All meta-data is stripped from the document prior to any further processing
56+
by Markdown.
5057

5158
Usage
5259
-----
5360

5461
See [Extensions](index.html) for general extension usage, specify `markdown.extensions.meta`
5562
as the name of the extension.
5663

57-
This extension does not accept any special configuration options.
64+
The following options are provided to configure the output:
65+
66+
* **`yaml`**: Support meta-data specified in YAML format.
67+
68+
Default: `False`
69+
70+
If `yaml` is set to `True`, the lines between `---` separators are parsed
71+
as a full YAML object. PyYAML is required for this, and a warning is
72+
issued if PyYAML (or equivalent) isn't available.
5873

5974
Accessing the Meta-Data
6075
-----------------------
@@ -85,8 +100,13 @@ line breaks if desired. Or the items could be joined where appropriate. No
85100
assumptions are made regarding the data. It is simply passed as found to the
86101
`Meta` attribute.
87102

88-
Perhaps the meta-data could be passed into a template system, or used by
89-
various Markdown extensions. The possibilities are left to the imagination of
103+
Note, if `yaml` option is set, the resulting `Meta` attribute is the object as
104+
returned by `yaml.load()` and may deviate significantly from the above
105+
description (e.g. may be a list of dicts, with value objects other than
106+
strings, ...).
107+
108+
Perhaps the meta-data could be passed into a template system, or used by
109+
various Markdown extensions. The possibilities are left to the imagination of
90110
the developer.
91111

92112
Compatible Extensions

markdown/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
# (major, minor, micro, alpha/beta/rc/final, #)
66
# (1, 1, 2, 'alpha', 0) => "1.1.2.dev"
77
# (1, 2, 0, 'beta', 2) => "1.2b2"
8-
version_info = (2, 5, 2, 'final', 0)
8+
version_info = (2, 6, 0, 'alpha', 0)
99

1010

1111
def _get_version():

markdown/extensions/meta.py

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,36 +20,68 @@
2020
from . import Extension
2121
from ..preprocessors import Preprocessor
2222
import re
23+
import logging
24+
25+
try: # pragma: no cover
26+
import yaml
27+
try:
28+
from yaml import CSafeLoader as SafeLoader
29+
except ImportError:
30+
from yaml import SafeLoader
31+
except ImportError:
32+
yaml = None
33+
34+
log = logging.getLogger('MARKDOWN')
2335

2436
# Global Vars
2537
META_RE = re.compile(r'^[ ]{0,3}(?P<key>[A-Za-z0-9_-]+):\s*(?P<value>.*)')
2638
META_MORE_RE = re.compile(r'^[ ]{4,}(?P<value>.*)')
39+
YAML_BEGIN_RE = re.compile(r'^-{3}(\s.*)?')
40+
YAML_END_RE = re.compile(r'^(-{3}|\.{3})(\s.*)?')
2741

2842

2943
class MetaExtension (Extension):
3044
""" Meta-Data extension for Python-Markdown. """
45+
def __init__(self, *args, **kwargs):
46+
self.config = {
47+
'yaml': [False, "Parse meta data specified as a "
48+
"'---' delimited YAML front matter"],
49+
}
50+
super(MetaExtension, self).__init__(*args, **kwargs)
3151

3252
def extendMarkdown(self, md, md_globals):
3353
""" Add MetaPreprocessor to Markdown instance. """
34-
35-
md.preprocessors.add(
36-
"meta", MetaPreprocessor(md), ">normalize_whitespace"
37-
)
54+
md.preprocessors.add("meta",
55+
MetaPreprocessor(md, self.getConfigs()),
56+
">normalize_whitespace")
3857

3958

4059
class MetaPreprocessor(Preprocessor):
4160
""" Get Meta-Data. """
4261

62+
def __init__(self, md, config):
63+
self.config = config
64+
super(MetaPreprocessor, self).__init__(md)
65+
4366
def run(self, lines):
4467
""" Parse Meta-Data and store in Markdown.Meta. """
4568
meta = {}
4669
key = None
70+
yaml_block = []
71+
have_yaml = False
72+
if lines and YAML_BEGIN_RE.match(lines[0]):
73+
have_yaml = True
74+
lines.pop(0)
75+
if self.config['yaml'] and not yaml: # pragma: no cover
76+
log.warning('Document with YAML header, but PyYAML unavailable')
4777
while lines:
4878
line = lines.pop(0)
49-
if line.strip() == '':
50-
break # blank line - done
5179
m1 = META_RE.match(line)
52-
if m1:
80+
if line.strip() == '' or have_yaml and YAML_END_RE.match(line):
81+
break # blank line or end of YAML header - done
82+
elif have_yaml and self.config['yaml'] and yaml:
83+
yaml_block.append(line)
84+
elif m1:
5385
key = m1.group('key').lower().strip()
5486
value = m1.group('value').strip()
5587
try:
@@ -64,6 +96,8 @@ def run(self, lines):
6496
else:
6597
lines.insert(0, line)
6698
break # no meta data - done
99+
if yaml_block:
100+
meta = yaml.load('\n'.join(yaml_block), SafeLoader)
67101
self.markdown.Meta = meta
68102
return lines
69103

tests/test_extensions.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"""
99

1010
from __future__ import unicode_literals
11+
import datetime
1112
import unittest
1213
import markdown
1314

@@ -513,6 +514,28 @@ def testBasicMetaData(self):
513514
}
514515
)
515516

517+
def testYamlMetaData(self):
518+
""" Test metadata specified as simple YAML. """
519+
520+
text = '''---
521+
Title: A Test Doc.
522+
Author: [Waylan Limberg, John Doe]
523+
Blank_Data:
524+
---
525+
526+
The body. This is paragraph one.'''
527+
self.assertEqual(
528+
self.md.convert(text),
529+
'<p>The body. This is paragraph one.</p>'
530+
)
531+
self.assertEqual(
532+
self.md.Meta, {
533+
'author': ['[Waylan Limberg, John Doe]'],
534+
'blank_data': [''],
535+
'title': ['A Test Doc.']
536+
}
537+
)
538+
516539
def testMissingMetaData(self):
517540
""" Test document without Meta Data. """
518541

@@ -530,6 +553,25 @@ def testMetaDataWithoutNewline(self):
530553
self.assertEqual(self.md.convert(text), '')
531554
self.assertEqual(self.md.Meta, {'title': ['No newline']})
532555

556+
def testYamlObjectMetaData(self):
557+
""" Test metadata specified as a complex YAML object. """
558+
md = markdown.Markdown(extensions=[markdown.extensions.meta.MetaExtension(yaml=True)])
559+
text = '''---
560+
Author: John Doe
561+
Date: 2014-11-29 14:15:16
562+
Integer: 0x16
563+
---
564+
565+
Some content.'''
566+
self.assertEqual(md.convert(text), '<p>Some content.</p>')
567+
self.assertEqual(
568+
md.Meta, {
569+
'Author': 'John Doe',
570+
'Date': datetime.datetime(2014, 11, 29, 14, 15, 16),
571+
'Integer': 22
572+
}
573+
)
574+
533575

534576
class TestWikiLinks(unittest.TestCase):
535577
""" Test Wikilinks Extension. """

0 commit comments

Comments
 (0)