大多数逻辑是本地AI生成的,自己进行了组合和微调
<?php /** * PHP 程序文件有效代码行数统计工具 code_counter.php * * 统计规则: * 1. 忽略行首的空白字符。 * 2. 处理多行注释 \/* ... *\/,遇到 \/* 则进入状态,直到遇到 *\/ 才会退出状态。 * 3. 处理单行注释 //,在非多行注释状态下,以 // 开头的行不计入代码。 * 4. 整行全是空白字符的行不计入代码。 * */ define('DEBUG_MODE', false); function code_counter(string $filePath) { // 状态变量:追踪是否处于多行注释块内部 $inMultilineComment = false; $validCodeLines = 0; $fp = fopen($filePath, 'rb'); if (!$fp) die("Error: Could not read the file $filePath.\n"); $lineNo = 0; while ($line = fgets($fp)) { $lineNo++; $trimmedLine = trim($line); // 移除两边空白 if ($trimmedLine === '') { if (DEBUG_MODE) echo "00: line $lineNo is empty line\n"; continue; // 空白行不计入 } // 是否遇到块注释结束。 这里仅考虑同一行只会出现注释符结束标记,后续没有代码和注释, // 最多错误计数1行而已。开始标记也只进行简单处理 if ($inMultilineComment && strpos($trimmedLine, '*/') !== false) { $inMultilineComment = false; if (DEBUG_MODE) echo "*/: line $lineNo is comment end line\n"; continue; } if (!$inMultilineComment && strpos($trimmedLine, '/*') !== false) { $inMultilineComment = true; if (DEBUG_MODE) echo "/*: line $lineNo is comment start line\n"; continue; } // 块注释内部的行不计入 if ($inMultilineComment) { if (DEBUG_MODE) echo "**: line $lineNo is the line inside a comment block\n"; continue; } // 单行注释不计入 if (str_starts_with($trimmedLine, '//')) { if (DEBUG_MODE) echo "//: line $lineNo is single comment line\n"; continue; } // 是有效行 $validCodeLines++; } fclose($fp); if (DEBUG_MODE) printf("Total %4d, valid %4d -> %s\n", $lineNo, $validCodeLines, $filePath); return $validCodeLines; } /** * * 功能: 遍历指定目录及其所有子目录,统计指定后缀的文件某种信息,我们是代码行数 * * 使用方法: php code_counter.php <目录路径> <逗号分隔的后缀列表> * * 示例: php code_counter.php /var/www/project php,html,css */ // 检查命令行参数 if ($argc !== 3) { echo "Usage: php $argv[0] <directory_path> <comma_separated_extensions>\n"; echo "Example: php $argv[0] ./my_project php,css,js\n"; exit(1); } $directoryPath = $argv[1]; $extensionsInput = $argv[2]; // 1. 校验目录是否存在且可读 if (!is_dir($directoryPath)) { echo "Error: Directory '$directoryPath' does not exist or is not a directory.\n"; exit(1); } // 2. 解析后缀列表 // 将逗号分隔的字符串转换为数组,并转换为小写,方便匹配 $allowedExtensions = array_map('trim', explode(',', $extensionsInput)); $allowedExtensions = array_map('strtolower', $allowedExtensions); // 3. 使用递归迭代器遍历目录 try { // RecursiveDirectoryIterator 遍历目录,RecursiveIteratorIterator 遍历目录树 $dirIterator = new RecursiveDirectoryIterator($directoryPath); $iterator = new RecursiveIteratorIterator($dirIterator); $foundFiles = 0; foreach ($iterator as $fileInfo) { // 仅处理文件,跳过目录 if ($fileInfo->isFile()) { $fileName = $fileInfo->getRealPath(); // https://www.php.net/manual/en/splfileinfo.getrealpath.php $fileExtension = strtolower($fileInfo->getExtension()); // 扩展名,不带点 // 检查文件扩展名是否在允许的列表中 if (in_array($fileExtension, $allowedExtensions)) { $validCodeLines = code_counter($fileName); echo $fileName. ",". $validCodeLines. "\n"; } } } } catch (UnexpectedValueException $e) { echo "Error accessing directory: " . $e->getMessage() . "\n"; } ?>