Skip to content

Commit e38d1c3

Browse files
authored
Merge pull request #402 from rust-lang/blogs
Add support for multiple blogs
2 parents 1a6cee5 + bf61a8b commit e38d1c3

File tree

8 files changed

+332
-227
lines changed

8 files changed

+332
-227
lines changed

posts/blog.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
title: Rust Blog
2+
index-title: The Rust Programming Language Blog
3+
description: Empowering everyone to build reliable and efficient software.
4+
maintained-by: the Rust Team

src/blogs.rs

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
use crate::posts::Post;
2+
use serde_derive::{Deserialize, Serialize};
3+
use std::error::Error;
4+
use std::path::{Path, PathBuf};
5+
6+
static MANIFEST_FILE: &str = "blog.yml";
7+
static POSTS_EXT: &str = "md";
8+
9+
#[derive(Deserialize)]
10+
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
11+
struct Manifest {
12+
title: String,
13+
index_title: String,
14+
description: String,
15+
maintained_by: String,
16+
}
17+
18+
#[derive(Serialize)]
19+
pub(crate) struct Blog {
20+
title: String,
21+
index_title: String,
22+
description: String,
23+
maintained_by: String,
24+
#[serde(serialize_with = "add_postfix_slash")]
25+
prefix: PathBuf,
26+
posts: Vec<Post>,
27+
}
28+
29+
impl Blog {
30+
fn load(prefix: PathBuf, dir: &Path) -> Result<Self, Box<dyn Error>> {
31+
let manifest_content = std::fs::read_to_string(dir.join(MANIFEST_FILE))?;
32+
let manifest: Manifest = serde_yaml::from_str(&manifest_content)?;
33+
34+
let mut posts = Vec::new();
35+
for entry in std::fs::read_dir(dir)? {
36+
let path = entry?.path();
37+
let ext = path.extension().and_then(|e| e.to_str());
38+
if path.metadata()?.file_type().is_file() && ext == Some(POSTS_EXT) {
39+
posts.push(Post::open(&path)?);
40+
}
41+
}
42+
43+
posts.sort_by_key(|post| post.url.clone());
44+
posts.reverse();
45+
46+
// Decide which posts should show the year in the index.
47+
posts[0].show_year = true;
48+
for i in 1..posts.len() {
49+
posts[i].show_year = posts[i - 1].year != posts[i].year;
50+
}
51+
52+
Ok(Blog {
53+
title: manifest.title,
54+
index_title: manifest.index_title,
55+
description: manifest.description,
56+
maintained_by: manifest.maintained_by,
57+
prefix,
58+
posts,
59+
})
60+
}
61+
62+
pub(crate) fn title(&self) -> &str {
63+
&self.title
64+
}
65+
66+
pub(crate) fn index_title(&self) -> &str {
67+
&self.index_title
68+
}
69+
70+
pub(crate) fn prefix(&self) -> &Path {
71+
&self.prefix
72+
}
73+
74+
pub(crate) fn posts(&self) -> &[Post] {
75+
&self.posts
76+
}
77+
}
78+
79+
/// Recursively load blogs in a directory. A blog is a directory with a `blog.yml`
80+
/// file inside it.
81+
pub(crate) fn load(base: &Path) -> Result<Vec<Blog>, Box<dyn Error>> {
82+
let mut blogs = Vec::new();
83+
load_recursive(base, base, &mut blogs)?;
84+
Ok(blogs)
85+
}
86+
87+
fn load_recursive(
88+
base: &Path,
89+
current: &Path,
90+
blogs: &mut Vec<Blog>,
91+
) -> Result<(), Box<dyn Error>> {
92+
for entry in std::fs::read_dir(current)? {
93+
let path = entry?.path();
94+
let file_type = path.metadata()?.file_type();
95+
96+
if file_type.is_dir() {
97+
load_recursive(base, &path, blogs)?;
98+
} else if file_type.is_file() {
99+
let file_name = path.file_name().and_then(|n| n.to_str());
100+
if let (Some(file_name), Some(parent)) = (file_name, path.parent()) {
101+
if file_name == MANIFEST_FILE {
102+
let prefix = parent
103+
.strip_prefix(base)
104+
.map(|p| p.to_path_buf())
105+
.unwrap_or_else(|_| PathBuf::new());
106+
blogs.push(Blog::load(prefix, parent)?);
107+
}
108+
}
109+
}
110+
}
111+
Ok(())
112+
}
113+
114+
fn add_postfix_slash<S>(path: &PathBuf, serializer: S) -> Result<S::Ok, S::Error>
115+
where
116+
S: serde::Serializer,
117+
{
118+
let mut str_repr = path.to_string_lossy().to_string();
119+
if !str_repr.is_empty() {
120+
str_repr.push('/');
121+
}
122+
serializer.serialize_str(&str_repr)
123+
}

0 commit comments

Comments
 (0)