Skip to content

Commit

Permalink
feat: support reading paths from stdin
Browse files Browse the repository at this point in the history
Signed-off-by: Brian McGee <brian@bmcgee.ie>
  • Loading branch information
brianmcgee committed Feb 15, 2024
1 parent 4dd4c55 commit 0fbae06
Show file tree
Hide file tree
Showing 11 changed files with 243 additions and 51 deletions.
16 changes: 16 additions & 0 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
url = "github:nix-community/gomod2nix";
inputs.nixpkgs.follows = "nixpkgs";
};

nix-filter.url = "github:numtide/nix-filter";
};

outputs = inputs @ {flake-parts, ...}:
Expand Down
9 changes: 2 additions & 7 deletions internal/cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ func putEntry(bucket *bolt.Bucket, path string, entry *Entry) error {

// ChangeSet is used to walk a filesystem, starting at root, and outputting any new or changed paths using pathsCh.
// It determines if a path is new or has changed by comparing against cache entries.
func ChangeSet(ctx context.Context, root string, walkerType walk.Type, pathsCh chan<- string) error {
func ChangeSet(ctx context.Context, walker walk.Walker, pathsCh chan<- string) error {
var tx *bolt.Tx
var bucket *bolt.Bucket
var processed int
Expand All @@ -185,12 +185,7 @@ func ChangeSet(ctx context.Context, root string, walkerType walk.Type, pathsCh c
}
}()

w, err := walk.New(walkerType, root)
if err != nil {
return fmt.Errorf("%w: failed to create walker", err)
}

return w.Walk(ctx, func(path string, info fs.FileInfo, err error) error {
return walker.Walk(ctx, func(path string, info fs.FileInfo, err error) error {
select {
case <-ctx.Done():
return ctx.Err()
Expand Down
21 changes: 10 additions & 11 deletions internal/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,29 @@ import (
"github.com/charmbracelet/log"
)

var Cli = Options{}
var Cli = Format{}

type Options struct {
AllowMissingFormatter bool `default:"false" help:"Do not exit with error if a configured formatter is missing."`
WorkingDirectory kong.ChangeDirFlag `default:"." short:"C" help:"Run as if treefmt was started in the specified working directory instead of the current working directory."`
ClearCache bool `short:"c" help:"Reset the evaluation cache. Use in case the cache is not precise enough."`
type Format struct {
AllowMissingFormatter bool `default:"false" help:"Do not exit with error if a configured formatter is missing"`
WorkingDirectory kong.ChangeDirFlag `default:"." short:"C" help:"Run as if treefmt was started in the specified working directory instead of the current working directory"`
ClearCache bool `short:"c" help:"Reset the evaluation cache. Use in case the cache is not precise enough"`
ConfigFile string `type:"existingfile" default:"./treefmt.toml"`
FailOnChange bool `help:"Exit with error if any changes were made. Useful for CI."`
Formatters []string `help:"Specify formatters to apply. Defaults to all formatters."`
TreeRoot string `type:"existingdir" default:"."`
Walk walk.Type `enum:"auto,git,filesystem" default:"auto" help:"The method used to traverse the files within --tree-root. Currently supports 'auto', 'git' or 'filesystem'."`
Verbosity int `name:"verbose" short:"v" type:"counter" default:"0" env:"LOG_LEVEL" help:"Set the verbosity of logs e.g. -vv."`

Format Format `cmd:"" default:"."`
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"`
}

func (c *Options) Configure() {
func (f *Format) Configure() {
log.SetReportTimestamp(false)

if c.Verbosity == 0 {
log.SetLevel(log.WarnLevel)
} else if c.Verbosity == 1 {
if f.Verbosity == 0 {
log.SetLevel(log.InfoLevel)
} else if c.Verbosity >= 2 {
} else if f.Verbosity > 0 {
log.SetLevel(log.DebugLevel)
}
}
29 changes: 22 additions & 7 deletions internal/cli/format.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cli

import (
"bufio"
"context"
"errors"
"fmt"
Expand All @@ -10,6 +11,8 @@ import (
"syscall"
"time"

"git.numtide.com/numtide/treefmt/internal/walk"

"git.numtide.com/numtide/treefmt/internal/config"

"git.numtide.com/numtide/treefmt/internal/cache"
Expand All @@ -19,8 +22,6 @@ import (
"golang.org/x/sync/errgroup"
)

type Format struct{}

var ErrFailOnChange = errors.New("unexpected changes detected, --fail-on-change is enabled")

func (f *Format) Run() error {
Expand Down Expand Up @@ -201,7 +202,7 @@ func (f *Format) Run() error {
return ErrFailOnChange
}

fmt.Printf("%v files changed in %v", changes, time.Now().Sub(start))
fmt.Printf("%v files changed in %v\n", changes, time.Now().Sub(start))
return nil
})

Expand Down Expand Up @@ -235,10 +236,24 @@ func (f *Format) Run() error {
return nil
})

eg.Go(func() error {
err := cache.ChangeSet(ctx, Cli.TreeRoot, Cli.Walk, pathsCh)
close(pathsCh)
return err
eg.Go(func() (err error) {
paths := Cli.Paths

if len(paths) == 0 && Cli.Stdin {
// read in all the paths
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
paths = append(paths, scanner.Text())
}
}

walker, err := walk.New(Cli.Walk, Cli.TreeRoot, paths)
if err != nil {
return fmt.Errorf("%w: failed to create walker", err)
}

defer close(pathsCh)
return cache.ChangeSet(ctx, walker, pathsCh)
})

// listen for shutdown and call cancel if required
Expand Down
100 changes: 100 additions & 0 deletions internal/cli/format_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -481,3 +481,103 @@ func TestOrderingFormatters(t *testing.T) {
as.NoError(err)
as.Contains(string(out), "8 files changed")
}

func TestPathsArg(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 := filepath.Join(tempDir, "/treefmt.toml")

// change working directory to temp root
as.NoError(os.Chdir(tempDir))

// basic config
cfg := config.Config{
Formatters: map[string]*config.Formatter{
"echo": {
Command: "echo",
Includes: []string{"*"},
},
},
}
test.WriteConfig(t, configPath, cfg)

// without any path args
out, err := cmd(t, "-C", tempDir)
as.NoError(err)
as.Contains(string(out), fmt.Sprintf("%d files changed", 29))

// specify some explicit paths
out, err = cmd(t, "-C", tempDir, "-c", "elm/elm.json", "haskell/Nested/Foo.hs")
as.NoError(err)
as.Contains(string(out), fmt.Sprintf("%d files changed", 2))

// specify a bad path
out, err = cmd(t, "-C", tempDir, "-c", "elm/elm.json", "haskell/Nested/Bar.hs")
as.ErrorContains(err, "no such file or directory")
}

func TestStdIn(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 := filepath.Join(tempDir, "/treefmt.toml")

// change working directory to temp root
as.NoError(os.Chdir(tempDir))

// basic config
cfg := config.Config{
Formatters: map[string]*config.Formatter{
"echo": {
Command: "echo",
Includes: []string{"*"},
},
},
}
test.WriteConfig(t, configPath, cfg)

// swap out stdin
prevStdIn := os.Stdin
stdin, err := os.CreateTemp("", "stdin")
as.NoError(err)

os.Stdin = stdin

t.Cleanup(func() {
os.Stdin = prevStdIn
_ = os.Remove(stdin.Name())
})

go func() {
_, err := stdin.WriteString(`treefmt.toml
elm/elm.json
go/main.go
`)
as.NoError(err, "failed to write to stdin")
as.NoError(stdin.Sync())
_, _ = stdin.Seek(0, 0)
}()

out, err := cmd(t, "-C", tempDir, "--stdin")
as.NoError(err)
as.Contains(string(out), fmt.Sprintf("%d files changed", 3))
}
25 changes: 21 additions & 4 deletions internal/walk/filesystem.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,38 @@ package walk

import (
"context"
"os"
"path/filepath"
)

type filesystemWalker struct {
root string
root string
paths []string
}

func (f filesystemWalker) Root() string {
return f.root
}

func (f filesystemWalker) Walk(_ context.Context, fn filepath.WalkFunc) error {
return filepath.Walk(f.root, fn)
if len(f.paths) == 0 {
return filepath.Walk(f.root, fn)
}

for _, path := range f.paths {
info, err := os.Stat(path)
if err = filepath.Walk(path, fn); err != nil {
return err
}

if err = fn(path, info, err); err != nil {
return err
}
}

return nil
}

func NewFilesystem(root string) (Walker, error) {
return filesystemWalker{root}, nil
func NewFilesystem(root string, paths []string) (Walker, error) {
return filesystemWalker{root, paths}, nil
}
Loading

0 comments on commit 0fbae06

Please sign in to comment.