| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163 |
- // Command xpjdiff compares two MPC Sample projects: the decompressed .xpj
- // files and the sample files in their data folders. It prints the paths of
- // the values that differ, or "identical". Works the same in cmd,
- // PowerShell and sh:
- //
- // go run ./tools/xpjdiff A.xpj B.xpj
- //
- // The project name is not part of the comparison (it lives only in the file
- // and folder names), so A and B may be named differently.
- package main
- import (
- "bytes"
- "fmt"
- "os"
- "path/filepath"
- "sort"
- "strings"
- "git.monoexp.com/bajdev/mpckit/internal/project"
- "git.monoexp.com/bajdev/mpckit/internal/xpj"
- )
- const maxShown = 40
- func main() {
- if len(os.Args) != 3 {
- fmt.Fprintln(os.Stderr, "usage: xpjdiff A.xpj B.xpj")
- os.Exit(2)
- }
- n, err := compare(os.Args[1], os.Args[2])
- if err != nil {
- fmt.Fprintln(os.Stderr, "xpjdiff:", err)
- os.Exit(2)
- }
- if n > 0 {
- os.Exit(1)
- }
- fmt.Println("identical")
- }
- // compare prints the differences and returns how many there are.
- func compare(a, b string) (int, error) {
- fa, err := xpj.ReadFile(a)
- if err != nil {
- return 0, fmt.Errorf("%s: %w", a, err)
- }
- fb, err := xpj.ReadFile(b)
- if err != nil {
- return 0, fmt.Errorf("%s: %w", b, err)
- }
- ea, err := fa.Encode()
- if err != nil {
- return 0, err
- }
- eb, err := fb.Encode()
- if err != nil {
- return 0, err
- }
- var diffs []string
- if !bytes.Equal(ea, eb) {
- if strings.Join(fa.Header, "\n") != strings.Join(fb.Header, "\n") {
- diffs = append(diffs, fmt.Sprintf("header: %q vs %q", fa.Header, fb.Header))
- }
- la, lb := map[string]string{}, map[string]string{}
- leaves(fa.Root, "", la)
- leaves(fb.Root, "", lb)
- for k, v := range la {
- if w, ok := lb[k]; !ok {
- diffs = append(diffs, fmt.Sprintf("%s: only in A (%s)", k, v))
- } else if v != w {
- diffs = append(diffs, fmt.Sprintf("%s: %s vs %s", k, v, w))
- }
- }
- for k, v := range lb {
- if _, ok := la[k]; !ok {
- diffs = append(diffs, fmt.Sprintf("%s: only in B (%s)", k, v))
- }
- }
- if len(diffs) == 0 {
- diffs = append(diffs, "same values, different key order or layout")
- }
- }
- diffs = append(diffs, compareData(a, b)...)
- sort.Strings(diffs)
- for i, d := range diffs {
- if i == maxShown {
- fmt.Printf("... and %d more\n", len(diffs)-maxShown)
- break
- }
- fmt.Println(d)
- }
- return len(diffs), nil
- }
- // leaves flattens a tree into path -> literal value.
- func leaves(n *xpj.Node, path string, out map[string]string) {
- switch n.Kind {
- case xpj.Object:
- for _, m := range n.Members {
- leaves(m.Value, path+"."+m.Key, out)
- }
- if len(n.Members) == 0 {
- out[path] = "{}"
- }
- case xpj.Array:
- for i, it := range n.Items {
- leaves(it, fmt.Sprintf("%s[%d]", path, i), out)
- }
- if len(n.Items) == 0 {
- out[path] = "[]"
- }
- default:
- b, _ := n.MarshalJSON()
- out[path] = string(b)
- }
- }
- func dataDir(xpjPath string) string {
- return strings.TrimSuffix(xpjPath, filepath.Ext(xpjPath)) + project.DataSuffix
- }
- // compareData compares the files in the two data folders.
- func compareData(a, b string) []string {
- da, db := dataDir(a), dataDir(b)
- ra, errA := os.ReadDir(da)
- rb, errB := os.ReadDir(db)
- if errA != nil || errB != nil {
- var out []string
- if errA != nil {
- out = append(out, "data folder A: "+errA.Error())
- }
- if errB != nil {
- out = append(out, "data folder B: "+errB.Error())
- }
- return out
- }
- names := map[string]int{}
- for _, e := range ra {
- names[e.Name()] |= 1
- }
- for _, e := range rb {
- names[e.Name()] |= 2
- }
- var out []string
- for name, in := range names {
- switch in {
- case 1:
- out = append(out, "data file only in A: "+name)
- case 2:
- out = append(out, "data file only in B: "+name)
- default:
- x, err1 := os.ReadFile(filepath.Join(da, name))
- y, err2 := os.ReadFile(filepath.Join(db, name))
- if err1 != nil || err2 != nil || !bytes.Equal(x, y) {
- out = append(out, "data file differs: "+name)
- }
- }
- }
- return out
- }
|