Commands that run alone one by one on reading and writing the file This should be used on commands that will modify a large part of the file (or generally large parts) Since that can fuck up the indices of other commands when ran together
262 lines
7.3 KiB
Go
262 lines
7.3 KiB
Go
package utils
|
|
|
|
import (
|
|
"fmt"
|
|
"modify/logger"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/bmatcuk/doublestar/v4"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
type ModifyCommand struct {
|
|
Name string `yaml:"name"`
|
|
Regex string `yaml:"regex"`
|
|
Lua string `yaml:"lua"`
|
|
Files []string `yaml:"files"`
|
|
Git bool `yaml:"git"`
|
|
Reset bool `yaml:"reset"`
|
|
LogLevel string `yaml:"loglevel"`
|
|
Isolate bool `yaml:"isolate"`
|
|
}
|
|
type CookFile []ModifyCommand
|
|
|
|
func (c *ModifyCommand) Validate() error {
|
|
if c.Regex == "" {
|
|
return fmt.Errorf("pattern is required")
|
|
}
|
|
if c.Lua == "" {
|
|
return fmt.Errorf("lua expression is required")
|
|
}
|
|
if len(c.Files) == 0 {
|
|
return fmt.Errorf("at least one file is required")
|
|
}
|
|
if c.LogLevel == "" {
|
|
c.LogLevel = "INFO"
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Ehh.. Not much better... Guess this wasn't the big deal
|
|
var matchesMemoTable map[string]bool = make(map[string]bool)
|
|
|
|
func Matches(path string, glob string) (bool, error) {
|
|
key := fmt.Sprintf("%s:%s", path, glob)
|
|
if matches, ok := matchesMemoTable[key]; ok {
|
|
logger.Debug("Found match for file %q and glob %q in memo table", path, glob)
|
|
return matches, nil
|
|
}
|
|
matches, err := doublestar.Match(glob, path)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to match glob %s with file %s: %w", glob, path, err)
|
|
}
|
|
matchesMemoTable[key] = matches
|
|
return matches, nil
|
|
}
|
|
|
|
type FileCommandAssociation struct {
|
|
File string
|
|
IsolateCommands []ModifyCommand
|
|
Commands []ModifyCommand
|
|
}
|
|
|
|
func AssociateFilesWithCommands(files []string, commands []ModifyCommand) (map[string]FileCommandAssociation, error) {
|
|
associationCount := 0
|
|
fileCommands := make(map[string]FileCommandAssociation)
|
|
|
|
for _, file := range files {
|
|
fileCommands[file] = FileCommandAssociation{
|
|
File: file,
|
|
IsolateCommands: []ModifyCommand{},
|
|
Commands: []ModifyCommand{},
|
|
}
|
|
for _, command := range commands {
|
|
for _, glob := range command.Files {
|
|
matches, err := Matches(file, glob)
|
|
if err != nil {
|
|
logger.Trace("Failed to match glob %s with file %s: %v", glob, file, err)
|
|
continue
|
|
}
|
|
if matches {
|
|
logger.Debug("Found match for file %q and command %q", file, command.Regex)
|
|
association := fileCommands[file]
|
|
|
|
if command.Isolate {
|
|
association.IsolateCommands = append(association.IsolateCommands, command)
|
|
} else {
|
|
association.Commands = append(association.Commands, command)
|
|
}
|
|
fileCommands[file] = association
|
|
associationCount++
|
|
}
|
|
}
|
|
}
|
|
logger.Debug("Found %d commands for file %q", len(fileCommands[file].Commands), file)
|
|
if len(fileCommands[file].Commands) == 0 {
|
|
logger.Info("No commands found for file %q", file)
|
|
}
|
|
if len(fileCommands[file].IsolateCommands) > 0 {
|
|
logger.Info("Found %d isolate commands for file %q", len(fileCommands[file].IsolateCommands), file)
|
|
}
|
|
}
|
|
logger.Info("Found %d associations between %d files and %d commands", associationCount, len(files), len(commands))
|
|
return fileCommands, nil
|
|
}
|
|
|
|
func AggregateGlobs(commands []ModifyCommand) map[string]struct{} {
|
|
logger.Info("Aggregating globs for %d commands", len(commands))
|
|
globs := make(map[string]struct{})
|
|
for _, command := range commands {
|
|
for _, glob := range command.Files {
|
|
globs[glob] = struct{}{}
|
|
}
|
|
}
|
|
logger.Info("Found %d unique globs", len(globs))
|
|
return globs
|
|
}
|
|
|
|
func ExpandGLobs(patterns map[string]struct{}) ([]string, error) {
|
|
var files []string
|
|
filesMap := make(map[string]bool)
|
|
|
|
cwd, err := os.Getwd()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get current working directory: %w", err)
|
|
}
|
|
|
|
logger.Debug("Expanding patterns from directory: %s", cwd)
|
|
for pattern := range patterns {
|
|
logger.Trace("Processing pattern: %s", pattern)
|
|
matches, _ := doublestar.Glob(os.DirFS(cwd), pattern)
|
|
logger.Debug("Found %d matches for pattern %s", len(matches), pattern)
|
|
for _, m := range matches {
|
|
info, err := os.Stat(m)
|
|
if err != nil {
|
|
logger.Warning("Error getting file info for %s: %v", m, err)
|
|
continue
|
|
}
|
|
if !info.IsDir() && !filesMap[m] {
|
|
logger.Trace("Adding file to process list: %s", m)
|
|
filesMap[m], files = true, append(files, m)
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(files) > 0 {
|
|
logger.Debug("Found %d files to process: %v", len(files), files)
|
|
}
|
|
return files, nil
|
|
}
|
|
|
|
func LoadCommands(args []string) ([]ModifyCommand, error) {
|
|
commands := []ModifyCommand{}
|
|
|
|
logger.Info("Loading commands from cook files: %s", *Cookfile)
|
|
newcommands, err := LoadCommandsFromCookFiles(*Cookfile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to load commands from cook files: %w", err)
|
|
}
|
|
logger.Info("Successfully loaded %d commands from cook files", len(newcommands))
|
|
commands = append(commands, newcommands...)
|
|
logger.Info("Now total commands: %d", len(commands))
|
|
|
|
logger.Info("Loading commands from arguments: %v", args)
|
|
newcommands, err = LoadCommandFromArgs(args)
|
|
if err != nil {
|
|
if len(commands) == 0 {
|
|
return nil, fmt.Errorf("failed to load commands from args: %w", err)
|
|
}
|
|
logger.Warning("Failed to load commands from args: %v", err)
|
|
}
|
|
logger.Info("Successfully loaded %d commands from args", len(newcommands))
|
|
commands = append(commands, newcommands...)
|
|
logger.Info("Now total commands: %d", len(commands))
|
|
|
|
return commands, nil
|
|
}
|
|
|
|
func LoadCommandFromArgs(args []string) ([]ModifyCommand, error) {
|
|
// Cannot reset without git, right?
|
|
if *ResetFlag {
|
|
*GitFlag = true
|
|
}
|
|
|
|
if len(args) < 3 {
|
|
return nil, fmt.Errorf("at least %d arguments are required", 3)
|
|
}
|
|
|
|
command := ModifyCommand{
|
|
Regex: args[0],
|
|
Lua: args[1],
|
|
Files: args[2:],
|
|
Git: *GitFlag,
|
|
Reset: *ResetFlag,
|
|
LogLevel: *LogLevel,
|
|
}
|
|
|
|
if err := command.Validate(); err != nil {
|
|
return nil, fmt.Errorf("invalid command: %w", err)
|
|
}
|
|
|
|
return []ModifyCommand{command}, nil
|
|
}
|
|
|
|
func LoadCommandsFromCookFiles(s string) ([]ModifyCommand, error) {
|
|
cwd, err := os.Getwd()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get current working directory: %w", err)
|
|
}
|
|
|
|
commands := []ModifyCommand{}
|
|
cookFiles, err := doublestar.Glob(os.DirFS(cwd), *Cookfile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to glob cook files: %w", err)
|
|
}
|
|
|
|
for _, cookFile := range cookFiles {
|
|
cookFileData, err := os.ReadFile(cookFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read cook file: %w", err)
|
|
}
|
|
newcommands, err := LoadCommandsFromCookFile(cookFileData)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to load commands from cook file: %w", err)
|
|
}
|
|
commands = append(commands, newcommands...)
|
|
}
|
|
|
|
return commands, nil
|
|
}
|
|
|
|
func LoadCommandsFromCookFile(cookFileData []byte) ([]ModifyCommand, error) {
|
|
commands := []ModifyCommand{}
|
|
err := yaml.Unmarshal(cookFileData, &commands)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal cook file: %w", err)
|
|
}
|
|
return commands, nil
|
|
}
|
|
|
|
// CountGlobsBeforeDedup counts the total number of glob patterns across all commands before deduplication
|
|
func CountGlobsBeforeDedup(commands []ModifyCommand) int {
|
|
count := 0
|
|
for _, cmd := range commands {
|
|
count += len(cmd.Files)
|
|
}
|
|
return count
|
|
}
|
|
|
|
func FilterCommands(commands []ModifyCommand, filter string) []ModifyCommand {
|
|
filteredCommands := []ModifyCommand{}
|
|
filters := strings.Split(filter, ",")
|
|
for _, cmd := range commands {
|
|
for _, filter := range filters {
|
|
if strings.Contains(cmd.Name, filter) {
|
|
filteredCommands = append(filteredCommands, cmd)
|
|
}
|
|
}
|
|
}
|
|
return filteredCommands
|
|
}
|