|
《用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,提供接口。 待续
|