diff --git a/cmd/format/format.go b/cmd/format/format.go index 2c3c77fd..b8740b4a 100644 --- a/cmd/format/format.go +++ b/cmd/format/format.go @@ -205,33 +205,34 @@ func Run(v *viper.Viper, statz *stats.Stats, cmd *cobra.Command, paths []string) return fmt.Errorf("failed to create walker: %w", err) } + // start traversing files := make([]*walk.File, BatchSize) + for { + // read the next batch ctx, cancel := context.WithTimeout(ctx, 1*time.Second) - n, err := reader.Read(ctx, files) - for idx := 0; idx < n; idx++ { - file := files[idx] + // ensure context is cancelled to release resources + cancel() - // check if this file is new or has changed when compared to the cache entry - if file.Cache == nil || file.Cache.HasChanged(file.Info) { - filesCh <- file - statz.Add(stats.Emitted, 1) - } + // pass each file into the file channel for processing + for idx := 0; idx < n; idx++ { + filesCh <- files[idx] } - cancel() - if errors.Is(err, io.EOF) { + // we have finished traversing break } else if err != nil { + // something went wrong log.Errorf("failed to read files: %v", err) cancel() break } } + // indicate no further files for processing close(filesCh) // wait for everything to complete @@ -263,6 +264,8 @@ func applyFormatters( // formatters which should be applied to their respective files batches := make(map[string][]*format.Task) + // apply check if the given batch key has enough tasks to trigger processing + // flush is used to force processing regardless of the number of tasks apply := func(key string, flush bool) { // lookup the batch and exit early if it's empty batch := batches[key] @@ -304,6 +307,7 @@ func applyFormatters( } } + // tryApply batches tasks by their batch key and processes the batch if there is enough ready tryApply := func(task *format.Task) { // append to batch key := task.BatchKey @@ -314,25 +318,29 @@ func applyFormatters( return func() error { defer func() { - // close processed channel + // indicate processing has finished close(formattedCh) }() + // parse unmatched log level unmatchedLevel, err := log.ParseLevel(cfg.OnUnmatched) if err != nil { return fmt.Errorf("invalid on-unmatched value: %w", err) } - // iterate the files channel + // iterate the file channel for file := range filesCh { // first check if this file has been globally excluded if format.PathMatches(file.RelPath, globalExcludes) { log.Debugf("path matched global excludes: %s", file.RelPath) - // mark it as processed and continue to the next - formattedCh <- &format.Task{ - File: file, + + // release the file + if err := file.Release(); err != nil { + return fmt.Errorf("failed to release file: %w", err) } + + // continue to the next file continue } @@ -344,21 +352,30 @@ func applyFormatters( } } - // see if any formatters matched + // check for no matches if len(matches) == 0 { - + // log that there was no match, exiting with an error if the unmatched level was set to fatal if unmatchedLevel == log.FatalLevel { return fmt.Errorf("no formatter for path: %s", file.RelPath) } + log.Logf(unmatchedLevel, "no formatter for path: %s", file.RelPath) - // mark it as processed and continue to the next - formattedCh <- &format.Task{ - File: file, + + // release the file + if err := file.Release(); err != nil { + return fmt.Errorf("failed to release file: %w", err) } - } else { - // record the match - statz.Add(stats.Matched, 1) - // create a new format task, add it to a batch based on its batch key and try to apply if the batch is full + + // continue to the next file + continue + } + + // record there was a match + statz.Add(stats.Matched, 1) + + // check if this file is new or has changed when compared to the cache entry + if file.Cache == nil || file.Cache.HasChanged(file.Info) { + // if so, generate a format task, add it to the relevant batch (by batch key) and try to process task := format.NewTask(file, matches) tryApply(&task) } @@ -398,16 +415,21 @@ func postProcessing( break LOOP } - // check if the file has changed + // grab the underlying file reference file := task.File + + // check if the file has changed changed, newInfo, err := file.Stat() if err != nil { return err } + // record that a format occurred + statz.Add(stats.Formatted, 1) + if changed { - // record the change - statz.Add(stats.Formatted, 1) + // record that a change in the underlying file occurred + statz.Add(stats.Changed, 1) logMethod := log.Debug if cfg.FailOnChange { @@ -434,8 +456,8 @@ func postProcessing( } } - // if fail on change has been enabled, check that no files were actually formatted, throwing an error if so - if cfg.FailOnChange && statz.Value(stats.Formatted) != 0 { + // if fail on change has been enabled, check that no files were actually changed, throwing an error if so + if cfg.FailOnChange && statz.Value(stats.Changed) != 0 { return ErrFailOnChange } diff --git a/cmd/root_test.go b/cmd/root_test.go index 6365f807..970fcc1c 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -72,24 +72,24 @@ func TestOnUnmatched(t *testing.T) { var out []byte // default is warn - out, _, err = treefmt(t, "-C", tempDir, "--allow-missing-formatter", "-c") + out, _, err = treefmt(t, "-C", tempDir, "--allow-missing-formatter") as.NoError(err) checkOutput("WARN", out) - out, _, err = treefmt(t, "-C", tempDir, "--allow-missing-formatter", "-c", "--on-unmatched", "warn") + out, _, err = treefmt(t, "-C", tempDir, "--allow-missing-formatter", "--on-unmatched", "warn") as.NoError(err) checkOutput("WARN", out) - out, _, err = treefmt(t, "-C", tempDir, "--allow-missing-formatter", "-c", "-u", "error") + out, _, err = treefmt(t, "-C", tempDir, "--allow-missing-formatter", "-u", "error") as.NoError(err) checkOutput("ERRO", out) - out, _, err = treefmt(t, "-C", tempDir, "--allow-missing-formatter", "-c", "-v", "--on-unmatched", "info") + out, _, err = treefmt(t, "-C", tempDir, "--allow-missing-formatter", "-v", "--on-unmatched", "info") as.NoError(err) checkOutput("INFO", out) t.Setenv("TREEFMT_ON_UNMATCHED", "debug") - out, _, err = treefmt(t, "-C", tempDir, "--allow-missing-formatter", "-c", "-vv") + out, _, err = treefmt(t, "-C", tempDir, "--allow-missing-formatter", "-vv") as.NoError(err) checkOutput("DEBU", out) } @@ -182,25 +182,53 @@ func TestSpecifyingFormatters(t *testing.T) { setup() _, statz, err := treefmt(t, "-c", "--config-file", configPath, "--tree-root", tempDir) as.NoError(err) - assertStats(t, as, statz, 32, 32, 3, 3) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 32, + stats.Matched: 3, + stats.Formatted: 3, + stats.Changed: 3, + }) setup() + _, statz, err = treefmt(t, "-c", "--config-file", configPath, "--tree-root", tempDir, "--formatters", "elm,nix") as.NoError(err) - assertStats(t, as, statz, 32, 32, 2, 2) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 32, + stats.Matched: 2, + stats.Formatted: 2, + stats.Changed: 2, + }) setup() + _, statz, err = treefmt(t, "-c", "--config-file", configPath, "--tree-root", tempDir, "-f", "ruby,nix") as.NoError(err) - assertStats(t, as, statz, 32, 32, 2, 2) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 32, + stats.Matched: 2, + stats.Formatted: 2, + stats.Changed: 2, + }) setup() + _, statz, err = treefmt(t, "-c", "--config-file", configPath, "--tree-root", tempDir, "--formatters", "nix") as.NoError(err) - assertStats(t, as, statz, 32, 32, 1, 1) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 32, + stats.Matched: 1, + stats.Formatted: 1, + stats.Changed: 1, + }) // test bad names setup() + _, _, err = treefmt(t, "-c", "--config-file", configPath, "--tree-root", tempDir, "--formatters", "foo") as.Errorf(err, "formatter not found in config: foo") @@ -228,7 +256,13 @@ func TestIncludesAndExcludes(t *testing.T) { test.WriteConfig(t, configPath, cfg) _, statz, err := treefmt(t, "-c", "--config-file", configPath, "--tree-root", tempDir) as.NoError(err) - assertStats(t, as, statz, 32, 32, 32, 0) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 32, + stats.Matched: 32, + stats.Formatted: 32, + stats.Changed: 0, + }) // globally exclude nix files cfg.Excludes = []string{"*.nix"} @@ -236,7 +270,13 @@ func TestIncludesAndExcludes(t *testing.T) { test.WriteConfig(t, configPath, cfg) _, statz, err = treefmt(t, "-c", "--config-file", configPath, "--tree-root", tempDir) as.NoError(err) - assertStats(t, as, statz, 32, 32, 31, 0) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 32, + stats.Matched: 31, + stats.Formatted: 31, + stats.Changed: 0, + }) // add haskell files to the global exclude cfg.Excludes = []string{"*.nix", "*.hs"} @@ -244,7 +284,13 @@ func TestIncludesAndExcludes(t *testing.T) { test.WriteConfig(t, configPath, cfg) _, statz, err = treefmt(t, "-c", "--config-file", configPath, "--tree-root", tempDir) as.NoError(err) - assertStats(t, as, statz, 32, 32, 25, 0) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 32, + stats.Matched: 25, + stats.Formatted: 25, + stats.Changed: 0, + }) echo := cfg.FormatterConfigs["echo"] @@ -254,7 +300,13 @@ func TestIncludesAndExcludes(t *testing.T) { test.WriteConfig(t, configPath, cfg) _, statz, err = treefmt(t, "-c", "--config-file", configPath, "--tree-root", tempDir) as.NoError(err) - assertStats(t, as, statz, 32, 32, 23, 0) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 32, + stats.Matched: 23, + stats.Formatted: 23, + stats.Changed: 0, + }) // remove go files from the echo formatter via env t.Setenv("TREEFMT_FORMATTER_ECHO_EXCLUDES", "*.py,*.go") @@ -262,7 +314,13 @@ func TestIncludesAndExcludes(t *testing.T) { test.WriteConfig(t, configPath, cfg) _, statz, err = treefmt(t, "-c", "--config-file", configPath, "--tree-root", tempDir) as.NoError(err) - assertStats(t, as, statz, 32, 32, 22, 0) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 32, + stats.Matched: 22, + stats.Formatted: 22, + stats.Changed: 0, + }) t.Setenv("TREEFMT_FORMATTER_ECHO_EXCLUDES", "") // reset @@ -272,7 +330,13 @@ func TestIncludesAndExcludes(t *testing.T) { test.WriteConfig(t, configPath, cfg) _, statz, err = treefmt(t, "-c", "--config-file", configPath, "--tree-root", tempDir) as.NoError(err) - assertStats(t, as, statz, 32, 32, 1, 0) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 32, + stats.Matched: 1, + stats.Formatted: 1, + stats.Changed: 0, + }) // add js files to echo formatter via env t.Setenv("TREEFMT_FORMATTER_ECHO_INCLUDES", "*.elm,*.js") @@ -280,7 +344,13 @@ func TestIncludesAndExcludes(t *testing.T) { test.WriteConfig(t, configPath, cfg) _, statz, err = treefmt(t, "-c", "--config-file", configPath, "--tree-root", tempDir) as.NoError(err) - assertStats(t, as, statz, 32, 32, 2, 0) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 32, + stats.Matched: 2, + stats.Formatted: 2, + stats.Changed: 0, + }) } func TestPrjRootEnvVariable(t *testing.T) { @@ -303,7 +373,13 @@ func TestPrjRootEnvVariable(t *testing.T) { t.Setenv("PRJ_ROOT", tempDir) _, statz, err := treefmt(t, "--config-file", configPath) as.NoError(err) - assertStats(t, as, statz, 32, 32, 32, 0) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 32, + stats.Matched: 32, + stats.Formatted: 32, + stats.Changed: 0, + }) } func TestCache(t *testing.T) { @@ -327,34 +403,76 @@ func TestCache(t *testing.T) { test.WriteConfig(t, configPath, cfg) _, statz, err := treefmt(t, "--config-file", configPath, "--tree-root", tempDir) as.NoError(err) - assertStats(t, as, statz, 32, 32, 32, 0) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 32, + stats.Matched: 32, + stats.Formatted: 32, + stats.Changed: 0, + }) _, statz, err = treefmt(t, "--config-file", configPath, "--tree-root", tempDir) as.NoError(err) - assertStats(t, as, statz, 32, 0, 0, 0) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 32, + stats.Matched: 32, + stats.Formatted: 0, + stats.Changed: 0, + }) // clear cache _, statz, err = treefmt(t, "--config-file", configPath, "--tree-root", tempDir, "-c") as.NoError(err) - assertStats(t, as, statz, 32, 32, 32, 0) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 32, + stats.Matched: 32, + stats.Formatted: 32, + stats.Changed: 0, + }) _, statz, err = treefmt(t, "--config-file", configPath, "--tree-root", tempDir) as.NoError(err) - assertStats(t, as, statz, 32, 0, 0, 0) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 32, + stats.Matched: 32, + stats.Formatted: 0, + stats.Changed: 0, + }) // clear cache _, statz, err = treefmt(t, "--config-file", configPath, "--tree-root", tempDir, "-c") as.NoError(err) - assertStats(t, as, statz, 32, 32, 32, 0) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 32, + stats.Matched: 32, + stats.Formatted: 32, + stats.Changed: 0, + }) _, statz, err = treefmt(t, "--config-file", configPath, "--tree-root", tempDir) as.NoError(err) - assertStats(t, as, statz, 32, 0, 0, 0) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 32, + stats.Matched: 32, + stats.Formatted: 0, + stats.Changed: 0, + }) // no cache _, statz, err = treefmt(t, "--config-file", configPath, "--tree-root", tempDir, "--no-cache") as.NoError(err) - assertStats(t, as, statz, 32, 32, 32, 0) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 32, + stats.Matched: 32, + stats.Formatted: 32, + stats.Changed: 0, + }) } func TestChangeWorkingDirectory(t *testing.T) { @@ -388,13 +506,25 @@ func TestChangeWorkingDirectory(t *testing.T) { // this should fail if the working directory hasn't been changed first _, statz, err := treefmt(t, "-C", tempDir) as.NoError(err) - assertStats(t, as, statz, 32, 32, 32, 0) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 32, + stats.Matched: 32, + stats.Formatted: 32, + stats.Changed: 0, + }) // use env t.Setenv("TREEFMT_WORKING_DIR", tempDir) _, statz, err = treefmt(t, "-c") as.NoError(err) - assertStats(t, as, statz, 32, 32, 32, 0) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 32, + stats.Matched: 32, + stats.Formatted: 32, + stats.Changed: 0, + }) } func TestFailOnChange(t *testing.T) { @@ -467,31 +597,61 @@ func TestBustCacheOnFormatterChange(t *testing.T) { args := []string{"--config-file", configPath, "--tree-root", tempDir} _, statz, err := treefmt(t, args...) as.NoError(err) - assertStats(t, as, statz, 32, 32, 3, 0) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 32, + stats.Matched: 3, + stats.Formatted: 3, + stats.Changed: 0, + }) // tweak mod time of elm formatter as.NoError(test.RecreateSymlink(t, binPath+"/"+"elm-format")) _, statz, err = treefmt(t, args...) as.NoError(err) - assertStats(t, as, statz, 32, 32, 3, 0) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 32, + stats.Matched: 3, + stats.Formatted: 3, + stats.Changed: 0, + }) // check cache is working _, statz, err = treefmt(t, args...) as.NoError(err) - assertStats(t, as, statz, 32, 0, 0, 0) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 32, + stats.Matched: 3, + stats.Formatted: 0, + stats.Changed: 0, + }) // tweak mod time of python formatter as.NoError(test.RecreateSymlink(t, binPath+"/"+"black")) _, statz, err = treefmt(t, args...) as.NoError(err) - assertStats(t, as, statz, 32, 32, 3, 0) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 32, + stats.Matched: 3, + stats.Formatted: 3, + stats.Changed: 0, + }) // check cache is working _, statz, err = treefmt(t, args...) as.NoError(err) - assertStats(t, as, statz, 32, 0, 0, 0) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 32, + stats.Matched: 3, + stats.Formatted: 0, + stats.Changed: 0, + }) // add go formatter cfg.FormatterConfigs["go"] = &config.Formatter{ @@ -503,12 +663,24 @@ func TestBustCacheOnFormatterChange(t *testing.T) { _, statz, err = treefmt(t, args...) as.NoError(err) - assertStats(t, as, statz, 32, 32, 4, 0) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 32, + stats.Matched: 4, + stats.Formatted: 4, + stats.Changed: 0, + }) // check cache is working _, statz, err = treefmt(t, args...) as.NoError(err) - assertStats(t, as, statz, 32, 0, 0, 0) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 32, + stats.Matched: 4, + stats.Formatted: 0, + stats.Changed: 0, + }) // remove python formatter delete(cfg.FormatterConfigs, "python") @@ -516,12 +688,24 @@ func TestBustCacheOnFormatterChange(t *testing.T) { _, statz, err = treefmt(t, args...) as.NoError(err) - assertStats(t, as, statz, 32, 32, 2, 0) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 32, + stats.Matched: 2, + stats.Formatted: 2, + stats.Changed: 0, + }) // check cache is working _, statz, err = treefmt(t, args...) as.NoError(err) - assertStats(t, as, statz, 32, 0, 0, 0) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 32, + stats.Matched: 2, + stats.Formatted: 0, + stats.Changed: 0, + }) // remove elm formatter delete(cfg.FormatterConfigs, "elm") @@ -529,12 +713,24 @@ func TestBustCacheOnFormatterChange(t *testing.T) { _, statz, err = treefmt(t, args...) as.NoError(err) - assertStats(t, as, statz, 32, 32, 1, 0) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 32, + stats.Matched: 1, + stats.Formatted: 1, + stats.Changed: 0, + }) // check cache is working _, statz, err = treefmt(t, args...) as.NoError(err) - assertStats(t, as, statz, 32, 0, 0, 0) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 32, + stats.Matched: 1, + stats.Formatted: 0, + stats.Changed: 0, + }) } func TestGitWorktree(t *testing.T) { @@ -569,10 +765,16 @@ func TestGitWorktree(t *testing.T) { wt, err := repo.Worktree() as.NoError(err, "failed to get git worktree") - run := func(traversed int32, emitted int32, matched int32, formatted int32) { + run := func(traversed int32, matched int32, formatted int32, changed int32) { _, statz, err := treefmt(t, "-c", "--config-file", configPath, "--tree-root", tempDir) as.NoError(err) - assertStats(t, as, statz, traversed, emitted, matched, formatted) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: traversed, + stats.Matched: matched, + stats.Formatted: formatted, + stats.Changed: changed, + }) } // run before adding anything to the worktree @@ -592,9 +794,16 @@ func TestGitWorktree(t *testing.T) { run(28, 28, 28, 0) // walk with filesystem instead of git + // we should traverse more files since we will look in the .git folder _, statz, err := treefmt(t, "-c", "--config-file", configPath, "--tree-root", tempDir, "--walk", "filesystem") as.NoError(err) - assertStats(t, as, statz, 60, 60, 60, 0) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 60, + stats.Matched: 60, + stats.Formatted: 60, + stats.Changed: 0, + }) // capture current cwd, so we can replace it after the test is finished cwd, err := os.Getwd() @@ -608,15 +817,33 @@ func TestGitWorktree(t *testing.T) { // format specific sub paths _, statz, err = treefmt(t, "-C", tempDir, "-c", "go", "-vv") as.NoError(err) - assertStats(t, as, statz, 2, 2, 2, 0) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 2, + stats.Matched: 2, + stats.Formatted: 2, + stats.Changed: 0, + }) _, statz, err = treefmt(t, "-C", tempDir, "-c", "go", "haskell") as.NoError(err) - assertStats(t, as, statz, 9, 9, 9, 0) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 9, + stats.Matched: 9, + stats.Formatted: 9, + stats.Changed: 0, + }) _, statz, err = treefmt(t, "-C", tempDir, "-c", "go", "haskell", "ruby") as.NoError(err) - assertStats(t, as, statz, 10, 10, 10, 0) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 10, + stats.Matched: 10, + stats.Formatted: 10, + stats.Changed: 0, + }) // try with a bad path _, _, err = treefmt(t, "-C", tempDir, "-c", "haskell", "foo") @@ -628,11 +855,23 @@ func TestGitWorktree(t *testing.T) { _, statz, err = treefmt(t, "-C", tempDir, "-c", "haskell", "foo.txt") as.NoError(err) - assertStats(t, as, statz, 8, 8, 8, 0) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 8, + stats.Matched: 8, + stats.Formatted: 8, + stats.Changed: 0, + }) _, statz, err = treefmt(t, "-C", tempDir, "-c", "foo.txt") as.NoError(err) - assertStats(t, as, statz, 1, 1, 1, 0) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 1, + stats.Matched: 1, + stats.Formatted: 1, + stats.Changed: 0, + }) } func TestPathsArg(t *testing.T) { @@ -677,19 +916,38 @@ func TestPathsArg(t *testing.T) { // without any path args _, statz, err := treefmt(t) as.NoError(err) - assertStats(t, as, statz, 32, 32, 32, 0) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 32, + stats.Matched: 32, + stats.Formatted: 32, + stats.Changed: 0, + }) // specify some explicit paths _, statz, err = treefmt(t, "-c", "elm/elm.json", "haskell/Nested/Foo.hs") as.NoError(err) - assertStats(t, as, statz, 2, 2, 2, 0) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 2, + stats.Matched: 2, + stats.Formatted: 2, + stats.Changed: 0, + }) // specify an absolute path absoluteInternalPath, err := filepath.Abs("elm/elm.json") as.NoError(err) + _, statz, err = treefmt(t, "-c", absoluteInternalPath) as.NoError(err) - assertStats(t, as, statz, 1, 1, 1, 0) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 1, + stats.Matched: 1, + stats.Formatted: 1, + stats.Changed: 0, + }) // specify a bad path _, _, err = treefmt(t, "-c", "elm/elm.json", "haskell/Nested/Bar.hs") @@ -742,7 +1000,13 @@ func TestStdin(t *testing.T) { out, statz, err := treefmt(t, "-C", tempDir, "--allow-missing-formatter", "--stdin", "test.nix") as.NoError(err) - assertStats(t, as, statz, 1, 1, 1, 1) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 1, + stats.Matched: 1, + stats.Formatted: 1, + stats.Changed: 1, + }) // the nix formatters should have reduced the example to the following as.Equal(`{ ...}: "hello" @@ -767,7 +1031,13 @@ func TestStdin(t *testing.T) { out, statz, err = treefmt(t, "-C", tempDir, "--allow-missing-formatter", "--stdin", "test.md") as.NoError(err) - assertStats(t, as, statz, 1, 1, 1, 1) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 1, + stats.Matched: 1, + stats.Formatted: 1, + stats.Changed: 1, + }) as.Equal(`| col1 | col2 | | ------ | --------- | @@ -881,7 +1151,13 @@ func TestRunInSubdir(t *testing.T) { // without any path args, should reformat the whole tree _, statz, err := treefmt(t) as.NoError(err) - assertStats(t, as, statz, 32, 32, 32, 0) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 32, + stats.Matched: 32, + stats.Formatted: 32, + stats.Changed: 0, + }) // specify some explicit paths, relative to the tree root // this should not work, as we're in a subdirectory @@ -891,7 +1167,13 @@ func TestRunInSubdir(t *testing.T) { // specify some explicit paths, relative to the current directory _, statz, err = treefmt(t, "-c", "elm.json", "../haskell/Nested/Foo.hs") as.NoError(err) - assertStats(t, as, statz, 2, 2, 2, 0) + + assertStats(t, as, statz, map[stats.Type]int32{ + stats.Traversed: 2, + stats.Matched: 2, + stats.Formatted: 2, + stats.Changed: 0, + }) } func treefmt(t *testing.T, args ...string) ([]byte, *stats.Stats, error) { @@ -945,10 +1227,15 @@ func treefmt(t *testing.T, args ...string) ([]byte, *stats.Stats, error) { return out, statz, nil } -func assertStats(t *testing.T, as *require.Assertions, statz *stats.Stats, traversed int32, emitted int32, matched int32, formatted int32) { +func assertStats( + t *testing.T, + as *require.Assertions, + statz *stats.Stats, + expected map[stats.Type]int32, +) { t.Helper() - as.Equal(traversed, statz.Value(stats.Traversed), "stats.traversed") - as.Equal(emitted, statz.Value(stats.Emitted), "stats.emitted") - as.Equal(matched, statz.Value(stats.Matched), "stats.matched") - as.Equal(formatted, statz.Value(stats.Formatted), "stats.formatted") + + for k, v := range expected { + as.Equal(v, statz.Value(k), k.String()) + } } diff --git a/stats/stats.go b/stats/stats.go index 8c5e960b..979e922b 100644 --- a/stats/stats.go +++ b/stats/stats.go @@ -7,13 +7,14 @@ import ( "time" ) +//go:generate enumer -type=Type -text -transform=snake -output=./stats_type.go type Type int const ( Traversed Type = iota - Emitted Matched Formatted + Changed ) type Stats struct { @@ -36,7 +37,7 @@ func (s *Stats) Elapsed() time.Duration { func (s *Stats) Print() { components := []string{ "traversed %d files", - "emitted %d files for processing", + "matched %d files", "formatted %d files (%d changed) in %v", "", } @@ -44,9 +45,9 @@ func (s *Stats) Print() { fmt.Printf( strings.Join(components, "\n"), s.Value(Traversed), - s.Value(Emitted), s.Value(Matched), s.Value(Formatted), + s.Value(Changed), s.Elapsed().Round(time.Millisecond), ) } @@ -55,9 +56,9 @@ func New() Stats { // init counters counters := make(map[Type]*atomic.Int32) counters[Traversed] = &atomic.Int32{} - counters[Emitted] = &atomic.Int32{} counters[Matched] = &atomic.Int32{} counters[Formatted] = &atomic.Int32{} + counters[Changed] = &atomic.Int32{} return Stats{ start: time.Now(), diff --git a/stats/stats_type.go b/stats/stats_type.go new file mode 100644 index 00000000..e865fdf1 --- /dev/null +++ b/stats/stats_type.go @@ -0,0 +1,98 @@ +// Code generated by "enumer -type=Type -text -transform=snake -output=./stats_type.go"; DO NOT EDIT. + +package stats + +import ( + "fmt" + "strings" +) + +const _TypeName = "traversedmatchedformattedchanged" + +var _TypeIndex = [...]uint8{0, 9, 16, 25, 32} + +const _TypeLowerName = "traversedmatchedformattedchanged" + +func (i Type) String() string { + if i < 0 || i >= Type(len(_TypeIndex)-1) { + return fmt.Sprintf("Type(%d)", i) + } + return _TypeName[_TypeIndex[i]:_TypeIndex[i+1]] +} + +// An "invalid array index" compiler error signifies that the constant values have changed. +// Re-run the stringer command to generate them again. +func _TypeNoOp() { + var x [1]struct{} + _ = x[Traversed-(0)] + _ = x[Matched-(1)] + _ = x[Formatted-(2)] + _ = x[Changed-(3)] +} + +var _TypeValues = []Type{Traversed, Matched, Formatted, Changed} + +var _TypeNameToValueMap = map[string]Type{ + _TypeName[0:9]: Traversed, + _TypeLowerName[0:9]: Traversed, + _TypeName[9:16]: Matched, + _TypeLowerName[9:16]: Matched, + _TypeName[16:25]: Formatted, + _TypeLowerName[16:25]: Formatted, + _TypeName[25:32]: Changed, + _TypeLowerName[25:32]: Changed, +} + +var _TypeNames = []string{ + _TypeName[0:9], + _TypeName[9:16], + _TypeName[16:25], + _TypeName[25:32], +} + +// TypeString retrieves an enum value from the enum constants string name. +// Throws an error if the param is not part of the enum. +func TypeString(s string) (Type, error) { + if val, ok := _TypeNameToValueMap[s]; ok { + return val, nil + } + + if val, ok := _TypeNameToValueMap[strings.ToLower(s)]; ok { + return val, nil + } + return 0, fmt.Errorf("%s does not belong to Type values", s) +} + +// TypeValues returns all values of the enum +func TypeValues() []Type { + return _TypeValues +} + +// TypeStrings returns a slice of all String values of the enum +func TypeStrings() []string { + strs := make([]string, len(_TypeNames)) + copy(strs, _TypeNames) + return strs +} + +// IsAType returns "true" if the value is listed in the enum definition. "false" otherwise +func (i Type) IsAType() bool { + for _, v := range _TypeValues { + if i == v { + return true + } + } + return false +} + +// MarshalText implements the encoding.TextMarshaler interface for Type +func (i Type) MarshalText() ([]byte, error) { + return []byte(i.String()), nil +} + +// UnmarshalText implements the encoding.TextUnmarshaler interface for Type +func (i *Type) UnmarshalText(text []byte) error { + var err error + *i, err = TypeString(string(text)) + return err +} diff --git a/walk/filesystem_test.go b/walk/filesystem_test.go index 8b1a2e86..60870fb4 100644 --- a/walk/filesystem_test.go +++ b/walk/filesystem_test.go @@ -81,7 +81,7 @@ func TestFilesystemReader(t *testing.T) { as.Equal(32, count) as.Equal(int32(32), statz.Value(stats.Traversed)) - as.Equal(int32(0), statz.Value(stats.Emitted)) as.Equal(int32(0), statz.Value(stats.Matched)) as.Equal(int32(0), statz.Value(stats.Formatted)) + as.Equal(int32(0), statz.Value(stats.Changed)) } diff --git a/walk/git_test.go b/walk/git_test.go index d7ed418f..2b2abbc0 100644 --- a/walk/git_test.go +++ b/walk/git_test.go @@ -62,7 +62,7 @@ func TestGitReader(t *testing.T) { as.Equal(32, count) as.Equal(int32(32), statz.Value(stats.Traversed)) - as.Equal(int32(0), statz.Value(stats.Emitted)) as.Equal(int32(0), statz.Value(stats.Matched)) as.Equal(int32(0), statz.Value(stats.Formatted)) + as.Equal(int32(0), statz.Value(stats.Changed)) }