Skip to content

Commit c5bd23c

Browse files
committed
Add rendering of the web into a directory
1 parent 93d89c5 commit c5bd23c

File tree

12 files changed

+670
-239
lines changed

12 files changed

+670
-239
lines changed

Cargo.lock

Lines changed: 217 additions & 182 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ authors = ["The Rust Project Developers"]
55
edition = "2024"
66

77
[dependencies]
8-
handlebars-fluent = "0.4.0"
8+
anyhow = "1"
9+
tokio = { version = "1", features = ["full"] }
10+
handlebars = { version = "6", features = ["dir_source"] }
11+
handlebars-fluent = "0.5"
912
rocket = "0.5.1"
1013
rocket_dyn_templates = { version = "0.2.0", features = ["handlebars"] }
1114
serde = { version = "1.0", features = ["derive"] }

src/cache.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ pub type Cache<T> = State<Arc<RwLock<T>>>;
1313

1414
pub trait Cached: Send + Sync + Clone + 'static {
1515
fn get_timestamp(&self) -> Instant;
16-
fn fetch() -> impl Future<Output = Result<Self, Box<dyn Error + Send + Sync>>> + Send;
16+
fn fetch() -> impl Future<Output = anyhow::Result<Self>> + Send;
1717
async fn get(cache: &Cache<Self>) -> Self {
1818
let cached = cache.read().await.clone();
1919
let timestamp = cached.get_timestamp();

src/i18n.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use rocket_dyn_templates::handlebars::{
1+
use handlebars::{
22
Context, Handlebars, Helper, HelperDef, HelperResult, Output, RenderContext, RenderErrorReason,
33
};
44

src/main.rs

Lines changed: 61 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ mod i18n;
66
mod redirect;
77
mod rust_version;
88
mod teams;
9+
mod render;
910

1011
use cache::Cache;
1112
use cache::Cached;
@@ -20,11 +21,14 @@ use teams::encode_zulip_stream;
2021
use std::collections::hash_map::DefaultHasher;
2122
use std::env;
2223
use std::fs;
24+
use std::fs::File;
2325
use std::hash::Hasher;
26+
use std::io::BufWriter;
2427
use std::path::{Path, PathBuf};
2528
use std::sync::Arc;
2629
use std::sync::LazyLock;
27-
30+
use anyhow::Context as _;
31+
use handlebars::{DirectorySourceOptions, Handlebars};
2832
use rocket::{
2933
fs::NamedFile,
3034
http::Status,
@@ -38,7 +42,7 @@ use sass_rs::{Options, compile_file};
3842
use category::Category;
3943

4044
use caching::CachedNamedFile;
41-
use handlebars_fluent::{FluentHelper, loader::Loader};
45+
use handlebars_fluent::{FluentHelper, loader::Loader, simple_loader};
4246
use i18n::{EXPLICIT_LOCALE_INFO, LocaleInfo, SupportedLocale, TeamHelper, create_loader};
4347

4448
const ZULIP_DOMAIN: &str = "https://rust-lang.zulipchat.com";
@@ -404,50 +408,59 @@ fn render_subject(category: Category, subject: &str, lang: String) -> Result<Tem
404408
Ok(Template::render(page, context))
405409
}
406410

407-
#[rocket::launch]
408-
async fn rocket() -> _ {
409-
let templating = Template::custom(|engine| {
410-
engine
411-
.handlebars
412-
.register_helper("fluent", Box::new(FluentHelper::new(create_loader())));
413-
engine
414-
.handlebars
415-
.register_helper("team-text", Box::new(TeamHelper::new()));
416-
engine
417-
.handlebars
418-
.register_helper("encode-zulip-stream", Box::new(encode_zulip_stream));
419-
});
420-
421-
let rust_version = RustVersion::fetch().await.unwrap_or_default();
422-
let teams = RustTeams::fetch().await.unwrap_or_default();
423-
424-
rocket::build()
425-
.attach(templating)
426-
.attach(headers::InjectHeaders)
427-
.manage(Arc::new(RwLock::new(rust_version)))
428-
.manage(Arc::new(RwLock::new(teams)))
429-
.mount(
430-
"/",
431-
rocket::routes![
432-
index,
433-
category_en,
434-
governance,
435-
team,
436-
subject,
437-
files,
438-
robots_txt,
439-
logos,
440-
index_locale,
441-
category_locale,
442-
governance_locale,
443-
team_locale,
444-
subject_locale,
445-
redirect_bare_en_us,
446-
well_known_security,
447-
],
448-
)
449-
.register(
450-
"/",
451-
rocket::catchers![not_found, unprocessable_content, catch_error],
452-
)
411+
// #[rocket::launch]
412+
// async fn rocket() -> _ {
413+
// let templating = Template::custom(|engine| {
414+
// engine
415+
// .handlebars
416+
// .register_helper("fluent", Box::new(FluentHelper::new(create_loader())));
417+
// engine
418+
// .handlebars
419+
// .register_helper("team-text", Box::new(TeamHelper::new()));
420+
// engine
421+
// .handlebars
422+
// .register_helper("encode-zulip-stream", Box::new(encode_zulip_stream));
423+
// });
424+
//
425+
// let rust_version = RustVersion::fetch().await.unwrap_or_default();
426+
// let teams = RustTeams::fetch().await.unwrap_or_default();
427+
//
428+
// rocket::build()
429+
// .attach(templating)
430+
// .attach(headers::InjectHeaders)
431+
// .manage(Arc::new(RwLock::new(rust_version)))
432+
// .manage(Arc::new(RwLock::new(teams)))
433+
// .mount(
434+
// "/",
435+
// rocket::routes![
436+
// index,
437+
// category_en,
438+
// governance,
439+
// team,
440+
// subject,
441+
// files,
442+
// robots_txt,
443+
// logos,
444+
// index_locale,
445+
// category_locale,
446+
// governance_locale,
447+
// team_locale,
448+
// subject_locale,
449+
// redirect_bare_en_us,
450+
// well_known_security,
451+
// ],
452+
// )
453+
// .register(
454+
// "/",
455+
// rocket::catchers![not_found, unprocessable_content, catch_error],
456+
// )
457+
// }
458+
459+
#[tokio::main]
460+
async fn main() -> anyhow::Result<()> {
461+
let rust_version = RustVersion::fetch().await?;
462+
let teams = RustTeams::fetch().await?;
463+
render::render(rust_version, teams)?;
464+
465+
Ok(())
453466
}

src/render/assets.rs

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
use crate::render::fs::ensure_directory;
2+
use anyhow::Context;
3+
use rocket::serde::Serialize;
4+
use sass_rs::{Options, compile_file};
5+
use std::fs;
6+
use std::hash::{DefaultHasher, Hasher};
7+
use std::path::Path;
8+
9+
fn write_file(path: &Path, bytes: &[u8]) -> anyhow::Result<()> {
10+
ensure_directory(path)?;
11+
Ok(fs::write(path, bytes)?)
12+
}
13+
14+
fn hash_string(content: &str) -> String {
15+
let mut hasher = DefaultHasher::new();
16+
hasher.write(content.as_bytes());
17+
hasher.finish().to_string()
18+
}
19+
20+
fn relative_url(path: &Path, out_dir: &Path) -> anyhow::Result<String> {
21+
Ok(path.strip_prefix(out_dir)?.to_str().unwrap().to_string())
22+
}
23+
24+
/// Compiles SASS file, stores it in `out_dir` and returns the relative URL to it.
25+
fn compile_sass(root_dir: &Path, out_dir: &Path, filename: &str) -> anyhow::Result<String> {
26+
let scss_file = root_dir
27+
.join("src")
28+
.join("styles")
29+
.join(format!("{filename}.scss"));
30+
31+
let css = compile_file(&scss_file, Options::default())
32+
.map_err(|e| anyhow::anyhow!("{e}"))
33+
.with_context(|| anyhow::anyhow!("couldn't compile sass: {}", scss_file.display()))?;
34+
35+
let css_sha = format!("{filename}_{}", hash_string(&css));
36+
let out_css_path = out_dir
37+
.join("static")
38+
.join("styles")
39+
.join(format!("{css_sha}.css"));
40+
41+
write_file(&out_css_path, &css.into_bytes())
42+
.with_context(|| anyhow::anyhow!("couldn't write css file: {}", out_css_path.display()))?;
43+
44+
Ok(relative_url(&out_css_path, &out_dir)?)
45+
}
46+
47+
fn concat_files(
48+
root_dir: &Path,
49+
out_dir: &Path,
50+
files: &[&str],
51+
directory: &str,
52+
extension: &str,
53+
) -> anyhow::Result<String> {
54+
let mut concatted = String::new();
55+
for filestem in files {
56+
let vendor_path = root_dir
57+
.join("static")
58+
.join(directory)
59+
.join(format!("{filestem}.{extension}"));
60+
let contents = fs::read_to_string(vendor_path)
61+
.with_context(|| anyhow::anyhow!("couldn't read vendor {extension}"))?;
62+
concatted.push_str(&contents);
63+
}
64+
65+
let file_sha = format!("vendor_{}", hash_string(&concatted));
66+
let out_file_path = out_dir
67+
.join("static")
68+
.join(directory)
69+
.join(format!("{file_sha}.{extension}"));
70+
71+
write_file(Path::new(&out_file_path), concatted.as_bytes())
72+
.with_context(|| anyhow::anyhow!("couldn't write vendor {extension}"))?;
73+
74+
Ok(relative_url(&out_file_path, &out_dir)?)
75+
}
76+
77+
fn concat_vendor_css(root_dir: &Path, out_dir: &Path, files: Vec<&str>) -> anyhow::Result<String> {
78+
concat_files(root_dir, out_dir, &files, "styles", "css")
79+
}
80+
81+
fn concat_app_js(root_dir: &Path, out_dir: &Path, files: Vec<&str>) -> anyhow::Result<String> {
82+
concat_files(root_dir, out_dir, &files, "scripts", "js")
83+
}
84+
85+
#[derive(Serialize, Debug)]
86+
pub struct CSSFiles {
87+
app: String,
88+
fonts: String,
89+
vendor: String,
90+
}
91+
92+
#[derive(Serialize, Debug)]
93+
pub struct JSFiles {
94+
app: String,
95+
}
96+
97+
#[derive(Serialize, Debug)]
98+
pub struct AssetFiles {
99+
css: CSSFiles,
100+
js: JSFiles,
101+
}
102+
103+
/// Compile all statics JS/CSS assets into the `out_dir` directory and return a structure
104+
/// that holds paths to them, based on the passed `baseurl`.
105+
pub fn compile_assets(
106+
root_dir: &Path,
107+
out_dir: &Path,
108+
baseurl: &str,
109+
) -> anyhow::Result<AssetFiles> {
110+
let app_css_file = compile_sass(root_dir, out_dir, "app")?;
111+
let fonts_css_file = compile_sass(root_dir, out_dir, "fonts")?;
112+
let vendor_css_file = concat_vendor_css(root_dir, out_dir, vec!["tachyons"])?;
113+
let app_js_file = concat_app_js(root_dir, out_dir, vec!["tools-install"])?;
114+
115+
Ok(AssetFiles {
116+
css: CSSFiles {
117+
app: format!("{baseurl}{app_css_file}"),
118+
fonts: format!("{baseurl}{fonts_css_file}"),
119+
vendor: format!("{baseurl}{vendor_css_file}"),
120+
},
121+
js: JSFiles {
122+
app: format!("{baseurl}{app_js_file}"),
123+
},
124+
})
125+
}

src/render/fs.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
use anyhow::Context;
2+
use std::path::Path;
3+
4+
pub fn ensure_directory(path: &Path) -> anyhow::Result<()> {
5+
if let Some(parent) = path.parent() {
6+
std::fs::create_dir_all(parent).with_context(|| {
7+
anyhow::anyhow!("Could not create parent directory for {}", path.display())
8+
})?;
9+
}
10+
Ok(())
11+
}

0 commit comments

Comments
 (0)