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
yaml
string 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
.