206 lines
7.3 KiB
Python
206 lines
7.3 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Auto-Publish to Astro Content Collections
|
|
|
|
Publishes blog posts to Astro content collections,
|
|
commits to git, and triggers auto-deploy.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import subprocess
|
|
import argparse
|
|
import re
|
|
from pathlib import Path
|
|
from datetime import datetime
|
|
from typing import Dict, Optional
|
|
|
|
|
|
class AstroPublisher:
|
|
"""Publish blog posts to Astro content collections"""
|
|
|
|
def __init__(self, website_repo: str):
|
|
"""
|
|
Initialize Astro publisher
|
|
|
|
Args:
|
|
website_repo: Path to Astro website repository
|
|
"""
|
|
self.website_repo = website_repo
|
|
self.content_dir = os.path.join(website_repo, 'src/content/blog')
|
|
self.images_dir = os.path.join(website_repo, 'public/images/blog')
|
|
|
|
def detect_language(self, content: str) -> str:
|
|
"""Detect if content is Thai or English"""
|
|
thai_chars = sum(1 for c in content if '\u0E00' <= c <= '\u0E7F')
|
|
total_chars = len(content)
|
|
thai_ratio = thai_chars / total_chars if total_chars > 0 else 0
|
|
return 'th' if thai_ratio > 0.3 else 'en'
|
|
|
|
def generate_slug(self, title: str, lang: str = 'en') -> str:
|
|
"""Generate URL-friendly slug"""
|
|
# Remove special characters
|
|
slug = re.sub(r'[^\w\s-]', '', title.lower())
|
|
# Replace whitespace with hyphens
|
|
slug = re.sub(r'[-\s]+', '-', slug)
|
|
# Remove leading/trailing hyphens
|
|
slug = slug.strip('-_')
|
|
# Limit length
|
|
return slug[:100]
|
|
|
|
def parse_frontmatter(self, content: str) -> Dict:
|
|
"""Parse frontmatter from markdown content"""
|
|
import yaml
|
|
|
|
if not content.startswith('---'):
|
|
return {}
|
|
|
|
try:
|
|
# Extract frontmatter
|
|
parts = content.split('---', 2)
|
|
if len(parts) >= 2:
|
|
frontmatter = yaml.safe_load(parts[1])
|
|
return frontmatter or {}
|
|
except:
|
|
pass
|
|
|
|
return {}
|
|
|
|
def publish(self, markdown_content: str, images: list = None, use_git: bool = False) -> Dict:
|
|
"""
|
|
Publish blog post to Astro content collections
|
|
|
|
Args:
|
|
markdown_content: Full markdown with frontmatter
|
|
images: List of image paths to copy
|
|
use_git: Whether to git commit and push (default: False - direct write only)
|
|
|
|
Returns:
|
|
Publication result
|
|
"""
|
|
try:
|
|
# Parse frontmatter
|
|
frontmatter = self.parse_frontmatter(markdown_content)
|
|
|
|
# Get required fields
|
|
title = frontmatter.get('title', 'Untitled')
|
|
slug = frontmatter.get('slug') or self.generate_slug(title)
|
|
lang = frontmatter.get('lang') or self.detect_language(markdown_content)
|
|
|
|
# Determine output path
|
|
lang_folder = f'({lang})'
|
|
output_dir = os.path.join(self.content_dir, lang_folder)
|
|
os.makedirs(output_dir, exist_ok=True)
|
|
|
|
output_path = os.path.join(output_dir, f'{slug}.md')
|
|
|
|
# Write markdown file (ALWAYS do this)
|
|
with open(output_path, 'w', encoding='utf-8') as f:
|
|
f.write(markdown_content)
|
|
|
|
print(f"\n✓ Saved: {output_path}")
|
|
|
|
# Copy images if provided
|
|
if images:
|
|
images_output = os.path.join(self.images_dir, slug)
|
|
os.makedirs(images_output, exist_ok=True)
|
|
|
|
for img_path in images:
|
|
if os.path.exists(img_path):
|
|
import shutil
|
|
shutil.copy(img_path, images_output)
|
|
print(f" ✓ Copied image: {os.path.basename(img_path)}")
|
|
|
|
# Git commit and push (OPTIONAL - only if requested and Gitea configured)
|
|
git_result = None
|
|
if use_git:
|
|
git_result = self.git_commit_and_push(slug, lang)
|
|
else:
|
|
print(f" ✓ Direct write complete (no git)")
|
|
|
|
return {
|
|
'success': True,
|
|
'slug': slug,
|
|
'language': lang,
|
|
'path': output_path,
|
|
'git_result': git_result,
|
|
'method': 'direct_write' if not use_git else 'git_push'
|
|
}
|
|
|
|
except Exception as e:
|
|
return {
|
|
'success': False,
|
|
'error': str(e)
|
|
}
|
|
|
|
def git_commit_and_push(self, slug: str, lang: str) -> Dict:
|
|
"""Commit and push changes to git"""
|
|
try:
|
|
# Check if git repo
|
|
if not os.path.exists(os.path.join(self.website_repo, '.git')):
|
|
return {'success': False, 'error': 'Not a git repository'}
|
|
|
|
# Git add
|
|
subprocess.run(['git', 'add', '.'], cwd=self.website_repo, check=True, capture_output=True)
|
|
|
|
# Git commit
|
|
message = f"Add blog post: {slug} ({lang})"
|
|
subprocess.run(['git', 'commit', '-m', message], cwd=self.website_repo, check=True, capture_output=True)
|
|
|
|
# Git push
|
|
subprocess.run(['git', 'push'], cwd=self.website_repo, check=True, capture_output=True)
|
|
|
|
print(f"✓ Committed: {message}")
|
|
print(f"✓ Pushed to remote")
|
|
|
|
return {
|
|
'success': True,
|
|
'commit_message': message,
|
|
'triggered_deploy': True
|
|
}
|
|
|
|
except subprocess.CalledProcessError as e:
|
|
print(f"✗ Git error: {e.stderr.decode() if e.stderr else str(e)}")
|
|
return {'success': False, 'error': 'Git operation failed'}
|
|
except Exception as e:
|
|
print(f"✗ Error: {e}")
|
|
return {'success': False, 'error': str(e)}
|
|
|
|
|
|
def main():
|
|
"""Test Astro publisher"""
|
|
parser = argparse.ArgumentParser(description='Publish to Astro')
|
|
parser.add_argument('--file', required=True, help='Markdown file to publish')
|
|
parser.add_argument('--website-repo', required=True, help='Path to website repo')
|
|
parser.add_argument('--image', action='append', help='Image files to copy')
|
|
parser.add_argument('--use-git', action='store_true', help='Use git commit/push (default: direct write only)')
|
|
|
|
args = parser.parse_args()
|
|
|
|
print(f"\n📝 Publishing to Astro\n")
|
|
|
|
# Read markdown file
|
|
with open(args.file, 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
|
|
# Publish (default: direct write, no git)
|
|
publisher = AstroPublisher(args.website_repo)
|
|
result = publisher.publish(content, args.image, use_git=args.use_git)
|
|
|
|
if result['success']:
|
|
print(f"\n✅ Published successfully!")
|
|
print(f" Slug: {result['slug']}")
|
|
print(f" Language: {result['language']}")
|
|
print(f" Path: {result['path']}")
|
|
print(f" Method: {result['method']}")
|
|
|
|
if result.get('git_result') and result['git_result'].get('success'):
|
|
print(f" ✓ Committed and pushed to Gitea")
|
|
print(f" ✓ Deployment triggered")
|
|
else:
|
|
print(f"\n❌ Publication failed: {result.get('error')}")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|