diff --git a/README.md b/README.md index 4d03c0d..8553326 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,62 @@ pelican-md-yaml =============== + +This [Pelican](https://github.com/getpelican/pelican) plugin adds a reader for Markdown files with [YAML](https://en.wikipedia.org/wiki/YAML) metadata. +As the well-known static site generator [Jekyll](https://github.com/jekyll/jekyll) uses Markdown files with YAML metadata, this eases migration from Jekyll to Pelican. +Also, YAML metadata allows for easier specification of more complex metadata, such as nested lists or dictionaries. + +Installation +------------ + +Copy the `md_yaml` directory to the `plugins` directory of your Pelican project (or whatever directory you specified for plugins in Pelican's `PLUGIN_PATHS` setting) and add +`'md_yaml'` to the list of plugins (Pelican setting `PLUGINS`) of your project. + +Usage +----- + +All your Markdown files (ending in `.md`, `.markdown`, `.mkd` and `.mdown`) will now be interpreted as using YAML for their metadata. +The following example shows a very simple article (only one line of text at the bottom) but with quite complex metadata (everything between the `---`): + +``` +--- +template: article_recipe +title: Tiramisù +components: + - name: Tiramisù + for: 10 + ingredients: + - - 4 + - eggs + - - 150g + - sugar + - - 10 small cups + - espresso + - - 500g + - mascarpone + - - 1 package + - ladyfingers + steps: + - Cook the espresso, pour it into a soup plate. + - Separate the eggs very carefully. + - Add very little salt to the egg white. + - Blend egg yolk and sugar and mix it extensively for some minutes using a mixer, until you obtain a homogenous mass. + - Add mascarpone and mix again very extensively. + - Beat the egg white and fold it into the other mass. + - Construct the tiramisù: First a layer of cream, then a layer of ladyfingers dipped into espresso, cream, ladyfingers, ..., cream. Sprinkle with cacao. + - Put the tiramisù into the fridge for about a night, serve cold! +--- + +Thank you Silvia for the recipe! +``` + +Warranty +-------- + +No warranty whatsoever is provided for either the code or the recipe provided above! ;) Use only at your own risk! + +References +---------- + +* This Pelican plugin uses the Markdown extension `mdx_meta_yaml` found here: +* The Pelican plugin `markdown-pullquote` was used as an example for a Pelican plugin providing a Markdown extension and can be found here: +* A similar approach to YAML metadata in Markdown files can be found here: diff --git a/md_yaml/__init__.py b/md_yaml/__init__.py new file mode 100644 index 0000000..2f6bceb --- /dev/null +++ b/md_yaml/__init__.py @@ -0,0 +1 @@ +from .md_yaml import * diff --git a/md_yaml/md_yaml.py b/md_yaml/md_yaml.py new file mode 100644 index 0000000..6c54ea8 --- /dev/null +++ b/md_yaml/md_yaml.py @@ -0,0 +1,63 @@ +import sys +import os + +from pelican import signals +from pelican.readers import BaseReader +from pelican.utils import pelican_open + +try: + from markdown import Markdown +except ImportError: + Markdown = False + +class MarkdownYAMLReader(BaseReader): + """Reader for Markdown files with YAML metadata""" + + enabled = bool(Markdown) + file_extensions = ['md', 'markdown', 'mkd', 'mdown'] + + def __init__(self, *args, **kwargs): + super(MarkdownYAMLReader, self).__init__(*args, **kwargs) + self.settings = args[0] + self.extensions = list(self.settings['MD_EXTENSIONS']) + if 'meta_yaml' not in self.extensions: + self.extensions.append('meta_yaml') + + def _parse_metadata(self, meta): + """Return the dict containing document metadata""" + + output = {} + for name, value in meta.items(): + name = name.lower() + output[name] = value + return output + + def read(self, source_path): + """Parse content and metadata of Markdown files with YAML metadata""" + + self.__set_plugin_path() + + self._md = Markdown(extensions=self.extensions) + with pelican_open(source_path) as text: + content = self._md.convert(text) + metadata = self._parse_metadata(self._md.Meta) + + self.__unset_plugin_path() + + return content, metadata + + def __set_plugin_path(self): + self.__sys_path_old = sys.path[:] + for pluginpath in self.settings['PLUGIN_PATHS']: + sys.path.insert(0, pluginpath) + sys.path.insert(0, os.path.join(pluginpath, 'md_yaml')) + + def __unset_plugin_path(self): + sys.path = self.__sys_path_old + +def add_reader(readers): + for k in MarkdownYAMLReader.file_extensions: + readers.reader_classes[k] = MarkdownYAMLReader + +def register(): + signals.readers_init.connect(add_reader) diff --git a/md_yaml/mdx_meta_yaml/LICENSE.md b/md_yaml/mdx_meta_yaml/LICENSE.md new file mode 100644 index 0000000..4cd8b14 --- /dev/null +++ b/md_yaml/mdx_meta_yaml/LICENSE.md @@ -0,0 +1,30 @@ +Copyright 2007, 2008 The Python Markdown Project (v. 1.7 and later) +Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) +Copyright 2004 Manfred Stienstra (the original version) + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +* Neither the name of the nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE PYTHON MARKDOWN PROJECT ''AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL ANY CONTRIBUTORS TO THE PYTHON MARKDOWN PROJECT +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + diff --git a/md_yaml/mdx_meta_yaml/__init__.py b/md_yaml/mdx_meta_yaml/__init__.py new file mode 100644 index 0000000..cd0229f --- /dev/null +++ b/md_yaml/mdx_meta_yaml/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- + +from mdx_meta_yaml.extension import MetaYamlExtension + +def makeExtension(configs=None): + if isinstance(configs, list): + configs = dict(configs) + return MetaYamlExtension(configs=configs) diff --git a/md_yaml/mdx_meta_yaml/extension.py b/md_yaml/mdx_meta_yaml/extension.py new file mode 100644 index 0000000..40d7114 --- /dev/null +++ b/md_yaml/mdx_meta_yaml/extension.py @@ -0,0 +1,100 @@ +""" +# YAML Meta Data Extension for [Python-Markdown](https://github.com/waylan/Python-Markdown) + +This extension adds YAML meta data handling to markdown. + +As in the original, meta data is parsed but not used in processing. + +(YAML meta data is used e.g. by [pandoc](http://johnmacfarlane.net/pandoc/)) + +Dependencies: [PyYAML](http://pyyaml.org/) + + +Basic Usage: + + >>> import markdown + >>> text = '''--- + ... Title: Test Doc. + ... Author: Waylan Limberg + ... Blank_Data: + ... ... + ... + ... The body. This is paragraph one. + ... ''' + >>> md = markdown.Markdown(['meta_yaml']) + >>> print(md.convert(text)) +

The body. This is paragraph one.

+ >>> print(md.Meta) # doctest: +SKIP + {'blank_data': [''], 'author': ['Waylan Limberg'], 'title': ['Test Doc.']} + +Make sure text without Meta Data still works (markdown < 1.6b returns a

). + + >>> text = ' Some Code - not extra lines of meta data.' + >>> md = markdown.Markdown(['meta_yaml']) + >>> print(md.convert(text)) +

Some Code - not extra lines of meta data.
+	
+ >>> md.Meta + {} + + +Copyright 2014 Bernhard Fisseni + +Based on the meta data extension included with Python-Markdown, +Copyright 2007-2008 [Waylan Limberg](http://achinghead.com). + +License: BSD (see LICENSE.md for details) + +""" + +from __future__ import absolute_import +from __future__ import unicode_literals +from markdown import Extension +from markdown.preprocessors import Preprocessor +import yaml +# from yaml.scanner import ScannerError + + +class MetaYamlExtension (Extension): + """Extension for parsing YAML-Metadata with Python-Markdown.""" + + def extendMarkdown(self, md, md_globals): + """Add MetaYamlPreprocessor to Markdown instance.""" + + md.preprocessors.add('meta_yaml', MetaYamlPreprocessor(md), "_begin") + + +class MetaYamlPreprocessor(Preprocessor): + """ + Get Meta-Data. + + A YAML block is delimited by + - a line '---' at the start + - and a '...' or '---' line + at the end. + """ + + def run(self, lines): + """ Parse Meta-Data and store in Markdown.Meta. """ + + in_yaml = False + yaml_block = [] + line = lines.pop(0) + + if line == '---': + in_yaml = True + else: + lines.insert(0, line) + + while in_yaml and lines: + line = lines.pop(0) + if line == '---' or line == '...': + break + yaml_block.append(line) + + if yaml_block: + yaml_block = "\n".join(yaml_block) + meta = yaml.load(yaml_block) + self.markdown.Meta = meta + + return lines