pygments-citry 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.
- pygments_citry/__init__.py +42 -0
- pygments_citry/citry_html.py +195 -0
- pygments_citry/lexers.py +79 -0
- pygments_citry/py.typed +0 -0
- pygments_citry-0.1.0.dist-info/METADATA +126 -0
- pygments_citry-0.1.0.dist-info/RECORD +10 -0
- pygments_citry-0.1.0.dist-info/WHEEL +5 -0
- pygments_citry-0.1.0.dist-info/entry_points.txt +3 -0
- pygments_citry-0.1.0.dist-info/licenses/LICENSE +22 -0
- pygments_citry-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pygments-citry: a Pygments lexer for Citry components.
|
|
3
|
+
|
|
4
|
+
Importing this package registers the ``citry`` lexer with Pygments, so
|
|
5
|
+
``get_lexer_by_name("citry")`` resolves even in an environment where the plugin
|
|
6
|
+
entry point was not picked up (for example an editable checkout whose plugin
|
|
7
|
+
metadata is not written). For an installed package, the ``pygments.lexers``
|
|
8
|
+
entry point in ``pyproject.toml`` is the primary registration and needs no
|
|
9
|
+
import.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from pygments.lexers import LEXERS
|
|
15
|
+
|
|
16
|
+
from pygments_citry.citry_html import CitryHtmlLexer
|
|
17
|
+
from pygments_citry.lexers import CitryPythonLexer
|
|
18
|
+
|
|
19
|
+
__all__ = ["CitryHtmlLexer", "CitryPythonLexer"]
|
|
20
|
+
|
|
21
|
+
# Register in Pygments' builtin lexer table. get_lexer_by_name scans this table
|
|
22
|
+
# before the entry-point plugins, so an explicit `import pygments_citry` becomes
|
|
23
|
+
# a deterministic loader. Each tuple is (module, name, aliases, filenames,
|
|
24
|
+
# mimetypes); _load_lexers imports the module and finds the class via __all__.
|
|
25
|
+
#
|
|
26
|
+
# `citry` is the full component lexer (Python + embedded template/js/css);
|
|
27
|
+
# `citry-html` is the template lexer alone, for a fence that shows only a
|
|
28
|
+
# template (`<c-*>` tags, `{{ }}`, `{# #}`) with no surrounding Python.
|
|
29
|
+
LEXERS["CitryPythonLexer"] = (
|
|
30
|
+
"pygments_citry",
|
|
31
|
+
"Citry Python",
|
|
32
|
+
("citry",),
|
|
33
|
+
(),
|
|
34
|
+
(),
|
|
35
|
+
)
|
|
36
|
+
LEXERS["CitryHtmlLexer"] = (
|
|
37
|
+
"pygments_citry",
|
|
38
|
+
"Citry HTML",
|
|
39
|
+
("citry-html",),
|
|
40
|
+
(),
|
|
41
|
+
(),
|
|
42
|
+
)
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The lexer for the HTML that lives inside a Citry component's ``template``.
|
|
3
|
+
|
|
4
|
+
Citry templates are HTML with a few additions: ``<c-*>`` component and
|
|
5
|
+
control-flow tags, ``c-*`` dynamic attributes whose values are Python
|
|
6
|
+
expressions, ``{{ python }}`` interpolation, ``{# comment #}``, and a verbatim
|
|
7
|
+
``<c-raw>`` element. This lexer extends Pygments' plain ``HtmlLexer`` with those,
|
|
8
|
+
so a Citry template highlights as HTML with the Citry bits called out, and the
|
|
9
|
+
Python inside ``{{ }}`` and ``c-*`` values is handed to the Python lexer.
|
|
10
|
+
|
|
11
|
+
It is registered as ``citry-html``, both on its own (for a fenced block that
|
|
12
|
+
shows only a template) and embedded by ``CitryPythonLexer`` to highlight the
|
|
13
|
+
``template`` string of a component.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from typing import TYPE_CHECKING, Any, ClassVar
|
|
19
|
+
|
|
20
|
+
from pygments.lexer import ExtendedRegexLexer, bygroups, using
|
|
21
|
+
from pygments.lexers.html import HtmlLexer
|
|
22
|
+
from pygments.lexers.javascript import JavascriptLexer
|
|
23
|
+
from pygments.lexers.python import PythonLexer
|
|
24
|
+
from pygments.token import Comment, Name, Operator, Punctuation, Text
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
import re
|
|
28
|
+
from collections.abc import Iterator
|
|
29
|
+
|
|
30
|
+
# An exact Citry tag name ends where the template grammar's tag name ends. The
|
|
31
|
+
# explicit lookahead stops a built-in prefix such as ``c-slot`` from stealing
|
|
32
|
+
# the start of a user component name such as ``c-slot-panel``.
|
|
33
|
+
_TAG_END = r"(?=\s|/?>|\{#)"
|
|
34
|
+
|
|
35
|
+
# One Python lexer, reused for the bodies of {{ ... }} interpolations.
|
|
36
|
+
_PYTHON = PythonLexer()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _interpolation(_lexer: Any, match: re.Match[str], ctx: Any) -> Iterator[tuple[int, Any, str]]:
|
|
40
|
+
"""
|
|
41
|
+
Tokenise a ``{{ ... }}`` interpolation, handing the expression to Python.
|
|
42
|
+
|
|
43
|
+
The closing ``}}`` is found by scanning for one at brace depth zero and
|
|
44
|
+
skipping Python string literals along the way, so a ``}}`` inside a string
|
|
45
|
+
(``{{ "}}" }}``) or a nested dict literal (``{{ {"a": {1: 2}} }}``) does not
|
|
46
|
+
end the block early. This mirrors how the engine's grammar finds the
|
|
47
|
+
boundary; a plain regex cannot, because it cannot count nesting.
|
|
48
|
+
|
|
49
|
+
Surrounding whitespace stays plain text and only the expression in the
|
|
50
|
+
middle is highlighted, matching how the rest of the lexer treats a value.
|
|
51
|
+
"""
|
|
52
|
+
text = match.string
|
|
53
|
+
yield match.start(), Punctuation, "{{"
|
|
54
|
+
start = match.end()
|
|
55
|
+
length = len(text)
|
|
56
|
+
i = start
|
|
57
|
+
depth = 0
|
|
58
|
+
close = None
|
|
59
|
+
while i < length:
|
|
60
|
+
char = text[i]
|
|
61
|
+
if char in "\"'": # skip a Python string literal, honouring backslash escapes
|
|
62
|
+
quote = char
|
|
63
|
+
i += 1
|
|
64
|
+
while i < length:
|
|
65
|
+
if text[i] == "\\":
|
|
66
|
+
i += 2
|
|
67
|
+
continue
|
|
68
|
+
if text[i] == quote:
|
|
69
|
+
i += 1
|
|
70
|
+
break
|
|
71
|
+
i += 1
|
|
72
|
+
continue
|
|
73
|
+
if char == "{":
|
|
74
|
+
depth += 1
|
|
75
|
+
elif char == "}":
|
|
76
|
+
if depth:
|
|
77
|
+
depth -= 1
|
|
78
|
+
elif i + 1 < length and text[i + 1] == "}":
|
|
79
|
+
close = i
|
|
80
|
+
break
|
|
81
|
+
i += 1
|
|
82
|
+
|
|
83
|
+
body = text[start : close if close is not None else length]
|
|
84
|
+
lead = body[: len(body) - len(body.lstrip())]
|
|
85
|
+
expr = body.strip()
|
|
86
|
+
if lead:
|
|
87
|
+
yield start, Text, lead
|
|
88
|
+
for offset, token, value in _PYTHON.get_tokens_unprocessed(expr):
|
|
89
|
+
yield start + len(lead) + offset, token, value
|
|
90
|
+
trail = body[len(lead) + len(expr) :]
|
|
91
|
+
if trail:
|
|
92
|
+
yield start + len(lead) + len(expr), Text, trail
|
|
93
|
+
|
|
94
|
+
if close is not None:
|
|
95
|
+
yield close, Punctuation, "}}"
|
|
96
|
+
ctx.pos = close + 2
|
|
97
|
+
else:
|
|
98
|
+
ctx.pos = length
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# The value of a c-* (or built-in) attribute is a Python expression, quoted or bare.
|
|
102
|
+
_PY_ATTR_STATE = "python-attr"
|
|
103
|
+
_JS_ATTR_STATE = "javascript-attr"
|
|
104
|
+
# Attribute rules shared by ordinary and built-in tags: framework passthrough
|
|
105
|
+
# names plain HtmlLexer cannot tokenise (@click, [style], (click)), ordinary
|
|
106
|
+
# attributes (covers :class and v-model, which HtmlLexer handles), and the close.
|
|
107
|
+
_COMMON_ATTR_RULES = [
|
|
108
|
+
(r"([@\[(][\w:@.()\[\]-]*)(\s*)(=)(\s*)", bygroups(Name.Attribute, Text, Operator, Text), "attr"),
|
|
109
|
+
(r"[@\[(][\w:@.()\[\]-]*", Name.Attribute),
|
|
110
|
+
(r"([\w:-]+)(\s*)(=)(\s*)", bygroups(Name.Attribute, Text, Operator, Text), "attr"),
|
|
111
|
+
(r"[\w:-]+", Name.Attribute),
|
|
112
|
+
(r"(/?)(\s*)(>)", bygroups(Punctuation, Text, Punctuation), "#pop"),
|
|
113
|
+
]
|
|
114
|
+
_WS_RULE = (r"\s+", Text)
|
|
115
|
+
# `$c-props` is evaluated by the client runtime, so its value is JavaScript.
|
|
116
|
+
_CLIENT_PROPS_ATTR_RULE = (
|
|
117
|
+
r"(\$c-props)(\s*)(=)(\s*)",
|
|
118
|
+
bygroups(Name.Attribute, Text, Operator, Text),
|
|
119
|
+
_JS_ATTR_STATE,
|
|
120
|
+
)
|
|
121
|
+
# A c-* dynamic attribute: the value is a Python expression.
|
|
122
|
+
_C_ATTR_RULE = (r"(c-[\w$:@.-]*)(\s*)(=)(\s*)", bygroups(Name.Attribute, Text, Operator, Text), _PY_ATTR_STATE)
|
|
123
|
+
# Structural control-flow tags carry Python-valued attributes that are not
|
|
124
|
+
# c-*-prefixed. These rules live in tag-specific states because the same names
|
|
125
|
+
# on other elements are ordinary static HTML attributes.
|
|
126
|
+
_COND_ATTR_RULE = (r"(cond)(\s*)(=)(\s*)", bygroups(Name.Attribute, Text, Operator, Text), _PY_ATTR_STATE)
|
|
127
|
+
_EACH_ATTR_RULE = (r"(each)(\s*)(=)(\s*)", bygroups(Name.Attribute, Text, Operator, Text), _PY_ATTR_STATE)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class CitryHtmlLexer(ExtendedRegexLexer, HtmlLexer):
|
|
131
|
+
"""Highlights the HTML (plus Citry tags and interpolation) of a Citry template."""
|
|
132
|
+
|
|
133
|
+
name = "Citry HTML"
|
|
134
|
+
aliases: ClassVar[list[str]] = ["citry-html"]
|
|
135
|
+
|
|
136
|
+
tokens: ClassVar[dict[str, Any]] = {
|
|
137
|
+
**HtmlLexer.tokens,
|
|
138
|
+
"root": [
|
|
139
|
+
# {# template comment #}
|
|
140
|
+
(r"\{#.*?#\}", Comment),
|
|
141
|
+
# {{ python-expression }}: a callback scans to the balanced }}
|
|
142
|
+
(r"\{\{", _interpolation),
|
|
143
|
+
# a lone { that is not part of {{ or {#
|
|
144
|
+
(r"\{", Text),
|
|
145
|
+
# <c-raw> ... </c-raw>: the body is verbatim (not interpreted)
|
|
146
|
+
(
|
|
147
|
+
r"(<)(\s*)(c-raw)" + _TAG_END,
|
|
148
|
+
bygroups(Punctuation, Text, Name.Tag),
|
|
149
|
+
("raw-content", "tag"),
|
|
150
|
+
),
|
|
151
|
+
# Only the structural tags with unprefixed Python attributes need
|
|
152
|
+
# special states. Every other c-* name falls through to HtmlLexer.
|
|
153
|
+
(
|
|
154
|
+
r"(</?)(\s*)(c-(?:if|elif))" + _TAG_END,
|
|
155
|
+
bygroups(Punctuation, Text, Name.Tag),
|
|
156
|
+
"condition-tag",
|
|
157
|
+
),
|
|
158
|
+
(
|
|
159
|
+
r"(</?)(\s*)(c-for)" + _TAG_END,
|
|
160
|
+
bygroups(Punctuation, Text, Name.Tag),
|
|
161
|
+
"for-tag",
|
|
162
|
+
),
|
|
163
|
+
# ordinary text, stopped at { so the interpolation/comment rules fire;
|
|
164
|
+
# everything after this is HtmlLexer's own root (its [^<&]+ text rule,
|
|
165
|
+
# which does not stop at {, is dropped by the [1:] slice)
|
|
166
|
+
(r"[^<&{]+", Text),
|
|
167
|
+
*HtmlLexer.tokens["root"][1:],
|
|
168
|
+
],
|
|
169
|
+
# An ordinary element's attributes.
|
|
170
|
+
"tag": [_WS_RULE, _CLIENT_PROPS_ATTR_RULE, _C_ATTR_RULE, *_COMMON_ATTR_RULES],
|
|
171
|
+
"condition-tag": [_WS_RULE, _CLIENT_PROPS_ATTR_RULE, _C_ATTR_RULE, _COND_ATTR_RULE, *_COMMON_ATTR_RULES],
|
|
172
|
+
"for-tag": [_WS_RULE, _CLIENT_PROPS_ATTR_RULE, _C_ATTR_RULE, _EACH_ATTR_RULE, *_COMMON_ATTR_RULES],
|
|
173
|
+
# The value of a c-* (or built-in) attribute: a Python expression.
|
|
174
|
+
"python-attr": [
|
|
175
|
+
(r'(")((?:[^"\\]|\\.)*)(")', bygroups(Punctuation, using(PythonLexer), Punctuation), "#pop"),
|
|
176
|
+
(r"(')((?:[^'\\]|\\.)*)(')", bygroups(Punctuation, using(PythonLexer), Punctuation), "#pop"),
|
|
177
|
+
(r"[^\s>]+", using(PythonLexer), "#pop"),
|
|
178
|
+
],
|
|
179
|
+
# The direct client props value is an Alpine/JavaScript expression.
|
|
180
|
+
"javascript-attr": [
|
|
181
|
+
(r'(")((?:[^"\\]|\\.)*)(")', bygroups(Punctuation, using(JavascriptLexer), Punctuation), "#pop"),
|
|
182
|
+
(r"(')((?:[^'\\]|\\.)*)(')", bygroups(Punctuation, using(JavascriptLexer), Punctuation), "#pop"),
|
|
183
|
+
(r"[^\s>]+", using(JavascriptLexer), "#pop"),
|
|
184
|
+
],
|
|
185
|
+
# The verbatim body of <c-raw>...</c-raw>: plain text until the close tag.
|
|
186
|
+
"raw-content": [
|
|
187
|
+
(
|
|
188
|
+
r"(<)(\s*)(/)(\s*)(c-raw)(\s*)(>)",
|
|
189
|
+
bygroups(Punctuation, Text, Punctuation, Text, Name.Tag, Text, Punctuation),
|
|
190
|
+
"#pop",
|
|
191
|
+
),
|
|
192
|
+
(r"[^<]+", Text),
|
|
193
|
+
(r"<", Text),
|
|
194
|
+
],
|
|
195
|
+
}
|
pygments_citry/lexers.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The ``citry`` lexer: Python, with the embedded ``template``/``js``/``css`` highlighted.
|
|
3
|
+
|
|
4
|
+
``CitryPythonLexer`` behaves like Pygments' ``PythonLexer`` but recognises the
|
|
5
|
+
three multiline string attributes a Citry component uses and hands each body to
|
|
6
|
+
the right language:
|
|
7
|
+
|
|
8
|
+
- ``template = \"\"\"...\"\"\"`` to the Citry HTML lexer (``<c-*>`` tags, ``{{ }}``, ...),
|
|
9
|
+
- ``js = \"\"\"...\"\"\"`` to the JavaScript lexer,
|
|
10
|
+
- ``css = \"\"\"...\"\"\"`` to the CSS lexer.
|
|
11
|
+
|
|
12
|
+
The detection is deliberately simple, matching the upstream ``pygments-djc``: it
|
|
13
|
+
fires on any ``template``/``js``/``css`` triple-quoted assignment, even one not
|
|
14
|
+
on a component class. That is fine for documentation, where these names almost
|
|
15
|
+
always mean a component.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from typing import Any, ClassVar
|
|
21
|
+
|
|
22
|
+
from pygments.lexer import bygroups, using
|
|
23
|
+
from pygments.lexers.css import CssLexer
|
|
24
|
+
from pygments.lexers.javascript import JavascriptLexer
|
|
25
|
+
from pygments.lexers.python import PythonLexer
|
|
26
|
+
from pygments.token import Name, Operator, Punctuation, String, Text
|
|
27
|
+
|
|
28
|
+
from pygments_citry.citry_html import CitryHtmlLexer
|
|
29
|
+
|
|
30
|
+
# Matches `template = """` or the typed `template: some.Type = """` (and the ''' form).
|
|
31
|
+
# The nine capture groups are highlighted individually by the bygroups action below.
|
|
32
|
+
_CAPTURE = r'({name})(\s*)(?:(:)(\s*)([^\s=]+))?(\s*)(=)(\s*)((?:"""|\'\'\'))'
|
|
33
|
+
|
|
34
|
+
# Highlights the nine groups of _CAPTURE. Reused for all three openers, which
|
|
35
|
+
# differ only in the attribute name and the state they push.
|
|
36
|
+
_OPENER = bygroups(
|
|
37
|
+
Name.Variable, # the attribute name (template / js / css)
|
|
38
|
+
Text,
|
|
39
|
+
Punctuation, # the ':' of an optional type annotation
|
|
40
|
+
Text,
|
|
41
|
+
Name.Class, # the annotation type, if present
|
|
42
|
+
Text,
|
|
43
|
+
Operator, # the '='
|
|
44
|
+
Text,
|
|
45
|
+
String.Doc, # the opening triple quote
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class CitryPythonLexer(PythonLexer):
|
|
50
|
+
"""Lexer for Citry component code: Python plus the embedded template/js/css."""
|
|
51
|
+
|
|
52
|
+
name = "Citry Python"
|
|
53
|
+
aliases: ClassVar[list[str]] = ["citry"]
|
|
54
|
+
|
|
55
|
+
tokens: ClassVar[dict[str, Any]] = {
|
|
56
|
+
**PythonLexer.tokens,
|
|
57
|
+
# The three openers are prepended to Python's root so they win over its
|
|
58
|
+
# own string handling; each pushes a state that re-lexes the body.
|
|
59
|
+
"root": [
|
|
60
|
+
(_CAPTURE.format(name="template"), _OPENER, "template_string"),
|
|
61
|
+
(_CAPTURE.format(name="js"), _OPENER, "js_string"),
|
|
62
|
+
(_CAPTURE.format(name="css"), _OPENER, "css_string"),
|
|
63
|
+
*PythonLexer.tokens["root"],
|
|
64
|
+
],
|
|
65
|
+
# Inside a body: the closing triple quote pops (first, so it wins);
|
|
66
|
+
# otherwise the run up to the close is handed to the embedded lexer.
|
|
67
|
+
"template_string": [
|
|
68
|
+
(r'(?:"""|\'\'\')', String.Doc, "#pop"),
|
|
69
|
+
(r'((?!"""|\'\'\')(?:.|\n))+', using(CitryHtmlLexer)),
|
|
70
|
+
],
|
|
71
|
+
"js_string": [
|
|
72
|
+
(r'(?:"""|\'\'\')', String.Doc, "#pop"),
|
|
73
|
+
(r'((?!"""|\'\'\')(?:.|\n))+', using(JavascriptLexer)),
|
|
74
|
+
],
|
|
75
|
+
"css_string": [
|
|
76
|
+
(r'(?:"""|\'\'\')', String.Doc, "#pop"),
|
|
77
|
+
(r'((?!"""|\'\'\')(?:.|\n))+', using(CssLexer)),
|
|
78
|
+
],
|
|
79
|
+
}
|
pygments_citry/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pygments-citry
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Pygments lexer for Citry components: highlights the HTML, JS, and CSS embedded in a component's template/js/css strings
|
|
5
|
+
Author-email: Juro Oravec <juraj.oravec.josefson@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://citry.dev
|
|
8
|
+
Project-URL: Repository, https://github.com/citry-dev/citry
|
|
9
|
+
Project-URL: Changelog, https://github.com/citry-dev/citry/blob/main/packages/py/pygments_citry/CHANGELOG.md
|
|
10
|
+
Project-URL: Issues, https://github.com/citry-dev/citry/issues
|
|
11
|
+
Keywords: citry,pygments,lexer,syntax highlighting,components,html
|
|
12
|
+
Classifier: Environment :: Plugins
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python
|
|
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: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Topic :: Text Processing :: Markup :: HTML
|
|
23
|
+
Classifier: Topic :: Software Development :: Documentation
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: <4.0,>=3.10
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: pygments>=2.15
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# pygments-citry
|
|
32
|
+
|
|
33
|
+
A [Pygments](https://pygments.org/) lexer for [Citry](https://citry.dev)
|
|
34
|
+
components. It highlights a component the way an editor would: the Python class,
|
|
35
|
+
plus the HTML, JavaScript, and CSS embedded in its `template`, `js`, and `css`
|
|
36
|
+
string attributes, each in its own language.
|
|
37
|
+
|
|
38
|
+
Without it, a Citry component in a ` ```python ` block renders the template,
|
|
39
|
+
JS script, and CSS stylesheet as flat string literals. With it, a ` ```citry `
|
|
40
|
+
block colours the `<c-*>` tags, the `{{ ... }}` interpolation, the embedded
|
|
41
|
+
stylesheet, and the embedded script.
|
|
42
|
+
|
|
43
|
+
## Install
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install pygments-citry
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The package registers a `citry` lexer as a Pygments plugin, so any tool that
|
|
50
|
+
resolves lexers by name picks it up once it is installed.
|
|
51
|
+
|
|
52
|
+
## Use it
|
|
53
|
+
|
|
54
|
+
On the command line:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pygmentize -l citry my_component.py
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
In Python:
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from pygments import highlight
|
|
64
|
+
from pygments.formatters import HtmlFormatter
|
|
65
|
+
from pygments.lexers import get_lexer_by_name
|
|
66
|
+
|
|
67
|
+
lexer = get_lexer_by_name("citry")
|
|
68
|
+
html = highlight(source, lexer, HtmlFormatter())
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
In a Markdown site (mkdocs with `pymdownx.highlight` / `pymdownx.superfences`,
|
|
72
|
+
or any python-markdown setup that resolves Pygments lexers by name), tag the
|
|
73
|
+
fence with `citry`:
|
|
74
|
+
|
|
75
|
+
````markdown
|
|
76
|
+
```citry
|
|
77
|
+
from citry import Component
|
|
78
|
+
|
|
79
|
+
class Welcome(Component):
|
|
80
|
+
template = """
|
|
81
|
+
<div class="card">
|
|
82
|
+
<h1>{{ title }}</h1>
|
|
83
|
+
<p c-if="count">You have {{ count }} new messages.</p>
|
|
84
|
+
</div>
|
|
85
|
+
"""
|
|
86
|
+
```
|
|
87
|
+
````
|
|
88
|
+
|
|
89
|
+
No superfences `custom_fences` entry is needed: once the package is installed,
|
|
90
|
+
`citry` resolves like any other Pygments lexer.
|
|
91
|
+
|
|
92
|
+
## What it highlights
|
|
93
|
+
|
|
94
|
+
A `citry` block is highlighted as Python, and the following Citry constructs
|
|
95
|
+
inside the `template` / `js` / `css` attributes are highlighted in their own
|
|
96
|
+
language:
|
|
97
|
+
|
|
98
|
+
- The HTML in `template`, including the `<c-*>` component and control-flow tags
|
|
99
|
+
(the built-ins `c-if`, `c-for`, `c-slot`, `<c-raw>`, and so on) and `c-*`
|
|
100
|
+
dynamic attributes.
|
|
101
|
+
- `{{ ... }}` interpolation, whose body is a Python expression.
|
|
102
|
+
- `{# ... #}` template comments.
|
|
103
|
+
- The JavaScript in `js` and the CSS in `css`.
|
|
104
|
+
|
|
105
|
+
The `<c-raw>...</c-raw>` element is treated as verbatim text, matching the
|
|
106
|
+
engine: `{{ }}` and tags inside it are not interpreted.
|
|
107
|
+
|
|
108
|
+
## Template-only blocks: `citry-html`
|
|
109
|
+
|
|
110
|
+
For a fenced block that shows only a template, with no surrounding Python (just
|
|
111
|
+
`<c-*>` tags, `{{ ... }}`, and `{# ... #}`), tag it `citry-html`:
|
|
112
|
+
|
|
113
|
+
````markdown
|
|
114
|
+
```citry-html
|
|
115
|
+
<c-for each="item in items">
|
|
116
|
+
<li c-if="item.visible">{{ item.label }}</li>
|
|
117
|
+
</c-for>
|
|
118
|
+
```
|
|
119
|
+
````
|
|
120
|
+
|
|
121
|
+
`citry` expects full component code (Python with embedded strings), so a bare
|
|
122
|
+
template belongs in a `citry-html` block instead.
|
|
123
|
+
|
|
124
|
+
## License
|
|
125
|
+
|
|
126
|
+
MIT. Part of the [Citry](https://github.com/citry-dev/citry) project.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
pygments_citry/__init__.py,sha256=jqjp95MlE7Rwt2TGvSS3ouWKlzbS3W0mBoEcqRuYh9U,1432
|
|
2
|
+
pygments_citry/citry_html.py,sha256=qSxLRwqAfN5NKxBbtanxzqzZFdqXd0wiqK4Tky7XqjQ,8357
|
|
3
|
+
pygments_citry/lexers.py,sha256=IU2oGLbDY9WWYTI1GK6p5HCV3cySGzrfooUbjq4wAdU,3130
|
|
4
|
+
pygments_citry/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
pygments_citry-0.1.0.dist-info/licenses/LICENSE,sha256=DWFXareDLD3iWrd8m9GgkBR6iIZC8AzSoaI35Rkr8y8,1063
|
|
6
|
+
pygments_citry-0.1.0.dist-info/METADATA,sha256=ssqsww7vE06fBgC4B27cHdpNRNmU_hOa_LfKhEFCeGc,4153
|
|
7
|
+
pygments_citry-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
8
|
+
pygments_citry-0.1.0.dist-info/entry_points.txt,sha256=_-eEsJ8zqulB42wm3CzbVZWySO-Zr-gVbtMeMChwjIQ,119
|
|
9
|
+
pygments_citry-0.1.0.dist-info/top_level.txt,sha256=AFAUv1A1S3yVGiOWkqgLJabSJRZt_CjOax56ueluKI4,15
|
|
10
|
+
pygments_citry-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 citry
|
|
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.
|
|
22
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pygments_citry
|