mirror of
https://github.com/h3pdesign/Neon-Vision-Editor
synced 2026-04-21 13:27:16 +00:00
docs(metrics): add traffic badges and views-vs-clones dual bar
This commit is contained in:
parent
20641de5c2
commit
e9366c9456
4 changed files with 230 additions and 72 deletions
|
|
@ -361,7 +361,7 @@
|
|||
CODE_SIGNING_ALLOWED = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 495;
|
||||
CURRENT_PROJECT_VERSION = 496;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = CS727NF72U;
|
||||
ENABLE_APP_SANDBOX = YES;
|
||||
|
|
@ -444,7 +444,7 @@
|
|||
CODE_SIGNING_ALLOWED = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 495;
|
||||
CURRENT_PROJECT_VERSION = 496;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = CS727NF72U;
|
||||
ENABLE_APP_SANDBOX = YES;
|
||||
|
|
|
|||
15
README.md
15
README.md
|
|
@ -143,16 +143,21 @@
|
|||
<img alt="v0.5.3 Downloads" src="https://img.shields.io/github/downloads/h3pdesign/Neon-Vision-Editor/v0.5.3/total?style=for-the-badge&label=v0.5.3&color=22C55E">
|
||||
</p>
|
||||
|
||||
<p align="center"><strong>Release Download + Clone Trend</strong></p>
|
||||
<p align="center"><strong>Release Download + Traffic Trend</strong></p>
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/images/release-download-trend.svg" alt="GitHub release downloads trend chart" width="100%">
|
||||
</p>
|
||||
|
||||
<p align="center"><em>Styled line chart shows per-release totals plus a scaled 14-day git clone volume bar.</em></p>
|
||||
<p align="center">Git clones (last 14 days): <strong>2624</strong>.</p>
|
||||
<p align="center">Clone data snapshot (UTC): <strong>2026-03-09 00:00</strong>.</p>
|
||||
<p align="center">Snapshot total downloads: <strong>623</strong> across releases.</p>
|
||||
<p align="center"><em>Styled line chart shows per-release totals with 14-day traffic counters for clones and views.</em></p>
|
||||
<p align="center">
|
||||
<img alt="Git clones (14d)" src="https://img.shields.io/badge/Git%20clones%20%2814d%29-2624-7C3AED?style=for-the-badge">
|
||||
<img alt="GitHub views (14d)" src="https://img.shields.io/badge/GitHub%20views%20%2814d%29-865-0EA5E9?style=for-the-badge">
|
||||
</p>
|
||||
<p align="center">
|
||||
<img alt="Clone snapshot (UTC)" src="https://img.shields.io/badge/Clone%20snapshot%20%28UTC%29-2026-03-09%2000%3A00-334155?style=flat-square">
|
||||
<img alt="View snapshot (UTC)" src="https://img.shields.io/badge/View%20snapshot%20%28UTC%29-2026-03-09%2000%3A00-334155?style=flat-square">
|
||||
</p>
|
||||
|
||||
## Project Docs
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<svg width="1200" height="560" viewBox="0 0 1200 560" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="title desc">
|
||||
<title id="title">GitHub Release Downloads and Clone Trend</title>
|
||||
<desc id="desc">Line chart of release downloads with highlighted points and a scaled 14-day git clone volume bar.</desc>
|
||||
<title id="title">GitHub Release Downloads and Traffic Trend</title>
|
||||
<desc id="desc">Line chart of release downloads with 14-day traffic bars for clones and views.</desc>
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="0" y1="0" x2="1200" y2="560" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#061423"/>
|
||||
|
|
@ -15,6 +15,10 @@
|
|||
<stop stop-color="#7C3AED"/>
|
||||
<stop offset="1" stop-color="#C084FC"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="viewFill" x1="86" y1="482" x2="1110" y2="482" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#0EA5E9"/>
|
||||
<stop offset="1" stop-color="#7DD3FC"/>
|
||||
</linearGradient>
|
||||
<filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
|
||||
<feGaussianBlur stdDeviation="4" result="blur"/>
|
||||
<feMerge>
|
||||
|
|
@ -78,14 +82,18 @@
|
|||
|
||||
<text x="776" y="56" fill="#D7F7FF" font-size="15" font-family="SF Pro Text, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif">Release trend line with highlighted points</text>
|
||||
<rect x="58" y="390" width="1084" height="132" rx="12" fill="#0A1A2B" stroke="#2A4762" stroke-width="1"/>
|
||||
<text x="84" y="420" fill="#E6F3FF" font-size="18" font-family="SF Pro Text, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif" font-weight="600">Git Clones (last 14 days): 2624</text>
|
||||
<rect x="86" y="450" width="1024" height="36" rx="10" fill="#15263A" stroke="#2B4255" stroke-width="1"/>
|
||||
<line x1="86" y1="442" x2="86" y2="494" stroke="#436280" stroke-width="1"/>
|
||||
<line x1="598.0" y1="442" x2="598.0" y2="494" stroke="#436280" stroke-width="1"/>
|
||||
<line x1="1110" y1="442" x2="1110" y2="494" stroke="#436280" stroke-width="1"/>
|
||||
<rect x="86" y="450" width="959.6" height="36" rx="10" fill="url(#cloneFill)"/>
|
||||
<text x="82" y="438" text-anchor="start" fill="#9CC3E6" font-size="12" font-family="SF Pro Text, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif">0</text>
|
||||
<text x="598.0" y="438" text-anchor="middle" fill="#9CC3E6" font-size="12" font-family="SF Pro Text, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif">1400</text>
|
||||
<text x="1114" y="438" text-anchor="end" fill="#9CC3E6" font-size="12" font-family="SF Pro Text, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif">2800</text>
|
||||
<text x="86" y="510" fill="#9CC3E6" font-size="13" font-family="SF Pro Text, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif">Scale: 0 to 2800 clones in the last 14 days.</text>
|
||||
<text x="84" y="420" fill="#E6F3FF" font-size="18" font-family="SF Pro Text, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif" font-weight="600">Repository Traffic (last 14 days)</text>
|
||||
<text x="86" y="434" fill="#C4B5FD" font-size="13" font-family="SF Pro Text, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif">Clones: 2624</text>
|
||||
<rect x="86" y="442" width="1024" height="18" rx="9" fill="#15263A" stroke="#2B4255" stroke-width="1"/>
|
||||
<rect x="86" y="442" width="959.6" height="18" rx="9" fill="url(#cloneFill)"/>
|
||||
<text x="86" y="466" fill="#7DD3FC" font-size="13" font-family="SF Pro Text, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif">Views: 865</text>
|
||||
<rect x="86" y="474" width="1024" height="18" rx="9" fill="#15263A" stroke="#2B4255" stroke-width="1"/>
|
||||
<rect x="86" y="474" width="316.3" height="18" rx="9" fill="url(#viewFill)"/>
|
||||
<line x1="86" y1="426" x2="86" y2="500" stroke="#436280" stroke-width="1"/>
|
||||
<line x1="598.0" y1="426" x2="598.0" y2="500" stroke="#436280" stroke-width="1"/>
|
||||
<line x1="1110" y1="426" x2="1110" y2="500" stroke="#436280" stroke-width="1"/>
|
||||
<text x="84" y="512" text-anchor="start" fill="#9CC3E6" font-size="12" font-family="SF Pro Text, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif">0</text>
|
||||
<text x="598.0" y="512" text-anchor="middle" fill="#9CC3E6" font-size="12" font-family="SF Pro Text, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif">1400</text>
|
||||
<text x="1112" y="512" text-anchor="end" fill="#9CC3E6" font-size="12" font-family="SF Pro Text, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif">2800</text>
|
||||
<text x="86" y="526" fill="#9CC3E6" font-size="13" font-family="SF Pro Text, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif">Shared scale: 0 to 2800 events in the last 14 days.</text>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 8.6 KiB |
|
|
@ -18,6 +18,7 @@ import re
|
|||
import subprocess
|
||||
import sys
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
|
|
@ -28,6 +29,7 @@ OWNER = "h3pdesign"
|
|||
REPO = "Neon-Vision-Editor"
|
||||
API_URL = f"https://api.github.com/repos/{OWNER}/{REPO}/releases?per_page=100"
|
||||
CLONES_API_URL = f"https://api.github.com/repos/{OWNER}/{REPO}/traffic/clones"
|
||||
VIEWS_API_URL = f"https://api.github.com/repos/{OWNER}/{REPO}/traffic/views"
|
||||
CLONES_WINDOW_DAYS = 14
|
||||
|
||||
|
||||
|
|
@ -44,6 +46,12 @@ class ClonePoint:
|
|||
count: int
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ViewPoint:
|
||||
timestamp: dt.datetime
|
||||
count: int
|
||||
|
||||
|
||||
def github_api_get(url: str) -> object:
|
||||
base_headers = {
|
||||
"Accept": "application/vnd.github+json",
|
||||
|
|
@ -153,6 +161,53 @@ def fetch_clone_traffic() -> tuple[list[ClonePoint], int | None, dt.datetime | N
|
|||
return points, None, latest_timestamp
|
||||
|
||||
|
||||
def fetch_view_traffic() -> tuple[list[ViewPoint], int | None, dt.datetime | None]:
|
||||
try:
|
||||
payload = github_api_get(VIEWS_API_URL)
|
||||
except Exception:
|
||||
payload = None
|
||||
if payload is None:
|
||||
# Local fallback: reuse authenticated gh CLI when direct API auth is unavailable.
|
||||
try:
|
||||
out = subprocess.run(
|
||||
["gh", "api", f"repos/{OWNER}/{REPO}/traffic/views"],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=20,
|
||||
)
|
||||
payload = json.loads(out.stdout)
|
||||
except Exception:
|
||||
return [], None, None
|
||||
if not isinstance(payload, dict):
|
||||
return [], None, None
|
||||
|
||||
raw_points = payload.get("views", [])
|
||||
if not isinstance(raw_points, list):
|
||||
raw_points = []
|
||||
|
||||
points: list[ViewPoint] = []
|
||||
for point in raw_points:
|
||||
if not isinstance(point, dict):
|
||||
continue
|
||||
ts_raw = point.get("timestamp")
|
||||
count = point.get("count", 0)
|
||||
if not isinstance(ts_raw, str) or not isinstance(count, int):
|
||||
continue
|
||||
try:
|
||||
ts = dt.datetime.fromisoformat(ts_raw.replace("Z", "+00:00"))
|
||||
except ValueError:
|
||||
continue
|
||||
points.append(ViewPoint(timestamp=ts, count=count))
|
||||
|
||||
points.sort(key=lambda p: p.timestamp)
|
||||
total_count = payload.get("count")
|
||||
latest_timestamp = points[-1].timestamp if points else None
|
||||
if isinstance(total_count, int):
|
||||
return points, total_count, latest_timestamp
|
||||
return points, None, latest_timestamp
|
||||
|
||||
|
||||
def y_top(max_value: int, ticks: int = 4) -> int:
|
||||
if max_value <= 0:
|
||||
return ticks
|
||||
|
|
@ -162,7 +217,7 @@ def y_top(max_value: int, ticks: int = 4) -> int:
|
|||
return step * ticks
|
||||
|
||||
|
||||
def generate_svg(points: list[ReleasePoint], clone_total: int, snapshot_date: str) -> str:
|
||||
def generate_svg(points: list[ReleasePoint], clone_total: int, view_total: int, snapshot_date: str) -> str:
|
||||
width = 1200
|
||||
height = 560
|
||||
left = 130
|
||||
|
|
@ -226,35 +281,43 @@ def generate_svg(points: list[ReleasePoint], clone_total: int, snapshot_date: st
|
|||
|
||||
clone_panel: list[str] = [
|
||||
' <rect x="58" y="390" width="1084" height="132" rx="12" fill="#0A1A2B" stroke="#2A4762" stroke-width="1"/>',
|
||||
f' <text x="84" y="420" fill="#E6F3FF" font-size="18" font-family="SF Pro Text, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif" font-weight="600">Git Clones (last {CLONES_WINDOW_DAYS} days): {clone_total}</text>',
|
||||
f' <text x="84" y="420" fill="#E6F3FF" font-size="18" font-family="SF Pro Text, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif" font-weight="600">Repository Traffic (last {CLONES_WINDOW_DAYS} days)</text>',
|
||||
]
|
||||
panel_left = 86
|
||||
panel_right = 1110
|
||||
bar_top = 450
|
||||
bar_bottom = 486
|
||||
clone_bar_top = 442
|
||||
clone_bar_bottom = 460
|
||||
view_bar_top = 474
|
||||
view_bar_bottom = 492
|
||||
track_width = panel_right - panel_left
|
||||
clone_scale_max = max(100, y_top(max(1, clone_total), ticks=4))
|
||||
fill_ratio = min(1.0, clone_total / clone_scale_max)
|
||||
fill_width = max(8.0, track_width * fill_ratio)
|
||||
mid_value = clone_scale_max // 2
|
||||
traffic_scale_max = max(100, y_top(max(1, clone_total, view_total), ticks=4))
|
||||
clone_fill_ratio = min(1.0, clone_total / traffic_scale_max)
|
||||
clone_fill_width = max(8.0, track_width * clone_fill_ratio)
|
||||
view_fill_ratio = min(1.0, view_total / traffic_scale_max)
|
||||
view_fill_width = max(8.0, track_width * view_fill_ratio)
|
||||
mid_value = traffic_scale_max // 2
|
||||
mid_x = panel_left + (track_width * 0.5)
|
||||
clone_panel.extend(
|
||||
[
|
||||
f' <rect x="{panel_left}" y="{bar_top}" width="{track_width}" height="{bar_bottom - bar_top}" rx="10" fill="#15263A" stroke="#2B4255" stroke-width="1"/>',
|
||||
f' <line x1="{panel_left}" y1="{bar_top - 8}" x2="{panel_left}" y2="{bar_bottom + 8}" stroke="#436280" stroke-width="1"/>',
|
||||
f' <line x1="{mid_x:.1f}" y1="{bar_top - 8}" x2="{mid_x:.1f}" y2="{bar_bottom + 8}" stroke="#436280" stroke-width="1"/>',
|
||||
f' <line x1="{panel_right}" y1="{bar_top - 8}" x2="{panel_right}" y2="{bar_bottom + 8}" stroke="#436280" stroke-width="1"/>',
|
||||
f' <rect x="{panel_left}" y="{bar_top}" width="{fill_width:.1f}" height="{bar_bottom - bar_top}" rx="10" fill="url(#cloneFill)"/>',
|
||||
f' <text x="{panel_left - 4}" y="{bar_top - 12}" text-anchor="start" fill="#9CC3E6" font-size="12" font-family="SF Pro Text, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif">0</text>',
|
||||
f' <text x="{mid_x:.1f}" y="{bar_top - 12}" text-anchor="middle" fill="#9CC3E6" font-size="12" font-family="SF Pro Text, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif">{mid_value}</text>',
|
||||
f' <text x="{panel_right + 4}" y="{bar_top - 12}" text-anchor="end" fill="#9CC3E6" font-size="12" font-family="SF Pro Text, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif">{clone_scale_max}</text>',
|
||||
f' <text x="{panel_left}" y="{bar_bottom + 24}" fill="#9CC3E6" font-size="13" font-family="SF Pro Text, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif">Scale: 0 to {clone_scale_max} clones in the last {CLONES_WINDOW_DAYS} days.</text>',
|
||||
f' <text x="{panel_left}" y="{clone_bar_top - 8}" fill="#C4B5FD" font-size="13" font-family="SF Pro Text, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif">Clones: {clone_total}</text>',
|
||||
f' <rect x="{panel_left}" y="{clone_bar_top}" width="{track_width}" height="{clone_bar_bottom - clone_bar_top}" rx="9" fill="#15263A" stroke="#2B4255" stroke-width="1"/>',
|
||||
f' <rect x="{panel_left}" y="{clone_bar_top}" width="{clone_fill_width:.1f}" height="{clone_bar_bottom - clone_bar_top}" rx="9" fill="url(#cloneFill)"/>',
|
||||
f' <text x="{panel_left}" y="{view_bar_top - 8}" fill="#7DD3FC" font-size="13" font-family="SF Pro Text, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif">Views: {view_total}</text>',
|
||||
f' <rect x="{panel_left}" y="{view_bar_top}" width="{track_width}" height="{view_bar_bottom - view_bar_top}" rx="9" fill="#15263A" stroke="#2B4255" stroke-width="1"/>',
|
||||
f' <rect x="{panel_left}" y="{view_bar_top}" width="{view_fill_width:.1f}" height="{view_bar_bottom - view_bar_top}" rx="9" fill="url(#viewFill)"/>',
|
||||
f' <line x1="{panel_left}" y1="{clone_bar_top - 16}" x2="{panel_left}" y2="{view_bar_bottom + 8}" stroke="#436280" stroke-width="1"/>',
|
||||
f' <line x1="{mid_x:.1f}" y1="{clone_bar_top - 16}" x2="{mid_x:.1f}" y2="{view_bar_bottom + 8}" stroke="#436280" stroke-width="1"/>',
|
||||
f' <line x1="{panel_right}" y1="{clone_bar_top - 16}" x2="{panel_right}" y2="{view_bar_bottom + 8}" stroke="#436280" stroke-width="1"/>',
|
||||
f' <text x="{panel_left - 2}" y="{view_bar_bottom + 20}" text-anchor="start" fill="#9CC3E6" font-size="12" font-family="SF Pro Text, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif">0</text>',
|
||||
f' <text x="{mid_x:.1f}" y="{view_bar_bottom + 20}" text-anchor="middle" fill="#9CC3E6" font-size="12" font-family="SF Pro Text, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif">{mid_value}</text>',
|
||||
f' <text x="{panel_right + 2}" y="{view_bar_bottom + 20}" text-anchor="end" fill="#9CC3E6" font-size="12" font-family="SF Pro Text, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif">{traffic_scale_max}</text>',
|
||||
f' <text x="{panel_left}" y="{view_bar_bottom + 34}" fill="#9CC3E6" font-size="13" font-family="SF Pro Text, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif">Shared scale: 0 to {traffic_scale_max} events in the last {CLONES_WINDOW_DAYS} days.</text>',
|
||||
]
|
||||
)
|
||||
|
||||
return """<svg width="1200" height="560" viewBox="0 0 1200 560" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="title desc">
|
||||
<title id="title">GitHub Release Downloads and Clone Trend</title>
|
||||
<desc id="desc">Line chart of release downloads with highlighted points and a scaled 14-day git clone volume bar.</desc>
|
||||
<title id="title">GitHub Release Downloads and Traffic Trend</title>
|
||||
<desc id="desc">Line chart of release downloads with 14-day traffic bars for clones and views.</desc>
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="0" y1="0" x2="1200" y2="560" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#061423"/>
|
||||
|
|
@ -269,6 +332,10 @@ def generate_svg(points: list[ReleasePoint], clone_total: int, snapshot_date: st
|
|||
<stop stop-color="#7C3AED"/>
|
||||
<stop offset="1" stop-color="#C084FC"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="viewFill" x1="86" y1="482" x2="1110" y2="482" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#0EA5E9"/>
|
||||
<stop offset="1" stop-color="#7DD3FC"/>
|
||||
</linearGradient>
|
||||
<filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
|
||||
<feGaussianBlur stdDeviation="4" result="blur"/>
|
||||
<feMerge>
|
||||
|
|
@ -339,12 +406,40 @@ def parse_existing_clone_snapshot(content: str) -> str | None:
|
|||
return value if value else None
|
||||
|
||||
|
||||
def parse_existing_view_total(content: str) -> int | None:
|
||||
match = re.search(r"GitHub views \(last \d+ days\): <strong>(\d+)</strong>\.", content)
|
||||
if not match:
|
||||
return None
|
||||
try:
|
||||
return int(match.group(1))
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def parse_existing_view_snapshot(content: str) -> str | None:
|
||||
match = re.search(r"View data snapshot \(UTC\): <strong>([^<]+)</strong>\.", content)
|
||||
if not match:
|
||||
return None
|
||||
value = match.group(1).strip()
|
||||
return value if value else None
|
||||
|
||||
|
||||
def shields_badge(label: str, message: str, color: str, style: str = "for-the-badge") -> str:
|
||||
encoded_label = urllib.parse.quote(label, safe="")
|
||||
encoded_message = urllib.parse.quote(message, safe="")
|
||||
return (
|
||||
f"https://img.shields.io/badge/{encoded_label}-{encoded_message}-{color}?style={style}"
|
||||
)
|
||||
|
||||
|
||||
def update_readme(
|
||||
content: str,
|
||||
latest_tag: str,
|
||||
total_downloads: int,
|
||||
clone_total: int,
|
||||
clone_snapshot_utc: str,
|
||||
view_total: int,
|
||||
view_snapshot_utc: str,
|
||||
today: str,
|
||||
) -> str:
|
||||
release_badge_line = (
|
||||
|
|
@ -358,56 +453,83 @@ def update_readme(
|
|||
content,
|
||||
)
|
||||
content = re.sub(
|
||||
r'<p align="center">Snapshot total downloads: <strong>\d+</strong> across releases\.</p>',
|
||||
f'<p align="center">Snapshot total downloads: <strong>{total_downloads}</strong> across releases.</p>',
|
||||
r'(?m)^<p align="center">Git clones \(last \d+ days\): <strong>\d+</strong>\.</p>\s*$',
|
||||
"",
|
||||
content,
|
||||
)
|
||||
content = re.sub(
|
||||
r'(?m)^<p align="center">GitHub views \(last \d+ days\): <strong>\d+</strong>\.</p>\s*$',
|
||||
"",
|
||||
content,
|
||||
)
|
||||
content = re.sub(
|
||||
r'(?m)^<p align="center">Clone data snapshot \(UTC\): <strong>[^<]+</strong>\.</p>\s*$',
|
||||
"",
|
||||
content,
|
||||
)
|
||||
content = re.sub(
|
||||
r'(?m)^<p align="center">View data snapshot \(UTC\): <strong>[^<]+</strong>\.</p>\s*$',
|
||||
"",
|
||||
content,
|
||||
)
|
||||
content = re.sub(
|
||||
r'(?m)^<p align="center">Snapshot total downloads: <strong>\d+</strong> across releases\.</p>\s*$',
|
||||
"",
|
||||
content,
|
||||
)
|
||||
clone_line = f'<p align="center">Git clones (last {CLONES_WINDOW_DAYS} days): <strong>{clone_total}</strong>.</p>'
|
||||
if re.search(r'<p align="center">Git clones \(last \d+ days\): <strong>\d+</strong>\.</p>', content):
|
||||
content = re.sub(
|
||||
r'<p align="center">Git clones \(last \d+ days\): <strong>\d+</strong>\.</p>',
|
||||
clone_line,
|
||||
content,
|
||||
)
|
||||
else:
|
||||
content = content.replace(
|
||||
'<p align="center">Snapshot total downloads: <strong>',
|
||||
clone_line + "\n<p align=\"center\">Snapshot total downloads: <strong>",
|
||||
1,
|
||||
)
|
||||
clone_snapshot_line = f'<p align="center">Clone data snapshot (UTC): <strong>{clone_snapshot_utc}</strong>.</p>'
|
||||
if re.search(r'<p align="center">Clone data snapshot \(UTC\): <strong>[^<]+</strong>\.</p>', content):
|
||||
content = re.sub(
|
||||
r'<p align="center">Clone data snapshot \(UTC\): <strong>[^<]+</strong>\.</p>',
|
||||
clone_snapshot_line,
|
||||
content,
|
||||
)
|
||||
else:
|
||||
content = content.replace(
|
||||
clone_line,
|
||||
clone_line + "\n" + clone_snapshot_line,
|
||||
1,
|
||||
)
|
||||
content = re.sub(
|
||||
r'(?m)^<p align="center"><strong>Release Download Trend</strong></p>$',
|
||||
'<p align="center"><strong>Release Download + Clone Trend</strong></p>',
|
||||
'<p align="center"><strong>Release Download + Traffic Trend</strong></p>',
|
||||
content,
|
||||
)
|
||||
content = re.sub(
|
||||
r'(?m)^<p align="center"><strong>Release Download \+ Clone Trend</strong></p>$',
|
||||
'<p align="center"><strong>Release Download + Traffic Trend</strong></p>',
|
||||
content,
|
||||
)
|
||||
content = re.sub(
|
||||
r'(?m)^<p align="center"><em>Styled line chart with highlighted points shows per-release totals and trend direction\.</em></p>$',
|
||||
'<p align="center"><em>Styled line chart shows per-release totals plus a scaled 14-day git clone volume bar.</em></p>',
|
||||
'<p align="center"><em>Styled line chart shows per-release totals with 14-day traffic counters for clones and views.</em></p>',
|
||||
content,
|
||||
)
|
||||
content = re.sub(
|
||||
r'(?m)^<p align="center"><em>Styled line chart shows per-release totals plus a 14-day git clone sparkline\.</em></p>$',
|
||||
'<p align="center"><em>Styled line chart shows per-release totals plus a scaled 14-day git clone volume bar.</em></p>',
|
||||
'<p align="center"><em>Styled line chart shows per-release totals with 14-day traffic counters for clones and views.</em></p>',
|
||||
content,
|
||||
)
|
||||
content = re.sub(
|
||||
r'(?m)^<p align="center"><em>Styled line chart shows per-release totals plus a 14-day git clone volume strip\.</em></p>$',
|
||||
'<p align="center"><em>Styled line chart shows per-release totals plus a scaled 14-day git clone volume bar.</em></p>',
|
||||
'<p align="center"><em>Styled line chart shows per-release totals with 14-day traffic counters for clones and views.</em></p>',
|
||||
content,
|
||||
)
|
||||
content = re.sub(
|
||||
r'(?m)^<p align="center"><em>Styled line chart shows per-release totals plus a scaled 14-day git clone volume bar\.</em></p>$',
|
||||
'<p align="center"><em>Styled line chart shows per-release totals with 14-day traffic counters for clones and views.</em></p>',
|
||||
content,
|
||||
)
|
||||
traffic_badges = (
|
||||
"<p align=\"center\">\n"
|
||||
f" <img alt=\"Git clones (14d)\" src=\"{shields_badge('Git clones (14d)', str(clone_total), '7C3AED')}\">\n"
|
||||
f" <img alt=\"GitHub views (14d)\" src=\"{shields_badge('GitHub views (14d)', str(view_total), '0EA5E9')}\">\n"
|
||||
"</p>\n"
|
||||
"<p align=\"center\">\n"
|
||||
f" <img alt=\"Clone snapshot (UTC)\" src=\"{shields_badge('Clone snapshot (UTC)', clone_snapshot_utc, '334155', style='flat-square')}\">\n"
|
||||
f" <img alt=\"View snapshot (UTC)\" src=\"{shields_badge('View snapshot (UTC)', view_snapshot_utc, '334155', style='flat-square')}\">\n"
|
||||
"</p>"
|
||||
)
|
||||
old_badges_pattern = (
|
||||
r'(?s)<p align="center">\s*<img alt="Git clones \(14d\)".*?</p>\s*'
|
||||
r'<p align="center">\s*<img alt="Clone snapshot \(UTC\)".*?</p>'
|
||||
)
|
||||
if re.search(old_badges_pattern, content):
|
||||
content = re.sub(old_badges_pattern, traffic_badges, content)
|
||||
else:
|
||||
content = content.replace(
|
||||
'<p align="center"><em>Styled line chart shows per-release totals with 14-day traffic counters for clones and views.</em></p>',
|
||||
'<p align="center"><em>Styled line chart shows per-release totals with 14-day traffic counters for clones and views.</em></p>\n'
|
||||
+ traffic_badges,
|
||||
1,
|
||||
)
|
||||
content = re.sub(
|
||||
r"(?m)^> Latest release: \*\*.*\*\*$",
|
||||
f"> Latest release: **{latest_tag}**",
|
||||
|
|
@ -425,6 +547,7 @@ def update_readme(
|
|||
)
|
||||
if f"label={latest_tag}" not in content:
|
||||
raise RuntimeError("README download badge replacement failed.")
|
||||
content = re.sub(r"\n</p>\n{3,}## Project Docs", "\n</p>\n\n## Project Docs", content)
|
||||
return content
|
||||
|
||||
|
||||
|
|
@ -447,6 +570,7 @@ def main() -> int:
|
|||
args = parse_args()
|
||||
releases = fetch_releases()
|
||||
clone_points, clone_total_api, clone_latest_timestamp = fetch_clone_traffic()
|
||||
view_points, view_total_api, view_latest_timestamp = fetch_view_traffic()
|
||||
releases_desc = sorted(releases, key=lambda r: r.published_at, reverse=True)
|
||||
latest = releases_desc[0]
|
||||
total_downloads = sum(r.downloads for r in releases_desc)
|
||||
|
|
@ -457,12 +581,20 @@ def main() -> int:
|
|||
readme_before = README.read_text(encoding="utf-8")
|
||||
existing_clone_total = parse_existing_clone_total(readme_before)
|
||||
existing_clone_snapshot = parse_existing_clone_snapshot(readme_before)
|
||||
existing_view_total = parse_existing_view_total(readme_before)
|
||||
existing_view_snapshot = parse_existing_view_snapshot(readme_before)
|
||||
if args.require_clone_api and (clone_total_api is None or clone_latest_timestamp is None):
|
||||
print(
|
||||
"Clone traffic API unavailable. Refusing to reuse stale README clone metrics.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
if args.require_clone_api and (view_total_api is None or view_latest_timestamp is None):
|
||||
print(
|
||||
"View traffic API unavailable. Refusing to reuse stale README view metrics.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
if clone_total_api is None:
|
||||
print(
|
||||
"Warning: clone traffic API unavailable; reusing existing README clone total.",
|
||||
|
|
@ -473,13 +605,25 @@ def main() -> int:
|
|||
clone_snapshot_utc = clone_latest_timestamp.astimezone(dt.UTC).strftime("%Y-%m-%d %H:%M")
|
||||
else:
|
||||
clone_snapshot_utc = existing_clone_snapshot or f"{snapshot_date} 00:00"
|
||||
svg = generate_svg(trend_points, clone_total, snapshot_date)
|
||||
if view_total_api is None:
|
||||
print(
|
||||
"Warning: view traffic API unavailable; reusing existing README view total.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
view_total = view_total_api if view_total_api is not None else (existing_view_total or 0)
|
||||
if view_latest_timestamp is not None:
|
||||
view_snapshot_utc = view_latest_timestamp.astimezone(dt.UTC).strftime("%Y-%m-%d %H:%M")
|
||||
else:
|
||||
view_snapshot_utc = existing_view_snapshot or f"{snapshot_date} 00:00"
|
||||
svg = generate_svg(trend_points, clone_total, view_total, snapshot_date)
|
||||
readme_after = update_readme(
|
||||
readme_before,
|
||||
latest_tag=latest.tag,
|
||||
total_downloads=total_downloads,
|
||||
clone_total=clone_total,
|
||||
clone_snapshot_utc=clone_snapshot_utc,
|
||||
view_total=view_total,
|
||||
view_snapshot_utc=view_snapshot_utc,
|
||||
today=snapshot_date,
|
||||
)
|
||||
|
||||
|
|
@ -498,7 +642,8 @@ def main() -> int:
|
|||
print(
|
||||
f"Updated metrics: latest={latest.tag} ({latest.downloads}) total={total_downloads} "
|
||||
f"clones14d={clone_total} points={len(trend_points)} clone_points={len(clone_points)} "
|
||||
f"clone_snapshot_utc={clone_snapshot_utc}"
|
||||
f"clone_snapshot_utc={clone_snapshot_utc} views14d={view_total} "
|
||||
f"view_points={len(view_points)} view_snapshot_utc={view_snapshot_utc}"
|
||||
)
|
||||
return 0
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue