[关闭]
@cloverwang 2016-05-26T11:54:38.000000Z 字数 7987 阅读 1851

Python实现C代码统计工具(二)

Python 代码统计


声明

本文将对《Python实现C代码统计工具(一)》中的C代码统计工具进行重构,以应对各种使用场景。

一. 问题提出

此前实现的C代码统计工具较为简陋,仅能遍历和分析当前目录及其子目录下的代码文件并输出统计报告。

在实际使用中,可能期望支持同时统计多个目录和(或)文件,并可指定遍历深度。当文件总数较少且文件路径较长时,期望支持显示基本文件名(basename)而非全路径;当文件总数较多时,期望支持仅显示总计信息。为求直观,还应显示代码注释率,即注释行/(注释行+有效代码行)。

二. 代码实现

存储统计结果的rawCountInfodetailCountInfo结构不变。当然,detailCountInfo也可定义为字典,以文件名为键。

CalcLines()函数也不变。注意,将该函数中的C注释符改为#和''',即可用于Python脚本行数信息统计。

CountFileLines()稍作修改,增加isShortName参数,真则使用基本文件名,假则使用全路径:

  1. def CountFileLines(filePath, isRawReport=True, isShortName=False):
  2. fileExt = os.path.splitext(filePath)
  3. if fileExt[1] != '.c' and fileExt[1] != '.h':
  4. return
  5. try:
  6. fileObj = open(filePath, 'r')
  7. except IOError:
  8. print 'Cannot open file (%s) for reading!', filePath
  9. else:
  10. lineList = fileObj.readlines()
  11. fileObj.close()
  12. if isRawReport:
  13. global rawCountInfo
  14. rawCountInfo[:-1] = [x+y for x,y in zip(rawCountInfo[:-1], CalcLines(lineList))]
  15. rawCountInfo[-1] += 1
  16. elif isShortName:
  17. detailCountInfo.append([os.path.basename(filePath), CalcLines(lineList)])
  18. else:
  19. detailCountInfo.append([filePath, CalcLines(lineList)])

ReportCounterInfo()内增加注释率统计:

  1. def ReportCounterInfo(isRawReport=True):
  2. def SafeDiv(dividend, divisor):
  3. if divisor: return float(dividend)/divisor
  4. elif dividend: return -1
  5. else: return 0
  6. print 'FileLines CodeLines CommentLines EmptyLines CommentPercent %s'\
  7. %(not isRawReport and 'FileName' or '')
  8. if isRawReport:
  9. print '%-11d%-11d%-14d%-12d%-16.2f<Total:%d Code Files>' %(rawCountInfo[0],\
  10. rawCountInfo[1], rawCountInfo[2], rawCountInfo[3], \
  11. SafeDiv(rawCountInfo[2], rawCountInfo[2]+rawCountInfo[1]), rawCountInfo[4])
  12. return
  13. total = [0, 0, 0, 0]
  14. #对detailCountInfo按第一列元素(文件名)排序,以提高输出可读性
  15. detailCountInfo.sort(key=lambda x:x[0])
  16. for item in detailCountInfo:
  17. print '%-11d%-11d%-14d%-12d%-16.2f%s' %(item[1][0], item[1][1], item[1][2], \
  18. item[1][3], SafeDiv(item[1][2], item[1][2]+item[1][1]), item[0])
  19. total[0] += item[1][0]; total[1] += item[1][1]
  20. total[2] += item[1][2]; total[3] += item[1][3]
  21. print '-' * 90 #输出90个负号(minus)或连字号(hyphen)
  22. print '%-11d%-11d%-14d%-12d%-16.2f<Total:%d Code Files>' \
  23. %(total[0], total[1], total[2], total[3], \
  24. SafeDiv(total[2], total[2]+total[1]), len(detailCountInfo))

为支持同时统计多个目录和(或)文件,使用ParseTargetList()解析目录-文件混合列表,将其元素分别存入目录和文件列表:

  1. def ParseTargetList(targetList):
  2. fileList, dirList = [], []
  3. if targetList == []:
  4. targetList.append(os.getcwd())
  5. for item in targetList:
  6. if os.path.isfile(item):
  7. fileList.append(os.path.abspath(item))
  8. elif os.path.isdir(item):
  9. dirList.append(os.path.abspath(item))
  10. else:
  11. print "'%s' is neither a file nor a directory!" %item
  12. return [fileList, dirList]

注意,只有basename的文件或目录默认为当前目录下的文件或子目录。此外,相对路径会被扩展为全路径。

CLineCounter()函数基于目录和文件列表进行统计,并增加isStay参数指定遍历深度(真则当前目录,假则递归其子目录):

  1. def CLineCounter(isStay=False, isRawReport=True, isShortName=False, targetList=[]):
  2. fileList, dirList = ParseTargetList(targetList)
  3. if fileList != []:
  4. CountFile(fileList, isRawReport, isShortName)
  5. elif dirList != []:
  6. CountDir(dirList, isStay, isRawReport, isShortName)
  7. else:
  8. pass
  9. def CountDir(dirList, isStay=False, isRawReport=True, isShortName=False):
  10. for dir in dirList:
  11. if isStay:
  12. for file in os.listdir(dir):
  13. CountFileLines(os.path.join(dir, file), isRawReport, isShortName)
  14. else:
  15. for root, dirs, files in os.walk(dir):
  16. for file in files:
  17. CountFileLines(os.path.join(root, file), isRawReport, isShortName)
  18. def CountFile(fileList, isRawReport=True, isShortName=False):
  19. for file in fileList:
  20. CountFileLines(file, isRawReport, isShortName)

然后,添加命令行解析处理。首选argparse模块解析命令行(Python2.7之后已废弃optparse模块),实现如下:

  1. import argparse
  2. def ParseCmdArgs(argv=sys.argv):
  3. parser = argparse.ArgumentParser(usage='%(prog)s [options] target',
  4. description='Count lines in C code files(c&h).')
  5. parser.add_argument('target', nargs='*',
  6. help='space-separated list of directories AND/OR files')
  7. parser.add_argument('-s', '--stay', action='store_true',
  8. help='do not walk down subdirectories')
  9. parser.add_argument('-d', '--detail', action='store_true',
  10. help='report counting result in detail')
  11. parser.add_argument('-b', '--basename', action='store_true',
  12. help='do not show file\'s full path')
  13. parser.add_argument('-v', '--version', action='version',
  14. version='%(prog)s 2.0 by xywang')
  15. args = parser.parse_args()
  16. return (args.stay, args.detail, args.basename, args.target)

argparse模块默认检查命令行参数sys.argv,也可直接解析字符串。例如,args = parser.parse_args('foo 1 -x 2'.split()),这在调试中很有用。

注意,argparse模块为Python2.7版本新增。在以前的版本中,可使用getopt模块解析命令行。如下所示:

  1. def Usage():
  2. '''
  3. usage: CLineCounter.py [options] target
  4. Count lines in C code files(c&h).
  5. positional arguments:
  6. target space-separated list of directories AND/OR files
  7. optional arguments:
  8. -h, --help show this help message and exit
  9. -s, --stay do not walk down subdirectories
  10. -d, --detail report counting result in detail
  11. -b, --basename do not show file's full path
  12. -v, --version show program's version number and exit'''
  13. print Usage.__doc__
  14. import getopt
  15. def ParseCmdArgs1(argv=sys.argv):
  16. try:
  17. opts, args = getopt.gnu_getopt(argv[1:], 'hsdbv', \
  18. ['help', 'stay', 'detail', 'basename', 'version'])
  19. except getopt.GetoptError, e:
  20. print str(e); Usage(); sys.exit()
  21. stay, detail, basename, target = False, False, False, []
  22. verbose = False
  23. for o, a in opts:
  24. if o in ("-h", "--help"):
  25. Usage(); sys.exit()
  26. elif o in ("-s", "--stay"):
  27. stay = True
  28. elif o in ("-d", "--detail"):
  29. detail = True
  30. elif o in ("-b", "--basename"):
  31. basename = True
  32. elif o in ("-v", "--version"):
  33. print '%s 2.0 by xywang' %os.path.basename(argv[0]); sys.exit()
  34. else:
  35. assert False, "unhandled option"
  36. return (stay, detail, basename, args)

其中,Usage()函数输出的帮助信息与argparse模块实现的-h选项输出相同。

建议将命令行处理放入另一文件内,以免python环境不支持argparse时导致代码统计本身不可用。

最后,调用以上函数统计代码并输出报告:

  1. if __name__ == '__main__':
  2. (stay, detail, basename, target) = ParseCmdArgs()
  3. CLineCounter(stay, not detail, basename, target)
  4. ReportCounterInfo(not detail)

三. 效果验证

为验证上节的代码实现,在lctest调试目录下新建subdir子目录。该目录下包含test.c文件。

在作者的Windows XP主机上,以不同的命令行参数运行CLineCounter.py,输出如下:

  1. E:\PyTest>tree /F lctest
  2. E:\PYTEST\LCTEST
  3. line.c
  4. test.c
  5. typec.JPG
  6. └─subdir
  7. test.c
  8. E:\PyTest>CLineCounter.py -v
  9. CLineCounter.py 2.0 by xywang
  10. E:\PyTest>CLineCounter.py -d lctest\line.c lctest\test.c
  11. FileLines CodeLines CommentLines EmptyLines CommentPercent FileName
  12. 33 19 15 4 0.44 lctest\line.c
  13. 44 34 3 7 0.08 lctest\test.c
  14. ------------------------------------------------------------------------------------------
  15. 77 53 18 11 0.25 <Total:2 Code Files>
  16. E:\PyTest>CLineCounter.py -s -d lctest\subdir\test.c lctest
  17. FileLines CodeLines CommentLines EmptyLines CommentPercent FileName
  18. 33 19 15 4 0.44 E:\PyTest\lctest\line.c
  19. 44 34 3 7 0.08 E:\PyTest\lctest\test.c
  20. 44 34 3 7 0.08 lctest\subdir\test.c
  21. ------------------------------------------------------------------------------------------
  22. 121 87 21 18 0.19 <Total:3 Code Files>
  23. E:\PyTest>CLineCounter.py -s -d lctest -b
  24. FileLines CodeLines CommentLines EmptyLines CommentPercent FileName
  25. 33 19 15 4 0.44 line.c
  26. 44 34 3 7 0.08 test.c
  27. ------------------------------------------------------------------------------------------
  28. 77 53 18 11 0.25 <Total:2 Code Files>
  29. E:\PyTest>CLineCounter.py -d lctest
  30. FileLines CodeLines CommentLines EmptyLines CommentPercent FileName
  31. 33 19 15 4 0.44 E:\PyTest\lctest\line.c
  32. 44 34 3 7 0.08 E:\PyTest\lctest\subdir\test.c
  33. 44 34 3 7 0.08 E:\PyTest\lctest\test.c
  34. ------------------------------------------------------------------------------------------
  35. 121 87 21 18 0.19 <Total:3 Code Files>
  36. E:\PyTest>CLineCounter.py lctest
  37. FileLines CodeLines CommentLines EmptyLines CommentPercent
  38. 121 87 21 18 0.19 <Total:3 Code Files>
  39. E:\PyTest\lctest\subdir>CLineCounter.py -d ..\test.c
  40. FileLines CodeLines CommentLines EmptyLines CommentPercent FileName
  41. 44 34 3 7 0.08 E:\PyTest\lctest\test.c
  42. ------------------------------------------------------------------------------------------
  43. 44 34 3 7 0.08 <Total:1 Code Files>

在作者的Linux Redhat主机(Python 2.4.3)上,运行CLineCounter.py后统计输出如下:

  1. [wangxiaoyuan_@localhost]$ python CLineCounter.py -d lctest
  2. FileLines CodeLines CommentLines EmptyLines CommentPercent FileName
  3. 33 19 15 4 0.44 /home/wangxiaoyuan_/lctest/line.c
  4. 44 34 3 7 0.08 /home/wangxiaoyuan_/lctest/subdir/test.c
  5. 44 34 3 7 0.08 /home/wangxiaoyuan_/lctest/test.c
  6. ------------------------------------------------------------------------------------------
  7. 121 87 21 18 0.19 <Total:3 Code Files>
  8. [wangxiaoyuan_@localhost subdir]$ python CLineCounter.py -d ../test.c
  9. FileLines CodeLines CommentLines EmptyLines CommentPercent FileName
  10. 44 34 3 7 0.08 /home/wangxiaoyuan_/lctest/test.c
  11. ------------------------------------------------------------------------------------------
  12. 44 34 3 7 0.08 <Total:1 Code Files>

经人工检验,统计信息正确。

此外,可以看出统计输出非常规整。若将其存入Windows文本文件(txt),可方便地转换为Excel文件。以Excel 2007为例,通过Office按钮打开待转换的文本文件,在【文本导入向导】中勾选"分隔符号",点击"完成",将文本文件载入Excel中。此后,即可在Excel界面里自定义修改并另存为Excel文件。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注