In the previous post, we discussed the challenges of migrating content from Obsidian to Hugo. Now, let’s dive into the automation that makes this transition seamless.

The core of this automation is a custom Rust script that processes Markdown files from my Obsidian vault and converts them into Hugo-compatible blog posts. This script ensures that the migration process is efficient, structured, and repeatable. In this post, we’ll explore the high-level architecture and workflow of the script, breaking down its key stages.

Understanding the Workflow

The script follows a structured process to transform raw Markdown notes into Hugo-ready content. Here’s a high-level overview of how it works:

  1. Read Markdown Files: The script scans a specific folder in the Obsidian vault for new Markdown files.
  2. Process Content: It extracts frontmatter, converts Obsidian-specific elements, and prepares images.
  3. Modify Metadata: The script adjusts metadata to match Hugo’s format and structure.
  4. Generate and Save Blog Post: The transformed content is saved in the correct Hugo directory with a URL-friendly filename.
  5. Track Processed Files: A log is maintained to ensure that files are not processed multiple times.

This automation enables a smooth, hands-off conversion from Obsidian to Hugo, allowing me to focus on writing without worrying about manual formatting.

Breaking Down the Main Function

The script’s main function orchestrates the entire workflow. Below is an overview of its key responsibilities:

fn main() {
    // Setup directories
    Home_Dir = Get_Home_Directory()
    Posting_Dir = Home_Dir + "/OneDrive/Obsidian/Vault/Posts"
    Images_Dir = Home_Dir + "/OneDrive/Obsidian/Vault/Images"
    Target_Images_Dir = Home_Dir + "/Blog/static/images"
    Blog_Posts_Dir = Home_Dir + "/Blog/content/en/posts"
    Checked_File = Home_Dir + "/Logs/postchecked.txt"
    
    // Create tracking file if it doesn't exist
    if not exists(Checked_File) {
        Create_Empty_File(Checked_File)
    }
    
    // Load list of already processed files
    Processed_Files = Load_Processed_Files(Checked_File)
    
    // Find unprocessed markdown files
    Md_Files = Find_Files_In_Directory(Posting_Dir) 
               where extension is "md" 
               and not in Processed_Files
    
    print "Found {Md_Files.length} unprocessed markdown files"
    
    // Process each file
    for each File_Path in Md_Files {
        print "Processing file: {File_Path}"
        
        // Read the content
        Original_Content = Read_File(File_Path)
        
        // STAGE 1: Extract filename for title
        Filename = Get_Filename_Without_Extension(File_Path)
        
        // STAGE 2: Parse frontmatter and content
        Frontmatter, Content = Extract_Frontmatter(Content_With_Images)
        
        // STAGE 3: Process Obsidian image links
        Content_With_Images, Processed_Images = Process_Obsidian_Images(
            Original_Content, Images_Dir, Target_Images_Dir
        )
        
        // STAGE 4: Extract thumbnail info
        Thumbnail_Info, Remaining_Content = Extract_Thumbnail_Info(Content)
        
        // STAGE 5: Modify frontmatter for Hugo
        Modified_Frontmatter = Modify_Frontmatter(Frontmatter, Filename, Thumbnail_Info)
        
        // STAGE 6: Process other image references in content
        Processed_Content = Process_Image_References(Remaining_Content)
        
        // STAGE 7: Combine everything
        Final_Content = "---\n{Modified_Frontmatter}---\n\n{Processed_Content}"
        
        // STAGE 8: Generate URL-friendly filename
        New_Filename = Generate_URL_Friendly_Filename(Blog_Posts_Dir, Filename)
        
        // STAGE 9: Write to new file
        Write_File(New_Filename, Final_Content)
        print "Created new blog post: {New_Filename}"
        
        // STAGE 10: Mark as processed
        Append_To_Processed_Files(Checked_File, File_Path)
        print "Marked as processed: {File_Path}"
        
        if Processed_Images is not empty {
            print "Processed images: {Processed_Images}"
        }
    }
}

1. Setting Up Directories

The script first determines relevant directories, including:

  • The source folder in the Obsidian vault
  • The image storage directory
  • The target directories in the Hugo project
  • A log file to track processed posts

2. Tracking Processed Files

To avoid duplicate processing, the script maintains a log of previously converted posts. It reads this log at the start and updates it after each successful conversion.

3. Finding New Markdown Files

The script scans the designated folder for Markdown files that haven’t been processed yet. These are then queued for conversion.

4. Processing Each File

For each new Markdown file:

  • The script extracts the filename as the post title.
  • It parses and modifies frontmatter to align with Hugo’s format.
  • It processes Obsidian’s wiki-style image links, converting them to standard Markdown and copying images to the correct Hugo directory.
  • A final blog post file is generated with a properly formatted name.

5. Logging and Finalization

Once a post is successfully created, the script updates the tracking log and prints a confirmation message. If images were processed, it also lists them.

Visualizing the Process

Here’s a simple flowchart of the script’s workflow:

By automating this workflow, I can write naturally in Obsidian and publish effortlessly to my Hugo-powered blog.

Next Steps

Now that we’ve covered the script’s architecture, the next post will dive deeper into handling frontmatter. We’ll explore how the script extracts, modifies, and formats metadata for Hugo, ensuring that each post has the correct tags, categories, and publishing information.

Stay tuned!