diff options
-rw-r--r-- | .gitignore | 1 | ||||
-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-- | markdown/util.py | 6 | ||||
-rwxr-xr-x | setup.py | 24 | ||||
-rw-r--r-- | tests/test_extensions.py | 42 |
7 files changed, 128 insertions, 27 deletions
@@ -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 """ @@ -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 — Python Markdown' % c['title'] @@ -131,7 +134,7 @@ class build_docs(Command): crumbs = [] ctemp = '<li><a href="%s">%s</a> »</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. """ |