From 1259495cbb0ebccd0972df1c0817b53e8e6e7736 Mon Sep 17 00:00:00 2001 From: csh Date: Fri, 22 May 2026 15:45:24 +0800 Subject: [PATCH] :bug: fix(stats): switch badges to self-hosted svg --- .gitea/ci/render_stats_svgs.py | 243 ++++++++++++++++++++ .gitea/workflows/update_stats_badge.yaml | 273 ++++------------------- WORKFLOW.md | 20 +- tests/stats_svg_render_test.sh | 57 +++++ tests/template_defaults_test.sh | 22 ++ 5 files changed, 385 insertions(+), 230 deletions(-) create mode 100644 .gitea/ci/render_stats_svgs.py create mode 100644 tests/stats_svg_render_test.sh diff --git a/.gitea/ci/render_stats_svgs.py b/.gitea/ci/render_stats_svgs.py new file mode 100644 index 0000000..08a1f2b --- /dev/null +++ b/.gitea/ci/render_stats_svgs.py @@ -0,0 +1,243 @@ +#!/usr/bin/env python3 + +import argparse +import unicodedata +from dataclasses import dataclass +from pathlib import Path +from typing import List + + +LABEL_BG = "#555555" +DEFAULT_TOTAL_COLOR = "#1f6feb" +DEFAULT_FILES_COLOR = "#2da44e" +DEFAULT_LANGUAGE_COUNT_COLOR = "#8250df" +TEXT_COLOR = "#ffffff" + + +@dataclass +class LanguageStat: + lang_id: str + display_name: str + code_lines: int + formatted_lines: str + file_count: int + color: str + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser() + parser.add_argument("--output-dir", required=True) + parser.add_argument("--readme-path", required=True) + parser.add_argument("--repo", required=True) + parser.add_argument("--generated-by", required=True) + parser.add_argument("--updated-at", required=True) + parser.add_argument("--total-color", default=DEFAULT_TOTAL_COLOR) + parser.add_argument("--files-color", default=DEFAULT_FILES_COLOR) + parser.add_argument("--language-count-color", default=DEFAULT_LANGUAGE_COUNT_COLOR) + parser.add_argument("--total-code", required=True, type=int) + parser.add_argument("--formatted-total-code", required=True) + parser.add_argument("--total-files", required=True, type=int) + parser.add_argument("--formatted-total-files", required=True) + parser.add_argument("--language-count", required=True, type=int) + parser.add_argument("--exclude-dirs", required=True) + parser.add_argument("--min-lines-threshold", required=True, type=int) + parser.add_argument("--lang-summary", required=True) + return parser.parse_args() + + +def escape_xml(value: str) -> str: + return ( + value.replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace('"', """) + .replace("'", "'") + ) + + +def display_units(value: str) -> int: + units = 0 + for char in value: + if unicodedata.east_asian_width(char) in {"F", "W"}: + units += 2 + else: + units += 1 + return units + + +def text_width(value: str) -> int: + return display_units(value) * 7 + 20 + + +def normalize_color(value: str, fallback: str) -> str: + if not value: + return fallback + return value if value.startswith("#") else f"#{value}" + + +def parse_language_summary(path: Path) -> List[LanguageStat]: + stats: List[LanguageStat] = [] + + if not path.exists(): + return stats + + for raw_line in path.read_text(encoding="utf-8").splitlines(): + line = raw_line.strip() + if not line: + continue + + parts = line.split("|") + if len(parts) < 6: + continue + + while len(parts) < 7: + parts.append("") + + lang_id, display_name, code_lines, formatted_lines, file_count, color, _icon = parts[:7] + stats.append( + LanguageStat( + lang_id=lang_id, + display_name=display_name, + code_lines=int(code_lines), + formatted_lines=formatted_lines, + file_count=int(file_count), + color=normalize_color(color, DEFAULT_TOTAL_COLOR), + ) + ) + + return stats + + +def badge_svg(label: str, message: str, color: str) -> str: + height = 20 + left_width = max(46, text_width(label)) + right_width = max(46, text_width(message)) + total_width = left_width + right_width + left_text_x = left_width / 2 + right_text_x = left_width + right_width / 2 + + return f""" + {escape_xml(label)}: {escape_xml(message)} + + + + + {escape_xml(label)} + {escape_xml(message)} + + +""" + + +def write_badge(path: Path, label: str, message: str, color: str) -> None: + path.write_text(badge_svg(label, message, color), encoding="utf-8") + + +def render_readme(args: argparse.Namespace, languages: List[LanguageStat], badge_dir_name: str) -> str: + language_rows = [] + + for stat in languages: + share = (stat.code_lines * 100 / args.total_code) if args.total_code else 0.0 + language_rows.append( + f"| **{stat.display_name}** | {stat.formatted_lines} | {stat.file_count} | {share:.1f}% | " + f"![{stat.display_name}](./{badge_dir_name}/{stat.lang_id}-lines.svg) |" + ) + + if language_rows: + language_table = "\n".join(language_rows) + else: + language_table = "| _未找到符合条件的语言_ | - | - | - | - |" + + return f"""# 📊 代码统计详细报告 + +> 此分支由 {args.generated_by} 自动生成和维护 +> +> ⚠️ **请勿手动修改此分支的内容!** +> +> 🤖 由 {args.generated_by} 自动生成 +> +> 📅 更新时间: {args.updated_at} + +![总代码](./{badge_dir_name}/total-lines.svg) +![总文件](./{badge_dir_name}/total-files.svg) +![语言种类](./{badge_dir_name}/language-count.svg) + +## 📈 总体统计 + +| 统计项 | 数值 | 徽章 | +|--------|------|------| +| 💻 总代码行数 | **{args.formatted_total_code}** 行 | ![总代码](./{badge_dir_name}/total-lines.svg) | +| 📁 总文件数 | **{args.formatted_total_files}** 个 | ![总文件](./{badge_dir_name}/total-files.svg) | +| 🌐 语言种类 | **{args.language_count}** 种 | ![语言种类](./{badge_dir_name}/language-count.svg) | + +## 🌈 语言分布 + +| 语言 | 代码行数 | 文件数 | 占比 | 徽章 | +|------|----------|--------|------|------| +{language_table} + +--- + +## ⚙️ 配置说明 + +### Token 配置 + +- 当前使用: **WORKFLOW** +- 需要在 Settings -> Actions -> Secrets 中配置 `WORKFLOW` + +### 排除规则 + +当前排除的目录: +``` +{args.exclude_dirs} +``` + +### 阈值设置 + +- 最小代码行数阈值: **{args.min_lines_threshold}** 行 +- 低于此阈值的语言不会生成徽章 + +--- + +*📊 统计仓库:`{args.repo}`* +""" + + +def main() -> None: + args = parse_args() + output_dir = Path(args.output_dir) + readme_path = Path(args.readme_path) + + output_dir.mkdir(parents=True, exist_ok=True) + readme_path.parent.mkdir(parents=True, exist_ok=True) + + languages = parse_language_summary(Path(args.lang_summary)) + badge_dir_name = output_dir.name + + write_badge( + output_dir / "total-lines.svg", + "代码", + f"{args.formatted_total_code} 行", + normalize_color(args.total_color, DEFAULT_TOTAL_COLOR), + ) + write_badge( + output_dir / "total-files.svg", + "文件", + f"{args.formatted_total_files} 个", + normalize_color(args.files_color, DEFAULT_FILES_COLOR), + ) + write_badge( + output_dir / "language-count.svg", + "语言", + f"{args.language_count} 种", + normalize_color(args.language_count_color, DEFAULT_LANGUAGE_COUNT_COLOR), + ) + + for stat in languages: + write_badge(output_dir / f"{stat.lang_id}-lines.svg", stat.display_name, f"{stat.formatted_lines} 行", stat.color) + + readme_path.write_text(render_readme(args, languages, badge_dir_name), encoding="utf-8") + + +if __name__ == "__main__": + main() diff --git a/.gitea/workflows/update_stats_badge.yaml b/.gitea/workflows/update_stats_badge.yaml index fa2c79f..8267e16 100644 --- a/.gitea/workflows/update_stats_badge.yaml +++ b/.gitea/workflows/update_stats_badge.yaml @@ -35,17 +35,12 @@ env: SPECIAL_INCLUDES: "" # 示例: 'dist:js,css|vendor:go,mod' - # ===== 徽章颜色配置 ===== + # ===== SVG 徽章颜色配置 ===== COLOR_TOTAL: "blue" COLOR_FILES: "green" - COLOR_DEFAULT: "brightgreen" - - # ===== 徽章样式配置 ===== - BADGE_STYLE: "flat" # 可选: flat, flat-square, plastic, for-the-badge, social + COLOR_LANGUAGE_COUNT: "purple" # ===== 输出配置 ===== - # 是否生成详细报告 - GENERATE_DETAILED_REPORT: "true" # 是否输出到 workflow summary OUTPUT_TO_SUMMARY: "true" # 最小代码行数阈值(低于此值的语言不生成徽章) @@ -55,18 +50,6 @@ env: GIT_USER_NAME: "ci[bot]" GIT_USER_EMAIL: "ci[bot]@tinysoft.com.cn" - # ===== 平台配置 ===== - # 平台类型: github 或 gitea - PLATFORM: "gitea" - # Git 服务器 URL(Gitea 示例: https://gitea.example.com) - GIT_SERVER_URL: "${{ github.server_url }}" - # 仓库路径(格式: owner/repo) - REPO_PATH: "${{ github.repository }}" - # Raw 文件基础 URL - # GitHub: https://raw.githubusercontent.com/{owner}/{repo}/{branch}/{path} - # Gitea: https://gitea.example.com/{owner}/{repo}/raw/branch/{branch}/{path} - RAW_URL_BASE: '${{ github.server_url }}/${{ github.repository }}/raw/branch' - # ========================================== # 🎨 语言分组配置 # 格式: 组名:后缀列表:显示名称:颜色:图标(可选) @@ -363,19 +346,19 @@ jobs: cat > README.md << 'EOF' # 📊 代码统计徽章数据 - > 此分支由 GitHub Actions 自动生成和维护 + > 此分支由 Gitea Actions 自动生成和维护 > > ⚠️ **请勿手动修改此分支的内容!** ## 📁 目录结构 ``` + README.md # 详细统计报告 badges/ - ├── total-lines.json # 总代码行数徽章 - ├── total-files.json # 总文件数徽章 - ├── {language}-lines.json # 各语言代码行数徽章 - ├── {language}-files.json # 各语言文件数徽章 - └── README.md # 详细统计报告 + ├── total-lines.svg # 总代码行数徽章 + ├── total-files.svg # 总文件数徽章 + ├── language-count.svg # 语言种类徽章 + └── {language}-lines.svg # 各语言代码行数徽章 ``` ## 🔄 更新机制 @@ -389,7 +372,7 @@ jobs: --- - *🤖 由 GitHub Actions 自动维护* + *🤖 由 Gitea Actions 自动维护* EOF # 创建徽章目录 @@ -555,212 +538,50 @@ jobs: echo "======================================" echo "" - - name: 🎨 生成徽章数据 + - name: 🎨 生成 SVG 徽章与统计报告 id: generate_badges run: | echo "======================================" - echo "🎨 生成徽章数据" + echo "🎨 生成 SVG 徽章与统计报告" echo "======================================" cd "${{ env.REPO_DIR }}" - # 确保在统计分支 + # 在主分支渲染产物,避免依赖 stats 分支中的脚本文件 + git checkout ${{ github.ref_name }} + + RENDER_ROOT=$(mktemp -d) + UPDATED_AT=$(date -u '+%Y-%m-%d %H:%M:%S UTC') + + python3 .gitea/ci/render_stats_svgs.py \ + --output-dir "$RENDER_ROOT/${{ env.BADGE_DIR }}" \ + --readme-path "$RENDER_ROOT/README.md" \ + --repo "${{ github.repository }}" \ + --generated-by "Gitea Actions" \ + --updated-at "$UPDATED_AT" \ + --total-color "${{ env.COLOR_TOTAL }}" \ + --files-color "${{ env.COLOR_FILES }}" \ + --language-count-color "${{ env.COLOR_LANGUAGE_COUNT }}" \ + --total-code "${{ steps.total.outputs.total_code }}" \ + --formatted-total-code "${{ steps.total.outputs.formatted_code }}" \ + --total-files "${{ steps.total.outputs.total_files }}" \ + --formatted-total-files "${{ steps.total.outputs.formatted_files }}" \ + --language-count "${{ steps.languages.outputs.language_count }}" \ + --exclude-dirs "${{ env.EXCLUDE_DIRS }}" \ + --min-lines-threshold "${{ env.MIN_LINES_THRESHOLD }}" \ + --lang-summary /tmp/lang_summary.txt + git checkout ${{ env.BADGE_BRANCH }} - - # 确保徽章目录存在 - mkdir -p ${{ env.BADGE_DIR }} - - GENERATED_COUNT=0 - - # 生成总代码行数徽章 - echo "📊 生成总代码行数徽章..." - cat > ${{ env.BADGE_DIR }}/total-lines.json << EOF - { - "schemaVersion": 1, - "label": "代码", - "message": "${{ steps.total.outputs.formatted_code }} 行", - "color": "${{ env.COLOR_TOTAL }}", - "style": "${{ env.BADGE_STYLE }}" - } - EOF - GENERATED_COUNT=$((GENERATED_COUNT + 1)) - - # 生成总文件数徽章 - echo "📁 生成总文件数徽章..." - cat > ${{ env.BADGE_DIR }}/total-files.json << EOF - { - "schemaVersion": 1, - "label": "文件", - "message": "${{ steps.total.outputs.formatted_files }} 个", - "color": "${{ env.COLOR_FILES }}", - "style": "${{ env.BADGE_STYLE }}" - } - EOF - GENERATED_COUNT=$((GENERATED_COUNT + 1)) - - # 生成各语言徽章 - if [ -f /tmp/lang_summary.txt ] && [ -s /tmp/lang_summary.txt ]; then - echo "" - echo "🌐 生成语言徽章..." - - while IFS='|' read -r lang_id display_name code_lines formatted_lines file_count color icon; do - [ -z "$lang_id" ] && continue - - echo " - $display_name" - - # 生成代码行数徽章(使用 heredoc) - if [ -n "$icon" ]; then - # 带图标的徽章 - cat > ${{ env.BADGE_DIR }}/${lang_id}-lines.json << EOFJSON - { - "schemaVersion": 1, - "label": "$display_name", - "message": "$formatted_lines 行", - "color": "$color", - "style": "${{ env.BADGE_STYLE }}", - "namedLogo": "$icon" - } - EOFJSON - else - # 不带图标的徽章 - cat > ${{ env.BADGE_DIR }}/${lang_id}-lines.json << EOFJSON - { - "schemaVersion": 1, - "label": "$display_name", - "message": "$formatted_lines 行", - "color": "$color", - "style": "${{ env.BADGE_STYLE }}" - } - EOFJSON - fi - GENERATED_COUNT=$((GENERATED_COUNT + 1)) - - # 生成文件数徽章 - cat > ${{ env.BADGE_DIR }}/${lang_id}-files.json << EOFJSON - { - "schemaVersion": 1, - "label": "$display_name 文件", - "message": "$file_count 个", - "color": "$color", - "style": "${{ env.BADGE_STYLE }}" - } - EOFJSON - GENERATED_COUNT=$((GENERATED_COUNT + 1)) - done < /tmp/lang_summary.txt - fi + rm -rf "${{ env.BADGE_DIR }}" + mkdir -p "${{ env.BADGE_DIR }}" + cp -f "$RENDER_ROOT/README.md" README.md + cp -f "$RENDER_ROOT/${{ env.BADGE_DIR }}"/* "${{ env.BADGE_DIR }}/" + GENERATED_COUNT=$(find "$RENDER_ROOT/${{ env.BADGE_DIR }}" -maxdepth 1 -type f | wc -l | tr -d ' ') + rm -rf "$RENDER_ROOT" echo "" echo "generated_count=$GENERATED_COUNT" >> $GITHUB_OUTPUT - echo "✅ 已生成 $GENERATED_COUNT 个徽章" - echo "======================================" - echo "" - - - name: 📝 生成详细统计报告 - if: env.GENERATE_DETAILED_REPORT == 'true' - run: | - echo "======================================" - echo "📝 生成详细统计报告" - echo "======================================" - - cd "${{ env.REPO_DIR }}" - - # 确保在统计分支 - git checkout ${{ env.BADGE_BRANCH }} - - # 生成 README - cat > ${{ env.BADGE_DIR }}/README.md << 'EOFMD' - # 📊 代码统计详细报告 - - > 🤖 由 GitHub Actions 自动生成 - > - > 📅 更新时间: TIMESTAMP_PLACEHOLDER - - ## 📈 总体统计 - - | 统计项 | 数值 | 徽章 | - |--------|------|------| - | 💻 总代码行数 | **TOTAL_CODE_PLACEHOLDER** 行 | ![代码行数](https://img.shields.io/endpoint?url=RAW_URL_PLACEHOLDER/BRANCH_PLACEHOLDER/BADGE_DIR_PLACEHOLDER/total-lines.json) | - | 📁 总文件数 | **TOTAL_FILES_PLACEHOLDER** 个 | ![文件数](https://img.shields.io/endpoint?url=RAW_URL_PLACEHOLDER/BRANCH_PLACEHOLDER/BADGE_DIR_PLACEHOLDER/total-files.json) | - | 🌐 语言种类 | **LANG_COUNT_PLACEHOLDER** 种 | - | - - ## 🌈 语言分布 - - EOFMD - - # 替换占位符 - sed -i "s|TIMESTAMP_PLACEHOLDER|$(date -u '+%Y-%m-%d %H:%M:%S UTC')|g" ${{ env.BADGE_DIR }}/README.md - sed -i "s|TOTAL_CODE_PLACEHOLDER|${{ steps.total.outputs.formatted_code }}|g" ${{ env.BADGE_DIR }}/README.md - sed -i "s|TOTAL_FILES_PLACEHOLDER|${{ steps.total.outputs.formatted_files }}|g" ${{ env.BADGE_DIR }}/README.md - sed -i "s|LANG_COUNT_PLACEHOLDER|${{ steps.languages.outputs.language_count }}|g" ${{ env.BADGE_DIR }}/README.md - sed -i "s|REPO_PLACEHOLDER|${{ github.repository }}|g" ${{ env.BADGE_DIR }}/README.md - sed -i "s|BRANCH_PLACEHOLDER|${{ env.BADGE_BRANCH }}|g" ${{ env.BADGE_DIR }}/README.md - sed -i "s|BADGE_DIR_PLACEHOLDER|${{ env.BADGE_DIR }}|g" ${{ env.BADGE_DIR }}/README.md - sed -i "s|RAW_URL_PLACEHOLDER|${{ env.RAW_URL_BASE }}|g" ${{ env.BADGE_DIR }}/README.md - - # 添加语言统计表格 - if [ -f /tmp/lang_summary.txt ] && [ -s /tmp/lang_summary.txt ]; then - cat >> ${{ env.BADGE_DIR }}/README.md << 'EOFTABLE' - | 语言 | 代码行数 | 文件数 | 占比 | 徽章 | - |------|----------|--------|------|------| - EOFTABLE - - TOTAL_CODE=${{ steps.total.outputs.total_code }} - while IFS='|' read -r lang_id display_name code_lines formatted_lines file_count color icon; do - [ -z "$lang_id" ] && continue - - if [ "$TOTAL_CODE" -gt 0 ]; then - PERCENT=$(echo "scale=1; $code_lines * 100 / $TOTAL_CODE" | bc) - else - PERCENT="0.0" - fi - - cat >> ${{ env.BADGE_DIR }}/README.md << EOFLANG - | **${display_name}** | ${formatted_lines} | ${file_count} | ${PERCENT}% | ![${display_name}](https://img.shields.io/endpoint?url=${{ env.RAW_URL_BASE }}/${{ env.BADGE_BRANCH }}/${{ env.BADGE_DIR }}/${lang_id}-lines.json) | - EOFLANG - done < /tmp/lang_summary.txt - fi - - # 添加配置说明 - cat >> ${{ env.BADGE_DIR }}/README.md << EOFMD - - --- - - ## ⚙️ 配置说明 - - ### Token 配置 - - - 当前使用: **${{ steps.validate_token.outputs.token_type }}** - - 需要在 Settings -> Secrets 中配置 `WORKFLOW` - - ### 排除规则 - - 当前排除的目录: - ``` - ${{ env.EXCLUDE_DIRS }} - ``` - - ### 阈值设置 - - - 最小代码行数阈值: **${{ env.MIN_LINES_THRESHOLD }}** 行 - - 低于此阈值的语言将不会生成徽章 - - ### 徽章样式 - - - 当前样式: **${{ env.BADGE_STYLE }}** - - 可选样式: flat, flat-square, plastic, for-the-badge, social - - --- - -
- - *📊 由 [GitHub Actions](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions) 自动生成和更新* - - *🤖 生成时间: $(date -u '+%Y-%m-%d %H:%M:%S UTC')* - -
- EOFMD - - echo "✅ 详细统计报告已生成" + echo "✅ 已生成 $GENERATED_COUNT 个 SVG 徽章" echo "======================================" echo "" @@ -781,7 +602,7 @@ jobs: git config user.email "${{ env.GIT_USER_EMAIL }}" # 添加所有更改 - git add ${{ env.BADGE_DIR }}/ + git add README.md ${{ env.BADGE_DIR }}/ # 检查是否有变更 if git diff --staged --quiet; then @@ -801,7 +622,7 @@ jobs: - 徽章数: ${{ steps.generate_badges.outputs.generated_count }} 个 🔗 触发提交: ${GITHUB_SHA:0:7} - 🤖 由 GitHub Actions 自动生成" + 🤖 由 Gitea Actions 自动生成" git commit -m "$COMMIT_MSG" @@ -842,7 +663,7 @@ jobs: ## 🔗 快速链接 - - 📊 [查看详细统计报告](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/src/branch/${{ env.BADGE_BRANCH }}/${{ env.BADGE_DIR }}/README.md) + - 📊 [查看详细统计报告](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/src/branch/${{ env.BADGE_BRANCH }}/README.md) - 🎨 [浏览徽章文件](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/src/branch/${{ env.BADGE_BRANCH }}/${{ env.BADGE_DIR }}) - 🔧 [查看 Workflow 配置](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/src/branch/${{ github.ref_name }}/.gitea/workflows/update_stats_badge.yaml) @@ -924,7 +745,7 @@ jobs: echo " - 统计分支: ${{ env.BADGE_BRANCH }}" echo "" echo "🔗 查看详细报告:" - echo " ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/src/branch/${{ env.BADGE_BRANCH }}/${{ env.BADGE_DIR }}/README.md" + echo " ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/src/branch/${{ env.BADGE_BRANCH }}/README.md" echo "" echo "🎨 徽章目录:" echo " ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/src/branch/${{ env.BADGE_BRANCH }}/${{ env.BADGE_DIR }}" @@ -957,7 +778,7 @@ jobs: if: always() run: | echo "🧹 清理临时文件..." - rm -rf /tmp/lang_stats /tmp/lang_summary.txt /tmp/total_stats*.json + rm -rf /tmp/lang_stats /tmp/lang_summary.txt if [ -n "${{ env.JOB_WORKSPACE }}" ] && [ "${{ env.JOB_WORKSPACE }}" != "/" ]; then echo "🧹 清理 Job 工作区: ${{ env.JOB_WORKSPACE }}" rm -rf "${{ env.JOB_WORKSPACE }}" diff --git a/WORKFLOW.md b/WORKFLOW.md index a2479de..9b55770 100644 --- a/WORKFLOW.md +++ b/WORKFLOW.md @@ -63,14 +63,14 @@ git push origin 1.0.0 #### 2. 📊 代码统计徽章工作流 (`update_stats_badge.yaml`) -**功能**:自动统计代码行数并生成徽章数据 +**功能**:自动统计代码行数并生成 SVG 徽章与统计报告 **特性**: - 📈 统计总代码行数和文件数 - 🌐 按语言分组统计 -- 🎨 自动生成徽章 JSON 数据 -- 📊 生成统计摘要(JSON 格式) +- 🎨 自动生成仓库内自托管的 SVG 徽章 +- 📊 在 `stats` 分支根目录生成统计报告 README - 🚫 灵活的目录排除配置 **触发方式**: @@ -85,7 +85,19 @@ git push origin main **配置文件**:[update_stats_badge.yaml](.gitea/workflows/update_stats_badge.yaml) -**markdown引用格式**: `![C++](https://img.shields.io/endpoint?url=https://你的gitea/用户名/仓库/raw/branch/分支/badges/cpp-lines.json)` +**markdown引用格式**: `![C++](https://你的gitea/用户名/仓库/raw/branch/stats/badges/cpp-lines.svg)` + +**产物结构**: + +```txt +stats +├── README.md +└── badges/ + ├── total-lines.svg + ├── total-files.svg + ├── language-count.svg + └── -lines.svg +``` 💡 **详细配置**:查看 `update_stats_badge.yaml` 文件顶部的 `env` 区域,包含语言分组、颜色、排除目录等配置 diff --git a/tests/stats_svg_render_test.sh b/tests/stats_svg_render_test.sh new file mode 100644 index 0000000..89d6dac --- /dev/null +++ b/tests/stats_svg_render_test.sh @@ -0,0 +1,57 @@ +#!/bin/bash +set -euo pipefail + +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +REPO_ROOT=$(cd "${SCRIPT_DIR}/.." && pwd) + +fail() { + echo "FAIL: $*" >&2 + exit 1 +} + +temp_root=$(mktemp -d) +lang_summary="${temp_root}/lang_summary.txt" +output_dir="${temp_root}/badges" +readme_path="${temp_root}/README.md" + +cat > "${lang_summary}" <<'EOF' +python|Python|1200|1,200|12|3572A5|python +typescript|TypeScript|800|800|9|3178c6|typescript +yaml|YAML|120|120|5|CB171E| +EOF + +python3 "${REPO_ROOT}/.gitea/ci/render_stats_svgs.py" \ + --output-dir "${output_dir}" \ + --readme-path "${readme_path}" \ + --repo "csh/actions-template" \ + --generated-by "Gitea Actions" \ + --updated-at "2026-05-22 12:00:00 UTC" \ + --total-code 2120 \ + --formatted-total-code "2,120" \ + --total-files 26 \ + --formatted-total-files "26" \ + --language-count 3 \ + --exclude-dirs "node_modules,dist,.git" \ + --min-lines-threshold 10 \ + --lang-summary "${lang_summary}" + +test -f "${output_dir}/total-lines.svg" || fail "renderer should create total-lines.svg" +test -f "${output_dir}/total-files.svg" || fail "renderer should create total-files.svg" +test -f "${output_dir}/language-count.svg" || fail "renderer should create language-count.svg" +test -f "${output_dir}/python-lines.svg" || fail "renderer should create per-language svg badges" +test -f "${readme_path}" || fail "renderer should create root README.md" + +grep -q '