Skip to content

Commit

Permalink
ssg
Browse files Browse the repository at this point in the history
  • Loading branch information
vincent-thomas committed Jan 16, 2025
1 parent 70ac754 commit a84e377
Show file tree
Hide file tree
Showing 12 changed files with 181 additions and 18 deletions.
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ members = [
"titan-html-core",
"titan-html-derive",
"titan-lambda"
]
, "titan-derive"]

[workspace.package]
authors = ["Vincent Thomas"]
Expand Down
16 changes: 8 additions & 8 deletions titan-core/src/respond.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,36 +42,36 @@ use titan_http::{body::Body, header, Response, ResponseBuilder, StatusCode};
/// // converted into an HTTP response.
/// ```
pub trait Respondable {
fn respond(self) -> Response<Body>;
fn respond(self) -> Response;
}

impl<T, E> Respondable for Result<T, E>
where
T: Respondable,
E: Respondable,
{
fn respond(self) -> Response<Body> {
fn respond(self) -> Response {
match self {
Ok(t) => t.respond(),
Err(e) => e.respond(),
}
}
}

impl Respondable for Response<Body> {
fn respond(self) -> Response<Body> {
impl Respondable for Response {
fn respond(self) -> Response {
self
}
}

impl Respondable for Infallible {
fn respond(self) -> Response<Body> {
fn respond(self) -> Response {
panic!("Not fallible :(")
}
}

impl Respondable for () {
fn respond(self) -> Response<Body> {
fn respond(self) -> Response {
ResponseBuilder::new().status(204).body(Body::from(())).unwrap()
}
}
Expand All @@ -80,7 +80,7 @@ impl<T> Respondable for (StatusCode, T)
where
T: Respondable,
{
fn respond(self) -> Response<Body> {
fn respond(self) -> Response {
let (status, body) = self;
let mut res = body.respond();

Expand Down Expand Up @@ -111,7 +111,7 @@ macro_rules! impl_respondable_for_int {
($($t:ty)*) => {
$(
impl Respondable for $t {
fn respond(self) -> Response<Body> {
fn respond(self) -> Response {
let body = Body::from(self);

let mut res = Response::new(body);
Expand Down
19 changes: 19 additions & 0 deletions titan-derive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "titan-derive"
authors.workspace = true
categories.workspace = true
keywords.workspace = true
license.workspace = true
edition.workspace = true
version.workspace = true
repository.workspace = true
documentation.workspace = true
rust-version.workspace = true

[lib]
proc-macro = true

[dependencies]
syn = { version = "2", features = ["full"] }
quote = "1"
proc-macro2 = "1.0.92"
77 changes: 77 additions & 0 deletions titan-derive/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use syn::parse_macro_input;
use syn::ItemFn;

#[proc_macro_attribute]
pub fn ssg(_: TokenStream, input: TokenStream) -> TokenStream {
let item = parse_macro_input!(input as ItemFn);

if !item.sig.inputs.is_empty() {
return syn::Error::new(
item.sig.ident.span(),
"Error: SSG routes cannot have arguments",
)
.into_compile_error()
.into();
}
impl_ssg(item).into()
}

fn impl_ssg(item: ItemFn) -> TokenStream2 {
let struct_ident = item.sig.ident.clone();

let ident_cache_str =
format!("{}_CACHE", item.sig.ident.to_string().to_uppercase());
let ident_cache = syn::Ident::new(&ident_cache_str, item.sig.ident.span());

let nice_fn = item.block;

quote::quote! {
titan::lazy_static! {
static ref #ident_cache: std::sync::RwLock<Option<Vec<u8>>> = std::sync::RwLock::new(None);
}

#[allow(non_camel_case_types)]
#[derive(Clone)]
pub struct #struct_ident;

impl titan::Handler<()> for #struct_ident {
type Output = titan::http::Response;
type Future =
std::pin::Pin<Box<dyn std::future::Future<Output = Self::Output> + Send>>;
fn call(&self, _: ()) -> Self::Future {
let _lock = match #ident_cache.read() {
Ok(v) => v,
Err(_) => panic!("oh no"),
};

if let Some(cache) = _lock.as_ref() {
let body =
titan::http::body::Body::Full(cache.clone().into_boxed_slice());
return Box::pin(async move {
titan::http::ResponseBuilder::new().status(200).body(body).unwrap()
});
};

Box::pin(async move {
let response = titan::FutureExt::map(async #nice_fn, |x| x.respond()).await;


match response.body() {
titan::http::body::Body::Full(ref body) => {
let mut refs = #ident_cache.write().unwrap();
*refs = Some(body.clone().to_vec());
}
titan::http::body::Body::Stream(_) => {
panic!(
"Body::Stream is not available in a cached request response :("
)
}
};
response
})
}
}
}
}
34 changes: 30 additions & 4 deletions titan-html-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ use lightningcss::{printer::PrinterOptions, properties::Property};
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{parse_macro_input, Field, Fields, ItemStruct, LitStr};
use titan_utils::validatecss::{self, CSSValidationError};
use syn::{parse_macro_input, Field, Fields, Ident, ItemStruct, LitStr};
use titan_utils::validatecss::{
validate_css, validate_globalcss, CSSValidationError,
};

fn from_stylerules_to_tokenstream(
prop: Vec<(String, Property<'_>)>,
Expand All @@ -29,6 +31,30 @@ fn from_stylerules_to_tokenstream(
#(#styles_tokens),*
}
}
//
//// Helper function to find and extract variables from CSS values
//fn extract_variables(css: &str) -> Vec<(usize, usize, String)> {
// let mut variables = Vec::new();
// let mut start = 0;
// while let Some(var_start) = css[start..].find("{") {
// if let Some(var_end) = css[start + var_start..].find('}') {
// let abs_start = start + var_start;
// let abs_end = start + var_start + var_end + 1;
// let var_name = css[abs_start + 1..abs_end - 1].to_string();
// variables.push((abs_start, abs_end, var_name));
// start = abs_end;
// } else {
// break;
// }
// }
// variables
//}
//
//#[derive(Debug)]
//enum StringBit {
// Var(Ident),
// Text(String),
//}

#[proc_macro]
pub fn global_css(input: TokenStream) -> TokenStream {
Expand All @@ -37,7 +63,7 @@ pub fn global_css(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as LitStr);
let result = input.value();

let err = validatecss::validate_globalcss(&result);
let err = validate_globalcss(&result);

quote! { titan::html::tags::Style::Text(#err.to_string()) }.into()
}
Expand All @@ -49,7 +75,7 @@ pub fn css(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as LitStr);
let result = input.value();

if let Err(err) = validatecss::validate_css(&result) {
if let Err(err) = validate_css(&result) {
match err {
CSSValidationError::FieldError(field) => {
let span = input.span();
Expand Down
4 changes: 1 addition & 3 deletions titan-html/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,5 @@ pub fn render(root: Html) -> String {
if let Some(nonce) = root.with_csp_nonce {
html.apply_nonce(&nonce);
}
dbg!(&html);

dbg!(format!("{}{}", DOCTYPE, html.to_string()))
format!("{}{}", DOCTYPE, html.to_string())
}
2 changes: 2 additions & 0 deletions titan/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,12 @@ titan-html = { path = "../titan-html", version = "0.4.0" }
titan-router = { path = "../titan-router", version = "0.4.0" }
titan-server = { path = "../titan-server", version = "0.4.0" }
titan-utils = { path = "../titan-utils", version = "0.4.0" }
titan-derive = { path = "../titan-derive", version = "0.4.0" }

pin-project-lite = "0.2.14"
futures-util = "0.3.31"
lambda_http = { version = "0.14.0", optional = true }
lazy_static = "1.5.0"

[dev-dependencies]
tokio = { version = "1.38.1", features = ["macros", "rt-multi-thread", "time"] }
15 changes: 13 additions & 2 deletions titan/examples/hello_world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ const TESTING: &[StyleRule] = css!(
"
);

async fn index() -> impl Respondable {
#[ssg]
fn index() -> impl Respondable {
println!("ran");
Html::from((
Head::default().global_style(global_css!("")).reset_css(),
Body::default().children([
Expand All @@ -52,11 +54,20 @@ async fn index() -> impl Respondable {
.with_csp("examplenonce")
}

use titan_derive::ssg;

#[ssg]
pub fn testing() -> titan_html::tags::html::Html {
println!("ran");
Html::from((Head::default(), Body::default()))
}

#[tokio::main]
async fn main() -> io::Result<()> {
let listener = TcpListener::bind("0.0.0.0:4000").await.unwrap();

let app = App::default().at("/", web::get(index));
let app =
App::default().at("/", web::get(index)).at("/test", web::get(testing));

titan::serve(listener, app).await
}
10 changes: 10 additions & 0 deletions titan/examples/nice_fns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ struct Queries {
test: Option<i32>,
}

use titan_derive::ssg;

#[titan::ssg]
pub fn testing() -> titan_html::tags::html::Html {
titan::html::tags::html::Html::from((
titan::html::tags::head::Head::default(),
titan::html::tags::Body::default(),
))
}

async fn index(
Cookies(cookies): Cookies,
authorization::Bearer(token): authorization::Bearer,
Expand Down
4 changes: 4 additions & 0 deletions titan/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ pub mod prelude;
pub mod route;
mod utils;

// For titan-derive
pub use utils::lazy_static;
pub use utils::FutureExt;

#[doc(hidden)]
#[cfg(feature = "internal-titan-lambda")]
pub mod lambda;
Expand Down
5 changes: 5 additions & 0 deletions titan/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ use std::task::{Context, Poll};
use futures_util::future::BoxFuture;
use titan_core::{Service, ServiceExt};

// Required for titan-derive
pub use lazy_static::lazy_static;

pub use futures_util::FutureExt;

pub trait CloneService<R>: Service<R> {
fn clone_box(
&self,
Expand Down

0 comments on commit a84e377

Please sign in to comment.