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  }