From b1b23ff7edd8756f4456a06609b036dc7c236293 Mon Sep 17 00:00:00 2001 From: PhuNH <1079742+PhuNH@users.noreply.github.com> Date: Sun, 27 Aug 2023 14:56:21 +0200 Subject: [PATCH] Support attribute after blocks and titles - For titles/headings, use the attributes stored in the opening tokens - For other blocks, use the ones stored in the closing tokens --- hugo_gettext/config.py | 3 ++ hugo_gettext/generation/renderer_md_l10n.py | 60 ++++++++++++++++++--- hugo_gettext/utils.py | 3 ++ pyproject.toml | 2 +- tests/resources/attributes.md | 49 +++++++++++++++++ tests/test_renderer_md_l10n.py | 12 ++++- 6 files changed, 118 insertions(+), 11 deletions(-) create mode 100644 tests/resources/attributes.md diff --git a/hugo_gettext/config.py b/hugo_gettext/config.py index e36866e..680d220 100644 --- a/hugo_gettext/config.py +++ b/hugo_gettext/config.py @@ -154,5 +154,8 @@ def __init__(self, customs_path: str = ''): extensions_config = goldmark_config.get('extensions', {}) self.parse_definition_list = extensions_config.get('definitionList', True) self.parse_table = extensions_config.get('table', True) + attribute_config = goldmark_config.get('parser', {}).get('attribute', {}) + self.parse_attribute_block = attribute_config.get('block', False) + self.parse_attribute_title = attribute_config.get('title', True) self.hugo_config = hugo_config diff --git a/hugo_gettext/generation/renderer_md_l10n.py b/hugo_gettext/generation/renderer_md_l10n.py index b0b2e45..5522611 100644 --- a/hugo_gettext/generation/renderer_md_l10n.py +++ b/hugo_gettext/generation/renderer_md_l10n.py @@ -7,7 +7,7 @@ import textwrap from collections.abc import MutableMapping from dataclasses import dataclass -from typing import Sequence, Tuple, List +from typing import Sequence, Tuple, List, Dict import pygments.token from markdown_it.renderer import RendererProtocol @@ -31,6 +31,7 @@ def __init__(self, env: MutableMapping): self.indent_1st_line_len = 0 self.indents = [] self.setext_heading = '' + self.heading_attrs = [] self.table_sep = '' self.in_table = False self.l10n_func: L10NFunc = env['l10n_func'] @@ -172,6 +173,25 @@ def _shortcode(token: Token, sc_params_to_localize: List, md_ctx: _MdCtx, conten content_result.localized += f"{md_ctx.get_line_indent()}{opening}{token.meta['name']}{args} {closing}" +def _attribute_block(attrs: Dict): + s = '' + for k, v in attrs.items(): + if k == 'class': + for c in v.split(' '): + s += f'.{c} ' + elif k == 'id': + s += f'#{v} ' + else: + s += f'{k}="{v}" ' + return '{' + s + '}' + + +def _attributes(token: Token, md_ctx: _MdCtx, content_result: L10NResult): + if token.attrs: + attrs_s = _attribute_block(token.attrs) + content_result.localized += f'{md_ctx.line_indent}{attrs_s}\n' + + class RendererMarkdownL10N(RendererProtocol): __output__ = "md" @@ -182,6 +202,15 @@ def __init__(self, _): if not (k.startswith("render") or k.startswith("_")) } + # TODO: elements that aren't pure Markdown but Hugo-related: + # hugo_lang_code + # hg_config.excluded_keys + # hg_config.shortcodes + # attributes after blocks and titles: + # 'blockquote_close', 'hr', 'bullet_list_close', 'ordered_list_close', + # 'paragraph_close', 'table_close', 'dl_close' + # 'heading_open', 'heading_close' + def render( self, tokens: Sequence[Token], options: OptionsDict, env: MutableMapping ) -> Tuple[L10NResult, L10NResult]: @@ -252,12 +281,10 @@ def blockquote_open(cls, tokens: Sequence[Token], idx: int, md_ctx: _MdCtx, _con md_ctx.line_indent = f'{md_ctx.get_line_indent()}{token.markup} ' @classmethod - def blockquote_close(cls, - _tokens: Sequence[Token], - _idx: int, - md_ctx: _MdCtx, - content_result: L10NResult): + def blockquote_close(cls, tokens: Sequence[Token], idx: int, md_ctx: _MdCtx, content_result: L10NResult): + token = tokens[idx] md_ctx.line_indent = md_ctx.line_indent[:-2] + _attributes(token, md_ctx, content_result) content_result.localized += f'{md_ctx.line_indent}\n' # heading @@ -271,9 +298,15 @@ def heading_open(cls, tokens: Sequence[Token], idx: int, md_ctx: _MdCtx, _conten md_ctx.indents.append(0) else: md_ctx.setext_heading = token.markup + if token.attrs: + md_ctx.heading_attrs.append(token.attrs) @classmethod def heading_close(cls, _tokens: Sequence[Token], _idx: int, md_ctx: _MdCtx, content_result: L10NResult): + if md_ctx.heading_attrs: + attrs_s = _attribute_block(md_ctx.heading_attrs.pop()) + content_result.localized += attrs_s + if md_ctx.setext_heading: content_result.localized += f'\n{md_ctx.get_line_indent()}{md_ctx.setext_heading}' md_ctx.setext_heading = '' @@ -288,6 +321,7 @@ def hr(cls, tokens: Sequence[Token], idx: int, md_ctx: _MdCtx, content_result: L token = tokens[idx] # always use '_' here to differentiate this from setext headings and bullet list items content_result.localized += f'{md_ctx.get_line_indent()}{len(token.markup) * "_"}\n' + _attributes(token, md_ctx, content_result) # list # TODO: loose lists? @@ -316,6 +350,8 @@ def bullet_list_close(cls, idx: int, md_ctx: _MdCtx, content_result: L10NResult): + token = tokens[idx] + _attributes(token, md_ctx, content_result) # add a blank line when next token is not a closing one if idx < len(tokens) - 1 and tokens[idx + 1].nesting != -1: content_result.localized += f'{md_ctx.line_indent}\n' @@ -326,6 +362,8 @@ def ordered_list_close(cls, idx: int, md_ctx: _MdCtx, content_result: L10NResult): + token = tokens[idx] + _attributes(token, md_ctx, content_result) # add a blank line when next token is not a closing one if idx < len(tokens) - 1 and tokens[idx + 1].nesting != -1: content_result.localized += f'{md_ctx.line_indent}\n' @@ -333,7 +371,9 @@ def ordered_list_close(cls, # paragraph @classmethod def paragraph_close(cls, tokens: Sequence[Token], idx: int, md_ctx: _MdCtx, content_result: L10NResult): + token = tokens[idx] content_result.localized += '\n' + _attributes(token, md_ctx, content_result) if idx < len(tokens) - 1: next_token = tokens[idx + 1] # add a blank line when next token is a setext heading_open, an indented code block, a paragraph open, @@ -408,7 +448,9 @@ def tr_close(cls, _tokens: Sequence[Token], _idx: int, _md_ctx: _MdCtx, content_ content_result.localized += '\n' @classmethod - def table_close(cls, _tokens: Sequence[Token], _idx: int, md_ctx: _MdCtx, content_result: L10NResult): + def table_close(cls, tokens: Sequence[Token], idx: int, md_ctx: _MdCtx, content_result: L10NResult): + token = tokens[idx] + _attributes(token, md_ctx, content_result) content_result.localized += f'{md_ctx.line_indent}\n' md_ctx.in_table = False @@ -421,9 +463,11 @@ def dd_open(cls, _tokens: Sequence[Token], _idx: int, md_ctx: _MdCtx, _content_r md_ctx.indents.append(2) @classmethod - def dd_close(cls, _tokens: Sequence[Token], _idx: int, md_ctx: _MdCtx, content_result: L10NResult): + def dd_close(cls, tokens: Sequence[Token], idx: int, md_ctx: _MdCtx, content_result: L10NResult): latest_len = md_ctx.indents.pop() md_ctx.line_indent = md_ctx.line_indent[:-latest_len] + if idx + 1 < len(tokens) and (token := tokens[idx+1]).type == 'dl_close': + _attributes(token, md_ctx, content_result) content_result.localized += f'{md_ctx.line_indent}\n' @classmethod diff --git a/hugo_gettext/utils.py b/hugo_gettext/utils.py index 14cfdc8..dc23611 100644 --- a/hugo_gettext/utils.py +++ b/hugo_gettext/utils.py @@ -9,6 +9,7 @@ from markdown_it import MarkdownIt from markdown_it.renderer import RendererProtocol +from mdit_py_hugo.attribute import attribute_plugin from mdit_py_hugo.shortcode import shortcode_plugin from mdit_py_plugins.deflist import deflist_plugin from mdit_py_plugins.front_matter import front_matter_plugin @@ -28,6 +29,8 @@ def initialize(customs_path: str, renderer_cls: Type[RendererProtocol]) -> Tuple mdi = mdi.enable('table') if hg_config.parse_definition_list: mdi = mdi.use(deflist_plugin) + if hg_config.parse_attribute_title or hg_config.parse_attribute_block: + mdi = mdi.use(attribute_plugin, block=hg_config.parse_attribute_block, title=hg_config.parse_attribute_title) return hg_config, mdi diff --git a/pyproject.toml b/pyproject.toml index 0c7dd5d..d98c64e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ [tool.poetry] name = "hugo-gettext" -version = "0.1.3" +version = "0.2.0" description = "I18n with gettext for Hugo" authors = ["Phu Hung Nguyen "] license = "GPL-3.0-or-later" diff --git a/tests/resources/attributes.md b/tests/resources/attributes.md new file mode 100644 index 0000000..fa61408 --- /dev/null +++ b/tests/resources/attributes.md @@ -0,0 +1,49 @@ +--- +authors: +- SPDX-FileCopyrightText: 2023 Phu Hung Nguyen +SPDX-License-Identifier: CC0-1.0 +--- +# atx heading {#hg-atx} + +setext heading {.hg-setext .hg-setext2 attr="setext heading attribute"} +== +{.hg-setext-block} + +--- +{.hg-hr} + +paragraph here +{.hg-paragraph} + +and more + +> blockquote +> nested coming +> +> > second level +> {.hg-second-bg} +{.hg-first-bq} + +* Fruit + * Apple + * Orange + * Banana + {.hg-fruits} +* Dairy + * Milk + * Cheese + {.hg-dairies} +{.hg-list} + +| head | head2 | +|------|-------| +| body | body2 | +{.hg-table} + +term number 1 +: details number 1 + +term number 2 +: details number 2 +onto the next line, without indentation +{.hg-deflist} diff --git a/tests/test_renderer_md_l10n.py b/tests/test_renderer_md_l10n.py index f0734ea..c9abed3 100644 --- a/tests/test_renderer_md_l10n.py +++ b/tests/test_renderer_md_l10n.py @@ -5,6 +5,8 @@ import importlib.resources as pkg_resources from markdown_it import MarkdownIt +from mdit_py_hugo.attribute import attribute_plugin +from mdit_py_hugo.shortcode import shortcode_plugin from mdit_py_plugins.deflist import deflist_plugin from mdit_py_plugins.front_matter import front_matter_plugin @@ -12,8 +14,8 @@ class RendererLocalizedMdTestCase(unittest.TestCase): - mdi = MarkdownIt(renderer_cls=RendererMarkdownL10N).use(front_matter_plugin) \ - .enable('table').use(deflist_plugin) + mdi = MarkdownIt(renderer_cls=RendererMarkdownL10N).use(front_matter_plugin).use(shortcode_plugin)\ + .enable('table').use(deflist_plugin).use(attribute_plugin) def _prep_test(self, f_obj): env = { @@ -46,6 +48,12 @@ def test_others(self): tokens, content_result, localized_tokens = self._prep_test(f_obj) self.assertEqual([token.type for token in tokens], [token.type for token in localized_tokens]) + def test_attributes(self): + with pkg_resources.open_text('tests.resources', 'attributes.md') as f_obj: + tokens, content_result, localized_tokens = self._prep_test(f_obj) + self.assertEqual([token.attrs for token in tokens if token.type != 'heading_close'], + [token.attrs for token in localized_tokens if token.type != 'heading_close']) + if __name__ == '__main__': unittest.main()