diff options
| author | Matthew Holt <mholt@users.noreply.github.com> | 2020-03-25 18:45:54 -0600 | 
|---|---|---|
| committer | Matthew Holt <mholt@users.noreply.github.com> | 2020-03-25 18:45:54 -0600 | 
| commit | 7ee3ab7baa2165990d3fd358878d818154f7ee86 (patch) | |
| tree | bf182a9059a1c927ecdf61300a48ebdb71e29d47 /caddyconfig/caddyfile/formatter.go | |
| parent | ba08833b2acceb054177149f1de1c45a548bd36b (diff) | |
caddyfile: Formatter enhancements
Diffstat (limited to 'caddyconfig/caddyfile/formatter.go')
| -rw-r--r-- | caddyconfig/caddyfile/formatter.go | 243 | 
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')  } | 
