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 }