66
|
1 package lexers
|
|
2
|
|
3 import (
|
|
4 . "github.com/alecthomas/chroma/v2" // nolint
|
|
5 )
|
|
6
|
|
7 // caddyfileCommon are the rules common to both of the lexer variants
|
|
8 func caddyfileCommonRules() Rules {
|
|
9 return Rules{
|
|
10 "site_block_common": {
|
|
11 // Import keyword
|
|
12 {`(import)(\s+)([^\s]+)`, ByGroups(Keyword, Text, NameVariableMagic), nil},
|
|
13 // Matcher definition
|
|
14 {`@[^\s]+(?=\s)`, NameDecorator, Push("matcher")},
|
|
15 // Matcher token stub for docs
|
|
16 {`\[\<matcher\>\]`, NameDecorator, Push("matcher")},
|
|
17 // These cannot have matchers but may have things that look like
|
|
18 // matchers in their arguments, so we just parse as a subdirective.
|
|
19 {`try_files`, Keyword, Push("subdirective")},
|
|
20 // These are special, they can nest more directives
|
|
21 {`handle_errors|handle|route|handle_path|not`, Keyword, Push("nested_directive")},
|
|
22 // Any other directive
|
|
23 {`[^\s#]+`, Keyword, Push("directive")},
|
|
24 Include("base"),
|
|
25 },
|
|
26 "matcher": {
|
|
27 {`\{`, Punctuation, Push("block")},
|
|
28 // Not can be one-liner
|
|
29 {`not`, Keyword, Push("deep_not_matcher")},
|
|
30 // Any other same-line matcher
|
|
31 {`[^\s#]+`, Keyword, Push("arguments")},
|
|
32 // Terminators
|
|
33 {`\n`, Text, Pop(1)},
|
|
34 {`\}`, Punctuation, Pop(1)},
|
|
35 Include("base"),
|
|
36 },
|
|
37 "block": {
|
|
38 {`\}`, Punctuation, Pop(2)},
|
|
39 // Not can be one-liner
|
|
40 {`not`, Keyword, Push("not_matcher")},
|
|
41 // Any other subdirective
|
|
42 {`[^\s#]+`, Keyword, Push("subdirective")},
|
|
43 Include("base"),
|
|
44 },
|
|
45 "nested_block": {
|
|
46 {`\}`, Punctuation, Pop(2)},
|
|
47 // Matcher definition
|
|
48 {`@[^\s]+(?=\s)`, NameDecorator, Push("matcher")},
|
|
49 // Something that starts with literally < is probably a docs stub
|
|
50 {`\<[^#]+\>`, Keyword, Push("nested_directive")},
|
|
51 // Any other directive
|
|
52 {`[^\s#]+`, Keyword, Push("nested_directive")},
|
|
53 Include("base"),
|
|
54 },
|
|
55 "not_matcher": {
|
|
56 {`\}`, Punctuation, Pop(2)},
|
|
57 {`\{(?=\s)`, Punctuation, Push("block")},
|
|
58 {`[^\s#]+`, Keyword, Push("arguments")},
|
|
59 {`\s+`, Text, nil},
|
|
60 },
|
|
61 "deep_not_matcher": {
|
|
62 {`\}`, Punctuation, Pop(2)},
|
|
63 {`\{(?=\s)`, Punctuation, Push("block")},
|
|
64 {`[^\s#]+`, Keyword, Push("deep_subdirective")},
|
|
65 {`\s+`, Text, nil},
|
|
66 },
|
|
67 "directive": {
|
|
68 {`\{(?=\s)`, Punctuation, Push("block")},
|
|
69 Include("matcher_token"),
|
|
70 Include("comments_pop_1"),
|
|
71 {`\n`, Text, Pop(1)},
|
|
72 Include("base"),
|
|
73 },
|
|
74 "nested_directive": {
|
|
75 {`\{(?=\s)`, Punctuation, Push("nested_block")},
|
|
76 Include("matcher_token"),
|
|
77 Include("comments_pop_1"),
|
|
78 {`\n`, Text, Pop(1)},
|
|
79 Include("base"),
|
|
80 },
|
|
81 "subdirective": {
|
|
82 {`\{(?=\s)`, Punctuation, Push("block")},
|
|
83 Include("comments_pop_1"),
|
|
84 {`\n`, Text, Pop(1)},
|
|
85 Include("base"),
|
|
86 },
|
|
87 "arguments": {
|
|
88 {`\{(?=\s)`, Punctuation, Push("block")},
|
|
89 Include("comments_pop_2"),
|
|
90 {`\\\n`, Text, nil}, // Skip escaped newlines
|
|
91 {`\n`, Text, Pop(2)},
|
|
92 Include("base"),
|
|
93 },
|
|
94 "deep_subdirective": {
|
|
95 {`\{(?=\s)`, Punctuation, Push("block")},
|
|
96 Include("comments_pop_3"),
|
|
97 {`\n`, Text, Pop(3)},
|
|
98 Include("base"),
|
|
99 },
|
|
100 "matcher_token": {
|
|
101 {`@[^\s]+`, NameDecorator, Push("arguments")}, // Named matcher
|
|
102 {`/[^\s]+`, NameDecorator, Push("arguments")}, // Path matcher
|
|
103 {`\*`, NameDecorator, Push("arguments")}, // Wildcard path matcher
|
|
104 {`\[\<matcher\>\]`, NameDecorator, Push("arguments")}, // Matcher token stub for docs
|
|
105 },
|
|
106 "comments": {
|
|
107 {`^#.*\n`, CommentSingle, nil}, // Comment at start of line
|
|
108 {`\s+#.*\n`, CommentSingle, nil}, // Comment preceded by whitespace
|
|
109 },
|
|
110 "comments_pop_1": {
|
|
111 {`^#.*\n`, CommentSingle, Pop(1)}, // Comment at start of line
|
|
112 {`\s+#.*\n`, CommentSingle, Pop(1)}, // Comment preceded by whitespace
|
|
113 },
|
|
114 "comments_pop_2": {
|
|
115 {`^#.*\n`, CommentSingle, Pop(2)}, // Comment at start of line
|
|
116 {`\s+#.*\n`, CommentSingle, Pop(2)}, // Comment preceded by whitespace
|
|
117 },
|
|
118 "comments_pop_3": {
|
|
119 {`^#.*\n`, CommentSingle, Pop(3)}, // Comment at start of line
|
|
120 {`\s+#.*\n`, CommentSingle, Pop(3)}, // Comment preceded by whitespace
|
|
121 },
|
|
122 "base": {
|
|
123 Include("comments"),
|
|
124 {`(on|off|first|last|before|after|internal|strip_prefix|strip_suffix|replace)\b`, NameConstant, nil},
|
|
125 {`(https?://)?([a-z0-9.-]+)(:)([0-9]+)`, ByGroups(Name, Name, Punctuation, LiteralNumberInteger), nil},
|
|
126 {`[a-z-]+/[a-z-+]+`, LiteralString, nil},
|
|
127 {`[0-9]+[km]?\b`, LiteralNumberInteger, nil},
|
|
128 {`\{[\w+.\$-]+\}`, LiteralStringEscape, nil}, // Placeholder
|
|
129 {`\[(?=[^#{}$]+\])`, Punctuation, nil},
|
|
130 {`\]|\|`, Punctuation, nil},
|
|
131 {`[^\s#{}$\]]+`, LiteralString, nil},
|
|
132 {`/[^\s#]*`, Name, nil},
|
|
133 {`\s+`, Text, nil},
|
|
134 },
|
|
135 }
|
|
136 }
|
|
137
|
|
138 // Caddyfile lexer.
|
|
139 var Caddyfile = Register(MustNewLexer(
|
|
140 &Config{
|
|
141 Name: "Caddyfile",
|
|
142 Aliases: []string{"caddyfile", "caddy"},
|
|
143 Filenames: []string{"Caddyfile*"},
|
|
144 MimeTypes: []string{},
|
|
145 },
|
|
146 caddyfileRules,
|
|
147 ))
|
|
148
|
|
149 func caddyfileRules() Rules {
|
|
150 return Rules{
|
|
151 "root": {
|
|
152 Include("comments"),
|
|
153 // Global options block
|
|
154 {`^\s*(\{)\s*$`, ByGroups(Punctuation), Push("globals")},
|
|
155 // Snippets
|
|
156 {`(\([^\s#]+\))(\s*)(\{)`, ByGroups(NameVariableAnonymous, Text, Punctuation), Push("snippet")},
|
|
157 // Site label
|
|
158 {`[^#{(\s,]+`, GenericHeading, Push("label")},
|
|
159 // Site label with placeholder
|
|
160 {`\{[\w+.\$-]+\}`, LiteralStringEscape, Push("label")},
|
|
161 {`\s+`, Text, nil},
|
|
162 },
|
|
163 "globals": {
|
|
164 {`\}`, Punctuation, Pop(1)},
|
|
165 {`[^\s#]+`, Keyword, Push("directive")},
|
|
166 Include("base"),
|
|
167 },
|
|
168 "snippet": {
|
|
169 {`\}`, Punctuation, Pop(1)},
|
|
170 // Matcher definition
|
|
171 {`@[^\s]+(?=\s)`, NameDecorator, Push("matcher")},
|
|
172 // Any directive
|
|
173 {`[^\s#]+`, Keyword, Push("directive")},
|
|
174 Include("base"),
|
|
175 },
|
|
176 "label": {
|
|
177 // Allow multiple labels, comma separated, newlines after
|
|
178 // a comma means another label is coming
|
|
179 {`,\s*\n?`, Text, nil},
|
|
180 {` `, Text, nil},
|
|
181 // Site label with placeholder
|
|
182 {`\{[\w+.\$-]+\}`, LiteralStringEscape, nil},
|
|
183 // Site label
|
|
184 {`[^#{(\s,]+`, GenericHeading, nil},
|
|
185 // Comment after non-block label (hack because comments end in \n)
|
|
186 {`#.*\n`, CommentSingle, Push("site_block")},
|
|
187 // Note: if \n, we'll never pop out of the site_block, it's valid
|
|
188 {`\{(?=\s)|\n`, Punctuation, Push("site_block")},
|
|
189 },
|
|
190 "site_block": {
|
|
191 {`\}`, Punctuation, Pop(2)},
|
|
192 Include("site_block_common"),
|
|
193 },
|
|
194 }.Merge(caddyfileCommonRules())
|
|
195 }
|
|
196
|
|
197 // Caddyfile directive-only lexer.
|
|
198 var CaddyfileDirectives = Register(MustNewLexer(
|
|
199 &Config{
|
|
200 Name: "Caddyfile Directives",
|
|
201 Aliases: []string{"caddyfile-directives", "caddyfile-d", "caddy-d"},
|
|
202 Filenames: []string{},
|
|
203 MimeTypes: []string{},
|
|
204 },
|
|
205 caddyfileDirectivesRules,
|
|
206 ))
|
|
207
|
|
208 func caddyfileDirectivesRules() Rules {
|
|
209 return Rules{
|
|
210 // Same as "site_block" in Caddyfile
|
|
211 "root": {
|
|
212 Include("site_block_common"),
|
|
213 },
|
|
214 }.Merge(caddyfileCommonRules())
|
|
215 }
|