Compare commits
5 commits
a23a71f39f
...
075dd95c03
Author | SHA1 | Date | |
---|---|---|---|
075dd95c03 | |||
10caf581ad | |||
60c2ac15e7 | |||
f4e9ef6af9 | |||
0e3ffc0991 |
39 changed files with 413 additions and 440 deletions
113
Cargo.lock
generated
113
Cargo.lock
generated
|
@ -17,15 +17,6 @@ version = "1.0.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.3.2"
|
||||
|
@ -452,15 +443,6 @@ version = "1.0.7"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fsevent-sys"
|
||||
version = "4.1.0"
|
||||
|
@ -677,16 +659,6 @@ dependencies = [
|
|||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
|
||||
dependencies = [
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
|
@ -857,18 +829,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "meowy-webring"
|
||||
version = "0.2.0"
|
||||
name = "meowy-assets"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"askama",
|
||||
"askama_rocket",
|
||||
"hex",
|
||||
"log",
|
||||
"notify",
|
||||
"proc_macros",
|
||||
"rocket",
|
||||
"rocket_cors",
|
||||
"rust-embed",
|
||||
"sha2",
|
||||
"shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "meowy-webring"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"meowy-assets",
|
||||
"notify",
|
||||
"rocket",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shared",
|
||||
|
@ -1206,8 +1186,6 @@ version = "1.8.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax 0.7.2",
|
||||
]
|
||||
|
||||
|
@ -1287,20 +1265,6 @@ dependencies = [
|
|||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rocket_cors"
|
||||
version = "0.6.0-alpha2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b12771b47f52e34d5d0e0e444aeba382863e73263cb9e18847e7d5b74aa2cbd0"
|
||||
dependencies = [
|
||||
"http",
|
||||
"log",
|
||||
"regex",
|
||||
"rocket",
|
||||
"unicase",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rocket_http"
|
||||
version = "0.5.0-rc.3"
|
||||
|
@ -1464,6 +1428,16 @@ dependencies = [
|
|||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
"sha2-asm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2-asm"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf27176fb5d15398e3a479c652c20459d9dac830dedd1fa55b42a77dbcdbfcea"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1640,21 +1614,6 @@ dependencies = [
|
|||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec_macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.29.0"
|
||||
|
@ -1851,44 +1810,18 @@ dependencies = [
|
|||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.1"
|
||||
|
|
35
Cargo.toml
35
Cargo.toml
|
@ -1,5 +1,5 @@
|
|||
[workspace]
|
||||
members = ["cli", "shared", "proc-macros"]
|
||||
members = ["crates/*"]
|
||||
|
||||
[package]
|
||||
name = "meowy-webring"
|
||||
|
@ -7,47 +7,24 @@ version = "0.2.0"
|
|||
edition = "2021"
|
||||
rust-version = "1.70"
|
||||
|
||||
[profile.dev]
|
||||
lto = false
|
||||
[profile.release]
|
||||
lto = "thin"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
hex = "0.4"
|
||||
log = "0.4"
|
||||
|
||||
[dependencies.rocket]
|
||||
version = "=0.5.0-rc.3"
|
||||
default-features = false
|
||||
features = ["json"]
|
||||
|
||||
[dependencies.rust-embed]
|
||||
version = "6"
|
||||
features = ["debug-embed"]
|
||||
|
||||
[dependencies.serde]
|
||||
version = "1.0"
|
||||
|
||||
[dependencies.serde_json]
|
||||
version = "1.0"
|
||||
|
||||
[dependencies.askama_rocket]
|
||||
git = "https://github.com/djc/askama.git"
|
||||
package = "askama_rocket"
|
||||
rev = "b9e51601560398766eac445517fb17c35090a952"
|
||||
default-features = false
|
||||
|
||||
[dependencies.askama]
|
||||
git = "https://github.com/djc/askama.git"
|
||||
package = "askama"
|
||||
rev = "b9e51601560398766eac445517fb17c35090a952"
|
||||
version = "0.12"
|
||||
default-features = false
|
||||
|
||||
[dependencies.shared]
|
||||
path = "./shared"
|
||||
path = "./crates/shared"
|
||||
|
||||
[dependencies.simple_logger]
|
||||
version = "4"
|
||||
|
@ -58,9 +35,5 @@ version = "6"
|
|||
default-features = false
|
||||
features = ["macos_fsevent"]
|
||||
|
||||
[dependencies.rocket_cors]
|
||||
version = "=0.6.0-alpha2"
|
||||
default_features = false
|
||||
|
||||
[dependencies.proc_macros]
|
||||
path = "./proc-macros"
|
||||
[dependencies.meowy-assets]
|
||||
path = "./crates/meowy-assets"
|
||||
|
|
40
crates/meowy-assets/Cargo.toml
Normal file
40
crates/meowy-assets/Cargo.toml
Normal file
|
@ -0,0 +1,40 @@
|
|||
[package]
|
||||
name = "meowy-assets"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
hex = "0.4"
|
||||
|
||||
[dependencies.rocket]
|
||||
version = "=0.5.0-rc.3"
|
||||
default-features = false
|
||||
|
||||
[dependencies.askama_rocket]
|
||||
git = "https://github.com/djc/askama.git"
|
||||
package = "askama_rocket"
|
||||
rev = "b9e51601560398766eac445517fb17c35090a952"
|
||||
default-features = false
|
||||
|
||||
[dependencies.askama]
|
||||
git = "https://github.com/djc/askama.git"
|
||||
package = "askama"
|
||||
rev = "b9e51601560398766eac445517fb17c35090a952"
|
||||
version = "0.12"
|
||||
default-features = false
|
||||
|
||||
[dependencies.rust-embed]
|
||||
version = "6"
|
||||
features = ["debug-embed"]
|
||||
|
||||
[dependencies.sha2]
|
||||
version = "0.10"
|
||||
features = ["asm"]
|
||||
|
||||
[dependencies.shared]
|
||||
path = "../shared"
|
||||
|
||||
[dependencies.proc_macros]
|
||||
path = "../proc-macros"
|
148
crates/meowy-assets/src/files.rs
Normal file
148
crates/meowy-assets/src/files.rs
Normal file
|
@ -0,0 +1,148 @@
|
|||
use super::templates::HyperlegibleTemplate;
|
||||
use askama::Template;
|
||||
use rocket::http::Status;
|
||||
use rust_embed::RustEmbed;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::{borrow::Cow, sync::OnceLock};
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "public/"]
|
||||
struct Assets;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Files {
|
||||
pub style: TextFile,
|
||||
pub hyperlegible: TextFile,
|
||||
pub atkinson_latin_woff2: BinaryFile,
|
||||
pub atkinson_latin_ext_woff2: BinaryFile,
|
||||
pub atkinson_all_woff: BinaryFile,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FileMetadata {
|
||||
pub filename: String,
|
||||
pub extension: String,
|
||||
pub hash: String,
|
||||
}
|
||||
|
||||
impl FileMetadata {
|
||||
pub fn get_hash_filename(&self) -> String {
|
||||
let mut hash = self.hash.clone();
|
||||
hash.truncate(8);
|
||||
format!("{}.{}.{}", self.filename, hash, self.extension)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BinaryFile {
|
||||
pub data: Cow<'static, [u8]>,
|
||||
pub metadata: FileMetadata,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TextFile {
|
||||
pub text: String,
|
||||
pub metadata: FileMetadata,
|
||||
}
|
||||
|
||||
trait GetFile {
|
||||
fn get(filename: &str, extension: &str) -> Option<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
impl GetFile for BinaryFile {
|
||||
fn get(filename: &str, extension: &str) -> Option<Self> {
|
||||
match Assets::get(&format!("{}.{}", filename, extension)) {
|
||||
Some(file) => {
|
||||
let metadata = FileMetadata {
|
||||
filename: filename.into(),
|
||||
extension: extension.into(),
|
||||
hash: hex::encode(file.metadata.sha256_hash()),
|
||||
};
|
||||
Some(BinaryFile {
|
||||
data: file.data,
|
||||
metadata,
|
||||
})
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GetFile for TextFile {
|
||||
fn get(filename: &str, extension: &str) -> Option<Self> {
|
||||
let file = BinaryFile::get(filename, extension)?;
|
||||
match std::str::from_utf8(&file.data) {
|
||||
Ok(string) => Some(TextFile {
|
||||
text: string.into(),
|
||||
metadata: file.metadata,
|
||||
}),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub static FILES: OnceLock<Files> = OnceLock::new();
|
||||
|
||||
pub fn get_file_wrapper() -> Result<&'static Files, Status> {
|
||||
match FILES.get() {
|
||||
Some(files) => Ok(files),
|
||||
None => Err(Status::InternalServerError),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_sha256_hash(string: &String) -> String {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(string);
|
||||
let result = hasher.finalize();
|
||||
hex::encode(result)
|
||||
}
|
||||
|
||||
fn get_hyperlegible(
|
||||
latin_woff2_filename: String,
|
||||
latin_ext_woff2_filename: String,
|
||||
all_woff_filename: String,
|
||||
) -> TextFile {
|
||||
let hyperlegible_template = HyperlegibleTemplate {
|
||||
atkinson_latin_woff2_filename: latin_woff2_filename,
|
||||
atkinson_latin_ext_woff2_filename: latin_ext_woff2_filename,
|
||||
atkinson_all_woff_filename: all_woff_filename,
|
||||
};
|
||||
|
||||
let rendered_template = hyperlegible_template.render().unwrap();
|
||||
let hash = get_sha256_hash(&rendered_template);
|
||||
|
||||
let metadata = FileMetadata {
|
||||
filename: "hyperlegible".into(),
|
||||
extension: "css".into(),
|
||||
hash,
|
||||
};
|
||||
|
||||
TextFile {
|
||||
text: rendered_template,
|
||||
metadata,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn initialize_files() {
|
||||
let atkinson_latin_woff2 =
|
||||
BinaryFile::get("atkinson-hyperlegible-latin-400-normal", "woff2").unwrap();
|
||||
let atkinson_latin_ext_woff2 =
|
||||
BinaryFile::get("atkinson-hyperlegible-latin-ext-400-normal", "woff2").unwrap();
|
||||
let atkinson_all_woff =
|
||||
BinaryFile::get("atkinson-hyperlegible-all-400-normal", "woff").unwrap();
|
||||
|
||||
let files = Files {
|
||||
style: TextFile::get("style", "css").unwrap(),
|
||||
hyperlegible: get_hyperlegible(
|
||||
atkinson_latin_woff2.metadata.get_hash_filename(),
|
||||
atkinson_latin_ext_woff2.metadata.get_hash_filename(),
|
||||
atkinson_all_woff.metadata.get_hash_filename(),
|
||||
),
|
||||
atkinson_latin_woff2,
|
||||
atkinson_latin_ext_woff2,
|
||||
atkinson_all_woff,
|
||||
};
|
||||
FILES.set(files).unwrap();
|
||||
}
|
|
@ -1,7 +1,11 @@
|
|||
pub mod files;
|
||||
mod responders;
|
||||
mod routes;
|
||||
pub mod templates;
|
||||
|
||||
#[macro_use]
|
||||
extern crate rocket;
|
||||
|
||||
pub use routes::style;
|
||||
pub use routes::woff2_font;
|
||||
pub use routes::woff_font;
|
44
crates/meowy-assets/src/responders.rs
Normal file
44
crates/meowy-assets/src/responders.rs
Normal file
|
@ -0,0 +1,44 @@
|
|||
use super::templates::ErrorTemplate;
|
||||
use rocket::{
|
||||
http::Header,
|
||||
response::{self, Responder},
|
||||
Response,
|
||||
};
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[derive(Responder)]
|
||||
#[response(status = 200, content_type = "font/woff2")]
|
||||
pub struct RawWoff2Font(pub Cow<'static, [u8]>);
|
||||
|
||||
#[derive(Responder)]
|
||||
#[response(status = 200, content_type = "font/woff")]
|
||||
pub struct RawWoffFont(pub Cow<'static, [u8]>);
|
||||
|
||||
#[derive(Responder)]
|
||||
pub struct ErrorTemplateResponder<'a> {
|
||||
template: ErrorTemplate<'a>,
|
||||
}
|
||||
|
||||
pub struct CachedResponse<T> {
|
||||
inner: T,
|
||||
}
|
||||
|
||||
impl<'r, T> Responder<'r, 'static> for CachedResponse<T>
|
||||
where
|
||||
T: Responder<'r, 'static>,
|
||||
{
|
||||
fn respond_to(self, request: &'r rocket::Request<'_>) -> response::Result<'static> {
|
||||
Response::build_from(self.inner.respond_to(request)?)
|
||||
.header(Header::new("Cache-Control", "max-age=31536000, immutable"))
|
||||
.ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r, T> From<T> for CachedResponse<T>
|
||||
where
|
||||
T: Responder<'r, 'static>,
|
||||
{
|
||||
fn from(value: T) -> Self {
|
||||
Self { inner: value }
|
||||
}
|
||||
}
|
51
crates/meowy-assets/src/routes.rs
Normal file
51
crates/meowy-assets/src/routes.rs
Normal file
|
@ -0,0 +1,51 @@
|
|||
use crate::{
|
||||
files::get_file_wrapper,
|
||||
responders::{CachedResponse, RawWoff2Font, RawWoffFont},
|
||||
};
|
||||
use rocket::{http::Status, response::content::RawCss};
|
||||
|
||||
#[get("/css/<style>")]
|
||||
pub fn style(style: &str) -> Result<CachedResponse<RawCss<String>>, Status> {
|
||||
let style_file = &get_file_wrapper()?.style;
|
||||
let hyperlegible_file = &get_file_wrapper()?.hyperlegible;
|
||||
|
||||
let style_name = style_file.metadata.get_hash_filename();
|
||||
let hyperlegible_name = hyperlegible_file.metadata.get_hash_filename();
|
||||
|
||||
if style == style_name {
|
||||
Ok(RawCss::<String>(style_file.text.clone()).into())
|
||||
} else if style == hyperlegible_name {
|
||||
Ok(RawCss::<String>(hyperlegible_file.text.clone()).into())
|
||||
} else {
|
||||
Err(Status::NotFound)
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/woff2/<font>")]
|
||||
pub fn woff2_font(font: &str) -> Result<CachedResponse<RawWoff2Font>, Status> {
|
||||
let latin_file = &get_file_wrapper()?.atkinson_latin_woff2;
|
||||
let latin_ext_file = &get_file_wrapper()?.atkinson_latin_ext_woff2;
|
||||
|
||||
let latin = latin_file.metadata.get_hash_filename();
|
||||
let latin_ext = latin_file.metadata.get_hash_filename();
|
||||
|
||||
if font == latin {
|
||||
Ok(RawWoff2Font(latin_file.data.clone()).into())
|
||||
} else if font == latin_ext {
|
||||
Ok(RawWoff2Font(latin_ext_file.data.clone()).into())
|
||||
} else {
|
||||
Err(Status::NotFound)
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/woff/<font>")]
|
||||
pub fn woff_font(font: &str) -> Result<CachedResponse<RawWoffFont>, Status> {
|
||||
let all_file = &get_file_wrapper()?.atkinson_all_woff;
|
||||
let all = all_file.metadata.get_hash_filename();
|
||||
|
||||
if font == all {
|
||||
Ok(RawWoffFont(all_file.data.clone()).into())
|
||||
} else {
|
||||
Err(Status::NotFound)
|
||||
}
|
||||
}
|
|
@ -3,7 +3,13 @@ use proc_macros::uses_base_template;
|
|||
use shared::names::Site;
|
||||
|
||||
pub struct BaseTemplate {
|
||||
pub hyperlegible_filename: String,
|
||||
pub style_filename: String,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "hyperlegible.css", escape = "none")]
|
||||
pub(super) struct HyperlegibleTemplate {
|
||||
pub atkinson_latin_woff2_filename: String,
|
||||
pub atkinson_latin_ext_woff2_filename: String,
|
||||
pub atkinson_all_woff_filename: String,
|
16
crates/meowy-assets/templates/base.html
Normal file
16
crates/meowy-assets/templates/base.html
Normal file
|
@ -0,0 +1,16 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Meowy Webring{% block title %}{% endblock %}</title>
|
||||
{% block head %}
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<link rel="stylesheet" href="/public/css/{{ base_template.hyperlegible_filename }}" />
|
||||
<link rel="stylesheet" href="/public/css/{{ base_template.style_filename }}" />
|
||||
{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</body>
|
||||
</html>
|
|
@ -3,8 +3,8 @@
|
|||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 400;
|
||||
src: url(/public/woff2/atkinson-hyperlegible-latin-ext-400-normal.woff2) format("woff2"),
|
||||
url(/public/woff/atkinson-hyperlegible-all-400-normal.woff) format("woff");
|
||||
src: url("/public/woff2/{{ atkinson_latin_ext_woff2_filename }}") format("woff2"),
|
||||
url("/public/woff/{{ atkinson_all_woff_filename }}") format("woff");
|
||||
unicode-range: U+0100-024F,
|
||||
U+0259,
|
||||
U+1E00-1EFF,
|
||||
|
@ -21,8 +21,8 @@
|
|||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 400;
|
||||
src: url(/public/woff2/atkinson-hyperlegible-latin-400-normal.woff2) format("woff2"),
|
||||
url(/public/woff/atkinson-hyperlegible-all-400-normal.woff) format("woff");
|
||||
src: url("/public/woff2/{{ atkinson_latin_woff2_filename }}") format("woff2"),
|
||||
url("/public/woff/{{ atkinson_all_woff_filename }}") format("woff");
|
||||
unicode-range: U+0000-00FF,
|
||||
U+0131,
|
||||
U+0152-0153,
|
26
crates/proc-macros/src/lib.rs
Normal file
26
crates/proc-macros/src/lib.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{parse::Parser, parse_macro_input, DeriveInput};
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn uses_base_template(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let mut input = parse_macro_input!(item as DeriveInput);
|
||||
|
||||
let base_template_field = syn::Field::parse_named
|
||||
.parse2(quote! {
|
||||
pub base_template: BaseTemplate
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
if let syn::Data::Struct(ref mut struct_data) = &mut input.data {
|
||||
if let syn::Fields::Named(fields) = &mut struct_data.fields {
|
||||
fields.named.push(base_template_field);
|
||||
}
|
||||
quote! {
|
||||
#input
|
||||
}
|
||||
.into()
|
||||
} else {
|
||||
panic!("bad")
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@ pub enum ErrorStatus {
|
|||
LoggerInitializationError,
|
||||
NotFoundError,
|
||||
AlreadyExistsError,
|
||||
GenericError
|
||||
GenericError,
|
||||
}
|
||||
|
||||
pub struct Error {
|
|
@ -1,3 +1,3 @@
|
|||
pub mod names;
|
||||
pub mod errors;
|
||||
pub mod directories;
|
||||
pub mod errors;
|
||||
pub mod names;
|
|
@ -1,35 +0,0 @@
|
|||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{parse::Parser, parse_macro_input, DeriveInput};
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn uses_base_template(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let mut input = parse_macro_input!(item as DeriveInput);
|
||||
|
||||
let base_template_field = syn::Field::parse_named
|
||||
.parse2(
|
||||
quote! {
|
||||
pub base_template: BaseTemplate
|
||||
}
|
||||
.into(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
match &mut input.data {
|
||||
syn::Data::Struct(ref mut struct_data) => {
|
||||
match &mut struct_data.fields {
|
||||
syn::Fields::Named(fields) => {
|
||||
fields.named.push(base_template_field);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
return quote! {
|
||||
#input
|
||||
}
|
||||
.into();
|
||||
}
|
||||
_ => {
|
||||
panic!("bad")
|
||||
}
|
||||
};
|
||||
}
|
|
@ -1,96 +0,0 @@
|
|||
use rocket::http::Status;
|
||||
use rust_embed::RustEmbed;
|
||||
use std::{borrow::Cow, sync::OnceLock};
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "public/"]
|
||||
struct Assets;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Files {
|
||||
pub atkinson_latin_woff2: BinaryFile,
|
||||
pub atkinson_latin_ext_woff2: BinaryFile,
|
||||
pub atkinson_all_woff: BinaryFile,
|
||||
pub style: TextFile,
|
||||
pub hyperlegible: TextFile,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FileMetadata {
|
||||
pub filename: String,
|
||||
pub extension: String,
|
||||
pub hash: String,
|
||||
}
|
||||
|
||||
impl FileMetadata {
|
||||
pub fn get_hash_filename(&self) -> String {
|
||||
let mut hash = self.hash.clone();
|
||||
hash.truncate(8);
|
||||
format!("{}.{}.{}", self.filename, hash, self.extension)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BinaryFile {
|
||||
pub data: Cow<'static, [u8]>,
|
||||
pub metadata: FileMetadata,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TextFile {
|
||||
pub text: String,
|
||||
pub metadata: FileMetadata,
|
||||
}
|
||||
|
||||
pub static FILES: OnceLock<Files> = OnceLock::new();
|
||||
|
||||
pub fn get_file_wrapper() -> Result<&'static Files, Status> {
|
||||
match FILES.get() {
|
||||
Some(files) => Ok(files),
|
||||
None => Err(Status::InternalServerError),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_binary_file(filename: &str, extension: &str) -> Option<BinaryFile> {
|
||||
match Assets::get(&format!("{}.{}", filename, extension)) {
|
||||
Some(file) => {
|
||||
let metadata = FileMetadata {
|
||||
filename: filename.into(),
|
||||
extension: extension.into(),
|
||||
hash: hex::encode(file.metadata.sha256_hash()),
|
||||
};
|
||||
Some(BinaryFile {
|
||||
data: file.data,
|
||||
metadata: metadata,
|
||||
})
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_text_file(filename: &str, extension: &str) -> Option<TextFile> {
|
||||
let file = get_binary_file(filename, extension)?;
|
||||
match std::str::from_utf8(&file.data) {
|
||||
Ok(string) => Some(TextFile {
|
||||
text: string.into(),
|
||||
metadata: file.metadata,
|
||||
}),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn initialize_files() {
|
||||
let files = Files {
|
||||
atkinson_latin_woff2: get_binary_file("atkinson-hyperlegible-latin-400-normal", "woff2")
|
||||
.unwrap(),
|
||||
atkinson_latin_ext_woff2: get_binary_file(
|
||||
"atkinson-hyperlegible-latin-ext-400-normal",
|
||||
"woff2",
|
||||
)
|
||||
.unwrap(),
|
||||
atkinson_all_woff: get_binary_file("atkinson-hyperlegible-all-400-normal", "woff").unwrap(),
|
||||
style: get_text_file("style", "css").unwrap(),
|
||||
hyperlegible: get_text_file("hyperlegible", "css").unwrap(),
|
||||
};
|
||||
FILES.set(files).unwrap();
|
||||
}
|
|
@ -1,96 +0,0 @@
|
|||
use super::{files::get_file_wrapper, templates::ErrorTemplate};
|
||||
use rocket::{
|
||||
http::{Header, Status},
|
||||
response::{self, content::RawCss, Responder},
|
||||
Response,
|
||||
};
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[derive(Responder)]
|
||||
#[response(status = 200, content_type = "font/woff2")]
|
||||
pub struct RawWoff2Font(pub Cow<'static, [u8]>);
|
||||
|
||||
#[derive(Responder)]
|
||||
#[response(status = 200, content_type = "font/woff")]
|
||||
pub struct RawWoffFont(pub Cow<'static, [u8]>);
|
||||
|
||||
#[derive(Responder)]
|
||||
pub struct ErrorTemplateResponder<'a> {
|
||||
template: ErrorTemplate<'a>,
|
||||
}
|
||||
|
||||
pub struct CachedResponse<T> {
|
||||
inner: T,
|
||||
}
|
||||
|
||||
impl<'r, T> Responder<'r, 'static> for CachedResponse<T>
|
||||
where
|
||||
T: Responder<'r, 'static>,
|
||||
{
|
||||
fn respond_to(self, request: &'r rocket::Request<'_>) -> response::Result<'static> {
|
||||
Response::build_from(self.inner.respond_to(request)?)
|
||||
.header(Header::new("Cache-Control", "max-age=31536000, immutable"))
|
||||
.ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r, T> From<T> for CachedResponse<T>
|
||||
where
|
||||
T: Responder<'r, 'static>,
|
||||
{
|
||||
fn from(value: T) -> Self {
|
||||
CachedResponse { inner: value }
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/css/<style>")]
|
||||
pub fn style(style: &str) -> Result<CachedResponse<RawCss<String>>, Status> {
|
||||
let style_file = &get_file_wrapper()?.style;
|
||||
let hyperlegible_file = &get_file_wrapper()?.hyperlegible;
|
||||
|
||||
let style_name = style_file.metadata.get_hash_filename();
|
||||
let hyperlegible_name = hyperlegible_file.metadata.get_hash_filename();
|
||||
|
||||
if style == style_name {
|
||||
Ok(CachedResponse::from(RawCss::<String>(
|
||||
style_file.text.clone(),
|
||||
)))
|
||||
} else if style == hyperlegible_name {
|
||||
Ok(CachedResponse::from(RawCss::<String>(
|
||||
hyperlegible_file.text.clone(),
|
||||
)))
|
||||
} else {
|
||||
Err(Status::NotFound)
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/woff2/<font>")]
|
||||
pub fn woff2_font(font: &str) -> Result<CachedResponse<RawWoff2Font>, Status> {
|
||||
let latin_file = &get_file_wrapper()?.atkinson_latin_woff2;
|
||||
let latin_ext_file = &get_file_wrapper()?.atkinson_latin_ext_woff2;
|
||||
|
||||
let latin = latin_file.metadata.get_hash_filename();
|
||||
let latin_ext = latin_file.metadata.get_hash_filename();
|
||||
|
||||
if font == latin {
|
||||
Ok(CachedResponse::from(RawWoff2Font(latin_file.data.clone())))
|
||||
} else if font == latin_ext {
|
||||
Ok(CachedResponse::from(RawWoff2Font(
|
||||
latin_ext_file.data.clone(),
|
||||
)))
|
||||
} else {
|
||||
Err(Status::NotFound)
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/woff/<font>")]
|
||||
pub fn woff_font(font: &str) -> Result<CachedResponse<RawWoffFont>, Status> {
|
||||
let all_file = &get_file_wrapper()?.atkinson_all_woff;
|
||||
let all = all_file.metadata.get_hash_filename();
|
||||
|
||||
if font == all {
|
||||
Ok(CachedResponse::from(RawWoffFont(all_file.data.clone())))
|
||||
} else {
|
||||
Err(Status::NotFound)
|
||||
}
|
||||
}
|
12
src/main.rs
12
src/main.rs
|
@ -1,13 +1,13 @@
|
|||
use crate::watcher::hot_reloading;
|
||||
use assets::files::initialize_files;
|
||||
use meowy_assets::files::initialize_files;
|
||||
use rocket::tokio;
|
||||
use sites::init_names;
|
||||
|
||||
#[macro_use]
|
||||
extern crate rocket;
|
||||
|
||||
mod assets;
|
||||
mod links;
|
||||
mod responders;
|
||||
mod routes;
|
||||
mod sites;
|
||||
mod watcher;
|
||||
|
@ -16,11 +16,9 @@ mod watcher;
|
|||
async fn rocket() -> _ {
|
||||
init_names().unwrap();
|
||||
initialize_files();
|
||||
let cors = rocket_cors::CorsOptions::default().to_cors().unwrap();
|
||||
tokio::task::spawn_blocking(hot_reloading);
|
||||
|
||||
rocket::build()
|
||||
.manage(cors)
|
||||
.mount(
|
||||
"/",
|
||||
routes![routes::index, routes::previous, routes::next, routes::name],
|
||||
|
@ -28,6 +26,10 @@ async fn rocket() -> _ {
|
|||
.register("/", catchers![routes::not_found])
|
||||
.mount(
|
||||
"/public",
|
||||
routes![assets::style, assets::woff2_font, assets::woff_font],
|
||||
routes![
|
||||
meowy_assets::style,
|
||||
meowy_assets::woff2_font,
|
||||
meowy_assets::woff_font
|
||||
],
|
||||
)
|
||||
}
|
||||
|
|
25
src/responders.rs
Normal file
25
src/responders.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
use rocket::{http::Header, response::Responder, Response};
|
||||
|
||||
pub struct CorsResponse<T> {
|
||||
pub inner: T,
|
||||
}
|
||||
|
||||
impl<'r, T> Responder<'r, 'static> for CorsResponse<T>
|
||||
where
|
||||
T: Responder<'r, 'static>,
|
||||
{
|
||||
fn respond_to(self, request: &'r rocket::Request<'_>) -> rocket::response::Result<'static> {
|
||||
Response::build_from(self.inner.respond_to(request)?)
|
||||
.header(Header::new("Access-Control-Allow-Origin", "*"))
|
||||
.ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r, T> From<T> for CorsResponse<T>
|
||||
where
|
||||
T: Responder<'r, 'static>,
|
||||
{
|
||||
fn from(value: T) -> Self {
|
||||
Self { inner: value }
|
||||
}
|
||||
}
|
|
@ -1,17 +1,17 @@
|
|||
use crate::{
|
||||
assets::{
|
||||
links::{next_name, next_url, previous_name, previous_url},
|
||||
responders::CorsResponse,
|
||||
sites::get_global_names,
|
||||
};
|
||||
use meowy_assets::{
|
||||
files::{get_file_wrapper, FileMetadata},
|
||||
templates::{BaseTemplate, ErrorTemplate, IndexTemplate},
|
||||
},
|
||||
links::{next_name, next_url, previous_name, previous_url},
|
||||
sites::get_global_names,
|
||||
};
|
||||
use rocket::{
|
||||
http::Status,
|
||||
response::Redirect,
|
||||
serde::{json::Json, Serialize},
|
||||
};
|
||||
use rocket_cors::{Guard, Responder};
|
||||
|
||||
fn get_hash_filename(metadata: &FileMetadata) -> Result<String, Status> {
|
||||
Ok(metadata.get_hash_filename())
|
||||
|
@ -20,17 +20,12 @@ fn get_hash_filename(metadata: &FileMetadata) -> Result<String, Status> {
|
|||
fn get_base_template() -> Result<BaseTemplate, Status> {
|
||||
let files = get_file_wrapper()?;
|
||||
|
||||
let hyperlegible_filename = get_hash_filename(&files.hyperlegible.metadata)?;
|
||||
let style_filename = get_hash_filename(&files.style.metadata)?;
|
||||
let atkinson_latin_woff2_filename = get_hash_filename(&files.atkinson_latin_woff2.metadata)?;
|
||||
let atkinson_latin_ext_woff2_filename =
|
||||
get_hash_filename(&files.atkinson_latin_ext_woff2.metadata)?;
|
||||
let atkinson_all_woff_filename = get_hash_filename(&files.atkinson_all_woff.metadata)?;
|
||||
|
||||
let template = BaseTemplate {
|
||||
hyperlegible_filename,
|
||||
style_filename,
|
||||
atkinson_latin_woff2_filename,
|
||||
atkinson_latin_ext_woff2_filename,
|
||||
atkinson_all_woff_filename,
|
||||
};
|
||||
|
||||
Ok(template)
|
||||
|
@ -80,22 +75,19 @@ pub async fn next(source_url: String) -> Result<Redirect, Status> {
|
|||
}
|
||||
|
||||
#[get("/name?<source_url>")]
|
||||
pub async fn name(
|
||||
source_url: String,
|
||||
cors: Guard<'_>,
|
||||
) -> Responder<Result<Json<JsonResponse>, Status>> {
|
||||
pub async fn name(source_url: String) -> Result<CorsResponse<Json<JsonResponse>>, Status> {
|
||||
let previous_site_name = previous_name(&source_url, &get_global_names().await);
|
||||
let next_site_name = next_name(&source_url, &get_global_names().await);
|
||||
|
||||
if previous_site_name.is_none() && next_site_name.is_none() {
|
||||
return cors.responder(Err(Status::NotFound));
|
||||
return Err(Status::NotFound);
|
||||
}
|
||||
|
||||
let response = Ok(Json(JsonResponse {
|
||||
Ok(Json(JsonResponse {
|
||||
previous_site_name,
|
||||
next_site_name,
|
||||
}));
|
||||
cors.responder(response)
|
||||
})
|
||||
.into())
|
||||
}
|
||||
|
||||
#[catch(404)]
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Meowy Webring{% block title %}{% endblock %}</title>
|
||||
{% block head %}
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: Atkinson Hyperlegible;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 400;
|
||||
src: url("/public/woff2/{{ base_template.atkinson_latin_ext_woff2_filename }}") format("woff2"),
|
||||
url("/public/woff/{{ base_template.atkinson_all_woff_filename }}") format("woff");
|
||||
unicode-range: U+0100-024F,
|
||||
U+0259,
|
||||
U+1E00-1EFF,
|
||||
U+2020,
|
||||
U+20A0-20AB,
|
||||
U+20AD-20CF,
|
||||
U+2113,
|
||||
U+2C60-2C7F,
|
||||
U+A720-A7FF
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Atkinson Hyperlegible;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
font-weight: 400;
|
||||
src: url("/public/woff2/{{ base_template.atkinson_latin_woff2_filename }}") format("woff2"),
|
||||
url("/public/woff/{{ base_template.atkinson_all_woff_filename }}") format("woff");
|
||||
unicode-range: U+0000-00FF,
|
||||
U+0131,
|
||||
U+0152-0153,
|
||||
U+02BB-02BC,
|
||||
U+02C6,
|
||||
U+02DA,
|
||||
U+02DC,
|
||||
U+2000-206F,
|
||||
U+2074,
|
||||
U+20AC,
|
||||
U+2122,
|
||||
U+2191,
|
||||
U+2193,
|
||||
U+2212,
|
||||
U+2215,
|
||||
U+FEFF,
|
||||
U+FFFD
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="/public/css/{{ base_template.style_filename }}" />
|
||||
{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in a new issue