markdown-html-rs - Converting Markdown Content to HTML
Alright folks, here it is...your Rust program of the week. I call it "markdown-html-rs". It takes in a file with markdown content, including frontmatter, and outputs said markdown content to html.
This project leverages the following community crates:
- clap (for CLI input)
- gray-matter (for parsing YAML style front matter)
- regex (to help identify front matter)
- thiserror (to reduce boilerplate in error handling)
- serde (for deserializing the incoming data)
// main.rs
// A command line program which takes a markdown file as input, converts to HTML, and outputs the HTML file
// dependencies
use clap::Parser;
use gray_matter::engine::YAML;
use gray_matter::Matter;
use regex::Regex;
use serde::Deserialize;
use std::fs;
use std::io::{self, Write};
use std::string::FromUtf8Error;
use thiserror::Error;
// enum to represent error types
#[derive(Error, Debug)]
enum ConversionError {
#[error("File read error: {0}")]
FileRead(std::io::Error),
#[error("Deserialization error: {0}")]
Deserialization(serde_json::error::Error),
#[error("File write error: {0}")]
FileWrite(std::io::Error),
#[error("HTML write error: {0}")]
HTMLWrite(std::io::Error),
#[error("Markdown conversion error: {0}")]
MarkdownConversion(FromUtf8Error),
#[error("Regex error: {0}")]
Regex(regex::Error),
}
// struct to represent command line arguments
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
#[arg(short, long)]
filename: String,
}
// struct to represent the front matter of the markdown document
#[allow(dead_code)]
#[derive(Debug, Deserialize)]
struct FrontMatter {
title: String,
date: String,
tags: Vec<String>,
}
impl Default for FrontMatter {
fn default() -> Self {
FrontMatter {
title: "".to_string(),
date: "".to_string(),
tags: Vec::new(),
}
}
}
fn main() -> Result<(), ConversionError> {
// create an output buffer
let mut stdout = io::stdout();
// get the file name from the command line input
let args = Args::parse();
// read the file contents and save it as a vector of u8
// convert the file contents into a markdown string
let file_contents = fs::read(args.filename).map_err(ConversionError::FileRead)?;
let markdown_input =
String::from_utf8(file_contents).map_err(ConversionError::MarkdownConversion)?;
// parse the front matter in the input string and deserialize it into a FrontMatter struct
// remove the front matter, leaving on the body content of the markdown file
let matter = Matter::<YAML>::new().parse(&markdown_input);
let front_matter: FrontMatter = matter
.data
.as_ref()
.map(|data| data.deserialize())
.transpose()
.map_err(ConversionError::Deserialization)?
.unwrap_or_default();
writeln!(stdout, "{:?}", front_matter).map_err(ConversionError::FileWrite)?;
let frontmatter_regex =
Regex::new(r"---\s*\n(?s:.+?)\n---\s*\n").map_err(ConversionError::Regex)?;
let markdown_body = frontmatter_regex.replace(&markdown_input, "");
// parse the markdown body and convert it to html, any html tags in the markdown file are passed through
let parser = pulldown_cmark::Parser::new(&markdown_body);
let mut html_output = String::new();
pulldown_cmark::html::push_html(&mut html_output, parser);
// write the html output file
fs::write("output.html", html_output).map_err(ConversionError::HTMLWrite)?;
writeln!(stdout, "Markdown converted and saved to output.html")
.map_err(ConversionError::FileWrite)?;
Ok(())
}
The GitHub repo lives here.