179 lines
5 KiB
Go
Executable file
179 lines
5 KiB
Go
Executable file
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
func main() {
|
|
// Define the --path and --patches flags
|
|
rootPath := flag.String("path", ".", "Root path for patch application")
|
|
patchSource := flag.String("patches", "./patches", "File or directory containing patch(es)")
|
|
flag.Parse()
|
|
|
|
// Convert root path to an absolute path
|
|
absoluteRootPath, err := filepath.Abs(*rootPath)
|
|
if err != nil {
|
|
log.Fatalf("Failed to resolve absolute path for root: %v", err)
|
|
}
|
|
|
|
// Resolve patches path (handle both absolute and relative paths)
|
|
absolutePatchesPath := *patchSource
|
|
if !filepath.IsAbs(absolutePatchesPath) {
|
|
absolutePatchesPath = filepath.Clean(filepath.Join(absoluteRootPath, *patchSource))
|
|
}
|
|
|
|
fmt.Printf("Starting custom patcher...\nRoot path: %s\nPatches source: %s\n", absoluteRootPath, absolutePatchesPath)
|
|
|
|
var successfulPatches, failedPatches []string
|
|
|
|
// Determine if --patches is a directory or a file
|
|
info, err := os.Stat(absolutePatchesPath)
|
|
if err != nil {
|
|
log.Fatalf("Failed to access patch source: %v", err)
|
|
}
|
|
|
|
if info.IsDir() {
|
|
// Walk through directory to apply all patches
|
|
err = filepath.Walk(absolutePatchesPath, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !info.IsDir() && strings.HasSuffix(info.Name(), ".patch") {
|
|
applyPatchWrapper(path, absoluteRootPath, &successfulPatches, &failedPatches)
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
log.Fatalf("Error reading patches from directory: %v", err)
|
|
}
|
|
} else {
|
|
// Single file provided
|
|
if strings.HasSuffix(info.Name(), ".patch") {
|
|
applyPatchWrapper(absolutePatchesPath, absoluteRootPath, &successfulPatches, &failedPatches)
|
|
} else {
|
|
log.Fatalf("Provided patch file is not a .patch file")
|
|
}
|
|
}
|
|
|
|
// Print the summary
|
|
fmt.Println("\nSummary:")
|
|
fmt.Printf("Successful patches (%d):\n", len(successfulPatches))
|
|
for _, patch := range successfulPatches {
|
|
fmt.Printf(" - %s\n", patch)
|
|
}
|
|
fmt.Printf("Failed patches (%d):\n", len(failedPatches))
|
|
for _, patch := range failedPatches {
|
|
fmt.Printf(" - %s\n", patch)
|
|
}
|
|
|
|
// Exit with appropriate status
|
|
if len(failedPatches) > 0 {
|
|
fmt.Println("\nPatch process completed with failures.")
|
|
os.Exit(1)
|
|
} else {
|
|
fmt.Println("\nAll patches applied successfully.")
|
|
os.Exit(0)
|
|
}
|
|
}
|
|
|
|
// applyPatchWrapper wraps the patch application and updates results
|
|
func applyPatchWrapper(patchPath, rootPath string, successfulPatches, failedPatches *[]string) {
|
|
fmt.Printf("Applying patch: %s\n", patchPath)
|
|
err := applyPatch(patchPath, rootPath)
|
|
if err != nil {
|
|
errorMsg := fmt.Sprintf("Failed to apply patch '%s': %v", patchPath, err)
|
|
fmt.Println(errorMsg)
|
|
*failedPatches = append(*failedPatches, errorMsg) // Log detailed error message
|
|
} else {
|
|
fmt.Printf("Successfully applied patch: %s\n", patchPath)
|
|
*successfulPatches = append(*successfulPatches, patchPath)
|
|
}
|
|
}
|
|
|
|
// applyPatch processes a single patch file
|
|
func applyPatch(patchPath, rootPath string) error {
|
|
file, err := os.Open(patchPath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open patch file: %v", err)
|
|
}
|
|
defer file.Close()
|
|
|
|
scanner := bufio.NewScanner(file)
|
|
var inputFilePath, outputFilePath string
|
|
var modifications []string
|
|
var patchType string
|
|
|
|
// Parse the patch file
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
|
|
// Check for header type
|
|
if strings.HasPrefix(line, "t:") && patchType == "" {
|
|
patchType = strings.TrimSpace(strings.TrimPrefix(line, "t:"))
|
|
fmt.Printf("Detected patch type: %s\n", patchType)
|
|
continue
|
|
}
|
|
|
|
// Detect the input file
|
|
if strings.HasPrefix(line, "i:") {
|
|
relativePath := strings.TrimSpace(strings.TrimPrefix(line, "i:"))
|
|
inputFilePath = filepath.Join(rootPath, relativePath)
|
|
continue
|
|
}
|
|
|
|
// Detect the output file
|
|
if strings.HasPrefix(line, "o:") {
|
|
relativePath := strings.TrimSpace(strings.TrimPrefix(line, "o:"))
|
|
outputFilePath = filepath.Join(rootPath, relativePath)
|
|
continue
|
|
}
|
|
|
|
// Ignore comment lines
|
|
if strings.HasPrefix(strings.TrimSpace(line), "//") {
|
|
continue
|
|
}
|
|
|
|
// Collect modifications
|
|
modifications = append(modifications, line)
|
|
}
|
|
|
|
if err := scanner.Err(); err != nil {
|
|
return fmt.Errorf("failed to read patch file: %v", err)
|
|
}
|
|
|
|
if inputFilePath == "" && patchType != "new" {
|
|
return fmt.Errorf("patch file must specify input (i:) file")
|
|
}
|
|
|
|
if outputFilePath == "" {
|
|
return fmt.Errorf("patch file must specify output (o:) file")
|
|
}
|
|
|
|
if patchType != "new" && patchType != "copy" {
|
|
err = os.Rename(inputFilePath, outputFilePath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to replace output file: %v", err)
|
|
}
|
|
}
|
|
|
|
// Process based on patch type
|
|
switch patchType {
|
|
case "pref":
|
|
return applyPrefModifications(outputFilePath, modifications)
|
|
case "standard":
|
|
return applyStandardModifications(outputFilePath, modifications)
|
|
case "new":
|
|
return applyNewModifications(outputFilePath, modifications)
|
|
case "copy":
|
|
return applyCopyPatch(inputFilePath, outputFilePath)
|
|
default:
|
|
fmt.Printf("Type not specified defaulting to standard")
|
|
return applyStandardModifications(outputFilePath, modifications)
|
|
}
|
|
}
|