Add dynamic-assets feature to facilitate rapid feedback when working on the assets

This commit is contained in:
Magnus Hoff 2018-07-09 21:27:34 +02:00
parent 8b0e58c24c
commit 963d70ff7a
6 changed files with 162 additions and 41 deletions

View file

@ -79,6 +79,9 @@ git = "https://github.com/maghoff/pulldown-cmark.git"
indoc = "0.2"
matches = "0.1"
[features]
dynamic-assets = []
[profile]
[profile.release]

View file

@ -1,30 +1,82 @@
use futures::Future;
use web::{Resource, ResponseFuture};
#[cfg(not(feature="dynamic-assets"))]
mod static_assets {
use std::collections::HashMap;
use futures::Future;
use web::{Resource, ResponseFuture};
// The CSS should be built to a single CSS file at compile time
#[derive(StaticResource)]
#[filename = "assets/themes.css"]
#[mime = "text/css"]
pub struct ThemesCss;
// The CSS should be built to a single CSS file at compile time
#[derive(StaticResource)]
#[filename = "assets/themes.css"]
#[mime = "text/css"]
pub struct ThemesCss;
#[derive(StaticResource)]
#[filename = "assets/style.css"]
#[mime = "text/css"]
pub struct StyleCss;
#[derive(StaticResource)]
#[filename = "assets/style.css"]
#[mime = "text/css"]
pub struct StyleCss;
#[derive(StaticResource)]
#[filename = "assets/script.js"]
#[mime = "application/javascript"]
pub struct ScriptJs;
#[derive(StaticResource)]
#[filename = "assets/script.js"]
#[mime = "application/javascript"]
pub struct ScriptJs;
#[derive(StaticResource)]
#[filename = "assets/search.js"]
#[mime = "application/javascript"]
pub struct SearchJs;
#[derive(StaticResource)]
#[filename = "assets/search.js"]
#[mime = "application/javascript"]
pub struct SearchJs;
// SIL Open Font License 1.1: http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL
// Copyright 2015 The Amatic SC Project Authors (contact@sansoxygen.com)
#[derive(StaticResource)]
#[filename = "assets/amatic-sc-v9-latin-regular.woff"]
#[mime = "application/font-woff"]
pub struct AmaticFont;
// SIL Open Font License 1.1: http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL
// Copyright 2015 The Amatic SC Project Authors (contact@sansoxygen.com)
// #[derive(StaticResource)]
// #[filename = "assets/amatic-sc-v9-latin-regular.woff"]
// #[mime = "application/font-woff"]
// pub struct AmaticFont;
type BoxResource = Box<Resource + Sync + Send>;
type ResourceFn = Box<Fn() -> BoxResource + Sync + Send>;
lazy_static! {
pub static ref ASSETS_MAP: HashMap<&'static str, ResourceFn> = hashmap!{
// The CSS should be built to a single CSS file at compile time
ThemesCss::resource_name() =>
Box::new(|| Box::new(ThemesCss) as BoxResource) as ResourceFn,
StyleCss::resource_name() =>
Box::new(|| Box::new(StyleCss) as BoxResource) as ResourceFn,
ScriptJs::resource_name() =>
Box::new(|| Box::new(ScriptJs) as BoxResource) as ResourceFn,
SearchJs::resource_name() =>
Box::new(|| Box::new(SearchJs) as BoxResource) as ResourceFn,
};
}
}
#[cfg(not(feature="dynamic-assets"))]
pub use self::static_assets::*;
#[cfg(feature="dynamic-assets")]
mod dynamic_assets {
pub struct ThemesCss;
impl ThemesCss {
pub fn resource_name() -> &'static str { "themes.css" }
}
pub struct StyleCss;
impl StyleCss {
pub fn resource_name() -> &'static str { "style.css" }
}
pub struct ScriptJs;
impl ScriptJs {
pub fn resource_name() -> &'static str { "script.js" }
}
pub struct SearchJs;
impl SearchJs {
pub fn resource_name() -> &'static str { "search.js" }
}
}
#[cfg(feature="dynamic-assets")]
pub use self::dynamic_assets::*;

View file

@ -7,6 +7,9 @@ pub const PROJECT_NAME: &str = env!("CARGO_PKG_NAME");
const SOFT_HYPHEN: &str = "\u{00AD}";
#[cfg(all(not(debug_assertions), feature="dynamic-assets"))]
compile_error!("dynamic-assets must not be used for production");
lazy_static! {
pub static ref VERSION: String = || -> String {
let mut components = Vec::<String>::new();
@ -17,6 +20,9 @@ lazy_static! {
#[cfg(test)]
components.push("test".into());
#[cfg(feature="dynamic-assets")]
components.push("dynamic-assets".into());
if let None = option_env!("CONTINUOUS_INTEGRATION") {
components.push("local-build".into());
}

View file

@ -7,6 +7,7 @@ mod changes_resource;
mod diff_resource;
mod html_resource;
mod new_article_resource;
mod read_only_resource;
mod search_resource;
mod sitemap_resource;
mod temporary_redirect_resource;
@ -18,6 +19,7 @@ pub use self::changes_resource::{ChangesLookup, ChangesResource};
pub use self::diff_resource::{DiffLookup, DiffResource};
pub use self::html_resource::HtmlResource;
pub use self::new_article_resource::NewArticleResource;
pub use self::read_only_resource::ReadOnlyResource;
pub use self::search_resource::SearchLookup;
pub use self::sitemap_resource::SitemapResource;
pub use self::temporary_redirect_resource::TemporaryRedirectResource;

View file

@ -0,0 +1,38 @@
use futures::Future;
use hyper::header::{ContentType, ContentLength, CacheControl, CacheDirective};
use hyper::server::*;
use hyper::StatusCode;
use web::{Resource, ResponseFuture};
#[allow(unused)]
pub struct ReadOnlyResource {
pub content_type: ::hyper::mime::Mime,
pub body: Vec<u8>,
}
impl Resource for ReadOnlyResource {
fn allow(&self) -> Vec<::hyper::Method> {
use ::hyper::Method::*;
vec![Options, Head, Get]
}
fn head(&self) -> ResponseFuture {
Box::new(::futures::finished(Response::new()
.with_status(StatusCode::Ok)
.with_header(ContentType(self.content_type.clone()))
.with_header(CacheControl(vec![
CacheDirective::MustRevalidate,
CacheDirective::NoStore,
]))
))
}
fn get(self: Box<Self>) -> ResponseFuture {
Box::new(self.head().map(move |head|
head
.with_header(ContentLength(self.body.len() as u64))
.with_body(self.body.clone())
))
}
}

View file

@ -8,29 +8,16 @@ use percent_encoding::percent_decode;
use slug::slugify;
use resources::*;
use assets::*;
use state::State;
use web::{Lookup, Resource};
#[allow(unused)]
use assets::*;
type BoxResource = Box<Resource + Sync + Send>;
type ResourceFn = Box<Fn() -> BoxResource + Sync + Send>;
lazy_static! {
static ref ASSETS_MAP: HashMap<&'static str, ResourceFn> = hashmap!{
// The CSS should be built to a single CSS file at compile time
ThemesCss::resource_name() =>
Box::new(|| Box::new(ThemesCss) as BoxResource) as ResourceFn,
StyleCss::resource_name() =>
Box::new(|| Box::new(StyleCss) as BoxResource) as ResourceFn,
ScriptJs::resource_name() =>
Box::new(|| Box::new(ScriptJs) as BoxResource) as ResourceFn,
SearchJs::resource_name() =>
Box::new(|| Box::new(SearchJs) as BoxResource) as ResourceFn,
};
static ref LICENSES_MAP: HashMap<&'static str, ResourceFn> = hashmap!{
"bsd-3-clause" => Box::new(|| Box::new(
HtmlResource::new(Some("../"), "The 3-Clause BSD License", include_str!("licenses/bsd-3-clause.html"))
@ -85,6 +72,35 @@ fn map_lookup(map: &HashMap<&str, ResourceFn>, path: &str) ->
}
}
#[allow(unused)]
fn fs_lookup(root: &str, path: &str) ->
FutureResult<Option<BoxResource>, Box<::std::error::Error + Send + Sync>>
{
use std::fs::File;
use std::io::prelude::*;
let extension = path.rsplitn(2, ".").next();
let content_type = match extension {
Some("css") => "text/css",
Some("js") => "application/javascript",
Some("woff") => "application/font-woff",
_ => "application/binary",
}.parse().unwrap();
let mut filename = root.to_string();
filename.push_str(path);
let mut f = File::open(&filename)
.unwrap_or_else(|_| panic!(format!("Not found: {}", filename)));
let mut body = Vec::new();
f.read_to_end(&mut body)
.expect("Unable to read file");
finished(Some(Box::new(ReadOnlyResource { content_type, body })))
}
impl WikiLookup {
pub fn new(state: State, show_authors: bool) -> WikiLookup {
let changes_lookup = ChangesLookup::new(state.clone(), show_authors);
@ -168,6 +184,10 @@ impl WikiLookup {
Box::new(finished(Some(Box::new(AboutResource::new()) as BoxResource))),
("_about", Some(license)) =>
Box::new(map_lookup(&LICENSES_MAP, license)),
#[cfg(feature="dynamic-assets")]
("_assets", Some(asset)) =>
Box::new(fs_lookup(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/"), asset)),
#[cfg(not(feature="dynamic-assets"))]
("_assets", Some(asset)) =>
Box::new(map_lookup(&ASSETS_MAP, asset)),
("_by_id", Some(tail)) =>