gopkg.in/alecthomas/gometalinter.v3@v3.0.0/_linters/src/github.com/mibk/dupl/output/html.go (about) 1 package output 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "regexp" 8 "sort" 9 10 "github.com/mibk/dupl/syntax" 11 ) 12 13 type HTMLPrinter struct { 14 iota int 15 *TextPrinter 16 } 17 18 func NewHTMLPrinter(w io.Writer, fr FileReader) *HTMLPrinter { 19 fmt.Fprint(w, `<!DOCTYPE html> 20 <meta charset="utf-8"/> 21 <title>Duplicates</title> 22 <style> 23 pre { 24 background-color: #FFD; 25 border: 1px solid #E2E2E2; 26 padding: 1ex; 27 } 28 </style> 29 `) 30 return &HTMLPrinter{ 31 TextPrinter: NewTextPrinter(w, fr), 32 } 33 } 34 35 func (p *HTMLPrinter) Print(dups [][]*syntax.Node) error { 36 p.iota++ 37 fmt.Fprintf(p.writer, "<h1>#%d found %d clones</h1>\n", p.iota, len(dups)) 38 39 clones := make([]clone, len(dups)) 40 for i, dup := range dups { 41 cnt := len(dup) 42 if cnt == 0 { 43 panic("zero length dup") 44 } 45 nstart := dup[0] 46 nend := dup[cnt-1] 47 48 file, err := p.freader.ReadFile(nstart.Filename) 49 if err != nil { 50 return err 51 } 52 53 lineStart, _ := blockLines(file, nstart.Pos, nend.End) 54 cl := clone{filename: nstart.Filename, lineStart: lineStart} 55 start := findLineBeg(file, nstart.Pos) 56 content := append(toWhitespace(file[start:nstart.Pos]), file[nstart.Pos:nend.End]...) 57 cl.fragment = deindent(content) 58 clones[i] = cl 59 } 60 61 sort.Sort(byNameAndLine(clones)) 62 for _, cl := range clones { 63 fmt.Fprintf(p.writer, "<h2>%s:%d</h2>\n<pre>%s</pre>\n", cl.filename, cl.lineStart, cl.fragment) 64 } 65 return nil 66 } 67 68 func (*HTMLPrinter) Finish() {} 69 70 func findLineBeg(file []byte, index int) int { 71 for i := index; i >= 0; i-- { 72 if file[i] == '\n' { 73 return i + 1 74 } 75 } 76 return 0 77 } 78 79 func toWhitespace(str []byte) []byte { 80 var out []byte 81 for _, c := range bytes.Runes(str) { 82 if c == '\t' { 83 out = append(out, '\t') 84 } else { 85 out = append(out, ' ') 86 } 87 } 88 return out 89 } 90 91 func deindent(block []byte) []byte { 92 const maxVal = 99 93 min := maxVal 94 re := regexp.MustCompile(`(^|\n)(\t*)\S`) 95 for _, line := range re.FindAllSubmatch(block, -1) { 96 indent := line[2] 97 if len(indent) < min { 98 min = len(indent) 99 } 100 } 101 if min == 0 || min == maxVal { 102 return block 103 } 104 block = block[min:] 105 Loop: 106 for i := 0; i < len(block); i++ { 107 if block[i] == '\n' && i != len(block)-1 { 108 for j := 0; j < min; j++ { 109 if block[i+j+1] != '\t' { 110 continue Loop 111 } 112 } 113 block = append(block[:i+1], block[i+1+min:]...) 114 } 115 } 116 return block 117 }