aboutsummaryrefslogtreecommitdiffstats
path: root/nikola/plugins/compile
diff options
context:
space:
mode:
authorLibravatarUnit 193 <unit193@unit193.net>2021-02-03 19:17:50 -0500
committerLibravatarUnit 193 <unit193@unit193.net>2021-02-03 19:17:50 -0500
commit475d074fd74425efbe783fad08f97f2df0c4909f (patch)
tree2acdae53999b3c74b716efa4edb5b40311fa356a /nikola/plugins/compile
parentcd502d52787f666fff3254d7d7e7578930c813c2 (diff)
parent3a0d66f07b112b6d2bdc2b57bbf717a89a351ce6 (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')
-rw-r--r--nikola/plugins/compile/__init__.py2
-rw-r--r--nikola/plugins/compile/html.plugin2
-rw-r--r--nikola/plugins/compile/html.py78
-rw-r--r--nikola/plugins/compile/ipynb.plugin6
-rw-r--r--nikola/plugins/compile/ipynb.py189
-rw-r--r--nikola/plugins/compile/markdown.plugin2
-rw-r--r--nikola/plugins/compile/markdown/__init__.py129
-rw-r--r--nikola/plugins/compile/markdown/mdx_gist.plugin2
-rw-r--r--nikola/plugins/compile/markdown/mdx_gist.py20
-rw-r--r--nikola/plugins/compile/markdown/mdx_nikola.plugin2
-rw-r--r--nikola/plugins/compile/markdown/mdx_nikola.py14
-rw-r--r--nikola/plugins/compile/markdown/mdx_podcast.plugin2
-rw-r--r--nikola/plugins/compile/markdown/mdx_podcast.py10
-rw-r--r--nikola/plugins/compile/pandoc.plugin2
-rw-r--r--nikola/plugins/compile/pandoc.py29
-rw-r--r--nikola/plugins/compile/php.plugin2
-rw-r--r--nikola/plugins/compile/php.py22
-rw-r--r--nikola/plugins/compile/rest.plugin4
-rw-r--r--nikola/plugins/compile/rest/__init__.py229
-rw-r--r--nikola/plugins/compile/rest/chart.plugin2
-rw-r--r--nikola/plugins/compile/rest/chart.py58
-rw-r--r--nikola/plugins/compile/rest/doc.plugin2
-rw-r--r--nikola/plugins/compile/rest/doc.py38
-rw-r--r--nikola/plugins/compile/rest/gist.plugin2
-rw-r--r--nikola/plugins/compile/rest/gist.py2
-rw-r--r--nikola/plugins/compile/rest/listing.plugin2
-rw-r--r--nikola/plugins/compile/rest/listing.py25
-rw-r--r--nikola/plugins/compile/rest/media.plugin2
-rw-r--r--nikola/plugins/compile/rest/media.py13
-rw-r--r--nikola/plugins/compile/rest/post_list.plugin6
-rw-r--r--nikola/plugins/compile/rest/post_list.py258
-rw-r--r--nikola/plugins/compile/rest/slides.plugin14
-rw-r--r--nikola/plugins/compile/rest/slides.py78
-rw-r--r--nikola/plugins/compile/rest/soundcloud.plugin2
-rw-r--r--nikola/plugins/compile/rest/soundcloud.py26
-rw-r--r--nikola/plugins/compile/rest/thumbnail.plugin2
-rw-r--r--nikola/plugins/compile/rest/thumbnail.py6
-rw-r--r--nikola/plugins/compile/rest/vimeo.plugin2
-rw-r--r--nikola/plugins/compile/rest/vimeo.py13
-rw-r--r--nikola/plugins/compile/rest/youtube.plugin2
-rw-r--r--nikola/plugins/compile/rest/youtube.py19
41 files changed, 618 insertions, 702 deletions
diff --git a/nikola/plugins/compile/__init__.py b/nikola/plugins/compile/__init__.py
index ff7e9a2..db78fce 100644
--- a/nikola/plugins/compile/__init__.py
+++ b/nikola/plugins/compile/__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
diff --git a/nikola/plugins/compile/html.plugin b/nikola/plugins/compile/html.plugin
index f95bdd5..be1f876 100644
--- a/nikola/plugins/compile/html.plugin
+++ b/nikola/plugins/compile/html.plugin
@@ -9,5 +9,5 @@ website = https://getnikola.com/
description = Compile HTML into HTML (just copy)
[Nikola]
-plugincategory = Compiler
+PluginCategory = Compiler
friendlyname = HTML
diff --git a/nikola/plugins/compile/html.py b/nikola/plugins/compile/html.py
index 942d6da..80b6713 100644
--- a/nikola/plugins/compile/html.py
+++ b/nikola/plugins/compile/html.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,15 +24,17 @@
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-"""Implementation of compile_html for HTML source files."""
+"""Page compiler plugin for HTML source files."""
-from __future__ import unicode_literals
-import os
import io
+import os
+
+import lxml.html
+from nikola import shortcodes as sc
from nikola.plugin_categories import PageCompiler
-from nikola.utils import makedirs, write_metadata
+from nikola.utils import LocaleBorg, makedirs, map_metadata, write_metadata
class CompileHtml(PageCompiler):
@@ -40,25 +42,27 @@ class CompileHtml(PageCompiler):
name = "html"
friendly_name = "HTML"
+ supports_metadata = True
- def compile_html(self, source, dest, is_two_file=True):
- """Compile source file into HTML and save as dest."""
+ def compile_string(self, data, source_path=None, is_two_file=True, post=None, lang=None):
+ """Compile HTML into HTML strings, with shortcode support."""
+ if not is_two_file:
+ _, data = self.split_metadata(data, post, lang)
+ new_data, shortcodes = sc.extract_shortcodes(data)
+ return self.site.apply_shortcodes_uuid(new_data, shortcodes, filename=source_path, extra_context={'post': post})
+
+ def compile(self, source, dest, is_two_file=True, post=None, lang=None):
+ """Compile the source file into HTML and save as dest."""
makedirs(os.path.dirname(dest))
- 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)
- data, shortcode_deps = self.site.apply_shortcodes(data, with_dependencies=True, extra_context=dict(post=post))
+ data, shortcode_deps = self.compile_string(data, source, is_two_file, post, lang)
out_file.write(data)
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
@@ -76,9 +80,41 @@ class CompileHtml(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, file_metadata_regexp=None, unslugify_titles=False, lang=None):
+ """Read the metadata from a post's meta tags, and return a metadata dict."""
+ if lang is None:
+ lang = LocaleBorg().current_lang
+ source_path = post.translated_source_path(lang)
+
+ with io.open(source_path, 'r', encoding='utf-8-sig') as inf:
+ data = inf.read()
+
+ metadata = {}
+ try:
+ doc = lxml.html.document_fromstring(data)
+ except lxml.etree.ParserError as e:
+ # Issue #374 -> #2851
+ if str(e) == "Document is empty":
+ return {}
+ # let other errors raise
+ raise
+ title_tag = doc.find('*//title')
+ if title_tag is not None and title_tag.text:
+ metadata['title'] = title_tag.text
+ meta_tags = doc.findall('*//meta')
+ for tag in meta_tags:
+ k = tag.get('name', '').lower()
+ if not k:
+ continue
+ elif k == 'keywords':
+ k = 'tags'
+ content = tag.get('content')
+ if content:
+ metadata[k] = content
+ map_metadata(metadata, 'html_metadata', self.site.config)
+ return metadata
diff --git a/nikola/plugins/compile/ipynb.plugin b/nikola/plugins/compile/ipynb.plugin
index c369ab2..c146172 100644
--- a/nikola/plugins/compile/ipynb.plugin
+++ b/nikola/plugins/compile/ipynb.plugin
@@ -6,8 +6,8 @@ module = ipynb
author = Damian Avila, Chris Warrick and others
version = 2.0.0
website = http://www.damian.oquanta.info/
-description = Compile IPython notebooks into Nikola posts
+description = Compile Jupyter notebooks into Nikola posts
[Nikola]
-plugincategory = Compiler
-friendlyname = Jupyter/IPython Notebook
+PluginCategory = Compiler
+friendlyname = Jupyter Notebook
diff --git a/nikola/plugins/compile/ipynb.py b/nikola/plugins/compile/ipynb.py
index f3fdeea..039604b 100644
--- a/nikola/plugins/compile/ipynb.py
+++ b/nikola/plugins/compile/ipynb.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
-# Copyright © 2013-2016 Damián Avila, Chris Warrick and others.
+# Copyright © 2013-2020 Damián Avila, Chris Warrick and others.
# Permission is hereby granted, free of charge, to any
# person obtaining a copy of this software and associated
@@ -24,99 +24,95 @@
# 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 nbconvert."""
+"""Page compiler plugin for nbconvert."""
-from __future__ import unicode_literals, print_function
import io
+import json
import os
-import sys
try:
+ import nbconvert
from nbconvert.exporters import HTMLExporter
import nbformat
current_nbformat = nbformat.current_nbformat
from jupyter_client import kernelspec
from traitlets.config import Config
+ NBCONVERT_VERSION_MAJOR = int(nbconvert.__version__.partition(".")[0])
flag = True
- ipy_modern = True
except ImportError:
- try:
- import IPython
- from IPython.nbconvert.exporters import HTMLExporter
- if IPython.version_info[0] >= 3: # API changed with 3.0.0
- from IPython import nbformat
- current_nbformat = nbformat.current_nbformat
- from IPython.kernel import kernelspec
- ipy_modern = True
- else:
- import IPython.nbformat.current as nbformat
- current_nbformat = 'json'
- kernelspec = None
- ipy_modern = False
-
- from IPython.config import Config
- flag = True
- except ImportError:
- flag = None
- ipy_modern = None
+ flag = None
+from nikola import shortcodes as sc
from nikola.plugin_categories import PageCompiler
-from nikola.utils import makedirs, req_missing, get_logger, STDERR_HANDLER
+from nikola.utils import makedirs, req_missing, LocaleBorg
class CompileIPynb(PageCompiler):
"""Compile IPynb into HTML."""
name = "ipynb"
- friendly_name = "Jupyter/IPython Notebook"
+ friendly_name = "Jupyter Notebook"
demote_headers = True
- default_kernel = 'python2' if sys.version_info[0] == 2 else 'python3'
+ default_kernel = 'python3'
+ supports_metadata = True
- def set_site(self, site):
- """Set Nikola site."""
- self.logger = get_logger('compile_ipynb', STDERR_HANDLER)
- super(CompileIPynb, self).set_site(site)
-
- def compile_html_string(self, source, is_two_file=True):
+ def _compile_string(self, nb_json):
"""Export notebooks as HTML strings."""
- if flag is None:
- req_missing(['ipython[notebook]>=2.0.0'], 'build this site (compile ipynb)')
- c = Config(self.site.config['IPYNB_CONFIG'])
+ self._req_missing_ipynb()
+ c = Config(get_default_jupyter_config())
+ c.merge(Config(self.site.config['IPYNB_CONFIG']))
+ if 'template_file' not in self.site.config['IPYNB_CONFIG'].get('Exporter', {}):
+ if NBCONVERT_VERSION_MAJOR >= 6:
+ c['Exporter']['template_file'] = 'classic/base.html.j2'
+ else:
+ c['Exporter']['template_file'] = 'basic.tpl' # not a typo
exportHtml = HTMLExporter(config=c)
- with io.open(source, "r", encoding="utf8") as in_file:
- nb_json = nbformat.read(in_file, current_nbformat)
- (body, resources) = exportHtml.from_notebook_node(nb_json)
+ body, _ = exportHtml.from_notebook_node(nb_json)
return body
- def compile_html(self, source, dest, is_two_file=True):
- """Compile source file into HTML and save as dest."""
+ @staticmethod
+ def _nbformat_read(in_file):
+ return nbformat.read(in_file, current_nbformat)
+
+ def _req_missing_ipynb(self):
+ if flag is None:
+ req_missing(['notebook>=4.0.0'], 'build this site (compile ipynb)')
+
+ def compile_string(self, data, source_path=None, is_two_file=True, post=None, lang=None):
+ """Compile notebooks into HTML strings."""
+ new_data, shortcodes = sc.extract_shortcodes(data)
+ output = self._compile_string(nbformat.reads(new_data, current_nbformat))
+ return self.site.apply_shortcodes_uuid(output, shortcodes, filename=source_path, extra_context={'post': post})
+
+ def compile(self, source, dest, is_two_file=False, post=None, lang=None):
+ """Compile the source file into HTML and save as dest."""
makedirs(os.path.dirname(dest))
- try:
- post = self.site.post_per_input_file[source]
- except KeyError:
- post = None
- with io.open(dest, "w+", encoding="utf8") as out_file:
- output = self.compile_html_string(source, is_two_file)
- output, shortcode_deps = self.site.apply_shortcodes(output, filename=source, with_dependencies=True, extra_context=dict(post=post))
+ with io.open(dest, "w+", encoding="utf-8") as out_file:
+ with io.open(source, "r", encoding="utf-8-sig") as in_file:
+ nb_str = in_file.read()
+ output, shortcode_deps = self.compile_string(nb_str, 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
- def read_metadata(self, post, file_metadata_regexp=None, unslugify_titles=False, lang=None):
+ def read_metadata(self, post, lang=None):
"""Read metadata directly from ipynb file.
- As ipynb file support arbitrary metadata as json, the metadata used by Nikola
+ As ipynb files support arbitrary metadata as json, the metadata used by Nikola
will be assume to be in the 'nikola' subfield.
"""
- if flag is None:
- req_missing(['ipython[notebook]>=2.0.0'], 'build this site (compile ipynb)')
- source = post.source_path
- with io.open(source, "r", encoding="utf8") as in_file:
+ self._req_missing_ipynb()
+ if lang is None:
+ lang = LocaleBorg().current_lang
+ source = post.translated_source_path(lang)
+ with io.open(source, "r", encoding="utf-8-sig") as in_file:
nb_json = nbformat.read(in_file, current_nbformat)
# Metadata might not exist in two-file posts or in hand-crafted
# .ipynb files.
@@ -124,11 +120,10 @@ class CompileIPynb(PageCompiler):
def create_post(self, path, **kw):
"""Create a new post."""
- if flag is None:
- req_missing(['ipython[notebook]>=2.0.0'], 'build this site (compile ipynb)')
+ self._req_missing_ipynb()
content = kw.pop('content', None)
onefile = kw.pop('onefile', False)
- kernel = kw.pop('ipython_kernel', None)
+ kernel = kw.pop('jupyter_kernel', None)
# is_page is not needed to create the file
kw.pop('is_page', False)
@@ -142,40 +137,52 @@ class CompileIPynb(PageCompiler):
# imported .ipynb file, guaranteed to start with "{" because it’s JSON.
nb = nbformat.reads(content, current_nbformat)
else:
- if ipy_modern:
- nb = nbformat.v4.new_notebook()
- nb["cells"] = [nbformat.v4.new_markdown_cell(content)]
- else:
- nb = nbformat.new_notebook()
- nb["worksheets"] = [nbformat.new_worksheet(cells=[nbformat.new_text_cell('markdown', [content])])]
-
- if kernelspec is not None:
- if kernel is None:
- kernel = self.default_kernel
- self.logger.notice('No kernel specified, assuming "{0}".'.format(kernel))
-
- IPYNB_KERNELS = {}
- ksm = kernelspec.KernelSpecManager()
- for k in ksm.find_kernel_specs():
- IPYNB_KERNELS[k] = ksm.get_kernel_spec(k).to_dict()
- IPYNB_KERNELS[k]['name'] = k
- del IPYNB_KERNELS[k]['argv']
-
- if kernel not in IPYNB_KERNELS:
- self.logger.error('Unknown kernel "{0}". Maybe you mispelled it?'.format(kernel))
- self.logger.info("Available kernels: {0}".format(", ".join(sorted(IPYNB_KERNELS))))
- raise Exception('Unknown kernel "{0}"'.format(kernel))
-
- nb["metadata"]["kernelspec"] = IPYNB_KERNELS[kernel]
- else:
- # Older IPython versions don’t need kernelspecs.
- pass
+ nb = nbformat.v4.new_notebook()
+ nb["cells"] = [nbformat.v4.new_markdown_cell(content)]
+
+ if kernel is None:
+ kernel = self.default_kernel
+ self.logger.warning('No kernel specified, assuming "{0}".'.format(kernel))
+
+ IPYNB_KERNELS = {}
+ ksm = kernelspec.KernelSpecManager()
+ for k in ksm.find_kernel_specs():
+ IPYNB_KERNELS[k] = ksm.get_kernel_spec(k).to_dict()
+ IPYNB_KERNELS[k]['name'] = k
+ del IPYNB_KERNELS[k]['argv']
+
+ if kernel not in IPYNB_KERNELS:
+ self.logger.error('Unknown kernel "{0}". Maybe you mispelled it?'.format(kernel))
+ self.logger.info("Available kernels: {0}".format(", ".join(sorted(IPYNB_KERNELS))))
+ raise Exception('Unknown kernel "{0}"'.format(kernel))
+
+ nb["metadata"]["kernelspec"] = IPYNB_KERNELS[kernel]
if onefile:
nb["metadata"]["nikola"] = metadata
- with io.open(path, "w+", encoding="utf8") as fd:
- if ipy_modern:
- nbformat.write(nb, fd, 4)
- else:
- nbformat.write(nb, fd, 'ipynb')
+ with io.open(path, "w+", encoding="utf-8") as fd:
+ nbformat.write(nb, fd, 4)
+
+
+def get_default_jupyter_config():
+ """Search default jupyter configuration location paths.
+
+ Return dictionary from configuration json files.
+ """
+ config = {}
+ from jupyter_core.paths import jupyter_config_path
+
+ for parent in jupyter_config_path():
+ try:
+ for file in os.listdir(parent):
+ if 'nbconvert' in file and file.endswith('.json'):
+ abs_path = os.path.join(parent, file)
+ with open(abs_path) as config_file:
+ config.update(json.load(config_file))
+ except OSError:
+ # some paths jupyter uses to find configurations
+ # may not exist
+ pass
+
+ return config
diff --git a/nikola/plugins/compile/markdown.plugin b/nikola/plugins/compile/markdown.plugin
index 2607413..85c67c3 100644
--- a/nikola/plugins/compile/markdown.plugin
+++ b/nikola/plugins/compile/markdown.plugin
@@ -9,5 +9,5 @@ website = https://getnikola.com/
description = Compile Markdown into HTML
[Nikola]
-plugincategory = Compiler
+PluginCategory = Compiler
friendlyname = Markdown
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
diff --git a/nikola/plugins/compile/markdown/mdx_gist.plugin b/nikola/plugins/compile/markdown/mdx_gist.plugin
index 85b5450..f962cb7 100644
--- a/nikola/plugins/compile/markdown/mdx_gist.plugin
+++ b/nikola/plugins/compile/markdown/mdx_gist.plugin
@@ -4,7 +4,7 @@ module = mdx_gist
[Nikola]
compiler = markdown
-plugincategory = CompilerExtension
+PluginCategory = CompilerExtension
[Documentation]
author = Roberto Alsina
diff --git a/nikola/plugins/compile/markdown/mdx_gist.py b/nikola/plugins/compile/markdown/mdx_gist.py
index 25c071f..f6ce20a 100644
--- a/nikola/plugins/compile/markdown/mdx_gist.py
+++ b/nikola/plugins/compile/markdown/mdx_gist.py
@@ -75,7 +75,10 @@ Error Case: non-existent file:
[:gist: 4747847 doesntexist.py]
"""
-from __future__ import unicode_literals, print_function
+import requests
+
+from nikola.plugin_categories import MarkdownExtension
+from nikola.utils import get_logger
try:
from markdown.extensions import Extension
@@ -87,12 +90,8 @@ except ImportError:
# the markdown compiler will fail first
Extension = Pattern = object
-from nikola.plugin_categories import MarkdownExtension
-from nikola.utils import get_logger, STDERR_HANDLER
-import requests
-
-LOGGER = get_logger('compile_markdown.mdx_gist', STDERR_HANDLER)
+LOGGER = get_logger('compile_markdown.mdx_gist')
GIST_JS_URL = "https://gist.github.com/{0}.js"
GIST_FILE_JS_URL = "https://gist.github.com/{0}.js?file={1}"
@@ -167,7 +166,7 @@ class GistPattern(Pattern):
pre_elem.text = AtomicString(raw_gist)
except GistFetchException as e:
- LOGGER.warn(e.message)
+ LOGGER.warning(e.message)
warning_comment = etree.Comment(' WARNING: {0} '.format(e.message))
noscript_elem.append(warning_comment)
@@ -186,15 +185,15 @@ class GistExtension(MarkdownExtension, Extension):
for key, value in configs:
self.setConfig(key, value)
- def extendMarkdown(self, md, md_globals):
+ def extendMarkdown(self, md, md_globals=None):
"""Extend Markdown."""
gist_md_pattern = GistPattern(GIST_MD_RE, self.getConfigs())
gist_md_pattern.md = md
- md.inlinePatterns.add('gist', gist_md_pattern, "<not_strong")
+ md.inlinePatterns.register(gist_md_pattern, 'gist', 175)
gist_rst_pattern = GistPattern(GIST_RST_RE, self.getConfigs())
gist_rst_pattern.md = md
- md.inlinePatterns.add('gist-rst', gist_rst_pattern, ">gist")
+ md.inlinePatterns.register(gist_rst_pattern, 'gist-rst', 176)
md.registerExtension(self)
@@ -203,6 +202,7 @@ def makeExtension(configs=None): # pragma: no cover
"""Make Markdown extension."""
return GistExtension(configs)
+
if __name__ == '__main__':
import doctest
diff --git a/nikola/plugins/compile/markdown/mdx_nikola.plugin b/nikola/plugins/compile/markdown/mdx_nikola.plugin
index 3c5c638..9751598 100644
--- a/nikola/plugins/compile/markdown/mdx_nikola.plugin
+++ b/nikola/plugins/compile/markdown/mdx_nikola.plugin
@@ -4,7 +4,7 @@ module = mdx_nikola
[Nikola]
compiler = markdown
-plugincategory = CompilerExtension
+PluginCategory = CompilerExtension
[Documentation]
author = Roberto Alsina
diff --git a/nikola/plugins/compile/markdown/mdx_nikola.py b/nikola/plugins/compile/markdown/mdx_nikola.py
index 59a5d5b..06a6d9a 100644
--- a/nikola/plugins/compile/markdown/mdx_nikola.py
+++ b/nikola/plugins/compile/markdown/mdx_nikola.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
@@ -30,8 +30,10 @@
- Strikethrough inline patterns.
"""
-from __future__ import unicode_literals
import re
+
+from nikola.plugin_categories import MarkdownExtension
+
try:
from markdown.postprocessors import Postprocessor
from markdown.inlinepatterns import SimpleTagPattern
@@ -41,8 +43,6 @@ except ImportError:
# the markdown compiler will fail first
Postprocessor = SimpleTagPattern = Extension = object
-from nikola.plugin_categories import MarkdownExtension
-
CODERE = re.compile('<div class="codehilite"><pre>(.*?)</pre></div>', flags=re.MULTILINE | re.DOTALL)
STRIKE_RE = r"(~{2})(.+?)(~{2})" # ~~strike~~
@@ -68,14 +68,14 @@ class NikolaExtension(MarkdownExtension, Extension):
def _add_nikola_post_processor(self, md):
"""Extend Markdown with the postprocessor."""
pp = NikolaPostProcessor()
- md.postprocessors.add('nikola_post_processor', pp, '_end')
+ md.postprocessors.register(pp, 'nikola_post_processor', 1)
def _add_strikethrough_inline_pattern(self, md):
"""Support PHP-Markdown style strikethrough, for example: ``~~strike~~``."""
pattern = SimpleTagPattern(STRIKE_RE, 'del')
- md.inlinePatterns.add('strikethrough', pattern, '_end')
+ md.inlinePatterns.register(pattern, 'strikethrough', 175)
- def extendMarkdown(self, md, md_globals):
+ def extendMarkdown(self, md, md_globals=None):
"""Extend markdown to Nikola flavours."""
self._add_nikola_post_processor(md)
self._add_strikethrough_inline_pattern(md)
diff --git a/nikola/plugins/compile/markdown/mdx_podcast.plugin b/nikola/plugins/compile/markdown/mdx_podcast.plugin
index c4ee7e9..df5260d 100644
--- a/nikola/plugins/compile/markdown/mdx_podcast.plugin
+++ b/nikola/plugins/compile/markdown/mdx_podcast.plugin
@@ -4,7 +4,7 @@ module = mdx_podcast
[Nikola]
compiler = markdown
-plugincategory = CompilerExtension
+PluginCategory = CompilerExtension
[Documentation]
author = Roberto Alsina
diff --git a/nikola/plugins/compile/markdown/mdx_podcast.py b/nikola/plugins/compile/markdown/mdx_podcast.py
index 96a70ed..5090407 100644
--- a/nikola/plugins/compile/markdown/mdx_podcast.py
+++ b/nikola/plugins/compile/markdown/mdx_podcast.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
-# Copyright © 2013-2016 Michael Rabbitt, Roberto Alsina and others.
+# Copyright © 2013-2020 Michael Rabbitt, Roberto Alsina and others.
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the
@@ -36,7 +36,6 @@ Basic Example:
<p><audio controls=""><source src="https://archive.org/download/Rebeldes_Stereotipos/rs20120609_1.mp3" type="audio/mpeg"></source></audio></p>
"""
-from __future__ import print_function, unicode_literals
from nikola.plugin_categories import MarkdownExtension
try:
from markdown.extensions import Extension
@@ -69,7 +68,7 @@ class PodcastPattern(Pattern):
class PodcastExtension(MarkdownExtension, Extension):
- """"Podcast extension for Markdown."""
+ """Podcast extension for Markdown."""
def __init__(self, configs={}):
"""Initialize extension."""
@@ -80,11 +79,11 @@ class PodcastExtension(MarkdownExtension, Extension):
for key, value in configs:
self.setConfig(key, value)
- def extendMarkdown(self, md, md_globals):
+ def extendMarkdown(self, md, md_globals=None):
"""Extend Markdown."""
podcast_md_pattern = PodcastPattern(PODCAST_RE, self.getConfigs())
podcast_md_pattern.md = md
- md.inlinePatterns.add('podcast', podcast_md_pattern, "<not_strong")
+ md.inlinePatterns.register(podcast_md_pattern, 'podcast', 175)
md.registerExtension(self)
@@ -92,6 +91,7 @@ def makeExtension(configs=None): # pragma: no cover
"""Make Markdown extension."""
return PodcastExtension(configs)
+
if __name__ == '__main__':
import doctest
doctest.testmod(optionflags=(doctest.NORMALIZE_WHITESPACE +
diff --git a/nikola/plugins/compile/pandoc.plugin b/nikola/plugins/compile/pandoc.plugin
index 2a69095..8f339e4 100644
--- a/nikola/plugins/compile/pandoc.plugin
+++ b/nikola/plugins/compile/pandoc.plugin
@@ -9,5 +9,5 @@ website = https://getnikola.com/
description = Compile markups into HTML using pandoc
[Nikola]
-plugincategory = Compiler
+PluginCategory = Compiler
friendlyname = Pandoc
diff --git a/nikola/plugins/compile/pandoc.py b/nikola/plugins/compile/pandoc.py
index 2368ae9..af14344 100644
--- a/nikola/plugins/compile/pandoc.py
+++ b/nikola/plugins/compile/pandoc.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,12 +24,11 @@
# 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 pandoc.
+"""Page compiler plugin for pandoc.
You will need, of course, to install pandoc
"""
-from __future__ import unicode_literals
import io
import os
@@ -48,25 +47,21 @@ class CompilePandoc(PageCompiler):
def set_site(self, site):
"""Set Nikola site."""
self.config_dependencies = [str(site.config['PANDOC_OPTIONS'])]
- super(CompilePandoc, self).set_site(site)
+ super().set_site(site)
- def compile_html(self, source, dest, is_two_file=True):
- """Compile source file into HTML and save as dest."""
+ def compile(self, source, dest, is_two_file=True, post=None, lang=None):
+ """Compile the source file into HTML and save as dest."""
makedirs(os.path.dirname(dest))
try:
- try:
- post = self.site.post_per_input_file[source]
- except KeyError:
- post = None
subprocess.check_call(['pandoc', '-o', dest, source] + self.site.config['PANDOC_OPTIONS'])
- with open(dest, 'r', encoding='utf-8') as inf:
- output, shortcode_deps = self.site.apply_shortcodes(inf.read(), with_dependencies=True)
+ with open(dest, 'r', encoding='utf-8-sig') as inf:
+ output, shortcode_deps = self.site.apply_shortcodes(inf.read())
with open(dest, 'w', encoding='utf-8') as outf:
outf.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
@@ -74,6 +69,10 @@ class CompilePandoc(PageCompiler):
if e.strreror == 'No such file or directory':
req_missing(['pandoc'], 'build this site (compile with pandoc)', python=False)
+ def compile_string(self, data, source_path=None, is_two_file=True, post=None, lang=None):
+ """Compile into HTML strings."""
+ raise ValueError("Pandoc compiler does not support compile_string due to multiple output formats")
+
def create_post(self, path, **kw):
"""Create a new post."""
content = kw.pop('content', None)
@@ -88,7 +87,5 @@ class CompilePandoc(PageCompiler):
content += '\n'
with io.open(path, "w+", encoding="utf8") 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)
diff --git a/nikola/plugins/compile/php.plugin b/nikola/plugins/compile/php.plugin
index f4fb0c1..13384bd 100644
--- a/nikola/plugins/compile/php.plugin
+++ b/nikola/plugins/compile/php.plugin
@@ -9,5 +9,5 @@ website = https://getnikola.com/
description = Compile PHP into HTML (just copy and name the file .php)
[Nikola]
-plugincategory = Compiler
+PluginCategory = Compiler
friendlyname = PHP
diff --git a/nikola/plugins/compile/php.py b/nikola/plugins/compile/php.py
index d2559fd..818e10d 100644
--- a/nikola/plugins/compile/php.py
+++ b/nikola/plugins/compile/php.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,16 +24,14 @@
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-"""Implementation of compile_html for HTML+php."""
+"""Page compiler plugin for PHP."""
-from __future__ import unicode_literals
-
-import os
import io
+import os
+from hashlib import md5
from nikola.plugin_categories import PageCompiler
from nikola.utils import makedirs, write_metadata
-from hashlib import md5
class CompilePhp(PageCompiler):
@@ -42,8 +40,8 @@ class CompilePhp(PageCompiler):
name = "php"
friendly_name = "PHP"
- def compile_html(self, source, dest, is_two_file=True):
- """Compile source file into HTML and save as dest."""
+ def compile(self, source, dest, is_two_file=True, post=None, lang=None):
+ """Compile the source file into HTML and save as dest."""
makedirs(os.path.dirname(dest))
with io.open(dest, "w+", encoding="utf8") as out_file:
with open(source, "rb") as in_file:
@@ -51,6 +49,10 @@ class CompilePhp(PageCompiler):
out_file.write('<!-- __NIKOLA_PHP_TEMPLATE_INJECTION source:{0} checksum:{1}__ -->'.format(source, hash))
return True
+ def compile_string(self, data, source_path=None, is_two_file=True, post=None, lang=None):
+ """Compile PHP into HTML strings."""
+ return data, []
+
def create_post(self, path, **kw):
"""Create a new post."""
content = kw.pop('content', None)
@@ -76,9 +78,7 @@ class CompilePhp(PageCompiler):
content += '\n'
with io.open(path, "w+", encoding="utf8") 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 extension(self):
diff --git a/nikola/plugins/compile/rest.plugin b/nikola/plugins/compile/rest.plugin
index 4d9041a..43bdf2d 100644
--- a/nikola/plugins/compile/rest.plugin
+++ b/nikola/plugins/compile/rest.plugin
@@ -6,8 +6,8 @@ module = rest
author = Roberto Alsina
version = 1.0
website = https://getnikola.com/
-description = Compile reSt into HTML
+description = Compile reST into HTML
[Nikola]
-plugincategory = Compiler
+PluginCategory = Compiler
friendlyname = reStructuredText
diff --git a/nikola/plugins/compile/rest/__init__.py b/nikola/plugins/compile/rest/__init__.py
index b75849f..44da076 100644
--- a/nikola/plugins/compile/rest/__init__.py
+++ b/nikola/plugins/compile/rest/__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
@@ -26,28 +26,28 @@
"""reStructuredText compiler for Nikola."""
-from __future__ import unicode_literals
import io
+import logging
import os
import docutils.core
import docutils.nodes
+import docutils.transforms
import docutils.utils
import docutils.io
import docutils.readers.standalone
-import docutils.writers.html4css1
+import docutils.writers.html5_polyglot
import docutils.parsers.rst.directives
from docutils.parsers.rst import roles
from nikola.nikola import LEGAL_VALUES
+from nikola.metadata_extractors import MetaCondition
from nikola.plugin_categories import PageCompiler
from nikola.utils import (
- unicode_str,
- get_logger,
makedirs,
write_metadata,
- STDERR_HANDLER,
- LocaleBorg
+ LocaleBorg,
+ map_metadata
)
@@ -58,15 +58,57 @@ class CompileRest(PageCompiler):
friendly_name = "reStructuredText"
demote_headers = True
logger = None
-
- def compile_html_string(self, data, source_path=None, is_two_file=True):
+ supports_metadata = True
+ metadata_conditions = [(MetaCondition.config_bool, "USE_REST_DOCINFO_METADATA")]
+
+ def read_metadata(self, post, lang=None):
+ """Read the metadata from a post, and return a metadata dict."""
+ if lang is None:
+ lang = LocaleBorg().current_lang
+ source_path = post.translated_source_path(lang)
+
+ # Silence reST errors, some of which are due to a different
+ # environment. Real issues will be reported while compiling.
+ null_logger = logging.getLogger('NULL')
+ null_logger.setLevel(1000)
+ with io.open(source_path, 'r', encoding='utf-8-sig') as inf:
+ data = inf.read()
+ _, _, _, document = rst2html(data, logger=null_logger, source_path=source_path, transforms=self.site.rst_transforms)
+ meta = {}
+ if 'title' in document:
+ meta['title'] = document['title']
+ for docinfo in document.traverse(docutils.nodes.docinfo):
+ for element in docinfo.children:
+ if element.tagname == 'field': # custom fields (e.g. summary)
+ name_elem, body_elem = element.children
+ name = name_elem.astext()
+ value = body_elem.astext()
+ elif element.tagname == 'authors': # author list
+ name = element.tagname
+ value = [element.astext() for element in element.children]
+ else: # standard fields (e.g. address)
+ name = element.tagname
+ value = element.astext()
+ name = name.lower()
+
+ meta[name] = value
+
+ # Put 'authors' meta field contents in 'author', too
+ if 'authors' in meta and 'author' not in meta:
+ meta['author'] = '; '.join(meta['authors'])
+
+ # Map metadata from other platforms to names Nikola expects (Issue #2817)
+ map_metadata(meta, 'rest_docinfo', self.site.config)
+ return meta
+
+ def compile_string(self, data, source_path=None, is_two_file=True, post=None, lang=None):
"""Compile reST into HTML strings."""
# If errors occur, this will be added to the line number reported by
# docutils so the line number matches the actual line number (off by
# 7 with default metadata, could be more or less depending on the post).
add_ln = 0
if not is_two_file:
- m_data, data = self.split_metadata(data)
+ m_data, data = self.split_metadata(data, post, lang)
add_ln = len(m_data.splitlines()) + 1
default_template_path = os.path.join(os.path.dirname(__file__), 'template.txt')
@@ -76,38 +118,42 @@ class CompileRest(PageCompiler):
'stylesheet_path': None,
'link_stylesheet': True,
'syntax_highlight': 'short',
- 'math_output': 'mathjax',
+ # This path is not used by Nikola, but we need something to silence
+ # warnings about it from reST.
+ 'math_output': 'mathjax /assets/js/mathjax.js',
'template': default_template_path,
- 'language_code': LEGAL_VALUES['DOCUTILS_LOCALES'].get(LocaleBorg().current_lang, 'en')
+ 'language_code': LEGAL_VALUES['DOCUTILS_LOCALES'].get(LocaleBorg().current_lang, 'en'),
+ 'doctitle_xform': self.site.config.get('USE_REST_DOCINFO_METADATA'),
+ 'file_insertion_enabled': self.site.config.get('REST_FILE_INSERTION_ENABLED'),
}
- output, error_level, deps = rst2html(
- data, settings_overrides=settings_overrides, logger=self.logger, source_path=source_path, l_add_ln=add_ln, transforms=self.site.rst_transforms,
- no_title_transform=self.site.config.get('NO_DOCUTILS_TITLE_TRANSFORM', False))
- if not isinstance(output, unicode_str):
+ from nikola import shortcodes as sc
+ new_data, shortcodes = sc.extract_shortcodes(data)
+ if self.site.config.get('HIDE_REST_DOCINFO', False):
+ self.site.rst_transforms.append(RemoveDocinfo)
+ output, error_level, deps, _ = rst2html(
+ new_data, settings_overrides=settings_overrides, logger=self.logger, source_path=source_path, l_add_ln=add_ln, transforms=self.site.rst_transforms)
+ if not isinstance(output, str):
# To prevent some weird bugs here or there.
# Original issue: empty files. `output` became a bytestring.
output = output.decode('utf-8')
- return output, error_level, deps
- def compile_html(self, source, dest, is_two_file=True):
- """Compile source file into HTML and save as dest."""
+ output, shortcode_deps = self.site.apply_shortcodes_uuid(output, shortcodes, filename=source_path, extra_context={'post': post})
+ return output, error_level, deps, shortcode_deps
+
+ def compile(self, source, dest, is_two_file=True, post=None, lang=None):
+ """Compile the source file into HTML and save as dest."""
makedirs(os.path.dirname(dest))
error_level = 100
- with io.open(dest, "w+", encoding="utf8") as out_file:
- try:
- post = self.site.post_per_input_file[source]
- except KeyError:
- post = None
- 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()
- output, error_level, deps = self.compile_html_string(data, source, is_two_file)
- output, shortcode_deps = self.site.apply_shortcodes(output, filename=source, with_dependencies=True, extra_context=dict(post=post))
+ output, error_level, deps, shortcode_deps = self.compile_string(data, source, is_two_file, post, lang)
out_file.write(output)
if post is None:
if deps.list:
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] += deps.list
@@ -129,23 +175,21 @@ class CompileRest(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(write_metadata(metadata))
- fd.write('\n')
+ fd.write(write_metadata(metadata, comment_wrap=False, site=self.site, compiler=self))
fd.write(content)
def set_site(self, site):
"""Set Nikola site."""
- super(CompileRest, self).set_site(site)
+ super().set_site(site)
self.config_dependencies = []
for plugin_info in self.get_compiler_extensions():
self.config_dependencies.append(plugin_info.name)
plugin_info.plugin_object.short_help = plugin_info.description
- self.logger = get_logger('compile_rest', STDERR_HANDLER)
if not site.debug:
- self.logger.level = 4
+ self.logger.level = logging.WARNING
def get_observer(settings):
@@ -155,19 +199,25 @@ def get_observer(settings):
Error code mapping:
- +------+---------+------+----------+
- | dNUM | dNAME | lNUM | lNAME | d = docutils, l = logbook
- +------+---------+------+----------+
- | 0 | DEBUG | 1 | DEBUG |
- | 1 | INFO | 2 | INFO |
- | 2 | WARNING | 4 | WARNING |
- | 3 | ERROR | 5 | ERROR |
- | 4 | SEVERE | 6 | CRITICAL |
- +------+---------+------+----------+
+ +----------+----------+
+ | docutils | logging |
+ +----------+----------+
+ | DEBUG | DEBUG |
+ | INFO | INFO |
+ | WARNING | WARNING |
+ | ERROR | ERROR |
+ | SEVERE | CRITICAL |
+ +----------+----------+
"""
- errormap = {0: 1, 1: 2, 2: 4, 3: 5, 4: 6}
+ errormap = {
+ docutils.utils.Reporter.DEBUG_LEVEL: logging.DEBUG,
+ docutils.utils.Reporter.INFO_LEVEL: logging.INFO,
+ docutils.utils.Reporter.WARNING_LEVEL: logging.WARNING,
+ docutils.utils.Reporter.ERROR_LEVEL: logging.ERROR,
+ docutils.utils.Reporter.SEVERE_LEVEL: logging.CRITICAL
+ }
text = docutils.nodes.Element.astext(msg)
- line = msg['line'] + settings['add_ln'] if 'line' in msg else 0
+ line = msg['line'] + settings['add_ln'] if 'line' in msg else ''
out = '[{source}{colon}{line}] {text}'.format(
source=settings['source'], colon=(':' if line else ''),
line=line, text=text)
@@ -179,32 +229,32 @@ def get_observer(settings):
class NikolaReader(docutils.readers.standalone.Reader):
"""Nikola-specific docutils reader."""
+ config_section = 'nikola'
+
def __init__(self, *args, **kwargs):
"""Initialize the reader."""
self.transforms = kwargs.pop('transforms', [])
- self.no_title_transform = kwargs.pop('no_title_transform', False)
+ self.logging_settings = kwargs.pop('nikola_logging_settings', {})
docutils.readers.standalone.Reader.__init__(self, *args, **kwargs)
def get_transforms(self):
"""Get docutils transforms."""
- transforms = docutils.readers.standalone.Reader(self).get_transforms() + self.transforms
- if self.no_title_transform:
- transforms = [t for t in transforms if str(t) != "<class 'docutils.transforms.frontmatter.DocTitle'>"]
- return transforms
+ return docutils.readers.standalone.Reader(self).get_transforms() + self.transforms
def new_document(self):
"""Create and return a new empty document tree (root node)."""
document = docutils.utils.new_document(self.source.source_path, self.settings)
document.reporter.stream = False
- document.reporter.attach_observer(get_observer(self.l_settings))
+ document.reporter.attach_observer(get_observer(self.logging_settings))
return document
def shortcode_role(name, rawtext, text, lineno, inliner,
options={}, content=[]):
- """A shortcode role that passes through raw inline HTML."""
+ """Return a shortcode role that passes through raw inline HTML."""
return [docutils.nodes.raw('', text, format='html')], []
+
roles.register_canonical_role('raw-html', shortcode_role)
roles.register_canonical_role('html', shortcode_role)
roles.register_canonical_role('sc', shortcode_role)
@@ -226,7 +276,7 @@ def add_node(node, visit_function=None, depart_function=None):
self.site = site
directives.register_directive('math', MathDirective)
add_node(MathBlock, visit_Math, depart_Math)
- return super(Plugin, self).set_site(site)
+ return super().set_site(site)
class MathDirective(Directive):
def run(self):
@@ -245,18 +295,53 @@ def add_node(node, visit_function=None, depart_function=None):
"""
docutils.nodes._add_node_class_names([node.__name__])
if visit_function:
- setattr(docutils.writers.html4css1.HTMLTranslator, 'visit_' + node.__name__, visit_function)
+ setattr(docutils.writers.html5_polyglot.HTMLTranslator, 'visit_' + node.__name__, visit_function)
if depart_function:
- setattr(docutils.writers.html4css1.HTMLTranslator, 'depart_' + node.__name__, depart_function)
+ setattr(docutils.writers.html5_polyglot.HTMLTranslator, 'depart_' + node.__name__, depart_function)
+
+
+# Output <code> for ``double backticks``. (Code and extra logic based on html4css1 translator)
+def visit_literal(self, node):
+ """Output <code> for double backticks."""
+ # special case: "code" role
+ classes = node.get('classes', [])
+ if 'code' in classes:
+ # filter 'code' from class arguments
+ node['classes'] = [cls for cls in classes if cls != 'code']
+ self.body.append(self.starttag(node, 'code', ''))
+ return
+ self.body.append(
+ self.starttag(node, 'code', '', CLASS='docutils literal'))
+ text = node.astext()
+ for token in self.words_and_spaces.findall(text):
+ if token.strip():
+ # Protect text like "--an-option" and the regular expression
+ # ``[+]?(\d+(\.\d*)?|\.\d+)`` from bad line wrapping
+ if self.in_word_wrap_point.search(token):
+ self.body.append('<span class="pre">%s</span>'
+ % self.encode(token))
+ else:
+ self.body.append(self.encode(token))
+ elif token in ('\n', ' '):
+ # Allow breaks at whitespace:
+ self.body.append(token)
+ else:
+ # Protect runs of multiple spaces; the last space can wrap:
+ self.body.append('&nbsp;' * (len(token) - 1) + ' ')
+ self.body.append('</code>')
+ # Content already processed:
+ raise docutils.nodes.SkipNode
+
+
+setattr(docutils.writers.html5_polyglot.HTMLTranslator, 'visit_literal', visit_literal)
def rst2html(source, source_path=None, source_class=docutils.io.StringInput,
destination_path=None, reader=None,
parser=None, parser_name='restructuredtext', writer=None,
- writer_name='html', settings=None, settings_spec=None,
- settings_overrides=None, config_section=None,
- enable_exit_status=None, logger=None, l_add_ln=0, transforms=None,
- no_title_transform=False):
+ writer_name='html5_polyglot', settings=None, settings_spec=None,
+ settings_overrides=None, config_section='nikola',
+ enable_exit_status=None, logger=None, l_add_ln=0, transforms=None):
"""Set up & run a ``Publisher``, and return a dictionary of document parts.
Dictionary keys are the names of parts, and values are Unicode strings;
@@ -268,20 +353,22 @@ def rst2html(source, source_path=None, source_class=docutils.io.StringInput,
publish_parts(..., settings_overrides={'input_encoding': 'unicode'})
- Parameters: see `publish_programmatically`.
+ For a description of the parameters, see `publish_programmatically`.
WARNING: `reader` should be None (or NikolaReader()) if you want Nikola to report
reStructuredText syntax errors.
"""
if reader is None:
- reader = NikolaReader(transforms=transforms, no_title_transform=no_title_transform)
# For our custom logging, we have special needs and special settings we
# specify here.
# logger a logger from Nikola
# source source filename (docutils gets a string)
- # add_ln amount of metadata lines (see comment in compile_html above)
- reader.l_settings = {'logger': logger, 'source': source_path,
- 'add_ln': l_add_ln}
+ # add_ln amount of metadata lines (see comment in CompileRest.compile above)
+ reader = NikolaReader(transforms=transforms,
+ nikola_logging_settings={
+ 'logger': logger, 'source': source_path,
+ 'add_ln': l_add_ln
+ })
pub = docutils.core.Publisher(reader, parser, writer, settings=settings,
source_class=source_class,
@@ -294,7 +381,8 @@ def rst2html(source, source_path=None, source_class=docutils.io.StringInput,
pub.set_destination(None, destination_path)
pub.publish(enable_exit_status=enable_exit_status)
- return pub.writer.parts['docinfo'] + pub.writer.parts['fragment'], pub.document.reporter.max_level, pub.settings.record_dependencies
+ return pub.writer.parts['docinfo'] + pub.writer.parts['fragment'], pub.document.reporter.max_level, pub.settings.record_dependencies, pub.document
+
# Alignment helpers for extensions
_align_options_base = ('left', 'center', 'right')
@@ -302,3 +390,14 @@ _align_options_base = ('left', 'center', 'right')
def _align_choice(argument):
return docutils.parsers.rst.directives.choice(argument, _align_options_base + ("none", ""))
+
+
+class RemoveDocinfo(docutils.transforms.Transform):
+ """Remove docinfo nodes."""
+
+ default_priority = 870
+
+ def apply(self):
+ """Remove docinfo nodes."""
+ for node in self.document.traverse(docutils.nodes.docinfo):
+ node.parent.remove(node)
diff --git a/nikola/plugins/compile/rest/chart.plugin b/nikola/plugins/compile/rest/chart.plugin
index 0a7896f..4434477 100644
--- a/nikola/plugins/compile/rest/chart.plugin
+++ b/nikola/plugins/compile/rest/chart.plugin
@@ -4,7 +4,7 @@ module = chart
[Nikola]
compiler = rest
-plugincategory = CompilerExtension
+PluginCategory = CompilerExtension
[Documentation]
author = Roberto Alsina
diff --git a/nikola/plugins/compile/rest/chart.py b/nikola/plugins/compile/rest/chart.py
index 24f459b..17363cb 100644
--- a/nikola/plugins/compile/rest/chart.py
+++ b/nikola/plugins/compile/rest/chart.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
@@ -23,21 +23,17 @@
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
"""Chart directive for reSTructuredText."""
-from ast import literal_eval
-
from docutils import nodes
from docutils.parsers.rst import Directive, directives
+from nikola.plugin_categories import RestExtension
+
try:
import pygal
except ImportError:
- pygal = None # NOQA
-
-from nikola.plugin_categories import RestExtension
-from nikola.utils import req_missing
+ pygal = None
_site = None
@@ -52,8 +48,7 @@ class Plugin(RestExtension):
global _site
_site = self.site = site
directives.register_directive('chart', Chart)
- self.site.register_shortcode('chart', _gen_chart)
- return super(Plugin, self).set_site(site)
+ return super().set_site(site)
class Chart(Directive):
@@ -77,6 +72,7 @@ class Chart(Directive):
"classes": directives.unchanged,
"css": directives.unchanged,
"defs": directives.unchanged,
+ "data_file": directives.unchanged,
"disable_xml_declaration": directives.unchanged,
"dots_size": directives.unchanged,
"dynamic_print_values": directives.unchanged,
@@ -157,41 +153,9 @@ class Chart(Directive):
def run(self):
"""Run the directive."""
self.options['site'] = None
- html = _gen_chart(self.arguments[0], data='\n'.join(self.content), **self.options)
+ html = _site.plugin_manager.getPluginByName(
+ 'chart', 'ShortcodePlugin').plugin_object.handler(
+ self.arguments[0],
+ data='\n'.join(self.content),
+ **self.options)
return [nodes.raw('', html, format='html')]
-
-
-def _gen_chart(chart_type, **_options):
- if pygal is None:
- msg = req_missing(['pygal'], 'use the Chart directive', optional=True)
- return '<div class="text-error">{0}</div>'.format(msg)
- options = {}
- data = _options.pop('data')
- _options.pop('post', None)
- _options.pop('site')
- if 'style' in _options:
- style_name = _options.pop('style')
- else:
- style_name = 'BlueStyle'
- if '(' in style_name: # Parametric style
- style = eval('pygal.style.' + style_name)
- else:
- style = getattr(pygal.style, style_name)
- for k, v in _options.items():
- try:
- options[k] = literal_eval(v)
- except:
- options[k] = v
- chart = pygal
- for o in chart_type.split('.'):
- chart = getattr(chart, o)
- chart = chart(style=style)
- if _site and _site.invariant:
- chart.no_prefix = True
- chart.config(**options)
- for line in data.splitlines():
- line = line.strip()
- if line:
- label, series = literal_eval('({0})'.format(line))
- chart.add(label, series)
- return chart.render().decode('utf8')
diff --git a/nikola/plugins/compile/rest/doc.plugin b/nikola/plugins/compile/rest/doc.plugin
index e447eb2..3b5c9c7 100644
--- a/nikola/plugins/compile/rest/doc.plugin
+++ b/nikola/plugins/compile/rest/doc.plugin
@@ -4,7 +4,7 @@ module = doc
[Nikola]
compiler = rest
-plugincategory = CompilerExtension
+PluginCategory = CompilerExtension
[Documentation]
author = Manuel Kaufmann
diff --git a/nikola/plugins/compile/rest/doc.py b/nikola/plugins/compile/rest/doc.py
index 55f576d..705c0bc 100644
--- a/nikola/plugins/compile/rest/doc.py
+++ b/nikola/plugins/compile/rest/doc.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
@@ -29,7 +29,7 @@
from docutils import nodes
from docutils.parsers.rst import roles
-from nikola.utils import split_explicit_title, LOGGER
+from nikola.utils import split_explicit_title, LOGGER, slugify
from nikola.plugin_categories import RestExtension
@@ -44,14 +44,11 @@ class Plugin(RestExtension):
roles.register_canonical_role('doc', doc_role)
self.site.register_shortcode('doc', doc_shortcode)
doc_role.site = site
- return super(Plugin, self).set_site(site)
+ return super().set_site(site)
-def _doc_link(rawtext, text, options={}, content=[]):
- """Handle the doc role."""
- # split link's text and post's slug in role content
- has_explicit_title, title, slug = split_explicit_title(text)
- # check if the slug given is part of our blog posts/pages
+def _find_post(slug):
+ """Find a post with the given slug in posts or pages."""
twin_slugs = False
post = None
for p in doc_role.site.timeline:
@@ -61,10 +58,27 @@ def _doc_link(rawtext, text, options={}, content=[]):
else:
twin_slugs = True
break
+ return post, twin_slugs
+
+
+def _doc_link(rawtext, text, options={}, content=[]):
+ """Handle the doc role."""
+ # split link's text and post's slug in role content
+ has_explicit_title, title, slug = split_explicit_title(text)
+ if '#' in slug:
+ slug, fragment = slug.split('#', 1)
+ else:
+ fragment = None
+
+ # Look for the unslugified input first, then try to slugify (Issue #3450)
+ post, twin_slugs = _find_post(slug)
+ if post is None:
+ slug = slugify(slug)
+ post, twin_slugs = _find_post(slug)
try:
if post is None:
- raise ValueError
+ raise ValueError("No post with matching slug found.")
except ValueError:
return False, False, None, None, slug
@@ -72,6 +86,8 @@ def _doc_link(rawtext, text, options={}, content=[]):
# use post's title as link's text
title = post.title()
permalink = post.permalink()
+ if fragment:
+ permalink += '#' + fragment
return True, twin_slugs, title, permalink, slug
@@ -83,7 +99,7 @@ def doc_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
if twin_slugs:
inliner.reporter.warning(
'More than one post with the same slug. Using "{0}"'.format(permalink))
- LOGGER.warn(
+ LOGGER.warning(
'More than one post with the same slug. Using "{0}" for doc role'.format(permalink))
node = make_link_node(rawtext, title, permalink, options)
return [node], []
@@ -101,7 +117,7 @@ def doc_shortcode(*args, **kwargs):
success, twin_slugs, title, permalink, slug = _doc_link(text, text, LOGGER)
if success:
if twin_slugs:
- LOGGER.warn(
+ LOGGER.warning(
'More than one post with the same slug. Using "{0}" for doc shortcode'.format(permalink))
return '<a href="{0}">{1}</a>'.format(permalink, title)
else:
diff --git a/nikola/plugins/compile/rest/gist.plugin b/nikola/plugins/compile/rest/gist.plugin
index 763c1d2..4a8a3a7 100644
--- a/nikola/plugins/compile/rest/gist.plugin
+++ b/nikola/plugins/compile/rest/gist.plugin
@@ -4,7 +4,7 @@ module = gist
[Nikola]
compiler = rest
-plugincategory = CompilerExtension
+PluginCategory = CompilerExtension
[Documentation]
author = Roberto Alsina
diff --git a/nikola/plugins/compile/rest/gist.py b/nikola/plugins/compile/rest/gist.py
index e40c3b2..08aa46d 100644
--- a/nikola/plugins/compile/rest/gist.py
+++ b/nikola/plugins/compile/rest/gist.py
@@ -19,7 +19,7 @@ class Plugin(RestExtension):
"""Set Nikola site."""
self.site = site
directives.register_directive('gist', GitHubGist)
- return super(Plugin, self).set_site(site)
+ return super().set_site(site)
class GitHubGist(Directive):
diff --git a/nikola/plugins/compile/rest/listing.plugin b/nikola/plugins/compile/rest/listing.plugin
index 3ebb296..5239f92 100644
--- a/nikola/plugins/compile/rest/listing.plugin
+++ b/nikola/plugins/compile/rest/listing.plugin
@@ -4,7 +4,7 @@ module = listing
[Nikola]
compiler = rest
-plugincategory = CompilerExtension
+PluginCategory = CompilerExtension
[Documentation]
author = Roberto Alsina
diff --git a/nikola/plugins/compile/rest/listing.py b/nikola/plugins/compile/rest/listing.py
index 4dfbedc..e5a73fa 100644
--- a/nikola/plugins/compile/rest/listing.py
+++ b/nikola/plugins/compile/rest/listing.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
@@ -28,26 +28,21 @@
"""Define and register a listing directive using the existing CodeBlock."""
-from __future__ import unicode_literals
import io
import os
import uuid
-try:
- from urlparse import urlunsplit
-except ImportError:
- from urllib.parse import urlunsplit # NOQA
+from urllib.parse import urlunsplit
import docutils.parsers.rst.directives.body
import docutils.parsers.rst.directives.misc
+import pygments
+import pygments.util
from docutils import core
from docutils import nodes
from docutils.parsers.rst import Directive, directives
from docutils.parsers.rst.roles import set_classes
from docutils.parsers.rst.directives.misc import Include
-
from pygments.lexers import get_lexer_by_name
-import pygments
-import pygments.util
from nikola import utils
from nikola.plugin_categories import RestExtension
@@ -119,6 +114,7 @@ class CodeBlock(Directive):
return [node]
+
# Monkey-patch: replace insane docutils CodeBlock with our implementation.
docutils.parsers.rst.directives.body.CodeBlock = CodeBlock
docutils.parsers.rst.directives.misc.CodeBlock = CodeBlock
@@ -142,7 +138,7 @@ class Plugin(RestExtension):
directives.register_directive('sourcecode', CodeBlock)
directives.register_directive('listing', Listing)
Listing.folders = site.config['LISTINGS_FOLDERS']
- return super(Plugin, self).set_site(site)
+ return super().set_site(site)
# Add sphinx compatibility option
@@ -186,7 +182,7 @@ class Listing(Include):
self.arguments.insert(0, fpath)
if 'linenos' in self.options:
self.options['number-lines'] = self.options['linenos']
- with io.open(fpath, 'r+', encoding='utf8') as fileobject:
+ with io.open(fpath, 'r+', encoding='utf-8-sig') as fileobject:
self.content = fileobject.read().splitlines()
self.state.document.settings.record_dependencies.add(fpath)
target = urlunsplit(("link", 'listing', fpath.replace('\\', '/'), '', ''))
@@ -200,8 +196,11 @@ class Listing(Include):
def get_code_from_file(self, data):
"""Create CodeBlock nodes from file object content."""
- return super(Listing, self).run()
+ return super().run()
def assert_has_content(self):
- """Listing has no content, override check from superclass."""
+ """Override check from superclass with nothing.
+
+ Listing has no content, override check from superclass.
+ """
pass
diff --git a/nikola/plugins/compile/rest/media.plugin b/nikola/plugins/compile/rest/media.plugin
index 8dfb19c..396c2f9 100644
--- a/nikola/plugins/compile/rest/media.plugin
+++ b/nikola/plugins/compile/rest/media.plugin
@@ -4,7 +4,7 @@ module = media
[Nikola]
compiler = rest
-plugincategory = CompilerExtension
+PluginCategory = CompilerExtension
[Documentation]
author = Roberto Alsina
diff --git a/nikola/plugins/compile/rest/media.py b/nikola/plugins/compile/rest/media.py
index 8a69586..d29d0a2 100644
--- a/nikola/plugins/compile/rest/media.py
+++ b/nikola/plugins/compile/rest/media.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
@@ -29,14 +29,13 @@
from docutils import nodes
from docutils.parsers.rst import Directive, directives
+from nikola.plugin_categories import RestExtension
+from nikola.utils import req_missing
+
try:
import micawber
except ImportError:
- micawber = None # NOQA
-
-
-from nikola.plugin_categories import RestExtension
-from nikola.utils import req_missing
+ micawber = None
class Plugin(RestExtension):
@@ -49,7 +48,7 @@ class Plugin(RestExtension):
self.site = site
directives.register_directive('media', Media)
self.site.register_shortcode('media', _gen_media_embed)
- return super(Plugin, self).set_site(site)
+ return super().set_site(site)
class Media(Directive):
diff --git a/nikola/plugins/compile/rest/post_list.plugin b/nikola/plugins/compile/rest/post_list.plugin
index 1802f2b..68abaef 100644
--- a/nikola/plugins/compile/rest/post_list.plugin
+++ b/nikola/plugins/compile/rest/post_list.plugin
@@ -4,11 +4,11 @@ module = post_list
[Nikola]
compiler = rest
-plugincategory = CompilerExtension
+PluginCategory = CompilerExtension
[Documentation]
author = Udo Spallek
-version = 0.1
+version = 0.2
website = https://getnikola.com/
-description = Includes a list of posts with tag and slide based filters.
+description = Includes a list of posts with tag and slice based filters.
diff --git a/nikola/plugins/compile/rest/post_list.py b/nikola/plugins/compile/rest/post_list.py
index 8cfd5bf..f7e95ed 100644
--- a/nikola/plugins/compile/rest/post_list.py
+++ b/nikola/plugins/compile/rest/post_list.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
-# Copyright © 2013-2016 Udo Spallek, Roberto Alsina and others.
+# Copyright © 2013-2020 Udo Spallek, Roberto Alsina and others.
# Permission is hereby granted, free of charge, to any
# person obtaining a copy of this software and associated
@@ -23,21 +23,13 @@
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
"""Post list directive for reStructuredText."""
-from __future__ import unicode_literals
-
-import os
-import uuid
-import natsort
-
from docutils import nodes
from docutils.parsers.rst import Directive, directives
from nikola import utils
from nikola.plugin_categories import RestExtension
-from nikola.packages.datecond import date_in_range
# WARNING: the directive name is post-list
# (with a DASH instead of an UNDERSCORE)
@@ -51,91 +43,14 @@ class Plugin(RestExtension):
def set_site(self, site):
"""Set Nikola site."""
self.site = site
- self.site.register_shortcode('post-list', _do_post_list)
- directives.register_directive('post-list', PostList)
- PostList.site = site
- return super(Plugin, self).set_site(site)
-
-
-class PostList(Directive):
- """Provide a reStructuredText directive to create a list of posts.
-
- Post List
- =========
- :Directive Arguments: None.
- :Directive Options: lang, start, stop, reverse, sort, date, tags, categories, sections, slugs, post_type, all, template, id
- :Directive Content: None.
-
- The posts appearing in the list can be filtered by options.
- *List slicing* is provided with the *start*, *stop* and *reverse* options.
-
- The following not required options are recognized:
-
- ``start`` : integer
- The index of the first post to show.
- A negative value like ``-3`` will show the *last* three posts in the
- post-list.
- Defaults to None.
-
- ``stop`` : integer
- The index of the last post to show.
- A value negative value like ``-1`` will show every post, but not the
- *last* in the post-list.
- Defaults to None.
-
- ``reverse`` : flag
- Reverse the order of the post-list.
- Defaults is to not reverse the order of posts.
-
- ``sort`` : string
- Sort post list by one of each post's attributes, usually ``title`` or a
- custom ``priority``. Defaults to None (chronological sorting).
+ directives.register_directive('post-list', PostListDirective)
+ directives.register_directive('post_list', PostListDirective)
+ PostListDirective.site = site
+ return super().set_site(site)
- ``date`` : string
- Show posts that match date range specified by this option. Format:
- * comma-separated clauses (AND)
- * clause: attribute comparison_operator value (spaces optional)
- * attribute: year, month, day, hour, month, second, weekday, isoweekday; or empty for full datetime
- * comparison_operator: == != <= >= < >
- * value: integer or dateutil-compatible date input
-
- ``tags`` : string [, string...]
- Filter posts to show only posts having at least one of the ``tags``.
- Defaults to None.
-
- ``categories`` : string [, string...]
- Filter posts to show only posts having one of the ``categories``.
- Defaults to None.
-
- ``sections`` : string [, string...]
- Filter posts to show only posts having one of the ``sections``.
- Defaults to None.
-
- ``slugs`` : string [, string...]
- Filter posts to show only posts having at least one of the ``slugs``.
- Defaults to None.
-
- ``post_type`` (or ``type``) : string
- Show only ``posts``, ``pages`` or ``all``.
- Replaces ``all``. Defaults to ``posts``.
-
- ``all`` : flag
- (deprecated, use ``post_type`` instead)
- Shows all posts and pages in the post list. Defaults to show only posts.
-
- ``lang`` : string
- The language of post *titles* and *links*.
- Defaults to default language.
-
- ``template`` : string
- The name of an alternative template to render the post-list.
- Defaults to ``post_list_directive.tmpl``
-
- ``id`` : string
- A manual id for the post list.
- Defaults to a random name composed by 'post_list_' + uuid.uuid4().hex.
- """
+class PostListDirective(Directive):
+ """Provide a reStructuredText directive to create a list of posts."""
option_spec = {
'start': int,
@@ -143,12 +58,12 @@ class PostList(Directive):
'reverse': directives.flag,
'sort': directives.unchanged,
'tags': directives.unchanged,
+ 'require_all_tags': directives.flag,
'categories': directives.unchanged,
'sections': directives.unchanged,
'slugs': directives.unchanged,
'post_type': directives.unchanged,
'type': directives.unchanged,
- 'all': directives.flag,
'lang': directives.unchanged,
'template': directives.path,
'id': directives.unchanged,
@@ -161,151 +76,42 @@ class PostList(Directive):
stop = self.options.get('stop')
reverse = self.options.get('reverse', False)
tags = self.options.get('tags')
+ require_all_tags = 'require_all_tags' in self.options
categories = self.options.get('categories')
sections = self.options.get('sections')
slugs = self.options.get('slugs')
post_type = self.options.get('post_type')
type = self.options.get('type', False)
- all = self.options.get('all', False)
lang = self.options.get('lang', utils.LocaleBorg().current_lang)
template = self.options.get('template', 'post_list_directive.tmpl')
sort = self.options.get('sort')
date = self.options.get('date')
-
- output, deps = _do_post_list(start, stop, reverse, tags, categories, sections, slugs, post_type, type,
- all, lang, template, sort, state=self.state, site=self.site, date=date)
- self.state.document.settings.record_dependencies.add("####MAGIC####TIMELINE")
+ filename = self.state.document.settings._nikola_source_path
+
+ output, deps = self.site.plugin_manager.getPluginByName(
+ 'post_list', 'ShortcodePlugin').plugin_object.handler(
+ start,
+ stop,
+ reverse,
+ tags,
+ require_all_tags,
+ categories,
+ sections,
+ slugs,
+ post_type,
+ type,
+ lang,
+ template,
+ sort,
+ state=self.state,
+ site=self.site,
+ date=date,
+ filename=filename)
+ self.state.document.settings.record_dependencies.add(
+ "####MAGIC####TIMELINE")
for d in deps:
self.state.document.settings.record_dependencies.add(d)
if output:
return [nodes.raw('', output, format='html')]
else:
return []
-
-
-def _do_post_list(start=None, stop=None, reverse=False, tags=None, categories=None,
- sections=None, slugs=None, post_type='post', type=False, all=False,
- lang=None, template='post_list_directive.tmpl', sort=None,
- id=None, data=None, state=None, site=None, date=None, filename=None, post=None):
- if lang is None:
- lang = utils.LocaleBorg().current_lang
- if site.invariant: # for testing purposes
- post_list_id = id or 'post_list_' + 'fixedvaluethatisnotauuid'
- else:
- post_list_id = id or 'post_list_' + uuid.uuid4().hex
-
- # Get post from filename if available
- if filename:
- self_post = site.post_per_input_file.get(filename)
- else:
- self_post = None
-
- if self_post:
- self_post.register_depfile("####MAGIC####TIMELINE", lang=lang)
-
- # If we get strings for start/stop, make them integers
- if start is not None:
- start = int(start)
- if stop is not None:
- stop = int(stop)
-
- # Parse tags/categories/sections/slugs (input is strings)
- tags = [t.strip().lower() for t in tags.split(',')] if tags else []
- categories = [c.strip().lower() for c in categories.split(',')] if categories else []
- sections = [s.strip().lower() for s in sections.split(',')] if sections else []
- slugs = [s.strip() for s in slugs.split(',')] if slugs else []
-
- filtered_timeline = []
- posts = []
- step = -1 if reverse is None else None
-
- if type is not False:
- post_type = type
-
- # TODO: remove in v8
- if all is not False:
- timeline = [p for p in site.timeline]
- elif post_type == 'page' or post_type == 'pages':
- timeline = [p for p in site.timeline if not p.use_in_feeds]
- elif post_type == 'all':
- timeline = [p for p in site.timeline]
- else: # post
- timeline = [p for p in site.timeline if p.use_in_feeds]
-
- # TODO: replaces all, uncomment in v8
- # if post_type == 'page' or post_type == 'pages':
- # timeline = [p for p in site.timeline if not p.use_in_feeds]
- # elif post_type == 'all':
- # timeline = [p for p in site.timeline]
- # else: # post
- # timeline = [p for p in site.timeline if p.use_in_feeds]
-
- if categories:
- timeline = [p for p in timeline if p.meta('category', lang=lang).lower() in categories]
-
- if sections:
- timeline = [p for p in timeline if p.section_name(lang).lower() in sections]
-
- for post in timeline:
- if tags:
- cont = True
- tags_lower = [t.lower() for t in post.tags]
- for tag in tags:
- if tag in tags_lower:
- cont = False
-
- if cont:
- continue
-
- filtered_timeline.append(post)
-
- if sort:
- filtered_timeline = natsort.natsorted(filtered_timeline, key=lambda post: post.meta[lang][sort], alg=natsort.ns.F | natsort.ns.IC)
-
- if date:
- filtered_timeline = [p for p in filtered_timeline if date_in_range(date, p.date)]
-
- for post in filtered_timeline[start:stop:step]:
- if slugs:
- cont = True
- for slug in slugs:
- if slug == post.meta('slug'):
- cont = False
-
- if cont:
- continue
-
- bp = post.translated_base_path(lang)
- if os.path.exists(bp) and state:
- state.document.settings.record_dependencies.add(bp)
- elif os.path.exists(bp) and self_post:
- self_post.register_depfile(bp, lang=lang)
-
- posts += [post]
-
- if not posts:
- return '', []
-
- template_deps = site.template_system.template_deps(template)
- if state:
- # Register template as a dependency (Issue #2391)
- for d in template_deps:
- state.document.settings.record_dependencies.add(d)
- elif self_post:
- for d in template_deps:
- self_post.register_depfile(d, lang=lang)
-
- template_data = {
- 'lang': lang,
- 'posts': posts,
- # Need to provide str, not TranslatableSetting (Issue #2104)
- 'date_format': site.GLOBAL_CONTEXT.get('date_format')[lang],
- 'post_list_id': post_list_id,
- 'messages': site.MESSAGES,
- }
- output = site.template_system.render_template(
- template, None, template_data)
- return output, template_deps
-
-# Request file name from shortcode (Issue #2412)
-_do_post_list.nikola_shortcode_pass_filename = True
diff --git a/nikola/plugins/compile/rest/slides.plugin b/nikola/plugins/compile/rest/slides.plugin
deleted file mode 100644
index 389da39..0000000
--- a/nikola/plugins/compile/rest/slides.plugin
+++ /dev/null
@@ -1,14 +0,0 @@
-[Core]
-name = rest_slides
-module = slides
-
-[Nikola]
-compiler = rest
-plugincategory = CompilerExtension
-
-[Documentation]
-author = Roberto Alsina
-version = 0.1
-website = https://getnikola.com/
-description = Slides directive
-
diff --git a/nikola/plugins/compile/rest/slides.py b/nikola/plugins/compile/rest/slides.py
deleted file mode 100644
index 7c5b34b..0000000
--- a/nikola/plugins/compile/rest/slides.py
+++ /dev/null
@@ -1,78 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Copyright © 2012-2016 Roberto Alsina and others.
-
-# Permission is hereby granted, free of charge, to any
-# person obtaining a copy of this software and associated
-# documentation files (the "Software"), to deal in the
-# Software without restriction, including without limitation
-# the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the
-# Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice
-# shall be included in all copies or substantial portions of
-# the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
-# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
-# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
-# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
-# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
-# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-"""Slides directive for reStructuredText."""
-
-from __future__ import unicode_literals
-
-import uuid
-
-from docutils import nodes
-from docutils.parsers.rst import Directive, directives
-
-from nikola.plugin_categories import RestExtension
-
-
-class Plugin(RestExtension):
- """Plugin for reST slides directive."""
-
- name = "rest_slides"
-
- def set_site(self, site):
- """Set Nikola site."""
- self.site = site
- directives.register_directive('slides', Slides)
- Slides.site = site
- return super(Plugin, self).set_site(site)
-
-
-class Slides(Directive):
- """reST extension for inserting slideshows."""
-
- has_content = True
-
- def run(self):
- """Run the slides directive."""
- if len(self.content) == 0: # pragma: no cover
- return
-
- if self.site.invariant: # for testing purposes
- carousel_id = 'slides_' + 'fixedvaluethatisnotauuid'
- else:
- carousel_id = 'slides_' + uuid.uuid4().hex
-
- output = self.site.template_system.render_template(
- 'slides.tmpl',
- None,
- {
- 'slides_content': self.content,
- 'carousel_id': carousel_id,
- }
- )
- return [nodes.raw('', output, format='html')]
-
-
-directives.register_directive('slides', Slides)
diff --git a/nikola/plugins/compile/rest/soundcloud.plugin b/nikola/plugins/compile/rest/soundcloud.plugin
index 4e36ea4..f85a964 100644
--- a/nikola/plugins/compile/rest/soundcloud.plugin
+++ b/nikola/plugins/compile/rest/soundcloud.plugin
@@ -4,7 +4,7 @@ module = soundcloud
[Nikola]
compiler = rest
-plugincategory = CompilerExtension
+PluginCategory = CompilerExtension
[Documentation]
author = Roberto Alsina
diff --git a/nikola/plugins/compile/rest/soundcloud.py b/nikola/plugins/compile/rest/soundcloud.py
index 9fabe70..5dbcfc3 100644
--- a/nikola/plugins/compile/rest/soundcloud.py
+++ b/nikola/plugins/compile/rest/soundcloud.py
@@ -1,5 +1,29 @@
# -*- coding: utf-8 -*-
+# 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
+# documentation files (the "Software"), to deal in the
+# Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice
+# shall be included in all copies or substantial portions of
+# the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
"""SoundCloud directive for reStructuredText."""
from docutils import nodes
@@ -19,7 +43,7 @@ class Plugin(RestExtension):
self.site = site
directives.register_directive('soundcloud', SoundCloud)
directives.register_directive('soundcloud_playlist', SoundCloudPlaylist)
- return super(Plugin, self).set_site(site)
+ return super().set_site(site)
CODE = """\
diff --git a/nikola/plugins/compile/rest/thumbnail.plugin b/nikola/plugins/compile/rest/thumbnail.plugin
index 3324c31..e7b649d 100644
--- a/nikola/plugins/compile/rest/thumbnail.plugin
+++ b/nikola/plugins/compile/rest/thumbnail.plugin
@@ -4,7 +4,7 @@ module = thumbnail
[Nikola]
compiler = rest
-plugincategory = CompilerExtension
+PluginCategory = CompilerExtension
[Documentation]
author = Pelle Nilsson
diff --git a/nikola/plugins/compile/rest/thumbnail.py b/nikola/plugins/compile/rest/thumbnail.py
index 37e0973..06ca9e4 100644
--- a/nikola/plugins/compile/rest/thumbnail.py
+++ b/nikola/plugins/compile/rest/thumbnail.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
-# Copyright © 2014-2016 Pelle Nilsson and others.
+# Copyright © 2014-2020 Pelle Nilsson and others.
# Permission is hereby granted, free of charge, to any
# person obtaining a copy of this software and associated
@@ -43,7 +43,7 @@ class Plugin(RestExtension):
"""Set Nikola site."""
self.site = site
directives.register_directive('thumbnail', Thumbnail)
- return super(Plugin, self).set_site(site)
+ return super().set_site(site)
class Thumbnail(Figure):
@@ -69,7 +69,7 @@ class Thumbnail(Figure):
"""Run the thumbnail directive."""
uri = directives.uri(self.arguments[0])
if uri.endswith('.svg'):
- # the ? at the end makes docutil output an <img> instead of an object for the svg, which colorbox requires
+ # the ? at the end makes docutil output an <img> instead of an object for the svg, which lightboxes may require
self.arguments[0] = '.thumbnail'.join(os.path.splitext(uri)) + '?'
else:
self.arguments[0] = '.thumbnail'.join(os.path.splitext(uri))
diff --git a/nikola/plugins/compile/rest/vimeo.plugin b/nikola/plugins/compile/rest/vimeo.plugin
index 688f981..89b171b 100644
--- a/nikola/plugins/compile/rest/vimeo.plugin
+++ b/nikola/plugins/compile/rest/vimeo.plugin
@@ -4,7 +4,7 @@ module = vimeo
[Nikola]
compiler = rest
-plugincategory = CompilerExtension
+PluginCategory = CompilerExtension
[Documentation]
description = Vimeo directive
diff --git a/nikola/plugins/compile/rest/vimeo.py b/nikola/plugins/compile/rest/vimeo.py
index f1ac6c3..7047b03 100644
--- a/nikola/plugins/compile/rest/vimeo.py
+++ b/nikola/plugins/compile/rest/vimeo.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
@@ -26,15 +26,14 @@
"""Vimeo directive for reStructuredText."""
-from docutils import nodes
-from docutils.parsers.rst import Directive, directives
-from nikola.plugins.compile.rest import _align_choice, _align_options_base
-
-import requests
import json
+import requests
+from docutils import nodes
+from docutils.parsers.rst import Directive, directives
from nikola.plugin_categories import RestExtension
+from nikola.plugins.compile.rest import _align_choice, _align_options_base
class Plugin(RestExtension):
@@ -46,7 +45,7 @@ class Plugin(RestExtension):
"""Set Nikola site."""
self.site = site
directives.register_directive('vimeo', Vimeo)
- return super(Plugin, self).set_site(site)
+ return super().set_site(site)
CODE = """<div class="vimeo-video{align}">
diff --git a/nikola/plugins/compile/rest/youtube.plugin b/nikola/plugins/compile/rest/youtube.plugin
index 5fbd67b..d83d0f8 100644
--- a/nikola/plugins/compile/rest/youtube.plugin
+++ b/nikola/plugins/compile/rest/youtube.plugin
@@ -4,7 +4,7 @@ module = youtube
[Nikola]
compiler = rest
-plugincategory = CompilerExtension
+PluginCategory = CompilerExtension
[Documentation]
version = 0.1
diff --git a/nikola/plugins/compile/rest/youtube.py b/nikola/plugins/compile/rest/youtube.py
index b3dde62..d52ec64 100644
--- a/nikola/plugins/compile/rest/youtube.py
+++ b/nikola/plugins/compile/rest/youtube.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
@@ -28,8 +28,8 @@
from docutils import nodes
from docutils.parsers.rst import Directive, directives
-from nikola.plugins.compile.rest import _align_choice, _align_options_base
+from nikola.plugins.compile.rest import _align_choice, _align_options_base
from nikola.plugin_categories import RestExtension
@@ -42,13 +42,14 @@ class Plugin(RestExtension):
"""Set Nikola site."""
self.site = site
directives.register_directive('youtube', Youtube)
- return super(Plugin, self).set_site(site)
+ return super().set_site(site)
CODE = """\
<div class="youtube-video{align}">
<iframe width="{width}" height="{height}"
-src="https://www.youtube.com/embed/{yid}?rel=0&amp;hd=1&amp;wmode=transparent"
+src="https://www.youtube-nocookie.com/embed/{yid}?rel=0&wmode=transparent"
+frameborder="0" allow="encrypted-media" allowfullscreen
></iframe>
</div>"""
@@ -66,8 +67,8 @@ class Youtube(Directive):
has_content = True
required_arguments = 1
option_spec = {
- "width": directives.positive_int,
- "height": directives.positive_int,
+ "width": directives.unchanged,
+ "height": directives.unchanged,
"align": _align_choice
}
@@ -76,10 +77,10 @@ class Youtube(Directive):
self.check_content()
options = {
'yid': self.arguments[0],
- 'width': 425,
- 'height': 344,
+ 'width': 560,
+ 'height': 315,
}
- options.update(self.options)
+ options.update({k: v for k, v in self.options.items() if v})
if self.options.get('align') in _align_options_base:
options['align'] = ' align-' + self.options['align']
else: