aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--docs/extensions/meta_data.txt32
-rw-r--r--markdown/__version__.py2
-rw-r--r--markdown/extensions/meta.py48
-rw-r--r--markdown/util.py6
-rwxr-xr-xsetup.py24
-rw-r--r--tests/test_extensions.py42
7 files changed, 128 insertions, 27 deletions
diff --git a/.gitignore b/.gitignore
index 3f7b30a..a542fb3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,4 @@ MANIFEST
.tox/*
.coverage
htmlcov/*
+*komodo* \ No newline at end of file
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/markdown/util.py b/markdown/util.py
index e3a4689..d3d48f0 100644
--- a/markdown/util.py
+++ b/markdown/util.py
@@ -15,9 +15,9 @@ if PY3: # pragma: no cover
text_type = str
int2str = chr
else: # pragma: no cover
- string_type = basestring
- text_type = unicode
- int2str = unichr
+ string_type = basestring # noqa
+ text_type = unicode # noqa
+ int2str = unichr # noqa
"""
diff --git a/setup.py b/setup.py
index 7e8862c..fec2dcb 100755
--- a/setup.py
+++ b/setup.py
@@ -44,7 +44,9 @@ SCRIPT_NAME = 'markdown_py'
class md_install_scripts(install_scripts):
+
""" Customized install_scripts. Create markdown_py.bat for win32. """
+
def run(self):
install_scripts.run(self)
@@ -59,13 +61,14 @@ class md_install_scripts(install_scripts):
f = open(bat_path, 'w')
f.write(bat_str)
f.close()
- print ('Created: %s' % bat_path)
+ print('Created: %s' % bat_path)
except Exception:
_, err, _ = sys.exc_info() # for both 2.x & 3.x compatability
- print ('ERROR: Unable to create %s: %s' % (bat_path, err))
+ print('ERROR: Unable to create %s: %s' % (bat_path, err))
class build_docs(Command):
+
""" Build markdown documentation into html."""
description = '"build" documentation (convert markdown text to html)'
@@ -73,7 +76,7 @@ class build_docs(Command):
user_options = [
('build-base=', 'd', 'directory to "build" to'),
('force', 'f', 'forcibly build everything (ignore file timestamps)'),
- ]
+ ]
boolean_options = ['force']
@@ -121,7 +124,7 @@ class build_docs(Command):
name, ext = os.path.splitext(file)
parts = [x for x in dir.split(os.sep) if x]
c['source'] = '%s.txt' % name
- c['base'] = '../'*len(parts)
+ c['base'] = '../' * len(parts)
# Build page title
if name.lower() != 'index' or parts:
c['page_title'] = '%s &#8212; Python Markdown' % c['title']
@@ -131,7 +134,7 @@ class build_docs(Command):
crumbs = []
ctemp = '<li><a href="%s">%s</a> &raquo;</li>'
for n, part in enumerate(parts):
- href = ('../'*n) + 'index.html'
+ href = ('../' * n) + 'index.html'
label = part.replace('_', ' ').capitalize()
crumbs.append(ctemp % (href, label))
if c['title'] and name.lower() != 'index':
@@ -147,7 +150,7 @@ class build_docs(Command):
try:
import markdown
except ImportError:
- print ('skipping build_docs: Markdown "import" failed!')
+ print('skipping build_docs: Markdown "import" failed!')
else:
with codecs.open('docs/_template.html', encoding='utf-8') as f:
template = f.read()
@@ -174,7 +177,7 @@ class build_docs(Command):
self.mkpath(os.path.split(outfile)[0])
if self.force or newer(infile, outfile):
if self.verbose:
- print ('Converting %s -> %s' % (infile, outfile))
+ print('Converting %s -> %s' % (infile, outfile))
if not self.dry_run:
with codecs.open(infile, encoding='utf-8') as f:
src = f.read()
@@ -189,11 +192,12 @@ class build_docs(Command):
class md_build(build):
+
""" Run "build_docs" command from "build" command. """
user_options = build.user_options + [
('no-build-docs', None, 'do not build documentation'),
- ]
+ ]
boolean_options = build.boolean_options + ['build-docs']
@@ -235,9 +239,9 @@ setup(
description='Python implementation of Markdown.',
long_description=long_description,
author='Manfred Stienstra, Yuri takhteyev and Waylan limberg',
- author_email='markdown [at] freewisdom.org',
+ author_email='waylan.limberg [at] icloud.com',
maintainer='Waylan Limberg',
- maintainer_email='waylan [at] gmail.com',
+ maintainer_email='waylan.limberg [at] icloud.com',
license='BSD License',
packages=['markdown', 'markdown.extensions'],
scripts=['bin/%s' % SCRIPT_NAME],
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. """