gopkg.in/alecthomas/gometalinter.v3@v3.0.0/_linters/src/github.com/mibk/dupl/output/text.go (about) 1 package output 2 3 import ( 4 "fmt" 5 "io" 6 "sort" 7 8 "github.com/mibk/dupl/syntax" 9 ) 10 11 type FileReader interface { 12 ReadFile(filename string) ([]byte, error) 13 } 14 15 type Printer interface { 16 Print(dups [][]*syntax.Node) error 17 Finish() 18 } 19 20 type TextPrinter struct { 21 writer io.Writer 22 freader FileReader 23 cnt int 24 } 25 26 func NewTextPrinter(w io.Writer, fr FileReader) *TextPrinter { 27 return &TextPrinter{ 28 writer: w, 29 freader: fr, 30 } 31 } 32 33 func (p *TextPrinter) Print(dups [][]*syntax.Node) error { 34 p.cnt++ 35 fmt.Fprintf(p.writer, "found %d clones:\n", len(dups)) 36 clones, err := p.prepareClonesInfo(dups) 37 if err != nil { 38 return err 39 } 40 sort.Sort(byNameAndLine(clones)) 41 for _, cl := range clones { 42 fmt.Fprintf(p.writer, " %s:%d,%d\n", cl.filename, cl.lineStart, cl.lineEnd) 43 } 44 return nil 45 } 46 47 func (p *TextPrinter) prepareClonesInfo(dups [][]*syntax.Node) ([]clone, error) { 48 clones := make([]clone, len(dups)) 49 for i, dup := range dups { 50 cnt := len(dup) 51 if cnt == 0 { 52 panic("zero length dup") 53 } 54 nstart := dup[0] 55 nend := dup[cnt-1] 56 57 file, err := p.freader.ReadFile(nstart.Filename) 58 if err != nil { 59 return nil, err 60 } 61 62 cl := clone{filename: nstart.Filename} 63 cl.lineStart, cl.lineEnd = blockLines(file, nstart.Pos, nend.End) 64 clones[i] = cl 65 } 66 return clones, nil 67 } 68 69 func (p *TextPrinter) Finish() { 70 fmt.Fprintf(p.writer, "\nFound total %d clone groups.\n", p.cnt) 71 } 72 73 func blockLines(file []byte, from, to int) (int, int) { 74 line := 1 75 lineStart, lineEnd := 0, 0 76 for offset, b := range file { 77 if b == '\n' { 78 line++ 79 } 80 if offset == from { 81 lineStart = line 82 } 83 if offset == to-1 { 84 lineEnd = line 85 break 86 } 87 } 88 return lineStart, lineEnd 89 } 90 91 type clone struct { 92 filename string 93 lineStart int 94 lineEnd int 95 fragment []byte 96 } 97 98 type byNameAndLine []clone 99 100 func (c byNameAndLine) Len() int { return len(c) } 101 102 func (c byNameAndLine) Swap(i, j int) { c[i], c[j] = c[j], c[i] } 103 104 func (c byNameAndLine) Less(i, j int) bool { 105 if c[i].filename == c[j].filename { 106 return c[i].lineStart < c[j].lineStart 107 } 108 return c[i].filename < c[j].filename 109 }