├── scripts
├── go-diff.sh
└── go-diff.gs
├── ACKNOWLEDGEMENTS
├── .gitignore
├── godiff.go
├── LICENSE
├── README.md
├── cmd
├── godiff_test.go
└── godiff.go
└── tm
├── tm_test.go
└── tm.go
/scripts/go-diff.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | go-diff $2 $5
--------------------------------------------------------------------------------
/ACKNOWLEDGEMENTS:
--------------------------------------------------------------------------------
1 | Dmitri Shuralyov (@shurcooL) for cleaning up of the code.
2 |
--------------------------------------------------------------------------------
/scripts/go-diff.gs:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env gosl
2 | # gosl: https://github.com/daviddengcn/gosl
3 |
4 | import godiff "github.com/daviddengcn/go-diff/cmd"
5 |
6 | if len(Args) < 7 {
7 | Fatalf("go-diff.gs is supposed to be called from Git")
8 | }
9 |
10 | godiff.Exec(Args[2], Args[5], godiff.Options{})
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled Object files, Static and Dynamic libs (Shared Objects)
2 | *.o
3 | *.a
4 | *.so
5 |
6 | # Folders
7 | _obj
8 | _test
9 |
10 | # Architecture specific extensions/prefixes
11 | *.[568vq]
12 | [568vq].out
13 |
14 | *.cgo1.go
15 | *.cgo2.c
16 | _cgo_defun.c
17 | _cgo_gotypes.go
18 | _cgo_export.*
19 |
20 | _testmain.go
21 |
22 | *.exe
23 |
--------------------------------------------------------------------------------
/godiff.go:
--------------------------------------------------------------------------------
1 | /*
2 | go-diff is a tool checking semantic difference between source files.
3 |
4 | Currently supported language:
5 |
6 | - Go (fully)
7 |
8 | If the language is not supported or parsing is failed for either file,
9 | a line-to-line comparing is imposed.
10 | */
11 | package main
12 |
13 | import (
14 | "flag"
15 | "os"
16 |
17 | "github.com/daviddengcn/go-diff/cmd"
18 | "github.com/golangplus/fmt"
19 | )
20 |
21 | func usage() {
22 | fmtp.Eprintfln("usage: go-diff [options] org-filename new-filename")
23 | flag.PrintDefaults()
24 | os.Exit(2)
25 | }
26 |
27 | func main() {
28 | var options godiff.Options
29 |
30 | flag.BoolVar(&options.NoColor, "no-color", false, "turn off the colors")
31 |
32 | flag.Usage = usage
33 | flag.Parse()
34 |
35 | if flag.NArg() < 2 {
36 | usage()
37 | return
38 | } // if
39 | orgFn := flag.Arg(0)
40 | newFn := flag.Arg(1)
41 |
42 | godiff.Exec(orgFn, newFn, options)
43 | }
44 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013, Yi DENG
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | 1. Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 | 2. Redistributions in binary form must reproduce the above copyright notice,
10 | this list of conditions and the following disclaimer in the documentation
11 | and/or other materials provided with the distribution.
12 |
13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 |
24 | The views and conclusions contained in the software and documentation are those
25 | of the authors and should not be interpreted as representing official policies,
26 | either expressed or implied, of the FreeBSD Project.
27 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | go-diff [](http://go-search.org/view?id=github.com%2Fdaviddengcn%2Fgo-diff)
2 | =======
3 |
4 | A diff tool for [Go languange](http://golang.org/). It shows the semantic differences between two Go source files.
5 |
6 | Ignored Difference
7 | ------------------
8 | 1. Order of import statements
9 | 1. Order of definitions of global type/const/var/func
10 | 1. Whether more than one parameters or global variables are declared in one line. e.g. var a, b int = 1, 2 is equivalent to var a int = 1; var b int = 2. (NOTE parallel assignments are not normalized)
11 | 1. All comments.
12 | 1. Code formats. e.g. some useless new lines.
13 |
14 | Other Features
15 | --------------
16 | 1. Smart matching algorithm on go ast tree.
17 | 1. If a function is deleted or added as a whole, only one-line message is shown (starting by === or ###)
18 | 1. Easily see which function or type, etc. the difference is in.
19 | 1. Import/const/var/func diffrences are shown in order, independent of the lines' order in the source.
20 | 2. Token based line-line difference presentation.
21 |
22 | Installation
23 | ------------
24 | ```bash
25 | $ go get -u github.com/daviddengcn/go-diff
26 | $ go install github.com/daviddengcn/go-diff
27 | $ go-diff
28 | ```
29 | (Make sure $GO_PATH/bin is in system's $PATH)
30 |
31 | Used as git diff
32 |
33 | 1. Link `scripts/go-diff.gs` (you need install [gosl](http://github.com/daviddengcn/gosl)) or `scripts/go-diff.sh` to a folder in `PATH`
34 |
35 | 1. Set Git external diff driver (change `go-diff.gs` to `go-diff.sh` accordingly if necessary)
36 |
37 | ```bash
38 | $ git config [--global] diff.external go-diff.gs
39 | $ git diff
40 | ```
41 |
42 | License
43 | -------
44 | BSD license
45 |
--------------------------------------------------------------------------------
/cmd/godiff_test.go:
--------------------------------------------------------------------------------
1 | package godiff
2 |
3 | import (
4 | "strings"
5 | "testing"
6 |
7 | "github.com/golangplus/bytes"
8 | "github.com/golangplus/testing/assert"
9 | )
10 |
11 | func TestNodeToLines_Literal(t *testing.T) {
12 | info, err := parse("", `
13 | package main
14 |
15 | func main() {
16 | a := Da {
17 | A: 10,
18 | B: 20,
19 | }
20 | }
21 |
22 | `)
23 | if !assert.NoError(t, err) {
24 | return
25 | }
26 |
27 | lines := info.funcs.sourceLines("")
28 | assert.StringEqual(t, "lines", lines, strings.Split(
29 | `func main() {
30 | a := Da{
31 | A: 10,
32 | B: 20,
33 | }
34 | }`, "\n"))
35 | }
36 |
37 | func TestDiffLines_1(t *testing.T) {
38 | var buf bytesp.Slice
39 | gOut = &buf
40 |
41 | src := strings.Split(`This a line with the word abc different only
42 | This a line with the word def different only
43 | This a line with the word ghi different only
44 | This a line with the word jkl different only`, "\n")
45 | dst := strings.Split(`This a line with the word abc different only
46 | This a line with the word ghi different only
47 | This a line with the word jkl different only
48 | This a line with the word def different only`, "\n")
49 | diffLines(src, dst, "%s")
50 |
51 | assert.Equal(t, "diff", string(buf), ` This a line with the word abc different only
52 | --- This a line with the word def different only
53 | This a line with the word ghi different only
54 | This a line with the word jkl different only
55 | +++ This a line with the word def different only
56 | `)
57 | }
58 |
59 | func TestDiffLines_2(t *testing.T) {
60 | var buf bytesp.Slice
61 | gOut = &buf
62 |
63 | src := strings.Split(`abc
64 | {
65 | hello
66 | }
67 | defg`, "\n")
68 | dst := strings.Split(`abc
69 | {
70 | gogogo
71 | }
72 | {
73 | hello
74 | }
75 | defg`, "\n")
76 | diffLines(src, dst, "%s")
77 |
78 | t.Logf("Diff: %s", string(buf))
79 |
80 | assert.StringEqual(t, "diff", strings.Split(string(buf), "\n"),
81 | strings.Split(` abc
82 | +++ {
83 | +++ gogogo
84 | +++ }
85 | {
86 | ... (2 lines)
87 | defg
88 | `, "\n"))
89 | }
90 |
--------------------------------------------------------------------------------
/tm/tm_test.go:
--------------------------------------------------------------------------------
1 | package tm
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "github.com/golangplus/testing/assert"
8 | )
9 |
10 | func TestMatchTokens1(t *testing.T) {
11 | delT, insT := LineToTokens("{abc{def}ghi}"), LineToTokens("{def}")
12 | assert.StringEqual(t, "delT", delT, "[{ abc { def } ghi }]")
13 | assert.StringEqual(t, "insT", insT, "[{ def }]")
14 |
15 | matA, matB := MatchTokens(delT, insT)
16 | assert.StringEqual(t, "matA", matA, "[-1 -1 0 1 2 -1 -1]")
17 | assert.StringEqual(t, "matB", matB, "[2 3 4]")
18 | }
19 |
20 | func TestMatchTokens2(t *testing.T) {
21 | delT, insT := LineToTokens("(a.b())"), LineToTokens("a.b()")
22 | assert.StringEqual(t, "delT", delT, "[( a . b ( ) )]")
23 | assert.StringEqual(t, "insT", insT, "[a . b ( )]")
24 | matA, matB := MatchTokens(delT, insT)
25 | assert.StringEqual(t, "matA", matA, "[-1 0 1 2 3 4 -1]")
26 | assert.StringEqual(t, "matB", matB, "[1 2 3 4 5]")
27 |
28 | delT, insT = LineToTokens("(a.b(), c)"), LineToTokens("(a.b, c)")
29 | assert.StringEqual(t, "delT", delT, "[( a . b ( ) , c )]")
30 | assert.StringEqual(t, "insT", insT, "[( a . b , c )]")
31 | matA, matB = MatchTokens(delT, insT)
32 | assert.StringEqual(t, "matA", matA, "[0 1 2 3 -1 -1 4 -1 6 7]")
33 | assert.StringEqual(t, "matB", matB, "[0 1 2 3 6 -1 8 9]")
34 |
35 | delT, insT = LineToTokens("[a.b[]]"), LineToTokens("[a.b]")
36 | assert.StringEqual(t, "delT", delT, "[[ a . b [ ] ]]")
37 | assert.StringEqual(t, "insT", insT, "[[ a . b ]]")
38 | matA, matB = MatchTokens(delT, insT)
39 | assert.StringEqual(t, "matA", matA, "[0 1 2 3 -1 -1 4]")
40 | assert.StringEqual(t, "matB", matB, "[0 1 2 3 6]")
41 |
42 | delT, insT = LineToTokens("(), (abc)"), LineToTokens("(abc)")
43 | assert.StringEqual(t, "delT", delT, "[( ) , ( abc )]")
44 | assert.StringEqual(t, "insT", insT, "[( abc )]")
45 | matA, matB = MatchTokens(delT, insT)
46 | assert.StringEqual(t, "matA", matA, "[-1 -1 -1 -1 0 1 2]")
47 | assert.StringEqual(t, "matB", matB, "[4 5 6]")
48 |
49 | delT, insT = LineToTokens(`"", "abc"`), LineToTokens(`"abc"`)
50 | assert.StringEqual(t, "delT", delT, `[" " , " abc "]`)
51 | assert.StringEqual(t, "insT", insT, `[" abc "]`)
52 | matA, matB = MatchTokens(delT, insT)
53 | assert.StringEqual(t, "matA", matA, "[-1 -1 -1 -1 0 1 2]")
54 | assert.StringEqual(t, "matB", matB, "[4 5 6]")
55 |
56 | delT, insT = LineToTokens(`cmd:=exec.Command("go", "abc")`), LineToTokens(`cmd:=villa.Path("go").Command("abc")`)
57 | assert.StringEqual(t, "delT", delT, `[cmd : = exec . Command ( " go " , " abc " )]`)
58 | assert.StringEqual(t, "insT", insT, `[cmd : = villa . Path ( " go " ) . Command ( " abc " )]`)
59 | matA, matB = MatchTokens(delT, insT)
60 | assert.StringEqual(t, "matA", matA, "[0 1 2 -1 11 12 13 -1 -1 -1 -1 -1 14 15 16 17]")
61 | assert.StringEqual(t, "matB", matB, "[0 1 2 -1 -1 -1 -1 -1 -1 -1 -1 4 5 6 12 13 14 15]")
62 |
63 | delT, insT = LineToTokens("Rename(m.exeFile(gsp))"), LineToTokens("Rename(exeFile)")
64 | assert.StringEqual(t, "delT", delT, `[Rename ( m . exe File ( gsp ) )]`)
65 | assert.StringEqual(t, "insT", insT, `[Rename ( exe File )]`)
66 | matA, matB = MatchTokens(delT, insT)
67 | assert.StringEqual(t, "matA", matA, "[0 1 -1 -1 2 3 -1 -1 -1 4]")
68 | assert.StringEqual(t, "matB", matB, "[0 1 4 5 9]")
69 |
70 | delT, insT = LineToTokens("Rename(exeFile)"), LineToTokens("Rename(m.exeFile(gsp))")
71 | assert.StringEqual(t, "delT", delT, `[Rename ( exe File )]`)
72 | assert.StringEqual(t, "insT", insT, `[Rename ( m . exe File ( gsp ) )]`)
73 | matA, matB = MatchTokens(delT, insT)
74 | assert.StringEqual(t, "matA", matA, "[0 1 4 5 9]")
75 | assert.StringEqual(t, "matB", matB, "[0 1 -1 -1 2 3 -1 -1 -1 4]")
76 |
77 | delT, insT = LineToTokens("Rename[m.exeFile[gsp]]"), LineToTokens("Rename[exeFile]")
78 | assert.StringEqual(t, "delT", delT, `[Rename [ m . exe File [ gsp ] ]]`)
79 | assert.StringEqual(t, "insT", insT, `[Rename [ exe File ]]`)
80 | matA, matB = MatchTokens(delT, insT)
81 | assert.StringEqual(t, "matA", matA, "[0 1 -1 -1 2 3 -1 -1 -1 4]")
82 | assert.StringEqual(t, "matB", matB, "[0 1 4 5 9]")
83 | }
84 |
85 | func TestMatchTokens3(t *testing.T) {
86 | MatchTokens(LineToTokens("[({gsp}}))]]"), LineToTokens("])}"))
87 | }
88 |
89 | func TestCalcDiffOfSourceLine(t *testing.T) {
90 | diff := CalcDiffOfSourceLine("return &monitor{", "m := &monitor{", 300)
91 | fmt.Println("diff", diff)
92 |
93 | diff = CalcDiffOfSourceLine("if (delO[iA] == 0) == (insO[iB] == 0) {", "if delL[iA] {", 1000)
94 | fmt.Println("diff", diff)
95 | diff = CalcDiffOfSourceLine("if (delO[iA] == 0) == (insO[iB] == 0) {", "c += diffAt(delT, iA - 1, insT, iB - 1)", 1000)
96 | fmt.Println("diff", diff)
97 | }
98 |
--------------------------------------------------------------------------------
/tm/tm.go:
--------------------------------------------------------------------------------
1 | package tm
2 |
3 | import (
4 | "github.com/daviddengcn/go-algs/ed"
5 | "github.com/daviddengcn/go-villa"
6 | "github.com/golangplus/math"
7 | )
8 |
9 | const (
10 | rune_SINGLE = iota
11 | rune_NUM
12 | rune_CAPITAL
13 | rune_LOWER
14 | )
15 |
16 | func newToken(last, cur int) bool {
17 | if last == rune_SINGLE || cur == rune_SINGLE {
18 | return true
19 | } // if
20 |
21 | if last == cur {
22 | return false
23 | } // if
24 |
25 | if last == rune_NUM || cur == rune_NUM {
26 | return true
27 | } // if
28 |
29 | if last == rune_LOWER && cur == rune_CAPITAL {
30 | return true
31 | } // if
32 |
33 | return false
34 | }
35 |
36 | func runeType(r rune) int {
37 | switch {
38 | case r >= '0' && r <= '9':
39 | return rune_NUM
40 |
41 | case r >= 'A' && r <= 'Z':
42 | return rune_CAPITAL
43 |
44 | case r >= 'a' && r <= 'z':
45 | return rune_LOWER
46 | } // if
47 |
48 | return rune_SINGLE
49 | }
50 |
51 | func LineToTokens(line string) (tokens []string) {
52 | lastTp := rune_SINGLE
53 | for _, c := range line {
54 | tp := runeType(c)
55 | if newToken(lastTp, tp) {
56 | tokens = append(tokens, "")
57 | }
58 | tokens[len(tokens)-1] = tokens[len(tokens)-1] + string(c)
59 |
60 | lastTp = tp
61 | }
62 |
63 | return tokens
64 | }
65 |
66 | func diffAt(a []string, iA int, b []string, iB int) int {
67 | if iA < 0 {
68 | if iB < 0 {
69 | return 0
70 | } else {
71 | return 1
72 | }
73 | }
74 | if iB < 0 {
75 | return 1
76 | }
77 |
78 | if iA >= len(a) {
79 | if iB >= len(b) {
80 | return 0
81 | } else {
82 | return 1
83 | }
84 | }
85 | if iB >= len(b) {
86 | return 1
87 | }
88 |
89 | if a[iA] == b[iB] {
90 | return 0
91 | }
92 |
93 | return 2
94 | }
95 |
96 | func nearChecks(a []string) (l, r []bool) {
97 | l, r = make([]bool, len(a)), make([]bool, len(a))
98 |
99 | const (
100 | normal = iota
101 | doubleQuoted
102 | singleQuoted
103 | )
104 |
105 | status := normal
106 | escaped := false
107 | for i, el := range a {
108 | l[i], r[i] = true, true
109 |
110 | switch status {
111 | case normal:
112 | switch el {
113 | case `"`:
114 | status = doubleQuoted
115 | l[i] = false
116 | case "'":
117 | status = singleQuoted
118 | l[i] = false
119 | case ",", ")":
120 | r[i] = false
121 | }
122 | case doubleQuoted:
123 | switch el {
124 | case `\`:
125 | if !escaped {
126 | escaped = true
127 | continue
128 | }
129 | case `"`:
130 | if !escaped {
131 | status = normal
132 | r[i] = false
133 | }
134 | }
135 | escaped = false
136 | case singleQuoted:
137 | switch el {
138 | case `\`:
139 | if !escaped {
140 | escaped = true
141 | continue
142 | }
143 | case "'":
144 | if !escaped {
145 | status = normal
146 | r[i] = false
147 | }
148 | }
149 | escaped = false
150 | } // switch status
151 | }
152 |
153 | return l, r
154 | }
155 |
156 | // if tks[i] and tks[j] are pairs, pairs[i], pairs[j] = j, i
157 | func findPairs(tks []string) (pairs []int) {
158 | pairs = make([]int, len(tks))
159 | var s0, s1, s2 villa.IntSlice
160 | for i, tk := range tks {
161 | pairs[i] = -1
162 |
163 | switch tk {
164 | case "(":
165 | s0.Add(i)
166 | case ")":
167 | if len(s0) > 0 {
168 | j := s0.Pop()
169 | pairs[i], pairs[j] = j, i
170 | }
171 |
172 | case "[":
173 | s1.Add(i)
174 | case "]":
175 | if len(s1) > 0 {
176 | j := s1.Pop()
177 | pairs[i], pairs[j] = j, i
178 | } // if
179 |
180 | case "{":
181 | s2.Add(i)
182 | case "}":
183 | if len(s2) > 0 {
184 | j := s2.Pop()
185 | pairs[i], pairs[j] = j, i
186 | } // if
187 | }
188 | }
189 |
190 | return pairs
191 | }
192 |
193 | func noMatchBetween(mat []int, p1, p2 int) bool {
194 | if p2 < p1 {
195 | p1, p2 = p2, p1
196 | }
197 | for i := p1 + 1; i < p2; i++ {
198 | if mat[i] >= 0 {
199 | return false
200 | }
201 | }
202 |
203 | return true
204 | }
205 |
206 | /*
207 | if one of the pairs is match, and the other is not. Some adjustment can be performed.
208 | */
209 | func alignPairs(matA, matB, pairA, pairB []int) {
210 | for {
211 | changed := false
212 | /*
213 | A i <---> j m
214 | ^ 7
215 | | /
216 | v L
217 | B k <---> l
218 | */
219 | for i := range matA {
220 | j, k := pairA[i], matA[i]
221 | if j >= 0 && k >= 0 && matA[j] < 0 {
222 | l := pairB[k]
223 | if l >= 0 && matB[l] >= 0 {
224 | m := matB[l]
225 | if noMatchBetween(matA, j, m) {
226 | matA[j], matA[m], matB[l] = l, -1, j
227 | changed = true
228 | }
229 | }
230 | }
231 | }
232 |
233 | /*
234 | B i <---> j m
235 | ^ 7
236 | | /
237 | v L
238 | A k <---> l
239 | */
240 | for i := range matB {
241 | j, k := pairB[i], matB[i]
242 | if j >= 0 && k >= 0 && matB[j] < 0 {
243 | l := pairA[k]
244 | if l >= 0 && matA[l] >= 0 {
245 | m := matA[l]
246 | if noMatchBetween(matB, j, m) {
247 | matB[j], matB[m], matA[l] = l, -1, j
248 | changed = true
249 | }
250 | }
251 | }
252 | }
253 |
254 | if !changed {
255 | break
256 | }
257 | }
258 | }
259 |
260 | func MatchTokens(delT, insT []string) (matA, matB []int) {
261 | delL, delR := nearChecks(delT)
262 |
263 | _, matA, matB = ed.EditDistanceFFull(len(delT), len(insT), func(iA, iB int) int {
264 | if delT[iA] == insT[iB] {
265 | c := 0
266 | if delL[iA] {
267 | c += diffAt(delT, iA-1, insT, iB-1)
268 | }
269 | if delR[iA] {
270 | c += diffAt(delT, iA+1, insT, iB+1)
271 | }
272 | return c
273 | } // if
274 | return len(delT[iA]) + len(insT[iB]) + 5
275 | }, func(iA int) int {
276 | if delT[iA] == " " {
277 | return 0
278 | } // if
279 |
280 | return len(delT[iA]) + 2
281 | }, func(iB int) int {
282 | if insT[iB] == " " {
283 | return 0
284 | } // if
285 |
286 | return len(insT[iB]) + 2
287 | })
288 |
289 | delP, insP := findPairs(delT), findPairs(insT)
290 | alignPairs(matA, matB, delP, insP)
291 |
292 | return matA, matB
293 | }
294 |
295 | func DiffOfStrings(a, b string, mx int) int {
296 | if a == b {
297 | return 0
298 | } // if
299 | return ed.String(a, b) * mx / mathp.MaxI(len(a), len(b))
300 | }
301 |
302 | var key_WORDS villa.StrSet = villa.NewStrSet(
303 | "if", "for", "return", "switch", "case", "select", "go")
304 |
305 | func isKeywords(a []string) (res []bool) {
306 | res = make([]bool, len(a))
307 | for i, w := range a {
308 | res[i] = key_WORDS.In(w)
309 | }
310 |
311 | return res
312 | }
313 |
314 | func CalcDiffOfSourceLine(a, b string, mx int) int {
315 | if a == b {
316 | return 0
317 | } // if
318 |
319 | delT, insT := LineToTokens(a), LineToTokens(b)
320 | delK, insK := isKeywords(delT), isKeywords(insT)
321 |
322 | diff := ed.EditDistanceF(len(delT), len(insT), func(iA, iB int) int {
323 | if delT[iA] == insT[iB] {
324 | return 0
325 | } // if
326 | return 50
327 | }, func(iA int) int {
328 | if delK[iA] {
329 | return 2
330 | }
331 | return 1
332 | }, func(iB int) int {
333 | if insK[iB] {
334 | return 2
335 | }
336 |
337 | return 1
338 | })
339 |
340 | return diff * mx / (len(delT) + len(insT))
341 | }
342 |
--------------------------------------------------------------------------------
/cmd/godiff.go:
--------------------------------------------------------------------------------
1 | /*
2 | go-diff is a tool checking semantic difference between source files.
3 |
4 | Currently supported language:
5 |
6 | - Go (fully)
7 |
8 | If the language is not supported or parsing is failed for either file,
9 | a line-to-line comparing is imposed.
10 | */
11 | package godiff
12 |
13 | import (
14 | "bytes"
15 | "fmt"
16 | "go/ast"
17 | "go/parser"
18 | "go/printer"
19 | "go/token"
20 | "io"
21 | "math"
22 | "os"
23 | "sort"
24 | "strings"
25 |
26 | "github.com/daviddengcn/go-algs/ed"
27 | "github.com/daviddengcn/go-colortext"
28 | "github.com/daviddengcn/go-diff/tm"
29 | "github.com/daviddengcn/go-villa"
30 | "github.com/golangplus/fmt"
31 | "github.com/golangplus/math"
32 | )
33 |
34 | func cat(a, sep, b string) string {
35 | if len(a) > 0 && len(b) > 0 {
36 | return a + sep + b
37 | }
38 |
39 | return a + b
40 | }
41 |
42 | func changeColor(fg ct.Color, fgBright bool, bg ct.Color, bgBright bool) {
43 | if gOptions.NoColor {
44 | return
45 | }
46 |
47 | ct.ChangeColor(fg, fgBright, bg, bgBright)
48 | }
49 |
50 | func resetColor() {
51 | if gOptions.NoColor {
52 | return
53 | }
54 |
55 | ct.ResetColor()
56 | }
57 |
58 | func greedyMatch(lenA, lenB int, diffF func(iA, iB int) int, delCost, insCost func(int) int) (diffMat villa.IntMatrix, cost int, matA, matB []int) {
59 | matA, matB = make([]int, lenA), make([]int, lenB)
60 | villa.IntSlice(matA).Fill(0, lenA, -1)
61 | villa.IntSlice(matB).Fill(0, lenB, -1)
62 |
63 | diffMat = villa.NewIntMatrix(lenA, lenB)
64 |
65 | for iA := 0; iA < lenA; iA++ {
66 | if matA[iA] >= 0 {
67 | continue
68 | }
69 | for iB := 0; iB < lenB; iB++ {
70 | if matB[iB] >= 0 {
71 | continue
72 | }
73 |
74 | d := diffF(iA, iB)
75 | diffMat[iA][iB] = d
76 |
77 | if d == 0 {
78 | matA[iA], matB[iB] = iB, iA
79 | break
80 | }
81 | }
82 | }
83 |
84 | mat := diffMat.Clone()
85 | // mx is a number greater or equal to all mat elements (need not be the exact maximum)
86 | mx := 0
87 | for iA := range mat {
88 | if matA[iA] >= 0 {
89 | continue
90 | } // if
91 | for iB := 0; iB < lenB; iB++ {
92 | if matB[iB] >= 0 {
93 | continue
94 | } // if
95 | mat[iA][iB] -= delCost(iA) + insCost(iB)
96 | if mat[iA][iB] > mx {
97 | mx = mat[iA][iB]
98 | } // if
99 | } // for c
100 | } // for r
101 |
102 | for {
103 | mn := mx + 1
104 | selA, selB := -1, -1
105 | for iA := range mat {
106 | if matA[iA] >= 0 {
107 | continue
108 | } // if
109 | for iB := 0; iB < lenB; iB++ {
110 | if matB[iB] >= 0 {
111 | continue
112 | } // if
113 |
114 | if mat[iA][iB] < mn {
115 | mn = mat[iA][iB]
116 | selA, selB = iA, iB
117 | } // if
118 | } // for iB
119 | } // for iA
120 |
121 | if selA < 0 || mn >= 0 {
122 | break
123 | } // if
124 |
125 | matA[selA] = selB
126 | matB[selB] = selA
127 | } // for
128 |
129 | for iA := range matA {
130 | if matA[iA] < 0 {
131 | cost += delCost(iA)
132 | } else {
133 | cost += diffMat[iA][matA[iA]]
134 | } // else
135 | } // for iA
136 | for iB := range matB {
137 | if matB[iB] < 0 {
138 | cost += insCost(iB)
139 | } // if
140 | } // for iB
141 |
142 | return diffMat, cost, matA, matB
143 | }
144 |
145 | const (
146 | df_NONE = iota
147 | df_TYPE
148 | df_CONST
149 | df_VAR
150 | df_STRUCT
151 | df_INTERFACE
152 | df_FUNC
153 | df_STAR
154 | df_VAR_LINE
155 | df_PAIR
156 | df_NAMES
157 | df_VALUES
158 | df_BLOCK
159 | df_RESULTS
160 | )
161 |
162 | var typeNames []string = []string{
163 | "",
164 | "type",
165 | "const",
166 | "var",
167 | "struct",
168 | "interface",
169 | "func",
170 | "*",
171 | "",
172 | "",
173 | "",
174 | "",
175 | "",
176 | ""}
177 |
178 | type diffFragment interface {
179 | Type() int
180 | Weight() int
181 |
182 | // Max diff = this.Weight() + that.Weight()
183 | calcDiff(that diffFragment) int
184 |
185 | showDiff(that diffFragment)
186 | // indent is the leading chars from the second line
187 | sourceLines(indent string) []string
188 | oneLine() string
189 | }
190 |
191 | type fragment struct {
192 | tp int
193 | Parts []diffFragment
194 | }
195 |
196 | func (f *fragment) Type() int {
197 | return f.tp
198 | }
199 |
200 | func (f *fragment) Weight() (w int) {
201 | if f == nil {
202 | return 10
203 | } // if
204 |
205 | switch f.Type() {
206 | case df_FUNC:
207 | for i := 0; i < 4; i++ {
208 | w += f.Parts[i].Weight()
209 | } // for i
210 | w += int(math.Sqrt(float64(f.Parts[4].Weight())/100.) * 100)
211 | default:
212 | for _, p := range f.Parts {
213 | w += p.Weight()
214 | } // for p
215 | }
216 |
217 | switch f.Type() {
218 | case df_STAR:
219 | w += 50
220 | }
221 | return w
222 | }
223 |
224 | func catLines(a []string, sep string, b []string) []string {
225 | if len(a) > 0 && len(b) > 0 {
226 | b[0] = cat(a[len(a)-1], sep, b[0])
227 | a = a[:len(a)-1]
228 | }
229 |
230 | return append(a, b...)
231 | }
232 |
233 | /*
234 | a[0]
235 | a[1]
236 | ...
237 | cat(a[end], sep, b[0])
238 | b[1]
239 | ...
240 | b[end]
241 | */
242 | func appendLines(a []string, sep string, b ...string) []string {
243 | if len(a) > 0 && len(b) > 0 {
244 | b[0] = cat(a[len(a)-1], sep, b[0])
245 | a = a[:len(a)-1]
246 | }
247 |
248 | return append(a, b...)
249 | }
250 |
251 | func insertIndent(indent string, lines []string) []string {
252 | for i := range lines {
253 | lines[i] = indent + lines[i]
254 | } // for i
255 |
256 | return lines
257 | }
258 |
259 | func insertIndent2(indent string, lines []string) []string {
260 | for i := range lines {
261 | if i > 0 {
262 | lines[i] = indent + lines[i]
263 | } // if
264 | } // for i
265 |
266 | return lines
267 | }
268 |
269 | func (f *fragment) oneLine() string {
270 | if f == nil {
271 | return ""
272 | }
273 | switch f.tp {
274 | }
275 | lines := f.sourceLines("")
276 | if len(lines) == 0 {
277 | return ""
278 | }
279 |
280 | if len(lines) == 1 {
281 | return lines[0]
282 | }
283 |
284 | return lines[0] + " ... " + lines[len(lines)-1] + fmt.Sprintf(" (%d lines)", len(lines))
285 | }
286 |
287 | func (f *fragment) sourceLines(indent string) (lines []string) {
288 | if f == nil {
289 | return nil
290 | } // if
291 |
292 | switch f.tp {
293 | case df_TYPE:
294 | lines = append(lines, typeNames[f.tp])
295 | lines = catLines(lines, " ", f.Parts[0].sourceLines(indent))
296 | lines = catLines(lines, " ", f.Parts[1].sourceLines(indent))
297 | case df_CONST:
298 | if len(f.Parts) == 1 {
299 | lines = append(lines, typeNames[f.tp])
300 | lines = catLines(lines, " ", f.Parts[0].sourceLines(indent))
301 | } else {
302 | lines = append(lines, typeNames[f.tp]+"(")
303 | for _, p := range f.Parts {
304 | lines = append(lines, catLines([]string{indent + " "}, "", p.sourceLines(indent+" "))...)
305 | } // p
306 | lines = append(lines, indent+")")
307 | } // else
308 | case df_VAR:
309 | lines = append(lines, typeNames[f.tp])
310 | lines = catLines(lines, " ", f.Parts[0].sourceLines(indent+" "))
311 | case df_VAR_LINE:
312 | lines = f.Parts[0].sourceLines(indent)
313 | lines = catLines(lines, " ", f.Parts[1].sourceLines(indent))
314 | lines = catLines(lines, " = ", f.Parts[2].sourceLines(indent))
315 | case df_FUNC:
316 | lines = append(lines, typeNames[f.tp])
317 | if f.Parts[0].(*fragment) != nil {
318 | lines = catLines(catLines(lines, " (", f.Parts[0].sourceLines(indent+" ")), "", []string{")"}) // recv
319 | } // if
320 | lines = catLines(lines, " ", f.Parts[1].sourceLines(indent+" ")) // name
321 | lines = catLines(catLines(catLines(lines, "", []string{"("}), "",
322 | f.Parts[2].sourceLines(indent+" ")), "", []string{")"}) // params
323 | lines = catLines(lines, " ", f.Parts[3].sourceLines(indent+" ")) // returns
324 | lines = catLines(lines, " ", f.Parts[4].sourceLines(indent)) // body
325 | case df_RESULTS:
326 | if len(f.Parts) > 0 {
327 | if len(f.Parts) > 1 || len(f.Parts[0].(*fragment).Parts[0].(*stringFrag).source) > 0 {
328 | lines = append(lines, "(")
329 | } // if
330 | for i, p := range f.Parts {
331 | if i > 0 {
332 | lines = catLines(lines, "", []string{", "})
333 | } // if
334 | lines = catLines(lines, "", p.sourceLines(indent+" "))
335 | } // for i, p
336 | if len(f.Parts) > 1 || len(f.Parts[0].(*fragment).Parts[0].(*stringFrag).source) > 0 {
337 | lines = catLines(lines, "", []string{")"})
338 | } // if
339 | } // if
340 | case df_BLOCK:
341 | lines = append(lines, "{")
342 | for _, p := range f.Parts {
343 | lines = append(lines, catLines([]string{indent + " "}, "", p.sourceLines(indent+" "))...)
344 | } // for p
345 | lines = append(lines, indent+"}")
346 | case df_STRUCT, df_INTERFACE:
347 | if len(f.Parts) == 0 {
348 | lines = append(lines, typeNames[f.tp]+"{}")
349 | } else {
350 | lines = append(lines, typeNames[f.tp]+" {")
351 | for _, p := range f.Parts {
352 | lns := p.sourceLines(indent + " ")
353 | if len(lns) > 0 {
354 | lns[0] = indent + " " + lns[0]
355 | lines = append(lines, lns...)
356 | } // if
357 | } // for p
358 | lines = append(lines, indent+"}")
359 | }
360 | case df_STAR:
361 | lines = append(lines, typeNames[f.tp])
362 | lines = catLines(lines, "", f.Parts[0].sourceLines(indent))
363 | case df_PAIR:
364 | lines = catLines(f.Parts[0].sourceLines(indent), " ", f.Parts[1].sourceLines(indent))
365 | case df_NAMES:
366 | s := ""
367 | for _, p := range f.Parts {
368 | s = cat(s, ", ", p.sourceLines(indent + " ")[0])
369 | } // for p
370 | lines = append(lines, s)
371 | case df_VALUES:
372 | for _, p := range f.Parts {
373 | lines = catLines(lines, ", ", p.sourceLines(indent+" "))
374 | } // for p
375 | case df_NONE:
376 | for _, p := range f.Parts {
377 | lines = append(lines, p.sourceLines(indent)...)
378 | } // for p
379 | default:
380 | lines = []string{"TYPE: " + typeNames[f.Type()]}
381 | for _, p := range f.Parts {
382 | lines = append(lines, p.sourceLines(indent+" ")...)
383 | } // for p
384 | }
385 |
386 | //f.lines = lines
387 | return lines
388 | }
389 |
390 | func (f *fragment) calcDiff(that diffFragment) int {
391 | switch g := that.(type) {
392 | case *fragment:
393 | if f == nil {
394 | if g == nil {
395 | return 0
396 | } else {
397 | return f.Weight() + g.Weight()
398 | } // else
399 | } // if
400 | if g == nil {
401 | return f.Weight() + g.Weight()
402 | } // if
403 |
404 | switch f.Type() {
405 | case df_STAR:
406 | if g.Type() == df_STAR {
407 | return f.Parts[0].calcDiff(g.Parts[0])
408 | } // if
409 |
410 | return f.Parts[0].calcDiff(g) + 50
411 | }
412 |
413 | if g.Type() == df_STAR {
414 | return f.calcDiff(g.Parts[0]) + 50
415 | } // if
416 |
417 | if f.Type() != g.Type() {
418 | return f.Weight() + g.Weight()
419 | } // if
420 |
421 | switch f.Type() {
422 | case df_FUNC:
423 | res := int(0)
424 | for i := 0; i < 4; i++ {
425 | res += f.Parts[i].calcDiff(g.Parts[i])
426 | } // for i
427 |
428 | res += int(math.Sqrt(float64(f.Parts[4].calcDiff(g.Parts[4]))/100.) * 100)
429 |
430 | return res
431 | }
432 |
433 | return ed.EditDistanceF(len(f.Parts), len(g.Parts), func(iA, iB int) int {
434 | return f.Parts[iA].calcDiff(g.Parts[iB]) * 3 / 2
435 | }, func(iA int) int {
436 | return f.Parts[iA].Weight()
437 | }, func(iB int) int {
438 | return g.Parts[iB].Weight()
439 | })
440 | }
441 | return f.Weight() + that.Weight()
442 | }
443 |
444 | func (f *fragment) showDiff(that diffFragment) {
445 | diffLines(f.sourceLines(""), that.sourceLines(""), `%s`)
446 | }
447 |
448 | type stringFrag struct {
449 | weight int
450 | source string
451 | }
452 |
453 | func newStringFrag(source string, weight int) *stringFrag {
454 | return &stringFrag{weight: weight, source: source}
455 | }
456 |
457 | func (sf *stringFrag) Type() int {
458 | return df_NONE
459 | }
460 |
461 | func (sf *stringFrag) Weight() int {
462 | return sf.weight
463 | }
464 |
465 | func (sf *stringFrag) calcDiff(that diffFragment) int {
466 | switch g := that.(type) {
467 | case *stringFrag:
468 | s1, s2 := strings.TrimSpace(sf.source), strings.TrimSpace(g.source)
469 | if len(s1)+len(s2) == 0 {
470 | return 0
471 | } // if
472 | wt := sf.weight + g.weight
473 | return ed.String(s1, s2) * wt / mathp.MaxI(len(s1), len(s2))
474 | } // switch
475 |
476 | return sf.Weight() + that.Weight()
477 | }
478 |
479 | func (sf *stringFrag) showDiff(that diffFragment) {
480 | diffLines(sf.sourceLines(" "), that.sourceLines(" "), `%s`)
481 | }
482 |
483 | func (sf *stringFrag) oneLine() string {
484 | if sf == nil {
485 | return ""
486 | } // if
487 |
488 | return sf.source
489 | }
490 |
491 | func (sf *stringFrag) sourceLines(indent string) []string {
492 | lines := strings.Split(sf.source, "\n")
493 | for i := range lines {
494 | if i > 0 {
495 | lines[i] = indent + lines[i]
496 | } // if
497 | } // for i
498 |
499 | return lines
500 | }
501 |
502 | const (
503 | td_STRUCT = iota
504 | td_INTERFACE
505 | td_POINTER
506 | td_ONELINE
507 | )
508 |
509 | func newNameTypes(fs *token.FileSet, fl *ast.FieldList) (dfs []diffFragment) {
510 | for _, f := range fl.List {
511 | if len(f.Names) > 0 {
512 | for _, name := range f.Names {
513 | dfs = append(dfs, &fragment{tp: df_PAIR,
514 | Parts: []diffFragment{newStringFrag(name.String(), 100),
515 | newTypeDef(fs, f.Type)}})
516 | } // for name
517 | } else {
518 | // embedding
519 | dfs = append(dfs, &fragment{tp: df_PAIR,
520 | Parts: []diffFragment{newStringFrag("", 50),
521 | newTypeDef(fs, f.Type)}})
522 | } // else
523 | } // for f
524 |
525 | return dfs
526 | }
527 |
528 | func newTypeDef(fs *token.FileSet, def ast.Expr) diffFragment {
529 | switch d := def.(type) {
530 | case *ast.StructType:
531 | return &fragment{tp: df_STRUCT, Parts: newNameTypes(fs, d.Fields)}
532 |
533 | case *ast.InterfaceType:
534 | return &fragment{tp: df_INTERFACE, Parts: newNameTypes(fs, d.Methods)}
535 |
536 | case *ast.StarExpr:
537 | return &fragment{tp: df_STAR, Parts: []diffFragment{newTypeDef(fs, d.X)}}
538 | } // switch
539 |
540 | var src bytes.Buffer
541 | (&printer.Config{Mode: printer.UseSpaces, Tabwidth: 4}).Fprint(&src, fs, def)
542 | return &stringFrag{weight: 50, source: src.String()}
543 | }
544 |
545 | func newTypeStmtInfo(fs *token.FileSet, name string, def ast.Expr) *fragment {
546 | var f fragment
547 |
548 | f.tp = df_TYPE
549 | f.Parts = []diffFragment{
550 | newStringFrag(name, 100),
551 | newTypeDef(fs, def)}
552 |
553 | return &f
554 | }
555 |
556 | func newExpDef(fs *token.FileSet, def ast.Expr) diffFragment {
557 | //ast.Print(fs, def)
558 | var src bytes.Buffer
559 | (&printer.Config{Mode: printer.UseSpaces, Tabwidth: 4}).Fprint(&src, fs, def)
560 | return &stringFrag{weight: 100, source: src.String()}
561 | }
562 |
563 | func newVarSpecs(fs *token.FileSet, specs []ast.Spec) (dfs []diffFragment) {
564 | for _, spec := range specs {
565 | f := &fragment{tp: df_VAR_LINE}
566 |
567 | names := &fragment{tp: df_NAMES}
568 | sp := spec.(*ast.ValueSpec)
569 | for _, name := range sp.Names {
570 | names.Parts = append(names.Parts, &stringFrag{weight: 100,
571 | source: fmt.Sprint(name)})
572 | }
573 | f.Parts = append(f.Parts, names)
574 |
575 | if sp.Type != nil {
576 | f.Parts = append(f.Parts, newTypeDef(fs, sp.Type))
577 | } else {
578 | f.Parts = append(f.Parts, (*fragment)(nil))
579 | } // else
580 |
581 | values := &fragment{tp: df_VALUES}
582 | for _, v := range sp.Values {
583 | values.Parts = append(values.Parts, newExpDef(fs, v))
584 | } // for v
585 | f.Parts = append(f.Parts, values)
586 |
587 | dfs = append(dfs, f)
588 | }
589 |
590 | return dfs
591 | }
592 |
593 | func printToLines(fs *token.FileSet, node interface{}) []string {
594 | var src bytes.Buffer
595 | (&printer.Config{Mode: printer.UseSpaces, Tabwidth: 4}).Fprint(&src, fs, node)
596 | return strings.Split(src.String(), "\n")
597 | }
598 |
599 | func nodeToLines(fs *token.FileSet, node interface{}) (lines []string) {
600 | switch nd := node.(type) {
601 | case *ast.IfStmt:
602 | lines = append(lines, "if")
603 | if nd.Init != nil {
604 | lines = appendLines(lines, " ", nodeToLines(fs, nd.Init)...)
605 | lines = appendLines(lines, "", ";")
606 | } // if
607 |
608 | lines = catLines(lines, " ", nodeToLines(fs, nd.Cond))
609 | lines = catLines(lines, " ", []string{"{"})
610 | lines = append(lines, insertIndent(" ", blockToLines(fs, nd.Body))...)
611 | lines = append(lines, "}")
612 | if nd.Else != nil {
613 | //ast.Print(fs, st.Else)
614 | lines = catLines(lines, "", []string{" else "})
615 | lines = catLines(lines, "", nodeToLines(fs, nd.Else))
616 | } // if
617 | case *ast.AssignStmt:
618 | for _, exp := range nd.Lhs {
619 | lines = catLines(lines, ", ", nodeToLines(fs, exp))
620 | } // for i
621 |
622 | lines = catLines(lines, "", []string{" " + nd.Tok.String() + " "})
623 |
624 | for i, exp := range nd.Rhs {
625 | if i > 0 {
626 | lines = catLines(lines, "", []string{", "})
627 | } // if
628 | lines = catLines(lines, "", nodeToLines(fs, exp))
629 | } // for i
630 |
631 | case *ast.ForStmt:
632 | lines = append(lines, "for")
633 | if nd.Cond != nil {
634 | lns := []string{}
635 | if nd.Init != nil {
636 | lns = catLines(lns, "; ", nodeToLines(fs, nd.Init))
637 | } // if
638 | lns = catLines(lns, "; ", nodeToLines(fs, nd.Cond))
639 | if nd.Post != nil {
640 | lns = catLines(lns, "; ", nodeToLines(fs, nd.Post))
641 | } // if
642 |
643 | lines = catLines(lines, " ", lns)
644 | } // if
645 | lines = catLines(lines, "", []string{" {"})
646 | lines = append(lines, insertIndent(" ", blockToLines(fs, nd.Body))...)
647 | lines = append(lines, "}")
648 | case *ast.RangeStmt:
649 | lines = append(lines, "for")
650 | lines = catLines(lines, " ", nodeToLines(fs, nd.Key))
651 | if nd.Value != nil {
652 | lines = catLines(lines, ", ", nodeToLines(fs, nd.Value))
653 | } // if
654 | lines = catLines(lines, "", []string{" " + nd.Tok.String() + " "})
655 | lines = catLines(lines, "", []string{" range"})
656 | lines = catLines(lines, " ", nodeToLines(fs, nd.X))
657 | lines = catLines(lines, "", []string{" {"})
658 | lines = append(lines, insertIndent(" ", blockToLines(fs, nd.Body))...)
659 | lines = append(lines, "}")
660 |
661 | case *ast.BlockStmt:
662 | lines = append(lines, "{")
663 | lines = append(lines, insertIndent(" ", blockToLines(fs, nd))...)
664 | lines = append(lines, "}")
665 |
666 | case *ast.ReturnStmt:
667 | lines = append(lines, "return")
668 | if nd.Results != nil {
669 | for i, e := range nd.Results {
670 | if i == 0 {
671 | lines = appendLines(lines, " ", nodeToLines(fs, e)...)
672 | } else {
673 | lines = appendLines(lines, ", ", nodeToLines(fs, e)...)
674 | } // else
675 | } // for i, e
676 | } // if
677 |
678 | case *ast.DeferStmt:
679 | lines = append(lines, "defer")
680 | lines = appendLines(lines, " ", nodeToLines(fs, nd.Call)...)
681 |
682 | case *ast.GoStmt:
683 | lines = append(lines, "go")
684 | lines = appendLines(lines, " ", nodeToLines(fs, nd.Call)...)
685 |
686 | case *ast.SendStmt:
687 | lines = append(lines, nodeToLines(fs, nd.Chan)...)
688 | lines = appendLines(lines, " ", "<-")
689 | lines = appendLines(lines, " ", nodeToLines(fs, nd.Value)...)
690 |
691 | case *ast.ExprStmt:
692 | return nodeToLines(fs, nd.X)
693 |
694 | case *ast.EmptyStmt:
695 | // Do nothing
696 |
697 | case *ast.SwitchStmt:
698 | lines = append(lines, "switch")
699 | if nd.Init != nil {
700 | lines = appendLines(lines, " ", nodeToLines(fs, nd.Init)...)
701 | lines = appendLines(lines, "", ";")
702 | } // if
703 | if nd.Tag != nil {
704 | lines = appendLines(lines, " ", nodeToLines(fs, nd.Tag)...)
705 | } // if
706 | lines = appendLines(lines, " ", nodeToLines(fs, nd.Body)...)
707 |
708 | case *ast.TypeSwitchStmt:
709 | lines = append(lines, "switch")
710 | if nd.Init != nil {
711 | lines = appendLines(lines, " ", nodeToLines(fs, nd.Init)...)
712 | lines = appendLines(lines, "", ";")
713 | } // if
714 | lines = appendLines(lines, " ", nodeToLines(fs, nd.Assign)...)
715 | lines = appendLines(lines, " ", nodeToLines(fs, nd.Body)...)
716 |
717 | case *ast.CompositeLit:
718 | if nd.Type != nil {
719 | lines = append(lines, printToLines(fs, nd.Type)...)
720 | }
721 | if len(nd.Elts) == 0 {
722 | // short form
723 | lines = appendLines(lines, "", "{}")
724 | } else {
725 | lines = appendLines(lines, "", "{")
726 |
727 | for _, el := range nd.Elts {
728 | lines = append(lines, insertIndent(" ", nodeToLines(fs, el))...)
729 | lines = appendLines(lines, "", ",")
730 | }
731 | // put } in a new line
732 | lines = append(lines, "")
733 | lines = appendLines(lines, "", "}")
734 | }
735 |
736 | case *ast.UnaryExpr:
737 | lines = append(lines, nd.Op.String())
738 | lines = appendLines(lines, "", nodeToLines(fs, nd.X)...)
739 |
740 | case *ast.BinaryExpr:
741 | lines = appendLines(lines, "", nodeToLines(fs, nd.X)...)
742 | lines = appendLines(lines, " ", nd.Op.String())
743 | lines = appendLines(lines, " ", nodeToLines(fs, nd.Y)...)
744 |
745 | case *ast.ParenExpr:
746 | lines = append(lines, "(")
747 | lines = appendLines(lines, "", nodeToLines(fs, nd.X)...)
748 | lines = appendLines(lines, "", ")")
749 |
750 | case *ast.CallExpr:
751 | lines = append(lines, nodeToLines(fs, nd.Fun)...)
752 | lines = appendLines(lines, "", "(")
753 | for i, a := range nd.Args {
754 | if i > 0 {
755 | lines = appendLines(lines, "", ", ")
756 | } // if
757 | lines = appendLines(lines, "", nodeToLines(fs, a)...)
758 | } // for i, el
759 | if nd.Ellipsis > 0 {
760 | lines = appendLines(lines, "", "...")
761 | } // if
762 | lines = appendLines(lines, "", ")")
763 | case *ast.KeyValueExpr:
764 | lines = append(lines, nodeToLines(fs, nd.Key)...)
765 | lines = appendLines(lines, ": ", nodeToLines(fs, nd.Value)...)
766 | case *ast.FuncLit:
767 | lines = nodeToLines(fs, nd.Type)
768 | lines = appendLines(lines, " ", nodeToLines(fs, nd.Body)...)
769 |
770 | case *ast.CaseClause:
771 | if nd.List == nil {
772 | lines = append(lines, "default:")
773 | } else {
774 | lines = append(lines, "case ")
775 | for i, e := range nd.List {
776 | if i > 0 {
777 | lines = appendLines(lines, "", ", ")
778 | } // if
779 | lines = appendLines(lines, "", nodeToLines(fs, e)...)
780 | } // for i
781 | lines = appendLines(lines, "", ":")
782 | } // else
783 |
784 | for _, st := range nd.Body {
785 | lines = append(lines, insertIndent(" ", nodeToLines(fs, st))...)
786 | } // for
787 |
788 | case *ast.SelectorExpr:
789 | lines = append(lines, nodeToLines(fs, nd.X)...)
790 | lines = appendLines(lines, "", ".")
791 | lines = appendLines(lines, "", nodeToLines(fs, nd.Sel)...)
792 | case *ast.LabeledStmt:
793 | lines = append(lines, nodeToLines(fs, nd.Label)...)
794 | lines = appendLines(lines, "", ":")
795 | lines = appendLines(lines, "", nodeToLines(fs, nd.Stmt)...)
796 | case *ast.Ident, *ast.BasicLit, *ast.DeclStmt, *ast.BranchStmt, *ast.IndexExpr, *ast.FuncType, *ast.SliceExpr, *ast.StarExpr, *ast.ArrayType, *ast.TypeAssertExpr:
797 | return printToLines(fs, nd)
798 | default:
799 | //ast.Print(fs, nd)
800 | //ast.Print(fs, printToLines(fs, nd))
801 |
802 | return printToLines(fs, nd)
803 | }
804 |
805 | return lines
806 | }
807 |
808 | func blockToLines(fs *token.FileSet, blk *ast.BlockStmt) (lines []string) {
809 | for _, s := range blk.List {
810 | lines = append(lines, nodeToLines(fs, s)...)
811 | } // for s
812 |
813 | return lines
814 | }
815 |
816 | func newBlockDecl(fs *token.FileSet, blk *ast.BlockStmt) (f *fragment) {
817 | f = &fragment{tp: df_BLOCK}
818 | lines := blockToLines(fs, blk)
819 | for _, line := range lines {
820 | f.Parts = append(f.Parts, &stringFrag{weight: 100, source: line})
821 | } // for line
822 |
823 | return f
824 | }
825 |
826 | func newFuncDecl(fs *token.FileSet, d *ast.FuncDecl) (f *fragment) {
827 | f = &fragment{tp: df_FUNC}
828 |
829 | // recv
830 | if d.Recv != nil {
831 | f.Parts = append(f.Parts, newNameTypes(fs, d.Recv)...)
832 | } else {
833 | f.Parts = append(f.Parts, (*fragment)(nil))
834 | } // else
835 |
836 | // name
837 | f.Parts = append(f.Parts, &stringFrag{weight: 200, source: fmt.Sprint(d.Name)})
838 |
839 | // params
840 | if d.Type.Params != nil {
841 | f.Parts = append(f.Parts, &fragment{tp: df_VALUES,
842 | Parts: newNameTypes(fs, d.Type.Params)})
843 | } else {
844 | f.Parts = append(f.Parts, (*fragment)(nil))
845 | } // else
846 |
847 | // Results
848 | if d.Type.Results != nil {
849 | f.Parts = append(f.Parts, &fragment{tp: df_RESULTS, Parts: newNameTypes(fs, d.Type.Results)})
850 | } else {
851 | f.Parts = append(f.Parts, (*fragment)(nil))
852 | } // else
853 |
854 | // body
855 | if d.Body != nil {
856 | f.Parts = append(f.Parts, newBlockDecl(fs, d.Body))
857 | } else {
858 | f.Parts = append(f.Parts, (*fragment)(nil))
859 | } // else
860 | return f
861 | }
862 |
863 | type fileInfo struct {
864 | f *ast.File
865 | fs *token.FileSet
866 | types *fragment
867 | vars *fragment
868 | funcs *fragment
869 | }
870 |
871 | func (info *fileInfo) collect() {
872 | info.types = &fragment{}
873 | info.vars = &fragment{}
874 | info.funcs = &fragment{}
875 |
876 | for _, decl := range info.f.Decls {
877 | switch d := decl.(type) {
878 | case *ast.GenDecl:
879 | switch d.Tok {
880 | case token.TYPE:
881 | for i := range d.Specs {
882 | spec := d.Specs[i].(*ast.TypeSpec)
883 | //ast.Print(info.fs, spec)
884 | ti := newTypeStmtInfo(info.fs, spec.Name.String(), spec.Type)
885 | info.types.Parts = append(info.types.Parts, ti)
886 | } // for i
887 | case token.CONST:
888 | // fmt.Println(d)
889 | //ast.Print(info.fs, d)
890 | v := &fragment{tp: df_CONST, Parts: newVarSpecs(info.fs, d.Specs)}
891 | info.vars.Parts = append(info.vars.Parts, v)
892 | case token.VAR:
893 | //ast.Print(info.fs, d)
894 | vss := newVarSpecs(info.fs, d.Specs)
895 | for _, vs := range vss {
896 | info.vars.Parts = append(info.vars.Parts, &fragment{tp: df_VAR, Parts: []diffFragment{vs}})
897 | }
898 | case token.IMPORT:
899 | // ignore
900 | default:
901 | // Unknow
902 | fmt.Fprintln(gOut, d)
903 | } // switch d.tok
904 | case *ast.FuncDecl:
905 | fd := newFuncDecl(info.fs, d)
906 | info.funcs.Parts = append(info.funcs.Parts, fd)
907 | //ast.Print(info.fs, d)
908 | default:
909 | // fmt.Println(d)
910 | } // switch decl.(type)
911 | } // for decl
912 | }
913 |
914 | func parse(fn string, src interface{}) (*fileInfo, error) {
915 | if fn == "/dev/null" {
916 | return &fileInfo{
917 | f: &ast.File{},
918 | types: &fragment{},
919 | vars: &fragment{},
920 | funcs: &fragment{},
921 | }, nil
922 | }
923 |
924 | fset := token.NewFileSet()
925 | f, err := parser.ParseFile(fset, fn, src, 0)
926 | if err != nil {
927 | return nil, err
928 | } // if
929 |
930 | info := &fileInfo{f: f, fs: fset}
931 | info.collect()
932 |
933 | return info, nil
934 | }
935 |
936 | const (
937 | // delete color
938 | del_COLOR = ct.Red
939 | // insert color
940 | ins_COLOR = ct.Green
941 | // matched color
942 | mat_COLOR = ct.White
943 | // folded color
944 | fld_COLOR = ct.Yellow
945 | )
946 |
947 | func showDelWholeLine(line string) {
948 | changeColor(del_COLOR, false, ct.None, false)
949 | fmt.Fprintln(gOut, "===", line)
950 | resetColor()
951 | }
952 | func showDelLine(line string) {
953 | changeColor(del_COLOR, false, ct.None, false)
954 | fmt.Fprintln(gOut, "---", line)
955 | resetColor()
956 | }
957 | func showColorDelLine(line, lcs string) {
958 | changeColor(del_COLOR, false, ct.None, false)
959 | fmt.Fprint(gOut, "--- ")
960 | lcsr := []rune(lcs)
961 | for _, c := range line {
962 | if len(lcsr) > 0 && lcsr[0] == c {
963 | resetColor()
964 | lcsr = lcsr[1:]
965 | } else {
966 | changeColor(del_COLOR, false, ct.None, false)
967 | } // else
968 | fmt.Fprintf(gOut, "%c", c)
969 | } // for c
970 |
971 | resetColor()
972 | fmt.Fprintln(gOut)
973 | }
974 |
975 | func showDelLines(lines []string, gapLines int) {
976 | if len(lines) <= gapLines*2+1 {
977 | for _, line := range lines {
978 | showDelLine(line)
979 | } // for line
980 | return
981 | } // if
982 |
983 | for i, line := range lines {
984 | if i < gapLines || i >= len(lines)-gapLines {
985 | showDelLine(line)
986 | } // if
987 | if i == gapLines {
988 | showDelWholeLine(fmt.Sprintf(" ... (%d lines)", len(lines)-gapLines*2))
989 | } // if
990 | } // for i
991 | }
992 |
993 | func showInsLine(line string) {
994 | changeColor(ins_COLOR, false, ct.None, false)
995 | fmt.Fprintln(gOut, "+++", line)
996 | resetColor()
997 | }
998 | func showColorInsLine(line, lcs string) {
999 | changeColor(ins_COLOR, false, ct.None, false)
1000 | fmt.Fprint(gOut, "+++ ")
1001 | lcsr := []rune(lcs)
1002 | for _, c := range line {
1003 | if len(lcsr) > 0 && lcsr[0] == c {
1004 | resetColor()
1005 | lcsr = lcsr[1:]
1006 | } else {
1007 | changeColor(ins_COLOR, false, ct.None, false)
1008 | } // else
1009 | fmt.Fprintf(gOut, "%c", c)
1010 | } // for c
1011 |
1012 | resetColor()
1013 | fmt.Fprintln(gOut)
1014 | }
1015 |
1016 | func showInsWholeLine(line string) {
1017 | changeColor(ins_COLOR, false, ct.None, false)
1018 | fmt.Fprintln(gOut, "###", line)
1019 | resetColor()
1020 | }
1021 |
1022 | func showInsLines(lines []string, gapLines int) {
1023 | if len(lines) <= gapLines*2+1 {
1024 | for _, line := range lines {
1025 | showInsLine(line)
1026 | } // for line
1027 | return
1028 | } // if
1029 |
1030 | for i, line := range lines {
1031 | if i < gapLines || i >= len(lines)-gapLines {
1032 | showInsLine(line)
1033 | } // if
1034 | if i == gapLines {
1035 | showInsWholeLine(fmt.Sprintf(" ... (%d lines)", len(lines)-gapLines*2))
1036 | } // if
1037 | } // for i
1038 | }
1039 |
1040 | func showDelTokens(del []string, mat []int, ins []string) {
1041 | changeColor(del_COLOR, false, ct.None, false)
1042 | fmt.Fprint(gOut, "--- ")
1043 |
1044 | for i, tk := range del {
1045 | if mat[i] < 0 || tk != ins[mat[i]] {
1046 | changeColor(del_COLOR, false, ct.None, false)
1047 | } else {
1048 | changeColor(mat_COLOR, false, ct.None, false)
1049 | }
1050 |
1051 | fmt.Fprint(gOut, tk)
1052 | } // for i
1053 |
1054 | resetColor()
1055 | fmt.Fprintln(gOut)
1056 | }
1057 |
1058 | func showInsTokens(ins []string, mat []int, del []string) {
1059 | changeColor(ins_COLOR, false, ct.None, false)
1060 | fmt.Fprint(gOut, "+++ ")
1061 |
1062 | for i, tk := range ins {
1063 | if mat[i] < 0 || tk != del[mat[i]] {
1064 | changeColor(ins_COLOR, false, ct.None, false)
1065 | } else {
1066 | resetColor()
1067 | } // else
1068 |
1069 | fmt.Fprint(gOut, tk)
1070 | } // for i
1071 |
1072 | resetColor()
1073 | fmt.Fprintln(gOut)
1074 | }
1075 |
1076 | func showDiffLine(del, ins string) {
1077 | delT, insT := tm.LineToTokens(del), tm.LineToTokens(ins)
1078 | matA, matB := tm.MatchTokens(delT, insT)
1079 |
1080 | showDelTokens(delT, matA, insT)
1081 | showInsTokens(insT, matB, delT)
1082 | }
1083 |
1084 | func diffLineSet(orgLines, newLines []string, format string) {
1085 | sort.Strings(orgLines)
1086 | sort.Strings(newLines)
1087 |
1088 | _, matA, matB := ed.EditDistanceFFull(len(orgLines), len(newLines), func(iA, iB int) int {
1089 | return tm.DiffOfStrings(orgLines[iA], newLines[iB], 4000)
1090 | }, ed.ConstCost(1000), ed.ConstCost(1000))
1091 |
1092 | for i, j := 0, 0; i < len(orgLines) || j < len(newLines); {
1093 | switch {
1094 | case j >= len(newLines) || i < len(orgLines) && matA[i] < 0:
1095 | showDelLine(fmt.Sprintf(format, orgLines[i]))
1096 | i++
1097 | case i >= len(orgLines) || j < len(newLines) && matB[j] < 0:
1098 | showInsLine(fmt.Sprintf(format, newLines[j]))
1099 | j++
1100 | default:
1101 | if strings.TrimSpace(orgLines[i]) != strings.TrimSpace(newLines[j]) {
1102 | showDiffLine(fmt.Sprintf(format, orgLines[i]), fmt.Sprintf(format, newLines[j]))
1103 | } // if
1104 | i++
1105 | j++
1106 | }
1107 | } // for i, j
1108 | }
1109 |
1110 | type lineOutputer interface {
1111 | outputIns(line string)
1112 | outputDel(line string)
1113 | outputSame(line string)
1114 | outputChange(del, ins string)
1115 | end()
1116 | }
1117 |
1118 | type lineOutput struct {
1119 | sameLines []string
1120 | }
1121 |
1122 | func (lo *lineOutput) outputIns(line string) {
1123 | lo.end()
1124 | showInsLine(line)
1125 | }
1126 |
1127 | func (lo *lineOutput) outputDel(line string) {
1128 | lo.end()
1129 | showDelLine(line)
1130 | }
1131 |
1132 | func (lo *lineOutput) outputChange(del, ins string) {
1133 | lo.end()
1134 | showDiffLine(del, ins)
1135 | }
1136 |
1137 | func (lo *lineOutput) outputSame(line string) {
1138 | lo.sameLines = append(lo.sameLines, line)
1139 | }
1140 |
1141 | func (lo *lineOutput) end() {
1142 | if len(lo.sameLines) > 0 {
1143 | fmt.Fprintln(gOut, " ", lo.sameLines[0])
1144 | if len(lo.sameLines) == 3 {
1145 | fmt.Fprintln(gOut, " ", lo.sameLines[1])
1146 | } // if
1147 | if len(lo.sameLines) > 3 {
1148 | changeColor(fld_COLOR, false, ct.None, false)
1149 | fmtp.Fprintfln(gOut, " ... (%d lines)", len(lo.sameLines)-2)
1150 | resetColor()
1151 | } // if
1152 | if len(lo.sameLines) > 1 {
1153 | fmt.Fprintln(gOut, " ", lo.sameLines[len(lo.sameLines)-1])
1154 | } // if
1155 | } // if
1156 |
1157 | lo.sameLines = nil
1158 | }
1159 |
1160 | func offsetHeadTails(orgLines, newLines []string) (start, orgEnd, newEnd int) {
1161 | start = 0
1162 | for start < len(orgLines) && start < len(newLines) && orgLines[start] == newLines[start] {
1163 | start++
1164 | }
1165 |
1166 | orgEnd, newEnd = len(orgLines), len(newLines)
1167 | for orgEnd > start && newEnd > start && orgLines[orgEnd-1] == newLines[newEnd-1] {
1168 | orgEnd--
1169 | newEnd--
1170 | }
1171 | return
1172 | }
1173 |
1174 | func isBlockStart(s string) bool {
1175 | return strings.HasPrefix(s, "{") || strings.HasPrefix(s, "/*")
1176 | }
1177 |
1178 | func diffLinesTo(orgLines, newLines []string, format string, lo lineOutputer) int {
1179 | if len(orgLines)+len(newLines) == 0 {
1180 | return 0
1181 | }
1182 |
1183 | start, orgEnd, newEnd := 0, len(orgLines), len(newLines)
1184 |
1185 | if len(orgLines)*len(newLines) > 1024*1024 {
1186 | // Use trivial comparison to offset same head and tail lines.
1187 | start, orgEnd, newEnd = offsetHeadTails(orgLines, newLines)
1188 | }
1189 |
1190 | fastMode := false
1191 | if len(orgLines)*len(newLines) > 1024*1024 {
1192 | fastMode = true
1193 | }
1194 |
1195 | _, matA, matB := ed.EditDistanceFFull(orgEnd-start, newEnd-start, func(iA, iB int) int {
1196 | sa, sb := orgLines[iA+start], newLines[iB+start]
1197 | rawEqual := sa == sb
1198 | sa, sb = strings.TrimSpace(sa), strings.TrimSpace(sb)
1199 |
1200 | posCost := 0
1201 | if isBlockStart(sa) {
1202 | posCost += (newEnd - start - 1 - iB) * 10 / (newEnd - start)
1203 | }
1204 |
1205 | if rawEqual {
1206 | return posCost
1207 | }
1208 | if sa == sb {
1209 | return posCost + 1
1210 | }
1211 |
1212 | mx := (len(sa) + len(sb)) * 150
1213 |
1214 | var dist int
1215 |
1216 | if fastMode && len(sa) > 10*len(sb) {
1217 | dist = 100 * (len(sa) - len(sb))
1218 | } else if fastMode && len(sb) > 10*len(sa) {
1219 | dist = 100 * (len(sb) - len(sa))
1220 | } else {
1221 | // When sa and sb has 1/3 in common, convertion const is equal to del+ins const
1222 | dist = tm.CalcDiffOfSourceLine(sa, sb, mx)
1223 | }
1224 | // Even a small change, both lines will be shown, so add a 20% penalty on that.
1225 | return (dist*4+mx)/5 + 1 + posCost
1226 | }, func(iA int) int {
1227 | return mathp.MaxI(1, len(strings.TrimSpace(orgLines[iA+start]))*100)
1228 | }, func(iB int) int {
1229 | return mathp.MaxI(1, len(strings.TrimSpace(newLines[iB+start]))*100)
1230 | })
1231 |
1232 | cnt := 0
1233 |
1234 | for i, j := 0, 0; i < len(orgLines) || j < len(newLines); {
1235 | switch {
1236 | case i < start || i >= orgEnd && j >= newEnd:
1237 | // cut by offsetHeadTails
1238 | lo.outputSame(fmt.Sprintf(format, newLines[j]))
1239 | i++
1240 | j++
1241 | case j >= newEnd || i < orgEnd && matA[i-start] < 0:
1242 | lo.outputDel(fmt.Sprintf(format, orgLines[i]))
1243 | cnt++
1244 | i++
1245 | case i >= orgEnd || j < newEnd && matB[j-start] < 0:
1246 | lo.outputIns(fmt.Sprintf(format, newLines[j]))
1247 | cnt++
1248 | j++
1249 | default:
1250 | if strings.TrimSpace(orgLines[i]) != strings.TrimSpace(newLines[j]) {
1251 | lo.outputChange(fmt.Sprintf(format, orgLines[i]), fmt.Sprintf(format, newLines[j]))
1252 | cnt += 2
1253 | } else {
1254 | lo.outputSame(fmt.Sprintf(format, newLines[j]))
1255 | } // else
1256 | i++
1257 | j++
1258 | }
1259 | }
1260 | lo.end()
1261 | return cnt
1262 | }
1263 |
1264 | /*
1265 | Returns the number of operation lines.
1266 | */
1267 | func diffLines(orgLines, newLines []string, format string) int {
1268 | return diffLinesTo(orgLines, newLines, format, &lineOutput{})
1269 | }
1270 |
1271 | /*
1272 | Diff Package
1273 | */
1274 | func diffPackage(orgInfo, newInfo *fileInfo) {
1275 | orgName := orgInfo.f.Name.String()
1276 | newName := newInfo.f.Name.String()
1277 | if orgName != newName {
1278 | showDiffLine("package "+orgName, "package "+newName)
1279 | } // if
1280 | }
1281 |
1282 | /*
1283 | Diff Imports
1284 | */
1285 | func extractImports(info *fileInfo) []string {
1286 | imports := make([]string, 0, len(info.f.Imports))
1287 | for _, imp := range info.f.Imports {
1288 | imports = append(imports, imp.Path.Value)
1289 | } // for imp
1290 |
1291 | return imports
1292 | }
1293 |
1294 | func diffImports(orgInfo, newInfo *fileInfo) {
1295 | orgImports := extractImports(orgInfo)
1296 | newImports := extractImports(newInfo)
1297 |
1298 | diffLineSet(orgImports, newImports, `import %s`)
1299 | }
1300 |
1301 | /*
1302 | Diff Types
1303 | */
1304 | func diffTypes(orgInfo, newInfo *fileInfo) {
1305 | mat, _, matA, matB := greedyMatch(len(orgInfo.types.Parts), len(newInfo.types.Parts), func(iA, iB int) int {
1306 | return orgInfo.types.Parts[iA].calcDiff(newInfo.types.Parts[iB]) * 3 / 2
1307 | }, func(iA int) int {
1308 | return orgInfo.types.Parts[iA].Weight()
1309 | }, func(iB int) int {
1310 | return newInfo.types.Parts[iB].Weight()
1311 | })
1312 |
1313 | j0 := 0
1314 | for i := range matA {
1315 | j := matA[i]
1316 | if j < 0 {
1317 | showDelWholeLine(orgInfo.types.Parts[i].oneLine())
1318 | } else {
1319 | for ; j0 < j; j0++ {
1320 | if matB[j0] < 0 {
1321 | showInsWholeLine(newInfo.types.Parts[j0].oneLine())
1322 | }
1323 | }
1324 |
1325 | if mat[i][j] > 0 {
1326 | orgInfo.types.Parts[i].showDiff(newInfo.types.Parts[j])
1327 | } // if
1328 | } // else
1329 | } // for i
1330 |
1331 | for ; j0 < len(matB); j0++ {
1332 | if matB[j0] < 0 {
1333 | showInsWholeLine(newInfo.types.Parts[j0].oneLine())
1334 | }
1335 | }
1336 | }
1337 |
1338 | func diffVars(orgInfo, newInfo *fileInfo) {
1339 | mat, _, matA, matB := greedyMatch(len(orgInfo.vars.Parts), len(newInfo.vars.Parts), func(iA, iB int) int {
1340 | return orgInfo.vars.Parts[iA].calcDiff(newInfo.vars.Parts[iB]) * 3 / 2
1341 | }, func(iA int) int {
1342 | return orgInfo.vars.Parts[iA].Weight()
1343 | }, func(iB int) int {
1344 | return newInfo.vars.Parts[iB].Weight()
1345 | })
1346 |
1347 | j0 := 0
1348 | for i := range matA {
1349 | j := matA[i]
1350 | if j < 0 {
1351 | showDelLines(orgInfo.vars.Parts[i].sourceLines(""), 2)
1352 | // fmt.Println()
1353 | } else {
1354 | for ; j0 < j; j0++ {
1355 | if matB[j0] < 0 {
1356 | showInsLines(newInfo.vars.Parts[j0].sourceLines(""), 2)
1357 | } // if
1358 | }
1359 |
1360 | if mat[i][j] > 0 {
1361 | orgInfo.vars.Parts[i].showDiff(newInfo.vars.Parts[j])
1362 | fmt.Fprintln(gOut)
1363 | } // if
1364 | } // else
1365 | } // for i
1366 |
1367 | for ; j0 < len(matB); j0++ {
1368 | if matB[j0] < 0 {
1369 | showInsLines(newInfo.vars.Parts[j0].sourceLines(""), 2)
1370 | } // if
1371 | }
1372 | }
1373 |
1374 | func diffFuncs(orgInfo, newInfo *fileInfo) {
1375 | mat, _, matA, matB := greedyMatch(len(orgInfo.funcs.Parts), len(newInfo.funcs.Parts), func(iA, iB int) int {
1376 | return orgInfo.funcs.Parts[iA].calcDiff(newInfo.funcs.Parts[iB]) * 3 / 2
1377 | }, func(iA int) int {
1378 | return orgInfo.funcs.Parts[iA].Weight()
1379 | }, func(iB int) int {
1380 | return newInfo.funcs.Parts[iB].Weight()
1381 | })
1382 |
1383 | j0 := 0
1384 | for i := range matA {
1385 | j := matA[i]
1386 | if j < 0 {
1387 | showDelWholeLine(orgInfo.funcs.Parts[i].oneLine())
1388 | } else {
1389 | for ; j0 < j; j0++ {
1390 | if matB[j0] < 0 {
1391 | showInsWholeLine(newInfo.funcs.Parts[j0].oneLine())
1392 | }
1393 | }
1394 | if mat[i][j] > 0 {
1395 | orgInfo.funcs.Parts[i].showDiff(newInfo.funcs.Parts[j])
1396 | } // if
1397 | } // else
1398 | } // for i
1399 |
1400 | for ; j0 < len(matB); j0++ {
1401 | if matB[j0] < 0 {
1402 | showInsWholeLine(newInfo.funcs.Parts[j0].oneLine())
1403 | }
1404 | }
1405 | }
1406 |
1407 | func diff(orgInfo, newInfo *fileInfo) {
1408 | diffPackage(orgInfo, newInfo)
1409 | diffImports(orgInfo, newInfo)
1410 | diffTypes(orgInfo, newInfo)
1411 | diffVars(orgInfo, newInfo)
1412 | diffFuncs(orgInfo, newInfo)
1413 | }
1414 |
1415 | func readLines(fn villa.Path) []string {
1416 | bts, err := fn.ReadFile()
1417 |
1418 | if err != nil {
1419 | return nil
1420 | }
1421 |
1422 | return strings.Split(string(bts), "\n")
1423 | }
1424 |
1425 | // Options specifies options for processing files.
1426 | type Options struct {
1427 | NoColor bool // Turn off the colors when printing.
1428 | }
1429 |
1430 | var (
1431 | gOut io.Writer
1432 | gOptions Options
1433 | )
1434 |
1435 | // Exec prints the difference between two Go files to stdout. Not thread-safe.
1436 | func Exec(orgFn, newFn string, options Options) {
1437 | gOut = os.Stdout
1438 | gOptions = options
1439 |
1440 | fmtp.Printfln("Difference between %s and %s ...", orgFn, newFn)
1441 |
1442 | orgInfo, err1 := parse(orgFn, nil)
1443 | newInfo, err2 := parse(newFn, nil)
1444 |
1445 | if err1 != nil || err2 != nil {
1446 | orgLines := readLines(villa.Path(orgFn))
1447 | newLines := readLines(villa.Path(newFn))
1448 |
1449 | diffLines(orgLines, newLines, "%s")
1450 | return
1451 | }
1452 |
1453 | diff(orgInfo, newInfo)
1454 | }
1455 |
1456 | // ExecWriter prints the difference between two parsed Go files into w. Not thread-safe.
1457 | func ExecWriter(w io.Writer, fset0 *token.FileSet, file0 *ast.File, fset1 *token.FileSet, file1 *ast.File, options Options) {
1458 | gOut = w
1459 | gOptions = options
1460 |
1461 | orgInfo := &fileInfo{f: file0, fs: fset0}
1462 | orgInfo.collect()
1463 | newInfo := &fileInfo{f: file1, fs: fset1}
1464 | newInfo.collect()
1465 |
1466 | diff(orgInfo, newInfo)
1467 | }
1468 |
--------------------------------------------------------------------------------