Export HGVS.
[lightning.git] / hgvs / diff.go
1 package hgvs
2
3 import (
4         "fmt"
5         "time"
6
7         "github.com/sergi/go-diff/diffmatchpatch"
8 )
9
10 type Variant struct {
11         Position int
12         Ref      string
13         New      string
14 }
15
16 func (v *Variant) String() string {
17         switch {
18         case len(v.New) == 0 && len(v.Ref) == 0:
19                 return fmt.Sprintf("%d=", v.Position)
20         case len(v.New) == 0 && len(v.Ref) == 1:
21                 return fmt.Sprintf("%ddel", v.Position)
22         case len(v.New) == 0:
23                 return fmt.Sprintf("%d_%ddel", v.Position, v.Position+len(v.Ref)-1)
24         case len(v.Ref) == 1 && len(v.New) == 1:
25                 return fmt.Sprintf("%d%s>%s", v.Position, v.Ref, v.New)
26         case len(v.Ref) == 0:
27                 return fmt.Sprintf("%d_%dins%s", v.Position-1, v.Position, v.New)
28         case len(v.Ref) == 1 && len(v.New) > 0:
29                 return fmt.Sprintf("%ddelins%s", v.Position, v.New)
30         default:
31                 return fmt.Sprintf("%d_%ddelins%s", v.Position, v.Position+len(v.Ref)-1, v.New)
32         }
33 }
34
35 func Diff(a, b string, timeout time.Duration) ([]Variant, bool) {
36         dmp := diffmatchpatch.New()
37         var deadline time.Time
38         if timeout > 0 {
39                 deadline = time.Now().Add(timeout)
40         }
41         diffs := dmp.DiffBisect(a, b, deadline)
42         timedOut := false
43         if timeout > 0 && time.Now().After(deadline) {
44                 timedOut = true
45         }
46         diffs = cleanup(dmp.DiffCleanupEfficiency(diffs))
47         pos := 1
48         var variants []Variant
49         for i := 0; i < len(diffs); i++ {
50                 switch diffs[i].Type {
51                 case diffmatchpatch.DiffEqual:
52                         pos += len(diffs[i].Text)
53                 case diffmatchpatch.DiffDelete:
54                         if i+1 < len(diffs) && diffs[i+1].Type == diffmatchpatch.DiffInsert {
55                                 // deletion followed by insertion
56                                 variants = append(variants, Variant{Position: pos, Ref: diffs[i].Text, New: diffs[i+1].Text})
57                                 pos += len(diffs[i].Text)
58                                 i++
59                         } else {
60                                 variants = append(variants, Variant{Position: pos, Ref: diffs[i].Text})
61                                 pos += len(diffs[i].Text)
62                         }
63                 case diffmatchpatch.DiffInsert:
64                         if i+1 < len(diffs) && diffs[i+1].Type == diffmatchpatch.DiffDelete {
65                                 // insertion followed by deletion
66                                 variants = append(variants, Variant{Position: pos, Ref: diffs[i+1].Text, New: diffs[i].Text})
67                                 pos += len(diffs[i+1].Text)
68                                 i++
69                         } else {
70                                 variants = append(variants, Variant{Position: pos, New: diffs[i].Text})
71                         }
72                 }
73         }
74         return variants, timedOut
75 }
76
77 func cleanup(in []diffmatchpatch.Diff) (out []diffmatchpatch.Diff) {
78         for i := 0; i < len(in); i++ {
79                 d := in[i]
80                 for i < len(in)-1 && in[i].Type == in[i+1].Type {
81                         d.Text += in[i+1].Text
82                         i++
83                 }
84                 out = append(out, d)
85         }
86         return
87 }