This repository has been archived on 2024-05-03. You can view files and clone it, but cannot push or open issues or pull requests.
treefmt/internal/cli/format.go

198 lines
3.8 KiB
Go
Raw Normal View History

2023-12-23 12:50:47 +00:00
package cli
import (
"context"
"fmt"
"os"
"os/signal"
"syscall"
2023-12-23 12:50:47 +00:00
"time"
"git.numtide.com/numtide/treefmt/internal/cache"
"git.numtide.com/numtide/treefmt/internal/format"
2023-12-23 12:50:47 +00:00
"github.com/charmbracelet/log"
"golang.org/x/sync/errgroup"
)
type Format struct{}
func (f *Format) Run() error {
start := time.Now()
Cli.Configure()
2023-12-23 12:50:47 +00:00
l := log.WithPrefix("format")
defer func() {
if err := cache.Close(); err != nil {
l.Errorf("failed to close cache: %v", err)
}
}()
// create an overall context
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// read config
cfg, err := format.ReadConfigFile(Cli.ConfigFile)
if err != nil {
return fmt.Errorf("%w: failed to read config file", err)
2023-12-23 12:50:47 +00:00
}
globalExcludes, err := format.CompileGlobs(cfg.Global.Excludes)
// create optional formatter filter set
formatterSet := make(map[string]bool)
for _, name := range Cli.Formatters {
_, ok := cfg.Formatters[name]
if !ok {
return fmt.Errorf("%w: formatter not found in config: %v", err, name)
}
formatterSet[name] = true
}
includeFormatter := func(name string) bool {
if len(formatterSet) == 0 {
return true
} else {
_, include := formatterSet[name]
return include
}
}
2023-12-23 12:50:47 +00:00
// init formatters
for name, formatter := 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
}
err = formatter.Init(name, globalExcludes)
if err == format.ErrFormatterNotFound && Cli.AllowMissingFormatter {
l.Debugf("formatter not found: %v", name)
// remove this formatter
delete(cfg.Formatters, name)
} else if err != nil {
return fmt.Errorf("%w: failed to initialise formatter: %v", err, name)
2023-12-23 12:50:47 +00:00
}
}
ctx = format.RegisterFormatters(ctx, cfg.Formatters)
if err = cache.Open(Cli.TreeRoot, Cli.ClearCache); err != nil {
return err
}
//
pendingCh := make(chan string, 1024)
completedCh := make(chan string, 1024)
ctx = format.SetCompletedChannel(ctx, completedCh)
//
eg, ctx := errgroup.WithContext(ctx)
// start the formatters
for name := range cfg.Formatters {
formatter := cfg.Formatters[name]
eg.Go(func() error {
return formatter.Run(ctx)
})
}
// determine paths to be formatted
pathsCh := make(chan string, 1024)
// update cache as paths are completed
eg.Go(func() error {
batchSize := 1024
batch := make([]string, batchSize)
var pending, completed, changes int
2023-12-23 12:50:47 +00:00
LOOP:
for {
select {
case _, ok := <-pendingCh:
if ok {
pending += 1
} else if pending == completed {
break LOOP
}
case path, ok := <-completedCh:
if !ok {
break LOOP
}
batch = append(batch, path)
if len(batch) == batchSize {
count, err := cache.Update(batch)
if err != nil {
2023-12-23 12:50:47 +00:00
return err
}
changes += count
2023-12-23 12:50:47 +00:00
batch = batch[:0]
}
completed += 1
if completed == pending {
close(completedCh)
}
}
}
// final flush
count, err := cache.Update(batch)
if err != nil {
2023-12-23 12:50:47 +00:00
return err
}
changes += count
2023-12-23 12:50:47 +00:00
fmt.Printf("%v files changed in %v", changes, time.Now().Sub(start))
2023-12-23 12:50:47 +00:00
return nil
})
eg.Go(func() error {
count := 0
for path := range pathsCh {
for _, formatter := range cfg.Formatters {
if formatter.Wants(path) {
pendingCh <- path
count += 1
formatter.Put(path)
}
}
}
for _, formatter := range cfg.Formatters {
formatter.Close()
}
if count == 0 {
close(completedCh)
}
return nil
})
eg.Go(func() error {
defer close(pathsCh)
return cache.ChangeSet(ctx, Cli.TreeRoot, pathsCh)
})
// listen for shutdown and call cancel if required
go func() {
exit := make(chan os.Signal, 1)
signal.Notify(exit, os.Interrupt, syscall.SIGTERM)
<-exit
cancel()
}()
2023-12-23 12:50:47 +00:00
return eg.Wait()
}