When it comes to serializing data to and from formats like JSON, yaml, and more, the serde crate is the first stop.
In our project, we’ll need both serde and serde_yaml. serde provides the base to derive the ability to serialize data between formats while serde_yaml specifically provides support for serializing and deserializing yaml.
❯ cargo add serde serde_yaml -F serde/derive
Updating crates.io index
Adding serde v1.0.175 to dependencies.
Features:
+ derive
+ serde_derive
+ std
- alloc
- rc
- unstable
Adding serde_yaml v0.9.25 to dependencies.
We need the derive feature to be able to use the Serialize derive macro. At this point our Cargo.toml looks like this.
[package]
name = "scaffold"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
camino = "1.1.6"
clap = { version = "4.3.19", features = ["derive"] }
serde = { version = "1.0.175", features = ["derive"] }
serde_yaml = "0.9.25"
slug = "0.1.4"
Back in main.rs, we need to bring the Serialize macro into scope.
use serde::Serialize;
and we need to derive it on Frontmatter.
#[derive(Debug, Serialize)]
struct Frontmatter {
layout: String,
tags: Vec<String>,
status: String,
title: String,
slug: String,
}
This allows us to use serde_yaml to serialize our Frontmatter struct instance into a yaml string.
serde_yaml::to_string accepts a shared reference to our frontmatter instance, and could potentially fail so it returns a Result. We don’t expect serialization to fail since we’re not doing anything particularly interesting, so we’ll unwrap the Result.
You could instead provide some Clap error handling code here as well.
let yaml = serde_yaml::to_string(&frontmatter).unwrap();
dbg!(yaml);
Debugging the result of that operation instead of the frontmatter struct itself results in.
❯ cargo run
Compiling scaffold v0.1.0 (/rust-adventure/scaffold)
Finished dev [unoptimized + debuginfo] target(s) in 0.34s
Running `target/debug/scaffold`
[src/main.rs:34] &args = Args {
layout: "post",
tags: [],
title: "A Post",
status: "draft",
output_dir: "content",
}
[src/main.rs:60] yaml = "layout: post\ntags: []\nstatus: draft\ntitle: A Post\nslug: a-post\n"
Instead of using dbg!, we can formalize our blog post format using the yaml result and a format! macro.
let yaml = serde_yaml::to_string(&frontmatter).unwrap();
let file_contents = format!(
"{yaml}
---
# {}",
args.title
);
if let Err(error) = fs::write(&filename, file_contents)
We’re now writing the file out with:
- the
yamlstring we produced into the file - a
---separator - and finally our post title with a markdown heading
#before it.
This results in a file with serialized yaml at the front.
❯ cat `content/A Post.md`
layout: post
tags: []
status: draft
title: A Post
slug: a-post
---
# A Post
Before we leave, let’s also use the slugified version of the title as the filename by moving the slug::slugify call to before the filename creation, and then using the resulting variable in join() as a shared reference and in the Frontmatter creation.
let post_slug = slug::slugify(&args.title);
let mut filename = args.output_dir.join(&post_slug);
filename.set_extension("md");
let frontmatter = Frontmatter {
layout: args.layout,
tags: args.tags,
status: args.status,
title: args.title.clone(),
slug: post_slug,
};
This now writes out a file called content/a-post.md.