1package main 2 3import ( 4 "bufio" 5 "flag" 6 "fmt" 7 "io/ioutil" 8 "log" 9 "os" 10 "strings" 11 12 jd "github.com/josephburnett/jd/lib" 13) 14 15var patch = flag.Bool("p", false, "Patch mode") 16var output = flag.String("o", "", "Output file") 17var set = flag.Bool("set", false, "Arrays as sets") 18var mset = flag.Bool("mset", false, "Arrays as multisets") 19var setkeys = flag.String("setkeys", "", "Keys to identify set objects") 20 21func main() { 22 flag.Parse() 23 metadata, err := parseMetadata() 24 if err != nil { 25 log.Fatalf(err.Error()) 26 } 27 var a, b string 28 switch len(flag.Args()) { 29 case 1: 30 a = readFile(flag.Arg(0)) 31 b = readStdin() 32 case 2: 33 a = readFile(flag.Arg(0)) 34 b = readFile(flag.Arg(1)) 35 default: 36 printUsageAndExit() 37 } 38 if *patch { 39 patchJson(a, b, metadata) 40 } else { 41 diffJson(a, b, metadata) 42 } 43} 44 45func parseMetadata() ([]jd.Metadata, error) { 46 metadata := make([]jd.Metadata, 0) 47 if *set { 48 metadata = append(metadata, jd.SET) 49 } 50 if *mset { 51 metadata = append(metadata, jd.MULTISET) 52 } 53 if *setkeys != "" { 54 keys := make([]string, 0) 55 ks := strings.Split(*setkeys, ",") 56 for _, k := range ks { 57 trimmed := strings.TrimSpace(k) 58 if trimmed == "" { 59 return nil, fmt.Errorf("Invalid set key: %v", k) 60 } 61 keys = append(keys, trimmed) 62 } 63 metadata = append(metadata, jd.Setkeys(keys...)) 64 } 65 return metadata, nil 66} 67 68func printUsageAndExit() { 69 for _, line := range []string{ 70 ``, 71 `Usage: jd [OPTION]... FILE1 [FILE2]`, 72 `Diff and patch JSON files.`, 73 ``, 74 `Prints the diff of FILE1 and FILE2 to STDOUT.`, 75 `When FILE2 is omitted the second input is read from STDIN.`, 76 `When patching (-p) FILE1 is a diff.`, 77 ``, 78 `Metadata:`, 79 ` -p Apply patch FILE1 to FILE2 or STDIN.`, 80 ` -o=FILE3 Write to FILE3 instead of STDOUT.`, 81 ` -set Treat arrays as sets.`, 82 ` -mset Treat arrays as multisets (bags).`, 83 ``, 84 `Examples:`, 85 ` jd a.json b.json`, 86 ` cat b.json | jd a.json`, 87 ` jd -o patch a.json b.json; jd patch a.json`, 88 ` jd -set a.json b.json`, 89 ``, 90 } { 91 fmt.Println(line) 92 } 93 os.Exit(1) 94} 95 96func diffJson(a, b string, metadata []jd.Metadata) { 97 aNode, err := jd.ReadJsonString(a) 98 if err != nil { 99 log.Fatalf(err.Error()) 100 } 101 bNode, err := jd.ReadJsonString(b) 102 if err != nil { 103 log.Fatalf(err.Error()) 104 } 105 diff := aNode.Diff(bNode, metadata...) 106 if *output == "" { 107 fmt.Print(diff.Render()) 108 } else { 109 ioutil.WriteFile(*output, []byte(diff.Render()), 0644) 110 } 111} 112 113func patchJson(p, a string, metadata []jd.Metadata) { 114 diff, err := jd.ReadDiffString(p) 115 if err != nil { 116 log.Fatalf(err.Error()) 117 } 118 aNode, err := jd.ReadJsonString(a) 119 if err != nil { 120 log.Fatalf(err.Error()) 121 } 122 bNode, err := aNode.Patch(diff) 123 if err != nil { 124 log.Fatalf(err.Error()) 125 } 126 if *output == "" { 127 fmt.Print(bNode.Json(metadata...)) 128 } else { 129 ioutil.WriteFile(*output, []byte(bNode.Json(metadata...)), 0644) 130 } 131} 132 133func readFile(filename string) string { 134 bytes, err := ioutil.ReadFile(filename) 135 if err != nil { 136 log.Fatalf(err.Error()) 137 } 138 return string(bytes) 139} 140 141func readStdin() string { 142 r := bufio.NewReader(os.Stdin) 143 bytes, err := ioutil.ReadAll(r) 144 if err != nil { 145 log.Fatalf(err.Error()) 146 } 147 return string(bytes) 148} 149