1
0
mirror of https://github.com/alecthomas/chroma.git synced 2025-03-27 21:49:13 +02:00

Major updates to Caddyfile lexer (#932)

* Major updates to Caddyfile lexer

* yaml editorconfig

* nolint false positive
This commit is contained in:
Francis Lavoie 2024-02-20 04:08:27 -05:00 committed by GitHub
parent e9292e6994
commit 381050ba00
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 425 additions and 77 deletions

View File

@ -11,3 +11,7 @@ insert_final_newline = true
indent_style = space
indent_size = 2
insert_final_newline = false
[*.yml]
indent_style = space
indent_size = 2

View File

@ -4,52 +4,82 @@ import (
. "github.com/alecthomas/chroma/v2" // nolint
)
// Matcher token stub for docs, or
// Named matcher: @name, or
// Path matcher: /foo, or
// Wildcard path matcher: *
// nolint: gosec
var caddyfileMatcherTokenRegexp = `(\[\<matcher\>\]|@[^\s]+|/[^\s]+|\*)`
// Comment at start of line, or
// Comment preceded by whitespace
var caddyfileCommentRegexp = `(^|\s+)#.*\n`
// caddyfileCommon are the rules common to both of the lexer variants
func caddyfileCommonRules() Rules {
return Rules{
"site_block_common": {
Include("site_body"),
// Any other directive
{`[^\s#]+`, Keyword, Push("directive")},
Include("base"),
},
"site_body": {
// Import keyword
{`(import)(\s+)([^\s]+)`, ByGroups(Keyword, Text, NameVariableMagic), nil},
{`\b(import|invoke)\b( [^\s#]+)`, ByGroups(Keyword, Text), Push("subdirective")},
// Matcher definition
{`@[^\s]+(?=\s)`, NameDecorator, Push("matcher")},
// Matcher token stub for docs
{`\[\<matcher\>\]`, NameDecorator, Push("matcher")},
// These cannot have matchers but may have things that look like
// matchers in their arguments, so we just parse as a subdirective.
{`try_files`, Keyword, Push("subdirective")},
{`\b(try_files|tls|log|bind)\b`, Keyword, Push("subdirective")},
// These are special, they can nest more directives
{`handle_errors|handle|route|handle_path|not`, Keyword, Push("nested_directive")},
// Any other directive
{`[^\s#]+`, Keyword, Push("directive")},
Include("base"),
{`\b(handle_errors|handle_path|handle_response|replace_status|handle|route)\b`, Keyword, Push("nested_directive")},
// uri directive has special syntax
{`\b(uri)\b`, Keyword, Push("uri_directive")},
},
"matcher": {
{`\{`, Punctuation, Push("block")},
// Not can be one-liner
{`not`, Keyword, Push("deep_not_matcher")},
// Heredoc for CEL expression
Include("heredoc"),
// Backtick for CEL expression
{"`", StringBacktick, Push("backticks")},
// Any other same-line matcher
{`[^\s#]+`, Keyword, Push("arguments")},
// Terminators
{`\n`, Text, Pop(1)},
{`\s*\n`, Text, Pop(1)},
{`\}`, Punctuation, Pop(1)},
Include("base"),
},
"block": {
{`\}`, Punctuation, Pop(2)},
// Using double quotes doesn't stop at spaces
{`"`, StringDouble, Push("double_quotes")},
// Using backticks doesn't stop at spaces
{"`", StringBacktick, Push("backticks")},
// Not can be one-liner
{`not`, Keyword, Push("not_matcher")},
// Any other subdirective
// Directives & matcher definitions
Include("site_body"),
// Any directive
{`[^\s#]+`, Keyword, Push("subdirective")},
Include("base"),
},
"nested_block": {
{`\}`, Punctuation, Pop(2)},
// Matcher definition
{`@[^\s]+(?=\s)`, NameDecorator, Push("matcher")},
// Something that starts with literally < is probably a docs stub
{`\<[^#]+\>`, Keyword, Push("nested_directive")},
// Any other directive
{`[^\s#]+`, Keyword, Push("nested_directive")},
// Using double quotes doesn't stop at spaces
{`"`, StringDouble, Push("double_quotes")},
// Using backticks doesn't stop at spaces
{"`", StringBacktick, Push("backticks")},
// Not can be one-liner
{`not`, Keyword, Push("not_matcher")},
// Directives & matcher definitions
Include("site_body"),
// Any other subdirective
{`[^\s#]+`, Keyword, Push("directive")},
Include("base"),
},
"not_matcher": {
@ -66,69 +96,97 @@ func caddyfileCommonRules() Rules {
},
"directive": {
{`\{(?=\s)`, Punctuation, Push("block")},
Include("matcher_token"),
Include("comments_pop_1"),
{`\n`, Text, Pop(1)},
{caddyfileMatcherTokenRegexp, NameDecorator, Push("arguments")},
{caddyfileCommentRegexp, CommentSingle, Pop(1)},
{`\s*\n`, Text, Pop(1)},
Include("base"),
},
"nested_directive": {
{`\{(?=\s)`, Punctuation, Push("nested_block")},
Include("matcher_token"),
Include("comments_pop_1"),
{`\n`, Text, Pop(1)},
{caddyfileMatcherTokenRegexp, NameDecorator, Push("nested_arguments")},
{caddyfileCommentRegexp, CommentSingle, Pop(1)},
{`\s*\n`, Text, Pop(1)},
Include("base"),
},
"subdirective": {
{`\{(?=\s)`, Punctuation, Push("block")},
Include("comments_pop_1"),
{`\n`, Text, Pop(1)},
{caddyfileCommentRegexp, CommentSingle, Pop(1)},
{`\s*\n`, Text, Pop(1)},
Include("base"),
},
"arguments": {
{`\{(?=\s)`, Punctuation, Push("block")},
Include("comments_pop_2"),
{caddyfileCommentRegexp, CommentSingle, Pop(2)},
{`\\\n`, Text, nil}, // Skip escaped newlines
{`\n`, Text, Pop(2)},
{`\s*\n`, Text, Pop(2)},
Include("base"),
},
"nested_arguments": {
{`\{(?=\s)`, Punctuation, Push("nested_block")},
{caddyfileCommentRegexp, CommentSingle, Pop(2)},
{`\\\n`, Text, nil}, // Skip escaped newlines
{`\s*\n`, Text, Pop(2)},
Include("base"),
},
"deep_subdirective": {
{`\{(?=\s)`, Punctuation, Push("block")},
Include("comments_pop_3"),
{`\n`, Text, Pop(3)},
{caddyfileCommentRegexp, CommentSingle, Pop(3)},
{`\s*\n`, Text, Pop(3)},
Include("base"),
},
"matcher_token": {
{`@[^\s]+`, NameDecorator, Push("arguments")}, // Named matcher
{`/[^\s]+`, NameDecorator, Push("arguments")}, // Path matcher
{`\*`, NameDecorator, Push("arguments")}, // Wildcard path matcher
{`\[\<matcher\>\]`, NameDecorator, Push("arguments")}, // Matcher token stub for docs
"uri_directive": {
{`\{(?=\s)`, Punctuation, Push("block")},
{caddyfileMatcherTokenRegexp, NameDecorator, nil},
{`(strip_prefix|strip_suffix|replace|path_regexp)`, NameConstant, Push("arguments")},
{caddyfileCommentRegexp, CommentSingle, Pop(1)},
{`\s*\n`, Text, Pop(1)},
Include("base"),
},
"comments": {
{`^#.*\n`, CommentSingle, nil}, // Comment at start of line
{`\s+#.*\n`, CommentSingle, nil}, // Comment preceded by whitespace
"double_quotes": {
Include("placeholder"),
{`\\"`, StringDouble, nil},
{`[^"]`, StringDouble, nil},
{`"`, StringDouble, Pop(1)},
},
"comments_pop_1": {
{`^#.*\n`, CommentSingle, Pop(1)}, // Comment at start of line
{`\s+#.*\n`, CommentSingle, Pop(1)}, // Comment preceded by whitespace
"backticks": {
Include("placeholder"),
{"\\\\`", StringBacktick, nil},
{"[^`]", StringBacktick, nil},
{"`", StringBacktick, Pop(1)},
},
"comments_pop_2": {
{`^#.*\n`, CommentSingle, Pop(2)}, // Comment at start of line
{`\s+#.*\n`, CommentSingle, Pop(2)}, // Comment preceded by whitespace
"optional": {
// Docs syntax for showing optional parts with [ ]
{`\[`, Punctuation, Push("optional")},
Include("name_constants"),
{`\|`, Punctuation, nil},
{`[^\[\]\|]+`, String, nil},
{`\]`, Punctuation, Pop(1)},
},
"comments_pop_3": {
{`^#.*\n`, CommentSingle, Pop(3)}, // Comment at start of line
{`\s+#.*\n`, CommentSingle, Pop(3)}, // Comment preceded by whitespace
"heredoc": {
{`(<<([a-zA-Z0-9_-]+))(\n(.*|\n)*)(\s*)(\2)`, ByGroups(StringHeredoc, nil, String, String, String, StringHeredoc), nil},
},
"name_constants": {
{`\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},
},
"placeholder": {
// Placeholder with dots, colon for default value, brackets for args[0:]
{`\{[\w+.\[\]\:\$-]+\}`, StringEscape, nil},
// Handle opening brackets with no matching closing one
{`\{[^\}\s]*\b`, String, nil},
},
"base": {
Include("comments"),
{`(on|off|first|last|before|after|internal|strip_prefix|strip_suffix|replace)\b`, NameConstant, nil},
{`(https?://)?([a-z0-9.-]+)(:)([0-9]+)`, ByGroups(Name, Name, Punctuation, LiteralNumberInteger), nil},
{`[a-z-]+/[a-z-+]+`, LiteralString, nil},
{`[0-9]+[km]?\b`, LiteralNumberInteger, nil},
{`\{[\w+.\$-]+\}`, LiteralStringEscape, nil}, // Placeholder
{`\[(?=[^#{}$]+\])`, Punctuation, nil},
{`\]|\|`, Punctuation, nil},
{`[^\s#{}$\]]+`, LiteralString, nil},
{caddyfileCommentRegexp, CommentSingle, nil},
{`\[\<matcher\>\]`, NameDecorator, nil},
Include("name_constants"),
Include("heredoc"),
{`(https?://)?([a-z0-9.-]+)(:)([0-9]+)([^\s]*)`, ByGroups(Name, Name, Punctuation, NumberInteger, Name), nil},
{`\[`, Punctuation, Push("optional")},
{"`", StringBacktick, Push("backticks")},
{`"`, StringDouble, Push("double_quotes")},
Include("placeholder"),
{`[a-z-]+/[a-z-+]+`, String, nil},
{`[0-9]+([smhdk]|ns|us|µs|ms)?\b`, NumberInteger, nil},
{`[^\s\n#\{]+`, String, nil},
{`/[^\s#]*`, Name, nil},
{`\s+`, Text, nil},
},
@ -149,27 +207,29 @@ var Caddyfile = Register(MustNewLexer(
func caddyfileRules() Rules {
return Rules{
"root": {
Include("comments"),
{caddyfileCommentRegexp, CommentSingle, nil},
// Global options block
{`^\s*(\{)\s*$`, ByGroups(Punctuation), Push("globals")},
// Top level import
{`(import)(\s+)([^\s]+)`, ByGroups(Keyword, Text, NameVariableMagic), nil},
// Snippets
{`(\([^\s#]+\))(\s*)(\{)`, ByGroups(NameVariableAnonymous, Text, Punctuation), Push("snippet")},
{`(&?\([^\s#]+\))(\s*)(\{)`, ByGroups(NameVariableAnonymous, Text, Punctuation), Push("snippet")},
// Site label
{`[^#{(\s,]+`, GenericHeading, Push("label")},
// Site label with placeholder
{`\{[\w+.\$-]+\}`, LiteralStringEscape, Push("label")},
{`\{[\w+.\[\]\:\$-]+\}`, StringEscape, Push("label")},
{`\s+`, Text, nil},
},
"globals": {
{`\}`, Punctuation, Pop(1)},
{`[^\s#]+`, Keyword, Push("directive")},
// Global options are parsed as subdirectives (no matcher)
{`[^\s#]+`, Keyword, Push("subdirective")},
Include("base"),
},
"snippet": {
{`\}`, Punctuation, Pop(1)},
// Matcher definition
{`@[^\s]+(?=\s)`, NameDecorator, Push("matcher")},
// Any directive
Include("site_body"),
// Any other directive
{`[^\s#]+`, Keyword, Push("directive")},
Include("base"),
},
@ -179,7 +239,7 @@ func caddyfileRules() Rules {
{`,\s*\n?`, Text, nil},
{` `, Text, nil},
// Site label with placeholder
{`\{[\w+.\$-]+\}`, LiteralStringEscape, nil},
Include("placeholder"),
// Site label
{`[^#{(\s,]+`, GenericHeading, nil},
// Comment after non-block label (hack because comments end in \n)

View File

@ -4,21 +4,42 @@
on_demand_tls {
ask https://example.com
}
log default {
output file /var/log/caddy/access.log
format json
}
auto_https disable_redirects
renew_interval 20m
# this is a comment
servers 192.168.1.2:8080 {
name public
trusted_proxies static private_ranges
log_credentials
}
}
(blocking) {
# top level comment
(blocking) {
@blocked {
path *.txt *.md *.mdown /site/*
}
redir @blocked /
}
http://example.com {
respond "http"
}
example.com, fake.org, {$ENV_SITE} {
root * /srv
respond /get-env {$ENV_VAR}
respond /get-env {$ENV_VAR:default}
tls off
tls internal
tls /path/to/cert.pem /path/to/key.pem
route {
# Add trailing slash for directory requests
@ -67,6 +88,55 @@ example.com, fake.org, {$ENV_SITE} {
respond @singleLine "Awesome."
import blocking
import blocking foo
import glob/*
file_server
}
@named host example.com
handle @named {
handle /foo* {
handle /foo* {
respond "{path} foo"
}
}
respond "foo"
}
handle_path /foo* {
respond "foo"
}
reverse_proxy /api/* unix//var/run/api.sock {
@good status 200
handle_response @good {
rewrite * /foo{uri}
file_server
}
}
respond <<HTML
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
</head>
<body>
<h1>Hello, world!</h1>
</body>
</html>
HTML 200
@file `file()`
@first `file({'try_files': [{path}, {path} + '/', 'index.html']})`
@smallest `file({'try_policy': 'smallest_size', 'try_files': ['a.txt', 'b.txt']})`
@without-both {
not {
path /api/*
method POST
}
}
path_regexp [<name>] <regexp>
}

View File

@ -16,13 +16,63 @@
{"type":"LiteralString","value":"https://example.com"},
{"type":"Text","value":"\n\t"},
{"type":"Punctuation","value":"}"},
{"type":"Text","value":"\n\t"},
{"type":"Keyword","value":"log"},
{"type":"Text","value":" "},
{"type":"LiteralString","value":"default"},
{"type":"Text","value":" "},
{"type":"Punctuation","value":"{"},
{"type":"Text","value":"\n\t\t"},
{"type":"Keyword","value":"output"},
{"type":"Text","value":" "},
{"type":"LiteralString","value":"file"},
{"type":"Text","value":" "},
{"type":"LiteralString","value":"/var/log/caddy/access.log"},
{"type":"Text","value":"\n\t\t"},
{"type":"Keyword","value":"format"},
{"type":"Text","value":" "},
{"type":"LiteralString","value":"json"},
{"type":"Text","value":"\n\t"},
{"type":"Punctuation","value":"}"},
{"type":"Text","value":"\n\t"},
{"type":"Keyword","value":"auto_https"},
{"type":"Text","value":" "},
{"type":"NameConstant","value":"disable_redirects"},
{"type":"Text","value":"\n\t"},
{"type":"Keyword","value":"renew_interval"},
{"type":"Text","value":" "},
{"type":"LiteralNumberInteger","value":"20m"},
{"type":"CommentSingle","value":"\n\n\t# this is a comment\n"},
{"type":"Text","value":"\t"},
{"type":"Keyword","value":"servers"},
{"type":"Text","value":" "},
{"type":"Name","value":"192.168.1.2"},
{"type":"Punctuation","value":":"},
{"type":"LiteralNumberInteger","value":"8080"},
{"type":"Text","value":" "},
{"type":"Punctuation","value":"{"},
{"type":"Text","value":"\n\t\t"},
{"type":"Keyword","value":"name"},
{"type":"Text","value":" "},
{"type":"LiteralString","value":"public"},
{"type":"Text","value":"\n\t\t"},
{"type":"Keyword","value":"trusted_proxies"},
{"type":"Text","value":" "},
{"type":"LiteralString","value":"static"},
{"type":"Text","value":" "},
{"type":"NameConstant","value":"private_ranges"},
{"type":"Text","value":"\n\t\t"},
{"type":"Keyword","value":"log_credentials"},
{"type":"Text","value":"\n\t"},
{"type":"Punctuation","value":"}"},
{"type":"Text","value":"\n"},
{"type":"Punctuation","value":"}"},
{"type":"Text","value":"\n\n"},
{"type":"CommentSingle","value":"\n\n# top level comment\n"},
{"type":"Text","value":"\n"},
{"type":"NameVariableAnonymous","value":"(blocking)"},
{"type":"Text","value":" "},
{"type":"Punctuation","value":"{"},
{"type":"Text","value":" \n\t"},
{"type":"Text","value":"\n\t"},
{"type":"NameDecorator","value":"@blocked"},
{"type":"Text","value":" "},
{"type":"Punctuation","value":"{"},
@ -47,6 +97,16 @@
{"type":"Text","value":"\n"},
{"type":"Punctuation","value":"}"},
{"type":"Text","value":"\n\n"},
{"type":"GenericHeading","value":"http://example.com"},
{"type":"Text","value":" "},
{"type":"Punctuation","value":"{"},
{"type":"Text","value":"\n\t"},
{"type":"Keyword","value":"respond"},
{"type":"Text","value":" "},
{"type":"LiteralStringDouble","value":"\"http\""},
{"type":"Text","value":"\n"},
{"type":"Punctuation","value":"}"},
{"type":"Text","value":"\n\n"},
{"type":"GenericHeading","value":"example.com"},
{"type":"Text","value":", "},
{"type":"GenericHeading","value":"fake.org"},
@ -66,10 +126,22 @@
{"type":"NameDecorator","value":"/get-env"},
{"type":"Text","value":" "},
{"type":"LiteralStringEscape","value":"{$ENV_VAR}"},
{"type":"Text","value":"\n\t"},
{"type":"Keyword","value":"respond"},
{"type":"Text","value":" "},
{"type":"NameDecorator","value":"/get-env"},
{"type":"Text","value":" "},
{"type":"LiteralStringEscape","value":"{$ENV_VAR:default}"},
{"type":"Text","value":"\n\n\t"},
{"type":"Keyword","value":"tls"},
{"type":"Text","value":" "},
{"type":"NameConstant","value":"off"},
{"type":"NameConstant","value":"internal"},
{"type":"Text","value":"\n\t"},
{"type":"Keyword","value":"tls"},
{"type":"Text","value":" "},
{"type":"LiteralString","value":"/path/to/cert.pem"},
{"type":"Text","value":" "},
{"type":"LiteralString","value":"/path/to/key.pem"},
{"type":"Text","value":"\n\n\t"},
{"type":"Keyword","value":"route"},
{"type":"Text","value":" "},
@ -203,9 +275,7 @@
{"type":"Text","value":"\n\t\t"},
{"type":"Keyword","value":"X-XSS-Protection"},
{"type":"Text","value":" "},
{"type":"LiteralString","value":"\"1;"},
{"type":"Text","value":" "},
{"type":"LiteralString","value":"mode=block\""},
{"type":"LiteralStringDouble","value":"\"1; mode=block\""},
{"type":"Text","value":"\n\t\t"},
{"type":"Keyword","value":"X-Robots-Tag"},
{"type":"Text","value":" "},
@ -213,9 +283,7 @@
{"type":"Text","value":"\n\t\t"},
{"type":"Keyword","value":"Content-Security-Policy"},
{"type":"Text","value":" "},
{"type":"LiteralString","value":"\"frame-ancestors"},
{"type":"Text","value":" "},
{"type":"LiteralString","value":"'self'\""},
{"type":"LiteralStringDouble","value":"\"frame-ancestors 'self'\""},
{"type":"Text","value":"\n\t\t"},
{"type":"Keyword","value":"X-Frame-Options"},
{"type":"Text","value":" "},
@ -239,13 +307,159 @@
{"type":"Text","value":" "},
{"type":"NameDecorator","value":"@singleLine"},
{"type":"Text","value":" "},
{"type":"LiteralString","value":"\"Awesome.\""},
{"type":"LiteralStringDouble","value":"\"Awesome.\""},
{"type":"Text","value":"\n\n\t"},
{"type":"Keyword","value":"import"},
{"type":"Text","value":" "},
{"type":"NameVariableMagic","value":"blocking"},
{"type":"Text","value":"\n\n\t"},
{"type":"Text","value":" blocking\n\t"},
{"type":"Keyword","value":"import"},
{"type":"Text","value":" blocking "},
{"type":"LiteralString","value":"foo"},
{"type":"Text","value":"\n\t"},
{"type":"Keyword","value":"import"},
{"type":"Text","value":" glob/*\n\n\t"},
{"type":"Keyword","value":"file_server"},
{"type":"Text","value":"\n\n\t"},
{"type":"NameDecorator","value":"@named"},
{"type":"Text","value":" "},
{"type":"Keyword","value":"host"},
{"type":"Text","value":" "},
{"type":"LiteralString","value":"example.com"},
{"type":"Text","value":"\n\t"},
{"type":"Keyword","value":"handle"},
{"type":"Text","value":" "},
{"type":"NameDecorator","value":"@named"},
{"type":"Text","value":" "},
{"type":"Punctuation","value":"{"},
{"type":"Text","value":"\n\t\t"},
{"type":"Keyword","value":"handle"},
{"type":"Text","value":" "},
{"type":"NameDecorator","value":"/foo*"},
{"type":"Text","value":" "},
{"type":"Punctuation","value":"{"},
{"type":"Text","value":"\n\t\t\t"},
{"type":"Keyword","value":"handle"},
{"type":"Text","value":" "},
{"type":"NameDecorator","value":"/foo*"},
{"type":"Text","value":" "},
{"type":"Punctuation","value":"{"},
{"type":"Text","value":"\n\t\t\t\t"},
{"type":"Keyword","value":"respond"},
{"type":"Text","value":" "},
{"type":"LiteralStringDouble","value":"\""},
{"type":"LiteralStringEscape","value":"{path}"},
{"type":"LiteralStringDouble","value":" foo\""},
{"type":"Text","value":"\n\t\t\t"},
{"type":"Punctuation","value":"}"},
{"type":"Text","value":"\n\t\t"},
{"type":"Punctuation","value":"}"},
{"type":"Text","value":"\n\t\t"},
{"type":"Keyword","value":"respond"},
{"type":"Text","value":" "},
{"type":"LiteralStringDouble","value":"\"foo\""},
{"type":"Text","value":"\n\t"},
{"type":"Punctuation","value":"}"},
{"type":"Text","value":"\n\n\t"},
{"type":"Keyword","value":"handle_path"},
{"type":"Text","value":" "},
{"type":"NameDecorator","value":"/foo*"},
{"type":"Text","value":" "},
{"type":"Punctuation","value":"{"},
{"type":"Text","value":"\n\t\t"},
{"type":"Keyword","value":"respond"},
{"type":"Text","value":" "},
{"type":"LiteralStringDouble","value":"\"foo\""},
{"type":"Text","value":"\n\t"},
{"type":"Punctuation","value":"}"},
{"type":"Text","value":"\n\n\t"},
{"type":"Keyword","value":"reverse_proxy"},
{"type":"Text","value":" "},
{"type":"NameDecorator","value":"/api/*"},
{"type":"Text","value":" "},
{"type":"LiteralString","value":"unix//var/run/api.sock"},
{"type":"Text","value":" "},
{"type":"Punctuation","value":"{"},
{"type":"Text","value":"\n\t\t"},
{"type":"NameDecorator","value":"@good"},
{"type":"Text","value":" "},
{"type":"Keyword","value":"status"},
{"type":"Text","value":" "},
{"type":"LiteralNumberInteger","value":"200"},
{"type":"Text","value":"\n\t\t"},
{"type":"Keyword","value":"handle_response"},
{"type":"Text","value":" "},
{"type":"NameDecorator","value":"@good"},
{"type":"Text","value":" "},
{"type":"Punctuation","value":"{"},
{"type":"Text","value":"\n\t\t\t"},
{"type":"Keyword","value":"rewrite"},
{"type":"Text","value":" "},
{"type":"NameDecorator","value":"*"},
{"type":"Text","value":" "},
{"type":"LiteralString","value":"/foo"},
{"type":"LiteralStringEscape","value":"{uri}"},
{"type":"Text","value":"\n\t\t\t"},
{"type":"Keyword","value":"file_server"},
{"type":"Text","value":"\n\t\t"},
{"type":"Punctuation","value":"}"},
{"type":"Text","value":"\n\t"},
{"type":"Punctuation","value":"}"},
{"type":"Text","value":"\n\n\t"},
{"type":"Keyword","value":"respond"},
{"type":"Text","value":" "},
{"type":"LiteralStringHeredoc","value":"\u003c\u003cHTML"},
{"type":"LiteralString","value":"\n\t\t\u003c!DOCTYPE html\u003e\n\t\t\u003chtml\u003e\n\t\t\t\u003chead\u003e\n\t\t\t\t\u003ctitle\u003eTest\u003c/title\u003e\n\t\t\t\u003c/head\u003e\n\t\t\t\u003cbody\u003e\n\t\t\t\t\u003ch1\u003eHello, world!\u003c/h1\u003e\n\t\t\t\u003c/body\u003e\n\t\t\u003c/html\u003e\n\t\t"},
{"type":"LiteralStringHeredoc","value":"HTML"},
{"type":"Text","value":" "},
{"type":"LiteralNumberInteger","value":"200"},
{"type":"Text","value":"\n\n\t"},
{"type":"NameDecorator","value":"@file"},
{"type":"Text","value":" "},
{"type":"LiteralStringBacktick","value":"`file()`"},
{"type":"Text","value":"\n\t"},
{"type":"NameDecorator","value":"@first"},
{"type":"Text","value":" "},
{"type":"LiteralStringBacktick","value":"`file("},
{"type":"LiteralString","value":"{'try_files"},
{"type":"LiteralStringBacktick","value":"': ["},
{"type":"LiteralStringEscape","value":"{path}"},
{"type":"LiteralStringBacktick","value":", "},
{"type":"LiteralStringEscape","value":"{path}"},
{"type":"LiteralStringBacktick","value":" + '/', 'index.html']})`"},
{"type":"Text","value":"\n\t"},
{"type":"NameDecorator","value":"@smallest"},
{"type":"Text","value":" "},
{"type":"LiteralStringBacktick","value":"`file("},
{"type":"LiteralString","value":"{'try_policy"},
{"type":"LiteralStringBacktick","value":"': 'smallest_size', 'try_files': ['a.txt', 'b.txt']})`"},
{"type":"Text","value":"\n\n\t"},
{"type":"NameDecorator","value":"@without-both"},
{"type":"Text","value":" "},
{"type":"Punctuation","value":"{"},
{"type":"Text","value":"\n\t\t"},
{"type":"Keyword","value":"not"},
{"type":"Text","value":" "},
{"type":"Punctuation","value":"{"},
{"type":"Text","value":"\n\t\t\t"},
{"type":"Keyword","value":"path"},
{"type":"Text","value":" "},
{"type":"LiteralString","value":"/api/*"},
{"type":"Text","value":"\n\t\t\t"},
{"type":"Keyword","value":"method"},
{"type":"Text","value":" "},
{"type":"LiteralString","value":"POST"},
{"type":"Text","value":"\n\t\t"},
{"type":"Punctuation","value":"}"},
{"type":"Text","value":"\n\t"},
{"type":"Punctuation","value":"}"},
{"type":"Text","value":"\n\n\t"},
{"type":"Keyword","value":"path_regexp"},
{"type":"Text","value":" "},
{"type":"Punctuation","value":"["},
{"type":"LiteralString","value":"\u003cname\u003e"},
{"type":"Punctuation","value":"]"},
{"type":"Text","value":" "},
{"type":"LiteralString","value":"\u003cregexp\u003e"},
{"type":"Text","value":"\n"},
{"type":"Punctuation","value":"}"}
{"type":"Punctuation","value":"}"},
{"type":"Text","value":"\n"}
]