termrender 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
termrender/style.py ADDED
@@ -0,0 +1,147 @@
1
+ """ANSI style primitives for terminal rendering."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ import unicodedata
7
+ from typing import TYPE_CHECKING
8
+
9
+ if TYPE_CHECKING:
10
+ from termrender.blocks import InlineSpan
11
+
12
+ # Compiled regex matching ANSI escape sequences
13
+ ANSI_RE = re.compile(r'\x1b\[[0-9;]*m')
14
+
15
+ # Style constants
16
+ RESET = '\x1b[0m'
17
+ BOLD = '\x1b[1m'
18
+ ITALIC = '\x1b[3m'
19
+ DIM = '\x1b[2m'
20
+
21
+ # Color name -> ANSI code mapping
22
+ COLOR_MAP: dict[str, str] = {
23
+ 'red': '\x1b[31m',
24
+ 'green': '\x1b[32m',
25
+ 'yellow': '\x1b[33m',
26
+ 'blue': '\x1b[34m',
27
+ 'magenta': '\x1b[35m',
28
+ 'cyan': '\x1b[36m',
29
+ 'white': '\x1b[37m',
30
+ 'gray': '\x1b[90m',
31
+ }
32
+
33
+
34
+ def resolve_color(name: str | None) -> str:
35
+ if name is None:
36
+ return ''
37
+ return COLOR_MAP.get(name, '')
38
+
39
+
40
+ def style(
41
+ text: str,
42
+ color: str | None = None,
43
+ bold: bool = False,
44
+ italic: bool = False,
45
+ dim: bool = False,
46
+ enabled: bool = True,
47
+ ) -> str:
48
+ if not enabled:
49
+ return text
50
+ prefix = resolve_color(color)
51
+ if bold:
52
+ prefix += BOLD
53
+ if italic:
54
+ prefix += ITALIC
55
+ if dim:
56
+ prefix += DIM
57
+ if not prefix:
58
+ return text
59
+ return prefix + text + RESET
60
+
61
+
62
+ def _char_width(c: str) -> int:
63
+ """Return display width of a single character (2 for wide/fullwidth, 1 otherwise)."""
64
+ return 2 if unicodedata.east_asian_width(c) in ('W', 'F') else 1
65
+
66
+
67
+ def visual_len(s: str) -> int:
68
+ stripped = ANSI_RE.sub('', s)
69
+ return sum(_char_width(c) for c in stripped)
70
+
71
+
72
+ def visual_ljust(s: str, width: int) -> str:
73
+ vl = visual_len(s)
74
+ if vl >= width:
75
+ return s
76
+ return s + ' ' * (width - vl)
77
+
78
+
79
+ def visual_center(s: str, width: int, fillchar: str = ' ') -> str:
80
+ vl = visual_len(s)
81
+ if vl >= width:
82
+ return s
83
+ total_pad = width - vl
84
+ left_pad = total_pad // 2
85
+ right_pad = total_pad - left_pad
86
+ return fillchar * left_pad + s + fillchar * right_pad
87
+
88
+
89
+ def wrap_text(text: str, width: int) -> list[str]:
90
+ if not text or text.isspace():
91
+ return ['']
92
+ if width <= 0:
93
+ return [text] if text else ['']
94
+ words = text.split(' ')
95
+ lines: list[str] = []
96
+ current = ''
97
+ for word in words:
98
+ if not word:
99
+ # consecutive spaces produce empty tokens
100
+ if current:
101
+ current += ' '
102
+ continue
103
+ # Hard-break words longer than width
104
+ while len(word) > width:
105
+ chunk_size = width if not current else width - len(current) - 1
106
+ if chunk_size <= 0:
107
+ # Current line is full, flush it and retry
108
+ lines.append(current)
109
+ current = ''
110
+ continue
111
+ chunk = word[:chunk_size]
112
+ if current and chunk:
113
+ lines.append(current + ' ' + chunk)
114
+ word = word[len(chunk):]
115
+ current = ''
116
+ elif current:
117
+ lines.append(current)
118
+ current = ''
119
+ else:
120
+ lines.append(word[:width])
121
+ word = word[width:]
122
+ if not word:
123
+ break
124
+ if not word:
125
+ continue
126
+ if not current:
127
+ current = word
128
+ elif len(current) + 1 + len(word) <= width:
129
+ current += ' ' + word
130
+ else:
131
+ lines.append(current)
132
+ current = word
133
+ if current:
134
+ lines.append(current)
135
+ return lines if lines else ['']
136
+
137
+
138
+ def render_spans(spans: list[InlineSpan], color: bool) -> str:
139
+ parts: list[str] = []
140
+ for span in spans:
141
+ text = span.text
142
+ if span.code:
143
+ text = style(text, dim=True, enabled=color)
144
+ elif span.bold or span.italic:
145
+ text = style(text, bold=span.bold, italic=span.italic, enabled=color)
146
+ parts.append(text)
147
+ return ''.join(parts)
@@ -0,0 +1,26 @@
1
+ Metadata-Version: 2.4
2
+ Name: termrender
3
+ Version: 0.1.0
4
+ Summary: Rich terminal rendering of directive-flavored markdown
5
+ Project-URL: Homepage, https://github.com/CaptainCrouton89/termrender
6
+ Project-URL: Repository, https://github.com/CaptainCrouton89/termrender
7
+ Project-URL: Issues, https://github.com/CaptainCrouton89/termrender/issues
8
+ Author: Silas Rhyneer
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: ansi,cli,markdown,rendering,terminal
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Terminals
22
+ Classifier: Topic :: Text Processing :: Markup :: Markdown
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: mermaid-ascii>=1.0
25
+ Requires-Dist: mistune>=3.0
26
+ Requires-Dist: pygments>=2.0
@@ -0,0 +1,23 @@
1
+ termrender/__init__.py,sha256=4iy-IPDdSCuLbKZQ-bGZYdjvhYSUbp_gH2uA5TkWon8,1310
2
+ termrender/__main__.py,sha256=Mi8pP88PngJzh3rG-y4mGwqbopek3MVsgWL3dxi1OyU,1057
3
+ termrender/blocks.py,sha256=4Tl9PZ7ZYFesuGdLni-ouRWMoKCKSz56mG_HWf4STDs,1070
4
+ termrender/emit.py,sha256=o526O02DG8ceKoJtu8jVkSz6nq2Tg_t01QQAfbPlMJM,1708
5
+ termrender/layout.py,sha256=7Geuxb0OWvGXuGlT3Kfr71jMT8JbFqt-ky7Zx9HEPCk,4255
6
+ termrender/parser.py,sha256=KpmZCkFLnopPROO_tdcmHH9CiV_4BkbaX64tKmqTTSo,11027
7
+ termrender/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ termrender/style.py,sha256=rtYugVVf6OhhqavnVAExN3cYeY8a0mUv2cMLINTBqoc,3839
9
+ termrender/renderers/__init__.py,sha256=yd6o7diiNq4TdDpzPq56-pwrPVVbAGuOiVYXIvL0lOc,43
10
+ termrender/renderers/borders.py,sha256=W6xZIxRXc4makLU3jcwSMICJhVW6yqVvqZBlkP71yIk,2109
11
+ termrender/renderers/code.py,sha256=aHWP60dLll-fx8Qe_1mXvOuLalagsMFiiC7Kr1OJOuQ,1249
12
+ termrender/renderers/columns.py,sha256=18huqrLXUqxZswOCCvEy4DFvw197oMbTcOL2pM7CX9o,1069
13
+ termrender/renderers/divider.py,sha256=ynNmx2Hk1HLVksvU2NDm6jjS_KVSOX_4ORzJ_M3SRBA,676
14
+ termrender/renderers/mermaid.py,sha256=Vq-xzwkcE01ug4DmTghUfWR8SE3TWeuPA7psDH8_EVc,913
15
+ termrender/renderers/panel.py,sha256=2HSio7PDtGXUX7D-3Ckna_gACn3GBonDKPdX3crn0kk,1779
16
+ termrender/renderers/quote.py,sha256=ulRuhgUNT2ELq1UTKdjVpFUqpGv1q44HucZswtLeKh0,969
17
+ termrender/renderers/text.py,sha256=2cbQNFmpfq2sadVEiz-CDEgcvuxwjBKJwmdyG4_MPLg,5378
18
+ termrender/renderers/tree.py,sha256=FKaoEpD_ysDsaqMQY8k2Yfp7ourFHSgU91FTrJIRYUA,5640
19
+ termrender-0.1.0.dist-info/METADATA,sha256=tBygYLYpe2Sp9C41cB2T_icV5mYIvvIuQkVuB9E3nVI,1081
20
+ termrender-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
21
+ termrender-0.1.0.dist-info/entry_points.txt,sha256=j0O0BL7svQS37Alal_l_KmiwuJzfQBn05l5M_lSRfOU,56
22
+ termrender-0.1.0.dist-info/licenses/LICENSE,sha256=zDCQvtSr_fdALZumhzMWEWBIKu7NxSGrl49Qe0YyRto,1070
23
+ termrender-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ termrender = termrender.__main__:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Silas Rhyneer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.