extern crate reqwest; use std::process::Command; use std::env; use std::path::{Path, PathBuf}; use std::fs::canonicalize; use std::fs::{File, create_dir_all}; use std::io::{Write, copy}; pub fn yarn_install () { if !Command::new("yarn").status().expect("failed to execute yarn process").success() { panic!("Failed to execute yarn process"); } } pub fn browserify (infile: &str, outfile: &str) -> PathBuf { let out_dir = env::var("OUT_DIR").unwrap(); let out_dir = Path::new(&out_dir); let full_outfile = out_dir.join(outfile); let status = Command::new("node_modules/.bin/browserify") .args(&[infile, "-o", full_outfile.to_str().expect("failed to build output path")]) .status() .expect("failed to build js bundle"); if !status.success() { panic!("Failed to browserify input file {}", infile); } full_outfile } pub fn lessc (infile: &str, outfile: &str) -> PathBuf { let out_dir = env::var("OUT_DIR").unwrap(); let out_dir = Path::new(&out_dir); let full_outfile = out_dir.join(outfile); let status = Command::new("node_modules/.bin/lessc") .args(&[infile, full_outfile.to_str().expect("failed to build output path")]) .status() .expect("failed to build css bundle"); if !status.success() { panic!("Failed to lessc input file {}", infile); } full_outfile } pub fn sass (infile: &str, outfile: &str) -> PathBuf { let out_dir = env::var("OUT_DIR").unwrap(); let out_dir = Path::new(&out_dir); let full_outfile = out_dir.join(outfile); let status = Command::new("node_modules/.bin/sass") .args(&[infile, full_outfile.to_str().expect("failed to build output path")]) .status() .expect("failed to build css bundle"); if !status.success() { panic!("Failed to sass input file {}", infile); } full_outfile } pub struct ResourcesGenerator { resources: Vec } impl ResourcesGenerator { pub fn new () -> ResourcesGenerator { ResourcesGenerator { resources: vec![] } } pub fn add (&mut self, resource: Resource) -> &mut ResourcesGenerator { self.resources.push(resource); self } pub fn write (&self) { let out_dir = env::var("OUT_DIR").unwrap(); let out_dir = Path::new(&out_dir); let mut outfile = File::create(out_dir.join("resources.rs").to_str().expect("failed to build output path")).expect("failed to create outfile"); let mut consts = String::new(); let mut routes = String::new(); let mut routes_fn = String::from("pub fn routes() -> Vec {\n\troutes!["); let mut handlebars = String::new(); for resource in &self.resources { let include = self.create_include(&resource); if resource.constant.is_some() { consts.push_str(&self.create_const(&resource, include.as_ref().expect("Expected include for constant"))); } if resource.mount_at.is_some() { let (function_name, route_function) = match include { Some(ref include) => self.create_rocket_route_include(resource, &include), None => self.create_rocket_route_link(resource) }; routes.push_str(&route_function); routes_fn.push_str(&function_name); routes_fn.push(','); } if resource.template_name.is_some() { handlebars.push_str(&(match include { Some(ref include) => self.create_handlebars_template_include(resource, &include), None => self.create_handlebars_template_link(resource) })); } } if !routes.is_empty() { self.write_rocket_routes(&mut outfile, &routes, &routes_fn); } if !handlebars.is_empty () { self.write_handlebars_templates(&mut outfile, &handlebars); } outfile.write(consts.as_bytes()); outfile.write(b"\n"); } fn create_include(&self, resource: &Resource) -> Option { if resource.link { None } else { let canonical_source = canonicalize(&resource.source).expect(&format!("failed to get canonical path for {:?}", resource.source)); Some(format!( "include_{}!(\"{}\")", if resource.raw { "bytes" } else { "str" }, canonical_source.display() )) } } fn create_const(&self, resource: &Resource, include: &str) -> String { format!( "const {}: &'static {} = {};\n", resource.constant.unwrap(), if resource.raw { "[u8]" } else { "str" }, include ) } fn create_rocket_route_include(&self, resource: &Resource, include: &str) -> (String, String) { let mount_at = resource.mount_at.as_ref().unwrap(); let mut function_name = String::from("route"); let mut function_return_type = "&'static str"; let mut function_return_value = String::from(resource.constant.unwrap_or(&include)); if resource.raw { function_return_type = "RawByteResource"; function_return_value = format!("RawByteResource({})", function_return_value); } function_name.push_str(&mount_at.endpoint.replace("/", "_").replace(".", "_").replace("-", "_")); (function_name.clone(), format!( "#[get(\"{}\")]\nfn {}() -> Content<{}> {{\n\tContent(ContentType::{}, {})\n}}\n\n", mount_at.endpoint, function_name, function_return_type, mount_at.content_type, function_return_value )) } fn create_rocket_route_link(&self, resource: &Resource) -> (String, String) { let mount_at = resource.mount_at.as_ref().unwrap(); let mut function_name = String::from("route"); function_name.push_str(&mount_at.endpoint.replace("/", "_").replace(".", "_").replace("-", "_")); (function_name.clone(), format!( "#[get(\"{}\")]\nfn {}() -> io::Result {{\n\tNamedFile::open(\"{}\")\n}}\n\n", mount_at.endpoint, function_name, resource.source.display() )) } fn write_rocket_routes(&self, outfile: &mut File, routes: &str, routes_fn: &str) { outfile.write(b"use rocket::{Route, Response, Request};\nuse rocket::response::{Content, Responder, NamedFile};\nuse rocket::http::{ContentType, Status};\n\n"); outfile.write(b"use std::io;\nuse std::io::Cursor;\n\n"); outfile.write(b"struct RawByteResource(&'static [u8]);\n"); outfile.write(b"impl<'r> Responder<'r> for RawByteResource { fn respond_to(self, _: &Request) -> Result, Status> { Response::build() .header(ContentType::Binary) .sized_body(Cursor::new(self.0)) .ok() } }"); outfile.write(routes.as_bytes()); outfile.write(routes_fn.as_bytes()); outfile.write(b"]\n}"); } fn create_handlebars_template_include(&self, resource: &Resource, include: &str) -> String { let template_name = resource.template_name.unwrap(); format!( "\n\thbs.register_template_string(\"{}\", {}).expect(\"Failed to register template: {}\");", template_name, resource.constant.unwrap_or(&include), template_name ) } fn create_handlebars_template_link(&self, resource: &Resource) -> String { let template_name = resource.template_name.unwrap(); format!( "\n\thbs.register_template_file(\"{}\", {}).expect(\"Failed to register template: {}\");", template_name, resource.source.display(), template_name ) } fn write_handlebars_templates(&self, outfile: &mut File, handlebars: &str) { outfile.write(b"use handlebars::Handlebars;\n\n"); outfile.write(b"pub fn handlebars() -> Handlebars {\n"); outfile.write(b"\tlet mut hbs = Handlebars::new();"); outfile.write(handlebars.as_bytes()); outfile.write(b"hbs\n}"); } } #[derive(Default)] pub struct Resource { pub source: PathBuf, pub mount_at: Option, pub constant: Option<&'static str>, pub template_name: Option<&'static str>, pub raw: bool, pub link: bool } pub struct ResourceMount { pub endpoint: String, pub content_type: &'static str } pub struct Downloader { cache: PathBuf } impl Downloader { pub fn new>(cache: P) -> Downloader { Downloader { cache: cache.as_ref().to_path_buf() } } pub fn get(&self, url: &str) -> DownloaderResult { let filename = url.get((url.rfind('/').expect("not a valid url") + 1..)).expect("failed to parse filename"); self.get_to(url, filename) } pub fn get_to(&self, url: &str, filename: &str) -> DownloaderResult { if !self.cache.exists() { create_dir_all(self.cache.as_path()).expect("failed to create cache directory"); } let mut out_path = self.cache.clone(); out_path.push(filename); let was_cached = out_path.exists(); if !was_cached { let mut result = reqwest::get(url).expect("failed to download url"); let mut file = File::create(out_path.as_path()).expect("failed to open file"); copy(&mut result, &mut file).expect("failed to write file"); } DownloaderResult { out_path: out_path, was_cached: was_cached } } } pub struct DownloaderResult { pub out_path: PathBuf, pub was_cached: bool }