最近组内准备整顿代码,领导让我写个简单的python脚本分析代码中注释的行数和无效注释。因为这个需求不是很急,所以我想把简单的事情做复杂点。于是就写了一个用VC内嵌Python,并通过模拟按键和发消息去控制其他软件的工具。
作为一个程序员,总是希望自己写的东西别人能用上且喜欢去用。因为python更新很快,往往两个版本中存在一些语法或者实现的改动。其实最讨厌的就是语法变动了,像2.X的print到3.X时就是print()了。我本意是希望做个大家都能用上的,于是我决定用VC内嵌一个python引擎去完成相关工作。
首先说一下环境准备,这个网上一大堆,主要是下载并配置好pythonXX_d.lib、pythonXX.lib、python.dll等几个文件。然后一切就可以开始了。
python分析的过程也很简单,AnalyzeFloder.py是对一个文件夹进行遍历,并做相关统计;AnalyzeFileComment.py是分析每个文件并且帅选出注释;CommentUseOrNo.py是分析每个注释,看看是否是无用注释;AnalyzeReport.py做相关统计和序列化、反序列化工作。AFCUI.py 是对上述功能的封装。这里说一下对无用注释的定义,我们定义如果注释内容为符合VC编码风格的代码即认为是无效代码。在现实开发中,特别是人员流动性特别大的公司,新人做需求时往往不敢去碰那些没有任何注释的代码,于是他们就直接把这些似乎不用了的代码注释掉。这样长久下来,“无用注释”的量就会很大了。
- <span style="white-space: pre;"> </span># a(x)
- # a (x)
- # a (x)
- # x = x
- # x x
- # x x,y y
- self.InitNoUseRe(r'[a-zA-Z](\w)*([ \t])*\([\w\s]*|([\w\s]*(,[\w\s]*))\)')
- # }
- # }a;
- self.InitNoUseRe(r'\}[ \t\r\n]*([a-zA-z]+;)?[ \t\r\n]*)
- self.InitNoUseRe(r'^([ \r\t\n]*)({[ \r\t\n]*))
- self.InitNoUseRe(r'[a-zA-Z]+[ \t]*[=><]{1,2}([ \t]*[a-z0-9A-Z]+[ \t]*))
- self.InitNoUseRe(r'#define[ \t][a-zA-Z]*([ \r\t\n]*))
- self.InitNoUseRe(r'return[ \t][a-zA-Z]*(;[ \r\t\n]*))
- self.InitNoUseRe(r'[a-zA-Z](\w)*\+\+(;[ \r\t\n]*))
- self.InitNoUseRe(r'[a-zA-Z](\w)*--(;[ \r\t\n]*))
- self.InitNoUseRe(r'^([ \r\t\n]*continue)(;[ \r\t\n]*))
- self.InitNoUseRe(r'^([ \r\t\n]*break)(;[ \r\t\n]*))
- self.InitNoUseRe(r'case (\w)+(:[ \r\t\n]*))
- self.InitNoUseRe(r'^([ \r\t\n]*[a-zA-Z](\w)*[ \r\t\n]+[_a-zA-Z](\w)*)((;|\{|(\[\w*\]);)[ \r\t\n]*))
- self.InitNoUseRe(r'^([ \r\t\n]*))
- self.InitNoUseRe(r'^([ \r\t\n]*(struct|class))([ \r\t\n]* [a-zA-Z](\w)*)((\{|;)?[ \r\t\n]*))
其次,对于分析出来的数据,我将其序列化到一个文件中,这样以后要是加载之前分析过的工程,就可以直接反序列化那个文件就行了,不用重复分析。
- def GetFileInfoMapFromDump(self,filepath):
- with open(filepath, 'rb') as f:
- data = pickle.load(f)
- return data
- def DumpFileInfoMap(self,filemap,filepath):
- with open(filepath, 'wb') as f:
- pickle.dump(filemap, f, pickle.HIGHEST_PROTOCOL)
- def GenerateVcDataFile(self,fileinfomap,vcdatefilepath):
- vcdatafileobj = open( vcdatefilepath, 'w+')
- for key in fileinfomap:
- strvcdata = None
- strvcdata = key
- index = 0
- for value in fileinfomap[key]:
- if index == 3:
- for linenum in value:
- strvcdata = strvcdata + '|' + str(linenum)
- else:
- strvcdata = strvcdata + '|' + str(value)
- index = index + 1
- strvcdata = strvcdata + '|\n'
- vcdatafileobj.write(strvcdata)
- if None != vcdatafileobj:
- vcdatafileobj.close()
- return vcdatefilepath
python的大致流程就是如此,VC要是想执行Python的脚本,就如下了
- CString CManageTask::AnanlyzeCodeFloder( const CString& cstrCodeFloderPath,
- const CString& cstrProjectFloderPath )
- {
- CString cstrVcDataPath = L"";
- Py_Initialize();
- // 进行初始化python
- PyObject* pModule = NULL;
- // 函数对象
- PyObject* pFunc = NULL;
- do
- {
- // 找到模块对象指针
- pModule = PyImport_ImportModule("AFCUI");
- if ( NULL == pModule )
- {
- ASSERT(pModule);
- break;
- }
- // 从模块对象中获取指定函数对象指针
- pFunc = PyObject_GetAttrString( pModule, "AFloderFunc" );
- if ( NULL == pFunc )
- {
- ASSERT(pFunc);
- break;
- }
- std::string strProjectPath = ConvertCstrint2String(cstrProjectFloderPath);
- std::string strCodeFloderPath = ConvertCstrint2String(cstrCodeFloderPath);
- // 构造参数
- PyObject* pargslist = Py_BuildValue("(s,s)", strCodeFloderPath.c_str(), strProjectPath.c_str());
- char* pchvcdatapath = NULL;
- // 调用函数
- PyObject* pstr = PyEval_CallObject(pFunc, pargslist);
- if ( NULL == pstr )
- {
- ASSERT(pstr);
- break;
- }
- PyArg_Parse(pstr, "s", &pchvcdatapath);
- if ( NULL != pchvcdatapath )
- {
- cstrVcDataPath = CA2W(pchvcdatapath);
- }
- } while (0);
- Py_Finalize();
- return cstrVcDataPath;
- }
但是使用中,我发现有点不爽。因为我发现,notepad.exe不可以显示行。UE呢还要注册,于是选用Notepadplusplus了。Notepadplusplus还支持多标签页,让我感觉很兴奋,超出我的预期。
贴一段控制notepadplusplus的代码。
- DWORD CManageTask::CreateNotePad()
- {
- STARTUPINFO si;
- PROCESS_INFORMATION pi;
- ZeroMemory( &pi, sizeof(pi) );
- ZeroMemory( &si, sizeof(si) );
- si.cb = sizeof(si);
- CString cstrNNotepadPath = m_strFloder + L"\\Notepadplusplus\\notepad++.exe";
- CreateProcess( cstrNNotepadPath,
- L"", NULL, NULL, FALSE, NULL, NULL,NULL , &si, &pi);
- return pi.dwProcessId;
- }
- RECT CManageTask::GetNotePadRect()
- {
- RECT rc;
- m_pAnalyzeCodeDlg->GetWindowRect(&rc);
- m_pAnalyzeCodeDlg->ClientToScreen(&rc);
- RECT nrc;
- nrc.left = rc.left;
- nrc.right = nrc.left + rc.right - rc.left;
- nrc.top = rc.top + rc.bottom - rc.top;
- nrc.bottom = nrc.top + rc.bottom - rc.top;
- m_pAnalyzeCodeDlg->ScreenToClient( &nrc );
- return nrc;
- }
- HWND CManageTask::GetNotePadFilePathEdit( HWND hwndP )
- {
- if ( NULL == hwndP )
- {
- return NULL;
- }
- HWND hComboBoxEx32 = ::FindWindowEx( hwndP, NULL, L"ComboBoxEx32", NULL );
- HWND hComboBox = ::FindWindowEx( hComboBoxEx32 , NULL, L"ComboBox", NULL );
- HWND hEdit = ::FindWindowEx( hComboBox , NULL, L"Edit", NULL );
- return hEdit;
- }
- HWND CManageTask::GetNotePadOkButton( HWND hwndP )
- {
- if ( NULL == hwndP )
- {
- return NULL;
- }
- HWND hOKButton = ::FindWindowEx( hwndP, NULL, L"Button", NOTEPADOPENFILENAME2 );
- return hOKButton;
- }
- HWND CManageTask::GetNotePadOpenFileDialog()
- {
- HWND hAfter = NULL;
- HWND h = ::FindWindowEx( NULL, hAfter, NULL, NOTEPADOPENFILENAME );
- DWORD dwPID = 0;
- while ( NULL != h )
- {
- h = ::FindWindowEx( NULL, hAfter, NULL, NOTEPADOPENFILENAME );
- GetWindowThreadProcessId( h, &dwPID );
- if ( dwPID == m_dwThreadNotePadID )
- {
- return h;
- }
- else
- {
- hAfter = h;
- }
- }
- return NULL;
- }
- HWND CManageTask::GetNotePadEdit( HWND hwndP )
- {
- if ( NULL == hwndP )
- {
- return NULL;
- }
- HWND hEidt = ::FindWindowEx( hwndP, NULL, L"Scintilla", NULL);
- return hEidt;
- }
- VOID CManageTask::OpenNewFile( const CString& cstrFilePath )
- {
- HWND hwndP = NULL;
- while ( NULL == hwndP )
- {
- ::SetForegroundWindow( m_hwndNotePad );
- ::SetActiveWindow( m_hwndNotePad );
- ::SetFocus( m_hwndNotePad );
- {
- keybd_event( VK_CONTROL, MapVirtualKey(VK_CONTROL, 0), 0, 0 );
- keybd_event( 0x4F, 0, 0, 0 );
- keybd_event( 0x4F, 0, KEYEVENTF_KEYUP, 0 );
- keybd_event( VK_CONTROL, MapVirtualKey(VK_CONTROL, 0), KEYEVENTF_KEYUP, 0 );
- }
- Sleep(500);
- hwndP = GetNotePadOpenFileDialog();
- }
- HWND hEdit = GetNotePadFilePathEdit(hwndP);
- HWND hOKButton = GetNotePadOkButton(hwndP);
- CString strTmp = cstrFilePath;
- if ( NULL != hEdit )
- {
- ::SendMessage( hEdit, WM_SETTEXT, 0, (LPARAM)strTmp.GetBuffer() );
- strTmp.ReleaseBuffer();
- if ( NULL != hOKButton )
- {
- ::SendMessage( hOKButton, WM_LBUTTONDOWN, 0, 0);
- ::SendMessage( hOKButton, WM_LBUTTONUP, 0, 0);
- }
- }
- }
![](http://hi.csdn.net/attachment/201201/9/0_1326134967tR4c.gif)
目前还有很多没有完善的地方,比如字符串的转码(程序放在中文目录下有转码问题,出在python回传结果的时候)和规则(规则不全面)。还有很多可以“玩”的功能,比如对单个文件的重新分析。目前我思路都想好了,也预留了接口。我想通过 FindFirstChangeNotification监控指定目录文件,如果有改动,就调用AFCUI.py中
- def AnalyzeCodeFilePathFunc(self,codefilepath):
- analyzefile = CAnalyzeFloder()
- analyzefile.AnalyzeFile(codefilepath)
- fileinfomap = analyzefile.GetCodeFloderInfo()
- AnalyzeReport(fileinfomap)
还有这次MFC代码,我尝试了下界面和逻辑分离的原则,很好玩,代码也清晰很多,所有的逻辑都在CManageTask类中。
代码链接:AnalyzeCode//https://onedrive.live.com/?cid=8d63dcd52e7cd4fe&id=8D63DCD52E7CD4FE%21151&authkey=!ANEGnTJMGSCR_Eo