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 { fmt.Printf("Failed to apply patch %s: %v\n", patchPath, err) *failedPatches = append(*failedPatches, patchPath) } 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" { // Replace the original file with the temporary file 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) default: fmt.Printf("Type not specified defaulting to standard") return applyStandardModifications(outputFilePath, modifications) } }