Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
c1cc945
Add categories and tags placeholders to blog post templates
mrbiggred Feb 24, 2026
602c2f0
Refactor reminder messages in create_post.sh to be consistent with th…
mrbiggred Feb 24, 2026
5a5618e
Refactor date calculation in create_post.sh for compatibility with ma…
mrbiggred Feb 24, 2026
e768f37
Add comment to category and tag section reminding people to use exist…
mrbiggred Feb 25, 2026
7700ed3
Fix indentation in YAML frontmatter comments and correct typo in READ…
mrbiggred Feb 25, 2026
6073e57
Add a script to harvest tags and categories. Initially by Grok, then…
normanlorrain Feb 25, 2026
57a0a98
restored the mistakenly auto-formatted lines
normanlorrain Feb 25, 2026
9e8a387
Refactor find_tags_categories.py to use string parsing instead of YAM…
mrbiggred Mar 9, 2026
17ee852
Printer out tags in alphabetical order
mrbiggred Mar 9, 2026
2ad9497
Moved the find tags script from docs/ to the new scripts/ folder.
mrbiggred Mar 9, 2026
a0e47b4
Fixed path to find tags script in create post scripts.
mrbiggred Mar 9, 2026
49848f9
Updated documentation about the find tags script.
mrbiggred Mar 9, 2026
82a366a
Removed unneeded check.
mrbiggred Mar 9, 2026
fe91f73
Moved discard tags to outside the loop.
mrbiggred Mar 9, 2026
ed97a82
Updated README tag/category examples to match the scripts
mrbiggred Mar 9, 2026
f0d1cae
Python should be launched with "py" or "python",
normanlorrain Mar 9, 2026
c5317c8
Re-added the YAML library to the find tags script.
mrbiggred Mar 17, 2026
ccf9aaa
Guard Python calls in create_post scripts to handle missing Python gr…
mrbiggred Mar 19, 2026
3ad2aac
Fix incorrect return type annotation in extract_frontmatter()
mrbiggred Mar 19, 2026
6884682
Normalize tags/categories to handle scalars and None in find_tags_cat…
mrbiggred Mar 19, 2026
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
26 changes: 21 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,13 @@ These scripts will:
authors:
- chris
categories:
- Category Name
# use existing categories when possible, in YAML list format.
- CATEGORY_ONE
- CATEGORY_TWO
tags:
- relevant-tag
# use existing tags when possible, in YAML list format.
- TAG_ONE
- TAG_TWO
---
```

Expand All @@ -116,11 +120,21 @@ These scripts will:

### Categories and Tags

Categories are broad topic groupings and tags are specific topic labels for filtering. New categories and tags can be added as needed — these are the ones currently in use.
Categories are broad topic groupings and tags are specific topic labels for filtering. **Please use existing categories and tags when possible** to keep the taxonomy consistent. New ones can be added when truly needed.

**Categories**: Business, Career, Community, Culture, Security, Technical
To see all categories and tags currently in use, run:

**Tags**: `administration`, `advent-of-code`, `advent-of-cyber`, `agile`, `ai`, `architecture`, `branding`, `cancelled`, `career`, `change-management`, `claude`, `code-quality`, `coding-challenges`, `coding-practices`, `community`, `conferences`, `creativity`, `ctf`, `culture`, `curriculum`, `cybersecurity`, `data`, `databases`, `design-patterns`, `devops`, `editors`, `education`, `entrepreneurship`, `ergonomics`, `estimation`, `ethics`, `events`, `finance`, `github-copilot`, `goals`, `gratitude`, `growth`, `habits`, `hacktoberfest`, `hardware`, `health`, `hiring`, `history`, `holiday`, `industry`, `infrastructure`, `innovation`, `inspiration`, `irl`, `job-market`, `learning`, `local-tech`, `marketing`, `meetup`, `meetups`, `metaphors`, `methodology`, `metrics`, `mob-programming`, `motivation`, `naming`, `networking`, `open-discussion`, `open-source`, `optimization`, `organizations`, `pair-programming`, `performance`, `philosophy`, `picoctf`, `predictions`, `priorities`, `privacy`, `productivity`, `project-management`, `prototyping`, `quality-assurance`, `reflection`, `remote-work`, `resilience`, `security`, `self-hosting`, `self-improvement`, `side-projects`, `society`, `supply-chain`, `surveys`, `swap-meet`, `systems`, `tdd`, `teaching`, `teamwork`, `technology`, `testing`, `thinking`, `time-management`, `tools`, `trends`, `unconference`, `version-control`, `watch-party`, `wellness`, `work-life-balance`, `workplace`, `workspace`, `year-in-review`
**Bash (Linux/macOS)**:
```bash
python3 scripts/find_tags_categories.py
```

**PowerShell (Windows)**:
```powershell
python scripts/find_tags_categories.py
```

This script requires `pyyaml`, which is included in `requirements.txt`. It is also run automatically by the `create_post` scripts when scaffolding a new post.

## Project Structure

Expand All @@ -131,6 +145,8 @@ Categories are broad topic groupings and tags are specific topic labels for filt
├── docker-compose.yml # Docker development environment
├── create_post.sh # Bash script to create blog posts
├── create_post.ps1 # PowerShell script to create blog posts
├── scripts/
│ └── find_tags_categories.py # List all existing tags and categories
├── .github/
│ ├── dependabot.yml # Dependabot configuration
│ └── workflows/
Expand Down
23 changes: 22 additions & 1 deletion create_post.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ title: "$title"
date: $date
authors:
- chris | norm | omar
categories:
# use existing categories when possible, in YAML list format.
- CATEGORY_ONE
- CATEGORY_TWO
tags:
# use existing tags when possible, in YAML list format.
- TAG_ONE
- TAG_TWO
---

TOPIC_INTRODUCTION_HERE
Expand All @@ -55,4 +63,17 @@ Everyone and anyone are welcome to [join](https://weeklydevchat.com/join/) as lo
# Write the content to the file
Set-Content -Path $filePath -Value $yamlContent

Write-Output "Blog post template created at $filePath"
Write-Output "Blog post template created at $filePath"
Write-Output ""
Write-Output "Reminder: Use existing categories and tags when possible."
# Optionally suggest existing tags and categories using the helper script, if available
$pythonCmd = Get-Command python -ErrorAction SilentlyContinue
if ($null -ne $pythonCmd) {
& python "./scripts/find_tags_categories.py"
if ($LASTEXITCODE -ne 0) {
Write-Warning "python ./scripts/find_tags_categories.py exited with code $LASTEXITCODE. The blog post file was created, but tag/category suggestions may be unavailable."
}
}
else {
Write-Warning "Python was not found on this system. Skipping tag/category suggestion step."
}
38 changes: 30 additions & 8 deletions create_post.sh
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
#!/bin/bash

# Calculate the next Tuesday (or today if it's Tuesday)
# In Linux date, weekday: 0=Sunday, 1=Monday, ..., 6=Saturday
current_weekday=$(date +%w) # 0=Sun ... 6=Sat
days_to_tuesday=$(( (2 - current_weekday + 7) % 7 ))

# If today is Tuesday, days_to_tuesday will be 0 → perfect
# Add the calculated days
tuesday=$(date -d "+${days_to_tuesday} days" +%Y-%m-%d)
# Add the calculated days (compatible with both macOS and Linux)
if date -v +0d &>/dev/null; then
# macOS (BSD date)
tuesday=$(date -v "+${days_to_tuesday}d" +%Y-%m-%d)
else
# Linux (GNU date)
tuesday=$(date -d "+${days_to_tuesday} days" +%Y-%m-%d)
fi

year=$(date -d "$tuesday" +%Y)
month=$(date -d "$tuesday" +%02m)
day=$(date -d "$tuesday" +%02d)
year=${tuesday%%-*} # 2026
month=${tuesday#*-}; month=${month%-*} # 02
day=${tuesday##*-} # 24

# Define paths
folder_path="docs/posts/$year/$month/$day"
Expand All @@ -27,6 +31,14 @@ title: "Your Blog Post Title"
date: $tuesday
authors:
- chris | norm | omar
categories:
# use existing categories when possible, in YAML list format.
- CATEGORY_ONE
- CATEGORY_TWO
tags:
# use existing tags when possible, in YAML list format.
- TAG_ONE
- TAG_TWO
---

TOPIC_INTRODUCTION_HERE
Expand All @@ -36,4 +48,14 @@ Everyone and anyone are welcome to [join](https://weeklydevchat.com/join/) as lo
![alt text](${tuesday}_image_filename.webp)
EOF

echo "Blog post template created at $file_path"
echo "Blog post template created at $file_path"
echo ""
echo "Reminder: Use existing categories and tags when possible."

# Optionally suggest existing tags and categories using the helper script, if available
if command -v python3 &>/dev/null; then
python3 "./scripts/find_tags_categories.py" || echo "Warning: tag/category suggestion script failed, but the blog post file was created."
else
echo "Warning: Python 3 was not found on this system. Skipping tag/category suggestion step."
fi

84 changes: 84 additions & 0 deletions scripts/find_tags_categories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#!/usr/bin/env python3
"""
Lists all unique tags and categories found in YAML front matter of .md files.
Looks for both 'tags' and 'categories' keys (common variations).
"""

import yaml
from pathlib import Path
from typing import Any

DOCS_DIR = Path(__file__).parent.parent / "docs"
EXTENSIONS = (".md", ".markdown", ".mkd")


def extract_frontmatter(file_path: Path) -> dict[str, Any]:
"""Extract YAML front matter if present."""
content = file_path.read_text(encoding="utf-8")
if not content.startswith("---"):
return {}
try:
parts = content.split("---", 2)
if len(parts) < 3:
return {}
fm = yaml.safe_load(parts[1])
if not isinstance(fm, dict):
return {}
return fm
except yaml.YAMLError:
print(f"Warning: Invalid YAML in {file_path}")
return {}


def collect_tags() -> tuple[set[str], set[str]]:
all_tags = set()
all_categories = set()

docs_path = Path(DOCS_DIR)
if not docs_path.is_dir():
print(f"Error: Directory not found: {docs_path}")
return all_tags, all_categories

for file_path in docs_path.rglob("*"):
if not file_path.is_file() or file_path.suffix.lower() not in EXTENSIONS:
continue

fm = extract_frontmatter(file_path)

tags = fm.get("tags") or []
if isinstance(tags, str):
tags = [tags]
elif not isinstance(tags, list):
tags = []
all_tags.update(str(t).strip() for t in tags if t)

cats = fm.get("categories") or []
if isinstance(cats, str):
cats = [cats]
elif not isinstance(cats, list):
cats = []
all_categories.update(str(c).strip() for c in cats if c)

all_tags.discard('TAG_ONE')
all_tags.discard('TAG_TWO')
all_categories.discard('CATEGORY_ONE')
all_categories.discard('CATEGORY_TWO')

return all_tags, all_categories


def main() -> None:
tags, categories = collect_tags()

print("\nExisting categories:")
for cat in sorted(categories):
print(f" {cat}")
print(f"\nExisting tags:")
for tag in sorted(tags):
print(f" {tag}")


if __name__ == "__main__":
main()


Loading