Linked styles

This commit is contained in:
2025-11-03 09:55:16 -08:00
parent 05bdac5b4f
commit 2f93cf7f8e
4 changed files with 159 additions and 55 deletions

View File

@@ -1,7 +1,7 @@
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::quote; use quote::quote;
use syn::{ use syn::{
Ident, LitStr, Result, Token, braced, Expr, Ident, LitStr, Result, Token, braced,
parse::{Parse, ParseStream}, parse::{Parse, ParseStream},
parse_macro_input, parse_macro_input,
}; };
@@ -92,7 +92,7 @@ pub fn assets(input: TokenStream) -> TokenStream {
// Generate documentation showing the original asset definition // Generate documentation showing the original asset definition
let doc = format!( let doc = format!(
"This is an `asset!`\n```notrust\n{} {{\n\tsource: \"{}\",\n\ttarget: \"{}\"\n}}\n```", "This is an `asset!`\n```notrust\n{} {{\n\tsource: \"{:?}\",\n\ttarget: \"{}\"\n}}\n```",
name, source, target name, source, target
); );
@@ -106,7 +106,7 @@ pub fn assets(input: TokenStream) -> TokenStream {
const URL_PREFIX: &'static str = #prefix; const URL_PREFIX: &'static str = #prefix;
const URL_POSTFIX: &'static str = #target; const URL_POSTFIX: &'static str = #target;
const URL: &'static str = concat!(#prefix, #target); const URL: &'static str = concat!(#prefix, #target);
const BYTES: &'static [u8] = include_bytes!(#source); const BYTES: &'static [u8] = #source;
} }
} }
}); });
@@ -115,9 +115,20 @@ pub fn assets(input: TokenStream) -> TokenStream {
let router_fn = if let Some(router_name) = &input.router { let router_fn = if let Some(router_name) = &input.router {
let route_definitions = input.assets.iter().map(|asset| { let route_definitions = input.assets.iter().map(|asset| {
let name = &asset.name; let name = &asset.name;
let headers = asset
.headers
.as_ref()
.map(|x| quote! { #x })
.unwrap_or(quote! { [] });
quote! { quote! {
.route(#name::URL_POSTFIX, ::axum::routing::get(|| async { .route(#name::URL_POSTFIX, ::axum::routing::get(|| async {
(::axum::http::StatusCode::OK, #name::BYTES) (
::axum::http::StatusCode::OK,
#headers,
#name::BYTES
)
})) }))
} }
}); });
@@ -159,8 +170,9 @@ struct AssetsInput {
/// Represents a single asset definition within the macro /// Represents a single asset definition within the macro
struct AssetDefinition { struct AssetDefinition {
name: Ident, name: Ident,
source: String, source: Expr,
target: String, target: String,
headers: Option<Expr>,
} }
impl Parse for AssetsInput { impl Parse for AssetsInput {
@@ -214,26 +226,60 @@ impl Parse for AssetDefinition {
let content; let content;
braced!(content in input); braced!(content in input);
// Parse "source:" // Parse fields in any order
let _source_ident: Ident = content.parse()?; let mut source: Option<Expr> = None;
let _colon1: Token![:] = content.parse()?; let mut target: Option<String> = None;
let source_lit: LitStr = content.parse()?; let mut headers: Option<Expr> = None;
let source = source_lit.value();
let _comma1: Token![,] = content.parse()?;
// Parse "target:" while !content.is_empty() {
let _target_ident: Ident = content.parse()?; // Parse field name
let _colon2: Token![:] = content.parse()?; let field_name: Ident = content.parse()?;
let target_lit: LitStr = content.parse()?; let _colon: Token![:] = content.parse()?;
let target = target_lit.value();
// Optional trailing comma // Parse field value based on name
let _ = content.parse::<Token![,]>(); match field_name.to_string().as_str() {
"source" => {
if source.is_some() {
return Err(syn::Error::new(field_name.span(), "duplicate 'source' field"));
}
source = Some(content.parse()?);
}
"target" => {
if target.is_some() {
return Err(syn::Error::new(field_name.span(), "duplicate 'target' field"));
}
let target_lit: LitStr = content.parse()?;
target = Some(target_lit.value());
}
"headers" => {
if headers.is_some() {
return Err(syn::Error::new(field_name.span(), "duplicate 'headers' field"));
}
headers = Some(content.parse()?);
}
_ => {
return Err(syn::Error::new(
field_name.span(),
format!("unknown field '{}', expected 'source', 'target', or 'headers'", field_name)
));
}
}
// Parse comma if not at end
if !content.is_empty() {
content.parse::<Token![,]>()?;
}
}
// Validate required fields
let source = source.ok_or_else(|| syn::Error::new(name.span(), "missing required field 'source'"))?;
let target = target.ok_or_else(|| syn::Error::new(name.span(), "missing required field 'target'"))?;
Ok(AssetDefinition { Ok(AssetDefinition {
name, name,
source, source,
target, target,
headers,
}) })
} }
} }

View File

@@ -1,7 +1,7 @@
use macro_sass::sass; use assetserver::Asset;
use maud::{DOCTYPE, Markup, PreEscaped, Render, html}; use maud::{DOCTYPE, Markup, PreEscaped, Render, html};
use crate::components::misc::FarLink; use crate::{components::misc::FarLink, routes::assets::Styles_Main};
pub struct PageMetadata { pub struct PageMetadata {
pub title: String, pub title: String,
@@ -30,15 +30,12 @@ impl Render for PageMetadata {
meta property="og:description" content=(description) {} meta property="og:description" content=(description) {}
meta property="twitter:description" content=(description) {} meta property="twitter:description" content=(description) {}
meta content=(image) property="og:image" {} meta content=(image) property="og:image" {}
link rel="shortcut icon" href=(image) type="image/x-icon" {} link rel="shortcut icon" href=(image) type="image/x-icon" {}
) )
} }
} }
const CSS: &str = sass!("css/main.scss");
pub struct BasePage<T: Render>(pub PageMetadata, pub T); pub struct BasePage<T: Render>(pub PageMetadata, pub T);
impl<T: Render> Render for BasePage<T> { impl<T: Render> Render for BasePage<T> {
@@ -54,9 +51,11 @@ impl<T: Render> Render for BasePage<T> {
meta content="text/html; charset=UTF-8" http-equiv="content-type" {} meta content="text/html; charset=UTF-8" http-equiv="content-type" {}
meta property="og:type" content="website" {} meta property="og:type" content="website" {}
link rel="stylesheet" href=(Styles_Main::URL) {}
(meta) (meta)
title { (PreEscaped(meta.title.clone())) } title { (PreEscaped(meta.title.clone())) }
style { (PreEscaped(CSS)) }
} }
body { body {

View File

@@ -1,27 +1,50 @@
use assetserver::Asset; use assetserver::Asset;
use axum::http::header;
use macro_assets::assets; use macro_assets::assets;
use macro_sass::sass;
assets! { assets! {
prefix: "/assets" prefix: "/assets"
router: asset_router() router: asset_router()
//
// MARK: styles
//
Styles_Main {
source: sass!("css/main.scss").as_bytes(),
target: "/css/main.css",
headers: [
(header::CONTENT_TYPE, "text/css")
]
}
// //
// MARK: images // MARK: images
// //
Image_Cover { Image_Cover {
source: "../../assets/images/cover-small.jpg", source: include_bytes!("../../assets/images/cover-small.jpg"),
target: "/img/face.jpg" target: "/img/face.jpg",
headers: [
(header::CONTENT_TYPE, "image/jpg")
]
} }
Image_Betalupi { Image_Betalupi {
source: "../../assets/images/betalupi-map.png", source: include_bytes!("../../assets/images/betalupi-map.png"),
target: "/img/betalupi.png" target: "/img/betalupi.png",
headers: [
(header::CONTENT_TYPE, "image/png")
]
} }
Image_Icon { Image_Icon {
source: "../../assets/images/icon.png", source: include_bytes!("../../assets/images/icon.png"),
target: "/img/icon.png" target: "/img/icon.png",
headers: [
(header::CONTENT_TYPE, "image/png")
]
} }
// //
@@ -29,33 +52,51 @@ assets! {
// //
FiraCode_Bold_woff2 { FiraCode_Bold_woff2 {
source: "../../assets/fonts/fira/FiraCode-Bold.woff2", source: include_bytes!("../../assets/fonts/fira/FiraCode-Bold.woff2"),
target: "/fonts/FiraCode-Bold.woff2" target: "/fonts/FiraCode-Bold.woff2",
headers: [
(header::CONTENT_TYPE, "application/font-woff2")
]
} }
FiraCode_Light_woff2 { FiraCode_Light_woff2 {
source: "../../assets/fonts/fira/FiraCode-Light.woff2", source: include_bytes!("../../assets/fonts/fira/FiraCode-Light.woff2"),
target: "/fonts/FiraCode-Light.woff2" target: "/fonts/FiraCode-Light.woff2",
headers: [
(header::CONTENT_TYPE, "application/font-woff2")
]
} }
FiraCode_Medium_woff2 { FiraCode_Medium_woff2 {
source: "../../assets/fonts/fira/FiraCode-Medium.woff2", source: include_bytes!("../../assets/fonts/fira/FiraCode-Medium.woff2"),
target: "/fonts/FiraCode-Medium.woff2" target: "/fonts/FiraCode-Medium.woff2",
headers: [
(header::CONTENT_TYPE, "application/font-woff2")
]
} }
FiraCode_Regular_woff2 { FiraCode_Regular_woff2 {
source: "../../assets/fonts/fira/FiraCode-Regular.woff2", source: include_bytes!("../../assets/fonts/fira/FiraCode-Regular.woff2"),
target: "/fonts/FiraCode-Regular.woff2" target: "/fonts/FiraCode-Regular.woff2",
headers: [
(header::CONTENT_TYPE, "application/font-woff2")
]
} }
FiraCode_SemiBold_woff2 { FiraCode_SemiBold_woff2 {
source: "../../assets/fonts/fira/FiraCode-SemiBold.woff2", source: include_bytes!("../../assets/fonts/fira/FiraCode-SemiBold.woff2"),
target: "/fonts/FiraCode-SemiBold.woff2" target: "/fonts/FiraCode-SemiBold.woff2",
headers: [
(header::CONTENT_TYPE, "application/font-woff2")
]
} }
FiraCode_VF_woff2 { FiraCode_VF_woff2 {
source: "../../assets/fonts/fira/FiraCode-VF.woff2", source: include_bytes!("../../assets/fonts/fira/FiraCode-VF.woff2"),
target: "/fonts/FiraCode-VF.woff2" target: "/fonts/FiraCode-VF.woff2",
headers: [
(header::CONTENT_TYPE, "application/font-woff2")
]
} }
// //
@@ -63,32 +104,50 @@ assets! {
// //
Fa_Brands_woff2 { Fa_Brands_woff2 {
source: "../../assets/fonts/fa/fa-brands-400.woff2", source: include_bytes!("../../assets/fonts/fa/fa-brands-400.woff2"),
target: "/fonts/fa/fa-brands-400.woff2" target: "/fonts/fa/fa-brands-400.woff2",
headers: [
(header::CONTENT_TYPE, "application/font-woff2")
]
} }
Fa_Regular_woff2 { Fa_Regular_woff2 {
source: "../../assets/fonts/fa/fa-regular-400.woff2", source: include_bytes!("../../assets/fonts/fa/fa-regular-400.woff2"),
target: "/fonts/fa/fa-regular-400.woff2" target: "/fonts/fa/fa-regular-400.woff2",
headers: [
(header::CONTENT_TYPE, "application/font-woff2")
]
} }
Fa_Solid_woff2 { Fa_Solid_woff2 {
source: "../../assets/fonts/fa/fa-solid-900.woff2", source: include_bytes!("../../assets/fonts/fa/fa-solid-900.woff2"),
target: "/fonts/fa/fa-solid-900.woff2" target: "/fonts/fa/fa-solid-900.woff2",
headers: [
(header::CONTENT_TYPE, "application/font-woff2")
]
} }
Fa_Brands_ttf { Fa_Brands_ttf {
source: "../../assets/fonts/fa/fa-brands-400.ttf", source: include_bytes!("../../assets/fonts/fa/fa-brands-400.ttf"),
target: "/fonts/fa/fa-brands-400.ttf" target: "/fonts/fa/fa-brands-400.ttf",
headers: [
(header::CONTENT_TYPE, "application/font-ttf")
]
} }
Fa_Regular_ttf { Fa_Regular_ttf {
source: "../../assets/fonts/fa/fa-regular-400.ttf", source: include_bytes!("../../assets/fonts/fa/fa-regular-400.ttf"),
target: "/fonts/fa/fa-regular-400.ttf" target: "/fonts/fa/fa-regular-400.ttf",
headers: [
(header::CONTENT_TYPE, "application/font-ttf")
]
} }
Fa_Solid_ttf { Fa_Solid_ttf {
source: "../../assets/fonts/fa/fa-solid-900.ttf", source: include_bytes!("../../assets/fonts/fa/fa-solid-900.ttf"),
target: "/fonts/fa/fa-solid-900.ttf" target: "/fonts/fa/fa-solid-900.ttf",
headers: [
(header::CONTENT_TYPE, "application/font-ttf")
]
} }
} }

View File

@@ -3,7 +3,7 @@ use axum::routing::get;
use tracing::info; use tracing::info;
use utoipa::OpenApi; use utoipa::OpenApi;
mod assets; pub mod assets;
mod betalupi; mod betalupi;
mod index; mod index;
mod links; mod links;