Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tide feature and improved example #94

Merged
merged 9 commits into from
Aug 7, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions examples/tide/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
[package]
name = "tide"
name = "ructe-tide"
version = "0.4.0"
authors = ["Rasmus Kaj <[email protected]>"]
edition = "2018"

build = "src/build.rs"

[build-dependencies]
ructe = { path = "../.." }
ructe = { path = "../..", features = ["mime03", "sass"] }

[dependencies]
async-std = { version = "1.6.0", features = ["attributes"] }
tide = "0.10.0"
tide = "0.12.0"
mime = "0.3.16"
httpdate = "0.3.1"
3 changes: 3 additions & 0 deletions examples/tide/src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,8 @@ use ructe::{Ructe, RucteError};

fn main() -> Result<(), RucteError> {
let mut ructe = Ructe::from_env()?;
let mut statics = ructe.statics()?;
statics.add_files("statics")?;
statics.add_sass_file("style.scss")?;
ructe.compile_templates("templates")
}
102 changes: 92 additions & 10 deletions examples/tide/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,106 @@
// And finally, include the generated code for templates and static files.
include!(concat!(env!("OUT_DIR"), "/templates.rs"));

//! An example of how ructe can be used with the tide framework.
mod ructe_tide;
use ructe_tide::Render;

use tide::{Response, StatusCode};
use httpdate::fmt_http_date;
use std::future::Future;
use std::io::{self, Write};
use std::pin::Pin;
use std::str::FromStr;
use std::time::{Duration, SystemTime};
use templates::statics::{cloud_svg, StaticFile};
use tide::http::headers::EXPIRES;
use tide::http::{mime, Error};
use tide::{Next, Redirect, Request, Response, StatusCode};

/// Main entry point.
///
/// Set up an app and start listening for requests.
#[async_std::main]
async fn main() -> Result<(), std::io::Error> {
let mut app = tide::new();

app.at("/").get(|_| async {
let mut res = Response::new(StatusCode::Ok);
res.render_html(|o| Ok(templates::hello(o, "world")?))?;
Ok(res)
});
app.middleware(handle_error);
app.at("/static/*path").get(static_file);
app.at("/favicon.ico")
.get(Redirect::new(format!("/static/{}", cloud_svg.name)));
kaj marked this conversation as resolved.
Show resolved Hide resolved
app.at("/").get(frontpage);

let addr = "127.0.0.1:3000";
println!("Starting server on http://{}/", addr);
app.listen(addr).await?;

Ok(())
}

/// Handler for a page in the web site.
async fn frontpage(_req: Request<()>) -> Result<Response, Error> {
// A real site would probably have some business logic here.
let mut res = Response::new(StatusCode::Ok);
kaj marked this conversation as resolved.
Show resolved Hide resolved
res.render_html(|o| {
Ok(templates::page(o, &[("world", 5), ("tide", 7)])?)
})?;
Ok(res)
}

/// Handler for static files.
///
/// Ructe provides the static files as constants, and the StaticFile
/// interface to get a file by url path.
async fn static_file(req: Request<()>) -> Result<Response, Error> {
let path = req.param::<String>("path")?;
let data = StaticFile::get(&path)
.ok_or_else(|| Error::from_str(StatusCode::NotFound, "not found"))?;
Ok(Response::builder(StatusCode::Ok)
// TODO: Provide http_types::Mime directly in ructe.
.content_type(mime::Mime::from_str(data.mime.as_ref()).unwrap())
.header(EXPIRES, fmt_http_date(SystemTime::now() + 180 * DAY))
.body(data.content)
.build())
}

/// 24 hours.
const DAY: Duration = Duration::from_secs(24 * 60 * 60);

/// This method can be used as a "template tag", i.e. a method that
/// can be called directly from a template.
fn footer(out: &mut dyn Write) -> io::Result<()> {
templates::footer(
out,
&[
("ructe", "https://crates.io/crates/ructe"),
("tide", "https://crates.io/crates/tide"),
],
)
}

/// A middleware to log errors and render a html error message.
///
/// If the response has content, this function does not overwrite it.
fn handle_error<'a>(
request: Request<()>,
next: Next<'a, ()>,
) -> Pin<Box<dyn Future<Output = Result<Response, Error>> + Send + 'a>> {
Box::pin(async {
// I don't really like to create this string for every request,
// but when I see if there is an error, the request is consumed.
let rdesc = format!("{} {:?}", request.method(), request.url());
let mut res = next.run(request).await;
let status = res.status();
if status.is_client_error() || status.is_server_error() {
println!("Error {} on {}: {:?}", status, rdesc, res.error());
if res.is_empty().unwrap_or(false) {
res.render_html(|o| {
Ok(templates::error(
o,
status,
status.canonical_reason(),
)?)
})?
}
}
Ok(res)
})
}

// And finally, include the generated code for templates and static files.
include!(concat!(env!("OUT_DIR"), "/templates.rs"));
1 change: 1 addition & 0 deletions examples/tide/statics/btfl.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions examples/tide/statics/cloud.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions examples/tide/statics/grass.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/tide/statics/squirrel.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
74 changes: 74 additions & 0 deletions examples/tide/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
html, body {
margin: 0;
padding: 0;
}

body {
background: left bottom / auto 9% repeat-x url(static-name(grass.svg)) fixed,
82% 96% / 5vh auto no-repeat url(static-name(btfl.svg)) fixed,
center bottom / auto 10% repeat-x url(static-name(grass.svg)) fixed,
right bottom / auto 11% repeat-x url(static-name(grass.svg)) fixed,
10% 90% / 8vh auto no-repeat url(static-name(btfl.svg)) fixed,
linear-gradient(#519dd2, #7ec0ec) fixed;
}

main {
padding: 2ex 3ex;
margin: 8vh auto 1em;
max-width: 37em;
background: white;
border-radius: 1em;
position: relative;

&:before, &:after {
content: url(static-name(cloud.svg));
display: block;
position: absolute;
z-index: -1;
}
&:before {
width: 52%;
top: -7vh;
left: -7%;
}
&:after {
width: 30%;
top: -6vh;
right: -4%;
}
}

footer {
background: #84ff5e;
border-radius: 1em 0 0;
bottom: 0;
padding: 0 1em;
position: fixed;
right: 0;
}

h1 {
margin: 0 0 1ex;
}
figure {
float: right;
margin: 0 0 1ex 1em;
}
p {
margin: 0 0 1em;
}

.error {
body {
background: left bottom / auto 9% repeat-x url(static-name(grass.svg)) fixed,
82% 98% / 5vh auto no-repeat url(static-name(btfl.svg)) fixed,
10% 97% / 6vh auto no-repeat url(static-name(btfl.svg)) fixed,
right bottom / auto 11% repeat-x url(static-name(grass.svg)) fixed,
linear-gradient(#89a, #568) fixed;
}
main {
background: linear-gradient(#fff, #fff, #eee, #ccc, #888) padding-box;
border: solid 1px rgba(white, 0.3);
border-width: 0 2px 2px 1px;
}
}
25 changes: 25 additions & 0 deletions examples/tide/templates/error.rs.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
@use super::statics::*;
@use crate::footer;
@use tide::StatusCode;

@(code: StatusCode, message: &str)

<!doctype html>
<html lang="en" class="error">
<head>
<title>Error @(u16::from(code)): @code.canonical_reason()</title>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<link rel="stylesheet" type="text/css" href="/static/@style_css.name"/>
</head>
<body>
<main>
<h1>@code.canonical_reason()</h1>

<p>@message</p>
<p>We are sorry about this.
In a real application, this would mention the incident having
been logged, and giving contact details for further reporting.</p>
</main>
@:footer()
</body>
</html>
14 changes: 14 additions & 0 deletions examples/tide/templates/footer.rs.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
@(frameworks: &[(&str, &str)])

<footer>
@if let Some(((last_name, last_href), prev)) = frameworks.split_last() {
Made with
@if let Some(((last_name, last_href), prev)) = prev.split_last() {
@for (name, href) in prev {
<a href="@href">@name</a>,
}
<a href="@last_href">@last_name</a> and
}
<a href="@last_href">@last_name</a>.
}
</footer>
36 changes: 36 additions & 0 deletions examples/tide/templates/page.rs.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
@use super::statics::*;
@use crate::footer;

@(paras: &[(&str, usize)])

<!doctype html>
<html lang="en">
<head>
<title>Example</title>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<link rel="stylesheet" type="text/css" href="/static/@style_css.name"/>
</head>
<body>
<main>
<h1>Example</h1>

<p>This is a simple sample page,
to be served with warp.
It contains an image of a squirrel and not much more.</p>

<figure>
<img src="/static/@squirrel_jpg.name" alt="Squirrel!"
width="240" height="160"/>
<figcaption>A squirrel</figcaption>
</figure>

@for (order, n) in paras {
<p>This is a @order paragraph, with @n repeats.
@for _ in 1..=*n {
This is a @order paragraph.
}
}
</main>
@:footer()
</body>
</html>