diff --git a/lexers/c/caddyfile.go b/lexers/c/caddyfile.go index e8e6e9f3a..219c90fca 100644 --- a/lexers/c/caddyfile.go +++ b/lexers/c/caddyfile.go @@ -5,15 +5,20 @@ import ( "github.com/alecthomas/chroma/lexers/internal" ) -// CaddyfileCommon are the rules common to both of the lexer variants -var CaddyfileCommon = Rules{ +// caddyfileCommon are the rules common to both of the lexer variants +var caddyfileCommon = Rules{ "site_block_common": { // Import keyword {`(import)(\s+)([^\s]+)`, ByGroups(Keyword, Text, NameVariableMagic), nil}, // Matcher definition - {`@[^\s]+\s+`, NameDecorator, Push("matcher")}, + {`@[^\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")}, // These are special, they can nest more directives - {`handle|route|handle_path|not`, Keyword, Push("nested_directive")}, + {`handle_errors|handle|route|handle_path|not`, Keyword, Push("nested_directive")}, // Any other directive {`[^\s#]+`, Keyword, Push("directive")}, Include("base"), @@ -40,7 +45,9 @@ var CaddyfileCommon = Rules{ "nested_block": { {`\}`, Punctuation, Pop(2)}, // Matcher definition - {`@[^\s]+\s+`, NameDecorator, Push("matcher")}, + {`@[^\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")}, Include("base"), @@ -60,32 +67,33 @@ var CaddyfileCommon = Rules{ "directive": { {`\{(?=\s)`, Punctuation, Push("block")}, Include("matcher_token"), - Include("comments_pop"), + Include("comments_pop_1"), {`\n`, Text, Pop(1)}, Include("base"), }, "nested_directive": { {`\{(?=\s)`, Punctuation, Push("nested_block")}, Include("matcher_token"), - Include("comments_pop"), + Include("comments_pop_1"), {`\n`, Text, Pop(1)}, Include("base"), }, "subdirective": { {`\{(?=\s)`, Punctuation, Push("block")}, - Include("comments_pop"), + Include("comments_pop_1"), {`\n`, Text, Pop(1)}, Include("base"), }, "arguments": { {`\{(?=\s)`, Punctuation, Push("block")}, - Include("comments_pop"), + Include("comments_pop_2"), + {`\\\n`, Text, nil}, // Skip escaped newlines {`\n`, Text, Pop(2)}, Include("base"), }, "deep_subdirective": { {`\{(?=\s)`, Punctuation, Push("block")}, - Include("comments_pop"), + Include("comments_pop_3"), {`\n`, Text, Pop(3)}, Include("base"), }, @@ -99,30 +107,41 @@ var CaddyfileCommon = Rules{ {`^#.*\n`, CommentSingle, nil}, // Comment at start of line {`\s+#.*\n`, CommentSingle, nil}, // Comment preceded by whitespace }, - "comments_pop": { + "comments_pop_1": { {`^#.*\n`, CommentSingle, Pop(1)}, // Comment at start of line {`\s+#.*\n`, CommentSingle, Pop(1)}, // Comment preceded by whitespace }, + "comments_pop_2": { + {`^#.*\n`, CommentSingle, Pop(2)}, // Comment at start of line + {`\s+#.*\n`, CommentSingle, Pop(2)}, // Comment preceded by whitespace + }, + "comments_pop_3": { + {`^#.*\n`, CommentSingle, Pop(3)}, // Comment at start of line + {`\s+#.*\n`, CommentSingle, Pop(3)}, // Comment preceded by whitespace + }, "base": { Include("comments"), - {`on|off`, NameConstant, nil}, + {`(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+.-]+\}`, NameAttribute, nil}, // Placeholder - {`[^\s#{}$]+`, LiteralString, nil}, + {`\{[\w+.\$-]+\}`, LiteralStringEscape, nil}, // Placeholder + {`\[(?=[^#{}$]+\])`, Punctuation, nil}, + {`\]|\|`, Punctuation, nil}, + {`[^\s#{}$\]]+`, LiteralString, nil}, {`/[^\s#]*`, Name, nil}, {`\s+`, Text, nil}, }, } -// CaddyfileRules are the merged rules for the main Caddyfile lexer -var CaddyfileRules = (func(a Rules, b Rules) Rules { - for k, v := range b { - a[k] = v - } - return a -})( +// Caddyfile lexer. +var Caddyfile = internal.Register(MustNewLexer( + &Config{ + Name: "Caddyfile", + Aliases: []string{"caddyfile", "caddy"}, + Filenames: []string{"Caddyfile*"}, + MimeTypes: []string{}, + }, Rules{ "root": { Include("comments"), @@ -131,28 +150,33 @@ var CaddyfileRules = (func(a Rules, b Rules) Rules { // Snippets {`(\([^\s#]+\))(\s*)(\{)`, ByGroups(NameVariableAnonymous, Text, Punctuation), Push("snippet")}, // Site label - {`[^#{(\s,]+`, NameLabel, Push("label")}, + {`[^#{(\s,]+`, GenericHeading, Push("label")}, // Site label with placeholder - {`\{[\w+.-]+\}`, NameAttribute, Push("label")}, + {`\{[\w+.\$-]+\}`, LiteralStringEscape, Push("label")}, {`\s+`, Text, nil}, }, "globals": { {`\}`, Punctuation, Pop(1)}, - {`[^\s#]+`, KeywordType, Push("directive")}, + {`[^\s#]+`, Keyword, Push("directive")}, Include("base"), }, "snippet": { {`\}`, Punctuation, Pop(1)}, // Matcher definition - {`@[^\s]+\s+`, NameDecorator, Push("matcher")}, + {`@[^\s]+(?=\s)`, NameDecorator, Push("matcher")}, // Any directive - {`[^\s#]+`, KeywordType, Push("directive")}, + {`[^\s#]+`, Keyword, Push("directive")}, Include("base"), }, "label": { - {`,`, Text, nil}, + // Allow multiple labels, comma separated, newlines after + // a comma means another label is coming + {`,\s*\n?`, Text, nil}, {` `, Text, nil}, - {`[^#{(\s,]+`, NameLabel, nil}, + // Site label with placeholder + {`\{[\w+.\$-]+\}`, LiteralStringEscape, nil}, + // Site label + {`[^#{(\s,]+`, GenericHeading, nil}, // Comment after non-block label (hack because comments end in \n) {`#.*\n`, CommentSingle, Push("site_block")}, // Note: if \n, we'll never pop out of the site_block, it's valid @@ -162,35 +186,7 @@ var CaddyfileRules = (func(a Rules, b Rules) Rules { {`\}`, Punctuation, Pop(2)}, Include("site_block_common"), }, - }, - CaddyfileCommon, -) - -// CaddyfileDirectiveRules are the merged rules for the secondary lexer -var CaddyfileDirectiveRules = (func(a Rules, b Rules) Rules { - for k, v := range b { - a[k] = v - } - return a -})( - Rules{ - // Same as "site_block" in Caddyfile - "root": { - Include("site_block_common"), - }, - }, - CaddyfileCommon, -) - -// Caddyfile lexer. -var Caddyfile = internal.Register(MustNewLexer( - &Config{ - Name: "Caddyfile", - Aliases: []string{"caddyfile", "caddy"}, - Filenames: []string{"Caddyfile*"}, - MimeTypes: []string{}, - }, - CaddyfileRules, + }.Merge(caddyfileCommon), )) // Caddyfile directive-only lexer. @@ -201,5 +197,10 @@ var CaddyfileDirectives = internal.Register(MustNewLexer( Filenames: []string{}, MimeTypes: []string{}, }, - CaddyfileDirectiveRules, + Rules{ + // Same as "site_block" in Caddyfile + "root": { + Include("site_block_common"), + }, + }.Merge(caddyfileCommon), )) diff --git a/lexers/testdata/caddyfile.actual b/lexers/testdata/caddyfile.actual index 9f6767efb..d4214209a 100644 --- a/lexers/testdata/caddyfile.actual +++ b/lexers/testdata/caddyfile.actual @@ -13,9 +13,11 @@ redir @blocked / } -example.com, fake.org { +example.com, fake.org, {$ENV_SITE} { root * /srv + respond /get-env {$ENV_VAR} + tls off route { diff --git a/lexers/testdata/caddyfile.expected b/lexers/testdata/caddyfile.expected index 989fd9481..97db4a198 100644 --- a/lexers/testdata/caddyfile.expected +++ b/lexers/testdata/caddyfile.expected @@ -1,13 +1,13 @@ [ {"type":"Punctuation","value":"{"}, {"type":"Text","value":"\n\t"}, - {"type":"KeywordType","value":"debug"}, + {"type":"Keyword","value":"debug"}, {"type":"Text","value":"\n\t"}, - {"type":"KeywordType","value":"admin"}, + {"type":"Keyword","value":"admin"}, {"type":"Text","value":" "}, {"type":"NameConstant","value":"off"}, {"type":"Text","value":"\n\t"}, - {"type":"KeywordType","value":"on_demand_tls"}, + {"type":"Keyword","value":"on_demand_tls"}, {"type":"Text","value":" "}, {"type":"Punctuation","value":"{"}, {"type":"Text","value":"\n\t\t"}, @@ -23,7 +23,8 @@ {"type":"Text","value":" "}, {"type":"Punctuation","value":"{"}, {"type":"Text","value":" \n\t"}, - {"type":"NameDecorator","value":"@blocked "}, + {"type":"NameDecorator","value":"@blocked"}, + {"type":"Text","value":" "}, {"type":"Punctuation","value":"{"}, {"type":"Text","value":"\n\t\t"}, {"type":"Keyword","value":"path"}, @@ -38,7 +39,7 @@ {"type":"Text","value":"\n\t"}, {"type":"Punctuation","value":"}"}, {"type":"Text","value":"\n\t"}, - {"type":"KeywordType","value":"redir"}, + {"type":"Keyword","value":"redir"}, {"type":"Text","value":" "}, {"type":"NameDecorator","value":"@blocked"}, {"type":"Text","value":" "}, @@ -46,9 +47,11 @@ {"type":"Text","value":"\n"}, {"type":"Punctuation","value":"}"}, {"type":"Text","value":"\n\n"}, - {"type":"NameLabel","value":"example.com"}, + {"type":"GenericHeading","value":"example.com"}, + {"type":"Text","value":", "}, + {"type":"GenericHeading","value":"fake.org"}, {"type":"Text","value":", "}, - {"type":"NameLabel","value":"fake.org"}, + {"type":"LiteralStringEscape","value":"{$ENV_SITE}"}, {"type":"Text","value":" "}, {"type":"Punctuation","value":"{"}, {"type":"Text","value":"\n\t"}, @@ -58,6 +61,12 @@ {"type":"Text","value":" "}, {"type":"LiteralString","value":"/srv"}, {"type":"Text","value":"\n\n\t"}, + {"type":"Keyword","value":"respond"}, + {"type":"Text","value":" "}, + {"type":"NameDecorator","value":"/get-env"}, + {"type":"Text","value":" "}, + {"type":"LiteralStringEscape","value":"{$ENV_VAR}"}, + {"type":"Text","value":"\n\n\t"}, {"type":"Keyword","value":"tls"}, {"type":"Text","value":" "}, {"type":"NameConstant","value":"off"}, @@ -67,7 +76,8 @@ {"type":"Punctuation","value":"{"}, {"type":"CommentSingle","value":"\n\t\t# Add trailing slash for directory requests\n"}, {"type":"Text","value":"\t\t"}, - {"type":"NameDecorator","value":"@canonicalPath "}, + {"type":"NameDecorator","value":"@canonicalPath"}, + {"type":"Text","value":" "}, {"type":"Punctuation","value":"{"}, {"type":"Text","value":"\n\t\t\t"}, {"type":"Keyword","value":"file"}, @@ -76,7 +86,7 @@ {"type":"Text","value":"\n\t\t\t\t"}, {"type":"Keyword","value":"try_files"}, {"type":"Text","value":" "}, - {"type":"NameAttribute","value":"{path}"}, + {"type":"LiteralStringEscape","value":"{path}"}, {"type":"LiteralString","value":"/index.php"}, {"type":"Text","value":"\n\t\t\t"}, {"type":"Punctuation","value":"}"}, @@ -93,7 +103,7 @@ {"type":"Text","value":" "}, {"type":"NameDecorator","value":"@canonicalPath"}, {"type":"Text","value":" "}, - {"type":"NameAttribute","value":"{path}"}, + {"type":"LiteralStringEscape","value":"{path}"}, {"type":"LiteralString","value":"/"}, {"type":"Text","value":" "}, {"type":"LiteralNumberInteger","value":"308"}, @@ -109,9 +119,9 @@ {"type":"Text","value":"\n\t\t\t\t"}, {"type":"Keyword","value":"try_files"}, {"type":"Text","value":" "}, - {"type":"NameAttribute","value":"{path}"}, + {"type":"LiteralStringEscape","value":"{path}"}, {"type":"Text","value":" "}, - {"type":"NameAttribute","value":"{path}"}, + {"type":"LiteralStringEscape","value":"{path}"}, {"type":"LiteralString","value":"/index.php"}, {"type":"Text","value":" "}, {"type":"LiteralString","value":"index.php"}, @@ -128,7 +138,7 @@ {"type":"Text","value":" "}, {"type":"NameDecorator","value":"@indexFiles"}, {"type":"Text","value":" "}, - {"type":"NameAttribute","value":"{http.matchers.file.relative}"}, + {"type":"LiteralStringEscape","value":"{http.matchers.file.relative}"}, {"type":"CommentSingle","value":"\n\n\t\t# Proxy PHP files to the FastCGI responder\n"}, {"type":"Text","value":"\t\t"}, {"type":"NameDecorator","value":"@phpFiles"}, @@ -165,7 +175,8 @@ {"type":"Text","value":"\n\t"}, {"type":"Punctuation","value":"}"}, {"type":"Text","value":"\n\n\t"}, - {"type":"NameDecorator","value":"@encode_exts "}, + {"type":"NameDecorator","value":"@encode_exts"}, + {"type":"Text","value":" "}, {"type":"Punctuation","value":"{"}, {"type":"Text","value":"\n\t\t"}, {"type":"Keyword","value":"path"}, @@ -216,7 +227,8 @@ {"type":"Text","value":"\n\t"}, {"type":"Punctuation","value":"}"}, {"type":"Text","value":"\n\n\t"}, - {"type":"NameDecorator","value":"@singleLine "}, + {"type":"NameDecorator","value":"@singleLine"}, + {"type":"Text","value":" "}, {"type":"Keyword","value":"not"}, {"type":"Text","value":" "}, {"type":"Keyword","value":"path"},