@@ -4,52 +4,82 @@ import (
44 . "github.com/alecthomas/chroma/v2" // nolint
55)
66
7+ // Matcher token stub for docs, or
8+ // Named matcher: @name, or
9+ // Path matcher: /foo, or
10+ // Wildcard path matcher: *
11+ // nolint: gosec
12+ var caddyfileMatcherTokenRegexp = `(\[\<matcher\>\]|@[^\s]+|/[^\s]+|\*)`
13+
14+ // Comment at start of line, or
15+ // Comment preceded by whitespace
16+ var caddyfileCommentRegexp = `(^|\s+)#.*\n`
17+
718// caddyfileCommon are the rules common to both of the lexer variants
819func caddyfileCommonRules () Rules {
920 return Rules {
1021 "site_block_common" : {
22+ Include ("site_body" ),
23+ // Any other directive
24+ {`[^\s#]+` , Keyword , Push ("directive" )},
25+ Include ("base" ),
26+ },
27+ "site_body" : {
1128 // Import keyword
12- {`(import)(\s+)( [^\s]+)` , ByGroups (Keyword , Text , NameVariableMagic ), nil },
29+ {`\b (import|invoke)\b( [^\s# ]+)` , ByGroups (Keyword , Text ), Push ( "subdirective" ) },
1330 // Matcher definition
1431 {`@[^\s]+(?=\s)` , NameDecorator , Push ("matcher" )},
1532 // Matcher token stub for docs
1633 {`\[\<matcher\>\]` , NameDecorator , Push ("matcher" )},
1734 // These cannot have matchers but may have things that look like
1835 // matchers in their arguments, so we just parse as a subdirective.
19- {`try_files` , Keyword , Push ("subdirective" )},
36+ {`\b( try_files|tls|log|bind)\b ` , Keyword , Push ("subdirective" )},
2037 // 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" ),
38+ {`\b(handle_errors|handle_path|handle_response|replace_status|handle|route)\b` , Keyword , Push ("nested_directive" )},
39+ // uri directive has special syntax
40+ {`\b(uri)\b` , Keyword , Push ("uri_directive" )},
2541 },
2642 "matcher" : {
2743 {`\{` , Punctuation , Push ("block" )},
2844 // Not can be one-liner
2945 {`not` , Keyword , Push ("deep_not_matcher" )},
46+ // Heredoc for CEL expression
47+ Include ("heredoc" ),
48+ // Backtick for CEL expression
49+ {"`" , StringBacktick , Push ("backticks" )},
3050 // Any other same-line matcher
3151 {`[^\s#]+` , Keyword , Push ("arguments" )},
3252 // Terminators
33- {`\n` , Text , Pop (1 )},
53+ {`\s*\ n` , Text , Pop (1 )},
3454 {`\}` , Punctuation , Pop (1 )},
3555 Include ("base" ),
3656 },
3757 "block" : {
3858 {`\}` , Punctuation , Pop (2 )},
59+ // Using double quotes doesn't stop at spaces
60+ {`"` , StringDouble , Push ("double_quotes" )},
61+ // Using backticks doesn't stop at spaces
62+ {"`" , StringBacktick , Push ("backticks" )},
3963 // Not can be one-liner
4064 {`not` , Keyword , Push ("not_matcher" )},
41- // Any other subdirective
65+ // Directives & matcher definitions
66+ Include ("site_body" ),
67+ // Any directive
4268 {`[^\s#]+` , Keyword , Push ("subdirective" )},
4369 Include ("base" ),
4470 },
4571 "nested_block" : {
4672 {`\}` , 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" )},
73+ // Using double quotes doesn't stop at spaces
74+ {`"` , StringDouble , Push ("double_quotes" )},
75+ // Using backticks doesn't stop at spaces
76+ {"`" , StringBacktick , Push ("backticks" )},
77+ // Not can be one-liner
78+ {`not` , Keyword , Push ("not_matcher" )},
79+ // Directives & matcher definitions
80+ Include ("site_body" ),
81+ // Any other subdirective
82+ {`[^\s#]+` , Keyword , Push ("directive" )},
5383 Include ("base" ),
5484 },
5585 "not_matcher" : {
@@ -66,69 +96,97 @@ func caddyfileCommonRules() Rules {
6696 },
6797 "directive" : {
6898 {`\{(?=\s)` , Punctuation , Push ("block" )},
69- Include ( "matcher_token" ) ,
70- Include ( "comments_pop_1" ) ,
71- {`\n` , Text , Pop (1 )},
99+ { caddyfileMatcherTokenRegexp , NameDecorator , Push ( "arguments" )} ,
100+ { caddyfileCommentRegexp , CommentSingle , Pop ( 1 )} ,
101+ {`\s*\ n` , Text , Pop (1 )},
72102 Include ("base" ),
73103 },
74104 "nested_directive" : {
75105 {`\{(?=\s)` , Punctuation , Push ("nested_block" )},
76- Include ( "matcher_token" ) ,
77- Include ( "comments_pop_1" ) ,
78- {`\n` , Text , Pop (1 )},
106+ { caddyfileMatcherTokenRegexp , NameDecorator , Push ( "nested_arguments" )} ,
107+ { caddyfileCommentRegexp , CommentSingle , Pop ( 1 )} ,
108+ {`\s*\ n` , Text , Pop (1 )},
79109 Include ("base" ),
80110 },
81111 "subdirective" : {
82112 {`\{(?=\s)` , Punctuation , Push ("block" )},
83- Include ( "comments_pop_1" ) ,
84- {`\n` , Text , Pop (1 )},
113+ { caddyfileCommentRegexp , CommentSingle , Pop ( 1 )} ,
114+ {`\s*\ n` , Text , Pop (1 )},
85115 Include ("base" ),
86116 },
87117 "arguments" : {
88118 {`\{(?=\s)` , Punctuation , Push ("block" )},
89- Include ( "comments_pop_2" ) ,
119+ { caddyfileCommentRegexp , CommentSingle , Pop ( 2 )} ,
90120 {`\\\n` , Text , nil }, // Skip escaped newlines
91- {`\n` , Text , Pop (2 )},
121+ {`\s*\n` , Text , Pop (2 )},
122+ Include ("base" ),
123+ },
124+ "nested_arguments" : {
125+ {`\{(?=\s)` , Punctuation , Push ("nested_block" )},
126+ {caddyfileCommentRegexp , CommentSingle , Pop (2 )},
127+ {`\\\n` , Text , nil }, // Skip escaped newlines
128+ {`\s*\n` , Text , Pop (2 )},
92129 Include ("base" ),
93130 },
94131 "deep_subdirective" : {
95132 {`\{(?=\s)` , Punctuation , Push ("block" )},
96- Include ("comments_pop_3" ),
97- {`\n` , Text , Pop (3 )},
133+ {caddyfileCommentRegexp , CommentSingle , Pop (3 )},
134+ {`\s*\n` , Text , Pop (3 )},
135+ Include ("base" ),
136+ },
137+ "uri_directive" : {
138+ {`\{(?=\s)` , Punctuation , Push ("block" )},
139+ {caddyfileMatcherTokenRegexp , NameDecorator , nil },
140+ {`(strip_prefix|strip_suffix|replace|path_regexp)` , NameConstant , Push ("arguments" )},
141+ {caddyfileCommentRegexp , CommentSingle , Pop (1 )},
142+ {`\s*\n` , Text , Pop (1 )},
98143 Include ("base" ),
99144 },
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
145+ "double_quotes " : {
146+ Include ( "placeholder" ),
147+ {`\\" ` , StringDouble , nil },
148+ {`[^"] ` , StringDouble , nil },
149+ {`" ` , StringDouble , Pop ( 1 )},
105150 },
106- "comments" : {
107- {`^#.*\n` , CommentSingle , nil }, // Comment at start of line
108- {`\s+#.*\n` , CommentSingle , nil }, // Comment preceded by whitespace
151+ "backticks" : {
152+ Include ("placeholder" ),
153+ {"\\ \\ `" , StringBacktick , nil },
154+ {"[^`]" , StringBacktick , nil },
155+ {"`" , StringBacktick , Pop (1 )},
109156 },
110- "comments_pop_1" : {
111- {`^#.*\n` , CommentSingle , Pop (1 )}, // Comment at start of line
112- {`\s+#.*\n` , CommentSingle , Pop (1 )}, // Comment preceded by whitespace
157+ "optional" : {
158+ // Docs syntax for showing optional parts with [ ]
159+ {`\[` , Punctuation , Push ("optional" )},
160+ Include ("name_constants" ),
161+ {`\|` , Punctuation , nil },
162+ {`[^\[\]\|]+` , String , nil },
163+ {`\]` , Punctuation , Pop (1 )},
113164 },
114- "comments_pop_2" : {
115- {`^#.*\n` , CommentSingle , Pop (2 )}, // Comment at start of line
116- {`\s+#.*\n` , CommentSingle , Pop (2 )}, // Comment preceded by whitespace
165+ "heredoc" : {
166+ {`(<<([a-zA-Z0-9_-]+))(\n(.*|\n)*)(\s*)(\2)` , ByGroups (StringHeredoc , nil , String , String , String , StringHeredoc ), nil },
117167 },
118- "comments_pop_3" : {
119- {`^#.*\n` , CommentSingle , Pop (3 )}, // Comment at start of line
120- {`\s+#.*\n` , CommentSingle , Pop (3 )}, // Comment preceded by whitespace
168+ "name_constants" : {
169+ {`\b(most_recently_modified|largest_size|smallest_size|first_exist|internal|disable_redirects|ignore_loaded_certs|disable_certs|private_ranges|first|last|before|after|on|off)\b(\||(?=\]|\s|$))` , ByGroups (NameConstant , Punctuation ), nil },
170+ },
171+ "placeholder" : {
172+ // Placeholder with dots, colon for default value, brackets for args[0:]
173+ {`\{[\w+.\[\]\:\$-]+\}` , StringEscape , nil },
174+ // Handle opening brackets with no matching closing one
175+ {`\{[^\}\s]*\b` , String , nil },
121176 },
122177 "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 },
178+ {caddyfileCommentRegexp , CommentSingle , nil },
179+ {`\[\<matcher\>\]` , NameDecorator , nil },
180+ Include ("name_constants" ),
181+ Include ("heredoc" ),
182+ {`(https?://)?([a-z0-9.-]+)(:)([0-9]+)([^\s]*)` , ByGroups (Name , Name , Punctuation , NumberInteger , Name ), nil },
183+ {`\[` , Punctuation , Push ("optional" )},
184+ {"`" , StringBacktick , Push ("backticks" )},
185+ {`"` , StringDouble , Push ("double_quotes" )},
186+ Include ("placeholder" ),
187+ {`[a-z-]+/[a-z-+]+` , String , nil },
188+ {`[0-9]+([smhdk]|ns|us|µs|ms)?\b` , NumberInteger , nil },
189+ {`[^\s\n#\{]+` , String , nil },
132190 {`/[^\s#]*` , Name , nil },
133191 {`\s+` , Text , nil },
134192 },
@@ -149,27 +207,29 @@ var Caddyfile = Register(MustNewLexer(
149207func caddyfileRules () Rules {
150208 return Rules {
151209 "root" : {
152- Include ( "comments" ) ,
210+ { caddyfileCommentRegexp , CommentSingle , nil } ,
153211 // Global options block
154212 {`^\s*(\{)\s*$` , ByGroups (Punctuation ), Push ("globals" )},
213+ // Top level import
214+ {`(import)(\s+)([^\s]+)` , ByGroups (Keyword , Text , NameVariableMagic ), nil },
155215 // Snippets
156- {`(\([^\s#]+\))(\s*)(\{)` , ByGroups (NameVariableAnonymous , Text , Punctuation ), Push ("snippet" )},
216+ {`(&? \([^\s#]+\))(\s*)(\{)` , ByGroups (NameVariableAnonymous , Text , Punctuation ), Push ("snippet" )},
157217 // Site label
158218 {`[^#{(\s,]+` , GenericHeading , Push ("label" )},
159219 // Site label with placeholder
160- {`\{[\w+.\$-]+\}` , LiteralStringEscape , Push ("label" )},
220+ {`\{[\w+.\[\]\:\ $-]+\}` , StringEscape , Push ("label" )},
161221 {`\s+` , Text , nil },
162222 },
163223 "globals" : {
164224 {`\}` , Punctuation , Pop (1 )},
165- {`[^\s#]+` , Keyword , Push ("directive" )},
225+ // Global options are parsed as subdirectives (no matcher)
226+ {`[^\s#]+` , Keyword , Push ("subdirective" )},
166227 Include ("base" ),
167228 },
168229 "snippet" : {
169230 {`\}` , Punctuation , Pop (1 )},
170- // Matcher definition
171- {`@[^\s]+(?=\s)` , NameDecorator , Push ("matcher" )},
172- // Any directive
231+ Include ("site_body" ),
232+ // Any other directive
173233 {`[^\s#]+` , Keyword , Push ("directive" )},
174234 Include ("base" ),
175235 },
@@ -179,7 +239,7 @@ func caddyfileRules() Rules {
179239 {`,\s*\n?` , Text , nil },
180240 {` ` , Text , nil },
181241 // Site label with placeholder
182- { `\{[\w+.\$-]+\}` , LiteralStringEscape , nil } ,
242+ Include ( "placeholder" ) ,
183243 // Site label
184244 {`[^#{(\s,]+` , GenericHeading , nil },
185245 // Comment after non-block label (hack because comments end in \n)
0 commit comments