formatter_basic.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. package dashdiffs
  2. import (
  3. "bytes"
  4. "html/template"
  5. diff "github.com/yudai/gojsondiff"
  6. )
  7. // A BasicDiff holds the stateful values that are used when generating a basic
  8. // diff from JSON tokens.
  9. type BasicDiff struct {
  10. narrow string
  11. keysIdent int
  12. writing bool
  13. LastIndent int
  14. Block *BasicBlock
  15. Change *BasicChange
  16. Summary *BasicSummary
  17. }
  18. // A BasicBlock represents a top-level element in a basic diff.
  19. type BasicBlock struct {
  20. Title string
  21. Old interface{}
  22. New interface{}
  23. Change ChangeType
  24. Changes []*BasicChange
  25. Summaries []*BasicSummary
  26. LineStart int
  27. LineEnd int
  28. }
  29. // A BasicChange represents the change from an old to new value. There are many
  30. // BasicChanges in a BasicBlock.
  31. type BasicChange struct {
  32. Key string
  33. Old interface{}
  34. New interface{}
  35. Change ChangeType
  36. LineStart int
  37. LineEnd int
  38. }
  39. // A BasicSummary represents the changes within a basic block that're too deep
  40. // or verbose to be represented in the top-level BasicBlock element, or in the
  41. // BasicChange. Instead of showing the values in this case, we simply print
  42. // the key and count how many times the given change was applied to that
  43. // element.
  44. type BasicSummary struct {
  45. Key string
  46. Change ChangeType
  47. Count int
  48. LineStart int
  49. LineEnd int
  50. }
  51. type BasicFormatter struct {
  52. jsonDiff *JSONFormatter
  53. tpl *template.Template
  54. }
  55. func NewBasicFormatter(left interface{}) *BasicFormatter {
  56. tpl := template.Must(template.New("block").Funcs(tplFuncMap).Parse(tplBlock))
  57. tpl = template.Must(tpl.New("change").Funcs(tplFuncMap).Parse(tplChange))
  58. tpl = template.Must(tpl.New("summary").Funcs(tplFuncMap).Parse(tplSummary))
  59. return &BasicFormatter{
  60. jsonDiff: NewJSONFormatter(left),
  61. tpl: tpl,
  62. }
  63. }
  64. func (b *BasicFormatter) Format(d diff.Diff) ([]byte, error) {
  65. // calling jsonDiff.Format(d) populates the JSON diff's "Lines" value,
  66. // which we use to compute the basic dif
  67. _, err := b.jsonDiff.Format(d)
  68. if err != nil {
  69. return nil, err
  70. }
  71. bd := &BasicDiff{}
  72. blocks := bd.Basic(b.jsonDiff.Lines)
  73. buf := &bytes.Buffer{}
  74. err = b.tpl.ExecuteTemplate(buf, "block", blocks)
  75. if err != nil {
  76. return nil, err
  77. }
  78. return buf.Bytes(), nil
  79. }
  80. // Basic is V2 of the basic diff
  81. func (b *BasicDiff) Basic(lines []*JSONLine) []*BasicBlock {
  82. // init an array you can append to for the basic "blocks"
  83. blocks := make([]*BasicBlock, 0)
  84. // iterate through each line
  85. for _, line := range lines {
  86. // TODO: this condition needs an explaination? what does it mean?
  87. if b.LastIndent == 2 && line.Indent == 1 && line.Change == ChangeNil {
  88. if b.Block != nil {
  89. blocks = append(blocks, b.Block)
  90. }
  91. }
  92. b.LastIndent = line.Indent
  93. // TODO: why special handling for indent 2?
  94. if line.Indent == 1 {
  95. switch line.Change {
  96. case ChangeNil:
  97. if line.Change == ChangeNil {
  98. if line.Key != "" {
  99. b.Block = &BasicBlock{
  100. Title: line.Key,
  101. Change: line.Change,
  102. }
  103. }
  104. }
  105. case ChangeAdded, ChangeDeleted:
  106. blocks = append(blocks, &BasicBlock{
  107. Title: line.Key,
  108. Change: line.Change,
  109. New: line.Val,
  110. LineStart: line.LineNum,
  111. })
  112. case ChangeOld:
  113. b.Block = &BasicBlock{
  114. Title: line.Key,
  115. Old: line.Val,
  116. Change: line.Change,
  117. LineStart: line.LineNum,
  118. }
  119. case ChangeNew:
  120. b.Block.New = line.Val
  121. b.Block.LineEnd = line.LineNum
  122. // then write out the change
  123. blocks = append(blocks, b.Block)
  124. default:
  125. // ok
  126. }
  127. }
  128. // TODO: why special handling for indent > 2 ?
  129. // Other Lines
  130. if line.Indent > 1 {
  131. // Ensure single line change
  132. if line.Key != "" && line.Val != nil && !b.writing {
  133. switch line.Change {
  134. case ChangeAdded, ChangeDeleted:
  135. b.Block.Changes = append(b.Block.Changes, &BasicChange{
  136. Key: line.Key,
  137. Change: line.Change,
  138. New: line.Val,
  139. LineStart: line.LineNum,
  140. })
  141. case ChangeOld:
  142. b.Change = &BasicChange{
  143. Key: line.Key,
  144. Change: line.Change,
  145. Old: line.Val,
  146. LineStart: line.LineNum,
  147. }
  148. case ChangeNew:
  149. b.Change.New = line.Val
  150. b.Change.LineEnd = line.LineNum
  151. b.Block.Changes = append(b.Block.Changes, b.Change)
  152. default:
  153. //ok
  154. }
  155. } else {
  156. if line.Change != ChangeUnchanged {
  157. if line.Key != "" {
  158. b.narrow = line.Key
  159. b.keysIdent = line.Indent
  160. }
  161. if line.Change != ChangeNil {
  162. if !b.writing {
  163. b.writing = true
  164. key := b.Block.Title
  165. if b.narrow != "" {
  166. key = b.narrow
  167. if b.keysIdent > line.Indent {
  168. key = b.Block.Title
  169. }
  170. }
  171. b.Summary = &BasicSummary{
  172. Key: key,
  173. Change: line.Change,
  174. LineStart: line.LineNum,
  175. }
  176. }
  177. }
  178. } else {
  179. if b.writing {
  180. b.writing = false
  181. b.Summary.LineEnd = line.LineNum
  182. b.Block.Summaries = append(b.Block.Summaries, b.Summary)
  183. }
  184. }
  185. }
  186. }
  187. }
  188. return blocks
  189. }
  190. // encStateMap is used in the template helper
  191. var (
  192. encStateMap = map[ChangeType]string{
  193. ChangeAdded: "added",
  194. ChangeDeleted: "deleted",
  195. ChangeOld: "changed",
  196. ChangeNew: "changed",
  197. }
  198. // tplFuncMap is the function map for each template
  199. tplFuncMap = template.FuncMap{
  200. "getChange": func(c ChangeType) string {
  201. state, ok := encStateMap[c]
  202. if !ok {
  203. return "changed"
  204. }
  205. return state
  206. },
  207. }
  208. )
  209. var (
  210. // tplBlock is the whole thing
  211. tplBlock = `{{ define "block" -}}
  212. {{ range . }}
  213. <div class="diff-group">
  214. <div class="diff-block">
  215. <h2 class="diff-block-title">
  216. <i class="diff-circle diff-circle-{{ getChange .Change }} fa fa-circle"></i>
  217. <strong class="diff-title">{{ .Title }}</strong> {{ getChange .Change }}
  218. </h2>
  219. <!-- Overview -->
  220. {{ if .Old }}
  221. <div class="diff-label">{{ .Old }}</div>
  222. <i class="diff-arrow fa fa-long-arrow-right"></i>
  223. {{ end }}
  224. {{ if .New }}
  225. <div class="diff-label">{{ .New }}</div>
  226. {{ end }}
  227. {{ if .LineStart }}
  228. <diff-link-json
  229. line-link="{{ .LineStart }}"
  230. line-display="{{ .LineStart }}{{ if .LineEnd }} - {{ .LineEnd }}{{ end }}"
  231. switch-view="ctrl.getDiff('html')"
  232. />
  233. {{ end }}
  234. </div>
  235. <!-- Basic Changes -->
  236. {{ range .Changes }}
  237. <ul class="diff-change-container">
  238. {{ template "change" . }}
  239. </ul>
  240. {{ end }}
  241. <!-- Basic Summary -->
  242. {{ range .Summaries }}
  243. {{ template "summary" . }}
  244. {{ end }}
  245. </div>
  246. {{ end }}
  247. {{ end }}`
  248. // tplChange is the template for changes
  249. tplChange = `{{ define "change" -}}
  250. <li class="diff-change-group">
  251. <span class="bullet-position-container">
  252. <div class="diff-change-item diff-change-title">{{ getChange .Change }} {{ .Key }}</div>
  253. <div class="diff-change-item">
  254. {{ if .Old }}
  255. <div class="diff-label">{{ .Old }}</div>
  256. <i class="diff-arrow fa fa-long-arrow-right"></i>
  257. {{ end }}
  258. {{ if .New }}
  259. <div class="diff-label">{{ .New }}</div>
  260. {{ end }}
  261. </div>
  262. {{ if .LineStart }}
  263. <diff-link-json
  264. line-link="{{ .LineStart }}"
  265. line-display="{{ .LineStart }}{{ if .LineEnd }} - {{ .LineEnd }}{{ end }}"
  266. switch-view="ctrl.getDiff('json')"
  267. />
  268. {{ end }}
  269. </span>
  270. </li>
  271. {{ end }}`
  272. // tplSummary is for basis summaries
  273. tplSummary = `{{ define "summary" -}}
  274. <div class="diff-group-name">
  275. <i class="diff-circle diff-circle-{{ getChange .Change }} fa fa-circle-o diff-list-circle"></i>
  276. {{ if .Count }}
  277. <strong>{{ .Count }}</strong>
  278. {{ end }}
  279. {{ if .Key }}
  280. <strong class="diff-summary-key">{{ .Key }}</strong>
  281. {{ getChange .Change }}
  282. {{ end }}
  283. {{ if .LineStart }}
  284. <diff-link-json
  285. line-link="{{ .LineStart }}"
  286. line-display="{{ .LineStart }}{{ if .LineEnd }} - {{ .LineEnd }}{{ end }}"
  287. switch-view="ctrl.getDiff('json')"
  288. />
  289. {{ end }}
  290. </div>
  291. {{ end }}`
  292. )