Add rust tutorials

This commit is contained in:
Timothy Warren 2016-08-03 20:27:06 -04:00
parent 1ac21a44f1
commit aae3029cd7
19 changed files with 1090 additions and 0 deletions

1
rust/hello-server/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
target

View File

@ -0,0 +1,9 @@
[package]
authors = ["Tim"]
name = "hello-server"
version = "0.1.0"
[dependencies]
handlebars = "0.18.1"
nickel = "0.8.1"
rustc-serialize = "0.3.19"

View File

@ -0,0 +1,108 @@
#[macro_use] extern crate nickel;
extern crate rustc_serialize;
extern crate handlebars;
use nickel::{Nickel, HttpRouter, FormBody};
use std::io;
use std::sync::{Arc, Mutex};
mod todo;
mod store;
mod template;
// Lets us type `Add("Todo item".to_string())` instead of `TodoAction::Add("Todo item".to_string())
use todo::TodoAction::{ Add, Remove, Toggle };
// Same with the Action enum and VisibilityFilter, Action::* would work too, but this way we list what we use
use store::Action::{ Todos, Visibility };
use store::VisibilityFilter::{ ShowActive, ShowAll, ShowCompleted };
use todo::{Todo};
use store::{ Store, State, reducer };
use template::render;
fn main() {
let mut server = Nickel::new();
// Create our todo list store
let mut store = Store::create_store(reducer);
// Add some todos so we've got something to remember
store.dispatch( Todos( Add("one thing".to_string()) ) );
store.dispatch( Todos( Add("another thing".to_string()) ) );
// Put the store in a container that will let us
// safely use it in multi-threaded environment
let store_container = Arc::new( Mutex::new(store) );
// Every clone of our container is counted
// so that when the last clone goes out of scope
// the container can be deallocated
let store = store_container.clone();
// At the / path let's just render our current todo list
server.get("/", middleware! { |_req, res|
// We get our store from the container by locking it
// from other threads
let store = store.lock().unwrap();
// Render from nickel_mustache takes the
// nickel Result struct, a path to a mustache
// template, and the data to use
return render(res, "./src/todos.tpl", store.get_state())
// And here the lock is released...
});
// Let's clone it again for the next closure
let store = store_container.clone();
// This time we look for requests like /toggle/1
server.get("/:action/:id", middleware! { |_req, res|
// We will dispatch an action on our store so we
// get a mutable reference
let mut store = store.lock().unwrap();
// We try to parse the id param to an int, this words for the
// toggle and remove actions
if let Ok(num) = _req.param("id").unwrap().parse::<i16>() {
match _req.param("action").unwrap() {
"toggle" => {
store.dispatch( Todos( Toggle(num) ) )
},
"remove" => store.dispatch( Todos ( Remove(num) ) ),
_ => (),
}
} else {
// Otherwise look for a show action
match _req.param("action").unwrap() {
"show" => {
match _req.param("id").unwrap() {
"all" => store.dispatch( Visibility(ShowAll) ),
"active" => store.dispatch( Visibility(ShowActive) ),
"completed" => store.dispatch( Visibility(ShowCompleted) ),
_ => (),
}
},
_ => (),
}
}
// And render the now updated todo list
return render(res, "./src/todos.tpl", store.get_state())
});
// Let's clone it again for the next closure
let store = store_container.clone();
server.post("/*", middleware! { |req, res|
let mut store = store.lock().unwrap();
let form_body = req.form_body().ok().unwrap();
if let Some(new_todo) = form_body.get("todo") {
if new_todo.len() > 0 {
store.dispatch( Todos( Add(new_todo.to_string()) ) );
}
}
return render(res, "./src/todos.tpl", store.get_state())
});
server.listen("0.0.0.0:3000");
}

View File

@ -0,0 +1,107 @@
use rustc_serialize::json::{self, Json, ToJson};
use store::Action::{ Visibility };
use todo::{ Todo, TodoAction, todo_reducer };
// Ripping off the canonical Redux todo example we'll add a
// visibility filter to our state in addation to the todos we already had
// This state struct will be the single source of state for our todo list program
#[derive(Clone, Debug, RustcEncodable, RustcDecodable)]
pub struct State {
pub todos: Vec<Todo>,
pub visibility_filter: VisibilityFilter
}
// By implementing a struct we are creating something very much like
// a class, we can attach methods to it refering to `&self` or `&mut self`
impl State {
// Can be called with State::default()
pub fn default() -> State {
State {
todos: Vec::new(),
visibility_filter: VisibilityFilter::ShowAll,
}
}
}
impl ToJson for State {
fn to_json(&self) -> Json {
Json::from_str(&json::encode(&self).unwrap()).unwrap()
}
}
// Rust has enums, so the enum type can replace the "type" property of Redux objects
// The enums will replace `action_creators` too since Todos(Add("Todo item".to_string()))
// is pretty clear
#[derive(Clone, Debug)]
pub enum Action {
Todos(TodoAction),
Visibility(VisibilityFilter),
}
// Our 3 visibility states
#[derive(Clone, Debug, RustcEncodable, RustcDecodable)]
pub enum VisibilityFilter {
ShowActive,
ShowAll,
ShowCompleted,
}
// Our main reducer, returns a new State with the results of the child-reducers
// No combineReducers is implemented here, so it calls the child reducers
// by function name
pub fn reducer(state: &State, action: Action) -> State {
// Always return a new state
State {
todos: todo_reducer(&state.todos, &action),
visibility_filter: visibility_reducer(&state.visibility_filter, &action),
}
}
// Very simple reducer since the action will either be a VisibilityFilter, in which
// case we will return that, otherwise just return the incoming state
fn visibility_reducer(state: &VisibilityFilter, action: &Action) -> VisibilityFilter {
match *action {
Visibility(ref vis_action) => vis_action.clone(),
_ => state.clone(),
}
}
// Redux store implementation
pub struct Store {
state: State,
listeners: Vec<fn(&State)>,
reducer: fn(&State, Action) -> State,
}
impl Store {
// Takes a reducer function as the only argument
// To keep it simple, State::default() privides the initial state in this example
pub fn create_store(reducer: fn(&State, Action) -> State) -> Store {
Store {
state: State::default(),
listeners: Vec::new(),
reducer: reducer,
}
}
// Pushes a listener that will be called for any state change
pub fn subscribe(&mut self, listener: fn(&State)) {
self.listeners.push(listener);
}
// Simply returns a borrowed reference to the state
#[allow(dead_code)]
pub fn get_state(&self) -> &State {
&self.state
}
// Called for every new action, calls the reducer to update the state
// and then calls every listener with the new State
pub fn dispatch(&mut self, action: Action) {
self.state = (self.reducer)(&self.state, action);
for listener in &self.listeners {
listener(&self.state);
}
}
}

View File

@ -0,0 +1,74 @@
use rustc_serialize::json::ToJson;
use nickel::{Response, MiddlewareResult};
use std::path::Path;
use handlebars::{Handlebars, Renderable, RenderError, RenderContext, Helper, Context};
fn filter_todo(c: &Context, h: &Helper, ha: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
let active_filter = c.navigate(".", "visibility_filter").as_string().unwrap();
let is_completed = c.navigate(rc.get_path(), "completed").as_boolean().unwrap();
let show_todo: bool = match active_filter {
"ShowAll" => true,
"ShowCompleted" => is_completed,
"ShowActive" => !is_completed,
_ => false
};
if show_todo {
h.template().unwrap().render(c, ha, rc)
} else {
Ok(())
}
}
fn is_selected_filter(c: &Context, h: &Helper, ha: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
let param = h.param(0).unwrap().value().as_string().unwrap();
let active_filter = c.navigate(".", "visibility_filter").as_string().unwrap();
let is_selected: bool = match active_filter {
"ShowAll" => if param == "ShowAll" { true } else { false },
"ShowCompleted" => if param == "ShowCompleted" { true } else { false },
"ShowActive" => if param == "ShowActive" { true } else { false },
_ => false
};
if is_selected {
h.template().unwrap().render(c, ha, rc)
} else {
Ok(())
}
}
#[allow(unused_variables)]
fn active_count(c: &Context, h: &Helper, ha: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
let todos = c.navigate(".", "todos").as_array().unwrap();
let count = todos
.into_iter()
.filter(|todo| {
!todo.find("completed").unwrap().as_boolean().unwrap() &&
!todo.find("deleted").unwrap().as_boolean().unwrap()
})
.count();
let mut output = count.to_string();
if count == 1 {
output.push_str(" item left");
} else {
output.push_str(" items left");
}
rc.writer.write(output.as_bytes()).unwrap();
Ok(())
}
pub fn render<'mw, T:ToJson>(res: Response<'mw>, path: &str, data: &T) -> MiddlewareResult<'mw> {
let mut handlebars = Handlebars::new();
handlebars.register_helper("filter_todo", Box::new(filter_todo));
handlebars.register_helper("active_count", Box::new(active_count));
handlebars.register_helper("is_selected_filter", Box::new(is_selected_filter));
handlebars.register_template_file("template", &Path::new(path)).ok().unwrap();
let result = handlebars.render("template", data).ok().unwrap();
res.send(result)
}

View File

@ -0,0 +1,70 @@
use store::{ Action };
use store::Action::{ Todos };
use todo::TodoAction::{ Add, Toggle, Remove };
// Same Todo as last time...
#[derive(Clone, Debug, RustcEncodable, RustcDecodable)]
pub struct Todo {
pub id: i16,
pub title: String,
pub completed: bool,
pub deleted: bool,
}
impl Todo {
pub fn new(id: i16, title: String) -> Todo {
Todo {
id: id,
title: title,
completed: false,
deleted: false,
}
}
}
// mark_done from the previous example becomes Toggle to align with the Redux example
// otherwise functionality is the same
#[derive(Clone, Debug)]
pub enum TodoAction {
Add(String),
Toggle(i16),
Remove(i16),
}
// Helper function for getting a mutable todo from a vector by todo_id
pub fn get_mut_todo(todos: &mut Vec<Todo>, todo_id: i16) -> Option<&mut Todo> {
todos.iter_mut().find(|todo| todo.id == todo_id)
}
// Our todo reducer, takes in state (todo list) and returns a new/cloned version
// after applying the action (is applicable)
pub fn todo_reducer(state: &Vec<Todo>, action: &Action) -> Vec<Todo> {
let mut new_state: Vec<Todo> = state.clone();
// First we make sure it's a `Todos` action, otherwise return clone of incoming state
match *action {
Todos(ref todo_action) => match *todo_action {
// Pretty simple from here on, check the type of Todos enum type
// If Add push a new item, and if `Toggle` or `Remove` user our get_mut_todo
// helper function and then change a property on the todo
Add(ref title) => {
let new_id = new_state.len() as i16 + 1;
new_state.push(Todo::new(new_id, title.to_string()))
},
Toggle(todo_id) => {
if let Some(todo) = get_mut_todo(&mut new_state, todo_id) {
if todo.completed { todo.completed = false; }
else { todo.completed = true }
}
},
Remove(todo_id) => {
if let Some(todo) = get_mut_todo(&mut new_state, todo_id) {
todo.deleted = true;
}
},
},
// If it's not a Todos action change nothing
_ => (),
}
return new_state;
}

View File

@ -0,0 +1,65 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Nickel Todo</title>
<link rel="stylesheet" href="http://todomvc.com/examples/react/node_modules/todomvc-common/base.css" />
<link rel="stylesheet" href="http://todomvc.com/examples/react/node_modules/todomvc-app-css/index.css" />
<style>
label a {
color: #4d4d4d;
test-decoration: none;
}
</style>
<script>
document.addEventListener('click', function clickHandler(e) {
if (e.target && e.target.dataset && e.target.dataset.id) {
window.location.href = '/' + e.target.dataset.action + '/' + e.target.dataset.id;
}
});
</script>
</head>
<body>
<section class="todoapp">
<header class="header">
<h1>Todos</h1>
<form class="add-todo" action="/" method="post">
<input class="new-todo" placeholder="What needs to be done?" name="todo" />
</form>
</header>
<main class="main">
<ul class="todo-list">
{{#each todos}}
{{#unless deleted}}
{{#filter_todo}}
<li{{#if completed}} class="completed"{{/if}} data-id={{id}}>
<div class="view">
<input class="toggle" type="checkbox"{{#if completed}}checked="checked"{{/if}} data-id={{id}} data-action="toggle" />
<label><a href="/toggle/{{id}}">{{title}}</a></label>
<button class="destroy" data-id="{{id}}" data-action="remove"></button>
</div>
</li>
{{/filter_todo}}
{{/unless}}
{{/each}}
</ul>
</main>
<footer class="footer">
<span class="todo-count">
<strong>{{#active_count}}{{/active_count}}</strong>
</span>
<ul class="filters">
<li><a href="/show/all"{{#is_selected_filter "ShowAll"}} class="selected"{{/is_selected_filter}}>All</a></li>
<span> </span>
<li><a href="/show/active"{{#is_selected_filter "ShowActive"}} class="selected"{{/is_selected_filter}}>Active</a></li>
<span> </span>
<li><a href="/show/completed"{{#is_selected_filter "ShowCompleted"}} class="selected"{{/is_selected_filter}}>Completed</a></li>
</ul>
</footer>
</section>
</body>
</html>

1
rust/intro/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
target

6
rust/intro/Cargo.toml Normal file
View File

@ -0,0 +1,6 @@
[package]
name = "intro"
version = "0.1.0"
authors = ["Tim"]
[dependencies]

153
rust/intro/src/main.rs Normal file
View File

@ -0,0 +1,153 @@
// Hey look, we are telling Rust we need to use HashMap from it's standard library
use std::collections::HashMap;
fn number_types() {
// Let's print the min and max value for each type so you get a feel of when to use them
println!("i8 MIN {}", std::i8::MIN);
println!("i8 MAX {}", std::i8::MAX);
println!("i16 MIN {}", std::i16::MIN);
println!("i16 MAX {}", std::i16::MAX);
println!("i32 MIN {}", std::i32::MIN);
println!("i32 MAX {}", std::i32::MAX);
println!("i64 MIN {}", std::i64::MIN);
println!("i64 MAX {}", std::i64::MAX);
println!("u8 MIN {}", std::u8::MIN);
println!("u8 MAX {}", std::u8::MAX);
println!("u16 MIN {}", std::u16::MIN);
println!("u16 MAX {}", std::u16::MAX);
println!("u32 MIN {}", std::u32::MIN);
println!("u32 MAX {}", std::u32::MAX);
println!("u64 MIN {}", std::u64::MIN);
println!("u64 MAX {}", std::u64::MAX);
println!("f32 MIN {}", std::f32::MIN);
println!("f32 MAX {}", std::f32::MAX);
println!("f64 MIN {}", std::f64::MIN);
println!("f64 MAX {}", std::f64::MAX);
}
fn structs_and_hashmaps() {
// Structs are great for representing data structures
struct Person {
name: String,
age: i16,
}
// Using structs is basically like when defining them, but with values
let fredrik = Person {
name: "Fredrik".to_string(),
age: 33,
};
// Snake case is so much the convention that the compiler will ward you if you try to use camelCase
let unknown_person = Person {
name: "Unknown".to_string(),
age: 0,
};
println!("Hi there {} and {}", fredrik.name, unknown_person.name);
// Let's create a HashMap, these work more or less as es6 Sets
// So when you want to hold arbitrary keys with arbitrary values HashMap is your new best friend
let mut ages = HashMap::new();
// Insert name as key and age as value into the HashMap
ages.insert(&fredrik.name, &fredrik.age);
ages.insert(&unknown_person.name, &unknown_person.age);
// Print ages to see what we have, notice the {:?} instead of {} here?
// Complex types need to specify how they should be printed to work with {}
// {:?} instead makes use of a Debug trait in Rust, that can print
// almost anything, though not always in a very pretty, readable format
println!("ages {:?}", ages);
// We can also remove stuff
ages.remove(&unknown_person.name);
println!("ages {:?}", ages);
// And we can also get stuff of course
if let Some(fredrik_from_the_ages) = ages.get(&fredrik.name) {
println!("Fredrik's age is {}", fredrik_from_the_ages);
}
}
fn vectors() {
// So we first specify the type of values the vector will hold,
// and then we call new to create an empty vector
// Also notice the `mut`, without it we can't push or change anything;
let mut fruits: Vec<String> = Vec::new();
// Now we can push stuff to it
fruits.push("Banana".to_string());
fruits.push("Banana".to_string());
fruits.push("Banana".to_string());
fruits.push("Orange".to_string());
fruits.push("Orange".to_string());
// values can be access by index of course
println!("{} is a fruit", &fruits[0]);
// for in should feel familiar
for fruit in &fruits {
println!("{}", fruit);
}
// You can also loop over a range of integers like this
// This will let us print out the lines of that bad joke you probably saw coming
// When the fruits vector was populated ;)
for i in 0..fruits.len() {
// Match is the switch of Rust, it's smarter as you'll learn later,
// but this might as well be a switch
match i {
// {} creates a block so we can do more than one thing here
// => will expect one expression
0 => {
println!("Knock, knock");
println!("Who's there?");
},
1 => {
println!("{}. Knock, knock", fruits[i]);
println!("Who's there?");
},
2 => {
println!("{}. Knock, knock", fruits[i]);
println!("WHO'S THERE???");
},
3 => {
println!("{}", fruits[i]);
println!("{}, who?", fruits[i]);
},
4 => {
println!("{} you glad I didn't say {}?", fruits[i], fruits[0]);
println!("facepalm");
},
// Rust wants to make sure your match statement always gets a match to avoid
// unexpected behaviors, `_` is the "default" or "catch all" rule
_ => println!("You are not even a fruit"),
}
}
}
fn functional_vector_methods() {
// The `vec!` macro is a shorthand for creating vectors
let nums = vec![1,2,3,4,5];
// We need to specify the type here to make the compiler happy
let multiplied: &Vec<i32> = &nums
// Get an iterator from numbs
.iter()
// Map over it and multiply each number by 2
.map(|num| num * 2)
// Filter out numbers that got too big for our taste
.filter(|num| *num < 8)
// collect the results into a new vector
.collect();
println!("Multiplied: {:?}", multiplied);
}
fn main() {
number_types();
structs_and_hashmaps();
vectors();
functional_vector_methods();
}

1
rust/todo-list-borrow/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
target

View File

@ -0,0 +1,6 @@
[package]
name = "todo-list-borrow"
version = "0.1.0"
authors = ["Tim"]
[dependencies]

View File

@ -0,0 +1,126 @@
fn copyable() {
// All numeric primitives, signed and unsigned are copyable
let a: i64 = 10;
let b = a;
let c: f32 = 0.1;
let d = c;
println!("a and c are not moved: {} {}", a, c);
// Bools too
let e = true;
let f = e;
println!("Bools are copyable too: {}", e);
// Char's and string slices are copyable too, BUT NOT Strings
let g: char = 'g';
let h = g;
let i = "string slices are copyable";
let j = i;
println!("chars are copyable: {} aaaand {}", g, i);
// Arrays and array slices too
let k = [1, 2, 3];
let l = k;
// Get a slice of the first entry in the k array
let m = &k[0..1];
let n = m;
println!("Arrays and array slices are copyable: {:?} {:?}", k, m);
// Tuples too
let o = (1, "str");
let p = o;
println!("Tuples are copyable {:?}", o);
// That was the types that are copyable by default
// We can also implement Copy on our own types as long
// as they hold copyable types themselves
struct Point {
x: i32,
y: i32,
}
impl Copy for Point {}
impl Clone for Point { fn clone(&self) -> Point { *self } }
let q = Point { x: 1, y: 10 };
let r = q;
println!("We made a copyable struct! {}/{}", q.x, q.y);
// Phew! that was some black magic. Copy and Clone are `traits` that are built into the language
// Traits are basically interfaces that you can implement on your types
// You can create them yourselves too, which we'll get to in a future post
// There is an easier way to make a type copyable though:
#[derive(Copy, Clone, Debug)]
struct Point3D {
x: i32,
y: i32,
z: i32,
}
let s = Point3D { x: 1, y: -20, z: 30 };
let t = s;
println!("Point3D is now copyable, and the Debug trait let's us print it easily {:?}", s);
}
fn borrowing() {
#[derive(Clone, Debug)]
struct Point {
x: i32,
y: i32,
}
let a = Point {
x: 0,
y: 0,
};
// As discussed this would cause a `moved value` error
// let b = a;
// let c = a;
// Borrowing is fine an unlimited amount of times though
let b = &a;
let c = &a;
// But only for as long as the reference lives
// Rust is block scoped, basically any time you see {} it's a block
{
let d = Point { x: 0, y: 0 };
// This is totally fine
let e = &d;
// But here, at the end of this block, d goes out of scope
}
// So this would fail
// let f = &d;
// You can also borrow a value mutably, but then the borrower has
// exclusive rights to mutate the value until it goes out of scope
let mut f = Point { x: 0, y: 0 };
let g = &mut f;
// This would fail
// let h = &mut f;
// Even the owner can't mutate itself without causing an error
// f.x = 1;
// This means that mutable borrows are mostly practical when passing something to a function
fn pointifier(point: &mut Point, multiply_by: i32) {
point.x *= multiply_by;
point.y *= multiply_by;
}
let mut h = Point { x: 1, y: 2 };
pointifier(&mut h, 20);
// once the pointifier function has finished, the borrow ends, and h is mutable again
h.x -= 1;
println!("Point {:?}", h);
}
fn main() {
copyable();
borrowing();
}

1
rust/todo-list-redux/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
target

View File

@ -0,0 +1,6 @@
[package]
name = "todo-list-redux"
version = "0.1.0"
authors = ["Tim"]
[dependencies]

View File

@ -0,0 +1,250 @@
use std::io;
// Lets us type `Add("Todo item".to_string())` instead of `TodoAction::Add("Todo item".to_string())
use TodoAction::{ Add, Remove, Toggle };
// Same with the Action enum and VisibilityFilter, Action::* would work too, but this way we list what we use
use Action::{ Todos, Visibility };
use VisibilityFilter::{ ShowActive, ShowAll, ShowCompleted };
// Ripping off the canonical Redux todo example we'll add a
// visibility filter to our state in addation to the todos we already had
// This state struct will be the single source of state for our todo list program
#[derive(Clone, Debug)]
struct State {
todos: Vec<Todo>,
visibility_filter: VisibilityFilter
}
// By implementing a struct we are creating something very much like
// a class, we can attach methods to it refering to `&self` or `&mut self`
impl State {
// Can be called with State::default()
fn default() -> State {
State {
todos: Vec::new(),
visibility_filter: VisibilityFilter::ShowAll,
}
}
}
// Same Todo as last time...
#[derive(Clone, Debug)]
struct Todo {
id: i16,
title: String,
completed: bool,
deleted: bool,
}
// Create a convenient Todo::new(id, title) method
impl Todo {
pub fn new(id: i16, title: String) -> Todo {
Todo {
id: id,
title: title,
completed: false,
deleted: false,
}
}
}
// Redux store implementation
struct Store {
state: State,
listeners: Vec<fn(&State)>,
reducer: fn(&State, Action) -> State,
}
impl Store {
// Takes a reducer function as the only argument
// To keep it simple, State::default() privides the initial state in this example
fn create_store(reducer: fn(&State, Action) -> State) -> Store {
Store {
state: State::default(),
listeners: Vec::new(),
reducer: reducer,
}
}
// Pushes a listener that will be called for any state change
fn subscribe(&mut self, listener: fn(&State)) {
self.listeners.push(listener);
}
// Simply returns a borrowed reference to the state
#[allow(dead_code)]
fn get_state(&self) -> &State {
&self.state
}
// Called for every new action, calls the reducer to update the state
// and then calls every listener with the new State
fn dispatch(&mut self, action: Action) {
self.state = (self.reducer)(&self.state, action);
for listener in &self.listeners {
listener(&self.state);
}
}
}
// Rust has enums, so the enum type can replace the "type" property of Redux objects
// The enums will replace `action_creators` too since Todos(Add("Todo item".to_string()))
// is pretty clear
#[derive(Clone, Debug)]
enum Action {
Todos(TodoAction),
Visibility(VisibilityFilter),
}
// mark_done from the previous example becomes Toggle to align with the Redux example
// otherwise functionality is the same
#[derive(Clone, Debug)]
enum TodoAction {
Add(String),
Toggle(i16),
Remove(i16),
}
// Our 3 visibility states
#[derive(Clone, Debug)]
enum VisibilityFilter {
ShowActive,
ShowAll,
ShowCompleted,
}
// Helper function for getting a mutable todo from a vector by todo_id
fn get_mut_todo(todos: &mut Vec<Todo>, todo_id: i16) -> Option<&mut Todo> {
todos.iter_mut().find(|todo| todo.id == todo_id)
}
// Our main reducer, returns a new State with the results of the child-reducers
// No combineReducers is implemented here, so it calls the child reducers
// by function name
fn reducer(state: &State, action: Action) -> State {
// Always return a new state
State {
todos: todo_reducer(&state.todos, &action),
visibility_filter: visibility_reducer(&state.visibility_filter, &action),
}
}
// Our todo reducer, takes in state (todo list) and returns a new/cloned version
// after applying the action (is applicable)
fn todo_reducer(state: &Vec<Todo>, action: &Action) -> Vec<Todo> {
let mut new_state: Vec<Todo> = state.clone();
// First we make sure it's a `Todos` action, otherwise return clone of incoming state
match *action {
Todos(ref todo_action) => match *todo_action {
// Pretty simple from here on, check the type of Todos enum type
// If Add push a new item, and if `Toggle` or `Remove` user our get_mut_todo
// helper function and then change a property on the todo
Add(ref title) => {
let new_id = new_state.len() as i16 + 1;
new_state.push(Todo::new(new_id, title.to_string()))
},
Toggle(todo_id) => {
if let Some(todo) = get_mut_todo(&mut new_state, todo_id) {
if todo.completed { todo.completed = false; }
else { todo.completed = true }
}
},
Remove(todo_id) => {
if let Some(todo) = get_mut_todo(&mut new_state, todo_id) {
todo.deleted = true;
}
},
},
// If it's not a Todos action change nothing
_ => (),
}
return new_state;
}
// Very simple reducer since the action will either be a VisibilityFilter, in which
// case we will return that, otherwise just return the incoming state
fn visibility_reducer(state: &VisibilityFilter, action: &Action) -> VisibilityFilter {
match *action {
Visibility(ref vis_action) => vis_action.clone(),
_ => state.clone(),
}
}
// Very simple function to print a todo
fn print_todo(todo: &Todo) {
let done = if todo.completed { "" } else { " " };
println!("[{}] {} {}", done, todo.id, todo.title);
}
fn print_todos(state: &State) {
let visibility = &state.visibility_filter;
println!("\n\nTodo List:\n--------------------");
for todo in &state.todos {
if !todo.deleted {
match *visibility {
ShowAll => print_todo(&todo),
ShowCompleted => if todo.completed { print_todo(&todo) },
ShowActive => if !todo.completed { print_todo(&todo) },
}
}
}
println!("-------------------\nVisibility filter: {:?}", visibility);
print_instructions();
}
fn print_instructions() {
println!("\nAvailable commands: \nadd [text] - toggle [id] - remove [id]\n show [all|active|completed]");
}
fn invalid_command(command: &str) {
println!("Invalid command: '{}'", command);
}
fn main() {
// Let's create our store and subscribe with print_todos so every update is printed
let mut store = Store::create_store(reducer);
store.subscribe(print_todos);
print_instructions();
// Same input handling as the last time, the interesting parts will be in our match statement
loop {
// Assign input lines to the `command` variable
let mut command = String::new();
io::stdin()
.read_line(&mut command)
.expect("failed to read line");
// Split input on whitespace
let command_parts: Vec<&str> = command.split_whitespace().collect();
// Now match the size of the vector holding the separate words in the command
match command_parts.len() {
// If 0 we can't really do much
0 => invalid_command(&command),
_ => {
match command_parts[0] {
// Since we prepared so well we just need to call dispatch on our store
// with the right action
"add" => store.dispatch(Todos(Add(command_parts[1..].join(" ").to_string() ))),
"remove" => if let Ok(num) = command_parts[1].parse::<i16>() {
store.dispatch(Todos(Remove(num)));
},
"toggle" => if let Ok(num) = command_parts[1].parse::<i16>() {
store.dispatch(Todos(Toggle(num)));
},
"show" => match command_parts[1] {
"all" => store.dispatch(Visibility(ShowAll)),
"active" => store.dispatch(Visibility(ShowActive)),
"completed" => store.dispatch(Visibility(ShowCompleted)),
_ => invalid_command(&command),
},
_ => invalid_command(&command),
}
}
}
}
}

1
rust/todo-list/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
target

View File

@ -0,0 +1,6 @@
[package]
name = "todo-list"
version = "0.1.0"
authors = ["Tim"]
[dependencies]

View File

@ -0,0 +1,99 @@
use std::io;
struct Todo {
id: i16,
title: String,
completed: bool,
deleted: bool,
}
fn add_todo(todos: &mut Vec<Todo>, title: &str) {
// The size of the vector + 1 makes a decent enough id
let new_id = todos.len() as i16 + 1;
todos.push(Todo {
id: new_id,
title: title.to_string(),
completed: false,
deleted: false,
});
}
fn remove_todo(todos: &mut Vec<Todo>, todo_id: i16) {
if let Some(todo) = todos.iter_mut().find(|todo| todo.id == todo_id) {
todo.deleted = true;
}
}
fn mark_done(todos: &mut Vec<Todo>, todo_id: i16) {
if let Some(todo) = todos.iter_mut().find(|todo| todo.id == todo_id) {
todo.completed = true;
}
}
fn print_todos(todos: &Vec<Todo>) {
println!("\n\nTodo List:\n--------------------");
for todo in todos {
if !todo.deleted {
let done = if todo.completed { "" } else { " " };
println!("[{}] {} {}", done, todo.id, todo.title);
}
}
}
fn invalid_command(command: &str) {
println!("Invalid command: '{}'", command);
}
fn main() {
let mut todos: Vec<Todo> = Vec::new();
// Print the todo list on start up
print_todos(&todos);
// Infinite loop
loop {
// Assign input lines to the `command` variable
let mut command = String::new();
io::stdin()
.read_line(&mut command)
.expect("failed to read line");
// Split input on whitespace
let command_parts: Vec<&str> = command.split_whitespace().collect();
// Now match the size of the vector holding the separate words in the command
match command_parts.len() {
// If 0 we can't really do much
0 => invalid_command(&command),
// If the length is 1 it's a `list` command, or an invalid command
1 => match command_parts[0] {
"list" => print_todos(&todos),
"quit" => break,
"exit" => break,
_ => invalid_command(&command),
},
// If the length is bigger than 1 we look for an `add x x x`, `remove x x x`, or `done x`
_ => {
// Match the first word in the command
match command_parts[0] {
// If add, let's join up all words except the first one as our todo title
// `[1..]` means from index 1 in the vector to the end
"add" => add_todo(&mut todos, &command_parts[1..].join(" ")),
// For remove and done we want to send in a todo_id,
// so we parse the string as an integer
// parse returns a `Result` that is either `Ok` or `Err`,
// so we can handle it much as an `Option` return
"remove" => if let Ok(num) = command_parts[1].parse::<i16>() {
remove_todo(&mut todos, num)
},
"done" => if let Ok(num) = command_parts[1].parse::<i16>() {
mark_done(&mut todos, num)
},
_ => invalid_command(&command),
}
},
}
// At the end of each loop print the list
print_todos(&todos);
}
}