formatter_basic.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. package formatter
  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. if b.LastIndent == 3 && line.Indent == 2 && line.Change == ChangeNil {
  87. if b.Block != nil {
  88. blocks = append(blocks, b.Block)
  89. }
  90. }
  91. b.LastIndent = line.Indent
  92. if line.Indent == 2 {
  93. switch line.Change {
  94. case ChangeNil:
  95. if line.Change == ChangeNil {
  96. if line.Key != "" {
  97. b.Block = &BasicBlock{
  98. Title: line.Key,
  99. Change: line.Change,
  100. }
  101. }
  102. }
  103. case ChangeAdded, ChangeDeleted:
  104. blocks = append(blocks, &BasicBlock{
  105. Title: line.Key,
  106. Change: line.Change,
  107. New: line.Val,
  108. LineStart: line.LineNum,
  109. })
  110. case ChangeOld:
  111. b.Block = &BasicBlock{
  112. Title: line.Key,
  113. Old: line.Val,
  114. Change: line.Change,
  115. LineStart: line.LineNum,
  116. }
  117. case ChangeNew:
  118. b.Block.New = line.Val
  119. b.Block.LineEnd = line.LineNum
  120. // then write out the change
  121. blocks = append(blocks, b.Block)
  122. default:
  123. // ok
  124. }
  125. }
  126. // Other Lines
  127. if line.Indent > 2 {
  128. // Ensure single line change
  129. if line.Key != "" && line.Val != nil && !b.writing {
  130. switch line.Change {
  131. case ChangeAdded, ChangeDeleted:
  132. b.Block.Changes = append(b.Block.Changes, &BasicChange{
  133. Key: line.Key,
  134. Change: line.Change,
  135. New: line.Val,
  136. LineStart: line.LineNum,
  137. })
  138. case ChangeOld:
  139. b.Change = &BasicChange{
  140. Key: line.Key,
  141. Change: line.Change,
  142. Old: line.Val,
  143. LineStart: line.LineNum,
  144. }
  145. case ChangeNew:
  146. b.Change.New = line.Val
  147. b.Change.LineEnd = line.LineNum
  148. b.Block.Changes = append(b.Block.Changes, b.Change)
  149. default:
  150. //ok
  151. }
  152. } else {
  153. if line.Change != ChangeUnchanged {
  154. if line.Key != "" {
  155. b.narrow = line.Key
  156. b.keysIdent = line.Indent
  157. }
  158. if line.Change != ChangeNil {
  159. if !b.writing {
  160. b.writing = true
  161. key := b.Block.Title
  162. if b.narrow != "" {
  163. key = b.narrow
  164. if b.keysIdent > line.Indent {
  165. key = b.Block.Title
  166. }
  167. }
  168. b.Summary = &BasicSummary{
  169. Key: key,
  170. Change: line.Change,
  171. LineStart: line.LineNum,
  172. }
  173. }
  174. }
  175. } else {
  176. if b.writing {
  177. b.writing = false
  178. b.Summary.LineEnd = line.LineNum
  179. b.Block.Summaries = append(b.Block.Summaries, b.Summary)
  180. }
  181. }
  182. }
  183. }
  184. }
  185. return blocks
  186. }
  187. // encStateMap is used in the template helper
  188. var (
  189. encStateMap = map[ChangeType]string{
  190. ChangeAdded: "added",
  191. ChangeDeleted: "deleted",
  192. ChangeOld: "changed",
  193. ChangeNew: "changed",
  194. }
  195. // tplFuncMap is the function map for each template
  196. tplFuncMap = template.FuncMap{
  197. "getChange": func(c ChangeType) string {
  198. state, ok := encStateMap[c]
  199. if !ok {
  200. return "changed"
  201. }
  202. return state
  203. },
  204. }
  205. )
  206. var (
  207. // tplBlock is the whole thing
  208. tplBlock = `{{ define "block" -}}
  209. {{ range . }}
  210. <div class="diff-group">
  211. <div class="diff-block">
  212. <h2 class="diff-block-title">
  213. <i class="diff-circle diff-circle-{{ getChange .Change }} fa fa-circle"></i>
  214. <strong class="diff-title">{{ .Title }}</strong> {{ getChange .Change }}
  215. </h2>
  216. <!-- Overview -->
  217. {{ if .Old }}
  218. <div class="change list-change diff-label">{{ .Old }}</div>
  219. <i class="diff-arrow fa fa-long-arrow-right"></i>
  220. {{ end }}
  221. {{ if .New }}
  222. <div class="change list-change diff-label">{{ .New }}</div>
  223. {{ end }}
  224. {{ if .LineStart }}
  225. <diff-link-json
  226. line-link="{{ .LineStart }}"
  227. line-display="{{ .LineStart }}{{ if .LineEnd }} - {{ .LineEnd }}{{ end }}"
  228. switch-view="ctrl.getDiff('html')"
  229. />
  230. {{ end }}
  231. </div>
  232. <!-- Basic Changes -->
  233. {{ range .Changes }}
  234. <ul class="diff-change-container">
  235. {{ template "change" . }}
  236. </ul>
  237. {{ end }}
  238. <!-- Basic Summary -->
  239. {{ range .Summaries }}
  240. {{ template "summary" . }}
  241. {{ end }}
  242. </div>
  243. {{ end }}
  244. {{ end }}`
  245. // tplChange is the template for changes
  246. tplChange = `{{ define "change" -}}
  247. <li class="diff-change-group">
  248. <span class="bullet-position-container">
  249. <div class="diff-change-item diff-change-title">{{ getChange .Change }} {{ .Key }}</div>
  250. <div class="diff-change-item">
  251. {{ if .Old }}
  252. <div class="change list-change diff-label">{{ .Old }}</div>
  253. <i class="diff-arrow fa fa-long-arrow-right"></i>
  254. {{ end }}
  255. {{ if .New }}
  256. <div class="change list-change diff-label">{{ .New }}</div>
  257. {{ end }}
  258. </div>
  259. {{ if .LineStart }}
  260. <diff-link-json
  261. line-link="{{ .LineStart }}"
  262. line-display="{{ .LineStart }}{{ if .LineEnd }} - {{ .LineEnd }}{{ end }}"
  263. switch-view="ctrl.getDiff('html')"
  264. />
  265. {{ end }}
  266. </span>
  267. </li>
  268. {{ end }}`
  269. // tplSummary is for basis summaries
  270. tplSummary = `{{ define "summary" -}}
  271. <div class="diff-group-name">
  272. <i class="diff-circle diff-circle-{{ getChange .Change }} fa fa-circle-o diff-list-circle"></i>
  273. {{ if .Count }}
  274. <strong>{{ .Count }}</strong>
  275. {{ end }}
  276. {{ if .Key }}
  277. <strong class="diff-summary-key">{{ .Key }}</strong>
  278. {{ getChange .Change }}
  279. {{ end }}
  280. {{ if .LineStart }}
  281. <diff-link-json
  282. line-link="{{ .LineStart }}"
  283. line-display="{{ .LineStart }}{{ if .LineEnd }} - {{ .LineEnd }}{{ end }}"
  284. switch-view="ctrl.getDiff('html')"
  285. />
  286. {{ end }}
  287. </div>
  288. {{ end }}`
  289. )