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