@marcel91200 / caddy Mirror · read-only / commits / 5fe69ac4
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 14, 05:15 UTC

cmd: Add `caddy fmt` command. (#3090)

This takes the config file as input and formats it. Prints the result to stdout. Can write changes to file if `--write` flag is passed. Fixes #3020 Signed-off-by: Vaibhav <vrongmeal@gmail.com>

Vaibhav committed Feb 29, 2020 at 22:42 UTC 5fe69ac4ab8bb1da84fc80776548fcc16f89b1db
4 files changed +346
caddyconfig/caddyfile/formatter.go new
+137
@@ -0,0 +1,137 @@
1 +// Copyright 2015 Matthew Holt and The Caddy Authors
2 +//
3 +// Licensed under the Apache License, Version 2.0 (the "License");
4 +// you may not use this file except in compliance with the License.
5 +// You may obtain a copy of the License at
6 +//
7 +// http://www.apache.org/licenses/LICENSE-2.0
8 +//
9 +// Unless required by applicable law or agreed to in writing, software
10 +// distributed under the License is distributed on an "AS IS" BASIS,
11 +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 +// See the License for the specific language governing permissions and
13 +// limitations under the License.
14 +
15 +package caddyfile
16 +
17 +import (
18 + "bytes"
19 + "io"
20 + "unicode"
21 +)
22 +
23 +// Format formats a Caddyfile to conventional standards.
24 +func Format(body []byte) []byte {
25 + reader := bytes.NewReader(body)
26 + result := new(bytes.Buffer)
27 +
28 + var (
29 + commented,
30 + quoted,
31 + escaped,
32 + block,
33 + environ,
34 + lineBegin bool
35 +
36 + firstIteration = true
37 +
38 + prev,
39 + curr,
40 + next rune
41 +
42 + err error
43 + )
44 +
45 + for {
46 + prev = curr
47 + curr = next
48 +
49 + if curr < 0 {
50 + break
51 + }
52 +
53 + next, _, err = reader.ReadRune()
54 + if err != nil {
55 + if err == io.EOF {
56 + next = -1
57 + } else {
58 + panic(err)
59 + }
60 + }
61 +
62 + if firstIteration {
63 + firstIteration = false
64 + lineBegin = true
65 + continue
66 + }
67 +
68 + if quoted {
69 + if escaped {
70 + escaped = false
71 + } else {
72 + if curr == '\\' {
73 + escaped = true
74 + }
75 + if curr == '"' {
76 + quoted = false
77 + }
78 + }
79 + if curr == '\n' {
80 + quoted = false
81 + }
82 + } else if commented {
83 + if curr == '\n' {
84 + commented = false
85 + }
86 + } else {
87 + if curr == '"' {
88 + quoted = true
89 + }
90 + if curr == '#' {
91 + commented = true
92 + }
93 + if curr == '}' {
94 + if environ {
95 + environ = false
96 + } else if block {
97 + block = false
98 + }
99 + }
100 + if curr == '{' {
101 + if unicode.IsSpace(next) {
102 + block = true
103 +
104 + if !unicode.IsSpace(prev) {
105 + result.WriteRune(' ')
106 + }
107 + } else {
108 + environ = true
109 + }
110 + }
111 + if lineBegin {
112 + if curr == ' ' || curr == '\t' {
113 + continue
114 + } else {
115 + lineBegin = false
116 + if block {
117 + result.WriteRune('\t')
118 + }
119 + }
120 + } else {
121 + if prev == '{' &&
122 + (curr == ' ' || curr == '\t') &&
123 + (next != '\n' && next != '\r') {
124 + curr = '\n'
125 + }
126 + }
127 + }
128 +
129 + if curr == '\n' {
130 + lineBegin = true
131 + }
132 +
133 + result.WriteRune(curr)
134 + }
135 +
136 + return result.Bytes()
137 +}
caddyconfig/caddyfile/formatter_test.go new
+161
@@ -0,0 +1,161 @@
1 +// Copyright 2015 Matthew Holt and The Caddy Authors
2 +//
3 +// Licensed under the Apache License, Version 2.0 (the "License");
4 +// you may not use this file except in compliance with the License.
5 +// You may obtain a copy of the License at
6 +//
7 +// http://www.apache.org/licenses/LICENSE-2.0
8 +//
9 +// Unless required by applicable law or agreed to in writing, software
10 +// distributed under the License is distributed on an "AS IS" BASIS,
11 +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 +// See the License for the specific language governing permissions and
13 +// limitations under the License.
14 +
15 +package caddyfile
16 +
17 +import (
18 + "testing"
19 +)
20 +
21 +func TestFormatBasicIndentation(t *testing.T) {
22 + input := []byte(`
23 + a
24 +b
25 +
26 + c {
27 + d
28 +}
29 +
30 +e { f
31 +}
32 +`)
33 + expected := []byte(`
34 +a
35 +b
36 +
37 +c {
38 + d
39 +}
40 +
41 +e {
42 + f
43 +}
44 +`)
45 + testFormat(t, input, expected)
46 +}
47 +
48 +func TestFormatBasicSpacing(t *testing.T) {
49 + input := []byte(`
50 +a{
51 + b
52 +}
53 +
54 +c{ d
55 +}
56 +`)
57 + expected := []byte(`
58 +a {
59 + b
60 +}
61 +
62 +c {
63 + d
64 +}
65 +`)
66 + testFormat(t, input, expected)
67 +}
68 +
69 +func TestFormatEnvironmentVariable(t *testing.T) {
70 + input := []byte(`
71 +{$A}
72 +
73 +b {
74 +{$C}
75 +}
76 +
77 +d { {$E}
78 +}
79 +`)
80 + expected := []byte(`
81 +{$A}
82 +
83 +b {
84 + {$C}
85 +}
86 +
87 +d {
88 + {$E}
89 +}
90 +`)
91 + testFormat(t, input, expected)
92 +}
93 +
94 +func TestFormatComments(t *testing.T) {
95 + input := []byte(`
96 +# a "\n"
97 +
98 +# b {
99 + c
100 +}
101 +
102 +d {
103 +e # f
104 +# g
105 +}
106 +
107 +h { # i
108 +}
109 +`)
110 + expected := []byte(`
111 +# a "\n"
112 +
113 +# b {
114 +c
115 +}
116 +
117 +d {
118 + e # f
119 + # g
120 +}
121 +
122 +h {
123 + # i
124 +}
125 +`)
126 + testFormat(t, input, expected)
127 +}
128 +
129 +func TestFormatQuotesAndEscapes(t *testing.T) {
130 + input := []byte(`
131 +"a \"b\" #c
132 + d
133 +
134 +e {
135 +"f"
136 +}
137 +
138 +g { "h"
139 +}
140 +`)
141 + expected := []byte(`
142 +"a \"b\" #c
143 +d
144 +
145 +e {
146 + "f"
147 +}
148 +
149 +g {
150 + "h"
151 +}
152 +`)
153 + testFormat(t, input, expected)
154 +}
155 +
156 +func testFormat(t *testing.T, input, expected []byte) {
157 + output := Format(input)
158 + if string(output) != string(expected) {
159 + t.Errorf("Expected:\n%s\ngot:\n%s", string(output), string(expected))
160 + }
161 +}
cmd/commandfuncs.go
+30
@@ -34,6 +34,7 @@ import (
34
35 "github.com/caddyserver/caddy/v2"
36 "github.com/caddyserver/caddy/v2/caddyconfig"
37 + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
38 "github.com/mholt/certmagic"
39 "go.uber.org/zap"
40 )
@@ -538,6 +539,35 @@ func cmdValidateConfig(fl Flags) (int, error) {
539 return caddy.ExitCodeSuccess, nil
540 }
541
542 +func cmdFormatConfig(fl Flags) (int, error) {
543 + // Default path of file is Caddyfile
544 + formatCmdConfigFile := fl.Arg(0)
545 + if formatCmdConfigFile == "" {
546 + formatCmdConfigFile = "Caddyfile"
547 + }
548 +
549 + formatCmdWriteFlag := fl.Bool("write")
550 +
551 + input, err := ioutil.ReadFile(formatCmdConfigFile)
552 + if err != nil {
553 + return caddy.ExitCodeFailedStartup,
554 + fmt.Errorf("reading input file: %v", err)
555 + }
556 +
557 + output := caddyfile.Format(input)
558 +
559 + if formatCmdWriteFlag {
560 + err = ioutil.WriteFile(formatCmdConfigFile, output, 0644)
561 + if err != nil {
562 + return caddy.ExitCodeFailedStartup, nil
563 + }
564 + } else {
565 + fmt.Print(string(output))
566 + }
567 +
568 + return caddy.ExitCodeSuccess, nil
569 +}
570 +
571 func cmdHelp(fl Flags) (int, error) {
572 const fullDocs = `Full documentation is available at:
573 https://caddyserver.com/docs/command-line`
cmd/commands.go
+18
@@ -242,6 +242,24 @@ provisioning stages.`,
242 }(),
243 })
244
245 + RegisterCommand(Command{
246 + Name: "fmt",
247 + Func: cmdFormatConfig,
248 + Usage: "[--write] [<path>]",
249 + Short: "Formats a Caddyfile",
250 + Long: `
251 +Formats the Caddyfile by adding proper indentation and spaces to improve
252 +human readability. It prints the result to stdout.
253 +
254 +If --write is specified, the output will be written to the config file
255 +directly instead of printing it.`,
256 + Flags: func() *flag.FlagSet {
257 + fs := flag.NewFlagSet("format", flag.ExitOnError)
258 + fs.Bool("write", false, "Over-write the output to specified file")
259 + return fs
260 + }(),
261 + })
262 +
263 }
264
265 // RegisterCommand registers the command cmd.