summaryrefslogtreecommitdiff
path: root/caddyconfig/caddyfile/formatter.go
diff options
context:
space:
mode:
authorMatthew Holt <mholt@users.noreply.github.com>2020-03-25 18:45:54 -0600
committerMatthew Holt <mholt@users.noreply.github.com>2020-03-25 18:45:54 -0600
commit7ee3ab7baa2165990d3fd358878d818154f7ee86 (patch)
treebf182a9059a1c927ecdf61300a48ebdb71e29d47 /caddyconfig/caddyfile/formatter.go
parentba08833b2acceb054177149f1de1c45a548bd36b (diff)
caddyfile: Formatter enhancements
Diffstat (limited to 'caddyconfig/caddyfile/formatter.go')
-rw-r--r--caddyconfig/caddyfile/formatter.go243
1 files changed, 154 insertions, 89 deletions
diff --git a/caddyconfig/caddyfile/formatter.go b/caddyconfig/caddyfile/formatter.go
index 82b8b3c..bf70d11 100644
--- a/caddyconfig/caddyfile/formatter.go
+++ b/caddyconfig/caddyfile/formatter.go
@@ -20,129 +20,194 @@ import (
"unicode"
)
-// Format formats a Caddyfile to conventional standards.
-func Format(body []byte) []byte {
- reader := bytes.NewReader(body)
- result := new(bytes.Buffer)
+// Format formats the input Caddyfile to a standard, nice-looking
+// appearance. It works by reading each rune of the input and taking
+// control over all the bracing and whitespace that is written; otherwise,
+// words, comments, placeholders, and escaped characters are all treated
+// literally and written as they appear in the input.
+func Format(input []byte) []byte {
+ input = bytes.TrimSpace(input)
+
+ out := new(bytes.Buffer)
+ rdr := bytes.NewReader(input)
var (
- commented,
- quoted,
- escaped,
- environ,
- lineBegin bool
+ last rune // the last character that was written to the result
+
+ space = true // whether current/previous character was whitespace (beginning of input counts as space)
+ beginningOfLine = true // whether we are at beginning of line
- firstIteration = true
+ openBrace bool // whether current word/token is or started with open curly brace
+ openBraceWritten bool // if openBrace, whether that brace was written or not
- indentation = 0
+ newLines int // count of newlines consumed
- prev,
- curr,
- next rune
+ comment bool // whether we're in a comment
+ quoted bool // whether we're in a quoted segment
+ escaped bool // whether current char is escaped
- err error
+ nesting int // indentation level
)
- insertTabs := func(num int) {
- for tabs := num; tabs > 0; tabs-- {
- result.WriteRune('\t')
- }
+ write := func(ch rune) {
+ out.WriteRune(ch)
+ last = ch
}
- for {
- prev = curr
- curr = next
-
- if curr < 0 {
- break
+ indent := func() {
+ for tabs := nesting; tabs > 0; tabs-- {
+ write('\t')
}
+ }
- next, _, err = reader.ReadRune()
+ nextLine := func() {
+ write('\n')
+ beginningOfLine = true
+ }
+
+ for {
+ ch, _, err := rdr.ReadRune()
if err != nil {
if err == io.EOF {
- next = -1
+ break
+ }
+ panic(err)
+ }
+
+ if comment {
+ if ch == '\n' {
+ comment = false
} else {
- panic(err)
+ write(ch)
+ continue
+ }
+ }
+
+ if !escaped && ch == '\\' {
+ if space {
+ write(' ')
+ space = false
}
+ write(ch)
+ escaped = true
+ continue
}
- if firstIteration {
- firstIteration = false
- lineBegin = true
+ if escaped {
+ write(ch)
+ escaped = false
continue
}
if quoted {
- if escaped {
- escaped = false
- } else {
- if curr == '\\' {
- escaped = true
- }
- if curr == '"' {
- quoted = false
- }
- }
- if curr == '\n' {
+ if ch == '"' {
quoted = false
}
- } else if commented {
- if curr == '\n' {
- commented = false
+ write(ch)
+ continue
+ }
+
+ if space && ch == '"' {
+ quoted = true
+ }
+
+ if unicode.IsSpace(ch) {
+ space = true
+ if ch == '\n' {
+ newLines++
}
- } else {
- if curr == '"' {
- quoted = true
+ continue
+ }
+ spacePrior := space
+ space = false
+
+ //////////////////////////////////////////////////////////
+ // I find it helpful to think of the formatting loop in two
+ // main sections; by the time we reach this point, we
+ // know we are in a "regular" part of the file: we know
+ // the character is not a space, not in a literal segment
+ // like a comment or quoted, it's not escaped, etc.
+ //////////////////////////////////////////////////////////
+
+ if ch == '#' {
+ if !spacePrior && !beginningOfLine {
+ write(' ')
}
- if curr == '#' {
- commented = true
+ comment = true
+ }
+
+ if openBrace && spacePrior && !openBraceWritten {
+ if nesting == 0 && last == '}' {
+ nextLine()
+ nextLine()
}
- if curr == '}' {
- if environ {
- environ = false
- } else if indentation > 0 {
- indentation--
- }
+
+ openBrace = false
+ if beginningOfLine {
+ indent()
+ } else {
+ write(' ')
}
- if curr == '{' {
- if unicode.IsSpace(next) {
- indentation++
-
- if !unicode.IsSpace(prev) && !lineBegin {
- result.WriteRune(' ')
- }
- } else {
- environ = true
- }
+ write('{')
+ nextLine()
+ newLines = 0
+ nesting++
+ }
+
+ switch {
+ case ch == '{':
+ openBrace = true
+ openBraceWritten = false
+ continue
+
+ case ch == '}' && (spacePrior || !openBrace):
+ if last != '\n' {
+ nextLine()
}
- if lineBegin {
- if curr == ' ' || curr == '\t' {
- continue
- } else {
- lineBegin = false
- if curr == '{' && unicode.IsSpace(next) {
- // If the block is global, i.e., starts with '{'
- // One less indentation for these blocks.
- insertTabs(indentation - 1)
- } else {
- insertTabs(indentation)
- }
- }
- } else {
- if prev == '{' &&
- (curr == ' ' || curr == '\t') &&
- (next != '\n' && next != '\r') {
- curr = '\n'
- }
+ if nesting > 0 {
+ nesting--
}
+ indent()
+ write('}')
+ newLines = 0
+ continue
}
- if curr == '\n' {
- lineBegin = true
+ if newLines > 2 {
+ newLines = 2
+ }
+ for i := 0; i < newLines; i++ {
+ nextLine()
+ }
+ newLines = 0
+ if beginningOfLine {
+ indent()
+ }
+ if nesting == 0 && last == '}' {
+ nextLine()
+ nextLine()
}
- result.WriteRune(curr)
+ if !beginningOfLine && spacePrior {
+ write(' ')
+ }
+
+ if openBrace && !openBraceWritten {
+ if !beginningOfLine {
+ write(' ')
+ }
+ write('{')
+ openBraceWritten = true
+ }
+ write(ch)
+
+ beginningOfLine = false
}
- return result.Bytes()
+ // the Caddyfile does not need any leading or trailing spaces, but...
+ trimmedResult := bytes.TrimSpace(out.Bytes())
+
+ // ...Caddyfiles should, however, end with a newline because
+ // newlines are significant to the syntax of the file
+ return append(trimmedResult, '\n')
}