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

标签: Python 代码统计


声明

本文将基于Python2.7脚本语言,实现一个简易的C代码统计工具。

本文同时也发布于作业部落,视觉效果略有不同。

一. 问题提出

代码规模较大时,不易对其做出准确的评估。通过代码统计工具,可自动分析和统计软件项目中的文件行数、有效代码行数、注释行数及空白行数,提供准确而直观的代码量报告。基于这种定量报告,可以有针对性地提升代码质量。例如,分拆组合以消除巨型文件,对注释率过低的文件增加注释信息,等等。

为简单起见,本文仅仅统计C语言代码,即后缀名为ch的文件。并且,约定以下几条统计原则:

  1. 当代码和注释位于同一行时,代码行数和注释行数均会加1。考虑到行注释的普遍性,因此代码行数、注释行数和空白行数的总和通常大于文件总行数。
  2. 块注释中间和空白行计入空白行数。
  3. "#if 0...endif"块视为代码行。

二. 代码实现

首先,定义两个存储统计结果的列表:

rawCountInfo = [0, 0, 0, 0, 0]
detailCountInfo = []

其中,rawCountInfo存储粗略的文件总行数信息,列表元素依次为文件行、代码行、注释行和空白行的总数,以及文件数目。detailCountInfo存储详细的统计信息,包括单个文件的行数信息和文件名,以及所有文件的行数总和。这是一个多维列表,存储内容示例如下:

[['line.c', [33, 19, 15, 4]], ['test.c', [44, 34, 3, 7]]]

以下将给出具体的实现代码。为避免大段粘贴代码,以函数为片段简要描述。

def CalcLines(lineList):
lineNo, totalLines = 0, len(lineList)
codeLines, commentLines, emptyLines = 0, 0, 0
while lineNo < len(lineList):
if lineList[lineNo].isspace(): #空行
emptyLines += 1; lineNo += 1; continue regMatch = re.match('^([^/]*)/(/|\*)+(.*)$', lineList[lineNo].strip())
if regMatch != None: #注释行
commentLines += 1 #代码&注释混合行
if regMatch.group(1) != '':
codeLines += 1
elif regMatch.group(2) == '*' \
and re.match('^.*\*/.+$', regMatch.group(3)) != None:
codeLines += 1 #行注释或单行块注释
if '/*' not in lineList[lineNo] or '*/' in lineList[lineNo]:
lineNo += 1; continue #跨行块注释
lineNo += 1
while '*/' not in lineList[lineNo]:
if lineList[lineNo].isspace():
emptyLines += 1
else:
commentLines += 1
lineNo = lineNo + 1; continue
commentLines += 1 #'*/'所在行
else: #代码行
codeLines += 1 lineNo += 1; continue return [totalLines, codeLines, commentLines, emptyLines]

CalcLines()函数基于C语法判断文件行属性,按代码、注释或空行分别统计。参数lineList由readlines()读取文件得到,读到的每行末尾均含换行符。strip()可剔除字符串首尾的空白字符(包括换行符)。当通过print输出文件行内容时,可采用如下两种写法剔除多余的换行符:

print '%s' %(line), #注意行末逗号
print '%s' %(line.strip())

行尾包含换行符的问题也存在于readline()和read()调用,包括for line in file的语法。对于read()调用,可在读取文件后split('\n')得到不带换行符的行列表。注意,调用readlines()和read()时,会读入整个文件,文件位置指示器将指向文件尾端。此后再调用时,必须先通过file.seek(0)方法返回文件开头,否则读取的内容为空。

def CountFileLines(filePath, isRaw=True):
fileExt = os.path.splitext(filePath)
if fileExt[1] != '.c' and fileExt[1] != '.h': #识别C文件
return try:
fileObj = open(filePath, 'r')
except IOError:
print 'Cannot open file (%s) for reading!', filePath
else:
lineList = fileObj.readlines()
fileObj.close() if isRaw:
global rawCountInfo
rawCountInfo[:-1] = [x+y for x,y in zip(rawCountInfo[:-1], CalcLines(lineList))]
rawCountInfo[-1] += 1
else:
detailCountInfo.append([filePath, CalcLines(lineList)])

CountFileLines()统计单个文件的行数信息,其参数isRaw指示统计报告是粗略还是详细的。对于详细报告,需要向detailCountInfo不断附加单个文件的统计结果;而对于详细报告,只需要保证rawCountInfo的元素值正确累加即可。

def ReportCounterInfo(isRaw=True):
#Python2.5版本引入条件表达式(if-else)实现三目运算符,低版本可采用and-or的短路特性
#print 'FileLines CodeLines CommentLines EmptyLines %s' %('' if isRaw else 'FileName')
print 'FileLines CodeLines CommentLines EmptyLines %s' %(not isRaw and 'FileName' or '') if isRaw:
print '%-11d%-11d%-14d%-12d<Total:%d Files>' %(rawCountInfo[0], rawCountInfo[1],\
rawCountInfo[2], rawCountInfo[3], rawCountInfo[4])
return total = [0, 0, 0, 0]
#对detailCountInfo按第一列元素(文件名)排序,以提高输出可读性
#import operator; detailCountInfo.sort(key=operator.itemgetter(0))
detailCountInfo.sort(key=lambda x:x[0]) #简洁灵活,但不如operator高效
for item in detailCountInfo:
print '%-11d%-11d%-14d%-12d%s' %(item[1][0], item[1][1], item[1][2], item[1][3], item[0])
total[0] += item[1][0]; total[1] += item[1][1]
total[2] += item[1][2]; total[3] += item[1][3]
print '%-11d%-11d%-14d%-12d<Total:%d Files>' %(total[0], total[1], total[2], total[3], len(detailCountInfo))

ReportCounterInfo()输出统计报告。注意,详细报告输出前,先按文件名排序。

def CountDirLines(dirPath, isRawReport=True):
if not os.path.exists(dirPath):
print dirPath + ' is non-existent!'
return if not os.path.isdir(dirPath):
print dirPath + ' is not a directory!'
return for root, dirs, files in os.walk(dirPath):
for file in files:
CountFileLines(os.path.join(root, file), isRawReport) ReportCounterInfo(isRawReport)

CountDirLines()统计当前目录及其子目录下所有文件的行数信息,并输出统计报告。注意,os.walk()不一定按字母顺序遍历文件。在作者的Windows XP主机上,os.walk()按文件名顺序遍历;而在Linux Redhat主机上,os.walk()以"乱序"遍历。

最后,添加简单的命令行处理:

if __name__ == '__main__':
DIR_PATH = r'E:\PyTest\lctest'
if len(sys.argv) == 1: #脚本名
CountDirLines(DIR_PATH)
sys.exit() if len(sys.argv) >= 2:
if int(sys.argv[1]):
CountDirLines(DIR_PATH, False)
else:
CountDirLines(DIR_PATH)
sys.exit()

三. 效果验证

为验证上节的代码实现,建立lctest调试目录。该目录下包含line.c及和《为C函数自动添加跟踪语句》一文中的test.c文件。其中,line.c内容如下:

#include <stdio.h>
/* {{{ comment */ /***********
Multiline
Comment
***********/
int test(int a/*comment*/, int b)
{
int a2; int b2; //comment
a2 = 1;
b2 = 2;
} /* {{{ test3 */
int test3(int a,
int b) /*test2 has been deleted,
so this is test3. */
{int a3 = 1; int b3 = 2;
if(a3)
{/*comment*/
a3 = 0;
}
//comment
b3 = 0;
}
/* }}} */ //comment //comment
/*FALSE*/ #if M_DEFINED
#error Defination!
#endif

以不同的命令行参数运行CLineCounter.py,输出如下:

E:\PyTest>CLineCounter.py
FileLines CodeLines CommentLines EmptyLines
77 53 18 11 <Total:2 Files> E:\PyTest>CLineCounter.py 0
FileLines CodeLines CommentLines EmptyLines
77 53 18 11 <Total:2 Files> E:\PyTest>CLineCounter.py 1
FileLines CodeLines CommentLines EmptyLines FileName
33 19 15 4 E:\PyTest\lctest\line.c
44 34 3 7 E:\PyTest\lctest\test.c
77 53 18 11 <Total:2 Files>

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

接着,在实际工程中运行python CLineCounter.py 1,截取部分运行输出如下:

[wangxiaoyuan_@localhost ~]$ python CLineCounter.py 1
FileLines CodeLines CommentLines EmptyLines FileName
99 21 58 24 /sdb1/wangxiaoyuan/include/Dsl_Alloc.h
120 79 28 24 /sdb1/wangxiaoyuan/include/Dsl_Backtrace.h
... ... ... ... ... ... ... ...
139 89 24 26 /sdb1/wangxiaoyuan/source/Dsl_Tbl_Map.c
617 481 64 78 /sdb1/wangxiaoyuan/source/Dsl_Test_Suite.c
797 569 169 82 /sdb1/wangxiaoyuan/source/xDSL_Common.c
15450 10437 3250 2538 <Total:40 Files>

四. 后记

本文所实现的C代码统计工具较为简陋,后续将重构代码并添加控制选项。

Python实现C代码统计工具(一)的更多相关文章

  1. Python实现C代码统计工具(四)

    目录 Python实现C代码统计工具(四) 标签: Python 计时 持久化 声明 运行测试环境 一. 自定义计时函数 1.1 整个程序计时 1.2 代码片段计时 1.3 单条语句计时 二. 性能优 ...

  2. Python实现C代码统计工具(三)

    目录 Python实现C代码统计工具(三) 声明 一. 性能分析 1.1 分析单条语句 1.2 分析代码片段 1.3 分析整个模块 二. 制作exe Python实现C代码统计工具(三) 标签: Py ...

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

    目录 Python实现C代码统计工具(二) 声明 一. 问题提出 二. 代码实现 三. 效果验证 Python实现C代码统计工具(二) 标签: Python 代码统计 声明 本文将对<Pytho ...

  4. Python代码统计工具

    目录 Python代码统计工具 声明 一. 问题提出 二. 代码实现 三. 效果验证 Python代码统计工具 标签: Python 代码统计 声明 本文将对<Python实现C代码统计工具(一 ...

  5. python 练习(一)代码统计工具的实现

    最近部门成立了一个python学习小组,旨在让大家在做项目中开始成长起来,于是老大就给布置了第一个小任务:代码统计工具,具体的需求如下: 需求: . 能够统计指定目录下C++程序的代码行数. . C+ ...

  6. Python实现代码统计工具——终极加速篇

    Python实现代码统计工具--终极加速篇 声明 本文对于先前系列文章中实现的C/Python代码统计工具(CPLineCounter),通过C扩展接口重写核心算法加以优化,并与网上常见的统计工具做对 ...

  7. C++代码统计工具

    自己前几天写的C++代码统计工具. http://pan.baidu.com/s/17SnnH

  8. svn代码统计工具的金额

    StatSVN介绍 StatSVN是Java写开源统计程序,从statCVS从移植.从能Subversion版本号来获取信息库,该项目开发的叙述性说明,然后生成各种表格和图表.例:时间线.针对每一个开 ...

  9. 代码统计工具-cloc

    官网地址:http://cloc.sourceforge.net/ https://sourceforge.NET/projects/cloc/files/ 下载得到cloc-1.64.exe Clo ...

随机推荐

  1. cached-query 将缓存和查询数据库高速连接起来的轻类库

    介绍 我们经常有这种需求:当我们把memcached增加到项目后我还还要写一个 cacheUtils 或者 cacheManager 之类的类来操作memcached. 而且一般的操作不外乎是这种操作 ...

  2. look-into-oracle-redo

    https://fritshoogland.wordpress.com/2018/02/05/a-look-into-oracle-redo-part-2-the-discovery-of-the-k ...

  3. perf 安装到分析

    https://yq.aliyun.com/articles/65255 https://yq.aliyun.com/articles/65257?spm=5176.100239.blogcont65 ...

  4. Revit API创建房间

    start [Transaction(TransactionMode.Manual)] [Regeneration(RegenerationOption.Manual)] );             ...

  5. Servlet第五课:Cookie的使用

    目标规划: 通过这一节课,我们能够懂得怎样使用Cookie.以及怎样获取Cookie中的内容. 插播广告:博客之星评选.点击投我一票.谢谢. Cookie的具体概述. 1. Cookie 是保存在cl ...

  6. Spark2.2+ES6.4.2(三十二):ES API之index的create/update/delete/open/close(创建index时设置setting,并创建index后根据avro模板动态设置index的mapping)

    要想通过ES API对es的操作,必须获取到TransportClient对象,让后根据TransportClient获取到IndicesAdminClient对象后,方可以根据IndicesAdmi ...

  7. 【FFmpeg】ffplay播放rtsp视频流花屏问题 (转)

    问题描述:ffplay播放rtsp视频流时,播放过程中随机出现花屏现象. 基本流程学习:阅读ffplay源码,熟悉其播放rtsp视频流的基本流程. 在ffplay源码阅读和分析的基础上,画出了其播放r ...

  8. 最佳实战Docker持续集成图文详解

    最佳实战Docker持续集成图文详解 这是一种真正的容器级的实现,这个带来的好处,不仅仅是效率的提升,更是一种变革:开发人员第一次真正为自己的代码负责——终于可以跳过运维和测试部门,自主维护运行环境( ...

  9. 【PMP】项目风险管理~重点知识

    1.什么是风险,项目的风险从哪里来? 开展项目,不仅要面对各种制约因素和假设条件,而且还要应对可能相互冲突和不断变化的相关方期望.组织应该有目的的以可控方式去冒项目风险,以便平衡风险和回报,并创造价值 ...

  10. ASP.NET MVC ViewBag/ViewData/TempData区别

    ViewBag/ViewData public dynamic ViewBag { get; } public ViewDataDictionary ViewData { get; set; } Vi ...