formatter_json.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  1. package dashdiffs
  2. import (
  3. "bytes"
  4. "errors"
  5. "fmt"
  6. "html/template"
  7. "sort"
  8. diff "github.com/yudai/gojsondiff"
  9. )
  10. type ChangeType int
  11. const (
  12. ChangeNil ChangeType = iota
  13. ChangeAdded
  14. ChangeDeleted
  15. ChangeOld
  16. ChangeNew
  17. ChangeUnchanged
  18. )
  19. var (
  20. // changeTypeToSymbol is used for populating the terminating characer in
  21. // the diff
  22. changeTypeToSymbol = map[ChangeType]string{
  23. ChangeNil: "",
  24. ChangeAdded: "+",
  25. ChangeDeleted: "-",
  26. ChangeOld: "-",
  27. ChangeNew: "+",
  28. }
  29. // changeTypeToName is used for populating class names in the diff
  30. changeTypeToName = map[ChangeType]string{
  31. ChangeNil: "same",
  32. ChangeAdded: "added",
  33. ChangeDeleted: "deleted",
  34. ChangeOld: "old",
  35. ChangeNew: "new",
  36. }
  37. )
  38. var (
  39. // tplJSONDiffWrapper is the template that wraps a diff
  40. tplJSONDiffWrapper = `{{ define "JSONDiffWrapper" -}}
  41. {{ range $index, $element := . }}
  42. {{ template "JSONDiffLine" $element }}
  43. {{ end }}
  44. {{ end }}`
  45. // tplJSONDiffLine is the template that prints each line in a diff
  46. tplJSONDiffLine = `{{ define "JSONDiffLine" -}}
  47. <p id="l{{ .LineNum }}" class="diff-line diff-json-{{ cton .Change }}">
  48. <span class="diff-line-number">
  49. {{if .LeftLine }}{{ .LeftLine }}{{ end }}
  50. </span>
  51. <span class="diff-line-number">
  52. {{if .RightLine }}{{ .RightLine }}{{ end }}
  53. </span>
  54. <span class="diff-value diff-indent-{{ .Indent }}" title="{{ .Text }}">
  55. {{ .Text }}
  56. </span>
  57. <span class="diff-line-icon">{{ ctos .Change }}</span>
  58. </p>
  59. {{ end }}`
  60. )
  61. var diffTplFuncs = template.FuncMap{
  62. "ctos": func(c ChangeType) string {
  63. if symbol, ok := changeTypeToSymbol[c]; ok {
  64. return symbol
  65. }
  66. return ""
  67. },
  68. "cton": func(c ChangeType) string {
  69. if name, ok := changeTypeToName[c]; ok {
  70. return name
  71. }
  72. return ""
  73. },
  74. }
  75. // JSONLine contains the data required to render each line of the JSON diff
  76. // and contains the data required to produce the tokens output in the basic
  77. // diff.
  78. type JSONLine struct {
  79. LineNum int `json:"line"`
  80. LeftLine int `json:"leftLine"`
  81. RightLine int `json:"rightLine"`
  82. Indent int `json:"indent"`
  83. Text string `json:"text"`
  84. Change ChangeType `json:"changeType"`
  85. Key string `json:"key"`
  86. Val interface{} `json:"value"`
  87. }
  88. func NewJSONFormatter(left interface{}) *JSONFormatter {
  89. tpl := template.Must(template.New("JSONDiffWrapper").Funcs(diffTplFuncs).Parse(tplJSONDiffWrapper))
  90. tpl = template.Must(tpl.New("JSONDiffLine").Funcs(diffTplFuncs).Parse(tplJSONDiffLine))
  91. return &JSONFormatter{
  92. left: left,
  93. Lines: []*JSONLine{},
  94. tpl: tpl,
  95. path: []string{},
  96. size: []int{},
  97. lineCount: 0,
  98. inArray: []bool{},
  99. }
  100. }
  101. type JSONFormatter struct {
  102. left interface{}
  103. path []string
  104. size []int
  105. inArray []bool
  106. lineCount int
  107. leftLine int
  108. rightLine int
  109. line *AsciiLine
  110. Lines []*JSONLine
  111. tpl *template.Template
  112. }
  113. type AsciiLine struct {
  114. // the type of change
  115. change ChangeType
  116. // the actual changes - no formatting
  117. key string
  118. val interface{}
  119. // level of indentation for the current line
  120. indent int
  121. // buffer containing the fully formatted line
  122. buffer *bytes.Buffer
  123. }
  124. func (f *JSONFormatter) Format(diff diff.Diff) (result string, err error) {
  125. if v, ok := f.left.(map[string]interface{}); ok {
  126. f.formatObject(v, diff)
  127. } else if v, ok := f.left.([]interface{}); ok {
  128. f.formatArray(v, diff)
  129. } else {
  130. return "", fmt.Errorf("expected map[string]interface{} or []interface{}, got %T",
  131. f.left)
  132. }
  133. b := &bytes.Buffer{}
  134. err = f.tpl.ExecuteTemplate(b, "JSONDiffWrapper", f.Lines)
  135. if err != nil {
  136. fmt.Printf("%v\n", err)
  137. return "", err
  138. }
  139. return b.String(), nil
  140. }
  141. func (f *JSONFormatter) formatObject(left map[string]interface{}, df diff.Diff) {
  142. f.addLineWith(ChangeNil, "{")
  143. f.push("ROOT", len(left), false)
  144. f.processObject(left, df.Deltas())
  145. f.pop()
  146. f.addLineWith(ChangeNil, "}")
  147. }
  148. func (f *JSONFormatter) formatArray(left []interface{}, df diff.Diff) {
  149. f.addLineWith(ChangeNil, "[")
  150. f.push("ROOT", len(left), true)
  151. f.processArray(left, df.Deltas())
  152. f.pop()
  153. f.addLineWith(ChangeNil, "]")
  154. }
  155. func (f *JSONFormatter) processArray(array []interface{}, deltas []diff.Delta) error {
  156. patchedIndex := 0
  157. for index, value := range array {
  158. f.processItem(value, deltas, diff.Index(index))
  159. patchedIndex++
  160. }
  161. // additional Added
  162. for _, delta := range deltas {
  163. switch delta.(type) {
  164. case *diff.Added:
  165. d := delta.(*diff.Added)
  166. // skip items already processed
  167. if int(d.Position.(diff.Index)) < len(array) {
  168. continue
  169. }
  170. f.printRecursive(d.Position.String(), d.Value, ChangeAdded)
  171. }
  172. }
  173. return nil
  174. }
  175. func (f *JSONFormatter) processObject(object map[string]interface{}, deltas []diff.Delta) error {
  176. names := sortKeys(object)
  177. for _, name := range names {
  178. value := object[name]
  179. f.processItem(value, deltas, diff.Name(name))
  180. }
  181. // Added
  182. for _, delta := range deltas {
  183. switch delta.(type) {
  184. case *diff.Added:
  185. d := delta.(*diff.Added)
  186. f.printRecursive(d.Position.String(), d.Value, ChangeAdded)
  187. }
  188. }
  189. return nil
  190. }
  191. func (f *JSONFormatter) processItem(value interface{}, deltas []diff.Delta, position diff.Position) error {
  192. matchedDeltas := f.searchDeltas(deltas, position)
  193. positionStr := position.String()
  194. if len(matchedDeltas) > 0 {
  195. for _, matchedDelta := range matchedDeltas {
  196. switch matchedDelta.(type) {
  197. case *diff.Object:
  198. d := matchedDelta.(*diff.Object)
  199. switch value.(type) {
  200. case map[string]interface{}:
  201. //ok
  202. default:
  203. return errors.New("Type mismatch")
  204. }
  205. o := value.(map[string]interface{})
  206. f.newLine(ChangeNil)
  207. f.printKey(positionStr)
  208. f.print("{")
  209. f.closeLine()
  210. f.push(positionStr, len(o), false)
  211. f.processObject(o, d.Deltas)
  212. f.pop()
  213. f.newLine(ChangeNil)
  214. f.print("}")
  215. f.printComma()
  216. f.closeLine()
  217. case *diff.Array:
  218. d := matchedDelta.(*diff.Array)
  219. switch value.(type) {
  220. case []interface{}:
  221. //ok
  222. default:
  223. return errors.New("Type mismatch")
  224. }
  225. a := value.([]interface{})
  226. f.newLine(ChangeNil)
  227. f.printKey(positionStr)
  228. f.print("[")
  229. f.closeLine()
  230. f.push(positionStr, len(a), true)
  231. f.processArray(a, d.Deltas)
  232. f.pop()
  233. f.newLine(ChangeNil)
  234. f.print("]")
  235. f.printComma()
  236. f.closeLine()
  237. case *diff.Added:
  238. d := matchedDelta.(*diff.Added)
  239. f.printRecursive(positionStr, d.Value, ChangeAdded)
  240. f.size[len(f.size)-1]++
  241. case *diff.Modified:
  242. d := matchedDelta.(*diff.Modified)
  243. savedSize := f.size[len(f.size)-1]
  244. f.printRecursive(positionStr, d.OldValue, ChangeOld)
  245. f.size[len(f.size)-1] = savedSize
  246. f.printRecursive(positionStr, d.NewValue, ChangeNew)
  247. case *diff.TextDiff:
  248. savedSize := f.size[len(f.size)-1]
  249. d := matchedDelta.(*diff.TextDiff)
  250. f.printRecursive(positionStr, d.OldValue, ChangeOld)
  251. f.size[len(f.size)-1] = savedSize
  252. f.printRecursive(positionStr, d.NewValue, ChangeNew)
  253. case *diff.Deleted:
  254. d := matchedDelta.(*diff.Deleted)
  255. f.printRecursive(positionStr, d.Value, ChangeDeleted)
  256. default:
  257. return errors.New("Unknown Delta type detected")
  258. }
  259. }
  260. } else {
  261. f.printRecursive(positionStr, value, ChangeUnchanged)
  262. }
  263. return nil
  264. }
  265. func (f *JSONFormatter) searchDeltas(deltas []diff.Delta, position diff.Position) (results []diff.Delta) {
  266. results = make([]diff.Delta, 0)
  267. for _, delta := range deltas {
  268. switch delta.(type) {
  269. case diff.PostDelta:
  270. if delta.(diff.PostDelta).PostPosition() == position {
  271. results = append(results, delta)
  272. }
  273. case diff.PreDelta:
  274. if delta.(diff.PreDelta).PrePosition() == position {
  275. results = append(results, delta)
  276. }
  277. default:
  278. panic("heh")
  279. }
  280. }
  281. return
  282. }
  283. func (f *JSONFormatter) push(name string, size int, array bool) {
  284. f.path = append(f.path, name)
  285. f.size = append(f.size, size)
  286. f.inArray = append(f.inArray, array)
  287. }
  288. func (f *JSONFormatter) pop() {
  289. f.path = f.path[0 : len(f.path)-1]
  290. f.size = f.size[0 : len(f.size)-1]
  291. f.inArray = f.inArray[0 : len(f.inArray)-1]
  292. }
  293. func (f *JSONFormatter) addLineWith(change ChangeType, value string) {
  294. f.line = &AsciiLine{
  295. change: change,
  296. indent: len(f.path),
  297. buffer: bytes.NewBufferString(value),
  298. }
  299. f.closeLine()
  300. }
  301. func (f *JSONFormatter) newLine(change ChangeType) {
  302. f.line = &AsciiLine{
  303. change: change,
  304. indent: len(f.path),
  305. buffer: bytes.NewBuffer([]byte{}),
  306. }
  307. }
  308. func (f *JSONFormatter) closeLine() {
  309. leftLine := 0
  310. rightLine := 0
  311. f.lineCount++
  312. switch f.line.change {
  313. case ChangeAdded, ChangeNew:
  314. f.rightLine++
  315. rightLine = f.rightLine
  316. case ChangeDeleted, ChangeOld:
  317. f.leftLine++
  318. leftLine = f.leftLine
  319. case ChangeNil, ChangeUnchanged:
  320. f.rightLine++
  321. f.leftLine++
  322. rightLine = f.rightLine
  323. leftLine = f.leftLine
  324. }
  325. s := f.line.buffer.String()
  326. f.Lines = append(f.Lines, &JSONLine{
  327. LineNum: f.lineCount,
  328. RightLine: rightLine,
  329. LeftLine: leftLine,
  330. Indent: f.line.indent,
  331. Text: s,
  332. Change: f.line.change,
  333. Key: f.line.key,
  334. Val: f.line.val,
  335. })
  336. }
  337. func (f *JSONFormatter) printKey(name string) {
  338. if !f.inArray[len(f.inArray)-1] {
  339. f.line.key = name
  340. fmt.Fprintf(f.line.buffer, `"%s": `, name)
  341. }
  342. }
  343. func (f *JSONFormatter) printComma() {
  344. f.size[len(f.size)-1]--
  345. if f.size[len(f.size)-1] > 0 {
  346. f.line.buffer.WriteRune(',')
  347. }
  348. }
  349. func (f *JSONFormatter) printValue(value interface{}) {
  350. switch value.(type) {
  351. case string:
  352. f.line.val = value
  353. fmt.Fprintf(f.line.buffer, `"%s"`, value)
  354. case nil:
  355. f.line.val = "null"
  356. f.line.buffer.WriteString("null")
  357. default:
  358. f.line.val = value
  359. fmt.Fprintf(f.line.buffer, `%#v`, value)
  360. }
  361. }
  362. func (f *JSONFormatter) print(a string) {
  363. f.line.buffer.WriteString(a)
  364. }
  365. func (f *JSONFormatter) printRecursive(name string, value interface{}, change ChangeType) {
  366. switch value.(type) {
  367. case map[string]interface{}:
  368. f.newLine(change)
  369. f.printKey(name)
  370. f.print("{")
  371. f.closeLine()
  372. m := value.(map[string]interface{})
  373. size := len(m)
  374. f.push(name, size, false)
  375. keys := sortKeys(m)
  376. for _, key := range keys {
  377. f.printRecursive(key, m[key], change)
  378. }
  379. f.pop()
  380. f.newLine(change)
  381. f.print("}")
  382. f.printComma()
  383. f.closeLine()
  384. case []interface{}:
  385. f.newLine(change)
  386. f.printKey(name)
  387. f.print("[")
  388. f.closeLine()
  389. s := value.([]interface{})
  390. size := len(s)
  391. f.push("", size, true)
  392. for _, item := range s {
  393. f.printRecursive("", item, change)
  394. }
  395. f.pop()
  396. f.newLine(change)
  397. f.print("]")
  398. f.printComma()
  399. f.closeLine()
  400. default:
  401. f.newLine(change)
  402. f.printKey(name)
  403. f.printValue(value)
  404. f.printComma()
  405. f.closeLine()
  406. }
  407. }
  408. func sortKeys(m map[string]interface{}) (keys []string) {
  409. keys = make([]string, 0, len(m))
  410. for key := range m {
  411. keys = append(keys, key)
  412. }
  413. sort.Strings(keys)
  414. return
  415. }