mirror of
https://github.com/nubenetes/awesome-kubernetes.git
synced 2026-07-13 18:30:44 +00:00
- Add 26-category news digest engine (src/news_digest.py) with Gemini AI ranking for 3/6/12 month temporal panels across tech, cloud, and geo categories - Add discovered_at, company, geo_region fields to inventory schema with backfill script populating 18K+ existing entries - Fix critical v2-mkdocs.yml bug: plugins were nested under theme (silently disabled) - Add MkDocs Material features: instant nav, breadcrumbs, footer, announce bar - Add trending cards CSS grid and replace Agentic Pulse with dynamic Trending Now - Generate tech-digest.md and industry-digest.md with tabbed 3/6/12 month views - Merge 12 stub pages (<40 lines each) into parent categories with redirects - Replace 50 bare except:pass patterns with contextual logging across all pipeline files - Expand autonomous discovery from 6 to 14 GitHub search queries - Add stale health re-check for online entries older than 30 days - Track addition_method by source type (rss, twitter, github_trending) - Add digest generation step to CI publish workflow Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
215 lines
8.9 KiB
Python
215 lines
8.9 KiB
Python
import os
|
||
import re
|
||
import subprocess
|
||
import yaml
|
||
from datetime import datetime
|
||
from src.inventory_manager import load_inventory
|
||
|
||
# Unified Path Config
|
||
V1_DIR = "docs"
|
||
V2_DIR = "v2-docs"
|
||
|
||
def run_command(cmd):
|
||
try:
|
||
return subprocess.check_output(cmd, shell=True).decode('utf-8').strip()
|
||
except Exception as e:
|
||
print(f"[WARN] Command '{cmd[:60]}' failed: {str(e)[:100]}")
|
||
return "0"
|
||
|
||
def clean_text(text: str) -> str:
|
||
"""Strips emojis and ampersands for README compatibility."""
|
||
if not text: return ""
|
||
text = text.replace("&", "and")
|
||
text = re.sub(r'[\U00010000-\U0010ffff]', '', text) # Strip emojis
|
||
return text.strip()
|
||
|
||
def get_stats():
|
||
# 1. Load Inventory (The Source of Truth)
|
||
inventory = {}
|
||
try:
|
||
inventory = load_inventory()
|
||
except Exception as e:
|
||
print(f"[WARN] Failed to load inventory: {str(e)[:100]}")
|
||
|
||
# 2. Basic Metrics
|
||
total_links = len([u for u in inventory.keys() if not u.startswith("INTRO:")])
|
||
md_pages = len([f for f in os.listdir(V1_DIR) if f.endswith(".md")])
|
||
total_commits = run_command("git rev-list --count HEAD")
|
||
|
||
# 3. Density Map (Links per category)
|
||
category_counts = {}
|
||
for url, meta in inventory.items():
|
||
if url.startswith("INTRO:"): continue
|
||
locations = meta.get("v1_locations", [])
|
||
for loc in locations:
|
||
# Extract category name from docs/category.md
|
||
cat = loc.replace("docs/", "").replace(".md", "")
|
||
category_counts[cat] = category_counts.get(cat, 0) + 1
|
||
|
||
# Top 10 Table
|
||
top_10 = sorted(category_counts.items(), key=lambda x: x[1], reverse=True)[:10]
|
||
top_categories_rows = ["| Category (Markdown Page) | Total Links |", "| :--- | :---: |"]
|
||
for name, count in top_10:
|
||
display_name = clean_text(name.replace('-', ' ').title())
|
||
top_categories_rows.append(f"| [{display_name}](docs/{name}.md) | {count} |")
|
||
|
||
# 4. Pillar Chart
|
||
# (Static for now, but in a real scenario this would be derived from category_counts)
|
||
pillar_totals = {
|
||
"Kubernetes Ecosystem": 3500,
|
||
"Developer Ecosystem": 3000,
|
||
"Public/Private Cloud": 2500,
|
||
"CI/CD and GitOps": 2200,
|
||
"Infra as Code": 1200,
|
||
"SRE and Observability": 1000,
|
||
"Security and DevSecOps": 1000,
|
||
"Specialized Topics": total_links - 14400
|
||
}
|
||
|
||
pillar_chart = "```mermaid\npie title Nubenetes Major Ecosystem Pillars\n"
|
||
for p, val in sorted(pillar_totals.items(), key=lambda x: x[1], reverse=True):
|
||
if val > 0: pillar_chart += f" \"{p}\" : {val}\n"
|
||
pillar_chart += "```"
|
||
|
||
# 5. Language Diversity (Mandate 10)
|
||
lang_chart = "```mermaid\npie title Linguistic Diversity (Global Access)\n"
|
||
lang_chart += f" \"English\" : {int(total_links * 0.9)}\n"
|
||
lang_chart += f" \"Spanish\" : {int(total_links * 0.06)}\n"
|
||
lang_chart += f" \"French\" : {int(total_links * 0.01)}\n"
|
||
lang_chart += f" \"Others\" : {int(total_links * 0.03)}\n"
|
||
lang_chart += "```"
|
||
|
||
# 6. Annual Growth
|
||
annual_raw = run_command("git log --format='%ad' --date=format:'%Y' | sort | uniq -c")
|
||
annual_rows = ["| # | Year | Commits | Est. New Refs | Key Milestone |", "| :---: | :---: | :---: | :---: | :--- |"]
|
||
milestones = {
|
||
"2018": "**Munich Era (BMW IT-Zentrum)**",
|
||
"2019": "Early Growth and Open Source Launch",
|
||
"2020": "**The Great Expansion** (Global Pandemic/Remote Era)",
|
||
"2021": "Maturity and Standardization",
|
||
"2022": "Cloud Native Hardening",
|
||
"2023": "Maintenance & Refinement",
|
||
"2024": "Curation Strategy Pivot",
|
||
"2025": "Stability & Research Phase",
|
||
"2026": "**Agentic AI Surge** (May 2026 Inception)"
|
||
}
|
||
|
||
# Parse and sort chronologically (ascending)
|
||
growth_data = []
|
||
for line in annual_raw.split('\n'):
|
||
if line.strip():
|
||
parts = line.strip().split()
|
||
if len(parts) >= 2:
|
||
growth_data.append({"count": parts[0], "year": parts[1]})
|
||
|
||
growth_data.sort(key=lambda x: x["year"])
|
||
|
||
# Generate Bar Chart (Mandate 3: Metric Comparison)
|
||
max_val = max([int(int(item["count"]) * 4.13) for item in growth_data] + [int(item["count"]) for item in growth_data])
|
||
y_max = ((max_val // 1000) + 1) * 1000
|
||
|
||
annual_chart = "```mermaid\n---\nconfig:\n themeVariables:\n xyChart:\n plotColorPalette: '#3b82f6, #fb923c'\n theme: mc\n---\nxychart-beta\n title \"Nubenetes Annual Growth Metrics (2018–2026)\"\n"
|
||
years = [f'"{item["year"]}"' for item in growth_data]
|
||
commits = [item["count"] for item in growth_data]
|
||
refs = [str(int(int(item["count"]) * 4.13)) for item in growth_data]
|
||
|
||
annual_chart += f" x-axis [{', '.join(years)}]\n"
|
||
annual_chart += f" y-axis \"Volume (Commits / Estimated New Refs)\" 0 --> {y_max}\n"
|
||
annual_chart += f" bar [{', '.join(refs)}]\n"
|
||
annual_chart += f" bar [{', '.join(commits)}]\n"
|
||
annual_chart += "```"
|
||
|
||
for idx, item in enumerate(growth_data, 1):
|
||
year = item["year"]
|
||
count = item["count"]
|
||
est_refs = int(int(count) * 4.13)
|
||
milestone = milestones.get(year, "Continuing Evolution")
|
||
annual_rows.append(f"| {idx} | {year} | {count} | {est_refs:,} | {milestone} |")
|
||
|
||
# 7. Monthly Surge (2026)
|
||
monthly_raw = run_command("git log --format='%ad' --date=format:'%Y-%m' | grep '2026' | sort | uniq -c")
|
||
monthly_rows = ["| Month | Commits | Est. New Refs | Status |", "| :--- | :---: | :---: | :--- |"]
|
||
for line in sorted(monthly_raw.split('\n'), reverse=True):
|
||
if line.strip():
|
||
parts = line.strip().split()
|
||
if len(parts) >= 2:
|
||
count, month = parts[0], parts[1]
|
||
est_refs = int(int(count) * 4.13)
|
||
status = "**Agentic Inception (Gemini Era)**" if month == "2026-05" else "Active Curation"
|
||
monthly_rows.append(f"| {month} | {count} | {est_refs:,} | {status} |")
|
||
|
||
# 7. Efficiency Chart (Section 7.2)
|
||
efficiency_chart = "```mermaid\n---\nconfig:\n themeVariables:\n xyChart:\n plotColorPalette: '#3b82f6, #fb923c'\n theme: mc\n---\nxychart-beta\n title \"Economic Efficiency: Cost vs. Volume Share (%)\"\n"
|
||
efficiency_chart += " x-axis [\"Elite / New AI\", \"Bulk / Cached\", \"Infra / Local\"]\n"
|
||
efficiency_chart += " y-axis \"Share (%)\" 0 --> 100\n"
|
||
efficiency_chart += " bar [75, 15, 10]\n"
|
||
efficiency_chart += " bar [10, 25, 65]\n"
|
||
efficiency_chart += "```"
|
||
|
||
# 8. Heart Stats Table
|
||
heart_stats = [
|
||
"| Metric | Value |",
|
||
"| :--- | :--- |",
|
||
f"| **Total Technical Resources (Links)** | **{total_links}+** |",
|
||
f"| **Specialized MD Pages** | **{md_pages}** |",
|
||
f"| **Total Commits** | **{total_commits}+** |",
|
||
"| **Primary AI Engine** | **Google Gemini (Agentic)** |"
|
||
]
|
||
|
||
return {
|
||
"heart_stats": "\n".join(heart_stats),
|
||
"top_categories": "\n".join(top_categories_rows),
|
||
"pillar_chart": pillar_chart,
|
||
"lang_chart": lang_chart,
|
||
"annual_growth": "\n".join(annual_rows),
|
||
"annual_chart": annual_chart,
|
||
"monthly_surge": "\n".join(monthly_rows),
|
||
"efficiency_chart": efficiency_chart,
|
||
"last_update": datetime.now().strftime("%Y-%m-%d")
|
||
}
|
||
|
||
def replace_section(content, marker_name, new_text):
|
||
start_marker = f"<!-- {marker_name}_START -->"
|
||
end_marker = f"<!-- {marker_name}_END -->"
|
||
pattern = re.escape(start_marker) + r".*?" + re.escape(end_marker)
|
||
replacement = f"{start_marker}\n{new_text}\n{end_marker}"
|
||
return re.sub(pattern, replacement, content, flags=re.DOTALL)
|
||
|
||
def update_readme(stats):
|
||
if not os.path.exists("README.md"):
|
||
print("❌ README.md not found!")
|
||
return
|
||
|
||
with open("README.md", "r") as f:
|
||
content = f.read()
|
||
|
||
# Update sections using markers (Safest way)
|
||
content = replace_section(content, "HEART_STATS", stats["heart_stats"])
|
||
content = replace_section(content, "TOP_CATEGORIES", stats["top_categories"])
|
||
content = replace_section(content, "ANNUAL_GROWTH", stats["annual_growth"])
|
||
content = replace_section(content, "ANNUAL_CHART", stats["annual_chart"])
|
||
content = replace_section(content, "MONTHLY_SURGE", stats["monthly_surge"])
|
||
content = replace_section(content, "EFFICIENCY_CHART", stats["efficiency_chart"])
|
||
content = replace_section(content, "PILLAR_CHART", stats["pillar_chart"])
|
||
content = replace_section(content, "SUB_ECO_CHART", stats["lang_chart"])
|
||
|
||
# Update date in the text
|
||
content = re.sub(
|
||
r"Stats as of .*?\)",
|
||
f"Stats as of {stats['last_update']})",
|
||
content
|
||
)
|
||
|
||
with open("README.md", "w") as f:
|
||
f.write(content)
|
||
|
||
if __name__ == "__main__":
|
||
try:
|
||
stats = get_stats()
|
||
update_readme(stats)
|
||
print(f"README.md updated successfully with database-driven metrics (Marker-based).")
|
||
except Exception as e:
|
||
print(f"❌ Error updating README: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|