|
《用flex寫的一個簡單代碼統計工具》一文中介紹了用flex工具寫得C代碼統計工具,但功能并不完整,統計子目錄不方便。近日在學習python,便有了用python實現代碼統計工具的想法。
整個程序分成兩個部分:工作部分和界面部分。工作部分就是執行統計工作。界面部分則負責接受分析用戶指令,調用工作部分進行統計和反饋結果。界面部分又分為窗口界面和控制臺界面,這些將在(2)中介紹,本文主要介紹工作部分。
統計工作只是簡單區分代碼行和注釋行,并不對文件進行詞法分析,因而較為簡單。比如C++代碼中,只是識別“/*”與“*/”之間和“//”后面的是注釋。而不去分析其他字符是否有意義,符合語法規則。
下面是counter.py的代碼:
# -*- coding: cp936 -*-
'''
按照某種語法規則如c,py,統計一個文件或者某個目錄下文件中代碼和注釋的行數
'''
import sys

def LineTypePy(line, info):
'''
根據py的語法規則,分析此行代碼屬性,使代碼還是注釋。
line:此行數據,info附加信息,在此無意義
返回值:1代碼,2注釋,3代碼和注釋,0空行
'''
state, size = 0, len(line)
line = line + '\n';
i = -1 # 從0開始
while i < size:
i += 1
if line[i] == '\n': # 換行符
break
elif line[i] == ' ' or line[i] == '\t': # 空字符
continue
elif line[i] == '#' or line[i] == ';': # 注釋起始符
state |= 2
else:
state |= 1

return state
def LineTypeC(line, info):
'''
根據C++的語法規則,分析此行代碼屬性,使代碼還是注釋。
line:此行數據,info附加信息,是否是塊注釋
返回值:1代碼,2注釋,3代碼和注釋,0空行
'''
state, size = 0, len(line)
line = line + '\n' #添加一個字符防止越界
i = -1
while i < size:
i += 1
if line[i] == '\n': # 換行符
break
elif line[i] == ' ' or line[i] == '\t': # 空字符
continue
elif line[i] == '/' and line[i+1] == '/':# 行注釋
state |= 2
i += 1
elif line[i] == '/' and line[i+1] == '*':# 塊注釋開始符
state |= 2
info[0] = 1
i += 1
elif line[i] == '*' and line[i+1] == '/':# 塊注釋結束符
state |= 2
info[0] = 0
i += 1
else:
if info[0] == 0:
state |= 1
else:
state |= 2
return state

def CounteFile(res, typefunc, filename):
'''
統計文件
res統計結果,typefunc行屬性判斷函數,filename文件名
'''
ret = [0,0,0,0,0]
info = [0]
for line in open(filename, 'rt'):
ret[typefunc(line, info)] += 1
ret[4] += 1 # 代碼總行數
res.append([filename,ret])

def CounteDir(res, typefunc, spath, modes, level):
'''
統計目錄下的文件
res統計結果,typefunc行屬性判斷函數,spath路徑名
modes文件后綴名,level統計幾層子目錄,-1為所有子目錄
'''
import os
import os.path
eles = os.listdir(spath)
dirs, files = [], []
#區分文件和目錄
for ele in eles:
ele = os.path.join(spath,ele)
if os.path.isdir(ele):
dirs.append(ele)
else:
files.append(ele)
# 統計文件
for f in files:
isokfile = True
if modes == []:
pass
else:
for m in modes:
if f[-len(m):] == m:
break
else:
isokfile = False
if isokfile:
CounteFile(res, typefunc, f)

# 判斷子目錄是否計算完全
if level == 0:
return

# 遞歸計算子目錄
for d in dirs:
CounteDir(res, typefunc, d, modes, level-1)


class CodeCounter:
'''
代碼統計器的類接口
'''
def __init__(self,codefiles=[],modes='.c,.h,.cpp',typefunc=LineTypeC,
codetype='c',level=1):
self.level = level
self.modes = modes
self.codefiles = codefiles
self.typefunc = typefunc
self.codetype = codetype

def Count(self, result):
'''
統計代碼
result為統計結果
'''
# 如果統計文件為空,默認統計當前目錄
if self.codefiles == []:
self.codefiles.append(['d', '.'])
for ele in self.codefiles:
if ele[0] == 'f': # 統計文件
CounteFile(result, self.typefunc, ele[1])
elif ele[0] == 'd': # 統計目錄
CounteDir(result, self.typefunc, ele[1],
self.modes.split(','), self.level)

def SetCodeType(self, codetype):
'''
設置統計代碼的類型
codetype: py表示Python語言,c表示c或c++
'''
if codetype == 'py':
self.typefunc = LineTypePy
else:
self.typefunc = LineTypeC
self.codetype = codetype

def AddCodeFiles(self, t, path):
'''
增加統計文件
t表示文件類型,f表示文件,d表示目錄
path表示對應的文件或目錄名字
'''
if t == 'f':
self.codefiles.append(['f', path])
elif t == 'd':
self.codefiles.append(['d', path])
def SetLevel(self, level):
'''
設置統計子目錄的層次
level表示統計幾層子目錄,0表示只統計當前目錄,-1表示所有目錄
'''
self.level = level

def SetModes(self, modes):
'''
設置統計文件的后綴名
modes 后綴名列表。例如[.c,.h]
'''
self.modes = modes
if __name__ == '__main__':
res = []
counter = CodeCounter()
counter.Count(res)

stat = [0,0,0,0,0]
for ele in res:
print ele[1][4], ele[1][1]+ele[1][3], ele[1][2]+ele[1][3], ele[1][0], ele[0]
for i in range(0, len(stat)):
stat[i] += ele[1][i]

print stat[4], stat[1]+stat[3], stat[2]+stat[3],stat[0],"Total"
這個程序實現了統計文件和目錄的功能。 LineTypePy()函數和LineTypeC()函數分別用py語法和c語法判斷該行字符是注釋還是代碼。 CounteFile()和CounterDir()分別可以統計文件和目錄下文件的代碼注釋。 CodeCounter類則是封裝了CounteFile和CounterDir,提供接口。 待續
|