Python實現C代碼統計工具(一)


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代碼統計工具較為簡陋,后續將重構代碼並添加控制選項。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM