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. }