package cli import ( "errors" "fmt" "log" "strings" "keydiff/interfaces/input" "keydiff/interfaces/output" "keydiff/usecases" "github.com/urfave/cli/v2" ) const RequiredArguments = 2 func RunCLI(args []string) error { app := &cli.App{ Name: "keydiff", Usage: "Find differences between two CSV files using a specified key column", Flags: []cli.Flag{ &cli.StringFlag{ Name: "key", Usage: "Comma-separated list of column indices to use as the key (default: 0)", Value: "0", }, }, Action: func(c *cli.Context) error { if c.NArg() != RequiredArguments { return errors.New("exactly two arguments are required: <original.csv> <modified.csv>") } originalFilePath := c.Args().Get(0) modifiedFilePath := c.Args().Get(1) keyIndicesStr := c.String("key") keyIndices := parseKeyIndices(keyIndicesStr) reader := input.CSVReader{} original, err := reader.Read(originalFilePath) if err != nil { return fmt.Errorf("error reading original file: %w", err) } modified, err := reader.Read(modifiedFilePath) if err != nil { return fmt.Errorf("error reading modified file: %w", err) } calculator := usecases.DiffCalculator{} results := calculator.CalculateDiff(original, modified, keyIndices) formatter := output.TextFormatter{} output := formatter.Format(results) fmt.Println(output) return nil }, } err := app.Run(args) if err != nil { log.Println("Failed to execute CLI:", err) return fmt.Errorf("failed to execute CLI: %w", err) } return nil } func parseKeyIndices(keyIndicesStr string) []int { parts := strings.Split(keyIndicesStr, ",") var indices []int for _, part := range parts { var index int _, err := fmt.Sscanf(part, "%d", &index) if err != nil { log.Fatalf("Invalid key index: %v", err) } indices = append(indices, index) } return indices }
package input import ( "encoding/csv" "fmt" "os" ) type CSVReader struct{} func (r *CSVReader) Read(filePath string) ([][]string, error) { file, err := os.Open(filePath) if err != nil { return nil, fmt.Errorf("failed to open file %s: %w", filePath, err) } defer file.Close() reader := csv.NewReader(file) records, err := reader.ReadAll() if err != nil { return nil, fmt.Errorf("failed to read CSV file %s: %w", filePath, err) } return records, nil }
package output import ( "fmt" "strings" "keydiff/entities" ) type TextFormatter struct{} func (f *TextFormatter) Format(results []entities.DiffResult) string { var builder strings.Builder for _, result := range results { builder.WriteString(fmt.Sprintf("Key: %s\n", result.Key)) if result.Original != nil { builder.WriteString(fmt.Sprintf("-: %s\n", strings.Join(result.Original, ", "))) } if result.Modified != nil { builder.WriteString(fmt.Sprintf("+: %s\n", strings.Join(result.Modified, ", "))) } builder.WriteString("\n") } return builder.String() }
package main import ( "log" "os" "keydiff/framework/cli" ) func main() { err := cli.RunCLI(os.Args) if err != nil { log.Fatalf("Error: %v\n", err) } }
package usecases import ( "strings" "keydiff/entities" ) type DiffCalculator struct{} func (dc *DiffCalculator) CalculateDiff(original, modified [][]string, keyIndices []int) []entities.DiffResult { results := []entities.DiffResult{} originalMap := make(map[string][]string) modifiedMap := make(map[string][]string) generateKey := func(row []string, indices []int) string { var keyParts []string for _, index := range indices { if index < len(row) { keyParts = append(keyParts, row[index]) } } return strings.Join(keyParts, ":") } for _, row := range original { key := generateKey(row, keyIndices) if key != "" { originalMap[key] = row } } for _, row := range modified { key := generateKey(row, keyIndices) if key != "" { modifiedMap[key] = row } } for key, originalRow := range originalMap { if modifiedRow, exists := modifiedMap[key]; exists { if !equalRows(originalRow, modifiedRow) { results = append(results, entities.DiffResult{ Key: key, Original: originalRow, Modified: modifiedRow, }) } } else { results = append(results, entities.DiffResult{ Key: key, Original: originalRow, Modified: nil, }) } } for key, modifiedRow := range modifiedMap { if _, exists := originalMap[key]; !exists { results = append(results, entities.DiffResult{ Key: key, Original: nil, Modified: modifiedRow, }) } } return results } func equalRows(a, b []string) bool { if len(a) != len(b) { return false } for i := range a { if a[i] != b[i] { return false } } return true }