Add a --write and --diff flag; require --write to be set before making any changes, for safety

This commit is contained in:
Pim van Pelt
2025-08-28 11:18:59 +02:00
parent 6bc0071bdb
commit d027ec9108
8 changed files with 133 additions and 26 deletions

View File

@@ -5,8 +5,12 @@ import (
"fmt"
"log"
"os"
"strings"
"time"
"github.com/hexops/gotextdiff"
"github.com/hexops/gotextdiff/myers"
"github.com/hexops/gotextdiff/span"
"gopkg.in/yaml.v3"
)
@@ -41,6 +45,9 @@ type Log struct {
func main() {
configFile := flag.String("c", "./tesseract-staging.yaml", "Path to the YAML configuration file")
wantDiff := flag.Bool("diff", false, "Show unified diff of changes")
allowWrite := flag.Bool("write", false, "Allow writing files (required for actual file modifications)")
noColor := flag.Bool("no-color", false, "Disable colored diff output")
flag.Parse()
args := flag.Args()
@@ -51,15 +58,15 @@ func main() {
switch args[0] {
case "gen-html":
generateHTML(*configFile)
generateHTML(*configFile, *wantDiff, *allowWrite, !*noColor)
case "gen-env":
generateEnv(*configFile)
generateEnv(*configFile, *wantDiff, *allowWrite, !*noColor)
case "gen-key":
generateKeys(*configFile)
generateKeys(*configFile, *wantDiff, *allowWrite, !*noColor)
case "gen-nginx":
generateNginx(*configFile)
generateNginx(*configFile, *wantDiff, *allowWrite, !*noColor)
case "gen-roots":
generateRoots(args[1:])
generateRoots(args[1:], *wantDiff, *allowWrite, !*noColor)
default:
fmt.Fprintf(os.Stderr, "Unknown command: %s\n", args[0])
showHelp()
@@ -97,17 +104,94 @@ func loadConfig(yamlFile string) Config {
return config
}
func writeFileWithStatus(filename string, content []byte) error {
// ANSI color codes
const (
colorReset = "\033[0m"
colorRed = "\033[31m"
colorGreen = "\033[32m"
colorYellow = "\033[33m"
colorCyan = "\033[36m"
)
// colorizeUnifiedDiff adds ANSI color codes to unified diff output
func colorizeUnifiedDiff(diff string) string {
lines := strings.Split(diff, "\n")
var colorizedLines []string
for _, line := range lines {
switch {
case strings.HasPrefix(line, "---"):
// File deletion header in cyan
colorizedLines = append(colorizedLines, colorCyan+line+colorReset)
case strings.HasPrefix(line, "+++"):
// File addition header in cyan
colorizedLines = append(colorizedLines, colorCyan+line+colorReset)
case strings.HasPrefix(line, "@@"):
// Hunk header in yellow
colorizedLines = append(colorizedLines, colorYellow+line+colorReset)
case strings.HasPrefix(line, "-"):
// Deleted lines in red
colorizedLines = append(colorizedLines, colorRed+line+colorReset)
case strings.HasPrefix(line, "+"):
// Added lines in green
colorizedLines = append(colorizedLines, colorGreen+line+colorReset)
default:
// Context lines unchanged
colorizedLines = append(colorizedLines, line)
}
}
return strings.Join(colorizedLines, "\n")
}
func writeFileWithStatus(filename string, content []byte, wantDiff bool, allowWrite bool, useColor bool) error {
existingContent, err := os.ReadFile(filename)
if os.IsNotExist(err) {
fmt.Printf("Creating %s\n", filename)
isNew := os.IsNotExist(err)
isUnchanged := false
if isNew {
if allowWrite {
fmt.Printf("Creating %s\n", filename)
} else {
fmt.Printf("Would create %s\n", filename)
}
} else if err != nil {
return fmt.Errorf("failed to read existing file %s: %v", filename, err)
} else if string(existingContent) == string(content) {
fmt.Printf("Unchanged %s\n", filename)
return nil
isUnchanged = true
} else {
fmt.Printf("Updating %s\n", filename)
if allowWrite {
fmt.Printf("Updating %s\n", filename)
} else {
fmt.Printf("Would update %s\n", filename)
}
}
if wantDiff && !isUnchanged {
if isNew {
// For new files, show the entire content as added
edits := myers.ComputeEdits(span.URIFromPath(filename), "", string(content))
diff := fmt.Sprint(gotextdiff.ToUnified("/dev/null", filename, "", edits))
if useColor {
fmt.Print(colorizeUnifiedDiff(diff))
} else {
fmt.Print(diff)
}
} else {
// For existing files, show the diff
edits := myers.ComputeEdits(span.URIFromPath(filename), string(existingContent), string(content))
diff := fmt.Sprint(gotextdiff.ToUnified(filename, filename+".new", string(existingContent), edits))
if useColor {
fmt.Print(colorizeUnifiedDiff(diff))
} else {
fmt.Print(diff)
}
}
}
if isUnchanged || !allowWrite {
return nil
}
err = os.WriteFile(filename, content, 0644)
@@ -119,8 +203,17 @@ func writeFileWithStatus(filename string, content []byte) error {
func showHelp() {
fmt.Printf("Usage: %s [options] <command>\n\n", os.Args[0])
fmt.Printf("Note: Flags must come before the command name.\n\n")
fmt.Printf("Options:\n")
fmt.Printf(" -c <file> Path to YAML configuration file (default: ./tesseract-staging.yaml)\n\n")
fmt.Printf(" -c <file> Path to YAML configuration file (default: ./tesseract-staging.yaml)\n")
fmt.Printf(" --diff Show unified diff of changes without writing files\n")
fmt.Printf(" --write Allow writing files (required for actual file modifications)\n")
fmt.Printf(" --no-color Disable colored diff output\n\n")
fmt.Printf("Examples:\n")
fmt.Printf(" %s --diff gen-html # Show colored diffs without writing\n", os.Args[0])
fmt.Printf(" %s --diff --no-color gen-html # Show plain diffs without writing\n", os.Args[0])
fmt.Printf(" %s --write gen-html # Write files\n", os.Args[0])
fmt.Printf(" %s --diff --write gen-html # Show colored diffs and write files\n\n", os.Args[0])
fmt.Printf("Commands:\n")
fmt.Printf(" gen-html Generate index.html and log.v3.json files in each log's localdirectory.\n")
fmt.Printf(" Creates HTML pages with log information and CT log metadata JSON.\n")