Testing Code with Claude for Automation

Posted by Ben Weaver on March 02, 2025
Claude

I’ve been putting off writing blog posts since switching over to the new, improved teachweaver site because the process annoys me. Writing is fine, but converting the post to HTML, adding it to a copy of my blog template, updating the meta tags, and modifying posts.json list of all my entries was taking entirely too long.

So, I decided to try Claude to write a Python program to help me streamline the process. I was really impressed with its coding ability—it only took about six prompts and some code details, and now I have a program that meets my needs.

The program automates the entire workflow:

  • Prompts me for the blog title, description, and image details.
  • Lets me input markdown directly or choose a file.
  • Converts markdown to HTML.
  • Fills in a template with metadata and formatted content.
  • Saves the result in my blog’s post directory.
  • Updates posts.json to include the new entry.

This removes all the tedious steps and lets me focus on writing. Now, instead of getting annoyed, I can just run the script and my post is ready to publish. I have considered adding an option to push the update right to Github, but for now I am going to do it manually so I can double check the post before making it live.

It’s not perfect, but it’s a huge improvement. I know there are other more convenient options, but I like having everything for my personal site stay on my coding VM and remain something I fully understand. I didn’t write the code myself, but thanks to Claude, I understand exactly what it does—and I could have written it myself… albeit much, much slower and with a lot of research. For now, it solved a problem that was keeping me from sharing more. It works—that’s what matters.

The program Claude created for me, or at least something similiar as I made a few changes before posting, is below. Check it out!

import os
import re
import json
import markdown
from datetime import datetime
import tkinter as tk
from tkinter import filedialog
import sys

def get_user_input(prompt, multiline=False):
    """Get input from user with the given prompt."""
    print(prompt)
    if multiline:
        print("(Type 'END' on a new line when finished)")
        lines = []
        while True:
            line = input()
            if line == "END":
                break
            lines.append(line)
        return "\n".join(lines)
    else:
        return input()

def slugify(title):
    """Convert title to URL-friendly slug."""
    # Convert to lowercase
    slug = title.lower()
    # Replace non-alphanumeric characters with hyphens
    slug = re.sub(r'[^a-z0-9]+', '-', slug)
    # Remove leading and trailing hyphens
    slug = slug.strip('-')
    return slug

def select_markdown_file():
    """Open a file dialog to select a markdown file."""
    root = tk.Tk()
    root.withdraw()  # Hide the main window
    file_path = filedialog.askopenfilename(
        title="Select Markdown File",
        filetypes=[("Markdown files", "*.md"), ("Text files", "*.txt"), ("All files", "*.*")]
    )
    root.destroy()
    return file_path if file_path else None

def read_markdown_file(file_path):
    """Read content from markdown file."""
    try:
        with open(file_path, 'r', encoding='utf-8') as file:
            return file.read()
    except Exception as e:
        print(f"Error reading markdown file: {e}")
        return None

def markdown_to_html(markdown_content):
    """Convert markdown content to HTML."""
    try:
        # Create a markdown converter with extensions
        html = markdown.markdown(
            markdown_content,
            extensions=[
                'markdown.extensions.extra',
                'markdown.extensions.codehilite',
                'markdown.extensions.smarty'
            ]
        )
        return html
    except Exception as e:
        print(f"Error converting markdown to HTML: {e}")
        return None

def generate_html(template_path, output_path, replacements):
    """Generate HTML file with the provided replacements."""
    try:
        with open(template_path, 'r', encoding='utf-8') as file:
            template = file.read()
        
        # Apply replacements
        for key, value in replacements.items():
            template = template.replace(key, value)
        
        # Write to output file
        with open(output_path, 'w', encoding='utf-8') as file:
            file.write(template)
        
        print(f"\nHTML blog post generated successfully at: {output_path}")
        return True
    except Exception as e:
        print(f"Error generating HTML file: {e}")
        return False

def get_text_excerpt(markdown_content, max_chars=250):
    """Extract a plain text excerpt from markdown content."""
    # Convert markdown to HTML
    html = markdown_to_html(markdown_content)
    if not html:
        return ""
    
    # Remove HTML tags to get plain text
    plain_text = re.sub(r'<[^>]+>', '', html)
    
    # Truncate to max_chars
    if len(plain_text) <= max_chars:
        return plain_text
    
    # Try to find the last complete sentence within max_chars
    truncated = plain_text[:max_chars]
    last_sentence_end = max(
        truncated.rfind('.'), 
        truncated.rfind('!'), 
        truncated.rfind('?')
    )
    
    if last_sentence_end > 0:
        return plain_text[:last_sentence_end+1]
    else:
        # If no sentence ending found, truncate at the last space
        last_space = truncated.rfind(' ')
        if last_space > 0:
            return plain_text[:last_space] + '...'
        else:
            return truncated + '...'

def update_posts_json(post_data, json_path):
    """Update the posts.json file with new blog post data."""
    try:
        # Read existing posts with proper error handling
        posts = []
        if os.path.exists(json_path):
            with open(json_path, 'r', encoding='utf-8') as file:
                content = file.read().strip()
                print(f"DEBUG: Read {len(content)} characters from {json_path}")
                
                if content:  # Check if file has content
                    try:
                        posts = json.loads(content)
                        print(f"DEBUG: Successfully loaded {len(posts)} posts from JSON")
                    except json.JSONDecodeError as json_err:
                        print(f"JSON parsing error: {json_err}")
                        # Try to recover by creating a backup of the file
                        backup_path = json_path + ".backup"
                        with open(backup_path, 'w', encoding='utf-8') as backup_file:
                            backup_file.write(content)
                        print(f"Created backup of original file at {backup_path}")
                        # Initialize as empty list since we couldn't parse the file
                        posts = []
        
        # Add new post at the beginning
        posts.insert(0, post_data)
        print(f"DEBUG: Preparing to write {len(posts)} posts to JSON file")
        
        # Write updated posts back to file
        with open(json_path, 'w', encoding='utf-8') as file:
            json.dump(posts, file, indent=2, ensure_ascii=False)
        
        print(f"\nSuccessfully updated posts.json at: {json_path}")
        return True
    except Exception as e:
        print(f"Error updating posts.json: {e}")
        return False
    
def main():
    print("===== Blog Post Generator =====")
    print("This program will help you generate an HTML blog post from your template.")
    
    # Get blog post details
    blog_title = get_user_input("Enter the blog post title:")
    blog_description = get_user_input("Enter a brief description of the blog post (for metadata):")
    
    # Ask for image details
    image_filename = get_user_input("Enter the image filename (e.g., blog-image.jpg):")
    image_alt = get_user_input("Enter alt text for the image:")
    
    # Get today's date formatted as Month Day, Year
    today_date = datetime.now().strftime("%B %d, %Y")
    
    # Create a slug from the title
    slug = slugify(blog_title)
    
    # Ask user if they want to input markdown directly or from a file
    use_file = get_user_input("Do you want to select a markdown file? (y/n)").lower() == 'y'
    
    markdown_content = ""
    if use_file:
        print("Please select your markdown file...")
        file_path = select_markdown_file()
        if file_path:
            markdown_content = read_markdown_file(file_path)
            if not markdown_content:
                print("Failed to read markdown content. Exiting.")
                return
        else:
            print("No file selected. Exiting.")
            return
    else:
        print("Enter your markdown content below:")
        markdown_content = get_user_input("", multiline=True)
    
    # Convert markdown to HTML
    html_content = markdown_to_html(markdown_content)
    if not html_content:
        print("Failed to convert markdown to HTML. Exiting.")
        return
    
    # Define replacements
    replacements = {
        "": blog_title,
        "": blog_title,
        "": blog_description,
        "": slug,
        "": blog_title,
        "": today_date,
        "": image_filename,
        "": image_alt,
        "": html_content
    }
    
    # Ask for output location with options
    print("\nWhere would you like to save the HTML file?")
    print("1. Current directory")
    print("2. Your blog posts directory")
    print("3. Enter a custom path")
    
    output_choice = get_user_input("Enter your choice (1, 2, or 3):")
    
    if output_choice == "1":
        output_directory = os.getcwd()
    elif output_choice == "2":
        # Default to a 'posts' subdirectory in the current working directory
        output_directory = os.path.join(os.getcwd(), "posts")
        # Create directory if it doesn't exist
        os.makedirs(output_directory, exist_ok=True)
    elif output_choice == "3":
        output_directory = get_user_input("Enter the custom output directory path:")
        # Create directory if it doesn't exist
        os.makedirs(output_directory, exist_ok=True)
    else:
        print("Invalid choice. Using current directory.")
        output_directory = os.getcwd()
    
    output_filename = f"{slug}.html"
    output_path = os.path.join(output_directory, output_filename)
    
    # Generate HTML file
    template_path = os.path.join(os.getcwd(), "blog_template.html")
    if not os.path.exists(template_path):
        template_path = get_user_input("Template file 'blog_template.html' not found. Enter the path to your template file:")
    
    result = generate_html(template_path, output_path, replacements)
    
    if result:
        # Ask if user wants to update posts.json
        print("\nDo you want to update a posts.json file with this blog post?")
        print("1. Yes")
        print("2. No")
        
        update_json_choice = get_user_input("Enter your choice (1 or 2):")
        
        if update_json_choice == "1":
            print("\nWhich posts.json file would you like to update?")
            print("1. Default location (./posts.json)")
            print("2. Enter a custom path")
            
            json_path_choice = get_user_input("Enter your choice (1 or 2):")
            
            if json_path_choice == "1":
                json_path = os.path.join(os.getcwd(), "posts.json")
            elif json_path_choice == "2":
                json_path = get_user_input("Enter the path to your posts.json file:")
            else:
                print("Invalid choice. Skipping JSON update.")
                json_path = None
            
            if json_path:
                # Extract description from markdown content
                description = get_text_excerpt(markdown_content)
                
                # Create post data object
                post_data = {
                    "title": blog_title,
                    "image": f"./images/{image_filename}",
                    "alt": image_alt,
                    "description": description,
                    "author": get_user_input("Enter author name:"),
                    "date": today_date,
                    "link": f"./posts/{slug}"
                }
                
                update_posts_json(post_data, json_path)
        
        # Ask if user wants to open the generated file
        open_file = get_user_input("Do you want to open the generated HTML file? (y/n)").lower() == 'y'
        if open_file:
            try:
                if sys.platform == 'win32':
                    os.startfile(output_path)
                elif sys.platform == 'darwin':  # macOS
                    os.system(f"open '{output_path}'")
                else:  # Linux
                    os.system(f"xdg-open '{output_path}'")
            except Exception as e:
                print(f"Error opening file: {e}")

if __name__ == "__main__":
    main()