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> |