diff options
| author | 2021-02-03 19:17:50 -0500 | |
|---|---|---|
| committer | 2021-02-03 19:17:50 -0500 | |
| commit | 475d074fd74425efbe783fad08f97f2df0c4909f (patch) | |
| tree | 2acdae53999b3c74b716efa4edb5b40311fa356a /nikola/plugins/compile/markdown/__init__.py | |
| parent | cd502d52787f666fff3254d7d7e7578930c813c2 (diff) | |
| parent | 3a0d66f07b112b6d2bdc2b57bbf717a89a351ce6 (diff) | |
Update upstream source from tag 'upstream/8.1.2'
Update to upstream version '8.1.2'
with Debian dir e5e966a9e6010ef70618dc9a61558fa4db35aceb
Diffstat (limited to 'nikola/plugins/compile/markdown/__init__.py')
| -rw-r--r-- | nikola/plugins/compile/markdown/__init__.py | 129 |
1 files changed, 95 insertions, 34 deletions
diff --git a/nikola/plugins/compile/markdown/__init__.py b/nikola/plugins/compile/markdown/__init__.py index 2e4234c..74e8c75 100644 --- a/nikola/plugins/compile/markdown/__init__.py +++ b/nikola/plugins/compile/markdown/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright © 2012-2016 Roberto Alsina and others. +# Copyright © 2012-2020 Roberto Alsina and others. # Permission is hereby granted, free of charge, to any # person obtaining a copy of this software and associated @@ -24,23 +24,44 @@ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -"""Implementation of compile_html based on markdown.""" - -from __future__ import unicode_literals +"""Page compiler plugin for Markdown.""" import io +import json import os +import threading + +from nikola import shortcodes as sc +from nikola.plugin_categories import PageCompiler +from nikola.utils import makedirs, req_missing, write_metadata, LocaleBorg, map_metadata try: - from markdown import markdown + from markdown import Markdown except ImportError: - markdown = None # NOQA - nikola_extension = None - gist_extension = None - podcast_extension = None + Markdown = None -from nikola.plugin_categories import PageCompiler -from nikola.utils import makedirs, req_missing, write_metadata + +class ThreadLocalMarkdown(threading.local): + """Convert Markdown to HTML using per-thread Markdown objects. + + See discussion in #2661. + """ + + def __init__(self, extensions, extension_configs): + """Create a Markdown instance.""" + self.markdown = Markdown(extensions=extensions, extension_configs=extension_configs, output_format="html5") + + def convert(self, data): + """Convert data to HTML and reset internal state.""" + result = self.markdown.convert(data) + try: + meta = {} + for k in self.markdown.Meta: # This reads everything as lists + meta[k.lower()] = ','.join(self.markdown.Meta[k]) + except Exception: + meta = {} + self.markdown.reset() + return result, meta class CompileMarkdown(PageCompiler): @@ -49,42 +70,61 @@ class CompileMarkdown(PageCompiler): name = "markdown" friendly_name = "Markdown" demote_headers = True - extensions = [] site = None + supports_metadata = False def set_site(self, site): """Set Nikola site.""" - super(CompileMarkdown, self).set_site(site) + super().set_site(site) self.config_dependencies = [] + extensions = [] for plugin_info in self.get_compiler_extensions(): self.config_dependencies.append(plugin_info.name) - self.extensions.append(plugin_info.plugin_object) + extensions.append(plugin_info.plugin_object) plugin_info.plugin_object.short_help = plugin_info.description - self.config_dependencies.append(str(sorted(site.config.get("MARKDOWN_EXTENSIONS")))) + site_extensions = self.site.config.get("MARKDOWN_EXTENSIONS") + self.config_dependencies.append(str(sorted(site_extensions))) + extensions.extend(site_extensions) + + site_extension_configs = self.site.config.get("MARKDOWN_EXTENSION_CONFIGS") + if site_extension_configs: + self.config_dependencies.append(json.dumps(site_extension_configs.values, sort_keys=True)) + + if Markdown is not None: + self.converters = {} + for lang in self.site.config['TRANSLATIONS']: + lang_extension_configs = site_extension_configs(lang) if site_extension_configs else {} + self.converters[lang] = ThreadLocalMarkdown(extensions, lang_extension_configs) + self.supports_metadata = 'markdown.extensions.meta' in extensions + + def compile_string(self, data, source_path=None, is_two_file=True, post=None, lang=None): + """Compile Markdown into HTML strings.""" + if lang is None: + lang = LocaleBorg().current_lang + if Markdown is None: + req_missing(['markdown'], 'build this site (compile Markdown)') + if not is_two_file: + _, data = self.split_metadata(data, post, lang) + new_data, shortcodes = sc.extract_shortcodes(data) + output, _ = self.converters[lang].convert(new_data) + output, shortcode_deps = self.site.apply_shortcodes_uuid(output, shortcodes, filename=source_path, extra_context={'post': post}) + return output, shortcode_deps - def compile_html(self, source, dest, is_two_file=True): - """Compile source file into HTML and save as dest.""" - if markdown is None: + def compile(self, source, dest, is_two_file=True, post=None, lang=None): + """Compile the source file into HTML and save as dest.""" + if Markdown is None: req_missing(['markdown'], 'build this site (compile Markdown)') makedirs(os.path.dirname(dest)) - self.extensions += self.site.config.get("MARKDOWN_EXTENSIONS") - try: - post = self.site.post_per_input_file[source] - except KeyError: - post = None - with io.open(dest, "w+", encoding="utf8") as out_file: - with io.open(source, "r", encoding="utf8") as in_file: + with io.open(dest, "w+", encoding="utf-8") as out_file: + with io.open(source, "r", encoding="utf-8-sig") as in_file: data = in_file.read() - if not is_two_file: - _, data = self.split_metadata(data) - output = markdown(data, self.extensions, output_format="html5") - output, shortcode_deps = self.site.apply_shortcodes(output, filename=source, with_dependencies=True, extra_context=dict(post=post)) + output, shortcode_deps = self.compile_string(data, source, is_two_file, post, lang) out_file.write(output) if post is None: if shortcode_deps: self.logger.error( - "Cannot save dependencies for post {0} due to unregistered source file name", + "Cannot save dependencies for post {0} (post unknown)", source) else: post._depfile[dest] += shortcode_deps @@ -102,9 +142,30 @@ class CompileMarkdown(PageCompiler): makedirs(os.path.dirname(path)) if not content.endswith('\n'): content += '\n' - with io.open(path, "w+", encoding="utf8") as fd: + with io.open(path, "w+", encoding="utf-8") as fd: if onefile: - fd.write('<!-- \n') - fd.write(write_metadata(metadata)) - fd.write('-->\n\n') + fd.write(write_metadata(metadata, comment_wrap=True, site=self.site, compiler=self)) fd.write(content) + + def read_metadata(self, post, lang=None): + """Read the metadata from a post, and return a metadata dict.""" + lang = lang or self.site.config['DEFAULT_LANG'] + if not self.supports_metadata: + return {} + if Markdown is None: + req_missing(['markdown'], 'build this site (compile Markdown)') + if lang is None: + lang = LocaleBorg().current_lang + source = post.translated_source_path(lang) + with io.open(source, 'r', encoding='utf-8-sig') as inf: + # Note: markdown meta returns lowercase keys + data = inf.read() + # If the metadata starts with "---" it's actually YAML and + # we should not let markdown parse it, because it will do + # bad things like setting empty tags to "''" + if data.startswith('---\n'): + return {} + _, meta = self.converters[lang].convert(data) + # Map metadata from other platforms to names Nikola expects (Issue #2817) + map_metadata(meta, 'markdown_metadata', self.site.config) + return meta |
