1"""
2 pygments.lexers.dns
3 ~~~~~~~~~~~~~~~~~~~
4
5 Pygments lexers for DNS
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.token import Comment, Operator, Keyword, Name, String, \
14 Number, Punctuation, Whitespace, Literal
15from pygments.lexer import RegexLexer, bygroups, include
16
17__all__ = ['DnsZoneLexer']
18
19
20CLASSES = [
21 "IN",
22 "CS",
23 "CH",
24 "HS",
25]
26
27CLASSES_RE = "(" + "|".join(CLASSES) + ')'
28
29
30class DnsZoneLexer(RegexLexer):
31
32 """
33 Lexer for DNS zone file
34 """
35
36 flags = re.MULTILINE
37
38 name = 'Zone'
39 aliases = ['zone']
40 filenames = [ "*.zone" ]
41 url = "https://datatracker.ietf.org/doc/html/rfc1035"
42 mimetypes = ['text/dns']
43 version_added = '2.16'
44
45 tokens = {
46 'root': [
47 # Empty/comment line:
48 (r'([ \t]*)(;.*)(\n)', bygroups(Whitespace, Comment.Single, Whitespace)),
49 # Special directives:
50 (r'^\$ORIGIN\b', Keyword, 'values'),
51 (r'^\$TTL\b', Keyword, 'values'),
52 (r'^\$INCLUDE\b', Comment.Preproc, 'include'),
53 # TODO, $GENERATE https://bind9.readthedocs.io/en/v9.18.14/chapter3.html#soa-rr
54 (r'^\$[A-Z]+\b', Keyword, 'values'),
55 # Records:
56 # <domain-name> [<TTL>] [<class>] <type> <RDATA> [<comment>]
57 (r'^(@)([ \t]+)(?:([0-9]+[smhdw]?)([ \t]+))?(?:' + CLASSES_RE + "([ \t]+))?([A-Z]+)([ \t]+)",
58 bygroups(Operator, Whitespace, Number.Integer, Whitespace, Name.Class, Whitespace, Keyword.Type, Whitespace),
59 "values"),
60 (r'^([^ \t\n]*)([ \t]+)(?:([0-9]+[smhdw]?)([ \t]+))?(?:' + CLASSES_RE + "([ \t]+))?([A-Z]+)([ \t]+)",
61 bygroups(Name, Whitespace, Number.Integer, Whitespace, Name.Class, Whitespace, Keyword.Type, Whitespace),
62 "values"),
63 # <domain-name> [<class>] [<TTL>] <type> <RDATA> [<comment>]
64 (r'^(Operator)([ \t]+)(?:' + CLASSES_RE + "([ \t]+))?(?:([0-9]+[smhdw]?)([ \t]+))?([A-Z]+)([ \t]+)",
65 bygroups(Name, Whitespace, Number.Integer, Whitespace, Name.Class, Whitespace, Keyword.Type, Whitespace),
66 "values"),
67 (r'^([^ \t\n]*)([ \t]+)(?:' + CLASSES_RE + "([ \t]+))?(?:([0-9]+[smhdw]?)([ \t]+))?([A-Z]+)([ \t]+)",
68 bygroups(Name, Whitespace, Number.Integer, Whitespace, Name.Class, Whitespace, Keyword.Type, Whitespace),
69 "values"),
70 ],
71 # Parsing values:
72 'values': [
73 (r'\n', Whitespace, "#pop"),
74 (r'\(', Punctuation, 'nested'),
75 include('simple-value'),
76 ],
77 # Parsing nested values (...):
78 'nested': [
79 (r'\)', Punctuation, "#pop"),
80 include('multiple-simple-values'),
81 ],
82 # Parsing values:
83 'simple-value': [
84 (r'(;.*)', bygroups(Comment.Single)),
85 (r'[ \t]+', Whitespace),
86 (r"@\b", Operator),
87 ('"', String, 'string'),
88 (r'[0-9]+[smhdw]?$', Number.Integer),
89 (r'([0-9]+[smhdw]?)([ \t]+)', bygroups(Number.Integer, Whitespace)),
90 (r'\S+', Literal),
91 ],
92 'multiple-simple-values': [
93 include('simple-value'),
94 (r'[\n]+', Whitespace),
95 ],
96 'include': [
97 (r'([ \t]+)([^ \t\n]+)([ \t]+)([-\._a-zA-Z]+)([ \t]+)(;.*)?$',
98 bygroups(Whitespace, Comment.PreprocFile, Whitespace, Name, Whitespace, Comment.Single), '#pop'),
99 (r'([ \t]+)([^ \t\n]+)([ \t\n]+)$', bygroups(Whitespace, Comment.PreprocFile, Whitespace), '#pop'),
100 ],
101 "string": [
102 (r'\\"', String),
103 (r'"', String, "#pop"),
104 (r'[^"]+', String),
105 ]
106 }
107
108 def analyse_text(text):
109 return text.startswith("$ORIGIN")