block decorations and wrap

This commit is contained in:
randogoth 2025-10-20 12:09:25 +03:00
parent de6e99c24a
commit c57cfc4747
4 changed files with 152 additions and 4 deletions

View file

@ -5,6 +5,9 @@ h3_font: caps
margin_left: 12 margin_left: 12
margin_right: 12 margin_right: 12
paragraph_spacing: 2 paragraph_spacing: 2
code_block_line_numbers: false
code_block_wrap: 4
blockquote_bars: false
hyphenate: true hyphenate: true
--- ---

View file

@ -79,6 +79,22 @@ def parse_frontmatter(lines: List[str]) -> Tuple[FrontMatter, List[str]]:
paragraph_spacing_value = frontmatter.get("lines_between_paragraphs") paragraph_spacing_value = frontmatter.get("lines_between_paragraphs")
if paragraph_spacing_value is None: if paragraph_spacing_value is None:
paragraph_spacing_value = frontmatter.get("paragraph_lines") paragraph_spacing_value = frontmatter.get("paragraph_lines")
default_wrap_indent = 2
wrap_code_blocks = _parse_bool(frontmatter.get("wrap_code_blocks"), False)
code_block_wrap_indent = default_wrap_indent if wrap_code_blocks else 0
code_block_wrap_value = frontmatter.get("code_block_wrap")
if code_block_wrap_value is not None:
normalized_wrap = code_block_wrap_value.strip()
if normalized_wrap:
if re.fullmatch(r"-?\d+", normalized_wrap):
wrap_code_blocks = True
code_block_wrap_indent = max(0, _parse_int(normalized_wrap, default_wrap_indent))
else:
wrap_flag = _parse_bool(normalized_wrap, wrap_code_blocks)
wrap_code_blocks = wrap_flag
code_block_wrap_indent = default_wrap_indent if wrap_flag else 0
code_block_line_numbers = _parse_bool(frontmatter.get("code_block_line_numbers"), True)
blockquote_bars = _parse_bool(frontmatter.get("blockquote_bars"), True)
fm = FrontMatter( fm = FrontMatter(
h1_font=frontmatter.get("h1_font", "standard").strip() or "standard", h1_font=frontmatter.get("h1_font", "standard").strip() or "standard",
h2_font=frontmatter.get("h2_font", "standard").strip() or "standard", h2_font=frontmatter.get("h2_font", "standard").strip() or "standard",
@ -90,6 +106,10 @@ def parse_frontmatter(lines: List[str]) -> Tuple[FrontMatter, List[str]]:
hyphen_lang=(frontmatter.get("hyphen_lang") or "en_US").strip() or "en_US", hyphen_lang=(frontmatter.get("hyphen_lang") or "en_US").strip() or "en_US",
figlet_fallback=_parse_bool(frontmatter.get("figlet_fallback"), False), figlet_fallback=_parse_bool(frontmatter.get("figlet_fallback"), False),
header_spacing=max(0, _parse_int(frontmatter.get("header_spacing"), 2)), header_spacing=max(0, _parse_int(frontmatter.get("header_spacing"), 2)),
wrap_code_blocks=wrap_code_blocks,
code_block_wrap_indent=code_block_wrap_indent,
code_block_line_numbers=code_block_line_numbers,
blockquote_bars=blockquote_bars,
) )
return fm, remaining return fm, remaining
@ -128,4 +148,3 @@ def main(argv: Optional[List[str]] = None) -> int:
if __name__ == "__main__": if __name__ == "__main__":
raise SystemExit(main()) raise SystemExit(main())

View file

@ -41,6 +41,10 @@ class FrontMatter:
hyphen_lang: str = "en_US" hyphen_lang: str = "en_US"
figlet_fallback: bool = False figlet_fallback: bool = False
header_spacing: int = 2 header_spacing: int = 2
wrap_code_blocks: bool = False
code_block_wrap_indent: int = 2
code_block_line_numbers: bool = True
blockquote_bars: bool = True
@dataclass @dataclass

View file

@ -73,6 +73,12 @@ class TextRenderer:
self.hyphen_lang = frontmatter.hyphen_lang or "en_US" self.hyphen_lang = frontmatter.hyphen_lang or "en_US"
self.figlet_fallback = frontmatter.figlet_fallback self.figlet_fallback = frontmatter.figlet_fallback
self.header_spacing = max(0, frontmatter.header_spacing) self.header_spacing = max(0, frontmatter.header_spacing)
self.wrap_code_blocks = frontmatter.wrap_code_blocks
self.code_block_line_numbers = frontmatter.code_block_line_numbers
self.blockquote_bars = frontmatter.blockquote_bars
self.wrap_code_blocks_indent = (
max(0, frontmatter.code_block_wrap_indent) if self.wrap_code_blocks else 0
)
self._base_style = BlockStyle( self._base_style = BlockStyle(
align="left", align="left",
margin_left=max(0, frontmatter.margin_left), margin_left=max(0, frontmatter.margin_left),
@ -157,7 +163,7 @@ class TextRenderer:
def _render_blockquote(self, payload: BlockQuotePayload, style: BlockStyle) -> None: def _render_blockquote(self, payload: BlockQuotePayload, style: BlockStyle) -> None:
processed = self._process_inline(payload.text) processed = self._process_inline(payload.text)
indent_unit = " | " indent_unit = " | " if self.blockquote_bars else " "
indent = indent_unit * max(1, payload.depth) indent = indent_unit * max(1, payload.depth)
wrapped = self._wrap_text( wrapped = self._wrap_text(
processed, processed,
@ -321,14 +327,131 @@ class TextRenderer:
def _format_code_block(self, lines: List[str], style: BlockStyle) -> List[str]: def _format_code_block(self, lines: List[str], style: BlockStyle) -> List[str]:
if not lines: if not lines:
return [] return []
width = max(2, len(str(len(lines)))) if self.wrap_code_blocks:
if self.code_block_line_numbers:
return self._format_wrapped_code_block_numbered(lines, style)
return self._format_wrapped_code_block_plain(lines, style)
if self.code_block_line_numbers:
return self._format_code_block_numbered(lines, style)
return self._format_code_block_plain(lines, style)
def _format_code_block_numbered(self, lines: List[str], style: BlockStyle) -> List[str]:
margin_indent = " " * max(0, style.margin_left) margin_indent = " " * max(0, style.margin_left)
width = max(2, len(str(len(lines))))
formatted: List[str] = [] formatted: List[str] = []
for idx, line in enumerate(lines, start=1): for idx, line in enumerate(lines, start=1):
formatted.append(f"{margin_indent}{idx:0{width}d} | {line}") formatted.append(f"{margin_indent}{idx:0{width}d} | {line}")
formatted.append(margin_indent if margin_indent else "") formatted.append(margin_indent if margin_indent else "")
return formatted return formatted
def _format_code_block_plain(self, lines: List[str], style: BlockStyle) -> List[str]:
margin_indent = " " * max(0, style.margin_left)
base_prefix = margin_indent + " "
formatted: List[str] = []
for line in lines:
if line:
formatted.append(base_prefix + line)
else:
formatted.append(base_prefix)
formatted.append(margin_indent if margin_indent else "")
return formatted
def _format_wrapped_code_block_numbered(self, lines: List[str], style: BlockStyle) -> List[str]:
margin_left = min(max(style.margin_left, 0), self.width - 1)
margin_indent = " " * margin_left
effective_width = self._effective_width(style)
number_width = max(2, len(str(len(lines))))
prefix_template = f"{margin_indent}{{num:0{number_width}d}} | "
continuation_prefix = f"{margin_indent}{' ' * number_width} | "
prefix_len_without_margin = len(prefix_template.format(num=0)) - len(margin_indent)
content_width = max(1, effective_width - prefix_len_without_margin)
formatted: List[str] = []
for idx, line in enumerate(lines, start=1):
if not line:
formatted.append(prefix_template.format(num=idx))
continue
wrapped_segments = self._wrap_code_line_segments(line, content_width)
if not wrapped_segments:
formatted.append(prefix_template.format(num=idx))
continue
first_segment, *rest_segments = wrapped_segments
formatted.append(prefix_template.format(num=idx) + first_segment)
for segment in rest_segments:
formatted.append(continuation_prefix + segment)
formatted.append(margin_indent if margin_indent else "")
return formatted
def _format_wrapped_code_block_plain(self, lines: List[str], style: BlockStyle) -> List[str]:
margin_left = min(max(style.margin_left, 0), self.width - 1)
margin_indent = " " * margin_left
effective_width = self._effective_width(style)
content_prefix = margin_indent + " "
content_width = max(1, effective_width - 3)
formatted: List[str] = []
for line in lines:
if not line:
formatted.append(content_prefix)
continue
wrapped_segments = self._wrap_code_line_segments(line, content_width)
if not wrapped_segments:
formatted.append(content_prefix)
continue
first_segment, *rest_segments = wrapped_segments
formatted.append(content_prefix + first_segment)
for segment in rest_segments:
formatted.append(content_prefix + segment)
formatted.append(margin_indent if margin_indent else "")
return formatted
def _wrap_code_line_segments(self, line: str, content_width: int) -> List[str]:
if not line:
return []
segments: List[str] = []
remaining = line
current_indent = 0
max_indent_allowed = max(0, content_width - 1)
while remaining:
available = max(1, content_width - current_indent)
segment, remaining = self._split_code_segment(remaining, available)
segments.append(" " * current_indent + segment)
leading = self._leading_space_count(segment)
total_indent = current_indent + leading
desired_indent = total_indent + self.wrap_code_blocks_indent
current_indent = min(max_indent_allowed, desired_indent)
return segments
def _split_code_segment(self, text: str, max_width: int) -> tuple[str, str]:
if max_width <= 0:
max_width = 1
if len(text) <= max_width:
return text, ""
slice_text = text[:max_width]
break_pos = -1
for idx, char in enumerate(slice_text):
if char in {" ", "\t"}:
break_pos = idx
if break_pos <= 0:
segment = slice_text
remainder = text[len(segment) :]
else:
segment = text[:break_pos]
remainder = text[break_pos:]
if not segment:
segment = slice_text
remainder = text[len(segment) :]
return segment, remainder
def _leading_space_count(self, text: str) -> int:
count = 0
for char in text:
if char == " ":
count += 1
elif char == "\t":
count += 1
else:
break
return count
def _process_inline(self, text: str) -> str: def _process_inline(self, text: str) -> str:
code_segments: List[str] = [] code_segments: List[str] = []
@ -759,4 +882,3 @@ class TextRenderer:
else: else:
break break
self.output.extend([""] * (self.header_spacing - existing)) self.output.extend([""] * (self.header_spacing - existing))