在开发中,我们有时候想要统计一个文件夹下的所有代码的行数,但是有时候找这样的工具也挺麻烦的。那么就自己实现一个吧。
思路:
1、通过命令行参数获取要统计的代码所在根目录以及代码文件的后缀,比如Go语言是.go C++语言是.cpp,可以同时统计多种类型的文件
2、从根目录开始递归遍历文件:1.如果是目录,就递归遍历;2.如果是普通文件,根据后缀判断是否是要统计的代码的文件,如果是就统计并打印代码行数。
代码如下:
package mainimport ("bufio""fmt"flag "github.com/spf13/pflag""io/ioutil""os""path""path/filepath""sync"
)var (suffixLines map[string]inttotalLines introotPath stringdefaultSuffix string = ".go"suffix map[string]struct{}wg sync.WaitGroupstaticticsWg sync.WaitGroup
)type Line struct {Filepath stringLines int
}func main() {// 先获取要统计的代码所在的rootPath 以及代码文件名后缀err := getPath()if err != nil {fmt.Println("Get Current Path error:", err)return}// 如果不是目录,直接统计文件行数,然后返回ok := IsDir(rootPath)if !ok {line := getFileLine(rootPath)fmt.Printf("%s: %d\n", rootPath, line)return}// 统计目录中所以指定后缀文件的行数staticticsWg.Add(1)staticsChan := make(chan *Line, 128)go statictics(staticsChan)// 遍历目录下的文件wg.Add(1)pathSeparator := string(os.PathSeparator)go staticticsAllFile(pathSeparator, rootPath, staticsChan)// wait返回表示目录已经遍历完毕wg.Wait()// 通知统计协程退出close(staticsChan)staticticsWg.Wait()for name, lines := range suffixLines {fmt.Printf("File suffix %s total line: %d\n", name, lines)}fmt.Println("Total Line: ", totalLines)
}// 给rootPath以及后缀赋值
func getPath() error {dafaultFile, err := os.Getwd()if err != nil {return err}filename := flag.String("f", dafaultFile, "-f filePath")sfxs := flag.StringSlice("s", []string{defaultSuffix}, "-s suffix1,...,suffixn")flag.Parse()// 如果是否是相对路径if (*filename)[0] != '.' {rootPath = *filename} else {rootPath, _ = filepath.Abs(*filename)}suffix = make(map[string]struct{}, len(*sfxs))for i := 0; i < len(*sfxs); i++ {key := (*sfxs)[i]suffix[key] = struct{}{}}fmt.Printf(`Statistics target Root Path: "%s"`, rootPath)fmt.Println()fmt.Println(`File Suffix: `, *sfxs)fmt.Println()fmt.Println()fmt.Println("--------------------------------------------------------------------------------")fmt.Println()return nil
}// 打印并统计代码总行数
func statictics(channel chan *Line) {defer staticticsWg.Done()suffixLines = make(map[string]int, len(suffix))for {line, ok := <-channelif !ok {return}totalLines += line.LinessuffixLines[path.Ext(line.Filepath)] += line.Linesfmt.Printf("%s: %d\n", line.Filepath, line.Lines)}}// 遍历所有文件,如果是普通文件就统计行数,如果是目录,递归遍历目录
func staticticsAllFile(pathSeparator string, fileDir string, channel chan *Line) {files, _ := ioutil.ReadDir(fileDir)for _, file := range files {filePath := fileDir + pathSeparator + file.Name()if file.IsDir() {if (file.Name())[0] == '.' { // 忽略隐藏文件continue}wg.Add(1)go staticticsAllFile(pathSeparator, filePath, channel)} else if _, ok := suffix[path.Ext(file.Name())]; ok {line := getFileLine(filePath)channel <- &Line{Filepath: filePath,Lines: line,}}}wg.Done()
}// 获取文件的行数
func getFileLine(filename string) (line int) {file, err := os.Open(filename)if err != nil {fmt.Println("getFileLine error, file name:", filename, " error:", err)return 0}defer file.Close()line = 0reader := bufio.NewReader(file)for {_, isPrefix, err := reader.ReadLine()if err != nil {break}if !isPrefix {line++}}return
}// 判断所给路径是否为文件夹
func IsDir(path string) bool {s, err := os.Stat(path)if err != nil {return false}return s.IsDir()
}
编译后,可以将其位置加入到环境变量。我使用的是git bash,所以我就将生成的可执行文件放在了git bash的/usr/bin文件下,这样就可以直接在终端中调用:
在添加 -f 参数时,要用引号将路径引起来,否则因为路径中有 \ 导致获取的路径会有问题,-s 参数不需要: