Blog Tools Version 0.1.2
Since the last time I wrote about blog-tools
the crate has had quite a bit of development, so I thought now would be a good time to shout out about it.
First, the create now comes with three working examples of a blog you can run right away. Simply clone the repo and run
cargo run --example high
cargo run --example medium
cargo run --example low
What do the high
, medium
and low
refer to? That's the bulk of the new features! blog-tools
now has a bunch of features to help with managing RAM.
high
refers to high RAM usage - the entire blog is stored in memory the whole time. medium
refers to medium RAM usage - most of the metadata is stored in memory but the blog posts themselves stay on disc till needed. low
refers to (you guessed it) low RAM usage - nothing is stored in memory at all. This would also be suitable in a serverless application.
high
Let's look at the high
module in more detail. Before version 0.1.0, this was the only mode supported. The main crux remains the same as before - place the blog, now the HighBlog
struct, in a lazy_static
pub static BLOG_ROOT: &str = "examples/blog";
lazy_static! {
pub static ref STATIC_BLOG_ENTRIES: HighBlog =
get_high_blog(PathBuf::from(BLOG_ROOT), None, None).unwrap();
}
You can then use this in just the same was as before, for example, in Rocket we could do something like
#[get("/blog")]
fn blog_index() -> Option<Template> {
let mut context = rocket_dyn_templates::tera::Context::new();
context.insert("blog", get_blog_context());
Some(Template::render("blog_index", context.into_json()))
}
And then the template we can make cards for the blog previews like so
<div class="blog-container">
{% for entry in blog.entries %}
<div class="blog-card">
<a href="/blog/{{ entry.slug }}" class="no-underline black-links">
<div class="card-body">
<h4>
<span>{{ entry.title }}</span>
</h4>
<h5>
<span>{{ entry.date }}</span>
</h5>
<div>
{{entry.preview | safe}}
</div>
</div>
</a>
<p><code>blog-tools</code> also supports tags - try clicking one!</p>
<div class="blog-tag-container">
{% for tag in entry.tags %}
<a href="/blog/tag/{{ tag }}" class="blog-tag">#{{ tag }}</a>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
I won't cover every single way you can use it here as that's detailed in the example code!
medium
With medium
we still place the MediumBlog
in a lazy static like so
pub static BLOG_ROOT: &str = "examples/blog";
lazy_static! {
pub static ref STATIC_BLOG_ENTRIES: MediumBlog =
get_medium_blog(PathBuf::from(BLOG_ROOT), None, None).unwrap();
}
The code for the preview page looks just the same!
#[get("/blog")]
fn blog_index() -> Option<Template> {
let mut context = rocket_dyn_templates::tera::Context::new();
context.insert("blog", get_blog_context());
Some(Template::render("blog_index", context.into_json()))
}
As does the template code, so I won't repeat it here. The difference comes when we want to return a specific blog post.
#[get("/blog/<date>/<slug>", rank = 2)]
fn blog_article(date: String, slug: String) -> Option<Template> {
let mut context = rocket_dyn_templates::tera::Context::new();
let all_blogs = get_blog_context();
let complete_slug = format!("{}/{}", date, slug);
let this_blog = match all_blogs.hash.get(&complete_slug) {
Some(x) => x,
None => return None,
};
context.insert(
"blog",
&this_blog
.render(PathBuf::from_str(BLOG_ROOT).unwrap())
.unwrap(),
);
Some(Template::render("blog", context.into_json()))
}
Since we don't have the actual blog entry in memory, we need to call this render
method! This actually produces the HTML from the markdown on the disc then and there
low
low
works quite differently. Since we don't want to keep anything in memory here, there is no lazy static. Instead, we have to read everything from disc every time a request comes in. For instance, our main blog preview is
#[get("/blog")]
fn blog_index() -> Option<Template> {
// I only use this dummy struct to keep consistency with the other two blog modes
#[derive(Serialize, Deserialize)]
struct Blogs {
entries: Vec<PreviewBlogEntry>,
tags: Vec<String>,
}
let mut context = rocket_dyn_templates::tera::Context::new();
let preview = preview_blogs(PathBuf::from_str(BLOG_ROOT).unwrap(), 2, None).unwrap();
let tags = get_blog_tag_list(PathBuf::from_str(BLOG_ROOT).unwrap()).unwrap();
context.insert(
"blog",
&Blogs {
entries: preview,
tags,
},
);
Some(Template::render("blog_index", context.into_json()))
}
Notice in the line
let preview = preview_blogs(PathBuf::from_str(BLOG_ROOT).unwrap(), 2, None).unwrap();
The second argument, the 2 in this case, is specifying the number of blog posts we want it to preview. Since low
is intended for instances where the whole blog can't fit into memory at once, you almost certainly can't preview the entire blog. So, this will only bother to read the most recent N blogs!
When we want to get a specific blog
#[get("/blog/<date>/<slug>", rank = 2)]
fn blog_article(date: String, slug: String) -> Option<Template> {
let mut context = rocket_dyn_templates::tera::Context::new();
let blog_post =
render_blog_post(PathBuf::from_str(BLOG_ROOT).unwrap(), date, slug, None).unwrap();
context.insert("blog", &blog_post);
Some(Template::render("blog", context.into_json()))
}
We again have to read it from the disc.
Conclusion
I hope you find this crate helpful! You can find the repo here, the crates.io entry at here and the documentation here.
As of now, blog-tools
is still very much a personal project I built for my own website. If you use it, I'd love to hear your feedback!! You can open issues on the GitHub, or email me