Skip to content

Commit

Permalink
Support attribute after blocks and titles
Browse files Browse the repository at this point in the history
- For titles/headings, use the attributes stored in the opening tokens
- For other blocks, use the ones stored in the closing tokens
  • Loading branch information
PhuNH committed Aug 27, 2023
1 parent e3ed895 commit b1b23ff
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 11 deletions.
3 changes: 3 additions & 0 deletions hugo_gettext/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
60 changes: 52 additions & 8 deletions hugo_gettext/generation/renderer_md_l10n.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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']
Expand Down Expand Up @@ -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"

Expand All @@ -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]:
Expand Down Expand Up @@ -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
Expand All @@ -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 = ''
Expand All @@ -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?
Expand Down Expand Up @@ -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'
Expand All @@ -326,14 +362,18 @@ 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'

# 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,
Expand Down Expand Up @@ -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

Expand All @@ -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
Expand Down
3 changes: 3 additions & 0 deletions hugo_gettext/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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


Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 <[email protected]>"]
license = "GPL-3.0-or-later"
Expand Down
49 changes: 49 additions & 0 deletions tests/resources/attributes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
---
authors:
- SPDX-FileCopyrightText: 2023 Phu Hung Nguyen <[email protected]>
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}
12 changes: 10 additions & 2 deletions tests/test_renderer_md_l10n.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@

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

from hugo_gettext.generation.renderer_md_l10n import RendererMarkdownL10N


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 = {
Expand Down Expand Up @@ -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()

0 comments on commit b1b23ff

Please sign in to comment.