Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions .github/scripts/inject_metadata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#!/usr/bin/env python3
"""
Generates _data/page_metadata.json with git metadata for every Markdown file.
Uses the GitHub API when GITHUB_TOKEN is set, otherwise falls back to git log.
Source .md files are never modified.
"""
import json
import os
import subprocess
import sys
import urllib.error
import urllib.parse
import urllib.request
from pathlib import Path

REPO = "codeday/docs"
TOKEN = os.environ.get("GITHUB_TOKEN")


def github_commits(file_path: str):
if not TOKEN:
return None
url = f"https://api.github.com/repos/{REPO}/commits?path={urllib.parse.quote(file_path)}&per_page=100"
req = urllib.request.Request(
url,
headers={
"Authorization": f"Bearer {TOKEN}",
"Accept": "application/vnd.github.v3+json",
},
)
try:
with urllib.request.urlopen(req) as r:
return json.loads(r.read())
except urllib.error.HTTPError as e:
print(f" GitHub API {e.code} for {file_path}", file=sys.stderr)
return None


def get_metadata(file_path: str) -> dict:
commits = github_commits(file_path)
if commits:
last = commits[0]["commit"]["committer"]["date"].split("T")[0]
latest = commits[0]
last_login = (latest.get("author") or {}).get("login")
last_name = latest["commit"]["author"]["name"]

seen, authors = set(), []
for c in commits:
login = (c.get("author") or {}).get("login")
name = c["commit"]["author"]["name"]
key = login or name
if key not in seen:
seen.add(key)
authors.append({"login": login, "name": name})

return {
"last_modified_date": last,
"last_modified_by": {"login": last_login, "name": last_name},
"authors": authors,
}

# Fallback: local git log
last_commit = subprocess.run(
["git", "log", "-1", "--format=%ai\t%an", "--", file_path],
capture_output=True, text=True,
).stdout.strip()
last_date, last_author_name = (last_commit.split("\t") + ["", ""])[:2]
last_date = last_date.split(" ")[0] or None

authors_out = subprocess.run(
["git", "log", "--format=%an", "--", file_path],
capture_output=True, text=True,
).stdout.strip().split("\n")
seen, authors = set(), []
for name in authors_out:
if name and name not in seen:
seen.add(name)
authors.append({"login": None, "name": name})

return {
"last_modified_date": last_date,
"last_modified_by": {"login": None, "name": last_author_name} if last_author_name else None,
"authors": authors,
}


# ---- main ---------------------------------------------------------------
source = Path(__file__).parent.parent.parent
md_files = [
p for p in source.rglob("*.md")
if not any(part.startswith(".") or part in ("_site", "node_modules") for part in p.parts)
]

metadata = {}
for filepath in sorted(md_files):
rel = str(filepath.relative_to(source))
print(f"Processing {rel} …", end=" ", flush=True)
metadata[rel] = get_metadata(rel)
print("done")

out = source / "_data" / "page_metadata.json"
out.parent.mkdir(exist_ok=True)
out.write_text(json.dumps(metadata, indent=2), encoding="utf-8")
print(f"\nWrote {out.relative_to(source)} ({len(metadata)} entries)")
6 changes: 6 additions & 0 deletions .github/workflows/jekyll.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Ruby
# https://github.com/ruby/setup-ruby/releases/tag/v1.207.0
uses: ruby/setup-ruby@4a9ddd6f338a97768b8006bf671dfbad383215f4
Expand All @@ -43,6 +45,10 @@ jobs:
- name: Setup Pages
id: pages
uses: actions/configure-pages@v5
- name: Inject page metadata
run: python3 .github/scripts/inject_metadata.py
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build with Jekyll
# Outputs to the './_site' directory by default
run: bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}"
Expand Down
1 change: 1 addition & 0 deletions .ruby-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.3.6
49 changes: 49 additions & 0 deletions _includes/footer_custom.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{% assign meta = site.data.page_metadata[page.path] %}
{% if meta %}
<div class="page-meta">
{% if meta.authors and meta.authors.size > 0 %}
<span class="page-meta-item">
{% if meta.authors.size > 1 %}Authors:{% else %}Author:{% endif %}
{% for author in meta.authors %}
{% if author.login %}
<a href="https://github.com/{{ author.login }}" target="_blank" rel="noopener">{{ author.name | default: author.login }}</a>
{% else %}
{{ author.name }}
{% endif %}
{% unless forloop.last %}, {% endunless %}
{% endfor %}
</span>
{% endif %}
{% if meta.last_modified_date %}
<span class="page-meta-item">
Last modified {{ meta.last_modified_date | date: "%b %-d, %Y" }}
{% if meta.last_modified_by %}
by
{% if meta.last_modified_by.login %}
<a href="https://github.com/{{ meta.last_modified_by.login }}" target="_blank" rel="noopener">{{ meta.last_modified_by.name | default: meta.last_modified_by.login }}</a>
{% else %}
{{ meta.last_modified_by.name }}
{% endif %}
{% endif %}
</span>
{% endif %}
</div>
{% endif %}

<style>
.page-meta {
margin-top: 0.75rem;
display: flex;
flex-wrap: wrap;
gap: 1rem;
font-size: 0.8rem;
color: inherit;
opacity: 0.75;
margin-bottom: 1rem;
}
.page-meta-item {
display: inline-flex;
align-items: center;
gap: 0.3rem;
}
</style>