Building a Dynamic Blog Renderer with Rust: Converting Markdown to HTML with Custom Themes, Categories, Tags, Timelines, and Comment Areas

Blogging has become an essential aspect of personal branding, allowing individuals and organizations to share their thoughts and ideas with the world. With the rise of web development, it has become increasingly simple to build a personal blog using content management systems (CMS) such as WordPress. However, for those looking for a more customizable solution, building a blog renderer from scratch can be an exciting project. In this article, we will explore how to use Rust to write a blog renderer that converts markdown files into HTML with custom themes, categories, tags, timelines, and comment areas.

Prerequisites

Before we dive into the implementation of the blog renderer, we need to have a basic understanding of Rust and HTML. Familiarity with markdown syntax is also beneficial. If you are new to Rust, I recommend checking out the official Rust book to get started.

Getting Started

The first step in building a blog renderer is to set up a project in Rust. To do this, we can use the following command in the terminal:

cargo new blog_renderer

This command will create a new Rust project with the name "blog_renderer".

Converting Markdown to HTML

One of the main challenges in building a blog renderer is converting markdown files into HTML. To achieve this, we can use a library such as Pulldown-Cmark, which is a fast CommonMark parser in Rust. To use this library, we need to add it as a dependency in the Cargo.toml file:

[dependencies]
pulldown-cmark = "0.7.0"

Next, we need to import the library in the main file and use its functions to parse the markdown file and convert it into HTML:

use pulldown_cmark::{html, Parser};

fn markdown_to_html(markdown: &str) -> String {
    let parser = Parser::new(markdown);
    let mut html_output = String::new();
    html::push_html(&mut html_output, parser);
    html_output
}

In the above code, we use the Parser and html modules from the pulldown-cmark library to parse the markdown file and convert it into HTML. The markdown_to_html function takes in a markdown string and returns the corresponding HTML string.

Adding Themes and Styles

Now that we have the ability to convert markdown files into HTML, we need to add styling to our blog renderer. We can use CSS to style the HTML output and create custom themes for our blog. To add CSS to our project, we can create a style.css file and link it in the HTML file:

<link rel="stylesheet" type="text/css" href="style.css">

Adding Categories, Tags, and Timelines

Now that we have the basic structure of our blog renderer set up, we can start to add more advanced features. One of the most important of these is the ability to categorize posts, add tags, and create timelines.

First, let's add categories. To do this, we will need to add a new field to our post data structure. For example:

struct Post {
    title: String,
    content: String,
    categories: Vec<String>,
    tags: Vec<String>,
    publish_date: DateTime<Utc>,
}

Next, we need to modify the rendering code to take these categories into account. For example:

fn render_post(post: &Post) -> String {
    let categories = post.categories.iter()
        .map(|category| format!("<a href='/category/{}'>{}</a>", category, category))
        .collect::<Vec<_>>()
        .join(", ");
    let tags = post.tags.iter()
        .map(|tag| format!("<a href='/tag/{}'>{}</a>", tag, tag))
        .collect::<Vec<_>>()
        .join(", ");

    format!("
        <h1>{}</h1>
        <p>Categories: {}</p>
        <p>Tags: {}</p>
        <div>{}</div>
    ", post.title, categories, tags, post.content)
}

Now, whenever we render a post, it will include links to the categories and tags that it belongs to.

To add timelines, we need to first create a new data structure that represents a timeline. For example:

struct Timeline {
    date: DateTime<Utc>,
    posts: Vec<Post>,
}

Next, we need to modify our renderer to create a timeline from our list of posts. For example:

// Create a timeline of all posts
let mut timeline = Vec::new();
for post in posts.iter() {
  timeline.push(post.date.clone());
}
timeline.sort();
timeline.dedup();

// Generate HTML for the timeline
let mut timeline_html = String::new();
for date in timeline.iter() {
  timeline_html.push_str(&format!("<h3>{}</h3>\n", date));
  for post in posts.iter().filter(|p| p.date == *date) {
    timeline_html.push_str(&format!("<a href='#{id}'>{title}</a><br>\n", id = post.id, title = post.title));
  }
}

// Replace the timeline placeholder in the template with the generated timeline
let html = template.replace("{{timeline}}", &timeline_html);

With this code, we have created a timeline of all the posts in our blog, sorted by date, and deduplicated the dates. This timeline will be displayed on the homepage of our blog. Each entry in the timeline is a link to the corresponding post, so that users can easily navigate to the post they are interested in.

Next, we will add the ability to display categories and tags for each post. This will allow users to easily find posts that belong to specific categories or have specific tags.

// Extract the categories and tags for each post
let mut categories = Vec::new();
let mut tags = Vec::new();
for post in posts.iter() {
  categories.extend(post.categories.iter());
  tags.extend(post.tags.iter());
}

// Generate HTML for the categories and tags
let mut categories_html = String::new();
for category in categories.iter().sorted().dedup() {
  categories_html.push_str(&format!("<a href='/categories/{category}'>{category}</a><br>\n", category = category));
}
let mut tags_html = String::new();
for tag in tags.iter().sorted().dedup() {
  tags_html.push_str(&format!("<a href='/tags/{tag}'>{tag}</a><br>\n", tag = tag));
}

// Replace the categories and tags placeholders in the template with the generated HTML
let html = html.replace("{{categories}}", &categories_html);
let html = html.replace("{{tags}}", &tags_html);

With this code, we have extracted the categories and tags for each post, sorted and deduplicated them, and generated HTML for each category and tag. The generated HTML contains links to the corresponding category or tag pages, which will show all posts that belong to that category or have that tag.

Finally, we can add a comment area to each post so that users can leave comments and engage with each other. To keep things simple, we will use a static comment system, where comments are stored in a file along with the corresponding post.

<!-- Comments Section -->
<section id="comments">
    <h2>Comments</h2>
    <div class="comments-list">
        <!-- Loop through the comments of a specific post -->
        {{#each comments}}
        <div class="comment">
            <h3>{{name}}</h3>
            <p>{{content}}</p>
        </div>
        {{/each}}
    </div>
    <form action="#" method="post">
        <div class="form-group">
            <label for="name">Name:</label>
            <input type="text" id="name" name="name">
        </div>
        <div class="form-group">
            <label for="content">Comment:</label>
            <textarea id="content" name="content"></textarea>
        </div>
        <input type="submit" value="Submit Comment">
    </form>
</section>

In the above code snippet, we are using Handlebars to loop through the comments of a specific post and display them in the comments-list section. Each comment has a name and a content, and they are displayed in a comment div element.

In addition, we have added a form at the bottom of the comment section, where users can submit their comments. The form contains fields for the name and the comment content, and it submits the form data using the post method.

This is just a basic implementation, and there are many ways to enhance and extend the comment system. For instance, you can add authentication, moderation, and email notification features to the comment system to make it more robust and user-friendly.

In conclusion, we have seen how to build a blog renderer using Rust. We started with a basic markdown to HTML conversion, then added categories, tags, and timelines to categorize and organize posts. Finally, we added a static comment system to each post, allowing users to engage with each other and the content. The code snippets provided can be used as a starting point for building your own blog renderer, and the possibilities for customization and expansion are endless. With Rust's performance and reliability, this renderer can easily handle even the largest and most complex blogs. Whether you are an experienced developer or just starting out, Rust is a powerful and enjoyable language to work with, and building a blog renderer is a great project for getting started.