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.