Compare commits

...

2 Commits

Author SHA1 Message Date
6ae0e4f8e4
feat: add pipeline priority field
Allows for fine-grained control of execution order.

Signed-off-by: Brian McGee <brian@bmcgee.ie>
2024-04-25 09:38:41 +01:00
c71d69051a
feat: have each formatter filter paths again if part of a pipeline
Signed-off-by: Brian McGee <brian@bmcgee.ie>
2024-04-25 09:17:51 +01:00
6 changed files with 72 additions and 41 deletions

View File

@ -59,6 +59,18 @@ func TestReadConfigFile(t *testing.T) {
as.Nil(alejandra.Options)
as.Equal([]string{"*.nix"}, alejandra.Includes)
as.Equal([]string{"examples/nix/sources.nix"}, alejandra.Excludes)
as.Equal("nix", alejandra.Pipeline)
as.Equal(1, alejandra.Priority)
// deadnix
deadnix, ok := cfg.Formatters["deadnix"]
as.True(ok, "deadnix formatter not found")
as.Equal("deadnix", deadnix.Command)
as.Nil(deadnix.Options)
as.Equal([]string{"*.nix"}, deadnix.Includes)
as.Nil(deadnix.Excludes)
as.Equal("nix", deadnix.Pipeline)
as.Equal(2, deadnix.Priority)
// ruby
ruby, ok := cfg.Formatters["ruby"]

View File

@ -9,6 +9,8 @@ type Formatter struct {
Includes []string
// Excludes is an optional list of glob patterns used to exclude certain files from this Formatter.
Excludes []string
//
// Indicates this formatter should be executed as part of a group of formatters all sharing the same pipeline key.
Pipeline string
// Indicates the order of precedence when executing as part of a pipeline.
Priority int
}

View File

@ -1,21 +0,0 @@
package format
import (
"context"
)
const (
completedChKey = "completedCh"
)
// SetCompletedChannel is used to set a channel for indication processing completion in the provided context.
func SetCompletedChannel(ctx context.Context, completedCh chan string) context.Context {
return context.WithValue(ctx, completedChKey, completedCh)
}
// MarkPathComplete is used to indicate that all processing has finished for the provided path.
// This is done by adding the path to the completion channel which should have already been set using
// SetCompletedChannel.
func MarkPathComplete(ctx context.Context, path string) {
ctx.Value(completedChKey).(chan string) <- path
}

View File

@ -27,6 +27,8 @@ type Formatter struct {
// internal compiled versions of Includes and Excludes.
includes []glob.Glob
excludes []glob.Glob
batch []string
}
// Executable returns the path to the executable defined by Command
@ -34,30 +36,53 @@ func (f *Formatter) Executable() string {
return f.executable
}
func (f *Formatter) Apply(ctx context.Context, paths []string) error {
// only apply if the resultant batch is not empty
if len(paths) > 0 {
// construct args, starting with config
args := f.config.Options
func (f *Formatter) Apply(ctx context.Context, paths []string, filter bool) error {
// construct args, starting with config
args := f.config.Options
// append each file path
// If filter is true it indicates we are executing as part of a pipeline.
// In such a scenario each formatter must sub filter the paths provided as different formatters might want different
// files in a pipeline.
if filter {
// reset the batch
f.batch = f.batch[:]
// filter paths
for _, path := range paths {
args = append(args, path)
if f.Wants(path) {
f.batch = append(f.batch, path)
}
}
// execute
start := time.Now()
cmd := exec.CommandContext(ctx, f.config.Command, args...)
if out, err := cmd.CombinedOutput(); err != nil {
f.log.Debugf("\n%v", string(out))
// todo log output
return err
// exit early if nothing to process
if len(f.batch) == 0 {
return nil
}
f.log.Infof("%v files processed in %v", len(paths), time.Now().Sub(start))
// append paths to the args
args = append(args, f.batch...)
} else {
// exit early if nothing to process
if len(paths) == 0 {
return nil
}
// append paths to the args
args = append(args, paths...)
}
// execute the command
start := time.Now()
cmd := exec.CommandContext(ctx, f.config.Command, args...)
if out, err := cmd.CombinedOutput(); err != nil {
f.log.Debugf("\n%v", string(out))
// todo log output
return err
}
f.log.Infof("%v files processed in %v", len(paths), time.Now().Sub(start))
return nil
}
@ -95,7 +120,11 @@ func NewFormatter(
f.executable = executable
// initialise internal state
f.log = log.WithPrefix("format | " + name)
if config.Pipeline == "" {
f.log = log.WithPrefix(fmt.Sprintf("format | %s", name))
} else {
f.log = log.WithPrefix(fmt.Sprintf("format | %s[%s]", config.Pipeline, name))
}
f.includes, err = CompileGlobs(config.Includes)
if err != nil {

View File

@ -1,6 +1,9 @@
package format
import "context"
import (
"context"
"slices"
)
type Pipeline struct {
sequence []*Formatter
@ -8,6 +11,10 @@ type Pipeline struct {
func (p *Pipeline) Add(f *Formatter) {
p.sequence = append(p.sequence, f)
// sort by priority in ascending order
slices.SortFunc(p.sequence, func(a, b *Formatter) int {
return a.config.Priority - b.config.Priority
})
}
func (p *Pipeline) Wants(path string) bool {
@ -23,7 +30,7 @@ func (p *Pipeline) Wants(path string) bool {
func (p *Pipeline) Apply(ctx context.Context, paths []string) error {
for _, f := range p.sequence {
if err := f.Apply(ctx, paths); err != nil {
if err := f.Apply(ctx, paths, len(p.sequence) > 1); err != nil {
return err
}
}

View File

@ -32,11 +32,13 @@ includes = ["*.nix"]
# Act as an example on how to exclude specific files
excludes = ["examples/nix/sources.nix"]
pipeline = "nix"
priority = 1
[formatter.deadnix]
command = "deadnix"
includes = ["*.nix"]
pipeline = "nix"
priority = 2
[formatter.ruby]
command = "rufo"