gopkg.in/alecthomas/gometalinter.v3@v3.0.0/_linters/src/github.com/mibk/dupl/main.go (about)

     1  package main
     2  
     3  import (
     4  	"bufio"
     5  	"flag"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"log"
     9  	"os"
    10  	"path/filepath"
    11  	"sort"
    12  	"strings"
    13  
    14  	"github.com/mibk/dupl/job"
    15  	"github.com/mibk/dupl/output"
    16  	"github.com/mibk/dupl/syntax"
    17  )
    18  
    19  const defaultThreshold = 15
    20  
    21  var (
    22  	paths     = []string{"."}
    23  	vendor    = flag.Bool("vendor", false, "check files in vendor directory")
    24  	verbose   = flag.Bool("verbose", false, "explain what is being done")
    25  	threshold = flag.Int("threshold", defaultThreshold, "minimum token sequence as a clone")
    26  	files     = flag.Bool("files", false, "files names from stdin")
    27  
    28  	html     = flag.Bool("html", false, "html output")
    29  	plumbing = flag.Bool("plumbing", false, "plumbing output for consumption by scripts or tools")
    30  )
    31  
    32  const (
    33  	vendorDirPrefix = "vendor" + string(filepath.Separator)
    34  	vendorDirInPath = string(filepath.Separator) + "vendor" + string(filepath.Separator)
    35  )
    36  
    37  func init() {
    38  	flag.BoolVar(verbose, "v", false, "alias for -verbose")
    39  	flag.IntVar(threshold, "t", defaultThreshold, "alias for -threshold")
    40  }
    41  
    42  func usage() {
    43  	fmt.Fprintln(os.Stderr, `Usage of dupl:
    44    dupl [flags] [paths]
    45  
    46  Paths:
    47    If the given path is a file, dupl will use it regardless of
    48    the file extension. If it is a directory it will recursively
    49    search for *.go files in that directory.
    50  
    51    If no path is given dupl will recursively search for *.go
    52    files in the current directory.
    53  
    54  Flags:
    55    -files
    56      	read file names from stdin one at each line
    57    -html
    58      	output the results as HTML, including duplicate code fragments
    59    -plumbing
    60      	plumbing (easy-to-parse) output for consumption by scripts or tools
    61    -t, -threshold size
    62      	minimum token sequence size as a clone (default 15)
    63    -vendor
    64      	check files in vendor directory
    65    -v, -verbose
    66      	explain what is being done
    67  
    68  Examples:
    69    dupl -t 100
    70      	Search clones in the current directory of size at least
    71      	100 tokens.
    72    dupl $(find app/ -name '*_test.go')
    73      	Search for clones in tests in the app directory.
    74    find app/ -name '*_test.go' |dupl -files
    75      	The same as above.`)
    76  	os.Exit(2)
    77  }
    78  
    79  func main() {
    80  	flag.Usage = usage
    81  	flag.Parse()
    82  	if *html && *plumbing {
    83  		log.Fatal("you can have either plumbing or HTML output")
    84  	}
    85  	if flag.NArg() > 0 {
    86  		paths = flag.Args()
    87  	}
    88  
    89  	if *verbose {
    90  		log.Println("Building suffix tree")
    91  	}
    92  	schan := job.Parse(filesFeed())
    93  	t, data, done := job.BuildTree(schan)
    94  	<-done
    95  
    96  	// finish stream
    97  	t.Update(&syntax.Node{Type: -1})
    98  
    99  	if *verbose {
   100  		log.Println("Searching for clones")
   101  	}
   102  	mchan := t.FindDuplOver(*threshold)
   103  	duplChan := make(chan syntax.Match)
   104  	go func() {
   105  		for m := range mchan {
   106  			match := syntax.FindSyntaxUnits(*data, m, *threshold)
   107  			if len(match.Frags) > 0 {
   108  				duplChan <- match
   109  			}
   110  		}
   111  		close(duplChan)
   112  	}()
   113  	printDupls(duplChan)
   114  }
   115  
   116  func filesFeed() chan string {
   117  	if *files {
   118  		fchan := make(chan string)
   119  		go func() {
   120  			s := bufio.NewScanner(os.Stdin)
   121  			for s.Scan() {
   122  				f := s.Text()
   123  				if strings.HasPrefix(f, "./") {
   124  					f = f[2:]
   125  				}
   126  				fchan <- f
   127  			}
   128  			close(fchan)
   129  		}()
   130  		return fchan
   131  	}
   132  	return crawlPaths(paths)
   133  }
   134  
   135  func crawlPaths(paths []string) chan string {
   136  	fchan := make(chan string)
   137  	go func() {
   138  		for _, path := range paths {
   139  			info, err := os.Lstat(path)
   140  			if err != nil {
   141  				log.Fatal(err)
   142  			}
   143  			if !info.IsDir() {
   144  				fchan <- path
   145  				continue
   146  			}
   147  			filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
   148  				if !*vendor && (strings.HasPrefix(path, vendorDirPrefix) ||
   149  					strings.Contains(path, vendorDirInPath)) {
   150  					return nil
   151  				}
   152  				if !info.IsDir() && strings.HasSuffix(info.Name(), ".go") {
   153  					fchan <- path
   154  				}
   155  				return nil
   156  			})
   157  		}
   158  		close(fchan)
   159  	}()
   160  	return fchan
   161  }
   162  
   163  func printDupls(duplChan <-chan syntax.Match) {
   164  	groups := make(map[string][][]*syntax.Node)
   165  	for dupl := range duplChan {
   166  		groups[dupl.Hash] = append(groups[dupl.Hash], dupl.Frags...)
   167  	}
   168  	keys := make([]string, 0, len(groups))
   169  	for k := range groups {
   170  		keys = append(keys, k)
   171  	}
   172  	sort.Strings(keys)
   173  
   174  	p := getPrinter()
   175  	for _, k := range keys {
   176  		uniq := unique(groups[k])
   177  		if len(uniq) > 1 {
   178  			if err := p.Print(uniq); err != nil {
   179  				log.Fatal(err)
   180  			}
   181  		}
   182  	}
   183  	p.Finish()
   184  }
   185  
   186  func getPrinter() output.Printer {
   187  	var fr fileReader
   188  	if *html {
   189  		return output.NewHTMLPrinter(os.Stdout, fr)
   190  	} else if *plumbing {
   191  		return output.NewPlumbingPrinter(os.Stdout, fr)
   192  	}
   193  	return output.NewTextPrinter(os.Stdout, fr)
   194  }
   195  
   196  type fileReader struct{}
   197  
   198  func (fileReader) ReadFile(filename string) ([]byte, error) {
   199  	return ioutil.ReadFile(filename)
   200  }
   201  
   202  func unique(group [][]*syntax.Node) [][]*syntax.Node {
   203  	fileMap := make(map[string]map[int]struct{})
   204  
   205  	var newGroup [][]*syntax.Node
   206  	for _, seq := range group {
   207  		node := seq[0]
   208  		file, ok := fileMap[node.Filename]
   209  		if !ok {
   210  			file = make(map[int]struct{})
   211  			fileMap[node.Filename] = file
   212  		}
   213  		if _, ok := file[node.Pos]; !ok {
   214  			file[node.Pos] = struct{}{}
   215  			newGroup = append(newGroup, seq)
   216  		}
   217  	}
   218  	return newGroup
   219  }