Skip to content

Commit 45881a4

Browse files
authored
Merge pull request #504 from numtide/feat/verbosity-changes
Verbosity changes
2 parents 25f44e5 + 10c9a72 commit 45881a4

File tree

9 files changed

+160
-41
lines changed

9 files changed

+160
-41
lines changed

cmd/format/format.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -197,9 +197,9 @@ func Run(v *viper.Viper, statz *stats.Stats, cmd *cobra.Command, paths []string)
197197
return fmt.Errorf("failed to close walker: %w", err)
198198
}
199199

200-
// print stats to stdout, unless we are processing from stdin and therefore outputting the results to stdout
201-
if !cfg.Stdin {
202-
statz.Print()
200+
// print stats to stderr
201+
if !cfg.Quiet {
202+
statz.PrintToStderr()
203203
}
204204

205205
if formatErr != nil {

cmd/root.go

+14-8
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ func runE(v *viper.Viper, statz *stats.Stats, cmd *cobra.Command, args []string)
131131
return fmt.Errorf("failed to find treefmt config file: %w", err)
132132
}
133133

134-
log.Infof("using config file: %s", configFile)
134+
log.Debugf("using config file: %s", configFile)
135135

136136
// read in the config
137137
v.SetConfigFile(configFile)
@@ -144,13 +144,19 @@ func runE(v *viper.Viper, statz *stats.Stats, cmd *cobra.Command, args []string)
144144
log.SetOutput(os.Stderr)
145145
log.SetReportTimestamp(false)
146146

147-
switch v.GetInt("verbose") {
148-
case 0:
149-
log.SetLevel(log.WarnLevel)
150-
case 1:
151-
log.SetLevel(log.InfoLevel)
152-
default:
153-
log.SetLevel(log.DebugLevel)
147+
if v.GetBool("quiet") {
148+
// if quiet, we only log errors
149+
log.SetLevel(log.ErrorLevel)
150+
} else {
151+
// otherwise, the verbose flag controls the log level
152+
switch v.GetInt("verbose") {
153+
case 0:
154+
log.SetLevel(log.WarnLevel)
155+
case 1:
156+
log.SetLevel(log.InfoLevel)
157+
default:
158+
log.SetLevel(log.DebugLevel)
159+
}
154160
}
155161

156162
// format

cmd/root_test.go

+81-25
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,9 @@ func TestOnUnmatched(t *testing.T) {
7272
}
7373
}
7474

75-
// default is WARN
75+
// default is INFO
7676
t.Run("default", func(t *testing.T) {
77-
treefmt(t, withNoError(t), withOutput(checkOutput(log.WarnLevel)))
77+
treefmt(t, withArgs("-v"), withNoError(t), withStderr(checkOutput(log.InfoLevel)))
7878
})
7979

8080
// should exit with error when using fatal
@@ -99,15 +99,15 @@ func TestOnUnmatched(t *testing.T) {
9999
treefmt(t,
100100
withArgs("-vv", "--on-unmatched", levelStr),
101101
withNoError(t),
102-
withOutput(checkOutput(level)),
102+
withStderr(checkOutput(level)),
103103
)
104104

105105
t.Setenv("TREEFMT_ON_UNMATCHED", levelStr)
106106

107107
treefmt(t,
108108
withArgs("-vv"),
109109
withNoError(t),
110-
withOutput(checkOutput(level)),
110+
withStderr(checkOutput(level)),
111111
)
112112
})
113113
}
@@ -131,6 +131,33 @@ func TestOnUnmatched(t *testing.T) {
131131
})
132132
}
133133

134+
func TestQuiet(t *testing.T) {
135+
as := require.New(t)
136+
tempDir := test.TempExamples(t)
137+
138+
test.ChangeWorkDir(t, tempDir)
139+
140+
// allow missing formatter
141+
t.Setenv("TREEFMT_ALLOW_MISSING_FORMATTER", "true")
142+
143+
noOutput := func(out []byte) {
144+
as.Empty(out)
145+
}
146+
147+
treefmt(t, withArgs("-q"), withNoError(t), withStdout(noOutput), withStderr(noOutput))
148+
treefmt(t, withArgs("--quiet"), withNoError(t), withStdout(noOutput), withStderr(noOutput))
149+
150+
t.Setenv("TREEFMT_QUIET", "true")
151+
treefmt(t, withNoError(t), withStdout(noOutput), withStderr(noOutput))
152+
153+
t.Setenv("TREEFMT_ALLOW_MISSING_FORMATTER", "false")
154+
155+
// check it doesn't suppress errors
156+
treefmt(t, withError(func(err error) {
157+
as.ErrorContains(err, "error looking up 'foo-fmt'")
158+
}))
159+
}
160+
134161
func TestCpuProfile(t *testing.T) {
135162
as := require.New(t)
136163
tempDir := test.TempExamples(t)
@@ -1583,7 +1610,7 @@ func TestStdin(t *testing.T) {
15831610
withError(func(err error) {
15841611
as.EqualError(err, "exactly one path should be specified when using the --stdin flag")
15851612
}),
1586-
withOutput(func(out []byte) {
1613+
withStderr(func(out []byte) {
15871614
as.Equal("Error: exactly one path should be specified when using the --stdin flag\n", string(out))
15881615
}),
15891616
)
@@ -1600,7 +1627,7 @@ func TestStdin(t *testing.T) {
16001627
stats.Formatted: 1,
16011628
stats.Changed: 1,
16021629
}),
1603-
withOutput(func(out []byte) {
1630+
withStdout(func(out []byte) {
16041631
as.Equal(`{ ...}: "hello"
16051632
`, string(out))
16061633
}),
@@ -1616,7 +1643,7 @@ func TestStdin(t *testing.T) {
16161643
withError(func(err error) {
16171644
as.Errorf(err, "path ../test.nix not inside the tree root %s", tempDir)
16181645
}),
1619-
withOutput(func(out []byte) {
1646+
withStderr(func(out []byte) {
16201647
as.Contains(string(out), "Error: path ../test.nix not inside the tree root")
16211648
}),
16221649
)
@@ -1639,7 +1666,7 @@ func TestStdin(t *testing.T) {
16391666
stats.Formatted: 1,
16401667
stats.Changed: 1,
16411668
}),
1642-
withOutput(func(out []byte) {
1669+
withStdout(func(out []byte) {
16431670
as.Equal(`| col1 | col2 |
16441671
| ------ | --------- |
16451672
| nice | fits |
@@ -1806,7 +1833,9 @@ type options struct {
18061833
value *config.Config
18071834
}
18081835

1809-
assertOut func([]byte)
1836+
assertStdout func([]byte)
1837+
assertStderr func([]byte)
1838+
18101839
assertError func(error)
18111840
assertStats func(*stats.Stats)
18121841

@@ -1873,9 +1902,15 @@ func withNoError(t *testing.T) option {
18731902
}
18741903
}
18751904

1876-
func withOutput(fn func([]byte)) option {
1905+
func withStdout(fn func([]byte)) option {
1906+
return func(o *options) {
1907+
o.assertStdout = fn
1908+
}
1909+
}
1910+
1911+
func withStderr(fn func([]byte)) option {
18771912
return func(o *options) {
1878-
o.assertOut = fn
1913+
o.assertStderr = fn
18791914
}
18801915
}
18811916

@@ -1931,17 +1966,19 @@ func treefmt(
19311966
t.Logf("treefmt %s", strings.Join(args, " "))
19321967

19331968
tempDir := t.TempDir()
1934-
tempOut := test.TempFile(t, tempDir, "combined_output", nil)
1969+
1970+
tempStdout := test.TempFile(t, tempDir, "stdout", nil)
1971+
tempStderr := test.TempFile(t, tempDir, "stderr", nil)
19351972

19361973
// capture standard outputs before swapping them
19371974
stdout := os.Stdout
19381975
stderr := os.Stderr
19391976

19401977
// swap them temporarily
1941-
os.Stdout = tempOut
1942-
os.Stderr = tempOut
1978+
os.Stdout = tempStdout
1979+
os.Stderr = tempStderr
19431980

1944-
log.SetOutput(tempOut)
1981+
log.SetOutput(tempStdout)
19451982

19461983
defer func() {
19471984
// swap outputs back
@@ -1954,30 +1991,49 @@ func treefmt(
19541991
root, statz := cmd.NewRoot()
19551992

19561993
root.SetArgs(args)
1957-
root.SetOut(tempOut)
1958-
root.SetErr(tempOut)
1994+
root.SetOut(tempStdout)
1995+
root.SetErr(tempStderr)
19591996

19601997
// execute the command
19611998
cmdErr := root.Execute()
19621999

1963-
// reset and read the temporary output
1964-
if _, resetErr := tempOut.Seek(0, 0); resetErr != nil {
2000+
// reset and read the temporary outputs
2001+
if _, resetErr := tempStdout.Seek(0, 0); resetErr != nil {
19652002
t.Fatal(fmt.Errorf("failed to reset temp output for reading: %w", resetErr))
19662003
}
19672004

1968-
out, readErr := io.ReadAll(tempOut)
2005+
if _, resetErr := tempStderr.Seek(0, 0); resetErr != nil {
2006+
t.Fatal(fmt.Errorf("failed to reset temp output for reading: %w", resetErr))
2007+
}
2008+
2009+
// read back stderr and validate
2010+
out, readErr := io.ReadAll(tempStderr)
19692011
if readErr != nil {
1970-
t.Fatal(fmt.Errorf("failed to read temp output: %w", readErr))
2012+
t.Fatal(fmt.Errorf("failed to read temp stderr: %w", readErr))
2013+
}
2014+
2015+
if opts.assertStderr != nil {
2016+
opts.assertStderr(out)
19712017
}
19722018

19732019
t.Log("\n" + string(out))
19742020

1975-
if opts.assertStats != nil {
1976-
opts.assertStats(statz)
2021+
// read back stdout and validate
2022+
out, readErr = io.ReadAll(tempStdout)
2023+
if readErr != nil {
2024+
t.Fatal(fmt.Errorf("failed to read temp stdout: %w", readErr))
19772025
}
19782026

1979-
if opts.assertOut != nil {
1980-
opts.assertOut(out)
2027+
t.Log("\n" + string(out))
2028+
2029+
if opts.assertStdout != nil {
2030+
opts.assertStdout(out)
2031+
}
2032+
2033+
// assert other properties
2034+
2035+
if opts.assertStats != nil {
2036+
opts.assertStats(statz)
19812037
}
19822038

19832039
if opts.assertError != nil {

config/config.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ type Config struct {
2222
Formatters []string `mapstructure:"formatters" toml:"formatters,omitempty"`
2323
NoCache bool `mapstructure:"no-cache" toml:"-"` // not allowed in config
2424
OnUnmatched string `mapstructure:"on-unmatched" toml:"on-unmatched,omitempty"`
25+
Quiet bool `mapstructure:"quiet" toml:"-"` // not allowed in config
2526
TreeRoot string `mapstructure:"tree-root" toml:"tree-root,omitempty"`
2627
TreeRootFile string `mapstructure:"tree-root-file" toml:"tree-root-file,omitempty"`
2728
Verbose uint8 `mapstructure:"verbose" toml:"verbose,omitempty"`
@@ -89,7 +90,7 @@ func SetFlags(fs *pflag.FlagSet) {
8990
"Ignore the evaluation cache entirely. Useful for CI. (env $TREEFMT_NO_CACHE)",
9091
)
9192
fs.StringP(
92-
"on-unmatched", "u", "warn",
93+
"on-unmatched", "u", "info",
9394
"Log paths that did not match any formatters at the specified log level. Possible values are "+
9495
"<debug|info|warn|error|fatal>. (env $TREEFMT_ON_UNMATCHED)",
9596
)
@@ -110,6 +111,9 @@ func SetFlags(fs *pflag.FlagSet) {
110111
"verbose", "v",
111112
"Set the verbosity of logs e.g. -vv. (env $TREEFMT_VERBOSE)",
112113
)
114+
fs.BoolP(
115+
"quiet", "q", false, "Disable all logs except errors. (env $TREEFMT_QUIET)",
116+
)
113117
fs.String(
114118
"walk", "auto",
115119
"The method used to traverse the files within the tree root. Currently supports "+

config/config_test.go

+31-1
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,36 @@ func TestNoCache(t *testing.T) {
323323
checkValue(true)
324324
}
325325

326+
func TestQuiet(t *testing.T) {
327+
as := require.New(t)
328+
329+
cfg := &config.Config{}
330+
v, flags := newViper(t)
331+
332+
checkValue := func(expected bool) {
333+
readValue(t, v, cfg, func(cfg *config.Config) {
334+
as.Equal(expected, cfg.Quiet)
335+
})
336+
}
337+
338+
// default with no flag, env or config
339+
checkValue(false)
340+
341+
// set config value and check that it has no effect
342+
// you are not allowed to set no-cache in config
343+
cfg.Quiet = true
344+
345+
checkValue(false)
346+
347+
// env override
348+
t.Setenv("TREEFMT_QUIET", "false")
349+
checkValue(false)
350+
351+
// flag override
352+
as.NoError(flags.Set("quiet", "true"))
353+
checkValue(true)
354+
}
355+
326356
func TestOnUnmatched(t *testing.T) {
327357
as := require.New(t)
328358

@@ -336,7 +366,7 @@ func TestOnUnmatched(t *testing.T) {
336366
}
337367

338368
// default with no flag, env or config
339-
checkValue("warn")
369+
checkValue("info")
340370

341371
// set config value
342372
cfg.OnUnmatched = "error"

docs/content/getting-started/configure.md

+16
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,22 @@ Possible values are `<debug|info|warn|error|fatal>`.
254254
on-unmatched = "debug"
255255
```
256256

257+
### `quiet`
258+
259+
Suppress all output except for errors.
260+
261+
=== "Flag"
262+
263+
```console
264+
treefmt --quiet
265+
```
266+
267+
=== "Env"
268+
269+
```console
270+
TREEFMT_QUIET=true treefmt
271+
```
272+
257273
### `stdin`
258274

259275
Format the context passed in via stdin.

docs/content/guides/unmatched-formatters.md

+5
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,28 @@ This helps you decide whether to add formatters for specific files or ignore the
1818
## Customizing Notifications
1919

2020
### Reducing Log Verbosity
21+
2122
If you find the unmatched file warnings too noisy, you can lower the logging level in your config:
2223

2324
`treefmt.toml`:
25+
2426
```toml
2527
on-unmatched = "debug"
2628
```
2729

2830
To later find out what files are unmatched, you can override this setting via the command line:
31+
2932
```console
3033
$ treefmt --on-unmatched warn
3134
```
3235

3336
### Enforcing Strict Matching
37+
3438
Another stricter policy approach is to fail the run if any unmatched files are found.
3539
This can be paired with an `excludes` list to ignore specific files:
3640

3741
`treefmt.toml`:
42+
3843
```toml
3944
# Fail if any unmatched files are found
4045
on-unmatched = "fatal"

mkdocs.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
site_name: Treefmt
33
site_url: https://treefmt.com
44
site_description: >-
5-
The formatter multiplexer.
5+
The formatter multiplexer.
66
77
# Repository
88
repo_name: numtide/treefmt

0 commit comments

Comments
 (0)