<commit_before>use std::io::prelude::*;
use std::fs::OpenOptions;
use std::io::SeekFrom;
use std::path::Path;
use std::error::Error;
use std::env;

extern crate ncurses;
use ncurses::*;

//extern crate getopts;
//use getopts::Options;

fn main() {
    let mut buffer = vec![];
    let mut cursorpos:usize = 0;
    const SPALTEN:usize = 16;
//    let mut command = vec![]; // If command is typed by user, store it
//    command.push(":asdf");
    let mut command = String::new();

    initscr(); //start ncursesw
    let screenheight : usize = getmaxy(stdscr) as usize;
    cbreak();  //ctrl+z and fg works with this
    noecho();
    start_color();
    init_pair(1, COLOR_GREEN, COLOR_BLACK);

    printw("Welcome to Hexdino.\nPlease find the any-key on your keyboard and press it.\n");

    let args: Vec<_> = env::args().collect();
    /*
    let program = args[0].clone();
    let mut opts = Options::new();
    opts.optopt("f", "", "set file name", "NAME");
    opts.optflag("h", "help", "print this help menu");

    let matches = match opts.parse(&args[1..]) {
        Ok(m) => { m }
        Err(f) => { panic!(f.to_string()) }
    };
    if matches.opt_present("h") {
        let brief = format!("Usage: {} FILE [options]", program);
            print!("{}", opts.usage(&brief));
        return;
    }
    let path = if !matches.free.is_empty() {
        matches.free[0].clone()
    } else {
        let brief = format!("Usage: {} FILE [options]", program);
            print!("{}", opts.usage(&brief));
        return;
    };*/

    let path = if args.len() > 1 {
        printw(&format!("Opening file {} (TODO)", args[1]));
        args[1].clone()
    } else {
        "foo.txt".into()
    };

    let path = Path::new(&path);
    let display = path.display();

    getch();
    clear();

    if !has_colors() {
        endwin();
        println!("Your terminal does not support color!\n");
        return;
    }

//TODO in this order: 1)disp files 2) hjkl movement 3) edit file by 'r' 4) save file 5) edit file by 'x, i' 6) search by '/'

    let mut file = match OpenOptions::new().read(true).write(true).create(true).open(&path) {
//        Err(why) => printw("oh... file not opened..."),
        Err(why) => panic!("couldn't open {}: {}", display,
            Error::description(&why)),
        Ok(file) => file,
    };

//    file.read_to_end(&mut buffer).ok().expect("File could not be read.");
    file.read_to_end(&mut buffer).ok().expect("File could not be read.");

    let mut mode = 0; // 0 Command mode, 1 replace next char, 2 type a command, TODO 3 Insert

    draw(&buffer, cursorpos, SPALTEN, screenheight, mode, &command);

    let mut key;
//    key = getch();
//    printw(&format!("{:?}", key));

    let mut ragequitnow = 0;
    while ragequitnow == 0 {
        key = getch();
        if mode == 0 { // movement mode
            match key {
                104 => if cursorpos != 0 {cursorpos-=1}, //left is "-1"
                106 => if cursorpos+SPALTEN < buffer.len() {cursorpos+=SPALTEN} //down is "+16"
                        else {cursorpos=buffer.len()-1}, //down if on last line is "to end"
                107 => if cursorpos >= SPALTEN {cursorpos-=SPALTEN}, //up is "-16"
                108 => if cursorpos != buffer.len()-1 {cursorpos+=1}, //right is "+1"
                 48 => cursorpos -= cursorpos%16, //start of line is "to start"
                 36 => if cursorpos-(cursorpos%16)+(SPALTEN-1) < buffer.len() {cursorpos = cursorpos-(cursorpos%16)+(SPALTEN-1)} //dollar is "to end"
                        else {cursorpos = buffer.len()-1},
                114 => mode = 1, //r replaces the next char
                 27 => ragequitnow = 1, // TODO replace by KEY_...?
                 58 => mode = 2, // ":" TODO use screenheight;
//                 63 => printw("{:?}", asdf), //TODO: print available key helpfile
                _ => (),
            }
        } else
        if mode == 1 { // r was pressed so replace next char
            match key {
                c @ 32...126 => { buffer[cursorpos] = c as u8; mode = 0 },
                27 => mode = 0,
                _ => (),
            }
        } else
        if mode == 2 {
            match key {
                c @ 32...126 => { command.push(c as u8 as char); }, //TODO check this
                27 => {command.clear();mode = 0;},
                10 => { // Enter pressed, compute command!
                        if command == "w".to_string() {
                            file.seek(SeekFrom::Start(0)).ok().expect("Filepointer could not be set to 0");
                            file.write_all(&mut buffer).ok().expect("File could not be written.");
                            mode = 0;
                        }
                    },
                _ => (),
            }
        }
 //       printw(&format!("{:?}", key));
        draw(&buffer, cursorpos, SPALTEN, screenheight, mode, &command);
    }

    refresh();
    endwin();
}

fn draw(buffer:&Vec<u8>, cursorpos:usize, spalten:usize, maxzeilen:usize, mode:usize, command:&String) {
//    let zeilen = buffer.len() / spalten;
    erase();

    let mut zeilen = buffer.len() / spalten;
    if zeilen >= maxzeilen-1 { // Last line reserved for Status/Commands/etc (Like in vim)
        zeilen = maxzeilen-2;
    }

    for z in 0 .. zeilen+1 {
        for s in 0 .. spalten {
            if z*spalten+s == cursorpos { attron(COLOR_PAIR(1)); }
            if z*spalten+s < buffer.len() { printw(&format!("{:02X} ", buffer[z*spalten+s]) ); }
                else {printw("-- "); }
            if z*spalten+s == cursorpos { attroff(COLOR_PAIR(1)); }
        }
        printw(" "); // Space between hex and ascii
        for s in 0 .. spalten {
            if z*spalten+s == cursorpos { attron(COLOR_PAIR(1)); }
                if z*spalten+s < buffer.len() {
                    if let c @ 32...126 = buffer[z*spalten+s] {printw(&format!("{}", c as char) );}
                    else {printw(&format!(".") );}
                }
            if z*spalten+s == cursorpos { attroff(COLOR_PAIR(1)); }
        }
        printw("\n");
    }
//TODO: use maxzeilen to draw the command (if in mode == 2) on last line of terminal
//show that the mode is 2 by adding a ':'
    if mode == 2 {
        printw(":");
    }
    printw(&format!("{}", command));
}
<commit_msg>Quitting the tool in vim-style<commit_after>use std::io::prelude::*;
use std::fs::OpenOptions;
use std::io::SeekFrom;
use std::path::Path;
use std::error::Error;
use std::env;

extern crate ncurses;
use ncurses::*;

//extern crate getopts;
//use getopts::Options;

fn main() {
    let mut buffer = vec![];
    let mut cursorpos:usize = 0;
    const SPALTEN:usize = 16;
//    let mut command = vec![]; // If command is typed by user, store it
//    command.push(":asdf");
    let mut command = String::new();

    initscr(); //start ncursesw
    let screenheight : usize = getmaxy(stdscr) as usize;
    cbreak();  //ctrl+z and fg works with this
    noecho();
    start_color();
    init_pair(1, COLOR_GREEN, COLOR_BLACK);

    printw("Welcome to Hexdino.\nPlease find the any-key on your keyboard and press it.\n");

    let args: Vec<_> = env::args().collect();
    /*
    let program = args[0].clone();
    let mut opts = Options::new();
    opts.optopt("f", "", "set file name", "NAME");
    opts.optflag("h", "help", "print this help menu");

    let matches = match opts.parse(&args[1..]) {
        Ok(m) => { m }
        Err(f) => { panic!(f.to_string()) }
    };
    if matches.opt_present("h") {
        let brief = format!("Usage: {} FILE [options]", program);
            print!("{}", opts.usage(&brief));
        return;
    }
    let path = if !matches.free.is_empty() {
        matches.free[0].clone()
    } else {
        let brief = format!("Usage: {} FILE [options]", program);
            print!("{}", opts.usage(&brief));
        return;
    };*/

    let path = if args.len() > 1 {
        printw(&format!("Opening file {} (TODO)", args[1]));
        args[1].clone()
    } else {
        "foo.txt".into()
    };

    let path = Path::new(&path);
    let display = path.display();

    getch();
    clear();

    if !has_colors() {
        endwin();
        println!("Your terminal does not support color!\n");
        return;
    }

//TODO in this order: 1)disp files 2) hjkl movement 3) edit file by 'r' 4) save file 5) edit file by 'x, i' 6) search by '/'

    let mut file = match OpenOptions::new().read(true).write(true).create(true).open(&path) {
//        Err(why) => printw("oh... file not opened..."),
        Err(why) => panic!("couldn't open {}: {}", display,
            Error::description(&why)),
        Ok(file) => file,
    };

//    file.read_to_end(&mut buffer).ok().expect("File could not be read.");
    file.read_to_end(&mut buffer).ok().expect("File could not be read.");

    let mut mode = 0; // 0 Command mode, 1 replace next char, 2 type a command, TODO 3 Insert

    draw(&buffer, cursorpos, SPALTEN, screenheight, mode, &command);

    let mut key;
//    key = getch();
//    printw(&format!("{:?}", key));

    let mut ragequitnow = 0;
    while ragequitnow == 0 {
        key = getch();
        if mode == 0 { // movement mode
            match key {
                104 => if cursorpos != 0 {cursorpos-=1}, //left is "-1"
                106 => if cursorpos+SPALTEN < buffer.len() {cursorpos+=SPALTEN} //down is "+16"
                        else {cursorpos=buffer.len()-1}, //down if on last line is "to end"
                107 => if cursorpos >= SPALTEN {cursorpos-=SPALTEN}, //up is "-16"
                108 => if cursorpos != buffer.len()-1 {cursorpos+=1}, //right is "+1"
                 48 => cursorpos -= cursorpos%16, //start of line is "to start"
                 36 => if cursorpos-(cursorpos%16)+(SPALTEN-1) < buffer.len() {cursorpos = cursorpos-(cursorpos%16)+(SPALTEN-1)} //dollar is "to end"
                        else {cursorpos = buffer.len()-1},
                114 => mode = 1, //r replaces the next char
                 58 => mode = 2, // ":" TODO use screenheight;
//                 63 => printw("{:?}", asdf), //TODO: print available key helpfile
                _ => (),
            }
        } else
        if mode == 1 { // r was pressed so replace next char
            match key {
                c @ 32...126 => { buffer[cursorpos] = c as u8; mode = 0 },
                27 => mode = 0,
                _ => (),
            }
        } else
        if mode == 2 {
            match key {
                c @ 32...126 => { command.push(c as u8 as char); }, //TODO check this
                27 => {command.clear();mode = 0;},
                10 => { // Enter pressed, compute command!
                        if (command == "w".to_string()) || (command == "wq".to_string()) {
                            file.seek(SeekFrom::Start(0)).ok().expect("Filepointer could not be set to 0");
                            file.write_all(&mut buffer).ok().expect("File could not be written.");
                            mode = 0;
                        }
                        if (command == "wq".to_string()) || (command == "q".to_string()) {
                            ragequitnow = 1;
                        }

                    },
                _ => (),
            }
        }
 //       printw(&format!("{:?}", key));
        draw(&buffer, cursorpos, SPALTEN, screenheight, mode, &command);
    }

    refresh();
    endwin();
}

fn draw(buffer:&Vec<u8>, cursorpos:usize, spalten:usize, maxzeilen:usize, mode:usize, command:&String) {
//    let zeilen = buffer.len() / spalten;
    erase();

    let mut zeilen = buffer.len() / spalten;
    if zeilen >= maxzeilen-1 { // Last line reserved for Status/Commands/etc (Like in vim)
        zeilen = maxzeilen-2;
    }

    for z in 0 .. zeilen+1 {
        for s in 0 .. spalten {
            if z*spalten+s == cursorpos { attron(COLOR_PAIR(1)); }
            if z*spalten+s < buffer.len() { printw(&format!("{:02X} ", buffer[z*spalten+s]) ); }
                else {printw("-- "); }
            if z*spalten+s == cursorpos { attroff(COLOR_PAIR(1)); }
        }
        printw(" "); // Space between hex and ascii
        for s in 0 .. spalten {
            if z*spalten+s == cursorpos { attron(COLOR_PAIR(1)); }
                if z*spalten+s < buffer.len() {
                    if let c @ 32...126 = buffer[z*spalten+s] {printw(&format!("{}", c as char) );}
                    else {printw(&format!(".") );}
                }
            if z*spalten+s == cursorpos { attroff(COLOR_PAIR(1)); }
        }
        printw("\n");
    }
//TODO: use maxzeilen to draw the command (if in mode == 2) on last line of terminal
//show that the mode is 2 by adding a ':'
    if mode == 2 {
        printw(":");
    }
    printw(&format!("{}", command));
}
<|endoftext|>


<commit_before>mod simulation;

extern crate getopts;
extern crate rand;

use std::env;
use std::str;
use getopts::Options;
use rand::Rng;
use simulation::CellState;
use simulation::Simulator;

fn print_usage(program: &str, opts: Options) {
	let brief = format!("Usage: {} OUTPUT_DIR [options]", program);
	print!("{}", opts.usage(&brief));
}

fn main() {
	// Define args
	let args: Vec<String> = env::args().collect();
	let program = args[0].clone();

	let mut opts = Options::new();
	opts.optflag("h", "help", "Display the help text");
	opts.optopt("w", "width", "Provide a width for the grid", "WIDTH");
	opts.optopt("t", "height", "Provide a height for the grid", "HEIGHT");
	opts.optopt("n", "interations", "Provide the number of iterations to run",
		"ITERATIONS");

	// Parse args
	let matches = match opts.parse(&args[1..]) {
		Ok(m) => { m }
		Err(f) => { panic!(f.to_string()) }
	};
	// Check for help request
	if matches.opt_present("h") {
		print_usage(&program, opts);
		return;
	}
	// Parse number of iterations to run
	let iterations_str = match matches.opt_str("n") {
		Some(s) => { s }
		None => {
			println!("Required parameter 'iterations' missing");
			print_usage(&program, opts);
			return;
		}
	};
	let iterations = match iterations_str.parse::<u32>() {
		Ok(i) => i,
		Err(err) => {
			println!("Invalid value for iterations (flag 'i'):\n{}\n", err);
			return;
		}
	};
	// Parse output dir
	let output = if !matches.free.is_empty() {
		matches.free[0].clone()
	} else {
		println!("Required argument OUTPUT_DIR missing\n");
		print_usage(&program, opts);
		return;
	};
	// TODO: Determine if using input file or random grid
	// Handle random starting grid
	// Parse height and width of grid
	let height_str = match matches.opt_str("t") {
		Some(expr) => expr,
		None => String::from("None"),
	};
	let height: usize = match height_str.parse::<usize>() {
		Ok(h) => h,
		Err(err) => {
			println!("Invalid value for height (flag 't'):\n{}\n", err);
			return;
		}
	};
	let width_str = match matches.opt_str("w") {
		Some(expr) => expr,
		None => String::from("None"),
	};
	let width: usize = match width_str.parse::<usize>() {
		Ok(w) => w,
		Err(err) => {
			println!("Invalid value for width (flag 'w'):\n{}\n", err);
			return;
		}
	};
	// Generate grid
	let mut grid = Vec::new();
	// Generation random grid
	for _ in 0..height {
		let mut row = Vec::new();
		for _ in 0..width {
			let rn = rand::thread_rng().gen_range(0, 2);
			let cell = match rn {
				0 => CellState::Dead,
				1 => CellState::Alive,
				_ => panic!("Somehow got a incorrect random number!?")
			};
			row.push(cell);
		}
		grid.push(row);
	}
	// Run simulation
	let mut sim = Simulator::new(iterations, output, grid);
	sim.run_simulation();
}
<commit_msg>Refactored random grid state generation into separate functions<commit_after>mod simulation;

extern crate getopts;
extern crate rand;

use std::env;
use std::str;
use getopts::Options;
use rand::Rng;
use simulation::CellState;
use simulation::Simulator;

fn print_usage(program: &str, opts: Options) {
	let brief = format!("Usage: {} OUTPUT_DIR [options]", program);
	print!("{}", opts.usage(&brief));
}

fn generate_grid(height: usize, width: usize) -> Vec<Vec<CellState>> {
	let mut grid = Vec::new();
	for _ in 0..height {
		let mut row = Vec::new();
		for _ in 0..width {
			let rn = rand::thread_rng().gen_range(0, 2);
			let cell = match rn {
				0 => CellState::Dead,
				1 => CellState::Alive,
				_ => panic!("Somehow got a incorrect random number!?")
			};
			row.push(cell);
		}
		grid.push(row);
	}
	return grid;
}

fn main() {
	// Define args
	let args: Vec<String> = env::args().collect();
	let program = args[0].clone();

	let mut opts = Options::new();
	opts.optflag("h", "help", "Display the help text");
	opts.optopt("w", "width", "Provide a width for the grid", "WIDTH");
	opts.optopt("t", "height", "Provide a height for the grid", "HEIGHT");
	opts.optopt("n", "interations", "Provide the number of iterations to run",
		"ITERATIONS");

	// Parse args
	let matches = match opts.parse(&args[1..]) {
		Ok(m) => { m }
		Err(f) => { panic!(f.to_string()) }
	};
	// Check for help request
	if matches.opt_present("h") {
		print_usage(&program, opts);
		return;
	}
	// Parse number of iterations to run
	let iterations_str = match matches.opt_str("n") {
		Some(s) => { s }
		None => {
			println!("Required parameter 'iterations' missing");
			print_usage(&program, opts);
			return;
		}
	};
	let iterations = match iterations_str.parse::<u32>() {
		Ok(i) => i,
		Err(err) => {
			println!("Invalid value for iterations (flag 'i'):\n{}\n", err);
			return;
		}
	};
	// Parse output dir
	let output = if !matches.free.is_empty() {
		matches.free[0].clone()
	} else {
		println!("Required argument OUTPUT_DIR missing\n");
		print_usage(&program, opts);
		return;
	};
	// TODO: Determine if using input file or random grid
	// Handle random starting grid
	// Parse height and width of grid
	let height_str = match matches.opt_str("t") {
		Some(expr) => expr,
		None => String::from("None"),
	};
	let height: usize = match height_str.parse::<usize>() {
		Ok(h) => h,
		Err(err) => {
			println!("Invalid value for height (flag 't'):\n{}\n", err);
			return;
		}
	};
	let width_str = match matches.opt_str("w") {
		Some(expr) => expr,
		None => String::from("None"),
	};
	let width: usize = match width_str.parse::<usize>() {
		Ok(w) => w,
		Err(err) => {
			println!("Invalid value for width (flag 'w'):\n{}\n", err);
			return;
		}
	};
	// Generation random grid
	let grid = generate_grid(height, width);
	// Run simulation
	let mut sim = Simulator::new(iterations, output, grid);
	sim.run_simulation();
}
<|endoftext|>


<commit_before>// Import the Ordering Enum from the Rust Standard Library
// http://doc.rust-lang.org/std/cmp/enum.Ordering.html
// Note: Enums are a powerful tool for data representation and 'generics' may be used
// to make Enums generic across Types to become even more powerful.
// Ordering Enum form used for Comparisons
// enum Ordering {
//   Less,
//   Equal,
//   Greater,
// }
// :: Symbol indicates a Namespace
// 'std' Module contains Sub-Module 'cmp' containing Sub-Sub-Module 'Ordering'
// The Enum Variants (Equal, Less, Greater) have been imported instead of just the Ordering
// Namespace itself to achieve compactness and convenience. This avoids full scoping and
// avoids long notation (i.e. use just Equal instead of Ordering::Equal), however name conflicts
// may arise (so it is best practice to import and apply the Namespace combined with a
// Variants together)
use std::cmp::Ordering;

// Configure the Ordering Enum
// 'cmp' is a Function that compares two given parameters and returns an Ordering
// where depending on the difference between these parameters, the returned Ordering
// may be Ordering:Less, Ordering::Greater, or Ordering::Equal
// Variants of the Enum (Ordering) are Namespaced under the Enum itself.
fn cmp(a: i32, b: i32) -> Ordering {
  if a < b      { Ordering::Less }
  else if a > b { Ordering::Greater }
  else          { Ordering::Equal }
}

/*
  Rust is an Expression-Based Systems language.
  Declare main function
*/
fn main() {

    // Metaprogramming calling a macro and passing staticaly allocated string arg
    println!("Hello, world!");

    /*
      Variable binding expression.
      <let expression> is Full Pattern for Pattern Matching.
      Rust's Type Inference automatically determines an expression's Type.
      Alternatively be explicit and supply an expression's Type after a colon.
      Or simply annotate the Type in a comment to understand the Type Rust infers.
      Rust Bindings are immutable by Default.
      Mutable Bindings require 'mut' prefix for Type Safety.
    */
    let a: i32 = 5; // assign immutable binding 'a' with Type i32 and Value 5

    // If statement based on Branch concept
    if a == 5 {
      println!("x is five");
    } else {
      println!("x is not five");
    }

    let (x, y, z) = (1, 2, 3);  // assign immutable bindings x, y, z to 1, 2, 3

    let mut b: i32 = 10; // assign mutable binding 'b'
    b = 20; // re-assign to new Value

    /*
      Rust's 'If' is an Expression. It returns a value to initialise
      the 'Let' binding ("Declaration Statement") with. 'Let' is assigned an Expression.
      Assignment to an already bound variable is an Expression.
      Assignment Value in Rust is the special Unit Type () in Rust's Type System.
      In other languages 'If' is a Statements (does not return value).

      Rust's "Expression Statement" converts an Expression into a Statement.
      Semi-colons separate each Expression.
      Semi-colons convert Expression into Statement, throws Value away, returns Unit Type ()
      (causing Type Error when expect a different Type such as i32 to be valid)
    */
    let c: i32 = if b == 20 { 25 } else { 15 };  // Explicitely specify 'c' as of Integer Type
    println!("b is: {}", b); // Interpret interpolation with moustaches
    println!("c is: {}", c);

    incr(b, c); // Call Fn passing Args
    print_sum(b, c); // Call Fn passing Args

    // Compound Data Structure Type Tuple is simple fixed size ordered list where &str is String Slice
    let tuple1: (i32, &str) = (1, "hello"); // Mutable. Type annotated. Without: let x = (1, "hello");
    let mut tuple2: (i32, i32) = (1, 2); // Type annotated. Without: let x = (1, "hello");
    let tuple3: (i32, i32) = (3, 4);

    // Note that for a Tuple with a Single value the trailing commas are mandatory
    let (x,): (i32,) = (1,);

    if tuple2 != tuple3 {
        tuple2 = tuple3; // Assign Tuple to another Mutable Tuple (with same Arity and Types)
        println!("tuple2 and tuple3 not equal so assigning");
    } else {
        println!("tuple2 and tuple3 do not have same Arity, Types, Order, and Value, not assigning");
    }

    /*
      Destructuring Let (access parameters of Tuple).
      Let statement performs Multiple Bindings assignments when LHS and RHS Pattern Match
    */
    let (t1, t2) = tuple2;

    println!("t1 and t2 is {}", t1 + t2); // Note: Expects t1 and t2 to be same Type

    // Tuples to return Multiple Values from a Function
    let (tnt1, tnt2) = next_two(5);
    println!("tnt1, tnt2 = {}, {}", tnt1, tnt2);

    try_structs();

    try_tuple_structs();

    try_newtype();

    try_enums();

    try_pattern_matching();
}

/*
  Rust Structs are of Record Type form similar to Tuples.
  Elements in Structs each have a Name (aka Field/Member) and a Type
*/

// Declare a Struct with a name (camel case). Values Immutable by Default
struct PointInSpace {
    x: i32,
    y: i32
}

fn try_structs() {
  // Create Instance (Mutable) of Struct using Let and set each Field (any order)
  let mut origin = PointInSpace { x: 0, y: 0 }; // origin: PointInSpace

  origin.x = 20;

  println!("Origin is at ({}, {})", origin.x, origin.y); // Dot Notation to access Fields
}

/*
  Rust Tuple Structs are a hybrid data type similar to Tuple and Struct.
  Elements in Tuple Structs DO NOT have a Name, they only have a Type.
  Structs are preferred over Tuple Structs since they have actual Names for Elements.
  Note: Tuple Structs must use round brackets and colon
*/

struct Color (
  i32,
  i32,
  i32
);

struct PointInWater (
  i32,
  i32,
  i32
);

fn try_tuple_structs() {
  let black = Color(0, 0, 0);
  let origin = PointInWater(0, 0, 0);
}

// Declare a Newtype.
struct Inches (i32);

// Newtypes are Tuple Structs with only One Element. Beneficial for creating
// New Types similar to others (Cloning)
fn try_newtype() {
  let length = Inches(10);
  // Extract inner Integer Type by Desctructing the Let
  let Inches(integer_length) = length; // Assigns length to integer_length
  println!("Newtype length is {} inches", integer_length);
}

// Enums variants are a "Sum Type" that ties a set of alternatives to a specific name
fn try_enums() {
  // Define a Character to be either a Digit or Other. The alternative names may be
  // used via their fully scoped names i.e. Character::Other
  // Sub-datastructure types allowable in an Enum include Structs, Tuple Structs
  // Enums do not have access to operators: ==, !=, *, +, <, >=

  enum Character {
    Digit(i32), // Character::Digit is a name tied to an i32
    Other, // Other is just a name (not tied to a specific Type)
  }

  // Assignment
  let ten  = Character::Digit(10);

  let x = 5;
  let y = 10;

  // // 'ordering' variable is of Ordering type (ordering: Ordering)
  // // so it contains one of the three values defined in the 'cmp' Function
  // // and we discover which one by using Conditional Operators to check
  // let ordering = cmp(x, y);

  // if ordering == Ordering::Less { println!("Less"); }
  // else if ordering == Ordering::Greater { println!("Greater"); }
  // else if ordering == Ordering::Equal { println!("Equal"); }

  // Replace the above 'if/else' with 'match' Pattern Matching instead
  // with less noise and which supports Exhaustiveness Checking across all
  // possible Variants of the Ordering Enum
  match cmp(x, y) {
      Ordering::Less => println!("less"),
      Ordering::Greater => println!("greater"),
      Ordering::Equal => println!("equal"),
  }
}

// Pattern Matching is implemented by 'match' and allows elegantly deconstructing Enums
// (known as the Sum Type in Type Theory) whilst using the 'match' keyword
// instead of avoiding 'if/else' (for multiple or a complex set of option cases)
fn try_pattern_matching() {
  let x = 5;

  // 'match' takes Expression and branches into Arms depending on its Value.
  // Evaluation is performed only on the matching Arm.
  match x {
    // Arms are of the form 'val => expression'
    1 => println!("one"),
    2 => println!("two"),
    3 => println!("three"),
    4 => println!("four"),
    5 => println!("five"),
    // Exhaustiveness Checking ensures all possible patterns of Values are covered
    // with the '_' Catch-all- Arm so if any Expression used that were not included
    // (not all bases covered) it prevents a compile error (as '_' will match)
    _ => println!("something else"),
  }
}

// Returned Tuple is a Single Value (containing Multiple Values)
fn next_two(x: i32) -> (i32, i32) { (x + 1, x + 2) }

// All control paths must Return a Value
fn incr(m: i32, n: i32) -> i32 {
    if n < m {
      return n; // Early Return
    } else {
      println!("incrementing n from: {}", n);
      return n + 10;
    }
}

/*
  Rust forces Declare expected Type of incoming Fn Args. Type Inference in Fn body.
  Rust allows Declaration expected Type of outgoing Return Value after '->'
*/
fn print_sum(m: i32, n: i32) -> () { // Return the Unit Type () (aka nil)
    if m < n {
      println!("sum with m < n is: {}", m + n);
    } else {
      println!("sum with m >= n is: {}", m + n);
    }
}<commit_msg>Destructuring Enums values with Match Expression. Error Handling with Match.<commit_after>// Import the Ordering Enum from the Rust Standard Library
// http://doc.rust-lang.org/std/cmp/enum.Ordering.html
// Note: Enums are a powerful tool for data representation and 'generics' may be used
// to make Enums generic across Types to become even more powerful.
// Ordering Enum form used for Comparisons
// enum Ordering {
//   Less,
//   Equal,
//   Greater,
// }
// :: Symbol indicates a Namespace
// 'std' Module contains Sub-Module 'cmp' containing Sub-Sub-Module 'Ordering'
// The Enum Variants (Equal, Less, Greater) have been imported instead of just the Ordering
// Namespace itself to achieve compactness and convenience. This avoids full scoping and
// avoids long notation (i.e. use just Equal instead of Ordering::Equal), however name conflicts
// may arise (so it is best practice to import and apply the Namespace combined with a
// Variants together)
use std::cmp::Ordering;

// Configure the Ordering Enum
// 'cmp' is a Function that compares two given parameters and returns an Ordering
// where depending on the difference between these parameters, the returned Ordering
// may be Ordering:Less, Ordering::Greater, or Ordering::Equal
// Variants of the Enum (Ordering) are Namespaced under the Enum itself.
fn cmp(a: i32, b: i32) -> Ordering {
  if a < b      { Ordering::Less }
  else if a > b { Ordering::Greater }
  else          { Ordering::Equal }
}

/*
  Rust is an Expression-Based Systems language.
  Declare main function
*/
fn main() {

    // Metaprogramming calling a macro and passing staticaly allocated string arg
    println!("Hello, world!");

    /*
      Variable binding expression.
      <let expression> is Full Pattern for Pattern Matching.
      Rust's Type Inference automatically determines an expression's Type.
      Alternatively be explicit and supply an expression's Type after a colon.
      Or simply annotate the Type in a comment to understand the Type Rust infers.
      Rust Bindings are immutable by Default.
      Mutable Bindings require 'mut' prefix for Type Safety.
    */
    let a: i32 = 5; // assign immutable binding 'a' with Type i32 and Value 5

    // If statement based on Branch concept
    if a == 5 {
      println!("x is five");
    } else {
      println!("x is not five");
    }

    let (x, y, z) = (1, 2, 3);  // assign immutable bindings x, y, z to 1, 2, 3

    let mut b: i32 = 10; // assign mutable binding 'b'
    b = 20; // re-assign to new Value

    /*
      Rust's 'If' is an Expression. It returns a value to initialise
      the 'Let' binding ("Declaration Statement") with. 'Let' is assigned an Expression.
      Assignment to an already bound variable is an Expression.
      Assignment Value in Rust is the special Unit Type () in Rust's Type System.
      In other languages 'If' is a Statements (does not return value).

      Rust's "Expression Statement" converts an Expression into a Statement.
      Semi-colons separate each Expression.
      Semi-colons convert Expression into Statement, throws Value away, returns Unit Type ()
      (causing Type Error when expect a different Type such as i32 to be valid)
    */
    let c: i32 = if b == 20 { 25 } else { 15 };  // Explicitely specify 'c' as of Integer Type
    println!("b is: {}", b); // Interpret interpolation with moustaches
    println!("c is: {}", c);

    incr(b, c); // Call Fn passing Args
    print_sum(b, c); // Call Fn passing Args

    // Compound Data Structure Type Tuple is simple fixed size ordered list where &str is String Slice
    let tuple1: (i32, &str) = (1, "hello"); // Mutable. Type annotated. Without: let x = (1, "hello");
    let mut tuple2: (i32, i32) = (1, 2); // Type annotated. Without: let x = (1, "hello");
    let tuple3: (i32, i32) = (3, 4);

    // Note that for a Tuple with a Single value the trailing commas are mandatory
    let (x,): (i32,) = (1,);

    if tuple2 != tuple3 {
        tuple2 = tuple3; // Assign Tuple to another Mutable Tuple (with same Arity and Types)
        println!("tuple2 and tuple3 not equal so assigning");
    } else {
        println!("tuple2 and tuple3 do not have same Arity, Types, Order, and Value, not assigning");
    }

    /*
      Destructuring Let (access parameters of Tuple).
      Let statement performs Multiple Bindings assignments when LHS and RHS Pattern Match
    */
    let (t1, t2) = tuple2;

    println!("t1 and t2 is {}", t1 + t2); // Note: Expects t1 and t2 to be same Type

    // Tuples to return Multiple Values from a Function
    let (tnt1, tnt2) = next_two(5);
    println!("tnt1, tnt2 = {}, {}", tnt1, tnt2);

    try_structs();

    try_tuple_structs();

    try_newtype();

    try_enums();

    try_pattern_matching();
}

/*
  Rust Structs are of Record Type form similar to Tuples.
  Elements in Structs each have a Name (aka Field/Member) and a Type
*/

// Declare a Struct with a name (camel case). Values Immutable by Default
struct PointInSpace {
    x: i32,
    y: i32
}

fn try_structs() {
  // Create Instance (Mutable) of Struct using Let and set each Field (any order)
  let mut origin = PointInSpace { x: 0, y: 0 }; // origin: PointInSpace

  origin.x = 20;

  println!("Origin is at ({}, {})", origin.x, origin.y); // Dot Notation to access Fields
}

/*
  Rust Tuple Structs are a hybrid data type similar to Tuple and Struct.
  Elements in Tuple Structs DO NOT have a Name, they only have a Type.
  Structs are preferred over Tuple Structs since they have actual Names for Elements.
  Note: Tuple Structs must use round brackets and colon
*/

struct Color (
  i32,
  i32,
  i32
);

struct PointInWater (
  i32,
  i32,
  i32
);

fn try_tuple_structs() {
  let black = Color(0, 0, 0);
  let origin = PointInWater(0, 0, 0);
}

// Declare a Newtype.
struct Inches (i32);

// Newtypes are Tuple Structs with only One Element. Beneficial for creating
// New Types similar to others (Cloning)
fn try_newtype() {
  let length = Inches(10);
  // Extract inner Integer Type by Desctructing the Let
  let Inches(integer_length) = length; // Assigns length to integer_length
  println!("Newtype length is {} inches", integer_length);
}

// Enums variants are a "Sum Type" that ties a set of alternatives to a specific name
fn try_enums() {
  // Define a Character to be either a Digit or Other. The alternative names may be
  // used via their fully scoped names i.e. Character::Other
  // Sub-datastructure types allowable in an Enum include Structs, Tuple Structs
  // Enums do not have access to operators: ==, !=, *, +, <, >=

  enum Character {
    Digit(i32), // Character::Digit is a name tied to an i32
    Missing, // Missing is just a name (not tied to a specific Type)
  }

  // Assignment
  let a  = Character::Digit(10);
  let b = Character::Missing;

  // Retrieve values contained in Enums, Error Handling where function output does not
  // match the expected Type
  match a {
    Character::Digit(n) => print!("\na is {} ", n),
    Character::Missing => print!("\na is missing"),
  }

  match b {
    Character::Digit(n) => print!("\nb is {}", n),
    Character::Missing => print!("\nb is missing"),
  }

  let x = 5;
  let y = 10;

  // // 'ordering' variable is of Ordering type (ordering: Ordering)
  // // so it contains one of the three values defined in the 'cmp' Function
  // // and we discover which one by using Conditional Operators to check
  // let ordering = cmp(x, y);

  // if ordering == Ordering::Less { println!("Less"); }
  // else if ordering == Ordering::Greater { println!("Greater"); }
  // else if ordering == Ordering::Equal { println!("Equal"); }

  // Replace the above 'if/else' with 'match' Pattern Matching expression instead
  // with less noise and which supports Exhaustiveness Checking across all
  // possible Variants of the Ordering Enum
  println!("{}", match cmp(x, y) {
    Ordering::Less => "\nless",
    Ordering::Greater => "\ngreater",
    Ordering::Equal => "\nequal",
  });

  // Equivalent to the above
  match cmp(x, y) {
    Ordering::Less => println!("\nless"),
    Ordering::Greater => println!("\ngreater"),
    Ordering::Equal => println!("\nequal"),
  };
}

// Pattern Matching is implemented by 'match' and allows elegantly deconstructing Enums
// (known as the Sum Type in Type Theory) whilst using the 'match' keyword
// instead of avoiding 'if/else' (for multiple or a complex set of option cases)
fn try_pattern_matching() {
  let x = 5;

  // 'match' takes Expression and branches into Arms depending on its Value.
  // Evaluation is performed only on the matching Arm.
  match x {
    // Arms are of the form 'val => expression'
    1 => println!("one"),
    2 => println!("two"),
    3 => println!("three"),
    4 => println!("four"),
    5 => println!("five"),
    // Exhaustiveness Checking ensures all possible patterns of Values are covered
    // with the '_' Catch-all- Arm so if any Expression used that were not included
    // (not all bases covered) it prevents a compile error (as '_' will match)
    _ => println!("something else"),
  }
}

// Returned Tuple is a Single Value (containing Multiple Values)
fn next_two(x: i32) -> (i32, i32) { (x + 1, x + 2) }

// All control paths must Return a Value
fn incr(m: i32, n: i32) -> i32 {
    if n < m {
      return n; // Early Return
    } else {
      println!("incrementing n from: {}", n);
      return n + 10;
    }
}

/*
  Rust forces Declare expected Type of incoming Fn Args. Type Inference in Fn body.
  Rust allows Declaration expected Type of outgoing Return Value after '->'
*/
fn print_sum(m: i32, n: i32) -> () { // Return the Unit Type () (aka nil)
    if m < n {
      println!("sum with m < n is: {}", m + n);
    } else {
      println!("sum with m >= n is: {}", m + n);
    }
}<|endoftext|>


<commit_before>#[macro_use]
extern crate clap;

#[macro_use]
extern crate log;
extern crate env_logger;

extern crate xmlrpc;
extern crate rustc_serialize;

extern crate regex;

use clap::{Arg, App};
use std::env;
use std::io;
use std::io::prelude::*;

use regex::Regex;

const GANDI_URL_PROD: &'static str = "https://rpc.gandi.net/xmlrpc/";

trait GandiAPI {
    fn init(&mut self, domain: &str);
    fn is_record_already_declared(&self, record_name: &str) -> Option<String>;
    fn update_record(&self, record_name: &str, ip_addr: &str) -> bool;
    fn create_record(&self, record_name: &str, ip_addr: &str);
}

struct GandiAPIImpl<'a> {
  xmlrpc_server: &'a str,
  apikey: &'a str,
  zone_id: Option<u32>,
  zone_id_version: u16,
}

impl<'a> GandiAPIImpl<'a> {

    fn new(gandi_url: &'a str, gandi_apikey: &'a str) -> GandiAPIImpl<'a> {
        GandiAPIImpl {
            xmlrpc_server: gandi_url,
            apikey: gandi_apikey,
            zone_id: None,
            zone_id_version: 0,
        }
    }
}

impl<'a> GandiAPI for GandiAPIImpl<'a> {

    fn init(&mut self, domain: &str) {

        let (client, mut request) = self.get_gandi_client("domain.info");
        request = request.argument(&domain.to_string());
        request = request.finalize();

        let response = client.remote_call(&request).unwrap();

        // TODO: fix this ugly code
        // Handle errors
        let zone_id_pos = response.body.find("zone_id").unwrap();
        let (_, body_end) = response.body.split_at(zone_id_pos);
        let first_int_markup = body_end.find("<int>").unwrap();
        let (_, body_end) = body_end.split_at(first_int_markup + "<int>".len());
        let first_end_int_markup = body_end.find("</int>").unwrap();
        let (zone_id, _) = body_end.split_at(first_end_int_markup);
        self.zone_id = zone_id.parse::<u32>().ok();

        debug!("Zone id: {}", self.zone_id.unwrap());
    }

    fn is_record_already_declared(&self, record_name: &str) -> Option<String> {

        let response = &self.get_record_list(record_name, &self.zone_id_version);

        // Extract already configured IP address
        // We are looking for something like that: <value><string>55.32.210.10</string></value>
        let regex = Regex::new(r"<value><string>([0-9.]*)</string></value>").unwrap();

        let caps = regex.captures(&*response.body);

        caps
            .map_or(None, |caps| caps.at(1))
            .map(|val| val.to_string())
    }

    fn update_record(&self, record_name: &str, ip_addr: &str) -> bool {

        // Create a new zone and get return version

        let new_zone_version = &self.create_new_zone();

        debug!("New zone version: {}", new_zone_version);

        // Extract new record id

        let new_record_id = &self.get_record_id(record_name, &new_zone_version);

        debug!("New record id: {}", new_record_id);

        // Update zone with the new record

        &self.update_zone_with_record(record_name, ip_addr, new_zone_version, new_record_id);

        // Activate the new zone
        debug!("Activate version '{}' of the zone '{}'", new_zone_version, &self.zone_id.unwrap());

        let (client, mut request) = self.get_gandi_client("domain.zone.version.set");
        request = request.argument(&self.zone_id.unwrap());
        request = request.argument(&new_zone_version);
        request = request.finalize();

        let response = client.remote_call(&request).unwrap();

        let regex = Regex::new(r"<boolean>([0-1]*)</boolean>").unwrap();

        let caps = regex.captures(&*response.body).unwrap();

        let result = caps.at(1).unwrap();

        debug!("Activate version result: {}", result);

        match result {
            "1" => true,
            "0" | _ => false,
        }
    }

    fn create_record(&self, record_name: &str, ip_addr: &str) {
        unimplemented!();
    }
}

impl<'a> GandiAPIImpl<'a> {

    fn get_gandi_client(&self, rpc_action: &str) -> (xmlrpc::Client, xmlrpc::Request) {
        let client = xmlrpc::Client::new(self.xmlrpc_server);
        let mut request = xmlrpc::Request::new(rpc_action);
        request = request.argument(&self.apikey.to_string());
        (client, request)
    }

    fn get_record_list(&self, record_name: &str, zone_id_version: &u16) -> xmlrpc::Response {

        let (client, mut request) = self.get_gandi_client("domain.zone.record.list");
        request = request.argument(&self.zone_id.unwrap());
        request = request.argument(zone_id_version);

        #[derive(Debug,RustcEncodable,RustcDecodable)]
        struct Record {
            name: String,
            type_: String,
        }

        let record = Record { name: record_name.to_string(), type_: "A".to_string() };

        request = request.argument(&record);

        request = request.finalize();

        // Horrible hack, because 'type' is a reserved keyword ...
        request.body = request.body.replace("type_", "type");

        client.remote_call(&request).unwrap()
    }

    fn create_new_zone(&self) -> u16 {
        let (client, mut request) = self.get_gandi_client("domain.zone.version.new");
        request = request.argument(&self.zone_id.unwrap());
        request = request.finalize();

        let response = client.remote_call(&request).unwrap();

        let regex = Regex::new(r"<int>([0-9]+)</int>").unwrap();

        let caps = regex.captures(&*response.body).unwrap();

        caps.at(1).unwrap().parse::<u16>().ok().unwrap()
    }

    fn get_record_id(&self, record_name: &str, new_zone_version: &u16) -> u32 {
        let response = &self.get_record_list(record_name, new_zone_version);

        let regex = Regex::new(r"<int>([0-9]+)</int>").unwrap();

        let caps = regex.captures(&*response.body).unwrap();

        caps.at(1).unwrap().parse::<u32>().ok().unwrap()
    }

    fn update_zone_with_record(&self, record_name: &str, ip_addr: &str, new_zone_version: &u16, new_record_id: &u32) {
        let (client, mut request) = self.get_gandi_client("domain.zone.record.update");
        request = request.argument(&self.zone_id.unwrap());
        request = request.argument(new_zone_version);

        #[derive(Debug,RustcEncodable,RustcDecodable)]
        struct NewRecordId { id: u32 };
        request = request.argument(&NewRecordId{ id: *new_record_id });

        #[derive(Debug,RustcEncodable,RustcDecodable)]
        struct Record {
            name: String,
            type_: String,
            value: String,
        }

        let record = Record {
            name: record_name.to_string(),
            type_: "A".to_string(),
            value: ip_addr.to_string(),
        };

        request = request.argument(&record);

        request = request.finalize();

        // Horrible hack, because 'type' is a reserved keyword ...
        request.body = request.body.replace("type_", "type");

        client.remote_call(&request); // ignore response
    }
}

fn main() {
    let matches = App::new("gdu")
        .version(&crate_version!()[..])
        .author("Damien Lecan <dev@dlecan.com>")
        .about("Gandi DNS updater, useful to reflect your dynamic IP address to your Gandi DNS zone file.\nIP address is read from stdin.")
        .args_from_usage(
            "-a --apikey=[apikey] 'Your API key provided by Gandi'
            -d --domain=[domain] 'The domain name whose active zonefile will be updated, e.g. \"domain.com\"'
            -n --dry-run 'Dry run, don't really update Gandi zone file'
            -f --force 'Force new zonefile creation even if IP address isn\'t modified'
            [verbose]... -v 'Verbose mode'")
        .arg(Arg::with_name("record_name")
            .help("Name of the A record to update or create (without domain)")
            .index(1)
            .required(true)
            .multiple(false))
        .get_matches();

    // Init logging to DEBUG only if user requires it
    match matches.occurrences_of("verbose") {
        0 => (),
        1 => {
                println!("Verbose mode");
                env::set_var("RUST_LOG", "DEBUG");
            },
        2 | _=> {
                println!("More verbose mode");
                env::set_var("RUST_LOG", "TRACE");
            }
    }
    env_logger::init().unwrap();

    let apikey = matches.value_of("apikey").unwrap();
    debug!("Using apikey: {}", apikey);

    let domain = matches.value_of("domain").unwrap();
    debug!("Using domain: {}", domain);

    let record_name = matches.value_of("record_name").unwrap();
    debug!("Using record name: {}", record_name);

    let dry_run = matches.is_present("dry-run");
    debug!("Dry run: {}", dry_run);

    let force = matches.is_present("force");
    debug!("Force: {}", force);

    let mut input = String::new();
    match io::stdin().read_line(&mut input) {
        Ok(_) => {
            println!("Input: {}", input);
        }
        Err(error) => println!("error: {}", error),
    }

    let detected_ip_addr = input.trim();

    let mut gandi_api = GandiAPIImpl::new(GANDI_URL_PROD, apikey);

    gandi_api.init(domain);

    let maybe_checked = gandi_api.is_record_already_declared(record_name);

    match maybe_checked {
        Some(ip_addr) => {
            debug!("Record already declared, with IP address: {}", &ip_addr);

            if !force && (&ip_addr == detected_ip_addr) {
                debug!("IP address not modified, no record to update");
            } else {
                debug!("Update record '{}' with IP address '{}'", record_name, &ip_addr);
                let result = gandi_api.update_record(record_name, &ip_addr);
                debug!("End of update process with result: {}", result);
            }
        }
    //     None => gandi_api.createRecord(record_name, detectedIpAddr)
        None => ()
    }

}
<commit_msg>Better names<commit_after>#[macro_use]
extern crate clap;

#[macro_use]
extern crate log;
extern crate env_logger;

extern crate xmlrpc;
extern crate rustc_serialize;

extern crate regex;

use clap::{Arg, App};
use std::env;
use std::io;
use std::io::prelude::*;

use regex::Regex;

const GANDI_URL_PROD: &'static str = "https://rpc.gandi.net/xmlrpc/";

trait DNSProvider {
    fn init(&mut self, domain: &str);
    fn is_record_already_declared(&self, record_name: &str) -> Option<String>;
    fn update_record(&self, record_name: &str, ip_addr: &str) -> bool;
    fn create_record(&self, record_name: &str, ip_addr: &str);
}

struct GandiDNSProvider<'a> {
  xmlrpc_server: &'a str,
  apikey: &'a str,
  zone_id: Option<u32>,
  zone_id_version: u16,
}

impl<'a> GandiDNSProvider<'a> {

    fn new(gandi_url: &'a str, gandi_apikey: &'a str) -> GandiDNSProvider<'a> {
        GandiDNSProvider {
            xmlrpc_server: gandi_url,
            apikey: gandi_apikey,
            zone_id: None,
            zone_id_version: 0,
        }
    }
}

impl<'a> DNSProvider for GandiDNSProvider<'a> {

    fn init(&mut self, domain: &str) {

        let (client, mut request) = self.get_gandi_client("domain.info");
        request = request.argument(&domain.to_string());
        request = request.finalize();

        let response = client.remote_call(&request).unwrap();

        // TODO: fix this ugly code
        // Handle errors
        let zone_id_pos = response.body.find("zone_id").unwrap();
        let (_, body_end) = response.body.split_at(zone_id_pos);
        let first_int_markup = body_end.find("<int>").unwrap();
        let (_, body_end) = body_end.split_at(first_int_markup + "<int>".len());
        let first_end_int_markup = body_end.find("</int>").unwrap();
        let (zone_id, _) = body_end.split_at(first_end_int_markup);
        self.zone_id = zone_id.parse::<u32>().ok();

        debug!("Zone id: {}", self.zone_id.unwrap());
    }

    fn is_record_already_declared(&self, record_name: &str) -> Option<String> {

        let response = &self.get_record_list(record_name, &self.zone_id_version);

        // Extract already configured IP address
        // We are looking for something like that: <value><string>55.32.210.10</string></value>
        let regex = Regex::new(r"<value><string>([0-9.]*)</string></value>").unwrap();

        let caps = regex.captures(&*response.body);

        caps
            .map_or(None, |caps| caps.at(1))
            .map(|val| val.to_string())
    }

    fn update_record(&self, record_name: &str, ip_addr: &str) -> bool {

        // Create a new zone and get return version

        let new_zone_version = &self.create_new_zone();

        debug!("New zone version: {}", new_zone_version);

        // Extract new record id

        let new_record_id = &self.get_record_id(record_name, &new_zone_version);

        debug!("New record id: {}", new_record_id);

        // Update zone with the new record

        &self.update_zone_with_record(record_name, ip_addr, new_zone_version, new_record_id);

        // Activate the new zone
        debug!("Activate version '{}' of the zone '{}'", new_zone_version, &self.zone_id.unwrap());

        let (client, mut request) = self.get_gandi_client("domain.zone.version.set");
        request = request.argument(&self.zone_id.unwrap());
        request = request.argument(&new_zone_version);
        request = request.finalize();

        let response = client.remote_call(&request).unwrap();

        let regex = Regex::new(r"<boolean>([0-1]*)</boolean>").unwrap();

        let caps = regex.captures(&*response.body).unwrap();

        let result = caps.at(1).unwrap();

        debug!("Activate version result: {}", result);

        match result {
            "1" => true,
            "0" | _ => false,
        }
    }

    fn create_record(&self, record_name: &str, ip_addr: &str) {
        unimplemented!();
    }
}

impl<'a> GandiDNSProvider<'a> {

    fn get_gandi_client(&self, rpc_action: &str) -> (xmlrpc::Client, xmlrpc::Request) {
        let client = xmlrpc::Client::new(self.xmlrpc_server);
        let mut request = xmlrpc::Request::new(rpc_action);
        request = request.argument(&self.apikey.to_string());
        (client, request)
    }

    fn get_record_list(&self, record_name: &str, zone_id_version: &u16) -> xmlrpc::Response {

        let (client, mut request) = self.get_gandi_client("domain.zone.record.list");
        request = request.argument(&self.zone_id.unwrap());
        request = request.argument(zone_id_version);

        #[derive(Debug,RustcEncodable,RustcDecodable)]
        struct Record {
            name: String,
            type_: String,
        }

        let record = Record { name: record_name.to_string(), type_: "A".to_string() };

        request = request.argument(&record);

        request = request.finalize();

        // Horrible hack, because 'type' is a reserved keyword ...
        request.body = request.body.replace("type_", "type");

        client.remote_call(&request).unwrap()
    }

    fn create_new_zone(&self) -> u16 {
        let (client, mut request) = self.get_gandi_client("domain.zone.version.new");
        request = request.argument(&self.zone_id.unwrap());
        request = request.finalize();

        let response = client.remote_call(&request).unwrap();

        let regex = Regex::new(r"<int>([0-9]+)</int>").unwrap();

        let caps = regex.captures(&*response.body).unwrap();

        caps.at(1).unwrap().parse::<u16>().ok().unwrap()
    }

    fn get_record_id(&self, record_name: &str, new_zone_version: &u16) -> u32 {
        let response = &self.get_record_list(record_name, new_zone_version);

        let regex = Regex::new(r"<int>([0-9]+)</int>").unwrap();

        let caps = regex.captures(&*response.body).unwrap();

        caps.at(1).unwrap().parse::<u32>().ok().unwrap()
    }

    fn update_zone_with_record(&self, record_name: &str, ip_addr: &str, new_zone_version: &u16, new_record_id: &u32) {
        let (client, mut request) = self.get_gandi_client("domain.zone.record.update");
        request = request.argument(&self.zone_id.unwrap());
        request = request.argument(new_zone_version);

        #[derive(Debug,RustcEncodable,RustcDecodable)]
        struct NewRecordId { id: u32 };
        request = request.argument(&NewRecordId{ id: *new_record_id });

        #[derive(Debug,RustcEncodable,RustcDecodable)]
        struct Record {
            name: String,
            type_: String,
            value: String,
        }

        let record = Record {
            name: record_name.to_string(),
            type_: "A".to_string(),
            value: ip_addr.to_string(),
        };

        request = request.argument(&record);

        request = request.finalize();

        // Horrible hack, because 'type' is a reserved keyword ...
        request.body = request.body.replace("type_", "type");

        client.remote_call(&request); // ignore response
    }
}

fn main() {
    let matches = App::new("gdu")
        .version(&crate_version!()[..])
        .author("Damien Lecan <dev@dlecan.com>")
        .about("Gandi DNS updater, useful to reflect your dynamic IP address to your Gandi DNS zone file.\nIP address is read from stdin.")
        .args_from_usage(
            "-a --apikey=[apikey] 'Your API key provided by Gandi'
            -d --domain=[domain] 'The domain name whose active zonefile will be updated, e.g. \"domain.com\"'
            -n --dry-run 'Dry run, don't really update Gandi zone file'
            -f --force 'Force new zonefile creation even if IP address isn\'t modified'
            [verbose]... -v 'Verbose mode'")
        .arg(Arg::with_name("record_name")
            .help("Name of the A record to update or create (without domain)")
            .index(1)
            .required(true)
            .multiple(false))
        .get_matches();

    // Init logging to DEBUG only if user requires it
    match matches.occurrences_of("verbose") {
        0 => (),
        1 => {
                println!("Verbose mode");
                env::set_var("RUST_LOG", "DEBUG");
            },
        2 | _=> {
                println!("More verbose mode");
                env::set_var("RUST_LOG", "TRACE");
            }
    }
    env_logger::init().unwrap();

    let apikey = matches.value_of("apikey").unwrap();
    debug!("Using apikey: {}", apikey);

    let domain = matches.value_of("domain").unwrap();
    debug!("Using domain: {}", domain);

    let record_name = matches.value_of("record_name").unwrap();
    debug!("Using record name: {}", record_name);

    let dry_run = matches.is_present("dry-run");
    debug!("Dry run: {}", dry_run);

    let force = matches.is_present("force");
    debug!("Force: {}", force);

    let mut input = String::new();
    match io::stdin().read_line(&mut input) {
        Ok(_) => {
            println!("Input: {}", input);
        }
        Err(error) => println!("error: {}", error),
    }

    let expected_ip_addr = input.trim();

    let mut dns_provider = GandiDNSProvider::new(GANDI_URL_PROD, apikey);

    dns_provider.init(domain);

    let maybe_checked = dns_provider.is_record_already_declared(record_name);

    match maybe_checked {
        Some(ip_addr) => {
            debug!("Record already declared, with IP address: {}", &ip_addr);

            if !force && (&ip_addr == expected_ip_addr) {
                debug!("IP address not modified, no record to update");
            } else {
                debug!("Update record '{}' with IP address '{}'", record_name, &expected_ip_addr);
                let result = dns_provider.update_record(record_name, &expected_ip_addr);
                debug!("End of update process with result: {}", result);
            }
        }
        None => dns_provider.create_record(record_name, expected_ip_addr)
    }

}
<|endoftext|>


<commit_before>#![feature(globs, macro_rules, phase)]

extern crate debug;
extern crate piston;
extern crate sdl2_game_window;
extern crate gfx;
#[phase(plugin)]
extern crate gfx_macros;
extern crate image;
extern crate libc;
extern crate cgmath;
extern crate time;

use sdl2_game_window::GameWindowSDL2 as Window;
use piston::input;
use piston::cam;
use piston::{
    AssetStore,
    GameIterator,
    GameIteratorSettings,
    GameWindow,
    GameWindowSettings,
    Input,
    Render,
    Update,
};

use array::*;
use texture::Texture;

pub mod array;
pub mod cube;
pub mod fps_counter;
pub mod shader;
pub mod texture;
pub mod vecmath;

fn main() {
    let mut window = Window::new(
        piston::shader_version::opengl::OpenGL_3_3,
        GameWindowSettings {
            title: "Hematite".to_string(),
            size: [854, 480],
            fullscreen: false,
            exit_on_esc: true,
        }
    );
    let (mut device, frame) = window.gfx();

    let asset_store = AssetStore::from_folder("../assets");

    // Load texture.
    let texture = asset_store.path("texture.png").unwrap();
    let texture = Texture::from_path(&texture, &mut device).unwrap();

    let game_iter_settings = GameIteratorSettings {
        updates_per_second: 120,
        max_frames_per_second: 60,
    };

    let mut renderer = shader::Renderer::new(device, frame, texture.tex);

    renderer.set_projection(cam::CameraPerspective {
        fov: 70.0,
        near_clip: 0.1,
        far_clip: 1000.0,
        aspect_ratio: {
            let (w, h) = window.get_size();
            (w as f32) / (h as f32)
        }
    }.projection());

    let mut camera = cam::Camera::new(0.5, 0.5, 4.0);
    let mut fps_controller_settings = cam::FPSControllerSettings::default();
    fps_controller_settings.speed_horizontal = 8.0;
    fps_controller_settings.speed_vertical = 4.0;
    let mut fps_controller = cam::FPSController::new(fps_controller_settings);
    camera.set_yaw_pitch(fps_controller.yaw, fps_controller.pitch);

    let mut fps_counter = fps_counter::FPSCounter::new();

    let mut capture_cursor = false;
    println!("Press C to capture mouse");

    let mut events = GameIterator::new(&mut window, &game_iter_settings);
    for e in events {
        match e {
            Render(_args) => {
                let mut tri = vec![];
                for face in cube::FaceIterator::new() {
                    let xyz = face.vertices([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
                    let [u0, v1, u1, v0] = [0.0, 0.75, 0.25, 1.0];
                    let uv = [
                        [u1, v0],
                        [u0, v0],
                        [u0, v1],
                        [u1, v1]
                    ];
                    let v = [
                        (xyz[0], uv[0], [1.0, 0.0, 0.0]),
                        (xyz[1], uv[1], [0.0, 1.0, 0.0]),
                        (xyz[2], uv[2], [0.0, 0.0, 1.0]),
                        (xyz[3], uv[3], [1.0, 0.0, 1.0])
                    ].map(|(xyz, uv, rgb)| shader::Vertex { xyz: xyz, uv: uv, rgb: rgb });

                    // Split the clockwise quad into two clockwise triangles.
                    tri.push_all([v[0], v[1], v[2]]);
                    tri.push_all([v[2], v[3], v[0]]);
                }
                let buf = renderer.create_buffer(tri.as_slice());
                renderer.set_view(camera.orthogonal());
                renderer.reset();
                renderer.render(buf);
                renderer.end_frame();
                renderer.delete_buffer(buf);

                let fps = fps_counter.update();
                let title = format!("Hematite @ {}FPS", fps);
                events.game_window.window.set_title(title.as_slice());
            }
            Input(input::KeyPress { key: input::keyboard::C }) => {
                println!("Turned cursor capture {}", if capture_cursor { "off" } else { "on" });
                capture_cursor = !capture_cursor;

                events.game_window.capture_cursor(capture_cursor);
            }
            Input(input::MouseRelativeMove { .. }) => {
                if !capture_cursor {
                    // Don't send the mouse event to the FPS controller.
                    continue;
                }
            }
            _ => {}
        }

        // Camera controller.
        match e {
            Input(ref args) => fps_controller.input(args, &mut camera),
            Update(piston::UpdateArgs { dt, .. }) => fps_controller.update(dt, &mut camera),
            _ => {}
        }
    }
}
<commit_msg>Remove any FPS caps to allow accurate comparisons.<commit_after>#![feature(globs, macro_rules, phase)]

extern crate debug;
extern crate piston;
extern crate sdl2;
extern crate sdl2_game_window;
extern crate gfx;
#[phase(plugin)]
extern crate gfx_macros;
extern crate image;
extern crate libc;
extern crate cgmath;
extern crate time;

use sdl2_game_window::GameWindowSDL2 as Window;
use piston::input;
use piston::cam;
use piston::{
    AssetStore,
    GameIterator,
    GameIteratorSettings,
    GameWindow,
    GameWindowSettings,
    Input,
    Render,
    Update,
};

use array::*;
use texture::Texture;

pub mod array;
pub mod cube;
pub mod fps_counter;
pub mod shader;
pub mod texture;
pub mod vecmath;

fn main() {
    let mut window = Window::new(
        piston::shader_version::opengl::OpenGL_3_3,
        GameWindowSettings {
            title: "Hematite".to_string(),
            size: [854, 480],
            fullscreen: false,
            exit_on_esc: true,
        }
    );
    let (mut device, frame) = window.gfx();

    let asset_store = AssetStore::from_folder("../assets");

    // Load texture.
    let texture = asset_store.path("texture.png").unwrap();
    let texture = Texture::from_path(&texture, &mut device).unwrap();

    let game_iter_settings = GameIteratorSettings {
        updates_per_second: 120,
        max_frames_per_second: 10000,
    };

    let mut renderer = shader::Renderer::new(device, frame, texture.tex);

    renderer.set_projection(cam::CameraPerspective {
        fov: 70.0,
        near_clip: 0.1,
        far_clip: 1000.0,
        aspect_ratio: {
            let (w, h) = window.get_size();
            (w as f32) / (h as f32)
        }
    }.projection());

    let mut camera = cam::Camera::new(0.5, 0.5, 4.0);
    let mut fps_controller_settings = cam::FPSControllerSettings::default();
    fps_controller_settings.speed_horizontal = 8.0;
    fps_controller_settings.speed_vertical = 4.0;
    let mut fps_controller = cam::FPSController::new(fps_controller_settings);
    camera.set_yaw_pitch(fps_controller.yaw, fps_controller.pitch);

    // Disable V-Sync.
    sdl2::video::gl_set_swap_interval(0);

    let mut fps_counter = fps_counter::FPSCounter::new();

    let mut capture_cursor = false;
    println!("Press C to capture mouse");

    let mut events = GameIterator::new(&mut window, &game_iter_settings);
    for e in events {
        match e {
            Render(_args) => {
                let mut tri = vec![];
                for face in cube::FaceIterator::new() {
                    let xyz = face.vertices([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
                    let [u0, v1, u1, v0] = [0.0, 0.75, 0.25, 1.0];
                    let uv = [
                        [u1, v0],
                        [u0, v0],
                        [u0, v1],
                        [u1, v1]
                    ];
                    let v = [
                        (xyz[0], uv[0], [1.0, 0.0, 0.0]),
                        (xyz[1], uv[1], [0.0, 1.0, 0.0]),
                        (xyz[2], uv[2], [0.0, 0.0, 1.0]),
                        (xyz[3], uv[3], [1.0, 0.0, 1.0])
                    ].map(|(xyz, uv, rgb)| shader::Vertex { xyz: xyz, uv: uv, rgb: rgb });

                    // Split the clockwise quad into two clockwise triangles.
                    tri.push_all([v[0], v[1], v[2]]);
                    tri.push_all([v[2], v[3], v[0]]);
                }
                let buf = renderer.create_buffer(tri.as_slice());
                renderer.set_view(camera.orthogonal());
                renderer.reset();
                renderer.render(buf);
                renderer.end_frame();
                renderer.delete_buffer(buf);

                let fps = fps_counter.update();
                let title = format!("Hematite @ {}FPS", fps);
                events.game_window.window.set_title(title.as_slice());
            }
            Input(input::KeyPress { key: input::keyboard::C }) => {
                println!("Turned cursor capture {}", if capture_cursor { "off" } else { "on" });
                capture_cursor = !capture_cursor;

                events.game_window.capture_cursor(capture_cursor);
            }
            Input(input::MouseRelativeMove { .. }) => {
                if !capture_cursor {
                    // Don't send the mouse event to the FPS controller.
                    continue;
                }
            }
            _ => {}
        }

        // Camera controller.
        match e {
            Input(ref args) => fps_controller.input(args, &mut camera),
            Update(piston::UpdateArgs { dt, .. }) => fps_controller.update(dt, &mut camera),
            _ => {}
        }
    }
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate glium;
extern crate clock_ticks;
extern crate nalgebra;
extern crate rustc_serialize;

use std::f32;
use glium::Surface;
use glium::glutin;
use glium::glutin::*;
use glium::index::PrimitiveType;
use nalgebra::Vec2;

mod settings;
use settings::*;

struct Ship {
    pub rotation         : f32,
    pub position         : Vec2<f32>,
    pub velocity         : Vec2<f32>,
    pub prev_position    : Vec2<f32>,
    pub prev_rotation    : f32,
    pub left_is_pressed  : bool,
    pub right_is_pressed : bool,
    pub up_is_pressed    : bool,
}

impl Ship {
    fn new() -> Ship {
        Ship {
            rotation         : 0f32,
            position         : Vec2::new(0.3f32, 0.1f32),
            velocity         : Vec2::new(0.0f32, 0.0f32),
            prev_position    : Vec2::new(0.3f32, 0.1f32),
            prev_rotation    : 0f32,
            left_is_pressed  : false,
            right_is_pressed : false,
            up_is_pressed    : false,
        }
    }
}

fn main() {
    use glium::DisplayBuild;

    let settings = load_settings("settings.json");

    let display = {
        let mut display_builder = glutin::WindowBuilder::new();

        if settings.fullscreen {
            display_builder = display_builder.with_fullscreen(glutin::get_primary_monitor());
        }

        display_builder.build_glium().unwrap()
    };

    let index_buffer = glium::IndexBuffer::new(&display, PrimitiveType::TrianglesList,
                                               &[0, 1, 2,
                                                 3, 4, 5u16]).unwrap();

    let program = program!(&display,
        140 => {
            vertex: "
                #version 140

                uniform mat4 matrix;

                in vec2 position;
                in vec2 global_position;
                in vec3 color;
                in float rotation;

                out vec3 vColor;

                void main() {
                    float cos_rotation = cos(rotation);
                    float sin_rotation = sin(rotation);
                    gl_Position = vec4(
                        position.x * cos_rotation - position.y * sin_rotation + global_position.x,
                        position.x * sin_rotation + position.y * cos_rotation + global_position.y,
                        0.0,
                        1.0) * matrix;
                    vColor = color;
                }
            ",

            fragment: "
                #version 140
                in vec3 vColor;
                out vec4 f_color;

                void main() {
                    f_color = vec4(vColor, 1.0);
                }
            "
        },
    ).unwrap();

    let mut accumulator = 0;
    let mut previous_clock = clock_ticks::precise_time_ns();

    let mut frames = 0;
    let mut fpses = Vec::new();

    let mut do_clear = true;

    let uniforms = uniform! {
        matrix: [
            [0.4, 0.0, 0.0, 0.0],
            [0.0, 0.5, 0.0, 0.0],
            [0.0, 0.0, 1.0, 0.0],
            [0.0, 0.0, 0.0, 1.0f32]
        ]
    };

    let mut ship = Ship::new();

    loop {
        let mut target = display.draw();

        if do_clear {
            target.clear_color(0.0, 0.0, 0.0, 0.0);
        }

        let vertex_buffer = {
            #[derive(Copy, Clone)]
            struct Vertex {
                position: [f32; 2],
                color: [f32; 3],
                rotation: f32,
                global_position: [f32; 2],
            }

            implement_vertex!(Vertex, position, color, rotation, global_position);

            glium::VertexBuffer::new(&display,
                &[
                    Vertex { position: [-0.05, -0.025], color: [0.3, 0.3, 0.3], rotation: ship.prev_rotation, global_position: *ship.prev_position.as_array() },
                    Vertex { position: [ 0.05,  0.000], color: [0.3, 0.3, 0.3], rotation: ship.prev_rotation, global_position: *ship.prev_position.as_array() },
                    Vertex { position: [-0.05,  0.025], color: [0.3, 0.3, 0.3], rotation: ship.prev_rotation, global_position: *ship.prev_position.as_array() },
                    Vertex { position: [-0.05, -0.025], color: [1.0, 1.0, 1.0], rotation: ship.rotation     , global_position: *ship.position.as_array() },
                    Vertex { position: [ 0.05,  0.000], color: [1.0, 1.0, 1.0], rotation: ship.rotation     , global_position: *ship.position.as_array() },
                    Vertex { position: [-0.05,  0.025], color: [1.0, 1.0, 1.0], rotation: ship.rotation     , global_position: *ship.position.as_array() },
                ]
            ).unwrap()
        };

        target.draw(&vertex_buffer, &index_buffer, &program, &uniforms, &Default::default()).unwrap();

        target.finish().unwrap();

        for event in display.poll_events() {
            match event {
                glutin::Event::Closed => return,
                glutin::Event::KeyboardInput(ElementState::Pressed, _, Some(VirtualKeyCode::C)) => { do_clear = !do_clear },
                glutin::Event::KeyboardInput(ElementState::Pressed, _, Some(VirtualKeyCode::Escape)) => return,

                glutin::Event::KeyboardInput(pressed, _, Some(VirtualKeyCode::Left )) => { ship.left_is_pressed  = pressed == ElementState::Pressed },
                glutin::Event::KeyboardInput(pressed, _, Some(VirtualKeyCode::Right)) => { ship.right_is_pressed = pressed == ElementState::Pressed },
                glutin::Event::KeyboardInput(pressed, _, Some(VirtualKeyCode::Up   )) => { ship.up_is_pressed    = pressed == ElementState::Pressed },

                glutin::Event::KeyboardInput(ElementState::Pressed, _, Some(keycode)) => { println!("Key pressed but not handled: {:?}", keycode); },
                _ => ()
            }
        }

        let now = clock_ticks::precise_time_ns();

        if settings.print_fps {
            frames += 1;

            if previous_clock / 1_000_000_000 < now / 1_000_000_000 {
                fpses.push(frames);
                frames = 0;

                if fpses.len() == 5 {
                    for fps in fpses.iter() {
                        println!("FPS {}", fps);
                    }
                    fpses.clear();
                }
            }
        }

        accumulator += now - previous_clock;
        previous_clock = now;

        const FIXED_TIME_STAMP: u64 = 1_000_000; // = 1 millisecond
        while accumulator >= FIXED_TIME_STAMP {
            accumulator -= FIXED_TIME_STAMP;

            ship.prev_position = ship.position;
            ship.prev_rotation = ship.rotation;

            if ship.left_is_pressed {
                ship.rotation += settings.rotation_speed;
            }
            if ship.right_is_pressed {
                ship.rotation -= settings.rotation_speed;
            }
            let acceleration = if ship.up_is_pressed { settings.acceleration } else { 0f32 };
            let direction = Vec2::new(f32::cos(ship.rotation), f32::sin(ship.rotation));
            ship.velocity = (ship.velocity + direction * acceleration) * settings.drag;
            ship.position = ship.position + ship.velocity;
        }
    }

}

<commit_msg>Keyboard mapping in player object<commit_after>#[macro_use]
extern crate glium;
extern crate clock_ticks;
extern crate nalgebra;
extern crate rustc_serialize;

use std::f32;
use glium::Surface;
use glium::glutin;
use glium::glutin::*;
use glium::index::PrimitiveType;
use nalgebra::Vec2;

mod settings;
use settings::*;

struct Ship {
    pub rotation         : f32,
    pub position         : Vec2<f32>,
    pub velocity         : Vec2<f32>,
    pub prev_position    : Vec2<f32>,
    pub prev_rotation    : f32,
}

struct LocalPlayer {
    ship                 : Option<Ship>,
    pub left_is_pressed  : bool,
    pub right_is_pressed : bool,
    pub up_is_pressed    : bool,
    pub left_key         : VirtualKeyCode,
    pub right_key        : VirtualKeyCode,
    pub up_key           : VirtualKeyCode,
}

impl LocalPlayer {
    fn new(left_key : VirtualKeyCode, right_key : VirtualKeyCode, up_key : VirtualKeyCode) -> Self {
        LocalPlayer {
            ship             : None,
            left_is_pressed  : false,
            right_is_pressed : false,
            up_is_pressed    : false,
            left_key         : left_key,
            right_key        : right_key,
            up_key           : up_key,
        }
    }

    fn ship(&self) -> &Option<Ship> {
        &self.ship
    }

    fn spawn(&mut self) -> Result<(), ()> {
        match self.ship {
            None => { self.ship = Some(Ship::new()); Ok(()) },
            _    => Err(()),
        }
    }

    fn on_key(&mut self, key: VirtualKeyCode, pressed: bool) -> bool {
        if      key == self.left_key  { self.left_is_pressed  = pressed; true }
        else if key == self.right_key { self.right_is_pressed = pressed; true }
        else if key == self.up_key    { self.up_is_pressed    = pressed; true }
        else { false }
    }
}

impl Ship {
    fn new() -> Ship {
        Ship {
            rotation         : 0f32,
            position         : Vec2::new(0.3f32, 0.1f32),
            velocity         : Vec2::new(0.0f32, 0.0f32),
            prev_position    : Vec2::new(0.3f32, 0.1f32),
            prev_rotation    : 0f32,
        }
    }
}

fn main() {
    use glium::DisplayBuild;

    let settings = load_settings("settings.json");

    let display = {
        let mut display_builder = glutin::WindowBuilder::new();

        if settings.fullscreen {
            display_builder = display_builder.with_fullscreen(glutin::get_primary_monitor());
        }

        display_builder.build_glium().unwrap()
    };

    let index_buffer = glium::IndexBuffer::new(&display, PrimitiveType::TrianglesList,
                                               &[0, 1, 2,
                                                 3, 4, 5u16]).unwrap();

    let program = program!(&display,
        140 => {
            vertex: "
                #version 140

                uniform mat4 matrix;

                in vec2 position;
                in vec2 global_position;
                in vec3 color;
                in float rotation;

                out vec3 vColor;

                void main() {
                    float cos_rotation = cos(rotation);
                    float sin_rotation = sin(rotation);
                    gl_Position = vec4(
                        position.x * cos_rotation - position.y * sin_rotation + global_position.x,
                        position.x * sin_rotation + position.y * cos_rotation + global_position.y,
                        0.0,
                        1.0) * matrix;
                    vColor = color;
                }
            ",

            fragment: "
                #version 140
                in vec3 vColor;
                out vec4 f_color;

                void main() {
                    f_color = vec4(vColor, 1.0);
                }
            "
        },
    ).unwrap();

    let mut accumulator = 0;
    let mut previous_clock = clock_ticks::precise_time_ns();

    let mut frames = 0;
    let mut fpses = Vec::new();

    let mut do_clear = true;

    let uniforms = uniform! {
        matrix: [
            [0.4, 0.0, 0.0, 0.0],
            [0.0, 0.5, 0.0, 0.0],
            [0.0, 0.0, 1.0, 0.0],
            [0.0, 0.0, 0.0, 1.0f32]
        ]
    };

    let mut player = LocalPlayer::new(VirtualKeyCode::Left,
                                      VirtualKeyCode::Right,
                                      VirtualKeyCode::Up);

    player.spawn().unwrap();

    loop {
        let mut target = display.draw();

        if do_clear {
            target.clear_color(0.0, 0.0, 0.0, 0.0);
        }

        let vertex_buffer = {
            #[derive(Copy, Clone)]
            struct Vertex {
                position: [f32; 2],
                color: [f32; 3],
                rotation: f32,
                global_position: [f32; 2],
            }

            implement_vertex!(Vertex, position, color, rotation, global_position);

            let mut vertices = Vec::new();

            match *player.ship() {
                None => {}
                Some(ref ship) => {
                    vertices.push(Vertex { position: [-0.05, -0.025], color: [0.3, 0.3, 0.3], rotation: ship.prev_rotation, global_position: *ship.prev_position.as_array() });
                    vertices.push(Vertex { position: [ 0.05,  0.000], color: [0.3, 0.3, 0.3], rotation: ship.prev_rotation, global_position: *ship.prev_position.as_array() });
                    vertices.push(Vertex { position: [-0.05,  0.025], color: [0.3, 0.3, 0.3], rotation: ship.prev_rotation, global_position: *ship.prev_position.as_array() });
                    vertices.push(Vertex { position: [-0.05, -0.025], color: [1.0, 1.0, 1.0], rotation: ship.rotation     , global_position: *ship.position.as_array() });
                    vertices.push(Vertex { position: [ 0.05,  0.000], color: [1.0, 1.0, 1.0], rotation: ship.rotation     , global_position: *ship.position.as_array() });
                    vertices.push(Vertex { position: [-0.05,  0.025], color: [1.0, 1.0, 1.0], rotation: ship.rotation     , global_position: *ship.position.as_array() });
                }
            };

            glium::VertexBuffer::new(&display, &vertices).unwrap()
        };

        target.draw(&vertex_buffer, &index_buffer, &program, &uniforms, &Default::default()).unwrap();

        target.finish().unwrap();

        for event in display.poll_events() {
            match event {
                glutin::Event::Closed => return,
                glutin::Event::KeyboardInput(ElementState::Pressed, _, Some(VirtualKeyCode::C)) => { do_clear = !do_clear },
                glutin::Event::KeyboardInput(ElementState::Pressed, _, Some(VirtualKeyCode::Escape)) => return,

                glutin::Event::KeyboardInput(pressed, _, Some(key)) => {
                    let is_pressed = pressed == ElementState::Pressed;
                    let handled = player.on_key(key, is_pressed);

                    if !handled && is_pressed {
                        println!("Key pressed but not handled: {:?}", key);
                    }
                },

                _ => ()
            }
        }

        let now = clock_ticks::precise_time_ns();

        if settings.print_fps {
            frames += 1;

            if previous_clock / 1_000_000_000 < now / 1_000_000_000 {
                fpses.push(frames);
                frames = 0;

                if fpses.len() == 5 {
                    for fps in fpses.iter() {
                        println!("FPS {}", fps);
                    }
                    fpses.clear();
                }
            }
        }

        accumulator += now - previous_clock;
        previous_clock = now;

        const FIXED_TIME_STAMP: u64 = 1_000_000; // = 1 millisecond
        while accumulator >= FIXED_TIME_STAMP {
            accumulator -= FIXED_TIME_STAMP;

            match *player.ship() {
                None => {}
                Some(ref mut ship) => {
                    ship.prev_position = ship.position;
                    ship.prev_rotation = ship.rotation;

                    if player.left_is_pressed {
                        ship.rotation += settings.rotation_speed;
                    }
                    if player.right_is_pressed {
                        ship.rotation -= settings.rotation_speed;
                    }
                    let acceleration = if player.up_is_pressed { settings.acceleration } else { 0f32 };
                    let direction = Vec2::new(f32::cos(ship.rotation), f32::sin(ship.rotation));
                    ship.velocity = (ship.velocity + direction * acceleration) * settings.drag;
                    ship.position = ship.position + ship.velocity;
                }
            }
        }
    }

}

<|endoftext|>