diff options
-rw-r--r-- | docs/extensions/meta_data.txt | 32 | ||||
-rw-r--r-- | markdown/__version__.py | 2 | ||||
-rw-r--r-- | markdown/extensions/meta.py | 48 | ||||
-rw-r--r-- | tests/test_extensions.py | 42 |
4 files changed, 110 insertions, 14 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/__version__.py b/markdown/__version__.py index 33d0ac5..911e340 100644 --- a/markdown/__version__.py +++ b/markdown/__version__.py @@ -5,7 +5,7 @@ # (major, minor, micro, alpha/beta/rc/final, #) # (1, 1, 2, 'alpha', 0) => "1.1.2.dev" # (1, 2, 0, 'beta', 2) => "1.2b2" -version_info = (2, 5, 2, 'final', 0) +version_info = (2, 6, 0, 'alpha', 0) def _get_version(): 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. """ |