Actual templating progress
    
    
      
        Getty Ritter
        7 years ago
      
    
    
  
  
  | 1 | 
                  [ | 
              |
| 1 | [[package]] | |
| 2 | name = "base64" | |
| 3 | version = "0.6.0" | |
| 4 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 5 | dependencies = [ | |
| 6 | "byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", | |
| 7 | "safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", | |
| 8 | ] | |
| 9 | ||
| 10 | [[package]] | |
| 11 | name = "bitflags" | |
| 12 | version = "0.7.0" | |
| 13 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 14 | ||
| 15 | [[package]] | |
| 16 | name = "bitflags" | |
| 17 | version = "1.0.1" | |
| 18 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 19 | ||
| 20 | [[package]] | |
| 21 | name = "byteorder" | |
| 22 | version = "1.1.0" | |
| 23 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 24 | ||
| 25 | [[package]] | |
| 26 | name = "bytes" | |
| 27 | version = "0.4.5" | |
| 28 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 29 | dependencies = [ | |
| 30 | "byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", | |
| 31 | "iovec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", | |
| 32 | ] | |
| 33 | ||
| 34 | [[package]] | |
| 35 | name = "cfg-if" | |
| 36 | version = "0.1.2" | |
| 37 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 38 | ||
| 39 | [[package]] | |
| 2 | 40 | name = "cg" | 
| 3 | 41 | version = "0.1.0" | 
| 4 | 42 | dependencies = [ | 
| 5 | 43 | "futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", | 
| 6 | 44 | "hyper 0.11.7 (registry+https://github.com/rust-lang/crates.io-index)", | 
| 45 | "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", | |
| 7 | 46 | "mustache 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", | 
| 8 | 47 | "rusqlite 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", | 
| 48 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", | |
| 9 | 49 | "time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", | 
| 10 | 50 | ] | 
| 11 | ||
| 12 | [[package]] | |
| 13 | name = "base64" | |
| 14 | version = "0.6.0" | |
| 15 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 16 | dependencies = [ | |
| 17 | "byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", | |
| 18 | "safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", | |
| 19 | ] | |
| 20 | ||
| 21 | [[package]] | |
| 22 | name = "bitflags" | |
| 23 | version = "0.7.0" | |
| 24 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 25 | ||
| 26 | [[package]] | |
| 27 | name = "bitflags" | |
| 28 | version = "1.0.1" | |
| 29 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 30 | ||
| 31 | [[package]] | |
| 32 | name = "byteorder" | |
| 33 | version = "1.1.0" | |
| 34 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 35 | ||
| 36 | [[package]] | |
| 37 | name = "bytes" | |
| 38 | version = "0.4.5" | |
| 39 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 40 | dependencies = [ | |
| 41 | "byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", | |
| 42 | "iovec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", | |
| 43 | ] | |
| 44 | ||
| 45 | [[package]] | |
| 46 | name = "cfg-if" | |
| 47 | version = "0.1.2" | |
| 48 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 49 | 51 | |
| 50 | 52 | [[package]] | 
| 51 | 53 | name = "fuchsia-zircon" | 
| 126 | 128 | [[package]] | 
| 127 | 129 | name = "language-tags" | 
| 128 | 130 | version = "0.2.2" | 
| 131 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 132 | ||
| 133 | [[package]] | |
| 134 | name = "lazy_static" | |
| 135 | version = "0.2.11" | |
| 129 | 136 | source = "registry+https://github.com/rust-lang/crates.io-index" | 
| 130 | 137 | |
| 131 | 138 | [[package]] | 
| 423 | 430 | "checksum iovec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b6e8b9c2247fcf6c6a1151f1156932be5606c9fd6f55a2d7f9fc1cb29386b2f7" | 
| 424 | 431 | "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" | 
| 425 | 432 | "checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" | 
| 433 | "checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" | |
| 426 | 434 | "checksum lazycell 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3b585b7a6811fb03aa10e74b278a0f00f8dd9b45dc681f148bb29fa5cb61859b" | 
| 427 | 435 | "checksum libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "5ba3df4dcb460b9dfbd070d41c94c19209620c191b0340b929ce748a2bcd42d2" | 
| 428 | 436 | "checksum libsqlite3-sys 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1bad50e980f2a86252585f7522a92f17672b1ab5c7087db6a0e566ae6294b8c9" | 
| 9 | 9 | time = "*" | 
| 10 | 10 | mustache = "*" | 
| 11 | 11 | futures = "*" | 
| 12 | 
                   | 
              |
| 12 | rustc-serialize = "*" | |
| 13 | lazy_static = "0.2" | 
Binary diff not shown
      
      | 39 | 39 | CGError::SQLite(err) | 
| 40 | 40 | } | 
| 41 | 41 | } | 
| 42 | ||
| 43 | #[derive(Debug)] | |
| 44 | pub enum RuntimeError { | |
| 45 | SQLite(::rusqlite::Error), | |
| 46 | Mustache(::mustache::Error), | |
| 47 | IOError(::std::io::Error), | |
| 48 | UnknownPath(String), | |
| 49 | } | |
| 50 | ||
| 51 | impl From<::rusqlite::Error> for RuntimeError { | |
| 52 | fn from(err: ::rusqlite::Error) -> Self { | |
| 53 | RuntimeError::SQLite(err) | |
| 54 | } | |
| 55 | } | |
| 56 | ||
| 57 | impl From<::mustache::Error> for RuntimeError { | |
| 58 | fn from(err: ::mustache::Error) -> Self { | |
| 59 | RuntimeError::Mustache(err) | |
| 60 | } | |
| 61 | } | |
| 62 | ||
| 63 | impl From<::std::io::Error> for RuntimeError { | |
| 64 | fn from(err: ::std::io::Error) -> Self { | |
| 65 | RuntimeError::IOError(err) | |
| 66 | } | |
| 67 | } | 
| 1 | #![feature(slice_patterns)] | |
| 2 | ||
| 1 | 3 | extern crate futures; | 
| 2 | 4 | extern crate hyper; | 
| 3 | 5 | extern crate rusqlite; | 
| 4 | 6 | extern crate time; | 
| 5 | 7 | extern crate mustache; | 
| 8 | extern crate rustc_serialize; | |
| 9 | ||
| 10 | #[macro_use] | |
| 11 | extern crate lazy_static; | |
| 6 | 12 | |
| 7 | 13 | pub mod errors; | 
| 8 | 14 | pub mod model; | 
| 15 | pub mod template; | |
| 9 | 16 | #[macro_use] | 
| 10 | 17 | pub mod util; | 
| 11 | 18 | |
| 12 | 19 | use futures::future::Future; | 
| 13 | 20 | use hyper::Method; | 
| 14 | 
                  use hyper::header:: | 
              |
| 21 | use hyper::header::{ContentLength,ContentType}; | |
| 15 | 22 | use hyper::server::{Http, Request, Response, Service}; | 
| 23 | ||
| 24 | use std::io::Read; | |
| 16 | 25 | |
| 17 | 26 | struct CG { | 
| 18 | 27 | db: rusqlite::Connection, | 
| 26 | 35 | |
| 27 | 36 | fn call(&self, req: Request) -> Self::Future { | 
| 28 | 37 | util::log_request(&req); | 
| 29 | let path: Vec<&str> = req.path().split("/").skip(1).collect(); | |
| 30 | ||
| 31 | match (req.method(), path.get(0), path.get(1)) { | |
| 32 | (&Method::Get, Some(&""), None) => { | |
| 33 | match model::get_cocktail_list(&self.db) { | |
| 34 | Ok(ck) => CG::respond(format!("cocktails: {:?}", ck)), | |
| 35 | Err(err) => { | |
| 36 | error!("Database problem: {}", err); | |
| 37 | CG::respond(format!("database problem yo")) | |
| 38 | } | |
| 39 | } | |
| 40 | } | |
| 41 | ||
| 42 | (&Method::Get, Some(&"favicon.ico"), None) => | |
| 43 | CG::respond("icon".to_owned()), | |
| 44 | ||
| 45 | (&Method::Get, Some(&"cocktail"), Some(name)) => | |
| 46 | CG::respond(format!("cocktail {}", name)), | |
| 47 | ||
| 48 | (&Method::Get, Some(&"tagged"), Some(tag)) => | |
| 49 | CG::respond(format!("tag {}", tag)), | |
| 50 | ||
| 51 | (&Method::Get, Some(&"imgs"), Some(filename)) => | |
| 52 | CG::respond(format!("image {}", filename)), | |
| 53 | ||
| 54 | (_, _, _) => { | |
| 55 | error!("unknown path: {}", req.path()); | |
| 56 | CG::respond(format!("path not found: {:?}", req.path())) | |
| 57 | ||
| 38 | match self.route(req) { | |
| 39 | Ok(pg) => | |
| 40 | CG::respond(pg), | |
| 41 | Err(err) => { | |
| 42 | error!("Found error: {:?}", err); | |
| 43 | CG::respond_404(format!("error: {:?}", err)) | |
| 58 | 44 | } | 
| 59 | 45 | } | 
| 60 | 46 | } | 
| 47 | } | |
| 48 | ||
| 49 | enum Page { | |
| 50 | HTML(String), | |
| 51 | PNG(Vec<u8>), | |
| 52 | StaticPNG(&'static [u8]), | |
| 61 | 53 | } | 
| 62 | 54 | |
| 63 | 55 | impl CG { | 
| 71 | 63 | Ok(server.run()?) | 
| 72 | 64 | } | 
| 73 | 65 | |
| 74 | 
                      fn r | 
              |
| 66 | fn route(&self, req: Request) -> Result<Page, errors::RuntimeError> { | |
| 67 | let path: Vec<&str> = req.path().split("/").skip(1).collect(); | |
| 68 | ||
| 69 | Ok(match (req.method(), path.as_slice()) { | |
| 70 | (&Method::Get, &[""]) => { | |
| 71 | let ck = model::get_cocktail_list(&self.db)?; | |
| 72 | Page::HTML(template::render_cocktails(ck)?) | |
| 73 | } | |
| 74 | ||
| 75 | (&Method::Get, &["favicon.ico"]) => | |
| 76 | Page::HTML("icon".to_owned()), | |
| 77 | ||
| 78 | (&Method::Get, &["cocktail", name]) => | |
| 79 | Page::HTML(format!("cocktail {}", name)), | |
| 80 | ||
| 81 | (&Method::Get, &["tagged", tag]) => | |
| 82 | Page::HTML(format!("tag {}", tag)), | |
| 83 | ||
| 84 | (&Method::Get, &["static", "header-logo.png"]) => | |
| 85 | Page::StaticPNG(template::HEADER_IMAGE), | |
| 86 | ||
| 87 | (&Method::Get, &["imgs", filename]) => { | |
| 88 | let mut img_path = std::path::PathBuf::from("imgs"); | |
| 89 | img_path.push(filename); | |
| 90 | let mut f = std::fs::File::open(img_path.as_path())?; | |
| 91 | let mut vec = Vec::new(); | |
| 92 | let _ = f.read_to_end(&mut vec)?; | |
| 93 | Page::PNG(vec) | |
| 94 | } | |
| 95 | ||
| 96 | (_, _) => { | |
| 97 | return Err(errors::RuntimeError::UnknownPath( | |
| 98 | req.path().to_owned())); | |
| 99 | ||
| 100 | } | |
| 101 | }) | |
| 102 | } | |
| 103 | ||
| 104 | fn respond(p: Page) -> <CG as Service>::Future { | |
| 105 | match p { | |
| 106 | Page::HTML(s) => | |
| 107 | Box::new(futures::future::ok( | |
| 108 | Response::new() | |
| 109 | .with_header(ContentLength(s.len() as u64)) | |
| 110 | .with_header(ContentType::html()) | |
| 111 | .with_body(s))), | |
| 112 | Page::PNG(s) => | |
| 113 | Box::new(futures::future::ok( | |
| 114 | Response::new() | |
| 115 | .with_header(ContentLength(s.len() as u64)) | |
| 116 | .with_header(ContentType::png()) | |
| 117 | .with_body(s))), | |
| 118 | Page::StaticPNG(s) => | |
| 119 | Box::new(futures::future::ok( | |
| 120 | Response::new() | |
| 121 | .with_header(ContentLength(s.len() as u64)) | |
| 122 | .with_header(ContentType::png()) | |
| 123 | .with_body(s))), | |
| 124 | } | |
| 125 | } | |
| 126 | ||
| 127 | fn respond_404(s: String) -> <CG as Service>::Future { | |
| 75 | 128 | Box::new(futures::future::ok( | 
| 76 | 129 | Response::new() | 
| 130 | .with_status(hyper::StatusCode::NotFound) | |
| 77 | 131 | .with_header(ContentLength(s.len() as u64)) | 
| 78 | 132 | .with_body(s))) | 
| 79 | 133 | } | 
| 1 | // extern crate rustc_serialize; | |
| 2 | 1 | use rusqlite::Connection; | 
| 3 | 2 | |
| 4 | 
                  #[derive(Debug, Clone | 
              |
| 3 | #[derive(Debug, Clone, RustcEncodable)] | |
| 5 | 4 | pub struct Cocktail { | 
| 6 | 5 | pub name: String, | 
| 7 | 6 | pub image: String, | 
| 8 | 7 | pub created: String, // ::time::Timespec, | 
| 9 | 8 | |
| 10 | 
                      pub tags: Vec< | 
              |
| 9 | pub tags: Vec<Tag>, | |
| 11 | 10 | pub ingredients: Vec<Ingredient>, | 
| 12 | 11 | } | 
| 13 | 12 | |
| 14 | #[derive(Debug, Clone)] | |
| 13 | ||
| 14 | #[derive(Debug, Clone, RustcEncodable)] | |
| 15 | pub struct Tag { | |
| 16 | pub name: String, | |
| 17 | } | |
| 18 | ||
| 19 | #[derive(Debug, Clone, RustcEncodable)] | |
| 15 | 20 | pub struct Ingredient { | 
| 16 | 21 | pub name: String, | 
| 17 | 22 | pub amount: String, | 
| 41 | 46 | let mut c = c.clone(); | 
| 42 | 47 | let mut tag_stmt = conn.prepare( | 
| 43 | 48 | "SELECT name FROM Tag WHERE cocktail_id = ?")?; | 
| 44 | 
                              let tags: Result<Vec< | 
              |
| 49 | let tags: Result<Vec<Tag>, ::rusqlite::Error> = | |
| 45 | 50 | tag_stmt.query_and_then(&[&id], |row| { | 
| 46 | 
                                       | 
              |
| 51 | Ok(Tag { name: row.get_checked(0)? }) | |
| 47 | 52 | })?.collect(); | 
| 48 | 53 | c.tags = tags?; | 
| 49 | 54 | Ok(c) | 
| 1 | use mustache::{Template, compile_str}; | |
| 2 | use model::Cocktail; | |
| 3 | ||
| 4 | pub const HEADER_IMAGE: &'static [u8] = | |
| 5 | include_bytes!("../static/header-logo.png"); | |
| 6 | ||
| 7 | lazy_static! { | |
| 8 | static ref MAIN_TEMPLATE: Template = | |
| 9 | compile_str(include_str!("../template/main.mustache")).unwrap(); | |
| 10 | static ref COCKTAILS_TEMPLATE: Template = | |
| 11 | compile_str(include_str!("../template/cocktails.mustache")).unwrap(); | |
| 12 | } | |
| 13 | ||
| 14 | #[derive(RustcEncodable)] | |
| 15 | struct MainContent { | |
| 16 | content: String, | |
| 17 | og_data: Option<()>, | |
| 18 | } | |
| 19 | ||
| 20 | pub fn render_main(content: String, og_data: Option<()>) -> | |
| 21 | Result<String, ::mustache::Error> | |
| 22 | { | |
| 23 | MAIN_TEMPLATE.render_to_string(&MainContent { | |
| 24 | content, og_data | |
| 25 | }) | |
| 26 | } | |
| 27 | ||
| 28 | #[derive(RustcEncodable)] | |
| 29 | struct CocktailList { | |
| 30 | cocktail: Vec<Cocktail>, | |
| 31 | } | |
| 32 | ||
| 33 | pub fn render_cocktails(cocktails: Vec<Cocktail>) -> Result<String, ::mustache::Error> { | |
| 34 | let content = COCKTAILS_TEMPLATE.render_to_string(&CocktailList { cocktail: cocktails })?; | |
| 35 | render_main(content, None) | |
| 36 | } | 
Binary diff not shown
      
      | 1 | {{#cocktail}} | |
| 2 | <div class="cocktail"> | |
| 3 | <a href="/cocktail/{{slug}}"><img src="/imgs/{{image}}"/></a> | |
| 4 | <div class="tags"> | |
| 5 | {{#tags}}<span class="tag"><a href="/tagged/{{name}}">#{{name}}</a></span> {{/tags}} | |
| 6 | </div> | |
| 7 | </div> | |
| 8 | {{/cocktail}} | 
| 1 | <?xml version="1.0" encoding="utf-8"?> | |
| 2 | <feed xmlns="http://w3.org/2005/Atom"> | |
| 3 | <title>Cocktail Diagrams</title> | |
| 4 | <link href="http://cocktail.graphics/feed/" ref="self" /> | |
| 5 | <link href="http://cocktail.graphics/" /> | |
| 6 | <updated>{{updated}}</updated> | |
| 7 | {{#entry}} | |
| 8 | <entry> | |
| 9 | <title>{{title}}</title> | |
| 10 | <link href="{{link}}" /> | |
| 11 | <updated>{{updated}}</updated> | |
| 12 | <summary>Some text.</summary> | |
| 13 | <content type="xhtml"> | |
| 14 | {{content}} | |
| 15 | </content> | |
| 16 | <author> | |
| 17 | <name>Getty Ritter</name> | |
| 18 | <email>gettyritter@gmail.com</email> | |
| 19 | </author> | |
| 20 | </entry> | |
| 21 | {{/entry}} | |
| 22 | </feed> | 
| 1 | <!DOCTYPE html> | |
| 2 | <html> | |
| 3 | <head> | |
| 4 | <title>visual cocktail recipes</title> | |
| 5 | <meta http-equiv="Content-type" content="text/html; charset=utf-8;"/> | |
| 6 | {{#ogdata}} | |
| 7 | <meta property="og:title" content="{{name}}" /> | |
| 8 | <meta property="og:type" content="website" /> | |
| 9 | <meta property="og:image" content="http://cocktail.graphics/imgs/{{image}}" /> | |
| 10 | <meta property="og:url" content="http://cocktail.graphics/cocktail/{{slug}}" /> | |
| 11 | <meta property="og:description" content="{{name}} recipe graph" /> | |
| 12 | <meta name="twitter:card" content="summary_large_image" /> | |
| 13 | <meta name="twitter:creator" content="@aisamanra" /> | |
| 14 | <meta name="twitter:title" content="{{name}}" /> | |
| 15 | <meta name="twitter:image" content="http://cocktail.graphics/imgs/{{image}}" /> | |
| 16 | <meta name="twitter:description" content="{{name}} recipe graph" /> | |
| 17 | {{/ogdata}} | |
| 18 | <style type="text/css"> | |
| 19 | body { | |
| 20 | font-family: "Fira Sans", "Arial", sans-serif; | |
| 21 | margin-left: 0px; | |
| 22 | margin-right: 0px; | |
| 23 | } | |
| 24 | .header { | |
| 25 | text-align: center; | |
| 26 | color: #fff; | |
| 27 | background-color: #3a73af; | |
| 28 | margin: 0 auto; | |
| 29 | marign-top: 20px; | |
| 30 | height: 80px; | |
| 31 | } | |
| 32 | .footer { | |
| 33 | color: #fff; | |
| 34 | background-color: #3a73af; | |
| 35 | margin-left: 0; | |
| 36 | margin-right: 0; | |
| 37 | margin-top: 40px; | |
| 38 | margin-bottom: 10px; | |
| 39 | padding-top: 5px; | |
| 40 | padding-bottom: 3px; | |
| 41 | text-align: center; | |
| 42 | } | |
| 43 | .cocktail { | |
| 44 | width: 50%; | |
| 45 | margin-left: auto; | |
| 46 | margin-right: auto; | |
| 47 | margin-top: 40px; | |
| 48 | } | |
| 49 | h1 { margin-bottom: 0px; } | |
| 50 | a { color: #3a73af; } | |
| 51 | .tags { margin-left: 40px; } | |
| 52 | .tag { margin-right: 10px; } | |
| 53 | </style> | |
| 54 | </head> | |
| 55 | <body> | |
| 56 | <div class="header"><a href="/"><img src="/static/header-logo.png"></a></div> | |
| 57 | <div class="content">{{{content}}}</div> | |
| 58 | <div class="footer">©2017 Getty Ritter</div> | |
| 59 | </body> | |
| 60 | </html> |