mirror of
https://github.com/numtide/treefmt.git
synced 2024-10-03 20:18:11 +03:00
Merge pull request #303 from numtide/feat/show-unmatched
--on-unwatched
This commit is contained in:
commit
7afdc7a3ee
@ -22,13 +22,15 @@ type Format struct {
|
||||
Version bool `name:"version" short:"V" help:"Print version."`
|
||||
Init bool `name:"init" short:"i" help:"Create a new treefmt.toml."`
|
||||
|
||||
OnUnmatched log.Level `name:"on-unmatched" short:"u" default:"warn" help:"Log paths that did not match any formatters at the specified log level, with fatal exiting the process with an error. Possible values are <debug|info|warn|error|fatal>."`
|
||||
|
||||
Paths []string `name:"paths" arg:"" type:"path" optional:"" help:"Paths to format. Defaults to formatting the whole tree."`
|
||||
Stdin bool `help:"Format the context passed in via stdin."`
|
||||
|
||||
CpuProfile string `optional:"" help:"The file into which a cpu profile will be written."`
|
||||
}
|
||||
|
||||
func ConfigureLogging() {
|
||||
func configureLogging() {
|
||||
log.SetReportTimestamp(false)
|
||||
|
||||
if Cli.Verbosity == 0 {
|
||||
|
@ -40,6 +40,9 @@ var (
|
||||
)
|
||||
|
||||
func (f *Format) Run() (err error) {
|
||||
// set log level and other options
|
||||
configureLogging()
|
||||
|
||||
// cpu profiling
|
||||
if Cli.CpuProfile != "" {
|
||||
cpuProfile, err := os.Create(Cli.CpuProfile)
|
||||
@ -355,8 +358,10 @@ func applyFormatters(ctx context.Context) func() error {
|
||||
}
|
||||
|
||||
if len(matches) == 0 {
|
||||
// no match, so we send it direct to the processed channel
|
||||
log.Debugf("no match found: %s", file.Path)
|
||||
if Cli.OnUnmatched == log.FatalLevel {
|
||||
return fmt.Errorf("no formatter for path: %s", file.Path)
|
||||
}
|
||||
log.Logf(Cli.OnUnmatched, "no formatter for path: %s", file.Path)
|
||||
processedCh <- file
|
||||
} else {
|
||||
// record the match
|
||||
|
@ -2,6 +2,7 @@ package cli
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
@ -9,7 +10,7 @@ import (
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
config2 "git.numtide.com/numtide/treefmt/config"
|
||||
"git.numtide.com/numtide/treefmt/config"
|
||||
"git.numtide.com/numtide/treefmt/format"
|
||||
"git.numtide.com/numtide/treefmt/test"
|
||||
|
||||
@ -21,6 +22,63 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestOnUnmatched(t *testing.T) {
|
||||
as := require.New(t)
|
||||
|
||||
// capture current cwd, so we can replace it after the test is finished
|
||||
cwd, err := os.Getwd()
|
||||
as.NoError(err)
|
||||
|
||||
t.Cleanup(func() {
|
||||
// return to the previous working directory
|
||||
as.NoError(os.Chdir(cwd))
|
||||
})
|
||||
|
||||
tempDir := test.TempExamples(t)
|
||||
|
||||
paths := []string{
|
||||
"go/go.mod",
|
||||
"haskell/haskell.cabal",
|
||||
"haskell/treefmt.toml",
|
||||
"html/scripts/.gitkeep",
|
||||
"nixpkgs.toml",
|
||||
"python/requirements.txt",
|
||||
"rust/Cargo.toml",
|
||||
"touch.toml",
|
||||
"treefmt.toml",
|
||||
}
|
||||
|
||||
out, err := cmd(t, "-C", tempDir, "--allow-missing-formatter", "--on-unmatched", "fatal")
|
||||
as.ErrorContains(err, fmt.Sprintf("no formatter for path: %s/%s", tempDir, paths[0]))
|
||||
|
||||
checkOutput := func(level string, output []byte) {
|
||||
for _, p := range paths {
|
||||
as.Contains(string(output), fmt.Sprintf("%s format: no formatter for path: %s/%s", level, tempDir, p))
|
||||
}
|
||||
}
|
||||
|
||||
// default is warn
|
||||
out, err = cmd(t, "-C", tempDir, "--allow-missing-formatter", "-c")
|
||||
as.NoError(err)
|
||||
checkOutput("WARN", out)
|
||||
|
||||
out, err = cmd(t, "-C", tempDir, "--allow-missing-formatter", "-c", "--on-unmatched", "warn")
|
||||
as.NoError(err)
|
||||
checkOutput("WARN", out)
|
||||
|
||||
out, err = cmd(t, "-C", tempDir, "--allow-missing-formatter", "-c", "-u", "error")
|
||||
as.NoError(err)
|
||||
checkOutput("ERRO", out)
|
||||
|
||||
out, err = cmd(t, "-C", tempDir, "--allow-missing-formatter", "-c", "-v", "--on-unmatched", "info")
|
||||
as.NoError(err)
|
||||
checkOutput("INFO", out)
|
||||
|
||||
out, err = cmd(t, "-C", tempDir, "--allow-missing-formatter", "-c", "-vv", "-u", "debug")
|
||||
as.NoError(err)
|
||||
checkOutput("DEBU", out)
|
||||
}
|
||||
|
||||
func TestCpuProfile(t *testing.T) {
|
||||
as := require.New(t)
|
||||
tempDir := test.TempExamples(t)
|
||||
@ -47,8 +105,8 @@ func TestAllowMissingFormatter(t *testing.T) {
|
||||
tempDir := test.TempExamples(t)
|
||||
configPath := tempDir + "/treefmt.toml"
|
||||
|
||||
test.WriteConfig(t, configPath, config2.Config{
|
||||
Formatters: map[string]*config2.Formatter{
|
||||
test.WriteConfig(t, configPath, config.Config{
|
||||
Formatters: map[string]*config.Formatter{
|
||||
"foo-fmt": {
|
||||
Command: "foo-fmt",
|
||||
},
|
||||
@ -65,8 +123,8 @@ func TestAllowMissingFormatter(t *testing.T) {
|
||||
func TestSpecifyingFormatters(t *testing.T) {
|
||||
as := require.New(t)
|
||||
|
||||
cfg := config2.Config{
|
||||
Formatters: map[string]*config2.Formatter{
|
||||
cfg := config.Config{
|
||||
Formatters: map[string]*config.Formatter{
|
||||
"elm": {
|
||||
Command: "touch",
|
||||
Options: []string{"-m"},
|
||||
@ -131,8 +189,8 @@ func TestIncludesAndExcludes(t *testing.T) {
|
||||
configPath := tempDir + "/touch.toml"
|
||||
|
||||
// test without any excludes
|
||||
cfg := config2.Config{
|
||||
Formatters: map[string]*config2.Formatter{
|
||||
cfg := config.Config{
|
||||
Formatters: map[string]*config.Formatter{
|
||||
"echo": {
|
||||
Command: "echo",
|
||||
Includes: []string{"*"},
|
||||
@ -203,8 +261,8 @@ func TestCache(t *testing.T) {
|
||||
configPath := tempDir + "/touch.toml"
|
||||
|
||||
// test without any excludes
|
||||
cfg := config2.Config{
|
||||
Formatters: map[string]*config2.Formatter{
|
||||
cfg := config.Config{
|
||||
Formatters: map[string]*config.Formatter{
|
||||
"echo": {
|
||||
Command: "echo",
|
||||
Includes: []string{"*"},
|
||||
@ -261,8 +319,8 @@ func TestChangeWorkingDirectory(t *testing.T) {
|
||||
configPath := tempDir + "/treefmt.toml"
|
||||
|
||||
// test without any excludes
|
||||
cfg := config2.Config{
|
||||
Formatters: map[string]*config2.Formatter{
|
||||
cfg := config.Config{
|
||||
Formatters: map[string]*config.Formatter{
|
||||
"echo": {
|
||||
Command: "echo",
|
||||
Includes: []string{"*"},
|
||||
@ -286,8 +344,8 @@ func TestFailOnChange(t *testing.T) {
|
||||
configPath := tempDir + "/touch.toml"
|
||||
|
||||
// test without any excludes
|
||||
cfg := config2.Config{
|
||||
Formatters: map[string]*config2.Formatter{
|
||||
cfg := config.Config{
|
||||
Formatters: map[string]*config.Formatter{
|
||||
"touch": {
|
||||
Command: "touch",
|
||||
Includes: []string{"*"},
|
||||
@ -322,8 +380,8 @@ func TestBustCacheOnFormatterChange(t *testing.T) {
|
||||
as.NoError(os.Setenv("PATH", binPath+":"+os.Getenv("PATH")))
|
||||
|
||||
// start with 2 formatters
|
||||
cfg := config2.Config{
|
||||
Formatters: map[string]*config2.Formatter{
|
||||
cfg := config.Config{
|
||||
Formatters: map[string]*config.Formatter{
|
||||
"python": {
|
||||
Command: "black",
|
||||
Includes: []string{"*.py"},
|
||||
@ -367,7 +425,7 @@ func TestBustCacheOnFormatterChange(t *testing.T) {
|
||||
assertStats(t, as, 31, 0, 0, 0)
|
||||
|
||||
// add go formatter
|
||||
cfg.Formatters["go"] = &config2.Formatter{
|
||||
cfg.Formatters["go"] = &config.Formatter{
|
||||
Command: "gofmt",
|
||||
Options: []string{"-w"},
|
||||
Includes: []string{"*.go"},
|
||||
@ -417,8 +475,8 @@ func TestGitWorktree(t *testing.T) {
|
||||
configPath := filepath.Join(tempDir, "/treefmt.toml")
|
||||
|
||||
// basic config
|
||||
cfg := config2.Config{
|
||||
Formatters: map[string]*config2.Formatter{
|
||||
cfg := config.Config{
|
||||
Formatters: map[string]*config.Formatter{
|
||||
"echo": {
|
||||
Command: "echo",
|
||||
Includes: []string{"*"},
|
||||
@ -484,8 +542,8 @@ func TestPathsArg(t *testing.T) {
|
||||
as.NoError(os.Chdir(tempDir))
|
||||
|
||||
// basic config
|
||||
cfg := config2.Config{
|
||||
Formatters: map[string]*config2.Formatter{
|
||||
cfg := config.Config{
|
||||
Formatters: map[string]*config.Formatter{
|
||||
"echo": {
|
||||
Command: "echo",
|
||||
Includes: []string{"*"},
|
||||
@ -528,8 +586,8 @@ func TestStdIn(t *testing.T) {
|
||||
as.NoError(os.Chdir(tempDir))
|
||||
|
||||
// basic config
|
||||
cfg := config2.Config{
|
||||
Formatters: map[string]*config2.Formatter{
|
||||
cfg := config.Config{
|
||||
Formatters: map[string]*config.Formatter{
|
||||
"echo": {
|
||||
Command: "echo",
|
||||
Includes: []string{"*"},
|
||||
@ -568,11 +626,20 @@ go/main.go
|
||||
func TestDeterministicOrderingInPipeline(t *testing.T) {
|
||||
as := require.New(t)
|
||||
|
||||
// capture current cwd, so we can replace it after the test is finished
|
||||
cwd, err := os.Getwd()
|
||||
as.NoError(err)
|
||||
|
||||
t.Cleanup(func() {
|
||||
// return to the previous working directory
|
||||
as.NoError(os.Chdir(cwd))
|
||||
})
|
||||
|
||||
tempDir := test.TempExamples(t)
|
||||
configPath := tempDir + "/treefmt.toml"
|
||||
|
||||
test.WriteConfig(t, configPath, config2.Config{
|
||||
Formatters: map[string]*config2.Formatter{
|
||||
test.WriteConfig(t, configPath, config.Config{
|
||||
Formatters: map[string]*config.Formatter{
|
||||
// a and b have no priority set, which means they default to 0 and should execute first
|
||||
// a and b should execute in lexicographical order
|
||||
// c should execute first since it has a priority of 1
|
||||
@ -595,7 +662,7 @@ func TestDeterministicOrderingInPipeline(t *testing.T) {
|
||||
},
|
||||
})
|
||||
|
||||
_, err := cmd(t, "-C", tempDir)
|
||||
_, err = cmd(t, "-C", tempDir)
|
||||
as.NoError(err)
|
||||
|
||||
matcher := regexp.MustCompile("^fmt-(.*)")
|
||||
|
@ -7,6 +7,8 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/charmbracelet/log"
|
||||
|
||||
"git.numtide.com/numtide/treefmt/stats"
|
||||
|
||||
"git.numtide.com/numtide/treefmt/test"
|
||||
@ -33,7 +35,7 @@ func cmd(t *testing.T, args ...string) ([]byte, error) {
|
||||
t.Helper()
|
||||
|
||||
// create a new kong context
|
||||
p := newKong(t, &Cli)
|
||||
p := newKong(t, &Cli, Options...)
|
||||
ctx, err := p.Parse(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -50,6 +52,8 @@ func cmd(t *testing.T, args ...string) ([]byte, error) {
|
||||
os.Stdout = tempOut
|
||||
os.Stderr = tempOut
|
||||
|
||||
log.SetOutput(tempOut)
|
||||
|
||||
// run the command
|
||||
if err = ctx.Run(); err != nil {
|
||||
return nil, err
|
||||
@ -68,6 +72,7 @@ func cmd(t *testing.T, args ...string) ([]byte, error) {
|
||||
// swap outputs back
|
||||
os.Stdout = stdout
|
||||
os.Stderr = stderr
|
||||
log.SetOutput(stderr)
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
39
cli/mappers.go
Normal file
39
cli/mappers.go
Normal file
@ -0,0 +1,39 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/alecthomas/kong"
|
||||
"github.com/charmbracelet/log"
|
||||
)
|
||||
|
||||
var Options []kong.Option
|
||||
|
||||
func init() {
|
||||
Options = []kong.Option{
|
||||
kong.TypeMapper(reflect.TypeOf(log.DebugLevel), logLevelDecoder()),
|
||||
}
|
||||
}
|
||||
|
||||
func logLevelDecoder() kong.MapperFunc {
|
||||
return func(ctx *kong.DecodeContext, target reflect.Value) error {
|
||||
t, err := ctx.Scan.PopValue("string")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var str string
|
||||
switch v := t.Value.(type) {
|
||||
case string:
|
||||
str = v
|
||||
default:
|
||||
return fmt.Errorf("expected a string but got %q (%T)", t, t.Value)
|
||||
}
|
||||
level, err := log.ParseLevel(str)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse '%v' as log level: %w", level, err)
|
||||
}
|
||||
target.Set(reflect.ValueOf(level))
|
||||
return nil
|
||||
}
|
||||
}
|
@ -26,6 +26,7 @@ Flags:
|
||||
-v, --verbose Set the verbosity of logs e.g. -vv ($LOG_LEVEL).
|
||||
-V, --version Print version.
|
||||
-i, --init Create a new treefmt.toml.
|
||||
-u, --on-unmatched=warn Log paths that did not match any formatters at the specified log level, with fatal exiting the process with an error. Possible values are <debug|info|warn|error|fatal>.
|
||||
--stdin Format the context passed in via stdin.
|
||||
--cpu-profile=STRING The file into which a cpu profile will be written.
|
||||
```
|
||||
@ -95,9 +96,19 @@ while `-vv` will also show `[DEBUG]` messages.
|
||||
|
||||
Create a new `treefmt.toml`.
|
||||
|
||||
### `-u --on-unmatched`
|
||||
|
||||
Log paths that did not match any formatters at the specified log level, with fatal exiting the process with an error. Possible values are <debug|info|warn|error|fatal>.
|
||||
|
||||
[default: warn]
|
||||
|
||||
### `--stdin`
|
||||
|
||||
Format the content passed in via stdin.
|
||||
Format the context passed in via stdin.
|
||||
|
||||
### `--cpu-profile`
|
||||
|
||||
The file into which a cpu profile will be written.
|
||||
|
||||
### `-V, --version`
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user