├── README.md ├── part1-introduction ├── intro │ ├── .gitignore │ ├── Cargo.toml │ └── src │ │ └── main.rs └── todo-list │ ├── .gitignore │ ├── Cargo.toml │ └── src │ └── main.rs ├── part2-borrowing ├── demo │ ├── .gitignore │ ├── Cargo.toml │ └── src │ │ └── main.rs └── redux-light │ ├── .gitignore │ ├── Cargo.toml │ └── src │ └── main.rs └── part3-web ├── hello-server ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs └── todo-web ├── Cargo.lock ├── Cargo.toml └── src ├── main.rs ├── store.rs ├── template.rs ├── todo.rs └── todos.tpl /README.md: -------------------------------------------------------------------------------- 1 | # rust-for-nodejs-devs 2 | Example code for the `Rust for Node.js developers` series, an attempt to introduce Rust for people with a high level language background. 3 | 4 | * Part 1 - [Introduction to Rust (blog post)](http://fredrik.anderzon.se/2016/05/10/rust-for-node-developers-part-1-introduction/) [[source code](part1-introduction/)] 5 | * Part 2 - [Can I borrow that? (blog post)](http://fredrik.anderzon.se/2016/06/17/rust-for-node-js-developers-part-2-can-i-borrow-that/) [[source code](part2-borrowing/)] 6 | * Part 3 - [Crates, Modules and the web (blog post)](http://fredrik.anderzon.se/rust-for-node-js-developers-part-3-crates-modules-and-the-web/) [[source code](part3-web/)] 7 | -------------------------------------------------------------------------------- /part1-introduction/intro/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock 7 | Cargo.lock 8 | -------------------------------------------------------------------------------- /part1-introduction/intro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "intro" 3 | version = "0.1.0" 4 | authors = ["Fredrik Andersson "] 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /part1-introduction/intro/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // NUMBERS 3 | 4 | // Rust can use numbers without needing the type specified 5 | let a = 10; 6 | let b = 1; 7 | let c = a + b; 8 | println!("c is {}", c); 9 | 10 | // But if you want to specify a type in Rust this is the syntax 11 | let d: i8 = 100; 12 | let e: f32 = 0.42; 13 | // and so on... 14 | 15 | // This value is too high for an 8 bit integer, so the compiler will warn you 16 | let f: i8 = 200; 17 | 18 | // Let's print the min and max value for each type so you get a feel of when to use them 19 | println!("i8 MIN {}", std::i8::MIN); 20 | println!("i8 MAX {}", std::i8::MAX); 21 | println!("i16 MIN {}", std::i16::MIN); 22 | println!("i16 MAX {}", std::i16::MAX); 23 | println!("i32 MIN {}", std::i32::MIN); 24 | println!("i32 MAX {}", std::i32::MAX); 25 | println!("i64 MIN {}", std::i64::MIN); 26 | println!("i64 MAX {}", std::i64::MAX); 27 | println!("u8 MIN {}", std::u8::MIN); 28 | println!("u8 MAX {}", std::u8::MAX); 29 | println!("u16 MIN {}", std::u16::MIN); 30 | println!("u16 MAX {}", std::u16::MAX); 31 | println!("u32 MIN {}", std::u32::MIN); 32 | println!("u32 MAX {}", std::u32::MAX); 33 | println!("u64 MIN {}", std::u64::MIN); 34 | println!("u64 MAX {}", std::u64::MAX); 35 | println!("f32 MIN {}", std::f32::MIN); 36 | println!("f32 MAX {}", std::f32::MAX); 37 | println!("f64 MIN {}", std::f64::MIN); 38 | println!("f64 MAX {}", std::f64::MAX); 39 | 40 | 41 | 42 | // OBJECTS 43 | 44 | // Hey look, we are telling Rust we need to use HashMap from it's standard library 45 | use std::collections::HashMap; 46 | 47 | // Structs are great for representing data structures 48 | struct Person { 49 | name: String, 50 | age: i16, // Yes, dangling commas are allowed and the convention 51 | } 52 | 53 | 54 | // Using structs is basically like when defining them, but with values 55 | let fredrik = Person { 56 | name: "Fredrik".to_string(), 57 | age: 33, 58 | }; 59 | // Snake case is so much the convention that the compiler will warn you if you try to use camelCase 60 | let unknown_person = Person { 61 | name: "Unknown".to_string(), 62 | age: 0, 63 | }; 64 | 65 | println!("Hi there {} and {}", fredrik.name, unknown_person.name); 66 | 67 | // Let's create a HashMap, these work more or less as es6 Sets 68 | // So when you want to hold arbitrary keys with arbitrary values HashMap is your new best friend 69 | let mut ages = HashMap::new(); 70 | 71 | // Insert name as key and age as value into the HashMap 72 | ages.insert(&fredrik.name, &fredrik.age); 73 | ages.insert(&unknown_person.name, &unknown_person.age); 74 | 75 | // Print ages to see what we have, notice the {:?} instead of {} here? 76 | // Complex types need to specify how they should be printed to work with {} 77 | // {:?} instead makes use of a Debug trait in Rust, that can print 78 | // almost anything, though not always in a very pretty, readable format 79 | println!("ages {:?}", ages); 80 | 81 | // We can also remove stuff 82 | ages.remove(&unknown_person.name); 83 | println!("ages {:?}", ages); 84 | 85 | // And we can also get stuff of course 86 | if let Some(fredrik_from_the_ages) = ages.get(&fredrik.name) { 87 | println!("Fredrik's age is {}", fredrik_from_the_ages); 88 | } 89 | // What is this sorcery? Why the if, and what is `Some`? 90 | 91 | 92 | 93 | // ARRAYS 94 | 95 | // So we first specify the type of values the vector will hold, 96 | // and then we call new to create an empty vector 97 | // Also notice the `mut`, without it we can't push or change anything 98 | let mut fruits: Vec = Vec::new(); 99 | 100 | // Now we can push stuff to it 101 | fruits.push("Banana".to_string()); 102 | fruits.push("Banana".to_string()); 103 | fruits.push("Banana".to_string()); 104 | fruits.push("Orange".to_string()); 105 | fruits.push("Orange".to_string()); 106 | 107 | // values can be accessed by index of course 108 | println!("{} is a fruit", &fruits[0]); 109 | 110 | // for in should feel familiar? will just print all fruits one by one 111 | for fruit in &fruits { 112 | println!("{}", fruit); 113 | } 114 | 115 | // You can also loop over a range of integers like this 116 | // This will let us print out the lines of that bad joke you probably saw coming 117 | // When the fruits vector was populated ;) 118 | for i in 0..fruits.len() { 119 | // Match is the switch of Rust, it's smarter as you'll learn later, 120 | // but this might as well be a switch 121 | match i { 122 | // {} creates a block so we can do more than one thing here 123 | // => will expect one expression 124 | 0 => { 125 | println!("Knock, knock"); 126 | println!("Who's there?"); 127 | }, 128 | 1 => { 129 | println!("{}. Knock, knock", fruits[i]); 130 | println!("Who's there???"); 131 | }, 132 | 2 => { 133 | println!("{}. Knock, knock", fruits[i]); 134 | println!("WHO'S THERE???"); 135 | }, 136 | 3 => { 137 | println!("{}", fruits[i]); 138 | println!("{}, who?", fruits[i]); }, 139 | 4 => { 140 | println!("{} you glad I didn't say {}?", fruits[i], fruits[0]); 141 | println!("facepalm"); 142 | }, 143 | // Rust wants to make sure your match statements always get a match to avoid 144 | // unexpected behaviors, `_` is the "default" or "catch all" rule 145 | _ => println!("You are not even a fruit"), 146 | } 147 | } 148 | 149 | // The `vec!` macro is a shorthand for creating vectors 150 | let nums = vec![1,2,3,4,5]; 151 | 152 | // We need to specify the type here to make the compiler happy 153 | let multiplied: &Vec = &nums 154 | // Get an iterator from nums 155 | .iter() 156 | // Map over it and multiply eac number by 2 157 | .map(|num| num * 2) 158 | // Filter out numbers that got too big for our taste 159 | .filter(|num| *num < 8) 160 | // collect the result into a new vector 161 | .collect(); 162 | println!("Multiplied: {:?}", multiplied); 163 | } 164 | -------------------------------------------------------------------------------- /part1-introduction/todo-list/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock 7 | Cargo.lock 8 | -------------------------------------------------------------------------------- /part1-introduction/todo-list/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "todo-list" 3 | version = "0.1.0" 4 | authors = ["Fredrik Andersson "] 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /part1-introduction/todo-list/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | struct Todo { 4 | id: i16, 5 | title: String, 6 | completed: bool, 7 | deleted: bool, 8 | } 9 | 10 | fn add_todo(todos: &mut Vec, title: &str) { 11 | // The size of the vector + 1 makes a decent enough id 12 | let new_id = todos.len() as i16 + 1; 13 | todos.push(Todo { 14 | id: new_id, 15 | title: title.to_string(), 16 | completed: false, 17 | deleted: false, 18 | }); 19 | } 20 | 21 | fn remove_todo(todos: &mut Vec, todo_id: i16) { 22 | if let Some(todo) = todos.iter_mut().find(|todo| todo.id == todo_id) { 23 | todo.deleted = true; 24 | } 25 | } 26 | 27 | fn mark_done(todos: &mut Vec, todo_id: i16) { 28 | if let Some(todo) = todos.iter_mut().find(|todo| todo.id == todo_id) { 29 | todo.completed = true; 30 | } 31 | } 32 | 33 | fn print_todos(todos: &Vec) { 34 | println!("\n\nTodo List:\n-------------------"); 35 | for todo in todos { 36 | if !todo.deleted { 37 | let done = if todo.completed { "✔" } else { " " }; 38 | println!("[{}] {} {}", done, todo.id, todo.title); 39 | } 40 | } 41 | } 42 | 43 | fn invalid_command(command: &str) { 44 | println!("Invalid command: {}", command); 45 | } 46 | 47 | fn main() { 48 | let mut todos: Vec = Vec::new(); 49 | print_todos(&todos); 50 | 51 | loop { 52 | let mut command = String::new(); 53 | io::stdin() 54 | .read_line(&mut command) 55 | .expect("failed to read line"); 56 | 57 | let command_parts: Vec<&str> = command.split_whitespace().collect(); 58 | 59 | match command_parts.len() { 60 | 0 => invalid_command(&command), 61 | // If the length is 1 it can be a `list` command 62 | 1 => match command_parts[0] { 63 | "list" => print_todos(&todos), 64 | _ => invalid_command(&command), 65 | }, 66 | // If the length is bigger than 1 we look for `add x x x x`, `remove x` or `done x` 67 | _ => { 68 | match command_parts[0] { 69 | "add" => add_todo(&mut todos, &command_parts[1..].join(" ")), 70 | "remove" => if let Ok(num) = command_parts[1].parse::() { 71 | remove_todo(&mut todos, num) 72 | }, 73 | "done" => if let Ok(num) = command_parts[1].parse::() { 74 | mark_done(&mut todos, num) 75 | }, 76 | _ => invalid_command(&command), 77 | } 78 | }, 79 | } 80 | 81 | // At the end of each loop print the list 82 | print_todos(&todos); 83 | 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /part2-borrowing/demo/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock 7 | Cargo.lock 8 | -------------------------------------------------------------------------------- /part2-borrowing/demo/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "demo" 3 | version = "0.1.0" 4 | authors = ["Fredrik Andersson "] 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /part2-borrowing/demo/src/main.rs: -------------------------------------------------------------------------------- 1 | // Let's reuse our Todo struct from last time 2 | struct Todo { 3 | id: i16, 4 | title: String, 5 | completed: bool, 6 | deleted: bool, 7 | } 8 | 9 | // Now the function takes the borrowed version `&Todo` 10 | fn print_todo(todo: &Todo) { 11 | println!("Todo item {}: {}", todo.id, todo.title); 12 | } 13 | 14 | fn main() { 15 | // Create a todo 16 | let item0 = Todo { 17 | id: 0, 18 | title: "Borrow something".to_string(), 19 | completed: false, 20 | deleted: false 21 | }; 22 | // Use our little function to print a borrowed value 23 | print_todo(&item0); 24 | // This still moves the value to the borrow_something variable 25 | let borrow_something = item0; 26 | 27 | // All numeric primitives, signed and unsigned are copyable 28 | let a: i64 = 10; 29 | let b = a; 30 | let c: f32 = 0.1; 31 | let d = c; 32 | println!("a and c are not moved: {} {}", a, c); 33 | 34 | // Bools too 35 | let e = true; 36 | let f = e; 37 | println!("Bools are copyable too: {}", e); 38 | 39 | // Char's and string slices are copyable too, BUT NOT Strings 40 | let g: char = 'g'; 41 | let h = g; 42 | let i = "string slices are copyable"; 43 | let j = i; 44 | println!("chars are copyable: {} aaaand {}", g, i); 45 | 46 | // Arrays and array slices too 47 | let k = [1, 2, 3]; 48 | let l = k; 49 | // Get a slice of the first entry in the k array 50 | let m = &k[0..1]; 51 | let n = m; 52 | println!("Arrays and array clices are copyable: {:?} {:?}", k, m); 53 | 54 | // Tuples too 55 | let o = (1, "str"); 56 | let p = o; 57 | println!("Tuples are copyable {:?}", o); 58 | 59 | // That was the types that are copyable by default 60 | // We can also implement Copy on our own types as long 61 | // as they hold copyable types themselves 62 | struct Point { 63 | x: i32, 64 | y: i32, 65 | } 66 | impl Copy for Point {} 67 | impl Clone for Point { fn clone(&self) -> Point { *self } } 68 | let q = Point { x: 1, y: 10 }; 69 | let r = q; 70 | println!("We made a copyable struct! {}/{}", q.x, q.y); 71 | 72 | // Phew! that was some black magic. Copy and Clone are `traits` that are built into language 73 | // Traits are basically interfaces that you can implement on your types 74 | // You can create them yourselves too, which we'll get to in a future post 75 | // There is an easier way to make a type copyable though: 76 | #[derive(Copy, Clone, Debug)] 77 | struct Point3D { 78 | x: i32, 79 | y: i32, 80 | z: i32, 81 | } 82 | let s = Point3D { x: 1, y: -20, z: 30 }; 83 | let t = s; 84 | println!("Point3D is now copyable, and the Debug trait let's us print it easily {:?}", s); 85 | } 86 | -------------------------------------------------------------------------------- /part2-borrowing/redux-light/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock 7 | Cargo.lock 8 | -------------------------------------------------------------------------------- /part2-borrowing/redux-light/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "redux-light" 3 | version = "0.1.0" 4 | authors = ["Fredrik Andersson "] 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /part2-borrowing/redux-light/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | // Lets us type `Add("Todo item".to_string())` instead of `TodoAction::Add("Todo item".to_string())` 4 | use TodoAction::{ Add, Remove, Toggle }; 5 | // Same with the Action enum and VisibilityFilter, Action::*; would work too, but this way we list what we use 6 | use Action::{ Todos, Visibility }; 7 | use VisibilityFilter:: { ShowActive, ShowAll, ShowCompleted }; 8 | 9 | // Ripping off the canonical Redux todo example we'll add a 10 | // visibility filter to our state except for the todos we already had 11 | #[derive(Clone, Debug)] 12 | struct State { 13 | todos: Vec, 14 | visibility_filter: VisibilityFilter 15 | } 16 | 17 | // By implementing a struct we are creating something very much like 18 | // a class, we can attach methods to it refering to &self` or `&mut self` 19 | impl State { 20 | // This gives us a quick way to initialize a default state with State::default() 21 | pub fn default() -> State { 22 | State { 23 | todos: Vec::new(), 24 | visibility_filter: VisibilityFilter::ShowAll, 25 | } 26 | } 27 | } 28 | 29 | // Same Todo as last time.. 30 | #[derive(Clone, Debug)] 31 | struct Todo { 32 | id: i16, 33 | title: String, 34 | completed: bool, 35 | deleted: bool, 36 | } 37 | 38 | // Create a convenient Todo::new(id, title) method 39 | impl Todo { 40 | pub fn new(id: i16, title: String) -> Todo { 41 | Todo { 42 | id: id, 43 | title: title, 44 | completed: false, 45 | deleted: false, 46 | } 47 | } 48 | } 49 | 50 | 51 | // Rust has enums, so the enum type can replace the "type" property of Redux objects 52 | // The enums will replace `action creators` too since Todos(Add("Todo item".to_string())) 53 | // is pretty clear 54 | #[derive(Clone, Debug)] 55 | enum Action { 56 | Todos(TodoAction), 57 | Visibility(VisibilityFilter), 58 | } 59 | 60 | // mark_done from the previous example becomes Toggle to align with the Redux example 61 | // otherwise functionality is the same 62 | #[derive(Clone, Debug)] 63 | enum TodoAction { 64 | Add(String), 65 | Toggle(i16), 66 | Remove(i16), 67 | } 68 | 69 | // Our 3 visibility states 70 | #[derive(Clone, Debug)] 71 | enum VisibilityFilter { 72 | ShowActive, 73 | ShowAll, 74 | ShowCompleted, 75 | } 76 | 77 | // Helper function for getting a mutable todo from a vector by todo_id 78 | fn get_mut_todo(todos: &mut Vec, todo_id: i16) -> Option<&mut Todo> { 79 | todos.iter_mut().find(|todo|todo.id == todo_id) 80 | } 81 | 82 | // Our main reducer, returns a new State with the results of the child-reducers 83 | // No combineReducers is implemented here, so it calls the child reducers 84 | // by function name 85 | fn reducer(state: &State, action: Action) -> State { 86 | // Always return a new state 87 | State { 88 | todos: todo_reducer(&state.todos, &action), 89 | visibility_filter: visibility_reducer(&state.visibility_filter, &action), 90 | } 91 | } 92 | 93 | // Our todo reducer, takes in state (todo list) and returns a new/cloned version 94 | // after applying the action (is applicable) 95 | fn todo_reducer(state: &Vec, action: &Action) -> Vec { 96 | let mut new_state: Vec = state.clone(); 97 | 98 | // First we make sure it's a `Todos` action, otherwise return clone of incoming state 99 | match *action { 100 | Todos(ref todo_action) => match *todo_action { 101 | // Pretty simple from here on, check the type of Todos enum type 102 | // If Add push a new item, and if `Toggle` or `Remove` use our get_mut_todo 103 | // helper function and then change a property on the todo 104 | Add(ref title) => { 105 | let new_id = new_state.len() as i16 + 1; 106 | new_state.push(Todo::new(new_id, title.to_string())) 107 | }, 108 | Toggle(todo_id) => { 109 | if let Some(todo) = get_mut_todo(&mut new_state, todo_id) { 110 | if todo.completed { todo.completed = false; } else { todo.completed = true; } 111 | } 112 | }, 113 | Remove(todo_id) => { 114 | if let Some(todo) = get_mut_todo(&mut new_state, todo_id) { 115 | todo.deleted = true; 116 | } 117 | }, 118 | }, 119 | // If it's not a Todos action change nothing 120 | _ => (), 121 | } 122 | return new_state; 123 | } 124 | 125 | // Very simple reducer since the action will either be a VisibilityFilter, in which 126 | // case we will return that, otherwise just return the incoming state 127 | fn visibility_reducer(state: &VisibilityFilter, action: &Action) -> VisibilityFilter { 128 | match *action { 129 | Visibility(ref vis_action) => vis_action.clone(), 130 | _ => state.clone(), 131 | } 132 | } 133 | 134 | // Redux store implementation 135 | struct Store { 136 | state: State, 137 | listeners: Vec, 138 | reducer: fn(&State, Action) -> State, 139 | } 140 | 141 | impl Store { 142 | // Takes a reducer function, we skip the initial_state and optional arguments 143 | // TO keep it simple, State::default() from earlier is our initial_state implementation 144 | fn create_store(reducer: fn(&State, Action) -> State) -> Store { 145 | Store { 146 | state: State::default(), 147 | listeners: Vec::new(), 148 | reducer: reducer, 149 | } 150 | } 151 | 152 | // Pushes a listener that will be called for any state change 153 | fn subscribe(&mut self, listener: fn(&State)) { 154 | self.listeners.push(listener); 155 | } 156 | 157 | // Simply returns the state 158 | #[allow(dead_code)] 159 | fn get_state(&self) -> &State { 160 | &self.state 161 | } 162 | 163 | // Called for every new action, calls the reducer to update the state 164 | // and then calls every listener 165 | fn dispatch(&mut self, action: Action) { 166 | self.state = (self.reducer)(&self.state, action); 167 | for listener in &self.listeners { 168 | listener(&self.state) 169 | } 170 | } 171 | } 172 | 173 | // Very simple function to print a todo 174 | fn print_todo(todo: &Todo) { 175 | let done = if todo.completed { "✔" } else { " " }; 176 | println!("[{}] {} {}", done, todo.id, todo.title); 177 | } 178 | 179 | // Our print_todos function from last time, a bit altered to take State 180 | // as input instead of a todos list directly 181 | fn print_todos(state: &State) { 182 | let visibility = &state.visibility_filter; 183 | println!("\n\nTodo List:\n-------------------"); 184 | for todo in &state.todos { 185 | if !todo.deleted { 186 | match *visibility { 187 | ShowAll => print_todo(&todo), 188 | ShowCompleted => if todo.completed { print_todo(&todo) }, 189 | ShowActive => if !todo.completed { print_todo(&todo) }, 190 | } 191 | } 192 | } 193 | println!("-------------------\nVisibility filter: {:?}", visibility); 194 | print_instructions(); 195 | } 196 | 197 | 198 | fn print_instructions() { 199 | println!("\nAvailable commands: \nadd [text] - toggle [id] - remove [id]\nshow [all|active|completed]"); 200 | } 201 | 202 | fn invalid_command(command: &str) { 203 | println!("Invalid command: {}", command); 204 | } 205 | 206 | fn main() { 207 | // Let's create our store and subscribe with print_todos so every update is printed 208 | let mut store = Store::create_store(reducer); 209 | store.subscribe(print_todos); 210 | 211 | print_instructions(); 212 | 213 | // Same input handling as last time, the interesting parts will be in our match statement 214 | loop { 215 | let mut command = String::new(); 216 | io::stdin() 217 | .read_line(&mut command) 218 | .expect("failed to read line"); 219 | let command_parts: Vec<&str> = command.split_whitespace().collect(); 220 | 221 | match command_parts.len() { 222 | 0 => invalid_command(&command), 223 | _ => { 224 | match command_parts[0] { 225 | // Since we prepared so well we just need to call dispatch on our store 226 | // With the right action 227 | "add" => store.dispatch( Todos(Add( command_parts[1..].join(" ").to_string() ))), 228 | "remove" => if let Ok(num) = command_parts[1].parse::() { 229 | store.dispatch( Todos(Remove(num))); 230 | }, 231 | "toggle" => if let Ok(num) = command_parts[1].parse::() { 232 | store.dispatch( Todos(Toggle(num))); 233 | }, 234 | "show" => match command_parts[1] { 235 | "all" => store.dispatch( Visibility(ShowAll) ), 236 | "active" => store.dispatch( Visibility(ShowActive) ), 237 | "completed" => store.dispatch( Visibility(ShowCompleted) ), 238 | _ => invalid_command(&command) 239 | }, 240 | _ => invalid_command(&command), 241 | } 242 | }, 243 | } 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /part3-web/hello-server/Cargo.lock: -------------------------------------------------------------------------------- 1 | [root] 2 | name = "hello-server" 3 | version = "0.1.0" 4 | dependencies = [ 5 | "nickel 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", 6 | ] 7 | 8 | [[package]] 9 | name = "aho-corasick" 10 | version = "0.5.2" 11 | source = "registry+https://github.com/rust-lang/crates.io-index" 12 | dependencies = [ 13 | "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", 14 | ] 15 | 16 | [[package]] 17 | name = "cookie" 18 | version = "0.2.5" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | dependencies = [ 21 | "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", 22 | "url 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 23 | ] 24 | 25 | [[package]] 26 | name = "groupable" 27 | version = "0.2.0" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | 30 | [[package]] 31 | name = "hpack" 32 | version = "0.2.0" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | dependencies = [ 35 | "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 36 | ] 37 | 38 | [[package]] 39 | name = "httparse" 40 | version = "1.1.2" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | 43 | [[package]] 44 | name = "hyper" 45 | version = "0.8.1" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | dependencies = [ 48 | "cookie 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", 49 | "httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 50 | "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 51 | "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 52 | "mime 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 53 | "num_cpus 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", 54 | "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", 55 | "solicit 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", 56 | "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", 57 | "traitobject 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 58 | "typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 59 | "unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 60 | "url 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", 61 | ] 62 | 63 | [[package]] 64 | name = "idna" 65 | version = "0.1.0" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | dependencies = [ 68 | "matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 69 | "unicode-bidi 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 70 | "unicode-normalization 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 71 | ] 72 | 73 | [[package]] 74 | name = "kernel32-sys" 75 | version = "0.2.2" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | dependencies = [ 78 | "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", 79 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 80 | ] 81 | 82 | [[package]] 83 | name = "language-tags" 84 | version = "0.2.2" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | 87 | [[package]] 88 | name = "lazy_static" 89 | version = "0.1.16" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | 92 | [[package]] 93 | name = "libc" 94 | version = "0.2.13" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | 97 | [[package]] 98 | name = "log" 99 | version = "0.3.6" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | 102 | [[package]] 103 | name = "matches" 104 | version = "0.1.2" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | 107 | [[package]] 108 | name = "memchr" 109 | version = "0.1.11" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | dependencies = [ 112 | "libc 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", 113 | ] 114 | 115 | [[package]] 116 | name = "mime" 117 | version = "0.2.1" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | dependencies = [ 120 | "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 121 | ] 122 | 123 | [[package]] 124 | name = "modifier" 125 | version = "0.1.0" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | 128 | [[package]] 129 | name = "mustache" 130 | version = "0.6.3" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | dependencies = [ 133 | "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 134 | "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", 135 | ] 136 | 137 | [[package]] 138 | name = "nickel" 139 | version = "0.8.1" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | dependencies = [ 142 | "groupable 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 143 | "hyper 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", 144 | "lazy_static 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", 145 | "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 146 | "modifier 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 147 | "mustache 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", 148 | "plugin 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", 149 | "regex 0.1.71 (registry+https://github.com/rust-lang/crates.io-index)", 150 | "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", 151 | "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", 152 | "typemap 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 153 | "url 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", 154 | ] 155 | 156 | [[package]] 157 | name = "num_cpus" 158 | version = "0.2.13" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | dependencies = [ 161 | "libc 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", 162 | ] 163 | 164 | [[package]] 165 | name = "plugin" 166 | version = "0.2.6" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | dependencies = [ 169 | "typemap 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 170 | ] 171 | 172 | [[package]] 173 | name = "rand" 174 | version = "0.3.14" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | dependencies = [ 177 | "libc 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", 178 | ] 179 | 180 | [[package]] 181 | name = "regex" 182 | version = "0.1.71" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | dependencies = [ 185 | "aho-corasick 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 186 | "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", 187 | "regex-syntax 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 188 | "thread_local 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", 189 | "utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 190 | ] 191 | 192 | [[package]] 193 | name = "regex-syntax" 194 | version = "0.3.3" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | 197 | [[package]] 198 | name = "rustc-serialize" 199 | version = "0.3.19" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | 202 | [[package]] 203 | name = "rustc_version" 204 | version = "0.1.7" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | dependencies = [ 207 | "semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", 208 | ] 209 | 210 | [[package]] 211 | name = "semver" 212 | version = "0.1.20" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | 215 | [[package]] 216 | name = "solicit" 217 | version = "0.4.4" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | dependencies = [ 220 | "hpack 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 221 | "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 222 | ] 223 | 224 | [[package]] 225 | name = "thread-id" 226 | version = "2.0.0" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | dependencies = [ 229 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 230 | "libc 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", 231 | ] 232 | 233 | [[package]] 234 | name = "thread_local" 235 | version = "0.2.6" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | dependencies = [ 238 | "thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 239 | ] 240 | 241 | [[package]] 242 | name = "time" 243 | version = "0.1.35" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | dependencies = [ 246 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 247 | "libc 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", 248 | "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", 249 | ] 250 | 251 | [[package]] 252 | name = "traitobject" 253 | version = "0.0.1" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | 256 | [[package]] 257 | name = "traitobject" 258 | version = "0.0.3" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | 261 | [[package]] 262 | name = "typeable" 263 | version = "0.1.2" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | 266 | [[package]] 267 | name = "typemap" 268 | version = "0.3.3" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | dependencies = [ 271 | "unsafe-any 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 272 | ] 273 | 274 | [[package]] 275 | name = "unicase" 276 | version = "1.4.0" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | dependencies = [ 279 | "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 280 | ] 281 | 282 | [[package]] 283 | name = "unicode-bidi" 284 | version = "0.2.3" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | dependencies = [ 287 | "matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 288 | ] 289 | 290 | [[package]] 291 | name = "unicode-normalization" 292 | version = "0.1.2" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | 295 | [[package]] 296 | name = "unsafe-any" 297 | version = "0.4.1" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | dependencies = [ 300 | "traitobject 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 301 | ] 302 | 303 | [[package]] 304 | name = "url" 305 | version = "0.5.9" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | dependencies = [ 308 | "matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 309 | "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", 310 | "unicode-bidi 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 311 | "unicode-normalization 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 312 | "uuid 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 313 | ] 314 | 315 | [[package]] 316 | name = "url" 317 | version = "1.1.1" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | dependencies = [ 320 | "idna 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 321 | "matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 322 | ] 323 | 324 | [[package]] 325 | name = "utf8-ranges" 326 | version = "0.1.3" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | 329 | [[package]] 330 | name = "uuid" 331 | version = "0.2.2" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | dependencies = [ 334 | "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", 335 | ] 336 | 337 | [[package]] 338 | name = "winapi" 339 | version = "0.2.7" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | 342 | [[package]] 343 | name = "winapi-build" 344 | version = "0.1.1" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | 347 | -------------------------------------------------------------------------------- /part3-web/hello-server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hello-server" 3 | version = "0.1.0" 4 | authors = ["Fredrik Andersson "] 5 | 6 | [dependencies] 7 | nickel = "*" 8 | -------------------------------------------------------------------------------- /part3-web/hello-server/src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] extern crate nickel; 2 | use nickel::{Nickel, HttpRouter}; 3 | 4 | // use nickel::{Nickel, HttpRouter, Request, Response, MiddlewareResult}; 5 | // fn hello_world<'mw>(_req: &mut Request, res: Response<'mw>) -> MiddlewareResult<'mw> { 6 | // res.send("Hello World!") 7 | // } 8 | 9 | fn main() { 10 | let mut app = Nickel::new(); 11 | app.get("/", middleware!("Hello World!")); 12 | app.listen("0.0.0.0:3000"); 13 | } 14 | -------------------------------------------------------------------------------- /part3-web/todo-web/Cargo.lock: -------------------------------------------------------------------------------- 1 | [root] 2 | name = "todo-web" 3 | version = "0.1.0" 4 | dependencies = [ 5 | "handlebars 0.18.1 (registry+https://github.com/rust-lang/crates.io-index)", 6 | "nickel 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", 7 | "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", 8 | ] 9 | 10 | [[package]] 11 | name = "aho-corasick" 12 | version = "0.5.2" 13 | source = "registry+https://github.com/rust-lang/crates.io-index" 14 | dependencies = [ 15 | "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", 16 | ] 17 | 18 | [[package]] 19 | name = "cookie" 20 | version = "0.2.5" 21 | source = "registry+https://github.com/rust-lang/crates.io-index" 22 | dependencies = [ 23 | "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", 24 | "url 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 25 | ] 26 | 27 | [[package]] 28 | name = "groupable" 29 | version = "0.2.0" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | 32 | [[package]] 33 | name = "handlebars" 34 | version = "0.18.1" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | dependencies = [ 37 | "lazy_static 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", 38 | "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 39 | "pest 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 40 | "quick-error 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 41 | "regex 0.1.71 (registry+https://github.com/rust-lang/crates.io-index)", 42 | "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", 43 | ] 44 | 45 | [[package]] 46 | name = "hpack" 47 | version = "0.2.0" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | dependencies = [ 50 | "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 51 | ] 52 | 53 | [[package]] 54 | name = "httparse" 55 | version = "1.1.2" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | 58 | [[package]] 59 | name = "hyper" 60 | version = "0.8.1" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | dependencies = [ 63 | "cookie 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", 64 | "httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 65 | "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 66 | "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 67 | "mime 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 68 | "num_cpus 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", 69 | "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", 70 | "solicit 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", 71 | "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", 72 | "traitobject 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 73 | "typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 74 | "unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 75 | "url 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", 76 | ] 77 | 78 | [[package]] 79 | name = "idna" 80 | version = "0.1.0" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | dependencies = [ 83 | "matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 84 | "unicode-bidi 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 85 | "unicode-normalization 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 86 | ] 87 | 88 | [[package]] 89 | name = "kernel32-sys" 90 | version = "0.2.2" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | dependencies = [ 93 | "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", 94 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 95 | ] 96 | 97 | [[package]] 98 | name = "language-tags" 99 | version = "0.2.2" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | 102 | [[package]] 103 | name = "lazy_static" 104 | version = "0.1.16" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | 107 | [[package]] 108 | name = "libc" 109 | version = "0.2.13" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | 112 | [[package]] 113 | name = "log" 114 | version = "0.3.6" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | 117 | [[package]] 118 | name = "matches" 119 | version = "0.1.2" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | 122 | [[package]] 123 | name = "memchr" 124 | version = "0.1.11" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | dependencies = [ 127 | "libc 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", 128 | ] 129 | 130 | [[package]] 131 | name = "mime" 132 | version = "0.2.1" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | dependencies = [ 135 | "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 136 | ] 137 | 138 | [[package]] 139 | name = "modifier" 140 | version = "0.1.0" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | 143 | [[package]] 144 | name = "mustache" 145 | version = "0.6.3" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | dependencies = [ 148 | "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 149 | "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", 150 | ] 151 | 152 | [[package]] 153 | name = "nickel" 154 | version = "0.8.1" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | dependencies = [ 157 | "groupable 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 158 | "hyper 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", 159 | "lazy_static 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", 160 | "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 161 | "modifier 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 162 | "mustache 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", 163 | "plugin 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", 164 | "regex 0.1.71 (registry+https://github.com/rust-lang/crates.io-index)", 165 | "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", 166 | "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", 167 | "typemap 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 168 | "url 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", 169 | ] 170 | 171 | [[package]] 172 | name = "num_cpus" 173 | version = "0.2.13" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | dependencies = [ 176 | "libc 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", 177 | ] 178 | 179 | [[package]] 180 | name = "pest" 181 | version = "0.3.2" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | 184 | [[package]] 185 | name = "plugin" 186 | version = "0.2.6" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | dependencies = [ 189 | "typemap 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 190 | ] 191 | 192 | [[package]] 193 | name = "quick-error" 194 | version = "1.1.0" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | 197 | [[package]] 198 | name = "rand" 199 | version = "0.3.14" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | dependencies = [ 202 | "libc 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", 203 | ] 204 | 205 | [[package]] 206 | name = "regex" 207 | version = "0.1.71" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | dependencies = [ 210 | "aho-corasick 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 211 | "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", 212 | "regex-syntax 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 213 | "thread_local 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", 214 | "utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 215 | ] 216 | 217 | [[package]] 218 | name = "regex-syntax" 219 | version = "0.3.3" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | 222 | [[package]] 223 | name = "rustc-serialize" 224 | version = "0.3.19" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | 227 | [[package]] 228 | name = "rustc_version" 229 | version = "0.1.7" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | dependencies = [ 232 | "semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", 233 | ] 234 | 235 | [[package]] 236 | name = "semver" 237 | version = "0.1.20" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | 240 | [[package]] 241 | name = "solicit" 242 | version = "0.4.4" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | dependencies = [ 245 | "hpack 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 246 | "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 247 | ] 248 | 249 | [[package]] 250 | name = "thread-id" 251 | version = "2.0.0" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | dependencies = [ 254 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 255 | "libc 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", 256 | ] 257 | 258 | [[package]] 259 | name = "thread_local" 260 | version = "0.2.6" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | dependencies = [ 263 | "thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 264 | ] 265 | 266 | [[package]] 267 | name = "time" 268 | version = "0.1.35" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | dependencies = [ 271 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 272 | "libc 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", 273 | "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", 274 | ] 275 | 276 | [[package]] 277 | name = "traitobject" 278 | version = "0.0.1" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | 281 | [[package]] 282 | name = "traitobject" 283 | version = "0.0.3" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | 286 | [[package]] 287 | name = "typeable" 288 | version = "0.1.2" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | 291 | [[package]] 292 | name = "typemap" 293 | version = "0.3.3" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | dependencies = [ 296 | "unsafe-any 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 297 | ] 298 | 299 | [[package]] 300 | name = "unicase" 301 | version = "1.4.0" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | dependencies = [ 304 | "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 305 | ] 306 | 307 | [[package]] 308 | name = "unicode-bidi" 309 | version = "0.2.3" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | dependencies = [ 312 | "matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 313 | ] 314 | 315 | [[package]] 316 | name = "unicode-normalization" 317 | version = "0.1.2" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | 320 | [[package]] 321 | name = "unsafe-any" 322 | version = "0.4.1" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | dependencies = [ 325 | "traitobject 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 326 | ] 327 | 328 | [[package]] 329 | name = "url" 330 | version = "0.5.9" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | dependencies = [ 333 | "matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 334 | "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", 335 | "unicode-bidi 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 336 | "unicode-normalization 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 337 | "uuid 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 338 | ] 339 | 340 | [[package]] 341 | name = "url" 342 | version = "1.1.1" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | dependencies = [ 345 | "idna 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 346 | "matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 347 | ] 348 | 349 | [[package]] 350 | name = "utf8-ranges" 351 | version = "0.1.3" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | 354 | [[package]] 355 | name = "uuid" 356 | version = "0.2.2" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | dependencies = [ 359 | "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", 360 | ] 361 | 362 | [[package]] 363 | name = "winapi" 364 | version = "0.2.7" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | 367 | [[package]] 368 | name = "winapi-build" 369 | version = "0.1.1" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | 372 | -------------------------------------------------------------------------------- /part3-web/todo-web/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Fredrik Andersson "] 3 | name = "todo-web" 4 | version = "0.1.0" 5 | 6 | [dependencies] 7 | handlebars = "0.18.1" 8 | nickel = "0.8.1" 9 | rustc-serialize = "0.3.19" 10 | -------------------------------------------------------------------------------- /part3-web/todo-web/src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] extern crate nickel; 2 | extern crate rustc_serialize; 3 | extern crate handlebars; 4 | mod store; 5 | mod template; 6 | mod todo; 7 | use template::render; 8 | use store::{ Store, reducer }; 9 | use todo::TodoAction::{ Add, Remove, Toggle }; 10 | use store::Action::{ Todos, Visibility }; 11 | use store::VisibilityFilter::{ ShowAll, ShowActive, ShowCompleted }; 12 | 13 | use std::sync::{Arc, Mutex}; 14 | 15 | use nickel::{Nickel, HttpRouter, FormBody}; 16 | 17 | fn main() { 18 | let mut server = Nickel::new(); 19 | 20 | // Create our todo list store 21 | let store = Store::create_store(reducer); 22 | 23 | // Put the store in a container that will let us 24 | // safely use it in multi-threaded environment 25 | let store_container = Arc::new( Mutex::new(store) ); 26 | 27 | // Every clone() of our container is counted 28 | // so that when the last clone goes out of scope 29 | // the container can be deallocated 30 | let store = store_container.clone(); 31 | 32 | // At the / path let's just render our current todo list 33 | server.get("/", middleware! { |_req, res| 34 | // We get our store from the container by locking it 35 | // from other threads. 36 | let store = store.lock().unwrap(); 37 | 38 | // Render from nickel_mustache takes the 39 | // nickel Result struct, a path to a mustache 40 | // template, and the data to use 41 | return render(res, "./src/todos.tpl", store.get_state()) 42 | // And here the lock is released.. 43 | }); 44 | 45 | // Let's clone it again for the next closure 46 | let store = store_container.clone(); 47 | 48 | // This time we look for requests like /toggle/1 49 | server.get("/:action/:id", middleware! { |_req, res| 50 | // We will dispatch an action on our store so we 51 | // get a mutable reference 52 | let mut store = store.lock().unwrap(); 53 | 54 | // We try to parse the id param to an int, this works for the 55 | // toggle and remove actions 56 | if let Ok(num) = _req.param("id").unwrap().parse::() { 57 | match _req.param("action").unwrap() { 58 | "toggle" => { 59 | store.dispatch( Todos( Toggle(num) ) ) 60 | }, 61 | 62 | "remove" => store.dispatch( Todos( Remove(num) ) ), 63 | _ => (), 64 | } 65 | } else { 66 | // Otherwise look for a show action 67 | match _req.param("action").unwrap() { 68 | "show" => { 69 | match _req.param("id").unwrap() { 70 | "all" => store.dispatch( Visibility( ShowAll ) ), 71 | "active" => store.dispatch( Visibility( ShowActive ) ), 72 | "completed" => store.dispatch( Visibility( ShowCompleted ) ), 73 | _ => (), 74 | } 75 | }, 76 | _ => (), 77 | } 78 | } 79 | // And render the now updated todo list 80 | return render(res, "./src/todos.tpl", store.get_state()) 81 | }); 82 | 83 | // Let's clone it again for the next closure 84 | let store = store_container.clone(); 85 | 86 | server.post("/*", middleware! { |req, res| 87 | let mut store = store.lock().unwrap(); 88 | let form_body = req.form_body().ok().unwrap(); 89 | if let Some(new_todo) = form_body.get("todo") { 90 | if new_todo.len() > 0 { 91 | store.dispatch( Todos( Add(new_todo.to_string()) ) ); 92 | } 93 | } 94 | 95 | return render(res, "./src/todos.tpl", store.get_state()) 96 | }); 97 | 98 | server.listen("0.0.0.0:3000"); 99 | } 100 | -------------------------------------------------------------------------------- /part3-web/todo-web/src/store.rs: -------------------------------------------------------------------------------- 1 | use store::Action::{ Visibility }; 2 | use rustc_serialize::json::{self, Json, ToJson}; 3 | use todo::{ Todo, TodoAction, todo_reducer }; 4 | 5 | #[derive(Clone, Debug, RustcEncodable, RustcDecodable)] 6 | pub struct State { 7 | pub todos: Vec, 8 | pub visibility_filter: VisibilityFilter 9 | } 10 | impl State { 11 | // This gives us a quick way to initialize a default state with State::default() 12 | pub fn default() -> State { 13 | State { 14 | todos: Vec::new(), 15 | visibility_filter: VisibilityFilter::ShowAll, 16 | } 17 | } 18 | } 19 | 20 | impl ToJson for State { 21 | fn to_json(&self) -> Json { 22 | Json::from_str( &json::encode(&self).unwrap() ).unwrap() 23 | } 24 | } 25 | 26 | #[derive(Clone, Debug)] 27 | pub enum Action { 28 | Todos(TodoAction), 29 | Visibility(VisibilityFilter), 30 | } 31 | 32 | #[derive(Clone, Debug, RustcEncodable, RustcDecodable)] 33 | pub enum VisibilityFilter { 34 | ShowActive, 35 | ShowAll, 36 | ShowCompleted, 37 | } 38 | 39 | // Our main reducer, returns a new State with the results of the child-reducers 40 | // No combineReducers is implemented here, so it calls the child reducers 41 | // by function name 42 | pub fn reducer(state: &State, action: Action) -> State { 43 | // Always return a new state 44 | State { 45 | todos: todo_reducer(&state.todos, &action), 46 | visibility_filter: visibility_reducer(&state.visibility_filter, &action), 47 | } 48 | } 49 | 50 | // Very simple reducer since the action will either be a VisibilityFilter, in which 51 | // case we will return that, otherwise just return the incoming state 52 | fn visibility_reducer(state: &VisibilityFilter, action: &Action) -> VisibilityFilter { 53 | match *action { 54 | Visibility(ref vis_action) => vis_action.clone(), 55 | _ => state.clone(), 56 | } 57 | } 58 | 59 | // Redux store implementation 60 | pub struct Store { 61 | state: State, 62 | listeners: Vec, 63 | reducer: fn(&State, Action) -> State, 64 | } 65 | 66 | impl Store { 67 | // Takes a reducer function, we skip the initial_state and optional arguments 68 | // TO keep it simple, State::default() from earlier is our initial_state implementation 69 | pub fn create_store(reducer: fn(&State, Action) -> State) -> Store { 70 | Store { 71 | state: State::default(), 72 | listeners: Vec::new(), 73 | reducer: reducer, 74 | } 75 | } 76 | 77 | // Pushes a listener that will be called for any state change 78 | #[allow(dead_code)] 79 | pub fn subscribe(&mut self, listener: fn(&State)) { 80 | self.listeners.push(listener); 81 | } 82 | 83 | // Simply returns the state 84 | #[allow(dead_code)] 85 | pub fn get_state(&self) -> &State { 86 | &self.state 87 | } 88 | 89 | // Called for every new action, calls the reducer to update the state 90 | // and then calls every listener 91 | pub fn dispatch(&mut self, action: Action) { 92 | self.state = (self.reducer)(&self.state, action); 93 | for listener in &self.listeners { 94 | listener(&self.state) 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /part3-web/todo-web/src/template.rs: -------------------------------------------------------------------------------- 1 | use rustc_serialize::json::ToJson; 2 | use std::path::Path; 3 | use std::fmt::Debug; 4 | use std::io::Write; 5 | use nickel::{Response, MiddlewareResult}; 6 | use handlebars::{Handlebars, Renderable, RenderError, RenderContext, Helper, Context, JsonRender}; 7 | 8 | fn filter_todo(c: &Context, h: &Helper, ha: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> { 9 | let active_filter = c.navigate(".", "visibility_filter").as_string().unwrap(); 10 | let is_completed = c.navigate( rc.get_path(), "completed").as_boolean().unwrap(); 11 | let show_todo: bool = match active_filter { 12 | "ShowAll" => true, 13 | "ShowCompleted" => is_completed, 14 | "ShowActive" => !is_completed, 15 | _ => false, 16 | }; 17 | 18 | if show_todo { 19 | h.template().unwrap().render(c, ha, rc) 20 | } else { 21 | Ok(()) 22 | } 23 | } 24 | 25 | fn is_selected_filter(c: &Context, h: &Helper, ha: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> { 26 | let param = h.param(0).unwrap().value().as_string().unwrap(); 27 | let active_filter = c.navigate(".", "visibility_filter").as_string().unwrap(); 28 | let is_selected: bool = match active_filter { 29 | "ShowAll" => if param == "ShowAll" { true } else { false }, 30 | "ShowCompleted" => if param == "ShowCompleted" { true } else { false }, 31 | "ShowActive" => if param == "ShowActive" { true } else { false }, 32 | _ => false, 33 | }; 34 | if is_selected { 35 | h.template().unwrap().render(c, ha, rc) 36 | } else { 37 | Ok(()) 38 | } 39 | } 40 | 41 | #[allow(unused_variables)] 42 | fn active_count(c: &Context, h: &Helper, ha: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> { 43 | let todos = c.navigate(".", "todos").as_array().unwrap(); 44 | let count = todos 45 | .into_iter() 46 | .filter(|todo| { 47 | !todo.find("completed").unwrap().as_boolean().unwrap() && 48 | !todo.find("deleted").unwrap().as_boolean().unwrap() 49 | }) 50 | .count(); 51 | 52 | let mut output = count.to_string(); 53 | if count == 1 { 54 | output.push_str(" item left"); 55 | } else { 56 | output.push_str(" items left"); 57 | } 58 | 59 | rc.writer.write(output.as_bytes()).unwrap(); 60 | Ok(()) 61 | } 62 | 63 | pub fn render<'mw, T:ToJson + Debug>(res: Response<'mw>, path: &str, data: &T) -> MiddlewareResult<'mw> { 64 | let mut handlebars = Handlebars::new(); 65 | 66 | handlebars.register_helper("filter_todo", Box::new(filter_todo)); 67 | handlebars.register_helper("active_count", Box::new(active_count)); 68 | handlebars.register_helper("is_selected_filter", Box::new(is_selected_filter)); 69 | handlebars.register_template_file("template", &Path::new(path)).ok().unwrap(); 70 | let result = handlebars.render("template", data).ok().unwrap(); 71 | 72 | res.send(result) 73 | } 74 | -------------------------------------------------------------------------------- /part3-web/todo-web/src/todo.rs: -------------------------------------------------------------------------------- 1 | use store::{ Action }; 2 | use store::Action::{ Todos }; 3 | use todo::TodoAction::{ Add, Toggle, Remove }; 4 | #[derive(Clone, Debug, RustcEncodable, RustcDecodable)] 5 | pub struct Todo { 6 | pub id: i16, 7 | pub title: String, 8 | pub completed: bool, 9 | pub deleted: bool, 10 | } 11 | impl Todo { 12 | pub fn new(id: i16, title: String) -> Todo { 13 | Todo { 14 | id: id, 15 | title: title, 16 | completed: false, 17 | deleted: false, 18 | } 19 | } 20 | } 21 | 22 | // mark_done from the previous example becomes Toggle to align with the Redux example 23 | // otherwise functionality is the same 24 | #[derive(Clone, Debug)] 25 | pub enum TodoAction { 26 | Add(String), 27 | Toggle(i16), 28 | Remove(i16), 29 | } 30 | 31 | // Helper function for getting a mutable todo from a vector by todo_id 32 | pub fn get_mut_todo(todos: &mut Vec, todo_id: i16) -> Option<&mut Todo> { 33 | todos.iter_mut().find(|todo|todo.id == todo_id) 34 | } 35 | 36 | // Our todo reducer, takes in state (todo list) and returns a new/cloned version 37 | // after applying the action (is applicable) 38 | pub fn todo_reducer(state: &Vec, action: &Action) -> Vec { 39 | let mut new_state: Vec = state.clone(); 40 | 41 | // First we make sure it's a `Todos` action, otherwise return clone of incoming state 42 | match *action { 43 | Todos(ref todo_action) => match *todo_action { 44 | // Pretty simple from here on, check the type of Todos enum type 45 | // If Add push a new item, and if `Toggle` or `Remove` use our get_mut_todo 46 | // helper function and then change a property on the todo 47 | Add(ref title) => { 48 | let new_id = new_state.len() as i16 + 1; 49 | new_state.push(Todo::new(new_id, title.to_string())) 50 | }, 51 | Toggle(todo_id) => { 52 | if let Some(todo) = get_mut_todo(&mut new_state, todo_id) { 53 | if todo.completed { todo.completed = false; } else { todo.completed = true; } 54 | } 55 | }, 56 | Remove(todo_id) => { 57 | if let Some(todo) = get_mut_todo(&mut new_state, todo_id) { 58 | todo.deleted = true; 59 | } 60 | }, 61 | }, 62 | // If it's not a Todos action change nothing 63 | _ => (), 64 | } 65 | return new_state; 66 | } 67 | -------------------------------------------------------------------------------- /part3-web/todo-web/src/todos.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Nickel Todo 6 | 7 | 8 | 14 | 21 | 22 | 23 |
24 |
25 |

todos

26 |
27 | 28 |
29 |
30 |
31 |
    32 | {{#each todos}} 33 | {{#unless deleted}} 34 | {{#filter_todo}} 35 | 36 |
    37 | 38 | 39 | 40 |
    41 | 42 | {{/filter_todo}} 43 | {{/unless}} 44 | {{/each}} 45 |
46 |
47 | 48 | 67 |
68 | 69 | 70 | 71 | --------------------------------------------------------------------------------