diff --git a/.editorconfig b/.editorconfig index d80374e07..cfb2c669e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -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 diff --git a/lexers/caddyfile.go b/lexers/caddyfile.go index 9100efa82..82a7efa48 100644 --- a/lexers/caddyfile.go +++ b/lexers/caddyfile.go @@ -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 = `(\[\\]|@[^\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 {`\[\\]`, 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"), + }, + "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"), }, - "matcher_token": { - {`@[^\s]+`, NameDecorator, Push("arguments")}, // Named matcher - {`/[^\s]+`, NameDecorator, Push("arguments")}, // Path matcher - {`\*`, NameDecorator, Push("arguments")}, // Wildcard path matcher - {`\[\\]`, NameDecorator, Push("arguments")}, // Matcher token stub for docs + "double_quotes": { + Include("placeholder"), + {`\\"`, StringDouble, nil}, + {`[^"]`, StringDouble, nil}, + {`"`, StringDouble, Pop(1)}, }, - "comments": { - {`^#.*\n`, CommentSingle, nil}, // Comment at start of line - {`\s+#.*\n`, CommentSingle, nil}, // Comment preceded by whitespace + "backticks": { + Include("placeholder"), + {"\\\\`", StringBacktick, nil}, + {"[^`]", StringBacktick, nil}, + {"`", StringBacktick, Pop(1)}, }, - "comments_pop_1": { - {`^#.*\n`, CommentSingle, Pop(1)}, // Comment at start of line - {`\s+#.*\n`, CommentSingle, Pop(1)}, // 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_2": { - {`^#.*\n`, CommentSingle, Pop(2)}, // Comment at start of line - {`\s+#.*\n`, CommentSingle, Pop(2)}, // Comment preceded by whitespace + "heredoc": { + {`(<<([a-zA-Z0-9_-]+))(\n(.*|\n)*)(\s*)(\2)`, ByGroups(StringHeredoc, nil, String, String, String, StringHeredoc), nil}, }, - "comments_pop_3": { - {`^#.*\n`, CommentSingle, Pop(3)}, // Comment at start of line - {`\s+#.*\n`, CommentSingle, Pop(3)}, // Comment preceded by whitespace + "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}, + {`\[\\]`, 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) diff --git a/lexers/testdata/caddyfile.actual b/lexers/testdata/caddyfile.actual index d4214209a..125bdd322 100644 --- a/lexers/testdata/caddyfile.actual +++ b/lexers/testdata/caddyfile.actual @@ -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 -} \ No newline at end of file + + @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 < + + + Test + + +

Hello, world!

+ + + 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 [] +} diff --git a/lexers/testdata/caddyfile.expected b/lexers/testdata/caddyfile.expected index 97db4a198..217e7de12 100644 --- a/lexers/testdata/caddyfile.expected +++ b/lexers/testdata/caddyfile.expected @@ -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":" 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":"NameVariableMagic","value":"blocking"}, + {"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"} ]