1"""
2 pygments.lexers.configs
3 ~~~~~~~~~~~~~~~~~~~~~~~
4
5 Lexers for configuration file formats.
6
7 :copyright: Copyright 2006-present by the Pygments team, see AUTHORS.
8 :license: BSD, see LICENSE for details.
9"""
10
11import re
12
13from pygments.lexer import ExtendedRegexLexer, RegexLexer, default, words, \
14 bygroups, include, using, this, line_re
15from pygments.token import Text, Comment, Operator, Keyword, Name, String, \
16 Number, Punctuation, Whitespace, Literal, Error, Generic
17from pygments.lexers.shell import BashLexer
18from pygments.lexers.data import JsonLexer
19
20__all__ = ['IniLexer', 'SystemdLexer', 'DesktopLexer', 'RegeditLexer', 'PropertiesLexer',
21 'KconfigLexer', 'Cfengine3Lexer', 'ApacheConfLexer', 'SquidConfLexer',
22 'NginxConfLexer', 'LighttpdConfLexer', 'DockerLexer',
23 'TerraformLexer', 'TermcapLexer', 'TerminfoLexer',
24 'PkgConfigLexer', 'PacmanConfLexer', 'AugeasLexer', 'TOMLLexer',
25 'NestedTextLexer', 'SingularityLexer', 'UnixConfigLexer',
26 'CaddyfileLexer']
27
28
29class IniLexer(RegexLexer):
30 """
31 Lexer for configuration files in INI style.
32 """
33
34 name = 'INI'
35 aliases = ['ini', 'cfg', 'dosini']
36 filenames = [
37 '*.ini', '*.cfg', '*.inf', '.editorconfig',
38 ]
39 mimetypes = ['text/x-ini', 'text/inf']
40 url = 'https://en.wikipedia.org/wiki/INI_file'
41 version_added = ''
42
43 tokens = {
44 'root': [
45 (r'\s+', Whitespace),
46 (r'[;#].*', Comment.Single),
47 (r'(\[.*?\])([ \t]*)$', bygroups(Keyword, Whitespace)),
48 (r'''(.*?)([ \t]*)([=:])([ \t]*)(["'])''',
49 bygroups(Name.Attribute, Whitespace, Operator, Whitespace, String),
50 "quoted_value"),
51 (r'(.*?)([ \t]*)([=:])([ \t]*)([^;#\n]*)(\\)(\s+)',
52 bygroups(Name.Attribute, Whitespace, Operator, Whitespace, String,
53 Text, Whitespace),
54 "value"),
55 (r'(.*?)([ \t]*)([=:])([ \t]*)([^ ;#\n]*(?: +[^ ;#\n]+)*)',
56 bygroups(Name.Attribute, Whitespace, Operator, Whitespace, String)),
57 # standalone option, supported by some INI parsers
58 (r'(.+?)$', Name.Attribute),
59 ],
60 'quoted_value': [
61 (r'''([^"'\n]*)(["'])(\s*)''',
62 bygroups(String, String, Whitespace), "#pop"),
63 (r'[;#].*', Comment.Single),
64 (r'$', String, "#pop"),
65 ],
66 'value': [ # line continuation
67 (r'\s+', Whitespace),
68 (r'(\s*)(.*)(\\)([ \t]*)',
69 bygroups(Whitespace, String, Text, Whitespace)),
70 (r'.*$', String, "#pop"),
71 ],
72 }
73
74 def analyse_text(text):
75 npos = text.find('\n')
76 if npos < 3:
77 return False
78 if text[0] == '[' and text[npos-1] == ']':
79 return 0.8
80 return False
81
82
83class DesktopLexer(RegexLexer):
84 """
85 Lexer for .desktop files.
86 """
87
88 name = 'Desktop file'
89 url = "https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html"
90 aliases = ['desktop']
91 filenames = ['*.desktop']
92 mimetypes = ['application/x-desktop']
93 version_added = '2.16'
94
95 tokens = {
96 'root': [
97 (r'^[ \t]*\n', Whitespace),
98 (r'^(#.*)(\n)', bygroups(Comment.Single, Whitespace)),
99 (r'(\[[^\]\n]+\])(\n)', bygroups(Keyword, Whitespace)),
100 (r'([-A-Za-z0-9]+)(\[[^\] \t=]+\])?([ \t]*)(=)([ \t]*)([^\n]*)([ \t\n]*\n)',
101 bygroups(Name.Attribute, Name.Namespace, Whitespace, Operator, Whitespace, String, Whitespace)),
102 ],
103 }
104
105 def analyse_text(text):
106 if text.startswith("[Desktop Entry]"):
107 return 1.0
108 if re.search(r"^\[Desktop Entry\][ \t]*$", text[:500], re.MULTILINE) is not None:
109 return 0.9
110 return 0.0
111
112
113class SystemdLexer(RegexLexer):
114 """
115 Lexer for systemd unit files.
116 """
117
118 name = 'Systemd'
119 url = "https://www.freedesktop.org/software/systemd/man/systemd.syntax.html"
120 aliases = ['systemd']
121 filenames = [
122 '*.service', '*.socket', '*.device', '*.mount', '*.automount',
123 '*.swap', '*.target', '*.path', '*.timer', '*.slice', '*.scope',
124 ]
125 version_added = '2.16'
126
127 tokens = {
128 'root': [
129 (r'^[ \t]*\n', Whitespace),
130 (r'^([;#].*)(\n)', bygroups(Comment.Single, Whitespace)),
131 (r'(\[[^\]\n]+\])(\n)', bygroups(Keyword, Whitespace)),
132 (r'([^=]+)([ \t]*)(=)([ \t]*)([^\n]*)(\\)(\n)',
133 bygroups(Name.Attribute, Whitespace, Operator, Whitespace, String,
134 Text, Whitespace),
135 "value"),
136 (r'([^=]+)([ \t]*)(=)([ \t]*)([^\n]*)(\n)',
137 bygroups(Name.Attribute, Whitespace, Operator, Whitespace, String, Whitespace)),
138 ],
139 'value': [
140 # line continuation
141 (r'^([;#].*)(\n)', bygroups(Comment.Single, Whitespace)),
142 (r'([ \t]*)([^\n]*)(\\)(\n)',
143 bygroups(Whitespace, String, Text, Whitespace)),
144 (r'([ \t]*)([^\n]*)(\n)',
145 bygroups(Whitespace, String, Whitespace), "#pop"),
146 ],
147 }
148
149 def analyse_text(text):
150 if text.startswith("[Unit]"):
151 return 1.0
152 if re.search(r"^\[Unit\][ \t]*$", text[:500], re.MULTILINE) is not None:
153 return 0.9
154 return 0.0
155
156
157class RegeditLexer(RegexLexer):
158 """
159 Lexer for Windows Registry files produced by regedit.
160 """
161
162 name = 'reg'
163 url = 'http://en.wikipedia.org/wiki/Windows_Registry#.REG_files'
164 aliases = ['registry']
165 filenames = ['*.reg']
166 mimetypes = ['text/x-windows-registry']
167 version_added = '1.6'
168
169 tokens = {
170 'root': [
171 (r'Windows Registry Editor.*', Text),
172 (r'\s+', Whitespace),
173 (r'[;#].*', Comment.Single),
174 (r'(\[)(-?)(HKEY_[A-Z_]+)(.*?\])$',
175 bygroups(Keyword, Operator, Name.Builtin, Keyword)),
176 # String keys, which obey somewhat normal escaping
177 (r'("(?:\\"|\\\\|[^"])+")([ \t]*)(=)([ \t]*)',
178 bygroups(Name.Attribute, Whitespace, Operator, Whitespace),
179 'value'),
180 # Bare keys (includes @)
181 (r'(.*?)([ \t]*)(=)([ \t]*)',
182 bygroups(Name.Attribute, Whitespace, Operator, Whitespace),
183 'value'),
184 ],
185 'value': [
186 (r'-', Operator, '#pop'), # delete value
187 (r'(dword|hex(?:\([0-9a-fA-F]\))?)(:)([0-9a-fA-F,]+)',
188 bygroups(Name.Variable, Punctuation, Number), '#pop'),
189 # As far as I know, .reg files do not support line continuation.
190 (r'.+', String, '#pop'),
191 default('#pop'),
192 ]
193 }
194
195 def analyse_text(text):
196 return text.startswith('Windows Registry Editor')
197
198
199class PropertiesLexer(RegexLexer):
200 """
201 Lexer for configuration files in Java's properties format.
202
203 Note: trailing whitespace counts as part of the value as per spec
204 """
205
206 name = 'Properties'
207 aliases = ['properties', 'jproperties']
208 filenames = ['*.properties']
209 mimetypes = ['text/x-java-properties']
210 url = 'https://en.wikipedia.org/wiki/.properties'
211 version_added = '1.4'
212
213 tokens = {
214 'root': [
215 # comments
216 (r'[!#].*|/{2}.*', Comment.Single),
217 # ending a comment or whitespace-only line
218 (r'\n', Whitespace),
219 # eat whitespace at the beginning of a line
220 (r'^[^\S\n]+', Whitespace),
221 # start lexing a key
222 default('key'),
223 ],
224 'key': [
225 # non-escaped key characters
226 (r'[^\\:=\s]+', Name.Attribute),
227 # escapes
228 include('escapes'),
229 # separator is the first non-escaped whitespace or colon or '=' on the line;
230 # if it's whitespace, = and : are gobbled after it
231 (r'([^\S\n]*)([:=])([^\S\n]*)',
232 bygroups(Whitespace, Operator, Whitespace),
233 ('#pop', 'value')),
234 (r'[^\S\n]+', Whitespace, ('#pop', 'value')),
235 # maybe we got no value after all
236 (r'\n', Whitespace, '#pop'),
237 ],
238 'value': [
239 # non-escaped value characters
240 (r'[^\\\n]+', String),
241 # escapes
242 include('escapes'),
243 # end the value on an unescaped newline
244 (r'\n', Whitespace, '#pop'),
245 ],
246 'escapes': [
247 # line continuations; these gobble whitespace at the beginning of the next line
248 (r'(\\\n)([^\S\n]*)', bygroups(String.Escape, Whitespace)),
249 # other escapes
250 (r'\\(.|\n)', String.Escape),
251 ],
252 }
253
254
255def _rx_indent(level):
256 # Kconfig *always* interprets a tab as 8 spaces, so this is the default.
257 # Edit this if you are in an environment where KconfigLexer gets expanded
258 # input (tabs expanded to spaces) and the expansion tab width is != 8,
259 # e.g. in connection with Trac (trac.ini, [mimeviewer], tab_width).
260 # Value range here is 2 <= {tab_width} <= 8.
261 tab_width = 8
262 # Regex matching a given indentation {level}, assuming that indentation is
263 # a multiple of {tab_width}. In other cases there might be problems.
264 if tab_width == 2:
265 space_repeat = '+'
266 else:
267 space_repeat = '{1,%d}' % (tab_width - 1)
268 if level == 1:
269 level_repeat = ''
270 else:
271 level_repeat = f'{{{level}}}'
272 return rf'(?:\t| {space_repeat}\t| {{{tab_width}}}){level_repeat}.*\n'
273
274
275class KconfigLexer(RegexLexer):
276 """
277 For Linux-style Kconfig files.
278 """
279
280 name = 'Kconfig'
281 aliases = ['kconfig', 'menuconfig', 'linux-config', 'kernel-config']
282 version_added = '1.6'
283 # Adjust this if new kconfig file names appear in your environment
284 filenames = ['Kconfig*', '*Config.in*', 'external.in*',
285 'standard-modules.in']
286 mimetypes = ['text/x-kconfig']
287 url = 'https://www.kernel.org/doc/html/latest/kbuild/kconfig-language.html'
288
289 # No re.MULTILINE, indentation-aware help text needs line-by-line handling
290 flags = 0
291
292 def call_indent(level):
293 # If indentation >= {level} is detected, enter state 'indent{level}'
294 return (_rx_indent(level), String.Doc, f'indent{level}')
295
296 def do_indent(level):
297 # Print paragraphs of indentation level >= {level} as String.Doc,
298 # ignoring blank lines. Then return to 'root' state.
299 return [
300 (_rx_indent(level), String.Doc),
301 (r'\s*\n', Text),
302 default('#pop:2')
303 ]
304
305 tokens = {
306 'root': [
307 (r'\s+', Whitespace),
308 (r'#.*?\n', Comment.Single),
309 (words((
310 'mainmenu', 'config', 'menuconfig', 'choice', 'endchoice',
311 'comment', 'menu', 'endmenu', 'visible if', 'if', 'endif',
312 'source', 'prompt', 'select', 'depends on', 'default',
313 'range', 'option'), suffix=r'\b'),
314 Keyword),
315 (r'(---help---|help)[\t ]*\n', Keyword, 'help'),
316 (r'(bool|tristate|string|hex|int|defconfig_list|modules|env)\b',
317 Name.Builtin),
318 (r'[!=&|]', Operator),
319 (r'[()]', Punctuation),
320 (r'[0-9]+', Number.Integer),
321 (r"'(''|[^'])*'", String.Single),
322 (r'"(""|[^"])*"', String.Double),
323 (r'\S+', Text),
324 ],
325 # Help text is indented, multi-line and ends when a lower indentation
326 # level is detected.
327 'help': [
328 # Skip blank lines after help token, if any
329 (r'\s*\n', Text),
330 # Determine the first help line's indentation level heuristically(!).
331 # Attention: this is not perfect, but works for 99% of "normal"
332 # indentation schemes up to a max. indentation level of 7.
333 call_indent(7),
334 call_indent(6),
335 call_indent(5),
336 call_indent(4),
337 call_indent(3),
338 call_indent(2),
339 call_indent(1),
340 default('#pop'), # for incomplete help sections without text
341 ],
342 # Handle text for indentation levels 7 to 1
343 'indent7': do_indent(7),
344 'indent6': do_indent(6),
345 'indent5': do_indent(5),
346 'indent4': do_indent(4),
347 'indent3': do_indent(3),
348 'indent2': do_indent(2),
349 'indent1': do_indent(1),
350 }
351
352
353class Cfengine3Lexer(RegexLexer):
354 """
355 Lexer for CFEngine3 policy files.
356 """
357
358 name = 'CFEngine3'
359 url = 'http://cfengine.org'
360 aliases = ['cfengine3', 'cf3']
361 filenames = ['*.cf']
362 mimetypes = []
363 version_added = '1.5'
364
365 tokens = {
366 'root': [
367 (r'#.*?\n', Comment),
368 (r'(body)(\s+)(\S+)(\s+)(control)',
369 bygroups(Keyword, Whitespace, Keyword, Whitespace, Keyword)),
370 (r'(body|bundle)(\s+)(\S+)(\s+)(\w+)(\()',
371 bygroups(Keyword, Whitespace, Keyword, Whitespace, Name.Function, Punctuation),
372 'arglist'),
373 (r'(body|bundle)(\s+)(\S+)(\s+)(\w+)',
374 bygroups(Keyword, Whitespace, Keyword, Whitespace, Name.Function)),
375 (r'(")([^"]+)(")(\s+)(string|slist|int|real)(\s*)(=>)(\s*)',
376 bygroups(Punctuation, Name.Variable, Punctuation,
377 Whitespace, Keyword.Type, Whitespace, Operator, Whitespace)),
378 (r'(\S+)(\s*)(=>)(\s*)',
379 bygroups(Keyword.Reserved, Whitespace, Operator, Text)),
380 (r'"', String, 'string'),
381 (r'(\w+)(\()', bygroups(Name.Function, Punctuation)),
382 (r'([\w.!&|()]+)(::)', bygroups(Name.Class, Punctuation)),
383 (r'(\w+)(:)', bygroups(Keyword.Declaration, Punctuation)),
384 (r'@[{(][^)}]+[})]', Name.Variable),
385 (r'[(){},;]', Punctuation),
386 (r'=>', Operator),
387 (r'->', Operator),
388 (r'\d+\.\d+', Number.Float),
389 (r'\d+', Number.Integer),
390 (r'\w+', Name.Function),
391 (r'\s+', Whitespace),
392 ],
393 'string': [
394 (r'\$[{(]', String.Interpol, 'interpol'),
395 (r'\\.', String.Escape),
396 (r'"', String, '#pop'),
397 (r'\n', String),
398 (r'.', String),
399 ],
400 'interpol': [
401 (r'\$[{(]', String.Interpol, '#push'),
402 (r'[})]', String.Interpol, '#pop'),
403 (r'[^${()}]+', String.Interpol),
404 ],
405 'arglist': [
406 (r'\)', Punctuation, '#pop'),
407 (r',', Punctuation),
408 (r'\w+', Name.Variable),
409 (r'\s+', Whitespace),
410 ],
411 }
412
413
414class ApacheConfLexer(RegexLexer):
415 """
416 Lexer for configuration files following the Apache config file
417 format.
418 """
419
420 name = 'ApacheConf'
421 aliases = ['apacheconf', 'aconf', 'apache']
422 filenames = ['.htaccess', 'apache.conf', 'apache2.conf']
423 mimetypes = ['text/x-apacheconf']
424 url = 'https://httpd.apache.org/docs/current/configuring.html'
425 version_added = '0.6'
426 flags = re.MULTILINE | re.IGNORECASE
427
428 tokens = {
429 'root': [
430 (r'\s+', Whitespace),
431 (r'#(.*\\\n)+.*$|(#.*?)$', Comment),
432 (r'(<[^\s>/][^\s>]*)(?:(\s+)(.*))?(>)',
433 bygroups(Name.Tag, Whitespace, String, Name.Tag)),
434 (r'(</[^\s>]+)(>)',
435 bygroups(Name.Tag, Name.Tag)),
436 (r'[a-z]\w*', Name.Builtin, 'value'),
437 (r'\.+', Text),
438 ],
439 'value': [
440 (r'\\\n', Text),
441 (r'\n+', Whitespace, '#pop'),
442 (r'\\', Text),
443 (r'[^\S\n]+', Whitespace),
444 (r'\d+\.\d+\.\d+\.\d+(?:/\d+)?', Number),
445 (r'\d+', Number),
446 (r'/([*a-z0-9][*\w./-]+)', String.Other),
447 (r'(on|off|none|any|all|double|email|dns|min|minimal|'
448 r'os|productonly|full|emerg|alert|crit|error|warn|'
449 r'notice|info|debug|registry|script|inetd|standalone|'
450 r'user|group)\b', Keyword),
451 (r'"([^"\\]*(?:\\(.|\n)[^"\\]*)*)"', String.Double),
452 (r'[^\s"\\]+', Text)
453 ],
454 }
455
456
457class SquidConfLexer(RegexLexer):
458 """
459 Lexer for squid configuration files.
460 """
461
462 name = 'SquidConf'
463 url = 'http://www.squid-cache.org/'
464 aliases = ['squidconf', 'squid.conf', 'squid']
465 filenames = ['squid.conf']
466 mimetypes = ['text/x-squidconf']
467 version_added = '0.9'
468 flags = re.IGNORECASE
469
470 keywords = (
471 "access_log", "acl", "always_direct", "announce_host",
472 "announce_period", "announce_port", "announce_to", "anonymize_headers",
473 "append_domain", "as_whois_server", "auth_param_basic",
474 "authenticate_children", "authenticate_program", "authenticate_ttl",
475 "broken_posts", "buffered_logs", "cache_access_log", "cache_announce",
476 "cache_dir", "cache_dns_program", "cache_effective_group",
477 "cache_effective_user", "cache_host", "cache_host_acl",
478 "cache_host_domain", "cache_log", "cache_mem", "cache_mem_high",
479 "cache_mem_low", "cache_mgr", "cachemgr_passwd", "cache_peer",
480 "cache_peer_access", "cache_replacement_policy", "cache_stoplist",
481 "cache_stoplist_pattern", "cache_store_log", "cache_swap",
482 "cache_swap_high", "cache_swap_log", "cache_swap_low", "client_db",
483 "client_lifetime", "client_netmask", "connect_timeout", "coredump_dir",
484 "dead_peer_timeout", "debug_options", "delay_access", "delay_class",
485 "delay_initial_bucket_level", "delay_parameters", "delay_pools",
486 "deny_info", "dns_children", "dns_defnames", "dns_nameservers",
487 "dns_testnames", "emulate_httpd_log", "err_html_text",
488 "fake_user_agent", "firewall_ip", "forwarded_for", "forward_snmpd_port",
489 "fqdncache_size", "ftpget_options", "ftpget_program", "ftp_list_width",
490 "ftp_passive", "ftp_user", "half_closed_clients", "header_access",
491 "header_replace", "hierarchy_stoplist", "high_response_time_warning",
492 "high_page_fault_warning", "hosts_file", "htcp_port", "http_access",
493 "http_anonymizer", "httpd_accel", "httpd_accel_host",
494 "httpd_accel_port", "httpd_accel_uses_host_header",
495 "httpd_accel_with_proxy", "http_port", "http_reply_access",
496 "icp_access", "icp_hit_stale", "icp_port", "icp_query_timeout",
497 "ident_lookup", "ident_lookup_access", "ident_timeout",
498 "incoming_http_average", "incoming_icp_average", "inside_firewall",
499 "ipcache_high", "ipcache_low", "ipcache_size", "local_domain",
500 "local_ip", "logfile_rotate", "log_fqdn", "log_icp_queries",
501 "log_mime_hdrs", "maximum_object_size", "maximum_single_addr_tries",
502 "mcast_groups", "mcast_icp_query_timeout", "mcast_miss_addr",
503 "mcast_miss_encode_key", "mcast_miss_port", "memory_pools",
504 "memory_pools_limit", "memory_replacement_policy", "mime_table",
505 "min_http_poll_cnt", "min_icp_poll_cnt", "minimum_direct_hops",
506 "minimum_object_size", "minimum_retry_timeout", "miss_access",
507 "negative_dns_ttl", "negative_ttl", "neighbor_timeout",
508 "neighbor_type_domain", "netdb_high", "netdb_low", "netdb_ping_period",
509 "netdb_ping_rate", "never_direct", "no_cache", "passthrough_proxy",
510 "pconn_timeout", "pid_filename", "pinger_program", "positive_dns_ttl",
511 "prefer_direct", "proxy_auth", "proxy_auth_realm", "query_icmp",
512 "quick_abort", "quick_abort_max", "quick_abort_min",
513 "quick_abort_pct", "range_offset_limit", "read_timeout",
514 "redirect_children", "redirect_program",
515 "redirect_rewrites_host_header", "reference_age",
516 "refresh_pattern", "reload_into_ims", "request_body_max_size",
517 "request_size", "request_timeout", "shutdown_lifetime",
518 "single_parent_bypass", "siteselect_timeout", "snmp_access",
519 "snmp_incoming_address", "snmp_port", "source_ping", "ssl_proxy",
520 "store_avg_object_size", "store_objects_per_bucket",
521 "strip_query_terms", "swap_level1_dirs", "swap_level2_dirs",
522 "tcp_incoming_address", "tcp_outgoing_address", "tcp_recv_bufsize",
523 "test_reachability", "udp_hit_obj", "udp_hit_obj_size",
524 "udp_incoming_address", "udp_outgoing_address", "unique_hostname",
525 "unlinkd_program", "uri_whitespace", "useragent_log",
526 "visible_hostname", "wais_relay", "wais_relay_host", "wais_relay_port",
527 )
528
529 opts = (
530 "proxy-only", "weight", "ttl", "no-query", "default", "round-robin",
531 "multicast-responder", "on", "off", "all", "deny", "allow", "via",
532 "parent", "no-digest", "heap", "lru", "realm", "children", "q1", "q2",
533 "credentialsttl", "none", "disable", "offline_toggle", "diskd",
534 )
535
536 actions = (
537 "shutdown", "info", "parameter", "server_list", "client_list",
538 r'squid.conf',
539 )
540
541 actions_stats = (
542 "objects", "vm_objects", "utilization", "ipcache", "fqdncache", "dns",
543 "redirector", "io", "reply_headers", "filedescriptors", "netdb",
544 )
545
546 actions_log = ("status", "enable", "disable", "clear")
547
548 acls = (
549 "url_regex", "urlpath_regex", "referer_regex", "port", "proto",
550 "req_mime_type", "rep_mime_type", "method", "browser", "user", "src",
551 "dst", "time", "dstdomain", "ident", "snmp_community",
552 )
553
554 ipv4_group = r'(\d+|0x[0-9a-f]+)'
555 ipv4 = rf'({ipv4_group}(\.{ipv4_group}){{3}})'
556 ipv6_group = r'([0-9a-f]{0,4})'
557 ipv6 = rf'({ipv6_group}(:{ipv6_group}){{1,7}})'
558 bare_ip = rf'({ipv4}|{ipv6})'
559 # XXX: /integer is a subnet mark, but what is /IP ?
560 # There is no test where it is used.
561 ip = rf'{bare_ip}(/({bare_ip}|\d+))?'
562
563 tokens = {
564 'root': [
565 (r'\s+', Whitespace),
566 (r'#', Comment, 'comment'),
567 (words(keywords, prefix=r'\b', suffix=r'\b'), Keyword),
568 (words(opts, prefix=r'\b', suffix=r'\b'), Name.Constant),
569 # Actions
570 (words(actions, prefix=r'\b', suffix=r'\b'), String),
571 (words(actions_stats, prefix=r'stats/', suffix=r'\b'), String),
572 (words(actions_log, prefix=r'log/', suffix=r'='), String),
573 (words(acls, prefix=r'\b', suffix=r'\b'), Keyword),
574 (ip, Number.Float),
575 (r'(?:\b\d+\b(?:-\b\d+|%)?)', Number),
576 (r'\S+', Text),
577 ],
578 'comment': [
579 (r'\s*TAG:.*', String.Escape, '#pop'),
580 (r'.+', Comment, '#pop'),
581 default('#pop'),
582 ],
583 }
584
585
586class NginxConfLexer(RegexLexer):
587 """
588 Lexer for Nginx configuration files.
589 """
590 name = 'Nginx configuration file'
591 url = 'http://nginx.net/'
592 aliases = ['nginx']
593 filenames = ['nginx.conf']
594 mimetypes = ['text/x-nginx-conf']
595 version_added = '0.11'
596
597 tokens = {
598 'root': [
599 (r'(include)(\s+)([^\s;]+)', bygroups(Keyword, Whitespace, Name)),
600 (r'[^\s;#]+', Keyword, 'stmt'),
601 include('base'),
602 ],
603 'block': [
604 (r'\}', Punctuation, '#pop:2'),
605 (r'[^\s;#]+', Keyword.Namespace, 'stmt'),
606 include('base'),
607 ],
608 'stmt': [
609 (r'\{', Punctuation, 'block'),
610 (r';', Punctuation, '#pop'),
611 include('base'),
612 ],
613 'base': [
614 (r'#.*\n', Comment.Single),
615 (r'on|off', Name.Constant),
616 (r'\$[^\s;#()]+', Name.Variable),
617 (r'([a-z0-9.-]+)(:)([0-9]+)',
618 bygroups(Name, Punctuation, Number.Integer)),
619 (r'[a-z-]+/[a-z-+]+', String), # mimetype
620 # (r'[a-zA-Z._-]+', Keyword),
621 (r'[0-9]+[km]?\b', Number.Integer),
622 (r'(~)(\s*)([^\s{]+)', bygroups(Punctuation, Whitespace, String.Regex)),
623 (r'[:=~]', Punctuation),
624 (r'[^\s;#{}$]+', String), # catch all
625 (r'/[^\s;#]*', Name), # pathname
626 (r'\s+', Whitespace),
627 (r'[$;]', Text), # leftover characters
628 ],
629 }
630
631
632class LighttpdConfLexer(RegexLexer):
633 """
634 Lexer for Lighttpd configuration files.
635 """
636 name = 'Lighttpd configuration file'
637 url = 'http://lighttpd.net/'
638 aliases = ['lighttpd', 'lighty']
639 filenames = ['lighttpd.conf']
640 mimetypes = ['text/x-lighttpd-conf']
641 version_added = '0.11'
642
643 tokens = {
644 'root': [
645 (r'#.*\n', Comment.Single),
646 (r'/\S*', Name), # pathname
647 (r'[a-zA-Z._-]+', Keyword),
648 (r'\d+\.\d+\.\d+\.\d+(?:/\d+)?', Number),
649 (r'[0-9]+', Number),
650 (r'=>|=~|\+=|==|=|\+', Operator),
651 (r'\$[A-Z]+', Name.Builtin),
652 (r'[(){}\[\],]', Punctuation),
653 (r'"([^"\\]*(?:\\.[^"\\]*)*)"', String.Double),
654 (r'\s+', Whitespace),
655 ],
656
657 }
658
659
660class DockerLexer(RegexLexer):
661 """
662 Lexer for Docker configuration files.
663 """
664 name = 'Docker'
665 url = 'http://docker.io'
666 aliases = ['docker', 'dockerfile']
667 filenames = ['Dockerfile', '*.docker']
668 mimetypes = ['text/x-dockerfile-config']
669 version_added = '2.0'
670
671 _keywords = (r'(?:MAINTAINER|EXPOSE|WORKDIR|USER|STOPSIGNAL)')
672 _bash_keywords = (r'(?:RUN|CMD|ENTRYPOINT|ENV|ARG|LABEL|ADD|COPY)')
673 _lb = r'(?:\s*\\?\s*)' # dockerfile line break regex
674 flags = re.IGNORECASE | re.MULTILINE
675
676 tokens = {
677 'root': [
678 (r'#.*', Comment),
679 (r'(FROM)([ \t]*)(\S*)([ \t]*)(?:(AS)([ \t]*)(\S*))?',
680 bygroups(Keyword, Whitespace, String, Whitespace, Keyword, Whitespace, String)),
681 (rf'(ONBUILD)(\s+)({_lb})', bygroups(Keyword, Whitespace, using(BashLexer))),
682 (rf'(HEALTHCHECK)(\s+)(({_lb}--\w+=\w+{_lb})*)',
683 bygroups(Keyword, Whitespace, using(BashLexer))),
684 (rf'(VOLUME|ENTRYPOINT|CMD|SHELL)(\s+)({_lb})(\[.*?\])',
685 bygroups(Keyword, Whitespace, using(BashLexer), using(JsonLexer))),
686 (rf'(LABEL|ENV|ARG)(\s+)(({_lb}\w+=\w+{_lb})*)',
687 bygroups(Keyword, Whitespace, using(BashLexer))),
688 (rf'({_keywords}|VOLUME)\b(\s+)(.*)', bygroups(Keyword, Whitespace, String)),
689 (rf'({_bash_keywords})(\s+)', bygroups(Keyword, Whitespace)),
690 (r'(.*\\\n)*.+', using(BashLexer)),
691 ]
692 }
693
694
695class TerraformLexer(ExtendedRegexLexer):
696 """
697 Lexer for terraformi ``.tf`` files.
698 """
699
700 name = 'Terraform'
701 url = 'https://www.terraform.io/'
702 aliases = ['terraform', 'tf', 'hcl']
703 filenames = ['*.tf', '*.hcl']
704 mimetypes = ['application/x-tf', 'application/x-terraform']
705 version_added = '2.1'
706
707 classes = ('backend', 'data', 'module', 'output', 'provider',
708 'provisioner', 'resource', 'variable')
709 classes_re = "({})".format(('|').join(classes))
710
711 types = ('string', 'number', 'bool', 'list', 'tuple', 'map', 'set', 'object', 'null')
712
713 numeric_functions = ('abs', 'ceil', 'floor', 'log', 'max',
714 'mix', 'parseint', 'pow', 'signum')
715
716 string_functions = ('chomp', 'format', 'formatlist', 'indent',
717 'join', 'lower', 'regex', 'regexall', 'replace',
718 'split', 'strrev', 'substr', 'title', 'trim',
719 'trimprefix', 'trimsuffix', 'trimspace', 'upper'
720 )
721
722 collection_functions = ('alltrue', 'anytrue', 'chunklist', 'coalesce',
723 'coalescelist', 'compact', 'concat', 'contains',
724 'distinct', 'element', 'flatten', 'index', 'keys',
725 'length', 'list', 'lookup', 'map', 'matchkeys',
726 'merge', 'range', 'reverse', 'setintersection',
727 'setproduct', 'setsubtract', 'setunion', 'slice',
728 'sort', 'sum', 'transpose', 'values', 'zipmap'
729 )
730
731 encoding_functions = ('base64decode', 'base64encode', 'base64gzip',
732 'csvdecode', 'jsondecode', 'jsonencode', 'textdecodebase64',
733 'textencodebase64', 'urlencode', 'yamldecode', 'yamlencode')
734
735 filesystem_functions = ('abspath', 'dirname', 'pathexpand', 'basename',
736 'file', 'fileexists', 'fileset', 'filebase64', 'templatefile')
737
738 date_time_functions = ('formatdate', 'timeadd', 'timestamp')
739
740 hash_crypto_functions = ('base64sha256', 'base64sha512', 'bcrypt', 'filebase64sha256',
741 'filebase64sha512', 'filemd5', 'filesha1', 'filesha256', 'filesha512',
742 'md5', 'rsadecrypt', 'sha1', 'sha256', 'sha512', 'uuid', 'uuidv5')
743
744 ip_network_functions = ('cidrhost', 'cidrnetmask', 'cidrsubnet', 'cidrsubnets')
745
746 type_conversion_functions = ('can', 'defaults', 'tobool', 'tolist', 'tomap',
747 'tonumber', 'toset', 'tostring', 'try')
748
749 builtins = numeric_functions + string_functions + collection_functions + encoding_functions +\
750 filesystem_functions + date_time_functions + hash_crypto_functions + ip_network_functions +\
751 type_conversion_functions
752 builtins_re = "({})".format(('|').join(builtins))
753
754 def heredoc_callback(self, match, ctx):
755 # Parse a terraform heredoc
756 # match: 1 = <<[-]?, 2 = name 3 = rest of line
757
758 start = match.start(1)
759 yield start, Operator, match.group(1) # <<[-]?
760 yield match.start(2), String.Delimiter, match.group(2) # heredoc name
761
762 ctx.pos = match.start(3)
763 ctx.end = match.end(3)
764 yield ctx.pos, String.Heredoc, match.group(3)
765 ctx.pos = match.end()
766
767 hdname = match.group(2)
768 tolerant = True # leading whitespace is always accepted
769
770 lines = []
771
772 for match in line_re.finditer(ctx.text, ctx.pos):
773 if tolerant:
774 check = match.group().strip()
775 else:
776 check = match.group().rstrip()
777 if check == hdname:
778 for amatch in lines:
779 yield amatch.start(), String.Heredoc, amatch.group()
780 yield match.start(), String.Delimiter, match.group()
781 ctx.pos = match.end()
782 break
783 else:
784 lines.append(match)
785 else:
786 # end of heredoc not found -- error!
787 for amatch in lines:
788 yield amatch.start(), Error, amatch.group()
789 ctx.end = len(ctx.text)
790
791 tokens = {
792 'root': [
793 include('basic'),
794 include('whitespace'),
795
796 # Strings
797 (r'(".*")', bygroups(String.Double)),
798
799 # Constants
800 (words(('true', 'false'), prefix=r'\b', suffix=r'\b'), Name.Constant),
801
802 # Types
803 (words(types, prefix=r'\b', suffix=r'\b'), Keyword.Type),
804
805 include('identifier'),
806 include('punctuation'),
807 (r'[0-9]+', Number),
808 ],
809 'basic': [
810 (r'\s*/\*', Comment.Multiline, 'comment'),
811 (r'\s*(#|//).*\n', Comment.Single),
812 include('whitespace'),
813
814 # e.g. terraform {
815 # e.g. egress {
816 (r'(\s*)([0-9a-zA-Z-_]+)(\s*)(=?)(\s*)(\{)',
817 bygroups(Whitespace, Name.Builtin, Whitespace, Operator, Whitespace, Punctuation)),
818
819 # Assignment with attributes, e.g. something = ...
820 (r'(\s*)([0-9a-zA-Z-_]+)(\s*)(=)(\s*)',
821 bygroups(Whitespace, Name.Attribute, Whitespace, Operator, Whitespace)),
822
823 # Assignment with environment variables and similar, e.g. "something" = ...
824 # or key value assignment, e.g. "SlotName" : ...
825 (r'(\s*)("\S+")(\s*)([=:])(\s*)',
826 bygroups(Whitespace, Literal.String.Double, Whitespace, Operator, Whitespace)),
827
828 # Functions, e.g. jsonencode(element("value"))
829 (builtins_re + r'(\()', bygroups(Name.Function, Punctuation)),
830
831 # List of attributes, e.g. ignore_changes = [last_modified, filename]
832 (r'(\[)([a-z_,\s]+)(\])', bygroups(Punctuation, Name.Builtin, Punctuation)),
833
834 # e.g. resource "aws_security_group" "allow_tls" {
835 # e.g. backend "consul" {
836 (classes_re + r'(\s+)("[0-9a-zA-Z-_]+")?(\s*)("[0-9a-zA-Z-_]+")(\s+)(\{)',
837 bygroups(Keyword.Reserved, Whitespace, Name.Class, Whitespace, Name.Variable, Whitespace, Punctuation)),
838
839 # here-doc style delimited strings
840 (r'(<<-?)\s*([a-zA-Z_]\w*)(.*?\n)', heredoc_callback),
841 ],
842 'identifier': [
843 (r'\b(var\.[0-9a-zA-Z-_\.\[\]]+)\b', bygroups(Name.Variable)),
844 (r'\b([0-9a-zA-Z-_\[\]]+\.[0-9a-zA-Z-_\.\[\]]+)\b',
845 bygroups(Name.Variable)),
846 ],
847 'punctuation': [
848 (r'[\[\]()\{\},.?:!=]', Punctuation),
849 ],
850 'comment': [
851 (r'[^*/]', Comment.Multiline),
852 (r'/\*', Comment.Multiline, '#push'),
853 (r'\*/', Comment.Multiline, '#pop'),
854 (r'[*/]', Comment.Multiline)
855 ],
856 'whitespace': [
857 (r'\n', Whitespace),
858 (r'\s+', Whitespace),
859 (r'(\\)(\n)', bygroups(Text, Whitespace)),
860 ],
861 }
862
863
864class TermcapLexer(RegexLexer):
865 """
866 Lexer for termcap database source.
867
868 This is very simple and minimal.
869 """
870 name = 'Termcap'
871 aliases = ['termcap']
872 filenames = ['termcap', 'termcap.src']
873 mimetypes = []
874 url = 'https://en.wikipedia.org/wiki/Termcap'
875 version_added = '2.1'
876
877 # NOTE:
878 # * multiline with trailing backslash
879 # * separator is ':'
880 # * to embed colon as data, we must use \072
881 # * space after separator is not allowed (mayve)
882 tokens = {
883 'root': [
884 (r'^#.*', Comment),
885 (r'^[^\s#:|]+', Name.Tag, 'names'),
886 (r'\s+', Whitespace),
887 ],
888 'names': [
889 (r'\n', Whitespace, '#pop'),
890 (r':', Punctuation, 'defs'),
891 (r'\|', Punctuation),
892 (r'[^:|]+', Name.Attribute),
893 ],
894 'defs': [
895 (r'(\\)(\n[ \t]*)', bygroups(Text, Whitespace)),
896 (r'\n[ \t]*', Whitespace, '#pop:2'),
897 (r'(#)([0-9]+)', bygroups(Operator, Number)),
898 (r'=', Operator, 'data'),
899 (r':', Punctuation),
900 (r'[^\s:=#]+', Name.Class),
901 ],
902 'data': [
903 (r'\\072', Literal),
904 (r':', Punctuation, '#pop'),
905 (r'[^:\\]+', Literal), # for performance
906 (r'.', Literal),
907 ],
908 }
909
910
911class TerminfoLexer(RegexLexer):
912 """
913 Lexer for terminfo database source.
914
915 This is very simple and minimal.
916 """
917 name = 'Terminfo'
918 aliases = ['terminfo']
919 filenames = ['terminfo', 'terminfo.src']
920 mimetypes = []
921 url = 'https://en.wikipedia.org/wiki/Terminfo'
922 version_added = '2.1'
923
924 # NOTE:
925 # * multiline with leading whitespace
926 # * separator is ','
927 # * to embed comma as data, we can use \,
928 # * space after separator is allowed
929 tokens = {
930 'root': [
931 (r'^#.*$', Comment),
932 (r'^[^\s#,|]+', Name.Tag, 'names'),
933 (r'\s+', Whitespace),
934 ],
935 'names': [
936 (r'\n', Whitespace, '#pop'),
937 (r'(,)([ \t]*)', bygroups(Punctuation, Whitespace), 'defs'),
938 (r'\|', Punctuation),
939 (r'[^,|]+', Name.Attribute),
940 ],
941 'defs': [
942 (r'\n[ \t]+', Whitespace),
943 (r'\n', Whitespace, '#pop:2'),
944 (r'(#)([0-9]+)', bygroups(Operator, Number)),
945 (r'=', Operator, 'data'),
946 (r'(,)([ \t]*)', bygroups(Punctuation, Whitespace)),
947 (r'[^\s,=#]+', Name.Class),
948 ],
949 'data': [
950 (r'\\[,\\]', Literal),
951 (r'(,)([ \t]*)', bygroups(Punctuation, Whitespace), '#pop'),
952 (r'[^\\,]+', Literal), # for performance
953 (r'.', Literal),
954 ],
955 }
956
957
958class PkgConfigLexer(RegexLexer):
959 """
960 Lexer for pkg-config
961 (see also `manual page <http://linux.die.net/man/1/pkg-config>`_).
962 """
963
964 name = 'PkgConfig'
965 url = 'http://www.freedesktop.org/wiki/Software/pkg-config/'
966 aliases = ['pkgconfig']
967 filenames = ['*.pc']
968 mimetypes = []
969 version_added = '2.1'
970
971 tokens = {
972 'root': [
973 (r'#.*$', Comment.Single),
974
975 # variable definitions
976 (r'^(\w+)(=)', bygroups(Name.Attribute, Operator)),
977
978 # keyword lines
979 (r'^([\w.]+)(:)',
980 bygroups(Name.Tag, Punctuation), 'spvalue'),
981
982 # variable references
983 include('interp'),
984
985 # fallback
986 (r'\s+', Whitespace),
987 (r'[^${}#=:\n.]+', Text),
988 (r'.', Text),
989 ],
990 'interp': [
991 # you can escape literal "$" as "$$"
992 (r'\$\$', Text),
993
994 # variable references
995 (r'\$\{', String.Interpol, 'curly'),
996 ],
997 'curly': [
998 (r'\}', String.Interpol, '#pop'),
999 (r'\w+', Name.Attribute),
1000 ],
1001 'spvalue': [
1002 include('interp'),
1003
1004 (r'#.*$', Comment.Single, '#pop'),
1005 (r'\n', Whitespace, '#pop'),
1006
1007 # fallback
1008 (r'\s+', Whitespace),
1009 (r'[^${}#\n\s]+', Text),
1010 (r'.', Text),
1011 ],
1012 }
1013
1014
1015class PacmanConfLexer(RegexLexer):
1016 """
1017 Lexer for pacman.conf.
1018
1019 Actually, IniLexer works almost fine for this format,
1020 but it yield error token. It is because pacman.conf has
1021 a form without assignment like:
1022
1023 UseSyslog
1024 Color
1025 TotalDownload
1026 CheckSpace
1027 VerbosePkgLists
1028
1029 These are flags to switch on.
1030 """
1031
1032 name = 'PacmanConf'
1033 url = 'https://www.archlinux.org/pacman/pacman.conf.5.html'
1034 aliases = ['pacmanconf']
1035 filenames = ['pacman.conf']
1036 mimetypes = []
1037 version_added = '2.1'
1038
1039 tokens = {
1040 'root': [
1041 # comment
1042 (r'#.*$', Comment.Single),
1043
1044 # section header
1045 (r'^(\s*)(\[.*?\])(\s*)$', bygroups(Whitespace, Keyword, Whitespace)),
1046
1047 # variable definitions
1048 # (Leading space is allowed...)
1049 (r'(\w+)(\s*)(=)',
1050 bygroups(Name.Attribute, Whitespace, Operator)),
1051
1052 # flags to on
1053 (r'^(\s*)(\w+)(\s*)$',
1054 bygroups(Whitespace, Name.Attribute, Whitespace)),
1055
1056 # built-in special values
1057 (words((
1058 '$repo', # repository
1059 '$arch', # architecture
1060 '%o', # outfile
1061 '%u', # url
1062 ), suffix=r'\b'),
1063 Name.Variable),
1064
1065 # fallback
1066 (r'\s+', Whitespace),
1067 (r'.', Text),
1068 ],
1069 }
1070
1071
1072class AugeasLexer(RegexLexer):
1073 """
1074 Lexer for Augeas.
1075 """
1076 name = 'Augeas'
1077 url = 'http://augeas.net'
1078 aliases = ['augeas']
1079 filenames = ['*.aug']
1080 version_added = '2.4'
1081
1082 tokens = {
1083 'root': [
1084 (r'(module)(\s*)([^\s=]+)', bygroups(Keyword.Namespace, Whitespace, Name.Namespace)),
1085 (r'(let)(\s*)([^\s=]+)', bygroups(Keyword.Declaration, Whitespace, Name.Variable)),
1086 (r'(del|store|value|counter|seq|key|label|autoload|incl|excl|transform|test|get|put)(\s+)', bygroups(Name.Builtin, Whitespace)),
1087 (r'(\()([^:]+)(\:)(unit|string|regexp|lens|tree|filter)(\))', bygroups(Punctuation, Name.Variable, Punctuation, Keyword.Type, Punctuation)),
1088 (r'\(\*', Comment.Multiline, 'comment'),
1089 (r'[*+\-.;=?|]', Operator),
1090 (r'[()\[\]{}]', Operator),
1091 (r'"', String.Double, 'string'),
1092 (r'\/', String.Regex, 'regex'),
1093 (r'([A-Z]\w*)(\.)(\w+)', bygroups(Name.Namespace, Punctuation, Name.Variable)),
1094 (r'.', Name.Variable),
1095 (r'\s+', Whitespace),
1096 ],
1097 'string': [
1098 (r'\\.', String.Escape),
1099 (r'[^"]', String.Double),
1100 (r'"', String.Double, '#pop'),
1101 ],
1102 'regex': [
1103 (r'\\.', String.Escape),
1104 (r'[^/]', String.Regex),
1105 (r'\/', String.Regex, '#pop'),
1106 ],
1107 'comment': [
1108 (r'[^*)]', Comment.Multiline),
1109 (r'\(\*', Comment.Multiline, '#push'),
1110 (r'\*\)', Comment.Multiline, '#pop'),
1111 (r'[)*]', Comment.Multiline)
1112 ],
1113 }
1114
1115
1116class TOMLLexer(RegexLexer):
1117 """
1118 Lexer for TOML, a simple language for config files.
1119 """
1120
1121 name = 'TOML'
1122 aliases = ['toml']
1123 filenames = ['*.toml', 'Pipfile', 'poetry.lock']
1124 mimetypes = ['application/toml']
1125 url = 'https://toml.io'
1126 version_added = '2.4'
1127
1128 # Based on the TOML spec: https://toml.io/en/v1.0.0
1129
1130 # The following is adapted from CPython's tomllib:
1131 _time = r"\d\d:\d\d(:\d\d(\.\d+)?)?"
1132 _datetime = rf"""(?x)
1133 \d\d\d\d-\d\d-\d\d # date, e.g., 1988-10-27
1134 (
1135 [Tt ] {_time} # optional time
1136 (
1137 [Zz]|[+-]\d\d:\d\d # optional time offset
1138 )?
1139 )?
1140 """
1141
1142 tokens = {
1143 'root': [
1144 # Note that we make an effort in order to distinguish
1145 # moments at which we're parsing a key and moments at
1146 # which we're parsing a value. In the TOML code
1147 #
1148 # 1234 = 1234
1149 #
1150 # the first "1234" should be Name, the second Integer.
1151
1152 # Whitespace
1153 (r'\s+', Whitespace),
1154
1155 # Comment
1156 (r'#.*', Comment.Single),
1157
1158 # Assignment keys
1159 include('key'),
1160
1161 # After "=", find a value
1162 (r'(=)(\s*)', bygroups(Operator, Whitespace), 'value'),
1163
1164 # Table header
1165 (r'\[\[?', Keyword, 'table-key'),
1166 ],
1167 'key': [
1168 # Start of bare key (only ASCII is allowed here).
1169 (r'[A-Za-z0-9_-]+', Name),
1170 # Quoted key
1171 (r'"', String.Double, 'basic-string'),
1172 (r"'", String.Single, 'literal-string'),
1173 # Dots act as separators in keys
1174 (r'\.', Punctuation),
1175 ],
1176 'table-key': [
1177 # This is like 'key', but highlights the name components
1178 # and separating dots as Keyword because it looks better
1179 # when the whole table header is Keyword. We do highlight
1180 # strings as strings though.
1181 # Start of bare key (only ASCII is allowed here).
1182 (r'[A-Za-z0-9_-]+', Keyword),
1183 (r'"', String.Double, 'basic-string'),
1184 (r"'", String.Single, 'literal-string'),
1185 (r'\.', Keyword),
1186 (r'\]\]?', Keyword, '#pop'),
1187
1188 # Inline whitespace allowed
1189 (r'[ \t]+', Whitespace),
1190 ],
1191 'value': [
1192 # Datetime, baretime
1193 (_datetime, Literal.Date, '#pop'),
1194 (_time, Literal.Date, '#pop'),
1195
1196 # Recognize as float if there is a fractional part
1197 # and/or an exponent.
1198 (r'[+-]?\d[0-9_]*[eE][+-]?\d[0-9_]*', Number.Float, '#pop'),
1199 (r'[+-]?\d[0-9_]*\.\d[0-9_]*([eE][+-]?\d[0-9_]*)?',
1200 Number.Float, '#pop'),
1201
1202 # Infinities and NaN
1203 (r'[+-]?(inf|nan)', Number.Float, '#pop'),
1204
1205 # Integers
1206 (r'-?0b[01_]+', Number.Bin, '#pop'),
1207 (r'-?0o[0-7_]+', Number.Oct, '#pop'),
1208 (r'-?0x[0-9a-fA-F_]+', Number.Hex, '#pop'),
1209 (r'[+-]?[0-9_]+', Number.Integer, '#pop'),
1210
1211 # Strings
1212 (r'"""', String.Double, ('#pop', 'multiline-basic-string')),
1213 (r'"', String.Double, ('#pop', 'basic-string')),
1214 (r"'''", String.Single, ('#pop', 'multiline-literal-string')),
1215 (r"'", String.Single, ('#pop', 'literal-string')),
1216
1217 # Booleans
1218 (r'true|false', Keyword.Constant, '#pop'),
1219
1220 # Start of array
1221 (r'\[', Punctuation, ('#pop', 'array')),
1222
1223 # Start of inline table
1224 (r'\{', Punctuation, ('#pop', 'inline-table')),
1225 ],
1226 'array': [
1227 # Whitespace, including newlines, is ignored inside arrays,
1228 # and comments are allowed.
1229 (r'\s+', Whitespace),
1230 (r'#.*', Comment.Single),
1231
1232 # Delimiters
1233 (r',', Punctuation),
1234
1235 # End of array
1236 (r'\]', Punctuation, '#pop'),
1237
1238 # Parse a value and come back
1239 default('value'),
1240 ],
1241 'inline-table': [
1242 # Whitespace (since TOML 1.1.0, same as in array)
1243 (r'\s+', Whitespace),
1244 (r'#.*', Comment.Single),
1245
1246 # Keys
1247 include('key'),
1248
1249 # Values
1250 (r'(=)(\s*)', bygroups(Punctuation, Whitespace), 'value'),
1251
1252 # Delimiters
1253 (r',', Punctuation),
1254
1255 # End of inline table
1256 (r'\}', Punctuation, '#pop'),
1257 ],
1258 'basic-string': [
1259 (r'"', String.Double, '#pop'),
1260 include('escapes'),
1261 (r'[^"\\]+', String.Double),
1262 ],
1263 'literal-string': [
1264 (r".*?'", String.Single, '#pop'),
1265 ],
1266 'multiline-basic-string': [
1267 (r'"""', String.Double, '#pop'),
1268 (r'(\\)(\n)', bygroups(String.Escape, Whitespace)),
1269 include('escapes'),
1270 (r'[^"\\]+', String.Double),
1271 (r'"', String.Double),
1272 ],
1273 'multiline-literal-string': [
1274 (r"'''", String.Single, '#pop'),
1275 (r"[^']+", String.Single),
1276 (r"'", String.Single),
1277 ],
1278 'escapes': [
1279 (r'\\x[0-9a-fA-F]{2}|\\u[0-9a-fA-F]{4}|\\U[0-9a-fA-F]{8}', String.Escape),
1280 (r'\\.', String.Escape),
1281 ],
1282 }
1283
1284class NestedTextLexer(RegexLexer):
1285 """
1286 Lexer for *NextedText*, a human-friendly data format.
1287
1288 .. versionchanged:: 2.16
1289 Added support for *NextedText* v3.0.
1290 """
1291
1292 name = 'NestedText'
1293 url = 'https://nestedtext.org'
1294 aliases = ['nestedtext', 'nt']
1295 filenames = ['*.nt']
1296 version_added = '2.9'
1297
1298 tokens = {
1299 'root': [
1300 # Comment: # ...
1301 (r'^([ ]*)(#.*)$', bygroups(Whitespace, Comment)),
1302
1303 # Inline dictionary: {...}
1304 (r'^([ ]*)(\{)', bygroups(Whitespace, Punctuation), 'inline_dict'),
1305
1306 # Inline list: [...]
1307 (r'^([ ]*)(\[)', bygroups(Whitespace, Punctuation), 'inline_list'),
1308
1309 # empty multiline string item: >
1310 (r'^([ ]*)(>)$', bygroups(Whitespace, Punctuation)),
1311
1312 # multiline string item: > ...
1313 (r'^([ ]*)(>)( )(.*?)([ \t]*)$', bygroups(Whitespace, Punctuation, Whitespace, Text, Whitespace)),
1314
1315 # empty list item: -
1316 (r'^([ ]*)(-)$', bygroups(Whitespace, Punctuation)),
1317
1318 # list item: - ...
1319 (r'^([ ]*)(-)( )(.*?)([ \t]*)$', bygroups(Whitespace, Punctuation, Whitespace, Text, Whitespace)),
1320
1321 # empty multiline key item: :
1322 (r'^([ ]*)(:)$', bygroups(Whitespace, Punctuation)),
1323
1324 # multiline key item: : ...
1325 (r'^([ ]*)(:)( )([^\n]*?)([ \t]*)$', bygroups(Whitespace, Punctuation, Whitespace, Name.Tag, Whitespace)),
1326
1327 # empty dict key item: ...:
1328 (r'^([ ]*)([^\{\[\s].*?)(:)$', bygroups(Whitespace, Name.Tag, Punctuation)),
1329
1330 # dict key item: ...: ...
1331 (r'^([ ]*)([^\{\[\s].*?)(:)( )(.*?)([ \t]*)$', bygroups(Whitespace, Name.Tag, Punctuation, Whitespace, Text, Whitespace)),
1332 ],
1333 'inline_list': [
1334 include('whitespace'),
1335 (r'[^\{\}\[\],\s]+', Text),
1336 include('inline_value'),
1337 (r',', Punctuation),
1338 (r'\]', Punctuation, '#pop'),
1339 (r'\n', Error, '#pop'),
1340 ],
1341 'inline_dict': [
1342 include('whitespace'),
1343 (r'[^\{\}\[\],:\s]+', Name.Tag),
1344 (r':', Punctuation, 'inline_dict_value'),
1345 (r'\}', Punctuation, '#pop'),
1346 (r'\n', Error, '#pop'),
1347 ],
1348 'inline_dict_value': [
1349 include('whitespace'),
1350 (r'[^\{\}\[\],:\s]+', Text),
1351 include('inline_value'),
1352 (r',', Punctuation, '#pop'),
1353 (r'\}', Punctuation, '#pop:2'),
1354 ],
1355 'inline_value': [
1356 include('whitespace'),
1357 (r'\{', Punctuation, 'inline_dict'),
1358 (r'\[', Punctuation, 'inline_list'),
1359 ],
1360 'whitespace': [
1361 (r'[ \t]+', Whitespace),
1362 ],
1363 }
1364
1365
1366class SingularityLexer(RegexLexer):
1367 """
1368 Lexer for Singularity definition files.
1369 """
1370
1371 name = 'Singularity'
1372 url = 'https://www.sylabs.io/guides/3.0/user-guide/definition_files.html'
1373 aliases = ['singularity']
1374 filenames = ['*.def', 'Singularity']
1375 version_added = '2.6'
1376 flags = re.IGNORECASE | re.MULTILINE | re.DOTALL
1377
1378 _headers = r'^(\s*)(bootstrap|from|osversion|mirrorurl|include|registry|namespace|includecmd)(:)'
1379 _section = r'^(%(?:pre|post|setup|environment|help|labels|test|runscript|files|startscript))(\s*)'
1380 _appsect = r'^(%app(?:install|help|run|labels|env|test|files))(\s*)'
1381
1382 tokens = {
1383 'root': [
1384 (_section, bygroups(Generic.Heading, Whitespace), 'script'),
1385 (_appsect, bygroups(Generic.Heading, Whitespace), 'script'),
1386 (_headers, bygroups(Whitespace, Keyword, Text)),
1387 (r'\s*#.*?\n', Comment),
1388 (r'\b(([0-9]+\.?[0-9]*)|(\.[0-9]+))\b', Number),
1389 (r'[ \t]+', Whitespace),
1390 (r'(?!^\s*%).', Text),
1391 ],
1392 'script': [
1393 (r'(.+?(?=^\s*%))|(.*)', using(BashLexer), '#pop'),
1394 ],
1395 }
1396
1397 def analyse_text(text):
1398 """This is a quite simple script file, but there are a few keywords
1399 which seem unique to this language."""
1400 result = 0
1401 if re.search(r'\b(?:osversion|includecmd|mirrorurl)\b', text, re.IGNORECASE):
1402 result += 0.5
1403
1404 if re.search(SingularityLexer._section[1:], text):
1405 result += 0.49
1406
1407 return result
1408
1409
1410class UnixConfigLexer(RegexLexer):
1411 """
1412 Lexer for Unix/Linux config files using colon-separated values, e.g.
1413
1414 * ``/etc/group``
1415 * ``/etc/passwd``
1416 * ``/etc/shadow``
1417 """
1418
1419 name = 'Unix/Linux config files'
1420 aliases = ['unixconfig', 'linuxconfig']
1421 filenames = []
1422 url = 'https://en.wikipedia.org/wiki/Configuration_file#Unix_and_Unix-like_operating_systems'
1423 version_added = '2.12'
1424
1425 tokens = {
1426 'root': [
1427 (r'^#.*', Comment),
1428 (r'\n', Whitespace),
1429 (r':', Punctuation),
1430 (r'[0-9]+', Number),
1431 (r'((?!\n)[a-zA-Z0-9\_\-\s\(\),]){2,}', Text),
1432 (r'[^:\n]+', String),
1433 ],
1434 }
1435
1436
1437class CaddyfileLexer(RegexLexer):
1438 """
1439 Lexer for Caddyfile, the configuration file format of the Caddy web server.
1440 """
1441
1442 name = 'Caddyfile'
1443 url = 'https://caddyserver.com/docs/caddyfile'
1444 aliases = ['caddyfile', 'caddy']
1445 filenames = ['Caddyfile']
1446 version_added = '2.21'
1447
1448 # The standard HTTP directives shipped with Caddy.
1449 directives = (
1450 'abort', 'acme_server', 'basic_auth', 'bind', 'encode', 'error',
1451 'file_server', 'forward_auth', 'handle', 'handle_errors',
1452 'handle_path', 'header', 'import', 'invoke', 'log', 'map', 'metrics',
1453 'php_fastcgi', 'push', 'redir', 'request_body', 'request_header',
1454 'respond', 'reverse_proxy', 'rewrite', 'root', 'route', 'templates',
1455 'tls', 'tracing', 'try_files', 'uri', 'vars',
1456 )
1457
1458 tokens = {
1459 'root': [
1460 # Directives are only keywords as the first token on a line, so
1461 # anchor them to the start of the (possibly indented) line. This
1462 # consumes the preceding newline(s)/indentation itself, otherwise
1463 # the generic whitespace rule below would swallow it first.
1464 (r'((?:\A|\s*\n)[ \t]*)(' + '|'.join(directives) + r')\b',
1465 bygroups(Whitespace, Keyword)),
1466 (r'\s+', Whitespace),
1467 (r'#.*', Comment.Single),
1468 # Heredoc: <<LABEL <args> ... LABEL. The remainder of the opening
1469 # line (e.g. a status code) is not part of the body, so re-lex it;
1470 # the closing label must stand alone on its line.
1471 (r'(<<)([a-zA-Z_]\w*)(.*\n)((?:.*\n)*?)([ \t]*)(\2)(?=[ \t]*$)',
1472 bygroups(Operator, String.Delimiter, using(this),
1473 String.Heredoc, Whitespace, String.Delimiter)),
1474 # Placeholders: {path}, {http.request.uri}, {$ENV}, {args[0]}, ...
1475 (r'\{[^{}\s]+\}', Name.Variable),
1476 # Blocks and snippet parentheses
1477 (r'[(){}]', Punctuation),
1478 # Named matcher, e.g. @api
1479 (r'@[\w.-]+', Name.Decorator),
1480 (r'"', String.Double, 'string'),
1481 (r'`', String.Backtick, 'backtick'),
1482 # Numbers, durations and sizes (443, 200, 30s, 1.5m, 10MB); a
1483 # trailing dot or more digits/letters means it is an address or
1484 # hostname, which must stay Text.
1485 (r'\d+(?:\.\d+)?[a-zA-Z]*(?=[\s#{}()"`]|$)', Number),
1486 (r'[^\s#{}()"`]+', Text),
1487 ],
1488 'string': [
1489 (r'\\.', String.Escape),
1490 (r'\{[^{}\s]+\}', Name.Variable),
1491 (r'[^"\\{]+', String.Double),
1492 (r'"', String.Double, '#pop'),
1493 (r'.', String.Double),
1494 ],
1495 'backtick': [
1496 (r'[^`]+', String.Backtick),
1497 (r'`', String.Backtick, '#pop'),
1498 ],
1499 }