Refactor modify and replace to their own files
This commit is contained in:
180
main.go
180
main.go
@@ -10,6 +10,8 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"modify/utils"
|
||||||
|
|
||||||
"github.com/bmatcuk/doublestar/v4"
|
"github.com/bmatcuk/doublestar/v4"
|
||||||
"github.com/go-git/go-git/v5"
|
"github.com/go-git/go-git/v5"
|
||||||
"github.com/go-git/go-git/v5/plumbing/object"
|
"github.com/go-git/go-git/v5/plumbing/object"
|
||||||
@@ -25,49 +27,12 @@ type GlobalStats struct {
|
|||||||
FailedFiles int
|
FailedFiles int
|
||||||
}
|
}
|
||||||
|
|
||||||
var stats GlobalStats = GlobalStats{}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
gitFlag = flag.Bool("git", false, "Use git to manage files")
|
|
||||||
resetFlag = flag.Bool("reset", false, "Reset files to their original state")
|
|
||||||
logLevel = flag.String("loglevel", "INFO", "Set log level: ERROR, WARNING, INFO, DEBUG, TRACE")
|
|
||||||
cookfile = flag.String("cook", "**/cook.yml", "Path to cook config files, can be globbed")
|
|
||||||
parallelfiles = flag.Int("P", 100, "Number of files to process in parallel")
|
|
||||||
repo *git.Repository
|
repo *git.Repository
|
||||||
worktree *git.Worktree
|
worktree *git.Worktree
|
||||||
|
stats GlobalStats = GlobalStats{}
|
||||||
)
|
)
|
||||||
|
|
||||||
type ModifyCommand struct {
|
|
||||||
Pattern string `yaml:"pattern"`
|
|
||||||
LuaExpr string `yaml:"lua"`
|
|
||||||
Files []string `yaml:"files"`
|
|
||||||
Git bool `yaml:"git"`
|
|
||||||
Reset bool `yaml:"reset"`
|
|
||||||
LogLevel string `yaml:"loglevel"`
|
|
||||||
}
|
|
||||||
type ReplaceCommand struct {
|
|
||||||
From int
|
|
||||||
To int
|
|
||||||
With string
|
|
||||||
}
|
|
||||||
type CookFile []ModifyCommand
|
|
||||||
|
|
||||||
func (c *ModifyCommand) Validate() error {
|
|
||||||
if c.Pattern == "" {
|
|
||||||
return fmt.Errorf("pattern is required")
|
|
||||||
}
|
|
||||||
if c.LuaExpr == "" {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Usage = func() {
|
flag.Usage = func() {
|
||||||
fmt.Fprintf(os.Stderr, "Usage: %s [options] <pattern> <lua_expression> <...files_or_globs>\n", os.Args[0])
|
fmt.Fprintf(os.Stderr, "Usage: %s [options] <pattern> <lua_expression> <...files_or_globs>\n", os.Args[0])
|
||||||
@@ -92,13 +57,13 @@ func main() {
|
|||||||
flag.Parse()
|
flag.Parse()
|
||||||
args := flag.Args()
|
args := flag.Args()
|
||||||
|
|
||||||
level := logger.ParseLevel(*logLevel)
|
level := logger.ParseLevel(*utils.LogLevel)
|
||||||
logger.Init(level)
|
logger.Init(level)
|
||||||
logger.Info("Initializing with log level: %s", level.String())
|
logger.Info("Initializing with log level: %s", level.String())
|
||||||
|
|
||||||
// The plan is:
|
// The plan is:
|
||||||
// Load all commands
|
// Load all commands
|
||||||
commands, err := LoadCommands(args)
|
commands, err := utils.LoadCommands(args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Failed to load commands: %v", err)
|
logger.Error("Failed to load commands: %v", err)
|
||||||
flag.Usage()
|
flag.Usage()
|
||||||
@@ -106,7 +71,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Then aggregate all the globs and deduplicate them
|
// Then aggregate all the globs and deduplicate them
|
||||||
globs := AggregateGlobs(commands)
|
globs := utils.AggregateGlobs(commands)
|
||||||
|
|
||||||
// Resolve all the files for all the globs
|
// Resolve all the files for all the globs
|
||||||
logger.Info("Found %d unique file patterns", len(globs))
|
logger.Info("Found %d unique file patterns", len(globs))
|
||||||
@@ -121,7 +86,7 @@ func main() {
|
|||||||
// For each file check every glob of every command
|
// For each file check every glob of every command
|
||||||
// Maybe memoize this part
|
// Maybe memoize this part
|
||||||
// That way we know what commands affect what files
|
// That way we know what commands affect what files
|
||||||
associations, err := AssociateFilesWithCommands(files, commands)
|
associations, err := utils.AssociateFilesWithCommands(files, commands)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Failed to associate files with commands: %v", err)
|
logger.Error("Failed to associate files with commands: %v", err)
|
||||||
return
|
return
|
||||||
@@ -139,7 +104,7 @@ func main() {
|
|||||||
fileDataStr := string(fileData)
|
fileDataStr := string(fileData)
|
||||||
|
|
||||||
// Aggregate all the modifications and execute them
|
// Aggregate all the modifications and execute them
|
||||||
modifications := []ReplaceCommand{}
|
modifications := []utils.ReplaceCommand{}
|
||||||
for _, command := range commands {
|
for _, command := range commands {
|
||||||
logger.Info("Processing file %q with command %q", file, command.Pattern)
|
logger.Info("Processing file %q with command %q", file, command.Pattern)
|
||||||
// TODO: Run processor and return modifications
|
// TODO: Run processor and return modifications
|
||||||
@@ -151,7 +116,8 @@ func main() {
|
|||||||
})
|
})
|
||||||
logger.Trace("Applying %d replacement commands in reverse order", len(modifications))
|
logger.Trace("Applying %d replacement commands in reverse order", len(modifications))
|
||||||
|
|
||||||
fileDataStr = ExecuteModifications(modifications, fileDataStr)
|
fileDataStr, count := utils.ExecuteModifications(modifications, fileDataStr)
|
||||||
|
logger.Info("Executed %d modifications for file %q", count, file)
|
||||||
|
|
||||||
err = os.WriteFile(file, []byte(fileDataStr), 0644)
|
err = os.WriteFile(file, []byte(fileDataStr), 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -257,132 +223,6 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExecuteModifications(modifications []ReplaceCommand, fileData string) string {
|
|
||||||
var err error
|
|
||||||
for _, modification := range modifications {
|
|
||||||
fileData, err = modification.Execute(fileData)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("Failed to execute modification: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fileData
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *ReplaceCommand) Execute(fileDataStr string) (string, error) {
|
|
||||||
err := m.Validate(len(fileDataStr))
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("failed to validate modification: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Trace("Replace pos %d-%d with %q", m.From, m.To, m.With)
|
|
||||||
return fileDataStr[:m.From] + m.With + fileDataStr[m.To:], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *ReplaceCommand) Validate(maxsize int) error {
|
|
||||||
if m.To < m.From {
|
|
||||||
return fmt.Errorf("command to is less than from: %v", m)
|
|
||||||
}
|
|
||||||
if m.From > maxsize || m.To > maxsize {
|
|
||||||
return fmt.Errorf("command from or to is greater than replacement length: %v", m)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func AssociateFilesWithCommands(files []string, commands []ModifyCommand) (map[string][]ModifyCommand, error) {
|
|
||||||
associationCount := 0
|
|
||||||
fileCommands := make(map[string][]ModifyCommand)
|
|
||||||
for _, file := range files {
|
|
||||||
for _, command := range commands {
|
|
||||||
for _, glob := range command.Files {
|
|
||||||
// TODO: Maybe memoize this function call
|
|
||||||
matches, err := doublestar.Match(glob, file)
|
|
||||||
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.Pattern)
|
|
||||||
fileCommands[file] = append(fileCommands[file], command)
|
|
||||||
associationCount++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logger.Debug("Found %d commands for file %q", len(fileCommands[file]), file)
|
|
||||||
if len(fileCommands[file]) == 0 {
|
|
||||||
logger.Info("No commands found for file %q", 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 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 {
|
|
||||||
return nil, fmt.Errorf("failed to load commands from args: %w", 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{
|
|
||||||
Pattern: args[0],
|
|
||||||
LuaExpr: 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) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupGit() error {
|
func setupGit() error {
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
13
utils/flags.go
Normal file
13
utils/flags.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
GitFlag = flag.Bool("git", false, "Use git to manage files")
|
||||||
|
ResetFlag = flag.Bool("reset", false, "Reset files to their original state")
|
||||||
|
LogLevel = flag.String("loglevel", "INFO", "Set log level: ERROR, WARNING, INFO, DEBUG, TRACE")
|
||||||
|
Cookfile = flag.String("cook", "**/cook.yml", "Path to cook config files, can be globbed")
|
||||||
|
ParallelFiles = flag.Int("P", 100, "Number of files to process in parallel")
|
||||||
|
)
|
128
utils/modifycommand.go
Normal file
128
utils/modifycommand.go
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"modify/logger"
|
||||||
|
|
||||||
|
"github.com/bmatcuk/doublestar/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ModifyCommand struct {
|
||||||
|
Pattern string `yaml:"pattern"`
|
||||||
|
LuaExpr string `yaml:"lua"`
|
||||||
|
Files []string `yaml:"files"`
|
||||||
|
Git bool `yaml:"git"`
|
||||||
|
Reset bool `yaml:"reset"`
|
||||||
|
LogLevel string `yaml:"loglevel"`
|
||||||
|
}
|
||||||
|
type CookFile []ModifyCommand
|
||||||
|
|
||||||
|
func (c *ModifyCommand) Validate() error {
|
||||||
|
if c.Pattern == "" {
|
||||||
|
return fmt.Errorf("pattern is required")
|
||||||
|
}
|
||||||
|
if c.LuaExpr == "" {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func AssociateFilesWithCommands(files []string, commands []ModifyCommand) (map[string][]ModifyCommand, error) {
|
||||||
|
associationCount := 0
|
||||||
|
fileCommands := make(map[string][]ModifyCommand)
|
||||||
|
for _, file := range files {
|
||||||
|
for _, command := range commands {
|
||||||
|
for _, glob := range command.Files {
|
||||||
|
// TODO: Maybe memoize this function call
|
||||||
|
matches, err := doublestar.Match(glob, file)
|
||||||
|
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.Pattern)
|
||||||
|
fileCommands[file] = append(fileCommands[file], command)
|
||||||
|
associationCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.Debug("Found %d commands for file %q", len(fileCommands[file]), file)
|
||||||
|
if len(fileCommands[file]) == 0 {
|
||||||
|
logger.Info("No commands found for file %q", 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 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 {
|
||||||
|
return nil, fmt.Errorf("failed to load commands from args: %w", 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{
|
||||||
|
Pattern: args[0],
|
||||||
|
LuaExpr: 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) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
47
utils/replacecommand.go
Normal file
47
utils/replacecommand.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"modify/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ReplaceCommand struct {
|
||||||
|
From int
|
||||||
|
To int
|
||||||
|
With string
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExecuteModifications(modifications []ReplaceCommand, fileData string) (string, int) {
|
||||||
|
var err error
|
||||||
|
executed := 0
|
||||||
|
for _, modification := range modifications {
|
||||||
|
fileData, err = modification.Execute(fileData)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Failed to execute modification: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
executed++
|
||||||
|
}
|
||||||
|
logger.Info("Executed %d modifications", executed)
|
||||||
|
return fileData, executed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ReplaceCommand) Execute(fileDataStr string) (string, error) {
|
||||||
|
err := m.Validate(len(fileDataStr))
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to validate modification: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Trace("Replace pos %d-%d with %q", m.From, m.To, m.With)
|
||||||
|
return fileDataStr[:m.From] + m.With + fileDataStr[m.To:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ReplaceCommand) Validate(maxsize int) error {
|
||||||
|
if m.To < m.From {
|
||||||
|
return fmt.Errorf("command to is less than from: %v", m)
|
||||||
|
}
|
||||||
|
if m.From > maxsize || m.To > maxsize {
|
||||||
|
return fmt.Errorf("command from or to is greater than replacement length: %v", m)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
Reference in New Issue
Block a user