From 963d70ff7aa47b2840b5cef4b7f71ccc09905e5e Mon Sep 17 00:00:00 2001 From: Magnus Hoff Date: Mon, 9 Jul 2018 21:27:34 +0200 Subject: [PATCH] Add dynamic-assets feature to facilitate rapid feedback when working on the assets --- Cargo.toml | 3 + src/assets.rs | 102 +++++++++++++++++++++------- src/build_config.rs | 6 ++ src/resources/mod.rs | 2 + src/resources/read_only_resource.rs | 38 +++++++++++ src/wiki_lookup.rs | 52 +++++++++----- 6 files changed, 162 insertions(+), 41 deletions(-) create mode 100644 src/resources/read_only_resource.rs diff --git a/Cargo.toml b/Cargo.toml index 706a2f7..d810dd6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,6 +79,9 @@ git = "https://github.com/maghoff/pulldown-cmark.git" indoc = "0.2" matches = "0.1" +[features] +dynamic-assets = [] + [profile] [profile.release] diff --git a/src/assets.rs b/src/assets.rs index fd12279..db22910 100644 --- a/src/assets.rs +++ b/src/assets.rs @@ -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; + type ResourceFn = Box 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::*; diff --git a/src/build_config.rs b/src/build_config.rs index 9c16d76..1ed7333 100644 --- a/src/build_config.rs +++ b/src/build_config.rs @@ -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::::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()); } diff --git a/src/resources/mod.rs b/src/resources/mod.rs index 4160bb4..28fcfcc 100644 --- a/src/resources/mod.rs +++ b/src/resources/mod.rs @@ -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; diff --git a/src/resources/read_only_resource.rs b/src/resources/read_only_resource.rs new file mode 100644 index 0000000..9ad2e65 --- /dev/null +++ b/src/resources/read_only_resource.rs @@ -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, +} + +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) -> ResponseFuture { + Box::new(self.head().map(move |head| + head + .with_header(ContentLength(self.body.len() as u64)) + .with_body(self.body.clone()) + )) + } +} diff --git a/src/wiki_lookup.rs b/src/wiki_lookup.rs index 5683089..76e2ea1 100644 --- a/src/wiki_lookup.rs +++ b/src/wiki_lookup.rs @@ -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; type ResourceFn = Box 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, 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)) =>