Skip to content

Commit

Permalink
feat: create config package (#25)
Browse files Browse the repository at this point in the history
Move all config related code into a config package.

Signed-off-by: Brian McGee <[email protected]>

Reviewed-on: https://git.numtide.com/numtide/treefmt/pulls/25
Reviewed-by: Jonas Chevalier <[email protected]>
Co-authored-by: Brian McGee <[email protected]>
Co-committed-by: Brian McGee <[email protected]>
  • Loading branch information
brianmcgee authored and Jonas Chevalier committed Jan 15, 2024
1 parent 15db7f4 commit b109358
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 83 deletions.
26 changes: 14 additions & 12 deletions internal/cli/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"syscall"
"time"

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

"git.numtide.com/numtide/treefmt/internal/cache"
"git.numtide.com/numtide/treefmt/internal/format"

Expand Down Expand Up @@ -39,7 +41,7 @@ func (f *Format) Run() error {
defer cancel()

// read config
cfg, err := format.ReadConfigFile(Cli.ConfigFile)
cfg, err := config.ReadFile(Cli.ConfigFile)
if err != nil {
return fmt.Errorf("%w: failed to read config file", err)
}
Expand Down Expand Up @@ -68,8 +70,8 @@ func (f *Format) Run() error {
formatters := make(map[string]*format.Formatter)

// detect broken dependencies
for name, config := range cfg.Formatters {
before := config.Before
for name, formatterCfg := range cfg.Formatters {
before := formatterCfg.Before
if before != "" {
// check child formatter exists
_, ok := cfg.Formatters[before]
Expand All @@ -80,40 +82,40 @@ func (f *Format) Run() error {
}

// dependency cycle detection
for name, config := range cfg.Formatters {
for name, formatterCfg := range cfg.Formatters {
var ok bool
var history []string
childName := name
for {
// add to history
history = append(history, childName)

if config.Before == "" {
if formatterCfg.Before == "" {
break
} else if config.Before == name {
} else if formatterCfg.Before == name {
return fmt.Errorf("formatter cycle detected %v", strings.Join(history, " -> "))
}

// load child config
childName = config.Before
config, ok = cfg.Formatters[config.Before]
childName = formatterCfg.Before
formatterCfg, ok = cfg.Formatters[formatterCfg.Before]
if !ok {
return fmt.Errorf("formatter not found: %v", config.Before)
return fmt.Errorf("formatter not found: %v", formatterCfg.Before)
}
}
}

// init formatters
for name, config := range cfg.Formatters {
for name, formatterCfg := range cfg.Formatters {
if !includeFormatter(name) {
// remove this formatter
delete(cfg.Formatters, name)
l.Debugf("formatter %v is not in formatter list %v, skipping", name, Cli.Formatters)
continue
}

formatter, err := format.NewFormatter(name, config, globalExcludes)
if errors.Is(err, format.ErrFormatterNotFound) && Cli.AllowMissingFormatter {
formatter, err := format.NewFormatter(name, formatterCfg, globalExcludes)
if errors.Is(err, format.ErrCommandNotFound) && Cli.AllowMissingFormatter {
l.Debugf("formatter not found: %v", name)
continue
} else if err != nil {
Expand Down
90 changes: 46 additions & 44 deletions internal/cli/format_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"path/filepath"
"testing"

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

"git.numtide.com/numtide/treefmt/internal/test"
"github.com/go-git/go-billy/v5/osfs"
"github.com/go-git/go-git/v5"
Expand All @@ -24,16 +26,16 @@ func TestAllowMissingFormatter(t *testing.T) {
tempDir := t.TempDir()
configPath := tempDir + "/treefmt.toml"

test.WriteConfig(t, configPath, format.Config{
Formatters: map[string]*format.FormatterConfig{
test.WriteConfig(t, configPath, config.Config{
Formatters: map[string]*config.Formatter{
"foo-fmt": {
Command: "foo-fmt",
},
},
})

_, err := cmd(t, "--config-file", configPath, "--tree-root", tempDir)
as.ErrorIs(err, format.ErrFormatterNotFound)
as.ErrorIs(err, format.ErrCommandNotFound)

_, err = cmd(t, "--config-file", configPath, "--tree-root", tempDir, "--allow-missing-formatter")
as.NoError(err)
Expand All @@ -45,8 +47,8 @@ func TestDependencyCycle(t *testing.T) {
tempDir := t.TempDir()
configPath := tempDir + "/treefmt.toml"

test.WriteConfig(t, configPath, format.Config{
Formatters: map[string]*format.FormatterConfig{
test.WriteConfig(t, configPath, config.Config{
Formatters: map[string]*config.Formatter{
"a": {Command: "echo", Before: "b"},
"b": {Command: "echo", Before: "c"},
"c": {Command: "echo", Before: "a"},
Expand All @@ -66,8 +68,8 @@ func TestSpecifyingFormatters(t *testing.T) {
tempDir := test.TempExamples(t)
configPath := tempDir + "/treefmt.toml"

test.WriteConfig(t, configPath, format.Config{
Formatters: map[string]*format.FormatterConfig{
test.WriteConfig(t, configPath, config.Config{
Formatters: map[string]*config.Formatter{
"elm": {
Command: "echo",
Includes: []string{"*.elm"},
Expand Down Expand Up @@ -115,66 +117,66 @@ func TestIncludesAndExcludes(t *testing.T) {
configPath := tempDir + "/echo.toml"

// test without any excludes
config := format.Config{
Formatters: map[string]*format.FormatterConfig{
cfg := config.Config{
Formatters: map[string]*config.Formatter{
"echo": {
Command: "echo",
Includes: []string{"*"},
},
},
}

test.WriteConfig(t, configPath, config)
test.WriteConfig(t, configPath, cfg)
out, err := cmd(t, "-c", "--config-file", configPath, "--tree-root", tempDir)
as.NoError(err)
as.Contains(string(out), fmt.Sprintf("%d files changed", 29))

// globally exclude nix files
config.Global.Excludes = []string{"*.nix"}
cfg.Global.Excludes = []string{"*.nix"}

test.WriteConfig(t, configPath, config)
test.WriteConfig(t, configPath, cfg)
out, err = cmd(t, "-c", "--config-file", configPath, "--tree-root", tempDir)
as.NoError(err)
as.Contains(string(out), fmt.Sprintf("%d files changed", 28))

// add haskell files to the global exclude
config.Global.Excludes = []string{"*.nix", "*.hs"}
cfg.Global.Excludes = []string{"*.nix", "*.hs"}

test.WriteConfig(t, configPath, config)
test.WriteConfig(t, configPath, cfg)
out, err = cmd(t, "-c", "--config-file", configPath, "--tree-root", tempDir)
as.NoError(err)
as.Contains(string(out), fmt.Sprintf("%d files changed", 22))

echo := config.Formatters["echo"]
echo := cfg.Formatters["echo"]

// remove python files from the echo formatter
echo.Excludes = []string{"*.py"}

test.WriteConfig(t, configPath, config)
test.WriteConfig(t, configPath, cfg)
out, err = cmd(t, "-c", "--config-file", configPath, "--tree-root", tempDir)
as.NoError(err)
as.Contains(string(out), fmt.Sprintf("%d files changed", 20))

// remove go files from the echo formatter
echo.Excludes = []string{"*.py", "*.go"}

test.WriteConfig(t, configPath, config)
test.WriteConfig(t, configPath, cfg)
out, err = cmd(t, "-c", "--config-file", configPath, "--tree-root", tempDir)
as.NoError(err)
as.Contains(string(out), fmt.Sprintf("%d files changed", 19))

// adjust the includes for echo to only include elm files
echo.Includes = []string{"*.elm"}

test.WriteConfig(t, configPath, config)
test.WriteConfig(t, configPath, cfg)
out, err = cmd(t, "-c", "--config-file", configPath, "--tree-root", tempDir)
as.NoError(err)
as.Contains(string(out), fmt.Sprintf("%d files changed", 1))

// add js files to echo formatter
echo.Includes = []string{"*.elm", "*.js"}

test.WriteConfig(t, configPath, config)
test.WriteConfig(t, configPath, cfg)
out, err = cmd(t, "-c", "--config-file", configPath, "--tree-root", tempDir)
as.NoError(err)
as.Contains(string(out), fmt.Sprintf("%d files changed", 2))
Expand All @@ -187,16 +189,16 @@ func TestCache(t *testing.T) {
configPath := tempDir + "/echo.toml"

// test without any excludes
config := format.Config{
Formatters: map[string]*format.FormatterConfig{
cfg := config.Config{
Formatters: map[string]*config.Formatter{
"echo": {
Command: "echo",
Includes: []string{"*"},
},
},
}

test.WriteConfig(t, configPath, config)
test.WriteConfig(t, configPath, cfg)
out, err := cmd(t, "--config-file", configPath, "--tree-root", tempDir)
as.NoError(err)
as.Contains(string(out), fmt.Sprintf("%d files changed", 29))
Expand All @@ -222,16 +224,16 @@ func TestChangeWorkingDirectory(t *testing.T) {
configPath := tempDir + "/treefmt.toml"

// test without any excludes
config := format.Config{
Formatters: map[string]*format.FormatterConfig{
cfg := config.Config{
Formatters: map[string]*config.Formatter{
"echo": {
Command: "echo",
Includes: []string{"*"},
},
},
}

test.WriteConfig(t, configPath, config)
test.WriteConfig(t, configPath, cfg)

// by default, we look for ./treefmt.toml and use the cwd for the tree root
// this should fail if the working directory hasn't been changed first
Expand All @@ -247,16 +249,16 @@ func TestFailOnChange(t *testing.T) {
configPath := tempDir + "/echo.toml"

// test without any excludes
config := format.Config{
Formatters: map[string]*format.FormatterConfig{
cfg := config.Config{
Formatters: map[string]*config.Formatter{
"echo": {
Command: "echo",
Includes: []string{"*"},
},
},
}

test.WriteConfig(t, configPath, config)
test.WriteConfig(t, configPath, cfg)
_, err := cmd(t, "--fail-on-change", "--config-file", configPath, "--tree-root", tempDir)
as.ErrorIs(err, ErrFailOnChange)
}
Expand All @@ -283,8 +285,8 @@ func TestBustCacheOnFormatterChange(t *testing.T) {
as.NoError(os.Setenv("PATH", binPath+":"+os.Getenv("PATH")))

// start with 2 formatters
config := format.Config{
Formatters: map[string]*format.FormatterConfig{
cfg := config.Config{
Formatters: map[string]*config.Formatter{
"python": {
Command: "black",
Includes: []string{"*.py"},
Expand All @@ -297,7 +299,7 @@ func TestBustCacheOnFormatterChange(t *testing.T) {
},
}

test.WriteConfig(t, configPath, config)
test.WriteConfig(t, configPath, cfg)
args := []string{"--config-file", configPath, "--tree-root", tempDir}
out, err := cmd(t, args...)
as.NoError(err)
Expand Down Expand Up @@ -328,12 +330,12 @@ func TestBustCacheOnFormatterChange(t *testing.T) {
as.Contains(string(out), "0 files changed")

// add go formatter
config.Formatters["go"] = &format.FormatterConfig{
cfg.Formatters["go"] = &config.Formatter{
Command: "gofmt",
Options: []string{"-w"},
Includes: []string{"*.go"},
}
test.WriteConfig(t, configPath, config)
test.WriteConfig(t, configPath, cfg)

out, err = cmd(t, args...)
as.NoError(err)
Expand All @@ -345,8 +347,8 @@ func TestBustCacheOnFormatterChange(t *testing.T) {
as.Contains(string(out), "0 files changed")

// remove python formatter
delete(config.Formatters, "python")
test.WriteConfig(t, configPath, config)
delete(cfg.Formatters, "python")
test.WriteConfig(t, configPath, cfg)

out, err = cmd(t, args...)
as.NoError(err)
Expand All @@ -358,8 +360,8 @@ func TestBustCacheOnFormatterChange(t *testing.T) {
as.Contains(string(out), "0 files changed")

// remove elm formatter
delete(config.Formatters, "elm")
test.WriteConfig(t, configPath, config)
delete(cfg.Formatters, "elm")
test.WriteConfig(t, configPath, cfg)

out, err = cmd(t, args...)
as.NoError(err)
Expand All @@ -378,15 +380,15 @@ func TestGitWorktree(t *testing.T) {
configPath := filepath.Join(tempDir, "/treefmt.toml")

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

// init a git repo
repo, err := git.Init(
Expand Down Expand Up @@ -433,8 +435,8 @@ func TestOrderingFormatters(t *testing.T) {
configPath := path.Join(tempDir, "treefmt.toml")

// missing child
test.WriteConfig(t, configPath, format.Config{
Formatters: map[string]*format.FormatterConfig{
test.WriteConfig(t, configPath, config.Config{
Formatters: map[string]*config.Formatter{
"hs-a": {
Command: "echo",
Includes: []string{"*.hs"},
Expand All @@ -447,8 +449,8 @@ func TestOrderingFormatters(t *testing.T) {
as.ErrorContains(err, "formatter hs-a is before hs-b but config for hs-b was not found")

// multiple roots
test.WriteConfig(t, configPath, format.Config{
Formatters: map[string]*format.FormatterConfig{
test.WriteConfig(t, configPath, config.Config{
Formatters: map[string]*config.Formatter{
"hs-a": {
Command: "echo",
Includes: []string{"*.hs"},
Expand Down
8 changes: 4 additions & 4 deletions internal/format/config.go → internal/config/config.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package format
package config

import "github.com/BurntSushi/toml"

Expand All @@ -8,11 +8,11 @@ type Config struct {
// Excludes is an optional list of glob patterns used to exclude certain files from all formatters.
Excludes []string
}
Formatters map[string]*FormatterConfig `toml:"formatter"`
Formatters map[string]*Formatter `toml:"formatter"`
}

// ReadConfigFile reads from path and unmarshals toml into a Config instance.
func ReadConfigFile(path string) (cfg *Config, err error) {
// ReadFile reads from path and unmarshals toml into a Config instance.
func ReadFile(path string) (cfg *Config, err error) {
_, err = toml.DecodeFile(path, &cfg)
return
}
Loading

0 comments on commit b109358

Please sign in to comment.