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