用Golang实现一个统计代码行数的程序

在开发中,我们有时候想要统计一个文件夹下的所有代码的行数,但是有时候找这样的工具也挺麻烦的。那么就自己实现一个吧。

思路:
1、通过命令行参数获取要统计的代码所在根目录以及代码文件的后缀,比如Go语言是.go C++语言是.cpp,可以同时统计多种类型的文件

2、从根目录开始递归遍历文件:1.如果是目录,就递归遍历;2.如果是普通文件,根据后缀判断是否是要统计的代码的文件,如果是就统计并打印代码行数。

代码如下:

package main

import (
	"bufio"
	"fmt"
	flag "github.com/spf13/pflag"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"sync"
)

var (
	suffixLines   map[string]int
	totalLines    int
	rootPath      string
	defaultSuffix string = ".go"
	suffix        map[string]struct{}

	wg           sync.WaitGroup
	staticticsWg sync.WaitGroup
)

type Line struct {
	Filepath string
	Lines    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 := <-channel
		if !ok {
			return
		}
		totalLines += line.Lines
		suffixLines[path.Ext(line.Filepath)] += line.Lines
		fmt.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 = 0
	reader := 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 参数不需要:
在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值