| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334 |
- package formatter
- import (
- "bytes"
- "html/template"
- diff "github.com/yudai/gojsondiff"
- )
- // A BasicDiff holds the stateful values that are used when generating a basic
- // diff from JSON tokens.
- type BasicDiff struct {
- narrow string
- keysIdent int
- writing bool
- LastIndent int
- Block *BasicBlock
- Change *BasicChange
- Summary *BasicSummary
- }
- // A BasicBlock represents a top-level element in a basic diff.
- type BasicBlock struct {
- Title string
- Old interface{}
- New interface{}
- Change ChangeType
- Changes []*BasicChange
- Summaries []*BasicSummary
- LineStart int
- LineEnd int
- }
- // A BasicChange represents the change from an old to new value. There are many
- // BasicChanges in a BasicBlock.
- type BasicChange struct {
- Key string
- Old interface{}
- New interface{}
- Change ChangeType
- LineStart int
- LineEnd int
- }
- // A BasicSummary represents the changes within a basic block that're too deep
- // or verbose to be represented in the top-level BasicBlock element, or in the
- // BasicChange. Instead of showing the values in this case, we simply print
- // the key and count how many times the given change was applied to that
- // element.
- type BasicSummary struct {
- Key string
- Change ChangeType
- Count int
- LineStart int
- LineEnd int
- }
- type BasicFormatter struct {
- jsonDiff *JSONFormatter
- tpl *template.Template
- }
- func NewBasicFormatter(left interface{}) *BasicFormatter {
- tpl := template.Must(template.New("block").Funcs(tplFuncMap).Parse(tplBlock))
- tpl = template.Must(tpl.New("change").Funcs(tplFuncMap).Parse(tplChange))
- tpl = template.Must(tpl.New("summary").Funcs(tplFuncMap).Parse(tplSummary))
- return &BasicFormatter{
- jsonDiff: NewJSONFormatter(left),
- tpl: tpl,
- }
- }
- func (b *BasicFormatter) Format(d diff.Diff) ([]byte, error) {
- // calling jsonDiff.Format(d) populates the JSON diff's "Lines" value,
- // which we use to compute the basic dif
- _, err := b.jsonDiff.Format(d)
- if err != nil {
- return nil, err
- }
- bd := &BasicDiff{}
- blocks := bd.Basic(b.jsonDiff.Lines)
- buf := &bytes.Buffer{}
- err = b.tpl.ExecuteTemplate(buf, "block", blocks)
- if err != nil {
- return nil, err
- }
- return buf.Bytes(), nil
- }
- // Basic is V2 of the basic diff
- func (b *BasicDiff) Basic(lines []*JSONLine) []*BasicBlock {
- // init an array you can append to for the basic "blocks"
- blocks := make([]*BasicBlock, 0)
- // iterate through each line
- for _, line := range lines {
- if b.LastIndent == 3 && line.Indent == 2 && line.Change == ChangeNil {
- if b.Block != nil {
- blocks = append(blocks, b.Block)
- }
- }
- b.LastIndent = line.Indent
- if line.Indent == 2 {
- switch line.Change {
- case ChangeNil:
- if line.Change == ChangeNil {
- if line.Key != "" {
- b.Block = &BasicBlock{
- Title: line.Key,
- Change: line.Change,
- }
- }
- }
- case ChangeAdded, ChangeDeleted:
- blocks = append(blocks, &BasicBlock{
- Title: line.Key,
- Change: line.Change,
- New: line.Val,
- LineStart: line.LineNum,
- })
- case ChangeOld:
- b.Block = &BasicBlock{
- Title: line.Key,
- Old: line.Val,
- Change: line.Change,
- LineStart: line.LineNum,
- }
- case ChangeNew:
- b.Block.New = line.Val
- b.Block.LineEnd = line.LineNum
- // then write out the change
- blocks = append(blocks, b.Block)
- default:
- // ok
- }
- }
- // Other Lines
- if line.Indent > 2 {
- // Ensure single line change
- if line.Key != "" && line.Val != nil && !b.writing {
- switch line.Change {
- case ChangeAdded, ChangeDeleted:
- b.Block.Changes = append(b.Block.Changes, &BasicChange{
- Key: line.Key,
- Change: line.Change,
- New: line.Val,
- LineStart: line.LineNum,
- })
- case ChangeOld:
- b.Change = &BasicChange{
- Key: line.Key,
- Change: line.Change,
- Old: line.Val,
- LineStart: line.LineNum,
- }
- case ChangeNew:
- b.Change.New = line.Val
- b.Change.LineEnd = line.LineNum
- b.Block.Changes = append(b.Block.Changes, b.Change)
- default:
- //ok
- }
- } else {
- if line.Change != ChangeUnchanged {
- if line.Key != "" {
- b.narrow = line.Key
- b.keysIdent = line.Indent
- }
- if line.Change != ChangeNil {
- if !b.writing {
- b.writing = true
- key := b.Block.Title
- if b.narrow != "" {
- key = b.narrow
- if b.keysIdent > line.Indent {
- key = b.Block.Title
- }
- }
- b.Summary = &BasicSummary{
- Key: key,
- Change: line.Change,
- LineStart: line.LineNum,
- }
- }
- }
- } else {
- if b.writing {
- b.writing = false
- b.Summary.LineEnd = line.LineNum
- b.Block.Summaries = append(b.Block.Summaries, b.Summary)
- }
- }
- }
- }
- }
- return blocks
- }
- // encStateMap is used in the template helper
- var (
- encStateMap = map[ChangeType]string{
- ChangeAdded: "added",
- ChangeDeleted: "deleted",
- ChangeOld: "changed",
- ChangeNew: "changed",
- }
- // tplFuncMap is the function map for each template
- tplFuncMap = template.FuncMap{
- "getChange": func(c ChangeType) string {
- state, ok := encStateMap[c]
- if !ok {
- return "changed"
- }
- return state
- },
- }
- )
- var (
- // tplBlock is the whole thing
- tplBlock = `{{ define "block" -}}
- {{ range . }}
- <div class="diff-group">
- <div class="diff-block">
- <h2 class="diff-block-title">
- <i class="diff-circle diff-circle-{{ getChange .Change }} fa fa-circle"></i>
- <strong class="diff-title">{{ .Title }}</strong> {{ getChange .Change }}
- </h2>
- <!-- Overview -->
- {{ if .Old }}
- <div class="change list-change diff-label">{{ .Old }}</div>
- <i class="diff-arrow fa fa-long-arrow-right"></i>
- {{ end }}
- {{ if .New }}
- <div class="change list-change diff-label">{{ .New }}</div>
- {{ end }}
- {{ if .LineStart }}
- <diff-link-json
- line-link="{{ .LineStart }}"
- line-display="{{ .LineStart }}{{ if .LineEnd }} - {{ .LineEnd }}{{ end }}"
- switch-view="ctrl.getDiff('html')"
- />
- {{ end }}
- </div>
- <!-- Basic Changes -->
- {{ range .Changes }}
- <ul class="diff-change-container">
- {{ template "change" . }}
- </ul>
- {{ end }}
- <!-- Basic Summary -->
- {{ range .Summaries }}
- {{ template "summary" . }}
- {{ end }}
- </div>
- {{ end }}
- {{ end }}`
- // tplChange is the template for changes
- tplChange = `{{ define "change" -}}
- <li class="diff-change-group">
- <span class="bullet-position-container">
- <div class="diff-change-item diff-change-title">{{ getChange .Change }} {{ .Key }}</div>
-
- <div class="diff-change-item">
- {{ if .Old }}
- <div class="change list-change diff-label">{{ .Old }}</div>
- <i class="diff-arrow fa fa-long-arrow-right"></i>
- {{ end }}
- {{ if .New }}
- <div class="change list-change diff-label">{{ .New }}</div>
- {{ end }}
- </div>
- {{ if .LineStart }}
- <diff-link-json
- line-link="{{ .LineStart }}"
- line-display="{{ .LineStart }}{{ if .LineEnd }} - {{ .LineEnd }}{{ end }}"
- switch-view="ctrl.getDiff('html')"
- />
- {{ end }}
- </span>
- </li>
- {{ end }}`
- // tplSummary is for basis summaries
- tplSummary = `{{ define "summary" -}}
- <div class="diff-group-name">
- <i class="diff-circle diff-circle-{{ getChange .Change }} fa fa-circle-o diff-list-circle"></i>
-
- {{ if .Count }}
- <strong>{{ .Count }}</strong>
- {{ end }}
- {{ if .Key }}
- <strong class="diff-summary-key">{{ .Key }}</strong>
- {{ getChange .Change }}
- {{ end }}
- {{ if .LineStart }}
- <diff-link-json
- line-link="{{ .LineStart }}"
- line-display="{{ .LineStart }}{{ if .LineEnd }} - {{ .LineEnd }}{{ end }}"
- switch-view="ctrl.getDiff('html')"
- />
- {{ end }}
- </div>
- {{ end }}`
- )
|