1"""
2 pygments.lexers.ul4
3 ~~~~~~~~~~~~~~~~~~~
4
5 Lexer for the UL4 templating language.
6
7 :copyright: Copyright 2006-2025 by the Pygments team, see AUTHORS.
8 :license: BSD, see LICENSE for details.
9"""
10
11import re
12
13from pygments.lexer import RegexLexer, DelegatingLexer, bygroups, words, include
14from pygments.token import Comment, Text, Keyword, String, Number, Literal, \
15 Name, Other, Operator
16from pygments.lexers.web import HtmlLexer, XmlLexer, CssLexer, JavascriptLexer
17from pygments.lexers.python import PythonLexer
18
19__all__ = ['UL4Lexer', 'HTMLUL4Lexer', 'XMLUL4Lexer', 'CSSUL4Lexer',
20 'JavascriptUL4Lexer', 'PythonUL4Lexer']
21
22
23class UL4Lexer(RegexLexer):
24 """
25 Generic lexer for UL4.
26 """
27
28 flags = re.MULTILINE | re.DOTALL
29
30 name = 'UL4'
31 aliases = ['ul4']
32 filenames = ['*.ul4']
33 url = 'https://python.livinglogic.de/UL4.html'
34 version_added = '2.12'
35
36 tokens = {
37 "root": [
38 (
39 # Template header without name:
40 # ``<?ul4?>``
41 r"(<\?)(\s*)(ul4)(\s*)(\?>)",
42 bygroups(Comment.Preproc, Text.Whitespace, Keyword,
43 Text.Whitespace, Comment.Preproc),
44 ),
45 (
46 # Template header with name (potentially followed by the signature):
47 # ``<?ul4 foo(bar=42)?>``
48 r"(<\?)(\s*)(ul4)(\s*)([a-zA-Z_][a-zA-Z_0-9]*)?",
49 bygroups(Comment.Preproc, Text.Whitespace, Keyword,
50 Text.Whitespace, Name.Function),
51 "ul4", # Switch to "expression" mode
52 ),
53 (
54 # Comment:
55 # ``<?note?>...<?end note?>``
56 r"<\?\s*note\s*\?>",
57 Comment,
58 "note", # Switch to "note" mode
59 ),
60 (
61 # Comment:
62 # ``<?note foobar?>``
63 r"<\?\s*note\s.*?\?>",
64 Comment,
65 ),
66 (
67 # Template documentation:
68 # ``<?doc?>...<?end doc?>``
69 r"<\?\s*doc\s*\?>",
70 String.Doc,
71 "doc",
72 ),
73 (
74 # Template documentation:
75 # ``<?doc foobar?>``
76 r"<\?\s*doc\s.*?\?>",
77 String.Doc,
78 ),
79 (
80 # ``<?ignore?>`` tag for commenting out code:
81 # ``<?ignore?>...<?end ignore?>``
82 r"<\?\s*ignore\s*\?>",
83 Comment,
84 "ignore", # Switch to "ignore" mode
85 ),
86 (
87 # ``<?def?>`` tag for defining local templates
88 # ``<?def foo(bar=42)?>...<?end def?>``
89 r"(<\?)(\s*)(def)(\s*)([a-zA-Z_][a-zA-Z_0-9]*)?",
90 bygroups(Comment.Preproc, Text.Whitespace, Keyword,
91 Text.Whitespace, Name.Function),
92 "ul4", # Switch to "expression" mode
93 ),
94 (
95 # The rest of the supported tags
96 r"(<\?)(\s*)(printx|print|for|if|elif|else|while|code|renderblocks?|render)\b",
97 bygroups(Comment.Preproc, Text.Whitespace, Keyword),
98 "ul4", # Switch to "expression" mode
99 ),
100 (
101 # ``<?end?>`` tag for ending ``<?def?>``, ``<?for?>``,
102 # ``<?if?>``, ``<?while?>``, ``<?renderblock?>`` and
103 # ``<?renderblocks?>`` blocks.
104 r"(<\?)(\s*)(end)\b",
105 bygroups(Comment.Preproc, Text.Whitespace, Keyword),
106 "end", # Switch to "end tag" mode
107 ),
108 (
109 # ``<?whitespace?>`` tag for configuring whitespace handlng
110 r"(<\?)(\s*)(whitespace)\b",
111 bygroups(Comment.Preproc, Text.Whitespace, Keyword),
112 "whitespace", # Switch to "whitespace" mode
113 ),
114 # Plain text
115 (r"[^<]+", Other),
116 (r"<", Other),
117 ],
118 # Ignore mode ignores everything upto the matching ``<?end ignore?>`` tag
119 "ignore": [
120 # Nested ``<?ignore?>`` tag
121 (r"<\?\s*ignore\s*\?>", Comment, "#push"),
122 # ``<?end ignore?>`` tag
123 (r"<\?\s*end\s+ignore\s*\?>", Comment, "#pop"),
124 # Everything else
125 (r"[^<]+", Comment),
126 (r".", Comment),
127 ],
128 # Note mode ignores everything upto the matching ``<?end note?>`` tag
129 "note": [
130 # Nested ``<?note?>`` tag
131 (r"<\?\s*note\s*\?>", Comment, "#push"),
132 # ``<?end note?>`` tag
133 (r"<\?\s*end\s+note\s*\?>", Comment, "#pop"),
134 # Everything else
135 (r"[^<]+", Comment),
136 (r".", Comment),
137 ],
138 # Doc mode ignores everything upto the matching ``<?end doc?>`` tag
139 "doc": [
140 # Nested ``<?doc?>`` tag
141 (r"<\?\s*doc\s*\?>", String.Doc, "#push"),
142 # ``<?end doc?>`` tag
143 (r"<\?\s*end\s+doc\s*\?>", String.Doc, "#pop"),
144 # Everything else
145 (r"[^<]+", String.Doc),
146 (r".", String.Doc),
147 ],
148 # UL4 expressions
149 "ul4": [
150 # End the tag
151 (r"\?>", Comment.Preproc, "#pop"),
152 # Start triple quoted string constant
153 ("'''", String, "string13"),
154 ('"""', String, "string23"),
155 # Start single quoted string constant
156 ("'", String, "string1"),
157 ('"', String, "string2"),
158 # Floating point number
159 (r"\d+\.\d*([eE][+-]?\d+)?", Number.Float),
160 (r"\.\d+([eE][+-]?\d+)?", Number.Float),
161 (r"\d+[eE][+-]?\d+", Number.Float),
162 # Binary integer: ``0b101010``
163 (r"0[bB][01]+", Number.Bin),
164 # Octal integer: ``0o52``
165 (r"0[oO][0-7]+", Number.Oct),
166 # Hexadecimal integer: ``0x2a``
167 (r"0[xX][0-9a-fA-F]+", Number.Hex),
168 # Date or datetime: ``@(2000-02-29)``/``@(2000-02-29T12:34:56.987654)``
169 (r"@\(\d\d\d\d-\d\d-\d\d(T(\d\d:\d\d(:\d\d(\.\d{6})?)?)?)?\)", Literal.Date),
170 # Color: ``#fff``, ``#fff8f0`` etc.
171 (r"#[0-9a-fA-F]{8}", Literal.Color),
172 (r"#[0-9a-fA-F]{6}", Literal.Color),
173 (r"#[0-9a-fA-F]{3,4}", Literal.Color),
174 # Decimal integer: ``42``
175 (r"\d+", Number.Integer),
176 # Operators
177 (r"//|==|!=|>=|<=|<<|>>|\+=|-=|\*=|/=|//=|<<=|>>=|&=|\|=|^=|=|[\[\]{},:*/().~%&|<>^+-]", Operator),
178 # Keywords
179 (words(("for", "in", "if", "else", "not", "is", "and", "or"), suffix=r"\b"), Keyword),
180 # Builtin constants
181 (words(("None", "False", "True"), suffix=r"\b"), Keyword.Constant),
182 # Variable names
183 (r"[a-zA-Z_][a-zA-Z0-9_]*", Name),
184 # Whitespace
185 (r"\s+", Text.Whitespace),
186 ],
187 # ``<?end ...?>`` tag for closing the last open block
188 "end": [
189 (r"\?>", Comment.Preproc, "#pop"),
190 (words(("for", "if", "def", "while", "renderblock", "renderblocks"), suffix=r"\b"), Keyword),
191 (r"\s+", Text),
192 ],
193 # Content of the ``<?whitespace ...?>`` tag:
194 # ``keep``, ``strip`` or ``smart``
195 "whitespace": [
196 (r"\?>", Comment.Preproc, "#pop"),
197 (words(("keep", "strip", "smart"), suffix=r"\b"), Comment.Preproc),
198 (r"\s+", Text.Whitespace),
199 ],
200 # Inside a string constant
201 "stringescapes": [
202 (r"""\\[\\'"abtnfr]""", String.Escape),
203 (r"\\x[0-9a-fA-F]{2}", String.Escape),
204 (r"\\u[0-9a-fA-F]{4}", String.Escape),
205 (r"\\U[0-9a-fA-F]{8}", String.Escape),
206 ],
207 # Inside a triple quoted string started with ``'''``
208 "string13": [
209 (r"'''", String, "#pop"),
210 include("stringescapes"),
211 (r"[^\\']+", String),
212 (r'.', String),
213 ],
214 # Inside a triple quoted string started with ``"""``
215 "string23": [
216 (r'"""', String, "#pop"),
217 include("stringescapes"),
218 (r'[^\\"]+', String),
219 (r'.', String),
220 ],
221 # Inside a single quoted string started with ``'``
222 "string1": [
223 (r"'", String, "#pop"),
224 include("stringescapes"),
225 (r"[^\\']+", String),
226 (r'.', String),
227 ],
228 # Inside a single quoted string started with ``"``
229 "string2": [
230 (r'"', String, "#pop"),
231 include("stringescapes"),
232 (r'[^\\"]+', String),
233 (r'.', String),
234 ],
235 }
236
237class HTMLUL4Lexer(DelegatingLexer):
238 """
239 Lexer for UL4 embedded in HTML.
240 """
241
242 name = 'HTML+UL4'
243 aliases = ['html+ul4']
244 filenames = ['*.htmlul4']
245 url = 'https://python.livinglogic.de/UL4.html'
246 version_added = ''
247
248 def __init__(self, **options):
249 super().__init__(HtmlLexer, UL4Lexer, **options)
250
251
252class XMLUL4Lexer(DelegatingLexer):
253 """
254 Lexer for UL4 embedded in XML.
255 """
256
257 name = 'XML+UL4'
258 aliases = ['xml+ul4']
259 filenames = ['*.xmlul4']
260 url = 'https://python.livinglogic.de/UL4.html'
261 version_added = ''
262
263 def __init__(self, **options):
264 super().__init__(XmlLexer, UL4Lexer, **options)
265
266
267class CSSUL4Lexer(DelegatingLexer):
268 """
269 Lexer for UL4 embedded in CSS.
270 """
271
272 name = 'CSS+UL4'
273 aliases = ['css+ul4']
274 filenames = ['*.cssul4']
275 url = 'https://python.livinglogic.de/UL4.html'
276 version_added = ''
277
278 def __init__(self, **options):
279 super().__init__(CssLexer, UL4Lexer, **options)
280
281
282class JavascriptUL4Lexer(DelegatingLexer):
283 """
284 Lexer for UL4 embedded in Javascript.
285 """
286
287 name = 'Javascript+UL4'
288 aliases = ['js+ul4']
289 filenames = ['*.jsul4']
290 url = 'https://python.livinglogic.de/UL4.html'
291 version_added = ''
292
293 def __init__(self, **options):
294 super().__init__(JavascriptLexer, UL4Lexer, **options)
295
296
297class PythonUL4Lexer(DelegatingLexer):
298 """
299 Lexer for UL4 embedded in Python.
300 """
301
302 name = 'Python+UL4'
303 aliases = ['py+ul4']
304 filenames = ['*.pyul4']
305 url = 'https://python.livinglogic.de/UL4.html'
306 version_added = ''
307
308 def __init__(self, **options):
309 super().__init__(PythonLexer, UL4Lexer, **options)