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  }