Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions cmd/format/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,9 +197,9 @@ func Run(v *viper.Viper, statz *stats.Stats, cmd *cobra.Command, paths []string)
return fmt.Errorf("failed to close walker: %w", err)
}

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

if formatErr != nil {
Expand Down
22 changes: 14 additions & 8 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ func runE(v *viper.Viper, statz *stats.Stats, cmd *cobra.Command, args []string)
return fmt.Errorf("failed to find treefmt config file: %w", err)
}

log.Infof("using config file: %s", configFile)
log.Debugf("using config file: %s", configFile)

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

switch v.GetInt("verbose") {
case 0:
log.SetLevel(log.WarnLevel)
case 1:
log.SetLevel(log.InfoLevel)
default:
log.SetLevel(log.DebugLevel)
if v.GetBool("quiet") {
// if quiet, we only log errors
log.SetLevel(log.ErrorLevel)
} else {
// otherwise, the verbose flag controls the log level
switch v.GetInt("verbose") {
case 0:
log.SetLevel(log.WarnLevel)
case 1:
log.SetLevel(log.InfoLevel)
default:
log.SetLevel(log.DebugLevel)
}
}

// format
Expand Down
106 changes: 81 additions & 25 deletions cmd/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@ func TestOnUnmatched(t *testing.T) {
}
}

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

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

t.Setenv("TREEFMT_ON_UNMATCHED", levelStr)

treefmt(t,
withArgs("-vv"),
withNoError(t),
withOutput(checkOutput(level)),
withStderr(checkOutput(level)),
)
})
}
Expand All @@ -131,6 +131,33 @@ func TestOnUnmatched(t *testing.T) {
})
}

func TestQuiet(t *testing.T) {
as := require.New(t)
tempDir := test.TempExamples(t)

test.ChangeWorkDir(t, tempDir)

// allow missing formatter
t.Setenv("TREEFMT_ALLOW_MISSING_FORMATTER", "true")

noOutput := func(out []byte) {
as.Empty(out)
}

treefmt(t, withArgs("-q"), withNoError(t), withStdout(noOutput), withStderr(noOutput))
treefmt(t, withArgs("--quiet"), withNoError(t), withStdout(noOutput), withStderr(noOutput))

t.Setenv("TREEFMT_QUIET", "true")
treefmt(t, withNoError(t), withStdout(noOutput), withStderr(noOutput))

t.Setenv("TREEFMT_ALLOW_MISSING_FORMATTER", "false")

// check it doesn't suppress errors
treefmt(t, withError(func(err error) {
as.ErrorContains(err, "error looking up 'foo-fmt'")
}))
}

func TestCpuProfile(t *testing.T) {
as := require.New(t)
tempDir := test.TempExamples(t)
Expand Down Expand Up @@ -1583,7 +1610,7 @@ func TestStdin(t *testing.T) {
withError(func(err error) {
as.EqualError(err, "exactly one path should be specified when using the --stdin flag")
}),
withOutput(func(out []byte) {
withStderr(func(out []byte) {
as.Equal("Error: exactly one path should be specified when using the --stdin flag\n", string(out))
}),
)
Expand All @@ -1600,7 +1627,7 @@ func TestStdin(t *testing.T) {
stats.Formatted: 1,
stats.Changed: 1,
}),
withOutput(func(out []byte) {
withStdout(func(out []byte) {
as.Equal(`{ ...}: "hello"
`, string(out))
}),
Expand All @@ -1616,7 +1643,7 @@ func TestStdin(t *testing.T) {
withError(func(err error) {
as.Errorf(err, "path ../test.nix not inside the tree root %s", tempDir)
}),
withOutput(func(out []byte) {
withStderr(func(out []byte) {
as.Contains(string(out), "Error: path ../test.nix not inside the tree root")
}),
)
Expand All @@ -1639,7 +1666,7 @@ func TestStdin(t *testing.T) {
stats.Formatted: 1,
stats.Changed: 1,
}),
withOutput(func(out []byte) {
withStdout(func(out []byte) {
as.Equal(`| col1 | col2 |
| ------ | --------- |
| nice | fits |
Expand Down Expand Up @@ -1806,7 +1833,9 @@ type options struct {
value *config.Config
}

assertOut func([]byte)
assertStdout func([]byte)
assertStderr func([]byte)

assertError func(error)
assertStats func(*stats.Stats)

Expand Down Expand Up @@ -1873,9 +1902,15 @@ func withNoError(t *testing.T) option {
}
}

func withOutput(fn func([]byte)) option {
func withStdout(fn func([]byte)) option {
return func(o *options) {
o.assertStdout = fn
}
}

func withStderr(fn func([]byte)) option {
return func(o *options) {
o.assertOut = fn
o.assertStderr = fn
}
}

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

tempDir := t.TempDir()
tempOut := test.TempFile(t, tempDir, "combined_output", nil)

tempStdout := test.TempFile(t, tempDir, "stdout", nil)
tempStderr := test.TempFile(t, tempDir, "stderr", nil)

// capture standard outputs before swapping them
stdout := os.Stdout
stderr := os.Stderr

// swap them temporarily
os.Stdout = tempOut
os.Stderr = tempOut
os.Stdout = tempStdout
os.Stderr = tempStderr

log.SetOutput(tempOut)
log.SetOutput(tempStdout)

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

root.SetArgs(args)
root.SetOut(tempOut)
root.SetErr(tempOut)
root.SetOut(tempStdout)
root.SetErr(tempStderr)

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

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

out, readErr := io.ReadAll(tempOut)
if _, resetErr := tempStderr.Seek(0, 0); resetErr != nil {
t.Fatal(fmt.Errorf("failed to reset temp output for reading: %w", resetErr))
}

// read back stderr and validate
out, readErr := io.ReadAll(tempStderr)
if readErr != nil {
t.Fatal(fmt.Errorf("failed to read temp output: %w", readErr))
t.Fatal(fmt.Errorf("failed to read temp stderr: %w", readErr))
}

if opts.assertStderr != nil {
opts.assertStderr(out)
}

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

if opts.assertStats != nil {
opts.assertStats(statz)
// read back stdout and validate
out, readErr = io.ReadAll(tempStdout)
if readErr != nil {
t.Fatal(fmt.Errorf("failed to read temp stdout: %w", readErr))
}

if opts.assertOut != nil {
opts.assertOut(out)
t.Log("\n" + string(out))

if opts.assertStdout != nil {
opts.assertStdout(out)
}

// assert other properties

if opts.assertStats != nil {
opts.assertStats(statz)
}

if opts.assertError != nil {
Expand Down
6 changes: 5 additions & 1 deletion config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type Config struct {
Formatters []string `mapstructure:"formatters" toml:"formatters,omitempty"`
NoCache bool `mapstructure:"no-cache" toml:"-"` // not allowed in config
OnUnmatched string `mapstructure:"on-unmatched" toml:"on-unmatched,omitempty"`
Quiet bool `mapstructure:"quiet" toml:"-"` // not allowed in config
TreeRoot string `mapstructure:"tree-root" toml:"tree-root,omitempty"`
TreeRootFile string `mapstructure:"tree-root-file" toml:"tree-root-file,omitempty"`
Verbose uint8 `mapstructure:"verbose" toml:"verbose,omitempty"`
Expand Down Expand Up @@ -89,7 +90,7 @@ func SetFlags(fs *pflag.FlagSet) {
"Ignore the evaluation cache entirely. Useful for CI. (env $TREEFMT_NO_CACHE)",
)
fs.StringP(
"on-unmatched", "u", "warn",
"on-unmatched", "u", "info",
"Log paths that did not match any formatters at the specified log level. Possible values are "+
"<debug|info|warn|error|fatal>. (env $TREEFMT_ON_UNMATCHED)",
)
Expand All @@ -110,6 +111,9 @@ func SetFlags(fs *pflag.FlagSet) {
"verbose", "v",
"Set the verbosity of logs e.g. -vv. (env $TREEFMT_VERBOSE)",
)
fs.BoolP(
"quiet", "q", false, "Disable all logs except errors. (env $TREEFMT_QUIET)",
)
fs.String(
"walk", "auto",
"The method used to traverse the files within the tree root. Currently supports "+
Expand Down
32 changes: 31 additions & 1 deletion config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,36 @@ func TestNoCache(t *testing.T) {
checkValue(true)
}

func TestQuiet(t *testing.T) {
as := require.New(t)

cfg := &config.Config{}
v, flags := newViper(t)

checkValue := func(expected bool) {
readValue(t, v, cfg, func(cfg *config.Config) {
as.Equal(expected, cfg.Quiet)
})
}

// default with no flag, env or config
checkValue(false)

// set config value and check that it has no effect
// you are not allowed to set no-cache in config
cfg.Quiet = true

checkValue(false)

// env override
t.Setenv("TREEFMT_QUIET", "false")
checkValue(false)

// flag override
as.NoError(flags.Set("quiet", "true"))
checkValue(true)
}

func TestOnUnmatched(t *testing.T) {
as := require.New(t)

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

// default with no flag, env or config
checkValue("warn")
checkValue("info")

// set config value
cfg.OnUnmatched = "error"
Expand Down
16 changes: 16 additions & 0 deletions docs/content/getting-started/configure.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,22 @@ Possible values are `<debug|info|warn|error|fatal>`.
on-unmatched = "debug"
```

### `quiet`

Suppress all output except for errors.

=== "Flag"

```console
treefmt --quiet
```

=== "Env"

```console
TREEFMT_QUIET=true treefmt
```

### `stdin`

Format the context passed in via stdin.
Expand Down
5 changes: 5 additions & 0 deletions docs/content/guides/unmatched-formatters.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,28 @@ This helps you decide whether to add formatters for specific files or ignore the
## Customizing Notifications

### Reducing Log Verbosity

If you find the unmatched file warnings too noisy, you can lower the logging level in your config:

`treefmt.toml`:

```toml
on-unmatched = "debug"
```

To later find out what files are unmatched, you can override this setting via the command line:

```console
$ treefmt --on-unmatched warn
```

### Enforcing Strict Matching

Another stricter policy approach is to fail the run if any unmatched files are found.
This can be paired with an `excludes` list to ignore specific files:

`treefmt.toml`:

```toml
# Fail if any unmatched files are found
on-unmatched = "fatal"
Expand Down
2 changes: 1 addition & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
site_name: Treefmt
site_url: https://treefmt.com
site_description: >-
The formatter multiplexer.
The formatter multiplexer.

# Repository
repo_name: numtide/treefmt
Expand Down
Loading