diff --git a/README.md b/README.md index 3fcca455..1b0366fb 100644 --- a/README.md +++ b/README.md @@ -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 --- ``` @@ -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 @@ -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/ diff --git a/create_post.ps1 b/create_post.ps1 index 32455617..5f93aceb 100644 --- a/create_post.ps1 +++ b/create_post.ps1 @@ -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 @@ -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" \ No newline at end of file +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." +} diff --git a/create_post.sh b/create_post.sh old mode 100644 new mode 100755 index 596ee4ac..3359938a --- a/create_post.sh +++ b/create_post.sh @@ -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" @@ -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 @@ -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" \ No newline at end of file +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 + diff --git a/scripts/find_tags_categories.py b/scripts/find_tags_categories.py new file mode 100644 index 00000000..6dc72893 --- /dev/null +++ b/scripts/find_tags_categories.py @@ -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() + +