Skip to content

Commit ef1ab10

Browse files
committed
Add governance pages
1 parent aed991f commit ef1ab10

File tree

4 files changed

+215
-126
lines changed

4 files changed

+215
-126
lines changed

src/main.rs

Lines changed: 109 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
use crate::assets::{AssetFiles, compile_assets};
2-
use crate::category::Category;
32
use crate::fs::ensure_directory;
43
use crate::i18n::{EXPLICIT_LOCALE_INFO, LocaleInfo, SUPPORTED_LOCALES, TeamHelper, create_loader};
54
use crate::rust_version::{RustVersion, fetch_rust_version};
6-
use crate::teams::{RustTeams, load_rust_teams};
5+
use crate::teams::{PageData, RustTeams, encode_zulip_stream, load_rust_teams};
76
use anyhow::Context;
87
use handlebars::{DirectorySourceOptions, Handlebars};
98
use handlebars_fluent::{FluentHelper, Loader, SimpleLoader};
109
use serde::Serialize;
10+
use std::ffi::OsStr;
1111
use std::fs::File;
1212
use std::io::BufWriter;
1313
use std::path::{Path, PathBuf};
1414

1515
mod assets;
16-
mod category;
1716
mod fs;
1817
mod i18n;
1918
mod rust_version;
@@ -67,7 +66,10 @@ impl<'a, T: Serialize> PageCtx<'a, T> {
6766

6867
let out_path = self.output_dir.join(path);
6968
ensure_directory(&out_path)?;
70-
let mut output_file = BufWriter::new(File::create(&out_path)?);
69+
let mut output_file = BufWriter::new(
70+
File::create(&out_path)
71+
.with_context(|| anyhow::anyhow!("Cannot create file at {}", path.display()))?,
72+
);
7173
eprintln!("Rendering `{template}` into {}", out_path.display());
7274

7375
self.handlebars
@@ -84,6 +86,7 @@ impl<'a, T: Serialize> PageCtx<'a, T> {
8486

8587
struct RenderCtx<'a> {
8688
handlebars: Handlebars<'a>,
89+
template_dir: PathBuf,
8790
fluent_loader: SimpleLoader,
8891
output_dir: PathBuf,
8992
rust_version: RustVersion,
@@ -124,16 +127,28 @@ impl<'a> RenderCtx<'a> {
124127
}
125128
}
126129

127-
fn copy_static_assets<P: AsRef<Path>>(&self, src_dir: P, dst_dir: P) -> anyhow::Result<()> {
130+
fn copy_asset_dir<P: AsRef<Path>>(&self, src_dir: P, dst_dir: P) -> anyhow::Result<()> {
128131
let dst = self.output_dir.join(dst_dir.as_ref());
129132
println!(
130-
"Copying static assets from {} to {}",
133+
"Copying static asset directory from {} to {}",
131134
src_dir.as_ref().display(),
132135
dst.display()
133136
);
134137
copy_dir_all(src_dir.as_ref(), dst)?;
135138
Ok(())
136139
}
140+
141+
fn copy_asset_file<P: AsRef<Path>>(&self, src: P, dst: P) -> anyhow::Result<()> {
142+
let dst = self.output_dir.join(dst.as_ref());
143+
println!(
144+
"Copying static asset file from {} to {}",
145+
src.as_ref().display(),
146+
dst.display()
147+
);
148+
ensure_directory(&dst)?;
149+
std::fs::copy(src.as_ref(), dst)?;
150+
Ok(())
151+
}
137152
}
138153

139154
/// Calls `func` for all supported languages.
@@ -181,6 +196,7 @@ fn setup_handlebars() -> anyhow::Result<Handlebars<'static>> {
181196
let helper = FluentHelper::new(loader);
182197
handlebars.register_helper("fluent", Box::new(helper));
183198
handlebars.register_helper("team-text", Box::new(TeamHelper::new()));
199+
handlebars.register_helper("encode-zulip-stream", Box::new(encode_zulip_stream));
184200
Ok(handlebars)
185201
}
186202

@@ -194,28 +210,35 @@ async fn main() -> anyhow::Result<()> {
194210
let _ = std::fs::remove_dir_all(&output_dir);
195211
std::fs::create_dir_all(&output_dir)?;
196212

197-
let assets = compile_assets(Path::new("."), &output_dir, "/")?;
213+
let root_dir = Path::new(".");
214+
let assets = compile_assets(root_dir, &output_dir, "/")?;
198215
let handlebars = setup_handlebars()?;
199216

200217
let ctx = RenderCtx {
218+
template_dir: root_dir.join("templates"),
201219
fluent_loader: create_loader(),
202220
assets,
203221
rust_version,
204222
teams,
205223
handlebars,
206224
output_dir,
207225
};
208-
ctx.copy_static_assets("static", "static")?;
226+
ctx.copy_asset_dir("static", "static")?;
227+
ctx.copy_asset_file(
228+
"static/text/well_known_security.txt",
229+
".well-known/security.txt",
230+
)?;
209231

210232
render_index(&ctx)?;
211233
render_governance(&ctx)?;
212-
render_category(&ctx, "community")?;
213-
render_category(&ctx, "learn")?;
214-
render_category(&ctx, "policies")?;
215-
render_category(&ctx, "tools")?;
216-
render_category(&ctx, "what")?;
234+
render_directory(&ctx, "community")?;
235+
render_directory(&ctx, "learn")?;
236+
render_directory(&ctx, "policies")?;
237+
render_directory(&ctx, "tools")?;
238+
render_directory(&ctx, "what")?;
239+
ctx.page("404", "", &(), ENGLISH).render("404.html")?;
217240

218-
// TODO: 404, redirects
241+
// TODO: redirects
219242

220243
Ok(())
221244
}
@@ -243,18 +266,78 @@ fn render_governance(render_ctx: &RenderCtx) -> anyhow::Result<()> {
243266
render_ctx
244267
.page("governance/index", "governance-page-title", &data, lang)
245268
.render(dst_path)
246-
})
269+
})?;
270+
for team in data.teams {
271+
let data: PageData = render_ctx
272+
.teams
273+
.page_data(team.section, &team.page_name)
274+
.unwrap_or_else(|error| panic!("Page data for team {team:?} not found: {error:?}"));
275+
276+
// We need to render into index.html to have an extensionless URL
277+
all_langs(
278+
&format!("governance/{}/index.html", team.url),
279+
|dst_path, lang| {
280+
render_ctx
281+
.page(
282+
"governance/group",
283+
&format!("governance-team-{}-title", team.team.name),
284+
&data,
285+
lang,
286+
)
287+
.render(dst_path)
288+
},
289+
)?;
290+
}
291+
292+
Ok(())
247293
}
248294

249-
fn render_category(render_ctx: &RenderCtx, category: &str) -> anyhow::Result<()> {
250-
all_langs(&format!("{category}/index.html"), |dst_path, lang| {
251-
render_ctx
252-
.page(
253-
&format!("{category}/index"),
254-
&format!("{category}-page-title"),
255-
&(),
256-
lang,
257-
)
258-
.render(dst_path)
259-
})
295+
/// Render all templates found in the given directory.
296+
fn render_directory(render_ctx: &RenderCtx, category: &str) -> anyhow::Result<()> {
297+
for dir in std::fs::read_dir(render_ctx.template_dir.join(category))? {
298+
let path = dir?.path();
299+
if path.is_file() && path.extension() == Some(OsStr::new("hbs")) {
300+
// foo.html.hbs => foo
301+
let subject = path
302+
.file_stem()
303+
.unwrap()
304+
.to_str()
305+
.unwrap()
306+
.split(".")
307+
.next()
308+
.unwrap();
309+
310+
// The "root" page of a category
311+
if subject == "index" {
312+
all_langs(&format!("{category}/index.html"), |dst_path, lang| {
313+
render_ctx
314+
.page(
315+
&format!("{category}/index"),
316+
&format!("{category}-page-title"),
317+
&(),
318+
lang,
319+
)
320+
.render(dst_path)
321+
})?;
322+
} else {
323+
// A subpage (subject) of the category
324+
// We need to render the page into a subdirectory, so that /foo/bar works without
325+
// needing a HTML suffix.
326+
all_langs(
327+
&format!("{category}/{subject}/index.html"),
328+
|dst_path, lang| {
329+
render_ctx
330+
.page(
331+
&format!("{category}/{subject}"),
332+
&format!("{category}-{subject}-page-title"),
333+
&(),
334+
lang,
335+
)
336+
.render(dst_path)
337+
},
338+
)?;
339+
}
340+
}
341+
}
342+
Ok(())
260343
}

src/teams.rs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
use percent_encoding::{AsciiSet, NON_ALPHANUMERIC, utf8_percent_encode};
2-
use rocket_dyn_templates::handlebars::{
1+
use handlebars::{
32
Context, Handlebars, Helper, HelperResult, Output, RenderContext, RenderErrorReason,
43
};
4+
use percent_encoding::{AsciiSet, NON_ALPHANUMERIC, utf8_percent_encode};
55
use rust_team_data::v1::{BASE_URL, Team, TeamKind, Teams};
66
use serde::Serialize;
77
use std::cmp::Reverse;
@@ -66,6 +66,8 @@ impl RustTeams {
6666
// teams.
6767
.filter(|team| team.kind == TeamKind::Team && team.subteam_of.is_none())
6868
.map(|team| IndexTeam {
69+
section: kind_to_str(team.kind),
70+
page_name: team.website_data.clone().unwrap().page,
6971
url: format!(
7072
"{}/{}",
7173
kind_to_str(team.kind),
@@ -81,13 +83,15 @@ impl RustTeams {
8183
IndexData { teams }
8284
}
8385

84-
pub fn page_data(&self, section: &str, team_name: &str) -> anyhow::Result<PageData> {
86+
pub fn page_data(&self, section: &str, team_page_name: &str) -> anyhow::Result<PageData> {
8587
let teams = self.0.clone();
8688

8789
// Find the main team first
8890
let main_team = teams
8991
.iter()
90-
.filter(|team| team.website_data.as_ref().map(|ws| ws.page.as_str()) == Some(team_name))
92+
.filter(|team| {
93+
team.website_data.as_ref().map(|ws| ws.page.as_str()) == Some(team_page_name)
94+
})
9195
.find(|team| kind_to_str(team.kind) == section)
9296
.cloned()
9397
.ok_or(TeamNotFound)?;
@@ -187,19 +191,21 @@ impl RustTeams {
187191

188192
#[derive(Serialize)]
189193
pub struct IndexData {
190-
teams: Vec<IndexTeam>,
194+
pub teams: Vec<IndexTeam>,
191195
}
192196

193-
#[derive(Serialize)]
197+
#[derive(Serialize, Debug)]
194198
pub struct IndexTeam {
195199
#[serde(flatten)]
196-
team: Team,
197-
url: String,
200+
pub team: Team,
201+
pub url: String,
202+
pub section: &'static str,
203+
pub page_name: String,
198204
}
199205

200206
#[derive(Serialize)]
201207
pub struct PageData {
202-
pub team: Team,
208+
team: Team,
203209
zulip_domain: &'static str,
204210
subteams: Vec<Team>,
205211
wgs: Vec<Team>,

0 commit comments

Comments
 (0)