pygments_better_html 0.1.6__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.
@@ -0,0 +1,261 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Better HTML formatter for Pygments.
3
+
4
+ Copyright © 2020-2026, Chris Warrick.
5
+ License: 3-clause BSD.
6
+ Portions copyright © 2006-2022, the Pygments authors. (2-clause BSD).
7
+ """
8
+
9
+ __all__ = ["BetterHtmlFormatter"]
10
+ __version__ = "0.1.6"
11
+
12
+ import enum
13
+ import re
14
+ import warnings
15
+
16
+ from pygments.formatters.html import HtmlFormatter
17
+
18
+ MANY_SPACES = re.compile("( +)")
19
+
20
+
21
+ def _sp_to_nbsp(m):
22
+ return " " * (m.end() - m.start())
23
+
24
+
25
+ class BetterLinenos(enum.Enum):
26
+ TABLE = "table"
27
+ OL = "ol"
28
+
29
+
30
+ class BetterHtmlFormatter(HtmlFormatter):
31
+ r"""
32
+ Format tokens as HTML 4 ``<span>`` tags, with alternate formatting styles.
33
+
34
+ * ``linenos = 'table'`` renders each line of code in a separate table row
35
+ * ``linenos = 'ol'`` renders each line in a <li> element (inside <ol>)
36
+
37
+ Both options allow word wrap and don't include line numbers when copying.
38
+ """
39
+
40
+ name = "HTML"
41
+ aliases = ["html"]
42
+ filenames = ["*.html", "*.htm"]
43
+
44
+ def __init__(self, **options):
45
+ """Initialize the formatter."""
46
+ super().__init__(**options)
47
+ self.linenos_name = self.options.get("linenos", "table")
48
+ if self.linenos_name is False:
49
+ self.linenos_val = False
50
+ self.linenos = 0
51
+ elif self.linenos_name is True:
52
+ self.linenos_name = "table"
53
+ if self.linenos_name is not False:
54
+ self.linenos_val = BetterLinenos(self.linenos_name)
55
+ self.linenos = 2 if self.linenos_val == BetterLinenos.OL else 1
56
+
57
+ def get_style_defs(self, arg=None, wrapper_classes=None):
58
+ """Generate CSS style definitions.
59
+
60
+ Return CSS style definitions for the classes produced by the current
61
+ highlighting style. ``arg`` can be a string or list of selectors to
62
+ insert before the token type classes. ``wrapper_classes`` are a list of
63
+ classes for the wrappers, defaults to the ``cssclass`` option.
64
+ """
65
+ base = super().get_style_defs(arg)
66
+ new_styles = (
67
+ ("{0} table, {0} tr, {0} td", "border-spacing: 0; border-collapse: separate; padding: 0"),
68
+ ("{0} pre", "white-space: pre-wrap; line-height: normal"),
69
+ (
70
+ "{0}table td.linenos",
71
+ "vertical-align: top; padding-left: 10px; padding-right: 10px; user-select: none; -webkit-user-select: none",
72
+ ),
73
+ (
74
+ "{0}table td.linenos .special",
75
+ "padding: 0",
76
+ ),
77
+ # Hack for Safari (user-select does not affect copy-paste)
78
+ ("{0}table td.linenos code:before", "content: attr(data-line-number)"),
79
+ ("{0}table td.code", "overflow-wrap: normal; border-collapse: collapse"),
80
+ (
81
+ "{0}table td.code code",
82
+ "overflow: unset; border: none; padding: 0; margin: 0; white-space: pre-wrap; line-height: unset; background: none",
83
+ ),
84
+ ("{0} .lineno.nonumber", "list-style: none"),
85
+ )
86
+ new_styles_code = []
87
+ if wrapper_classes is None:
88
+ wrapper_classes = ["." + self.cssclass]
89
+ for cls, rule in new_styles:
90
+ new_styles_code.append(", ".join(cls.format(c) for c in wrapper_classes) + " { " + rule + " }")
91
+ return base + "\n" + "\n".join(new_styles_code)
92
+
93
+ def _wrap_tablelinenos(self, inner):
94
+ lncount = 0
95
+ codelines = []
96
+ for t, line in inner:
97
+ if t:
98
+ lncount += 1
99
+ codelines.append(line)
100
+
101
+ fl = self.linenostart
102
+ mw = len(str(lncount + fl - 1))
103
+ sp = self.linenospecial
104
+ st = self.linenostep
105
+ la = self.lineanchors
106
+ aln = self.anchorlinenos
107
+ nocls = self.noclasses
108
+ if sp:
109
+ lines = []
110
+
111
+ for i in range(fl, fl + lncount):
112
+ line_before = ""
113
+ line_after = ""
114
+ if i % st == 0:
115
+ if i % sp == 0:
116
+ if aln:
117
+ line_before = '<a href="#%s-%d" class="special">' % (la, i)
118
+ line_after = "</a>"
119
+ else:
120
+ line_before = '<span class="special">'
121
+ line_after = "</span>"
122
+ elif aln:
123
+ line_before = '<a href="#%s-%d">' % (la, i)
124
+ line_after = "</a>"
125
+ lines.append((line_before, "%*d" % (mw, i), line_after))
126
+ else:
127
+ lines.append(("", "", ""))
128
+ else:
129
+ lines = []
130
+ for i in range(fl, fl + lncount):
131
+ line_before = ""
132
+ line_after = ""
133
+ if i % st == 0:
134
+ if aln:
135
+ line_before = '<a href="#%s-%d">' % (la, i)
136
+ line_after = "</a>"
137
+ lines.append((line_before, "%*d" % (mw, i), line_after))
138
+ else:
139
+ lines.append(("", "", ""))
140
+
141
+ yield 0, '<div class="%s"><table class="%stable">' % (
142
+ self.cssclass,
143
+ self.cssclass,
144
+ )
145
+ for lndata, cl in zip(lines, codelines):
146
+ ln_b, ln, ln_a = lndata
147
+ cl = MANY_SPACES.sub(_sp_to_nbsp, cl)
148
+ if nocls:
149
+ yield 0, (
150
+ '<tr><td class="linenos linenodiv" style="background-color: #f0f0f0; padding-right: 10px">'
151
+ + ln_b
152
+ + '<code data-line-number="'
153
+ + ln
154
+ + '"></code>'
155
+ + ln_a
156
+ + '</td><td class="code"><code>'
157
+ + cl
158
+ + "</code></td></tr>"
159
+ )
160
+ else:
161
+ yield 0, (
162
+ '<tr><td class="linenos linenodiv">'
163
+ + ln_b
164
+ + '<code data-line-number="'
165
+ + ln
166
+ + '"></code>'
167
+ + ln_a
168
+ + '</td><td class="code"><code>'
169
+ + cl
170
+ + "</code></td></tr>"
171
+ )
172
+ yield 0, "</table></div>"
173
+
174
+ def _wrap_inlinelinenos(self, inner):
175
+ # Override with new method
176
+ return self._wrap_ollineos(self, inner)
177
+
178
+ def _wrap_ollinenos(self, inner):
179
+ lines = inner
180
+ sp = self.linenospecial
181
+ st = self.linenostep or 1
182
+ num = self.linenostart
183
+
184
+ if self.anchorlinenos:
185
+ warnings.warn("anchorlinenos is not supported for linenos='ol'.")
186
+
187
+ yield 0, "<ol>"
188
+ if self.noclasses:
189
+ if sp:
190
+ for t, line in lines:
191
+ if num % sp == 0:
192
+ style = "background-color: #ffffc0; padding: 0 5px 0 5px"
193
+ else:
194
+ style = "background-color: #f0f0f0; padding: 0 5px 0 5px"
195
+ if num % st != 0:
196
+ style += "; list-style: none"
197
+ yield 1, '<li style="%s" value="%s">' % (style, num,) + line + "</li>"
198
+ num += 1
199
+ else:
200
+ for t, line in lines:
201
+ yield 1, (
202
+ '<li style="background-color: #f0f0f0; padding: 0 5px 0 5px%s" value="%s">'
203
+ % (("; list-style: none" if num % st != 0 else ""), num)
204
+ + line
205
+ + "</li>"
206
+ )
207
+ num += 1
208
+ elif sp:
209
+ for t, line in lines:
210
+ yield 1, '<li class="lineno%s%s" value="%s">' % (
211
+ " special" if num % sp == 0 else "",
212
+ " nonumber" if num % st != 0 else "",
213
+ num,
214
+ ) + line + "</li>"
215
+ num += 1
216
+ else:
217
+ for t, line in lines:
218
+ yield 1, '<li class="lineno%s" value="%s">' % (
219
+ "" if num % st != 0 else " nonumber",
220
+ num,
221
+ ) + line + "</li>"
222
+ num += 1
223
+
224
+ yield 0, "</ol>"
225
+
226
+ def format_unencoded(self, tokensource, outfile):
227
+ """Format code and write to outfile.
228
+
229
+ The formatting process uses several nested generators; which of
230
+ them are used is determined by the user's options.
231
+
232
+ Each generator should take at least one argument, ``inner``,
233
+ and wrap the pieces of text generated by this.
234
+
235
+ Always yield 2-tuples: (code, text). If "code" is 1, the text
236
+ is part of the original tokensource being highlighted, if it's
237
+ 0, the text is some piece of wrapping. This makes it possible to
238
+ use several different wrappers that process the original source
239
+ linewise, e.g. line number generators.
240
+ """
241
+ if self.linenos_val is False:
242
+ return super().format_unencoded(tokensource, outfile)
243
+ source = self._format_lines(tokensource)
244
+ if self.hl_lines:
245
+ source = self._highlight_lines(source)
246
+ if not self.nowrap:
247
+ if self.linenos_val == BetterLinenos.OL:
248
+ source = self._wrap_ollinenos(source)
249
+ if self.lineanchors:
250
+ source = self._wrap_lineanchors(source)
251
+ if self.linespans:
252
+ source = self._wrap_linespans(source)
253
+ if self.linenos_val == BetterLinenos.TABLE:
254
+ source = self._wrap_tablelinenos(source)
255
+ if self.linenos_val == BetterLinenos.OL:
256
+ source = self.wrap(source)
257
+ if self.full:
258
+ source = self._wrap_full(source, outfile)
259
+
260
+ for t, piece in source:
261
+ outfile.write(piece)
@@ -0,0 +1,94 @@
1
+ Metadata-Version: 2.4
2
+ Name: pygments_better_html
3
+ Version: 0.1.6
4
+ Summary: Better HTML formatter for Pygments.
5
+ Keywords: pygments,html,code,highlighting
6
+ Author-email: Chris Warrick <chris@chriswarrick.com>
7
+ Requires-Python: >=3.7
8
+ Description-Content-Type: text/markdown
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Programming Language :: Python
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Topic :: Software Development
13
+ License-File: LICENSE
14
+ License-File: LICENSE.pygments
15
+ Requires-Dist: Pygments>=2.15.1
16
+ Project-URL: Homepage, https://github.com/Kwpolska/pygments_better_html
17
+
18
+ # Better line numbers for Pygments HTML
19
+
20
+ This library provides improved line numbers for the Pygments HTML formatter. The `BetterHtmlFormatter` supports two styles:
21
+
22
+ * `linenos="table"` (the default) — every line of the code is a separate table row (a 2xN table, as opposed to Pygments’ standard 2x1 table) This improves the appearance if the code contains characters with unusual line-height, and allows for the code to be word-wrapped with the numbers kept in the right places.
23
+ * `linenos="ol"` — lines are `<li>` elements in an `<ol>` list.
24
+
25
+ Both styles allow for copy-pasting into a code editor. Directly copy-pasting into Microsoft Word (or similar) might produce something ugly. The first style is inspired by GitHub, and the second can be seen at pastebin.com.
26
+
27
+ The `table` style is more flexible and looks better. The `ol` style is slightly more compatible with broken browsers and minifiers. Pick whichever one works best for you.
28
+
29
+ ## Usage
30
+
31
+ In most cases, it’s a drop-in replacement for `HtmlFormatter`. Just add the import:
32
+
33
+ ```py
34
+ from pygments_better_html import BetterHtmlFormatter
35
+ ```
36
+
37
+ and when calling `highlight()`, instead of `HtmlFormatter`, pass the `BetterHtmlFormatter` class:
38
+
39
+ ```py
40
+ BetterHtmlFormatter(linenos="table", ..., other options, ...)
41
+ BetterHtmlFormatter(linenos="ol", ..., other options, ...)
42
+ ```
43
+
44
+ You can see a simple demo in `demo.py`.
45
+
46
+ ### Required CSS
47
+
48
+ To make this work, you will need to add the following CSS:
49
+
50
+ ```css
51
+ .highlight table, .highlight tr, .highlight td { border-spacing: 0; border-collapse: separate; padding: 0 }
52
+ .highlight pre { white-space: pre-wrap; line-height: normal }
53
+ .highlighttable td.linenos { vertical-align: top; padding-left: 10px; padding-right: 10px; user-select: none; -webkit-user-select: none }
54
+ .highlighttable td.linenos code:before { content: attr(data-line-number) }
55
+ .highlighttable td.code { overflow-wrap: normal; border-collapse: collapse }
56
+ .highlighttable td.code code { overflow: unset; border: none; padding: 0; margin: 0; white-space: pre-wrap; line-height: unset; background: none }
57
+ .highlight .lineno.nonumber { list-style: none }
58
+ ```
59
+
60
+ If you’re using ``get_style_defs``, those will be included for you.
61
+
62
+ ## Browser support
63
+
64
+ All reasonably modern versions of reasonable browsers are supported. Internet Explorer is neither, so it isn’t supported. Firefox, Chrome and Safari are supported. Either mode works with these browsers, although I’ve seen Firefox add extra spaces to the front of lines randomly, and Safari requires an ugly hack for the table mode.
65
+
66
+ ## Known limitations
67
+
68
+ 1. The `anchorlinenos` option is not supported for `linenos="ol"`.
69
+ 2. Third-party minifier tools may destroy your indentation if you use tabs. Spaces use a work-around, described in the following point.
70
+ 3. Because of overly clever HTML minifiers, `&nbsp;` tags are used for indentation and sequences of whitespace longer than one character. This might break in the event web browsers decide to copy non-breaking spaces as non-breaking instead of regular spaces. Currently, browsers do the right thing on all platforms. It might also lead to degraded apperance in some edge cases (indentation longer than the code box width, or long runs of spaces inside the code).
71
+ 4. Some completely broken HTML minifiers will remove line numbers, because they are empty tags (that’s the only way to make Safari ignore them on copy-paste). Removing empty tags is just wrong, considering how many browser hacks were built on top of these throughout the years. I saw this issue with HTML Tidy, which is an antique tool detached from reality (even in the HTML5 fork).
72
+
73
+ If you care about compatiblity with bad tools or unusual scenarios, and don’t mind losing `anchorlinenos`, consider using the `lineos="ol"` mode instead of `lineos="table"`.
74
+
75
+ ### Browsers vs code vs minifiers
76
+
77
+ Limitations (3) and (4) might be considered bugs in my code and not the minifiers. But note that browsers don’t ignore whitespace when parsing, and although the default `white-space: normal` setting for most tags collapses them, you can use `white-space: pre` or `white-space: pre-wrap` to display them. Those minifiers don’t take the CSS into account, and assume the default behavior, collapsing spaces outside of `<pre>` tags. Which is wrong if you override `white-space` on other elements, and “wasteful” if you do `pre { white-space: normal }` for some unusual reason (yeah, don’t do that.)
78
+
79
+ Collapsing whitespace could be worked around with a `<pre>` tag around each line of code, but Firefox will add extra newlines when copying (so the actual code is on every other line of the copied text). This is not avoidable and hard-coded (the plaintext conversion does not look at CSS either, and has a special case for `<pre>` since it makes sense for normal use of that tag. And you can’t wrap the entire table in a `<pre>` tag. If I added one, browsers would move it outside of the table to make the HTML valid. But if browsers do that, some of those clever minifiers might fix HTML syntax as well.
80
+
81
+ I decided to use a different solution, and work around these tools, by using non-breaking spaces for longer runs of spaces. This depends on web browsers replacing those with regular spaces when copying. Luckily, all browsers do this, and considering a 2008 4chan meme (“can’t triforce”, search results might be NSFW), that’s been a thing since forever and is not likely to change.
82
+
83
+ The selected solution of replacing runs of spaces with non-breaking spaces can lead to the code overflowing the box/adding a horizontal scrollbar. Those will happen only in very specific edge cases, i.e. very narrow windows + very large fonts + large indents + no regular (single) spaces close to the indent.
84
+
85
+ I also decided not to apply it to tabs (\t, ^I, U+0009 HORIZONTAL TAB), because tab width can be random, and tabs move the caret to a place, not by a set amount (so in `"a\tb"` and `"aa\tb"`, the `"b"` appears in the same place on the line). Which is generally difficult to handle properly, and you should be using spaces to indent your code anyway.
86
+
87
+ You should also note that GitHub uses both of these techniques, and BitBucket uses the first one. And that it’s easier for everyone to find a better tool if their current tool does stupid stuff.
88
+
89
+ ## License
90
+
91
+ Copyright © 2020-2026, Chris Warrick. Licensed under the 3-clause BSD license.
92
+
93
+ Many parts of the code are taken from Pygments’ original HTMLFormatter, which is copyright © 2006-2022 by the Pygments team, and is licensed under the 2-clause BSD license.
94
+
@@ -0,0 +1,6 @@
1
+ pygments_better_html/__init__.py,sha256=_n0f-Jgje14aR5dCEm0L-ulDU_Zi2BbCJ1GFlK_AEW8,9705
2
+ pygments_better_html-0.1.6.dist-info/licenses/LICENSE,sha256=GkQeIqf8-mNiwQK4LK5sZ14T2vLSw1Z3ZwbsKJ31CvM,1526
3
+ pygments_better_html-0.1.6.dist-info/licenses/LICENSE.pygments,sha256=qdZvHVJt8C4p3Oc0NtNOVuhjL0bCdbvf_HBWnogvnxc,1331
4
+ pygments_better_html-0.1.6.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
5
+ pygments_better_html-0.1.6.dist-info/METADATA,sha256=ryRWoWEUMOxqb1GSjhea0dDIJJwszuwc30Z0WZWtfDE,7173
6
+ pygments_better_html-0.1.6.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: flit 3.12.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,30 @@
1
+ Copyright © 2020-2026, Chris Warrick.
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are
6
+ met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright
9
+ notice, this list of conditions, and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright
12
+ notice, this list of conditions, and the following disclaimer in the
13
+ documentation and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the author of this software nor the names of
16
+ contributors to this software may be used to endorse or promote
17
+ products derived from this software without specific prior written
18
+ consent.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,25 @@
1
+ Copyright (c) 2006-2022 by the respective authors (see AUTHORS file).
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are
6
+ met:
7
+
8
+ * Redistributions of source code must retain the above copyright
9
+ notice, this list of conditions and the following disclaimer.
10
+
11
+ * Redistributions in binary form must reproduce the above copyright
12
+ notice, this list of conditions and the following disclaimer in the
13
+ documentation and/or other materials provided with the distribution.
14
+
15
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.