Compare commits
No commits in common. "b6405d07140eb168e179f1af54767c9b8088a822" and "4c45d2aa7e322ca40b6d7c1f42bc933ed16ffbb2" have entirely different histories.
b6405d0714
...
4c45d2aa7e
29
internal/cache/cache.go
vendored
29
internal/cache/cache.go
vendored
@ -8,7 +8,6 @@ import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/adrg/xdg"
|
||||
"github.com/juju/errors"
|
||||
@ -20,19 +19,8 @@ const (
|
||||
modifiedBucket = "modified"
|
||||
)
|
||||
|
||||
// Entry represents a cache entry, indicating the last size and modified time for a file path.
|
||||
type Entry struct {
|
||||
Size int64
|
||||
Modified time.Time
|
||||
}
|
||||
|
||||
var db *bolt.DB
|
||||
|
||||
// Open creates an instance of bolt.DB for a given treeRoot path.
|
||||
// If clean is true, Open will delete any existing data in the cache.
|
||||
//
|
||||
// The database will be located in `XDG_CACHE_DIR/treefmt/eval-cache/<id>.db`, where <id> is determined by hashing
|
||||
// the treeRoot path. This associates a given treeRoot with a given instance of the cache.
|
||||
func Open(treeRoot string, clean bool) (err error) {
|
||||
// determine a unique and consistent db name for the tree root
|
||||
h := sha1.New()
|
||||
@ -42,7 +30,7 @@ func Open(treeRoot string, clean bool) (err error) {
|
||||
name := base32.StdEncoding.EncodeToString(digest)
|
||||
path, err := xdg.CacheFile(fmt.Sprintf("treefmt/eval-cache/%v.db", name))
|
||||
|
||||
// force a clean of the cache if specified
|
||||
// bust the cache if specified
|
||||
if clean {
|
||||
err := os.Remove(path)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
@ -72,7 +60,6 @@ func Open(treeRoot string, clean bool) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Close closes any open instance of the cache.
|
||||
func Close() error {
|
||||
if db == nil {
|
||||
return nil
|
||||
@ -80,11 +67,10 @@ func Close() error {
|
||||
return db.Close()
|
||||
}
|
||||
|
||||
// getEntry is a helper for reading cache entries from bolt.
|
||||
func getEntry(bucket *bolt.Bucket, path string) (*Entry, error) {
|
||||
func getFileInfo(bucket *bolt.Bucket, path string) (*FileInfo, error) {
|
||||
b := bucket.Get([]byte(path))
|
||||
if b != nil {
|
||||
var cached Entry
|
||||
var cached FileInfo
|
||||
if err := msgpack.Unmarshal(b, &cached); err != nil {
|
||||
return nil, errors.Annotatef(err, "failed to unmarshal cache info for path '%v'", path)
|
||||
}
|
||||
@ -94,8 +80,6 @@ func getEntry(bucket *bolt.Bucket, path string) (*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, pathsCh chan<- string) error {
|
||||
return db.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(modifiedBucket))
|
||||
@ -115,7 +99,7 @@ func ChangeSet(ctx context.Context, root string, pathsCh chan<- string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
cached, err := getEntry(bucket, path)
|
||||
cached, err := getFileInfo(bucket, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -134,7 +118,6 @@ func ChangeSet(ctx context.Context, root string, pathsCh chan<- string) error {
|
||||
})
|
||||
}
|
||||
|
||||
// Update is used to record updated cache information for the specified list of paths.
|
||||
func Update(paths []string) (int, error) {
|
||||
if len(paths) == 0 {
|
||||
return 0, nil
|
||||
@ -150,7 +133,7 @@ func Update(paths []string) (int, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
cached, err := getEntry(bucket, path)
|
||||
cached, err := getFileInfo(bucket, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -167,7 +150,7 @@ func Update(paths []string) (int, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
cacheInfo := Entry{
|
||||
cacheInfo := FileInfo{
|
||||
Size: pathInfo.Size(),
|
||||
Modified: pathInfo.ModTime(),
|
||||
}
|
||||
|
8
internal/cache/types.go
vendored
Normal file
8
internal/cache/types.go
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
package cache
|
||||
|
||||
import "time"
|
||||
|
||||
type FileInfo struct {
|
||||
Size int64
|
||||
Modified time.Time
|
||||
}
|
@ -2,12 +2,10 @@ package format
|
||||
|
||||
import "github.com/BurntSushi/toml"
|
||||
|
||||
// Config is used to represent the list of configured Formatters.
|
||||
type Config struct {
|
||||
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) {
|
||||
_, err = toml.DecodeFile(path, &cfg)
|
||||
return
|
||||
|
@ -9,24 +9,28 @@ const (
|
||||
completedChKey = "completedCh"
|
||||
)
|
||||
|
||||
// RegisterFormatters is used to set a map of formatters in the provided context.
|
||||
func RegisterFormatters(ctx context.Context, formatters map[string]*Formatter) context.Context {
|
||||
return context.WithValue(ctx, formattersKey, formatters)
|
||||
}
|
||||
|
||||
// GetFormatters is used to retrieve a formatters map from the provided context.
|
||||
func GetFormatters(ctx context.Context) map[string]*Formatter {
|
||||
return ctx.Value(formattersKey).(map[string]*Formatter)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// MarkFormatComplete 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 MarkFormatComplete(ctx context.Context, path string) {
|
||||
ctx.Value(completedChKey).(chan string) <- path
|
||||
}
|
||||
|
||||
func ForwardPath(ctx context.Context, path string, names []string) {
|
||||
if len(names) == 0 {
|
||||
return
|
||||
}
|
||||
formatters := GetFormatters(ctx)
|
||||
for _, name := range names {
|
||||
formatters[name].Put(path)
|
||||
}
|
||||
}
|
||||
|
@ -11,50 +11,41 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// ErrFormatterNotFound is returned when the Command for a Formatter is not available.
|
||||
ErrFormatterNotFound = errors.ConstError("formatter not found")
|
||||
)
|
||||
|
||||
// Formatter represents a command which should be applied to a filesystem.
|
||||
type Formatter struct {
|
||||
// Command is the command invoke when applying this Formatter.
|
||||
Command string
|
||||
// Options are an optional list of args to be passed to Command.
|
||||
Options []string
|
||||
// Includes is a list of glob patterns used to determine whether this Formatter should be applied against a path.
|
||||
Name string
|
||||
Command string
|
||||
Options []string
|
||||
Includes []string
|
||||
// Excludes is an optional list of glob patterns used to exclude certain files from this Formatter.
|
||||
Excludes []string
|
||||
Before []string
|
||||
|
||||
name string
|
||||
log *log.Logger
|
||||
log *log.Logger
|
||||
|
||||
// internal compiled versions of Includes and Excludes.
|
||||
// globs for matching against paths
|
||||
includes []glob.Glob
|
||||
excludes []glob.Glob
|
||||
|
||||
// inbox is used to accept new paths for formatting.
|
||||
inbox chan string
|
||||
|
||||
// Entries from inbox are batched according to batchSize and stored in batch for processing when the batchSize has
|
||||
// been reached or Close is invoked.
|
||||
batch []string
|
||||
batchSize int
|
||||
}
|
||||
|
||||
func (f *Formatter) Init(name string) error {
|
||||
// capture the name from the config file
|
||||
f.name = name
|
||||
f.Name = name
|
||||
|
||||
// test if the formatter is available
|
||||
if err := exec.Command(f.Command, "--help").Run(); err != nil {
|
||||
return ErrFormatterNotFound
|
||||
}
|
||||
|
||||
// initialise internal state
|
||||
f.log = log.WithPrefix("format | " + name)
|
||||
f.inbox = make(chan string, 1024)
|
||||
|
||||
f.batchSize = 1024
|
||||
f.inbox = make(chan string, f.batchSize)
|
||||
f.batch = make([]string, f.batchSize)
|
||||
f.batch = f.batch[:0]
|
||||
|
||||
@ -63,7 +54,7 @@ func (f *Formatter) Init(name string) error {
|
||||
for _, pattern := range f.Includes {
|
||||
g, err := glob.Compile("**/" + pattern)
|
||||
if err != nil {
|
||||
return errors.Annotatef(err, "failed to compile include pattern '%v' for formatter '%v'", pattern, f.name)
|
||||
return errors.Annotatef(err, "failed to compile include pattern '%v' for formatter '%v'", pattern, f.Name)
|
||||
}
|
||||
f.includes = append(f.includes, g)
|
||||
}
|
||||
@ -73,7 +64,7 @@ func (f *Formatter) Init(name string) error {
|
||||
for _, pattern := range f.Excludes {
|
||||
g, err := glob.Compile("**/" + pattern)
|
||||
if err != nil {
|
||||
return errors.Annotatef(err, "failed to compile exclude pattern '%v' for formatter '%v'", pattern, f.name)
|
||||
return errors.Annotatef(err, "failed to compile exclude pattern '%v' for formatter '%v'", pattern, f.Name)
|
||||
}
|
||||
f.excludes = append(f.excludes, g)
|
||||
}
|
||||
@ -82,8 +73,6 @@ func (f *Formatter) Init(name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Wants is used to test if a Formatter wants path based on it's configured Includes and Excludes patterns.
|
||||
// Returns true if the Formatter should be applied to path, false otherwise.
|
||||
func (f *Formatter) Wants(path string) bool {
|
||||
match := !PathMatches(path, f.excludes) && PathMatches(path, f.includes)
|
||||
if match {
|
||||
@ -92,31 +81,24 @@ func (f *Formatter) Wants(path string) bool {
|
||||
return match
|
||||
}
|
||||
|
||||
// Put add path into this Formatter's inbox for processing.
|
||||
func (f *Formatter) Put(path string) {
|
||||
f.inbox <- path
|
||||
}
|
||||
|
||||
// Run is the main processing loop for this Formatter.
|
||||
// It accepts a context which is used to lookup certain dependencies and for cancellation.
|
||||
func (f *Formatter) Run(ctx context.Context) (err error) {
|
||||
LOOP:
|
||||
// keep processing until ctx has been cancelled or inbox has been closed
|
||||
for {
|
||||
select {
|
||||
|
||||
case <-ctx.Done():
|
||||
// ctx has been cancelled
|
||||
err = ctx.Err()
|
||||
break LOOP
|
||||
|
||||
case path, ok := <-f.inbox:
|
||||
// check if the inbox has been closed
|
||||
if !ok {
|
||||
break LOOP
|
||||
}
|
||||
|
||||
// add path to the current batch
|
||||
// add to the current batch
|
||||
f.batch = append(f.batch, path)
|
||||
|
||||
if len(f.batch) == f.batchSize {
|
||||
@ -128,17 +110,14 @@ LOOP:
|
||||
}
|
||||
}
|
||||
|
||||
// check if LOOP was exited due to an error
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// processing any lingering batch
|
||||
// final flush
|
||||
return f.apply(ctx)
|
||||
}
|
||||
|
||||
// apply executes Command against the latest batch of paths.
|
||||
// It accepts a context which is used to lookup certain dependencies and for cancellation.
|
||||
func (f *Formatter) apply(ctx context.Context) error {
|
||||
// empty check
|
||||
if len(f.batch) == 0 {
|
||||
@ -153,7 +132,6 @@ func (f *Formatter) apply(ctx context.Context) error {
|
||||
args = append(args, path)
|
||||
}
|
||||
|
||||
// execute
|
||||
start := time.Now()
|
||||
cmd := exec.CommandContext(ctx, f.Command, args...)
|
||||
|
||||
@ -164,9 +142,15 @@ func (f *Formatter) apply(ctx context.Context) error {
|
||||
|
||||
f.log.Infof("%v files processed in %v", len(f.batch), time.Now().Sub(start))
|
||||
|
||||
// mark each path in this batch as completed
|
||||
for _, path := range f.batch {
|
||||
MarkFormatComplete(ctx, path)
|
||||
// mark completed or forward on
|
||||
if len(f.Before) == 0 {
|
||||
for _, path := range f.batch {
|
||||
MarkFormatComplete(ctx, path)
|
||||
}
|
||||
} else {
|
||||
for _, path := range f.batch {
|
||||
ForwardPath(ctx, path, f.Before)
|
||||
}
|
||||
}
|
||||
|
||||
// reset batch
|
||||
@ -175,7 +159,6 @@ func (f *Formatter) apply(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close is used to indicate that a Formatter should process any remaining paths and then stop it's processing loop.
|
||||
func (f *Formatter) Close() {
|
||||
close(f.inbox)
|
||||
}
|
||||
|
21
internal/log/writer.go
Normal file
21
internal/log/writer.go
Normal file
@ -0,0 +1,21 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
|
||||
"github.com/charmbracelet/log"
|
||||
)
|
||||
|
||||
type Writer struct {
|
||||
Log *log.Logger
|
||||
}
|
||||
|
||||
func (l *Writer) Write(p []byte) (n int, err error) {
|
||||
scanner := bufio.NewScanner(bytes.NewReader(p))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
l.Log.Debug(line)
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
@ -23,7 +23,7 @@
|
||||
packages = with pkgs; [
|
||||
# golang
|
||||
go
|
||||
gotools
|
||||
go-tools
|
||||
delve
|
||||
golangci-lint
|
||||
|
||||
|
Reference in New Issue
Block a user