block decorations and wrap
This commit is contained in:
parent
de6e99c24a
commit
c57cfc4747
4 changed files with 152 additions and 4 deletions
|
|
@ -5,6 +5,9 @@ h3_font: caps
|
|||
margin_left: 12
|
||||
margin_right: 12
|
||||
paragraph_spacing: 2
|
||||
code_block_line_numbers: false
|
||||
code_block_wrap: 4
|
||||
blockquote_bars: false
|
||||
hyphenate: true
|
||||
---
|
||||
|
||||
|
|
|
|||
21
md2txt.py
21
md2txt.py
|
|
@ -79,6 +79,22 @@ def parse_frontmatter(lines: List[str]) -> Tuple[FrontMatter, List[str]]:
|
|||
paragraph_spacing_value = frontmatter.get("lines_between_paragraphs")
|
||||
if paragraph_spacing_value is None:
|
||||
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(
|
||||
h1_font=frontmatter.get("h1_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",
|
||||
figlet_fallback=_parse_bool(frontmatter.get("figlet_fallback"), False),
|
||||
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
|
||||
|
||||
|
|
@ -128,4 +148,3 @@ def main(argv: Optional[List[str]] = None) -> int:
|
|||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
||||
|
|
|
|||
|
|
@ -41,6 +41,10 @@ class FrontMatter:
|
|||
hyphen_lang: str = "en_US"
|
||||
figlet_fallback: bool = False
|
||||
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
|
||||
|
|
|
|||
128
text_renderer.py
128
text_renderer.py
|
|
@ -73,6 +73,12 @@ class TextRenderer:
|
|||
self.hyphen_lang = frontmatter.hyphen_lang or "en_US"
|
||||
self.figlet_fallback = frontmatter.figlet_fallback
|
||||
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(
|
||||
align="left",
|
||||
margin_left=max(0, frontmatter.margin_left),
|
||||
|
|
@ -157,7 +163,7 @@ class TextRenderer:
|
|||
|
||||
def _render_blockquote(self, payload: BlockQuotePayload, style: BlockStyle) -> None:
|
||||
processed = self._process_inline(payload.text)
|
||||
indent_unit = " | "
|
||||
indent_unit = " | " if self.blockquote_bars else " "
|
||||
indent = indent_unit * max(1, payload.depth)
|
||||
wrapped = self._wrap_text(
|
||||
processed,
|
||||
|
|
@ -321,14 +327,131 @@ class TextRenderer:
|
|||
def _format_code_block(self, lines: List[str], style: BlockStyle) -> List[str]:
|
||||
if not lines:
|
||||
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)
|
||||
width = max(2, len(str(len(lines))))
|
||||
formatted: List[str] = []
|
||||
for idx, line in enumerate(lines, start=1):
|
||||
formatted.append(f"{margin_indent}{idx:0{width}d} | {line}")
|
||||
formatted.append(margin_indent if margin_indent else "")
|
||||
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:
|
||||
code_segments: List[str] = []
|
||||
|
||||
|
|
@ -759,4 +882,3 @@ class TextRenderer:
|
|||
else:
|
||||
break
|
||||
self.output.extend([""] * (self.header_spacing - existing))
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue