Skip to content

Commit 3e5ef9b

Browse files
committed
md 파일 글 배포 날짜, 수정 날짜 자동 기입 기능 추가
1 parent a7c26b6 commit 3e5ef9b

2 files changed

Lines changed: 204 additions & 0 deletions

File tree

.github/scripts/stamp_dates.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
"""Stamp date/lastmod in Hugo front matter for added/modified blog posts.
2+
3+
Reads newline-separated paths from ADDED_FILES and MODIFIED_FILES env vars,
4+
rewrites only the date/lastmod lines in the front matter (preserving the rest
5+
verbatim), and signals whether anything changed via the `changed` output.
6+
"""
7+
8+
import os
9+
import re
10+
import sys
11+
from datetime import datetime, timezone, timedelta
12+
from pathlib import Path
13+
14+
KST = timezone(timedelta(hours=9))
15+
INDEX_NAME = "_index.md"
16+
BLOG_PREFIX = "content/blog/"
17+
DATE_RE = re.compile(r"^date:\s*.*$", re.MULTILINE)
18+
LASTMOD_RE = re.compile(r"^lastmod:\s*.*$", re.MULTILINE)
19+
20+
21+
def kst_now_iso() -> str:
22+
s = datetime.now(KST).strftime("%Y-%m-%dT%H:%M:%S%z")
23+
return s[:-2] + ":" + s[-2:]
24+
25+
26+
def split_front_matter(text: str) -> tuple[str, str] | None:
27+
if not text.startswith("---"):
28+
return None
29+
rest = text[3:]
30+
if rest.startswith("\n"):
31+
rest = rest[1:]
32+
sep = rest.find("\n---")
33+
if sep == -1:
34+
return None
35+
fm = rest[:sep]
36+
body = rest[sep + 4:]
37+
return fm, body
38+
39+
40+
def replace_or_insert(fm: str, key_re: re.Pattern, key: str, value: str) -> str:
41+
line = f"{key}: {value}"
42+
if key_re.search(fm):
43+
return key_re.sub(line, fm, count=1)
44+
if not fm.endswith("\n"):
45+
fm += "\n"
46+
return fm + line + "\n"
47+
48+
49+
def stamp(path: Path, set_date: bool, now: str) -> bool:
50+
original = path.read_text(encoding="utf-8")
51+
parts = split_front_matter(original)
52+
if parts is None:
53+
print(f" SKIP {path}: no front matter", file=sys.stderr)
54+
return False
55+
fm, body = parts
56+
57+
new_fm = fm
58+
if set_date:
59+
new_fm = replace_or_insert(new_fm, DATE_RE, "date", now)
60+
new_fm = replace_or_insert(new_fm, LASTMOD_RE, "lastmod", now)
61+
62+
if new_fm == fm:
63+
return False
64+
65+
new_text = "---\n" + new_fm
66+
if not new_text.endswith("\n"):
67+
new_text += "\n"
68+
new_text += "---" + body
69+
path.write_text(new_text, encoding="utf-8")
70+
return True
71+
72+
73+
def collect(env_name: str) -> list[Path]:
74+
raw_lines = [
75+
line.strip()
76+
for line in os.environ.get(env_name, "").splitlines()
77+
if line.strip()
78+
]
79+
paths = []
80+
for raw in raw_lines:
81+
if not raw.startswith(BLOG_PREFIX):
82+
continue
83+
p = Path(raw)
84+
if p.name == INDEX_NAME or p.suffix != ".md" or not p.exists():
85+
continue
86+
paths.append(p)
87+
return paths
88+
89+
90+
def write_output(name: str, value: str) -> None:
91+
out = os.environ.get("GITHUB_OUTPUT")
92+
if not out:
93+
return
94+
with open(out, "a", encoding="utf-8") as f:
95+
f.write(f"{name}={value}\n")
96+
97+
98+
def main() -> int:
99+
now = kst_now_iso()
100+
print(f"Stamping with {now}")
101+
102+
added = collect("ADDED_FILES")
103+
modified = [p for p in collect("MODIFIED_FILES") if p not in added]
104+
105+
changed = False
106+
for p in added:
107+
if stamp(p, set_date=True, now=now):
108+
print(f" stamped (new): {p}")
109+
changed = True
110+
for p in modified:
111+
if stamp(p, set_date=False, now=now):
112+
print(f" stamped (modified): {p}")
113+
changed = True
114+
115+
write_output("changed", "true" if changed else "false")
116+
return 0
117+
118+
119+
if __name__ == "__main__":
120+
sys.exit(main())

.github/workflows/stamp-dates.yml

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
name: Stamp post dates
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
paths:
8+
- 'content/blog/**/*.md'
9+
10+
permissions:
11+
contents: write
12+
13+
concurrency:
14+
group: stamp-dates
15+
cancel-in-progress: false
16+
17+
jobs:
18+
stamp:
19+
runs-on: ubuntu-latest
20+
steps:
21+
- name: Checkout
22+
uses: actions/checkout@v4
23+
with:
24+
fetch-depth: 0
25+
token: ${{ secrets.GITHUB_TOKEN }}
26+
27+
- name: Determine added/modified blog files
28+
id: diff
29+
run: |
30+
set -euo pipefail
31+
BEFORE="${{ github.event.before }}"
32+
AFTER="${{ github.sha }}"
33+
ZERO="0000000000000000000000000000000000000000"
34+
35+
if [ -z "$BEFORE" ] || [ "$BEFORE" = "$ZERO" ]; then
36+
echo "First push or unknown base; skipping."
37+
ADDED=""
38+
MODIFIED=""
39+
else
40+
ADDED=$(git diff --name-only --diff-filter=A "$BEFORE" "$AFTER" -- 'content/blog/**/*.md' || true)
41+
MODIFIED=$(git diff --name-only --diff-filter=M "$BEFORE" "$AFTER" -- 'content/blog/**/*.md' || true)
42+
fi
43+
44+
{
45+
echo "added<<__EOF__"
46+
echo "$ADDED"
47+
echo "__EOF__"
48+
echo "modified<<__EOF__"
49+
echo "$MODIFIED"
50+
echo "__EOF__"
51+
} >> "$GITHUB_OUTPUT"
52+
53+
echo "Added:"
54+
echo "$ADDED"
55+
echo "Modified:"
56+
echo "$MODIFIED"
57+
58+
- name: Set up Python
59+
if: steps.diff.outputs.added != '' || steps.diff.outputs.modified != ''
60+
uses: actions/setup-python@v5
61+
with:
62+
python-version: '3.12'
63+
64+
- name: Stamp dates
65+
id: stamp
66+
if: steps.diff.outputs.added != '' || steps.diff.outputs.modified != ''
67+
env:
68+
ADDED_FILES: ${{ steps.diff.outputs.added }}
69+
MODIFIED_FILES: ${{ steps.diff.outputs.modified }}
70+
run: python .github/scripts/stamp_dates.py
71+
72+
- name: Commit and push
73+
if: steps.stamp.outputs.changed == 'true'
74+
run: |
75+
set -euo pipefail
76+
git config user.name "github-actions[bot]"
77+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
78+
git add content/blog
79+
if git diff --cached --quiet; then
80+
echo "No staged changes after stamping."
81+
exit 0
82+
fi
83+
git commit -m "chore: stamp post dates [skip ci]"
84+
git push origin HEAD:main

0 commit comments

Comments
 (0)