diff options
Diffstat (limited to '')
| -rw-r--r-- | packages/blog-markdown-headings/lektor_blog_markdown_headings.py | 152 | ||||
| -rw-r--r-- | packages/blog-markdown-headings/setup.py | 13 |
2 files changed, 165 insertions, 0 deletions
diff --git a/packages/blog-markdown-headings/lektor_blog_markdown_headings.py b/packages/blog-markdown-headings/lektor_blog_markdown_headings.py new file mode 100644 index 0000000..5293676 --- /dev/null +++ b/packages/blog-markdown-headings/lektor_blog_markdown_headings.py @@ -0,0 +1,152 @@ +import uuid +from collections import namedtuple +from html.parser import HTMLParser + +import mistune +from lektor.pluginsystem import Plugin +from lektor.utils import slugify +from markupsafe import Markup, escape + +TocEntry = namedtuple("TocEntry", ["anchor", "title", "children"]) + + +def get_int_config(config, key, default, minimum=None, maximum=None): + value = config.get(key, str(default)) + + try: + value = int(value) + except (TypeError, ValueError): + value = default + + if minimum is not None: + value = max(value, minimum) + + if maximum is not None: + value = min(value, maximum) + + return value + + +class BlogMarkdownHeadingsPlugin(Plugin): + name = "Blog Markdown Headings" + description = ( + "Adds Markdown heading anchors, TOC metadata, and configurable heading offsets." + ) + + def on_markdown_config(self, config, **extra): + plugin_config = self.get_config() + + anchor_type = plugin_config.get("anchors.type", "slug") + + heading_offset = get_int_config( + plugin_config, + "headings.offset", + default=2, + minimum=-5, + maximum=5, + ) + + max_heading_level = get_int_config( + plugin_config, + "headings.max_level", + default=6, + minimum=1, + maximum=6, + ) + + min_heading_level = get_int_config( + plugin_config, + "headings.min_level", + default=1, + minimum=1, + maximum=6, + ) + + def adjusted_level(level): + return min( + max(level + heading_offset, min_heading_level), + max_heading_level, + ) + + config.renderer_mixins.append( + renderer_mixin_factory( + anchor_type=anchor_type, + adjusted_level=adjusted_level, + ) + ) + + def on_markdown_meta_init(self, meta, **extra): + meta["toc"] = [] + + def on_markdown_meta_postprocess(self, meta, **extra): + prev_level = None + toc = [] + stack = [toc] + + for level, anchor, title in meta["toc"]: + if prev_level is None: + prev_level = level + elif prev_level == level - 1: + stack.append(stack[-1][-1].children) + prev_level = level + elif prev_level > level: + while prev_level > level: + if len(stack) > 1: + stack.pop() + prev_level -= 1 + + stack[-1].append(TocEntry(anchor, title, [])) + + meta["toc"] = toc + + +def renderer_mixin_factory(anchor_type, adjusted_level): + def render_heading(text, original_level, meta, raw=None): + level = adjusted_level(original_level) + + if anchor_type == "random": + anchor = uuid.uuid4().hex[:6] + else: + if raw is None: + raw = escape(_strip_tags(text)) + anchor = slugify(raw) + + meta["toc"].append((level, anchor, Markup(text))) + + return '<h%d id="%s">%s</h%d>' % (level, anchor, text, level) + + class RendererMixinMistune0(object): + def header(self, text, level, raw): + return render_heading(text, level, self.meta, raw) + + class RendererMixinMistune2(object): + def heading(self, text, level): + meta = self.lektor.meta + return render_heading(text, level, meta) + + if mistune.__version__.startswith("0."): + return RendererMixinMistune0 + + if mistune.__version__.startswith("2."): + return RendererMixinMistune2 + + raise RuntimeError("Unsupported mistune version: %s" % mistune.__version__) + + +def _strip_tags(inline_html): + parser = _StripTagsParser() + parser.feed(inline_html) + return parser.close() + + +class _StripTagsParser(HTMLParser): + def reset(self): + super().reset() + self._data = [] + + def handle_data(self, data): + self._data.append(data) + + def close(self): + super().close() + return "".join(self._data) diff --git a/packages/blog-markdown-headings/setup.py b/packages/blog-markdown-headings/setup.py new file mode 100644 index 0000000..59f9895 --- /dev/null +++ b/packages/blog-markdown-headings/setup.py @@ -0,0 +1,13 @@ +from setuptools import setup + +setup( + name="lektor-blog-markdown-headings", + version="0.1", + py_modules=["lektor_blog_markdown_headings"], + entry_points={ + "lektor.plugins": [ + "blog-markdown-headings = lektor_blog_markdown_headings:BlogMarkdownHeadingsPlugin", + ] + }, + install_requires=[], +) |
