<commit_before>#[macro_use] extern crate log;
extern crate url;
extern crate rustc_serialize;
extern crate clap;

use clap::{Arg, App};
use std::io::Read;
use std::net::{TcpListener, TcpStream};
use std::os::unix::process::ExitStatusExt;
use std::process::Command;
use std::thread;
use url::Url;

fn main() {
    let matches = App::new("ffto")
        .version("0.0.2")
        .author("Timothée Ravier <tim@siosm.fr>")
        .about("Open URLs received as input in the default browser")
        .arg(Arg::with_name("command")
             .short("c")
             .long("command")
             .value_name("cmd")
             .help("Command executed for each URL received")
             .default_value("xdg-open"))
        .arg(Arg::with_name("listen address")
             .short("l")
             .long("listen-address")
             .value_name("addr")
             .help("Address and port to listen to")
             .default_value("127.0.0.1:7777"))
        .get_matches();

    let browser_command = matches.value_of("command").unwrap().to_string();
    let address = matches.value_of("listen address").unwrap();

    let listener = match TcpListener::bind(&address[..]) {
        Ok(l)  => l,
        Err(e) => panic!("Could not bind to {}: {}", address, e)
    };

    for stream in listener.incoming() {
        match stream {
            Ok(stream)  => {
                debug!("Accepted new incoming connection");
                let command = browser_command.clone();
                thread::spawn(move || {
                    debug!("Spawned new thread to handle connection");
                    handle_client(stream, command)
                });
            },
            Err(e) => panic!("Could not handle incoming connection: {}", e)
        }
    }

    drop(listener);
}

fn handle_client(mut stream: TcpStream, browser_command: String) {
    let mut message = String::new();
    match stream.read_to_string(& mut message) {
        Ok(_) => {},
        Err(e) => {
            error!("Input isn't a valid UTF-8 string: {}", e);
            return
        }
    };

    for line in message.split('\n') {
        debug!("Current line is: {}", line);
        match Url::parse(line) {
            Ok(u)  => {
                debug!("Found URL in: {}", line);
                if url_valid(&u) {
                    debug!("Found URL is valid");
                    spawn_browser(&browser_command, &format!("{}", u));
                }
            },
            Err(e) => info!("No URL found in '{}': {}", line, e)
        }
    }
}

fn url_valid(u: &Url) -> bool {
    (u.scheme() == "http" || u.scheme() == "https")
        && u.host().is_some()
}

fn spawn_browser(command: &str, url: &str) {
    debug!("Spawning process: {} {}", command, url);
    let output = match Command::new(command).arg(url).output() {
        Ok(output) => output,
        Err(e) => panic!("Failed to spawn process '{} {}': {}", command, url, e)
    };
    if output.status.success() {
        debug!("Process exited successfully");
    } else {
        info!("stdout:\n{:?}\n\nstderr:\n{:?}",
              output.stdout,
              output.stderr);
        match output.status.code() {
            None => {
                match output.status.signal() {
                    None => panic!("Should never happen!"),
                    Some(i) => panic!("Process received signal: {}", i)
                }
            }
            Some(i) => panic!("Process exited with status: {}", i)
        }
    }
}
<commit_msg>Format code using cargo fmt<commit_after>#[macro_use]
extern crate log;
extern crate url;
extern crate rustc_serialize;
extern crate clap;

use clap::{Arg, App};
use std::io::Read;
use std::net::{TcpListener, TcpStream};
use std::os::unix::process::ExitStatusExt;
use std::process::Command;
use std::thread;
use url::Url;

fn main() {
    let matches = App::new("ffto")
        .version("0.0.2")
        .author("Timothée Ravier <tim@siosm.fr>")
        .about("Open URLs received as input in the default browser")
        .arg(Arg::with_name("command")
            .short("c")
            .long("command")
            .value_name("cmd")
            .help("Command executed for each URL received")
            .default_value("xdg-open"))
        .arg(Arg::with_name("listen address")
            .short("l")
            .long("listen-address")
            .value_name("addr")
            .help("Address and port to listen to")
            .default_value("127.0.0.1:7777"))
        .get_matches();

    let browser_command = matches.value_of("command").unwrap().to_string();
    let address = matches.value_of("listen address").unwrap();

    let listener = match TcpListener::bind(&address[..]) {
        Ok(l) => l,
        Err(e) => panic!("Could not bind to {}: {}", address, e),
    };

    for stream in listener.incoming() {
        match stream {
            Ok(stream) => {
                debug!("Accepted new incoming connection");
                let command = browser_command.clone();
                thread::spawn(move || {
                    debug!("Spawned new thread to handle connection");
                    handle_client(stream, command)
                });
            }
            Err(e) => panic!("Could not handle incoming connection: {}", e),
        }
    }

    drop(listener);
}

fn handle_client(mut stream: TcpStream, browser_command: String) {
    let mut message = String::new();
    match stream.read_to_string(&mut message) {
        Ok(_) => {}
        Err(e) => {
            error!("Input isn't a valid UTF-8 string: {}", e);
            return;
        }
    };

    for line in message.split('\n') {
        debug!("Current line is: {}", line);
        match Url::parse(line) {
            Ok(u) => {
                debug!("Found URL in: {}", line);
                if url_valid(&u) {
                    debug!("Found URL is valid");
                    spawn_browser(&browser_command, &format!("{}", u));
                }
            }
            Err(e) => info!("No URL found in '{}': {}", line, e),
        }
    }
}

fn url_valid(u: &Url) -> bool {
    (u.scheme() == "http" || u.scheme() == "https") && u.host().is_some()
}

fn spawn_browser(command: &str, url: &str) {
    debug!("Spawning process: {} {}", command, url);
    let output = match Command::new(command).arg(url).output() {
        Ok(output) => output,
        Err(e) => panic!("Failed to spawn process '{} {}': {}", command, url, e),
    };
    if output.status.success() {
        debug!("Process exited successfully");
    } else {
        info!("stdout:\n{:?}\n\nstderr:\n{:?}",
              output.stdout,
              output.stderr);
        match output.status.code() {
            None => {
                match output.status.signal() {
                    None => panic!("Should never happen!"),
                    Some(i) => panic!("Process received signal: {}", i),
                }
            }
            Some(i) => panic!("Process exited with status: {}", i),
        }
    }
}
<|endoftext|>


<commit_before>extern crate adapton;

pub use adapton::name::printstuff;

fn main () {
    printstuff();
    println!("hello world")
}
<commit_msg>learning how to make thunks<commit_after>extern crate adapton;
extern crate lazy;

pub use lazy::single::Thunk;
pub use adapton::name::printstuff;

fn main () {
    printstuff();
    let x = Thunk::new(move |:| { 666u });
    println!("hello world, the answer is {}.", x.unwrap())
}
<|endoftext|>


<commit_before>#![feature(collections_range, range_contains)]

extern crate dotenv;
extern crate itertools;
#[macro_use]
extern crate lazy_static;
extern crate rand;
extern crate serenity;

use std::collections::BTreeMap;
use std::collections::range::RangeArgument;
use std::env;
use std::ops::RangeInclusive;

use dotenv::dotenv;
use itertools::Itertools;
use rand::Rng;
use serenity::prelude::*;
use serenity::model::prelude::*;

trait Command {
    fn required_arg_count(&self) -> &RangeInclusive<usize>; //&RangeArgument<usize>;
    fn exec(&self, ctx: &Context, msg: &Message, args: &[&str]);
}
type CommandMap = BTreeMap<&'static str, Box<Command + Sync>>;

lazy_static! {
    static ref COMMAND_MAP: CommandMap = {
        let mut commands = CommandMap::new();
        commands.insert("help", Box::new(Help));
        commands.insert("flip", Box::new(Flip));
        commands.insert("roll", Box::new(Roll));
        commands
    };
}

fn main() {
    let _ = dotenv();

    let creds = env::var("Z_CREDENTIALS")
        .expect("Unspecified credentials: set Z_CREDENTIALS in the environment (or in `.env`).");
    let mut client = Client::new(&creds, Handler).unwrap();
    client.start().unwrap();
}

struct Handler;
impl EventHandler for Handler {
    fn ready(&self, ctx: Context, _: Ready) {
        ctx.set_presence(Some(Game::playing("Rust")), OnlineStatus::Online)
    }

    fn message(&self, ctx: Context, msg: Message) {
        if msg.content.chars().next() == Some('!') {
            let mut tokens = msg.content[1..].split_whitespace();
            let maybe_command = tokens.next();
            let args = tokens.collect::<Vec<_>>();

            if let Some(command) = maybe_command {
                if let Some(command_handler) = COMMAND_MAP.get(command) {
                    let valid_arg_range = command_handler.required_arg_count();
                    if valid_arg_range.contains(args.len()) {
                        command_handler.exec(&ctx, &msg, &*args);
                    } else {
                        msg.reply(&format!(
                            "This command requires {:?} to {:?} arguments.",
                            valid_arg_range.start(),
                            valid_arg_range.end()
                        )).unwrap();
                    }
                } else {
                    msg.reply(&format!("Unknown command: {}", command)).unwrap();
                }
            } else {
                msg.reply("No command specified.").unwrap();
            }
        }
    }
}

struct Help;
impl Command for Help {
    fn required_arg_count(&self) -> &RangeInclusive<usize> {
        &(0..=0)
    }

    fn exec(&self, _ctx: &Context, msg: &Message, _args: &[&str]) {
        let help_output = COMMAND_MAP
            .keys()
            .intersperse(&", !")
            .fold("Available commands:\n!".to_owned(), |output, command| {
                output + command
            });

        msg.channel_id.say(&help_output).unwrap();
    }
}

struct Flip;
impl Command for Flip {
    fn required_arg_count(&self) -> &RangeInclusive<usize> {
        &(0..=0)
    }

    fn exec(&self, _ctx: &Context, msg: &Message, _args: &[&str]) {
        if rand::thread_rng().gen() {
            msg.reply("Heads!").unwrap();
        } else {
            msg.reply("Tails!").unwrap();
        }
    }
}

struct Roll;
impl Command for Roll {
    fn required_arg_count(&self) -> &RangeInclusive<usize> {
        &(0..=1)
    }

    fn exec(&self, _ctx: &Context, msg: &Message, args: &[&str]) {
        let upper_bound = if args.len() == 0 {
            6
        } else if args.len() == 1 {
            if let Ok(num) = args[0].parse() {
                num
            } else {
                msg.reply("That is not a number.").unwrap();
                return;
            }
        } else {
            unreachable!();
        };

        if upper_bound <= 0 {
            msg.reply("The upper bound must be a positive number.")
                .unwrap();
        } else {
            let result = rand::thread_rng().gen_range(0, upper_bound) + 1;
            msg.reply(&format!("{}", result)).unwrap();
        }
    }
}
<commit_msg>Run clippy.<commit_after>#![feature(collections_range, range_contains)]

extern crate dotenv;
extern crate itertools;
#[macro_use]
extern crate lazy_static;
extern crate rand;
extern crate serenity;

use std::collections::BTreeMap;
use std::collections::range::RangeArgument;
use std::env;
use std::ops::RangeInclusive;

use dotenv::dotenv;
use itertools::Itertools;
use rand::Rng;
use serenity::prelude::*;
use serenity::model::prelude::*;

trait Command {
    fn required_arg_count(&self) -> &RangeInclusive<usize>; //&RangeArgument<usize>;
    fn exec(&self, ctx: &Context, msg: &Message, args: &[&str]);
}
type CommandMap = BTreeMap<&'static str, Box<Command + Sync>>;

lazy_static! {
    static ref COMMAND_MAP: CommandMap = {
        let mut commands = CommandMap::new();
        commands.insert("help", Box::new(Help));
        commands.insert("flip", Box::new(Flip));
        commands.insert("roll", Box::new(Roll));
        commands
    };
}

fn main() {
    let _ = dotenv();

    let creds = env::var("Z_CREDENTIALS")
        .expect("Unspecified credentials: set Z_CREDENTIALS in the environment (or in `.env`).");
    let mut client = Client::new(&creds, Handler).unwrap();
    client.start().unwrap();
}

struct Handler;
impl EventHandler for Handler {
    fn ready(&self, ctx: Context, _: Ready) {
        ctx.set_presence(Some(Game::playing("Rust")), OnlineStatus::Online)
    }

    fn message(&self, ctx: Context, msg: Message) {
        if msg.content.starts_with('!') {
            let mut tokens = msg.content[1..].split_whitespace();
            let maybe_command = tokens.next();
            let args = tokens.collect::<Vec<_>>();

            if let Some(command) = maybe_command {
                if let Some(command_handler) = COMMAND_MAP.get(command) {
                    let valid_arg_range = command_handler.required_arg_count();
                    if valid_arg_range.contains(args.len()) {
                        command_handler.exec(&ctx, &msg, &*args);
                    } else {
                        msg.reply(&format!(
                            "This command requires {:?} to {:?} arguments.",
                            valid_arg_range.start(),
                            valid_arg_range.end()
                        )).unwrap();
                    }
                } else {
                    msg.reply(&format!("Unknown command: {}", command)).unwrap();
                }
            } else {
                msg.reply("No command specified.").unwrap();
            }
        }
    }
}

struct Help;
impl Command for Help {
    fn required_arg_count(&self) -> &RangeInclusive<usize> {
        &(0..=0)
    }

    fn exec(&self, _ctx: &Context, msg: &Message, _args: &[&str]) {
        let help_output = COMMAND_MAP
            .keys()
            .intersperse(&", !")
            .fold("Available commands:\n!".to_owned(), |output, command| {
                output + command
            });

        msg.channel_id.say(&help_output).unwrap();
    }
}

struct Flip;
impl Command for Flip {
    fn required_arg_count(&self) -> &RangeInclusive<usize> {
        &(0..=0)
    }

    fn exec(&self, _ctx: &Context, msg: &Message, _args: &[&str]) {
        if rand::thread_rng().gen() {
            msg.reply("Heads!").unwrap();
        } else {
            msg.reply("Tails!").unwrap();
        }
    }
}

struct Roll;
impl Command for Roll {
    fn required_arg_count(&self) -> &RangeInclusive<usize> {
        &(0..=1)
    }

    fn exec(&self, _ctx: &Context, msg: &Message, args: &[&str]) {
        let upper_bound = if args.is_empty() {
            6
        } else if args.len() == 1 {
            if let Ok(num) = args[0].parse() {
                num
            } else {
                msg.reply("That is not a number.").unwrap();
                return;
            }
        } else {
            unreachable!();
        };

        if upper_bound <= 0 {
            msg.reply("The upper bound must be a positive number.")
                .unwrap();
        } else {
            let result = rand::thread_rng().gen_range(0, upper_bound) + 1;
            msg.reply(&format!("{}", result)).unwrap();
        }
    }
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate serde_derive;
extern crate docopt;

use docopt::Docopt;

#[macro_use]
extern crate log;
use log::{Record, Level, LevelFilter, Metadata};

use std::collections::{HashSet, HashMap};
use std::fs::File;
use std::io::{BufRead, BufReader, Seek, SeekFrom};
use std::path::Path;

static MY_LOGGER: SimpleLogger = SimpleLogger;
struct SimpleLogger;

impl log::Log for SimpleLogger {
    fn enabled(&self, metadata: &Metadata) -> bool {
        metadata.level() <= Level::Info
    }

    fn log(&self, record: &Record) {
        if self.enabled(record.metadata()) {
            println!("{} - {}", record.level(), record.args());
        }
    }

    fn flush(&self) {}
}

struct CsvDesc<'a> {
    file_path: &'a  Path,
    delimiter:      char,
    quote:          Option<char>,
}

impl<'a> std::fmt::Display for CsvDesc<'a> {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "{} {} {:?}", self.file_path.display(), self.delimiter, self.quote)
    }
}

fn parse_args<'a>(path_arg:         &'a String,
                  delimiter_arg:    &'a String,
                  quote_arg:        &'a String) -> Result<CsvDesc<'a>, &'static str>
{

    let csv_file_path = Path::new(path_arg);

    let csv_delimiter = match delimiter_arg.chars().next() {
                            Some(result) => result,
                            None         => return Err("incorrect delimiter"),
                        };

    let csv_quote     = quote_arg.chars().next();

    Ok(CsvDesc {file_path: &csv_file_path, delimiter: csv_delimiter, quote: csv_quote})
}

fn get_csv_cols(csv_desc: &CsvDesc) -> Result<Vec<String>, String> {

    let csv_file = match File::open(csv_desc.file_path) {
        Err(why) => panic!("couldn't open csv @ {}: {}", csv_desc.file_path.display(), why),
        Ok(file) => file,
    };

    let csv_reader = BufReader::new(csv_file);

    let mut csv_line_iter = csv_reader.lines();

    let csv_header: String = match csv_line_iter.next() {
        Some(result) => match result {
                            Err(why) => return Err(format!("error getting csv header: {}", why)),
                            Ok(header) => header,
                        },
        None         => return Err("csv header reading failed".to_string()),
    };

    let csv_cols: Vec<String> = {
        let cols_iter = csv_header.split(csv_desc.delimiter);
        match csv_desc.quote {
            Some(q) => cols_iter.map(|s| {s.trim_matches(q).to_string()}).collect(),
            None    => cols_iter.map(|s| {s.to_string()}).collect(),
        }
    };

    Ok(csv_cols)
}

fn build_index(csv_desc: &CsvDesc) -> Result<HashMap<String, u64>, String> {
    let mut csv_index = HashMap::new();
    let csv_file = match File::open(csv_desc.file_path) {
        Err(why) => panic!("couldn't open csv @ {}: {}", csv_desc.file_path.display(), why),
        Ok(file) => file,
    };

    let csv_reader = BufReader::new(csv_file);
    let mut csv_line_iter = csv_reader.lines();

    let mut offset_in_file: u64 = 0;
    let mut expected_col_count = 0;
    let mut row_idx = 0;
    loop {

        let csv_row: String = match csv_line_iter.next() {
            Some(result) => match result {
                                Err(why) => return Err(format!("error getting csv row: {}", why)),
                                Ok(header) => header,
                            },
            None         => break,
        };

        let csv_cols: Vec<String> = {
            let cols_iter = csv_row.split(csv_desc.delimiter);
            match csv_desc.quote {
                Some(q) => cols_iter.map(|s| {s.trim_matches(q).to_string()}).collect(),
                None    => cols_iter.map(|s| {s.to_string()}).collect(),
            }
        };

        let curr_col_count = csv_cols.len();
        if curr_col_count == 0 {
            return Err(format!("zero columns in row{}", row_idx));
        }

        if expected_col_count != 0 && expected_col_count != curr_col_count {
            return Err(format!("{} columns in row #{}, {} expected", curr_col_count,
                                                                     row_idx,
                                                                     expected_col_count));
        }
        expected_col_count = curr_col_count;
        row_idx += 1;

        let key = format!("{}{}", csv_cols[0], csv_cols[1]);

        csv_index.insert(key, offset_in_file);
        offset_in_file += (csv_row.len() + 1) as u64;
    }

    Ok(csv_index)
}

fn get_csv_row(csv_desc: &CsvDesc, line_offset: u64) -> Result<Vec<String>, String> {

    let mut csv_file = match File::open(csv_desc.file_path) {
        Err(why) => panic!("couldn't open csv @ {}: {}", csv_desc.file_path.display(), why),
        Ok(file) => file,
    };

    if let Err(e) = csv_file.seek(SeekFrom::Start(line_offset)) {
        return Err(format!("error seeking offset: {}", e));
    }

    let mut csv_reader = BufReader::new(csv_file);
    let mut row_buff = String::new();

    match csv_reader.read_line(&mut row_buff) {
        Ok(_n)  => {
            if row_buff.ends_with("\n") {
                row_buff.pop();
            }
        }
        Err(e) => return Err(format!("error gettig csv row: {}", e)),
    };

    let result: Vec<String> = {
        let cols_iter = row_buff.split(csv_desc.delimiter);
        match csv_desc.quote {
            Some(q) => cols_iter.map(|s| {s.trim_matches(q).to_string()}).collect(),
            None    => cols_iter.map(|s| {s.to_string()}).collect(),
        }
    };

    Ok(result)
}

static USAGE: &'static str = "
Usage: rusty-csv-diff <csv1> <delim1> <quote1> <csv2> <delim2> <quote2>
       rusty-csv-diff --help

Options:
    -h, --help        Show this message.
";

#[derive(Debug, Deserialize)]
struct Args {
    arg_csv1: String,
    arg_delim1: String,
    arg_quote1: String,
    arg_csv2: String,
    arg_delim2: String,
    arg_quote2: String,
}

fn main() {

/*

1. Parse arguments
2. Open CSV files
3. Get columns (cols_N)
4. Get intersection of those two sets of columns(cols_to_compare)
5. Create {column name : column index in cols_N} dicts
6. Create {CSV_col_value : CSV row index in file} dicts, where CSV_col_value is a unique key made of the value of several CSV columns.
   For example, {Alex38 : 76}. In this example a name and age form a unique key for the 76th CSV row.
7. Get intersection of key sets of dicts from step 6 (row_keys_to_compare)
8. Loop through row_keys_to_compare, use dicts from step 6 to get line numbers for CSV files
    8.1 Loop through cols_to_compare, use dicts from step 5 to extract column values from CSV rows(step 8)
    8.2 Compare values

Input parameters: CSV paths, delimiters, quotes

For example, ./main file_1.csv "," "'" file_2.csv " " ""

*/

    /*** 0 ***/
    log::set_logger(&MY_LOGGER).unwrap();
    log::set_max_level(LevelFilter::Error);

    /*** 1 ***/
    let args: Args = Docopt::new(USAGE)
                            .and_then(|d| d.deserialize())
                            .unwrap_or_else(|e| e.exit());

    let csv_desc_1: CsvDesc = match parse_args(&args.arg_csv1, &args.arg_delim1, &args.arg_quote1) {
        Err(why)    => panic!("error parsing arguments for CSV #1: {}", why),
        Ok(result)  => result,
    };

    let csv_desc_2: CsvDesc = match parse_args(&args.arg_csv2, &args.arg_delim2, &args.arg_quote2) {
        Err(why)    => panic!("error parsing arguments for CSV #2: {}", why),
        Ok(result)  => result,
    };

    /*** 2&3 ***/
    let csv_cols_1: Vec<String> = match get_csv_cols(&csv_desc_1) {
        Err(why) => panic!("couldn't get columns: {}", why),
        Ok(cols) => cols,
    };

    let csv_cols_2: Vec<String> = match get_csv_cols(&csv_desc_2) {
        Err(why) => panic!("couldn't get columns: {}", why),
        Ok(cols) => cols,
    };

    /*** 5 ***/
    let mut csv_col_index_1 = HashMap::new();
    for i in 0..csv_cols_1.len() {
        let key = csv_cols_1[i].clone();
        if csv_col_index_1.contains_key(&key) {
            panic!("duplicate column found in CSV #1: {}", key);
        };
        csv_col_index_1.insert(key, i);
    }
    info!("{:?}", csv_col_index_1);

    let mut csv_col_index_2 = HashMap::new();
    for i in 0..csv_cols_2.len() {
        let key = csv_cols_2[i].clone();
        if csv_col_index_2.contains_key(&key) {
            panic!("duplicate column found in CSV #1: {}", key);
        };
        csv_col_index_2.insert(key, i);
    }
    info!("{:?}", csv_col_index_2);

    /*** 4 ***/
    let mut cols_to_compare = HashSet::new();
    for col_1 in csv_col_index_1.keys() {
        if csv_col_index_2.contains_key(col_1) {
            cols_to_compare.insert(col_1);
        };
    }
    info!("{:?}", cols_to_compare);

    /*** 6 ***/
    // let's assume that the unique key is (col_0 + col_1)
    let csv_index_1 = match build_index(&csv_desc_1) {
        Err(why)  => panic!("failed building index #1: {}", why),
        Ok(index) => index,
    };

    let csv_index_2 = match build_index(&csv_desc_2) {
        Err(why)  => panic!("failed building index #2: {}", why),
        Ok(index) => index,
    };

    /*** 7 ***/
    let mut row_keys_to_compare = HashSet::new();
    for key_1 in csv_index_1.keys() {
        if csv_index_2.contains_key(key_1) {
            row_keys_to_compare.insert(key_1);
        };
    }
    info!("{:?}", row_keys_to_compare);

    /*** 8 ***/
    for row_key in row_keys_to_compare {

        let index_1 = *csv_index_1.get(row_key).unwrap();
        let index_2 = *csv_index_2.get(row_key).unwrap();

        let row_1 = match get_csv_row(&csv_desc_1, index_1) {
            Ok(row) => row,
            Err(e)  => panic!("failed getting csv row #1: {}", e),
        };

        let row_2 = match get_csv_row(&csv_desc_2, index_2) {
            Ok(row) => row,
            Err(e)  => panic!("failed getting csv row #2: {}", e),
        };

        info!("comparing {}:", row_key);
        info!("line #1: {:?}", row_1);
        info!("line #2: {:?}", row_2);

        for col in &cols_to_compare {

            let col_index_1 = *csv_col_index_1.get(*col).unwrap();
            let col_index_2 = *csv_col_index_2.get(*col).unwrap();

            info!("column {}, index_1={}, index_2={}", col, col_index_1, col_index_2);

            if row_1[col_index_1] != row_2[col_index_2] {
                println!("found a difference for {}, {}: {} / {}", row_key, col, row_1[col_index_1], row_2[col_index_2]);
            }
        }

    }

}
<commit_msg>Run "cargo fmt"<commit_after>extern crate docopt;
#[macro_use]
extern crate serde_derive;

use docopt::Docopt;

#[macro_use]
extern crate log;
use log::{Level, LevelFilter, Metadata, Record};

use std::collections::{HashMap, HashSet};
use std::fs::File;
use std::io::{BufRead, BufReader, Seek, SeekFrom};
use std::path::Path;

static MY_LOGGER: SimpleLogger = SimpleLogger;
struct SimpleLogger;

impl log::Log for SimpleLogger {
    fn enabled(&self, metadata: &Metadata) -> bool {
        metadata.level() <= Level::Info
    }

    fn log(&self, record: &Record) {
        if self.enabled(record.metadata()) {
            println!("{} - {}", record.level(), record.args());
        }
    }

    fn flush(&self) {}
}

struct CsvDesc<'a> {
    file_path: &'a Path,
    delimiter: char,
    quote: Option<char>,
}

impl<'a> std::fmt::Display for CsvDesc<'a> {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(
            f,
            "{} {} {:?}",
            self.file_path.display(),
            self.delimiter,
            self.quote
        )
    }
}

fn parse_args<'a>(
    path_arg: &'a String,
    delimiter_arg: &'a String,
    quote_arg: &'a String,
) -> Result<CsvDesc<'a>, &'static str> {
    let csv_file_path = Path::new(path_arg);

    let csv_delimiter = match delimiter_arg.chars().next() {
        Some(result) => result,
        None => return Err("incorrect delimiter"),
    };

    let csv_quote = quote_arg.chars().next();

    Ok(CsvDesc {
        file_path: &csv_file_path,
        delimiter: csv_delimiter,
        quote: csv_quote,
    })
}

fn get_csv_cols(csv_desc: &CsvDesc) -> Result<Vec<String>, String> {
    let csv_file = match File::open(csv_desc.file_path) {
        Err(why) => panic!(
            "couldn't open csv @ {}: {}",
            csv_desc.file_path.display(),
            why
        ),
        Ok(file) => file,
    };

    let csv_reader = BufReader::new(csv_file);

    let mut csv_line_iter = csv_reader.lines();

    let csv_header: String = match csv_line_iter.next() {
        Some(result) => match result {
            Err(why) => return Err(format!("error getting csv header: {}", why)),
            Ok(header) => header,
        },
        None => return Err("csv header reading failed".to_string()),
    };

    let csv_cols: Vec<String> = {
        let cols_iter = csv_header.split(csv_desc.delimiter);
        match csv_desc.quote {
            Some(q) => cols_iter.map(|s| s.trim_matches(q).to_string()).collect(),
            None => cols_iter.map(|s| s.to_string()).collect(),
        }
    };

    Ok(csv_cols)
}

fn build_index(csv_desc: &CsvDesc) -> Result<HashMap<String, u64>, String> {
    let mut csv_index = HashMap::new();
    let csv_file = match File::open(csv_desc.file_path) {
        Err(why) => panic!(
            "couldn't open csv @ {}: {}",
            csv_desc.file_path.display(),
            why
        ),
        Ok(file) => file,
    };

    let csv_reader = BufReader::new(csv_file);
    let mut csv_line_iter = csv_reader.lines();

    let mut offset_in_file: u64 = 0;
    let mut expected_col_count = 0;
    let mut row_idx = 0;
    loop {
        let csv_row: String = match csv_line_iter.next() {
            Some(result) => match result {
                Err(why) => return Err(format!("error getting csv row: {}", why)),
                Ok(header) => header,
            },
            None => break,
        };

        let csv_cols: Vec<String> = {
            let cols_iter = csv_row.split(csv_desc.delimiter);
            match csv_desc.quote {
                Some(q) => cols_iter.map(|s| s.trim_matches(q).to_string()).collect(),
                None => cols_iter.map(|s| s.to_string()).collect(),
            }
        };

        let curr_col_count = csv_cols.len();
        if curr_col_count == 0 {
            return Err(format!("zero columns in row{}", row_idx));
        }

        if expected_col_count != 0 && expected_col_count != curr_col_count {
            return Err(format!(
                "{} columns in row #{}, {} expected",
                curr_col_count, row_idx, expected_col_count
            ));
        }
        expected_col_count = curr_col_count;
        row_idx += 1;

        let key = format!("{}{}", csv_cols[0], csv_cols[1]);

        csv_index.insert(key, offset_in_file);
        offset_in_file += (csv_row.len() + 1) as u64;
    }

    Ok(csv_index)
}

fn get_csv_row(csv_desc: &CsvDesc, line_offset: u64) -> Result<Vec<String>, String> {
    let mut csv_file = match File::open(csv_desc.file_path) {
        Err(why) => panic!(
            "couldn't open csv @ {}: {}",
            csv_desc.file_path.display(),
            why
        ),
        Ok(file) => file,
    };

    if let Err(e) = csv_file.seek(SeekFrom::Start(line_offset)) {
        return Err(format!("error seeking offset: {}", e));
    }

    let mut csv_reader = BufReader::new(csv_file);
    let mut row_buff = String::new();

    match csv_reader.read_line(&mut row_buff) {
        Ok(_n) => {
            if row_buff.ends_with("\n") {
                row_buff.pop();
            }
        }
        Err(e) => return Err(format!("error gettig csv row: {}", e)),
    };

    let result: Vec<String> = {
        let cols_iter = row_buff.split(csv_desc.delimiter);
        match csv_desc.quote {
            Some(q) => cols_iter.map(|s| s.trim_matches(q).to_string()).collect(),
            None => cols_iter.map(|s| s.to_string()).collect(),
        }
    };

    Ok(result)
}

static USAGE: &'static str = "
Usage: rusty-csv-diff <csv1> <delim1> <quote1> <csv2> <delim2> <quote2>
       rusty-csv-diff --help

Options:
    -h, --help        Show this message.
";

#[derive(Debug, Deserialize)]
struct Args {
    arg_csv1: String,
    arg_delim1: String,
    arg_quote1: String,
    arg_csv2: String,
    arg_delim2: String,
    arg_quote2: String,
}

fn main() {
    /*

1. Parse arguments
2. Open CSV files
3. Get columns (cols_N)
4. Get intersection of those two sets of columns(cols_to_compare)
5. Create {column name : column index in cols_N} dicts
6. Create {CSV_col_value : CSV row index in file} dicts,
   where CSV_col_value is a unique key made of the value of several CSV columns.
   For example, {Alex38 : 76}. Here the name and age form a unique key for the 76th CSV row.
7. Get intersection of key sets of dicts from step 6 (row_keys_to_compare)
8. Loop through row_keys_to_compare, use dicts from step 6 to get line numbers for CSV files
    8.1 Loop through cols_to_compare, use dicts from step 5 to extract column values from CSV rows
    8.2 Compare values

Input parameters: CSV paths, delimiters, quotes

For example, ./main file_1.csv "," "'" file_2.csv " " ""

*/

    /*** 0 ***/
    log::set_logger(&MY_LOGGER).unwrap();
    log::set_max_level(LevelFilter::Error);

    /*** 1 ***/
    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.deserialize())
        .unwrap_or_else(|e| e.exit());

    let csv_desc_1: CsvDesc = match parse_args(&args.arg_csv1, &args.arg_delim1, &args.arg_quote1) {
        Err(why) => panic!("error parsing arguments for CSV #1: {}", why),
        Ok(result) => result,
    };

    let csv_desc_2: CsvDesc = match parse_args(&args.arg_csv2, &args.arg_delim2, &args.arg_quote2) {
        Err(why) => panic!("error parsing arguments for CSV #2: {}", why),
        Ok(result) => result,
    };

    /*** 2&3 ***/
    let csv_cols_1: Vec<String> = match get_csv_cols(&csv_desc_1) {
        Err(why) => panic!("couldn't get columns: {}", why),
        Ok(cols) => cols,
    };

    let csv_cols_2: Vec<String> = match get_csv_cols(&csv_desc_2) {
        Err(why) => panic!("couldn't get columns: {}", why),
        Ok(cols) => cols,
    };

    /*** 5 ***/
    let mut csv_col_index_1 = HashMap::new();
    for i in 0..csv_cols_1.len() {
        let key = csv_cols_1[i].clone();
        if csv_col_index_1.contains_key(&key) {
            panic!("duplicate column found in CSV #1: {}", key);
        };
        csv_col_index_1.insert(key, i);
    }
    info!("{:?}", csv_col_index_1);

    let mut csv_col_index_2 = HashMap::new();
    for i in 0..csv_cols_2.len() {
        let key = csv_cols_2[i].clone();
        if csv_col_index_2.contains_key(&key) {
            panic!("duplicate column found in CSV #1: {}", key);
        };
        csv_col_index_2.insert(key, i);
    }
    info!("{:?}", csv_col_index_2);

    /*** 4 ***/
    let mut cols_to_compare = HashSet::new();
    for col_1 in csv_col_index_1.keys() {
        if csv_col_index_2.contains_key(col_1) {
            cols_to_compare.insert(col_1);
        };
    }
    info!("{:?}", cols_to_compare);

    /*** 6 ***/
    // let's assume that the unique key is (col_0 + col_1)
    let csv_index_1 = match build_index(&csv_desc_1) {
        Err(why) => panic!("failed building index #1: {}", why),
        Ok(index) => index,
    };

    let csv_index_2 = match build_index(&csv_desc_2) {
        Err(why) => panic!("failed building index #2: {}", why),
        Ok(index) => index,
    };

    /*** 7 ***/
    let mut row_keys_to_compare = HashSet::new();
    for key_1 in csv_index_1.keys() {
        if csv_index_2.contains_key(key_1) {
            row_keys_to_compare.insert(key_1);
        };
    }
    info!("{:?}", row_keys_to_compare);

    /*** 8 ***/
    for row_key in row_keys_to_compare {
        let index_1 = *csv_index_1.get(row_key).unwrap();
        let index_2 = *csv_index_2.get(row_key).unwrap();

        let row_1 = match get_csv_row(&csv_desc_1, index_1) {
            Ok(row) => row,
            Err(e) => panic!("failed getting csv row #1: {}", e),
        };

        let row_2 = match get_csv_row(&csv_desc_2, index_2) {
            Ok(row) => row,
            Err(e) => panic!("failed getting csv row #2: {}", e),
        };

        info!("comparing {}:", row_key);
        info!("line #1: {:?}", row_1);
        info!("line #2: {:?}", row_2);

        for col in &cols_to_compare {
            let col_index_1 = *csv_col_index_1.get(*col).unwrap();
            let col_index_2 = *csv_col_index_2.get(*col).unwrap();

            info!(
                "column {}, index_1={}, index_2={}",
                col, col_index_1, col_index_2
            );

            if row_1[col_index_1] != row_2[col_index_2] {
                println!(
                    "found a difference for {}, {}: {} / {}",
                    row_key, col, row_1[col_index_1], row_2[col_index_2]
                );
            }
        }
    }
}
<|endoftext|>


<commit_before>use std::fmt;
use std::fmt::Display;
use std::iter;

struct Tower {
    weight : usize,
}

impl Display for Tower {
    fn fmt(&self, f : &mut fmt::Formatter) -> fmt::Result {
        f.write_fmt(format_args!("[{0}]", iter::repeat("#").take(self.weight).collect::<String>()))
    }
}

struct Rod {
    stack : Vec<Tower>,
}

struct Desk {
    rods : Vec<Rod>,
}

impl Display for Desk {
    fn fmt(&self, f : &mut fmt::Formatter) -> fmt::Result {
        let max_weight : usize =
            self.rods.iter()
                .map(|r| r.stack.iter().map(|s| s.weight).max().unwrap_or(0))
                .max().unwrap_or(0);
        let height = max_weight + 1;

        let dup = |s: &str, n: usize| iter::repeat(s).take(n).collect::<String>();
        let mut display : Vec<String> = Vec::new();

        for i in 0..height {
            let mut s : String = "  ".to_string();

            for rod in &self.rods {
                let stower: Option<&Tower> = rod.stack.get(i);
                let tmp = match stower {
                    None => format!("{0}|{0}", dup(" ", max_weight + 1)),
                    Some(tower) => format!("{1}[{0}|{0}]{1}", dup("#", tower.weight), dup(" ", max_weight - tower.weight)),
                };
                s.push_str(" ");
                s.push_str(&tmp);
            }

            s.push_str("  ");
            s.push_str("\n");
            display.push(s);
        }

        display.reverse();
        f.write_fmt(format_args!("{0}", display.connect("")))
    }
}

fn main() {
    let desk1 = Desk {
        rods : vec![
            Rod { stack:  vec![ Tower {weight: 4, }, Tower {weight: 3, }, ], },
            Rod { stack:  vec![ Tower {weight: 2, }, ], },
            Rod { stack:  vec![ Tower {weight: 1, }, ], },
        ],
    };
    println!("{0}", desk1);

    let desk2 = Desk {
        rods : vec![
            Rod { stack:  vec![ Tower {weight: 4, }, Tower {weight: 2, }, ], },
            Rod { stack:  vec![ Tower {weight: 3, }, ], },
            Rod { stack:  vec![ Tower {weight: 1, }, ], },
        ],
    };
    println!("{0}", desk2);

    let desk3 = Desk {
        rods : vec![
            Rod { stack:  vec![ Tower {weight: 4, }, Tower {weight: 2, }, ], },
            Rod { stack:  vec![ Tower {weight: 3, }, Tower {weight: 1 }, ], },
        ],
    };
    println!("{0}", desk3);
}
<commit_msg>Add constructors and come skeletons<commit_after>use std::fmt;
use std::fmt::Display;
use std::iter;

struct Tower {
    weight : usize,
}

impl Tower {
    pub fn new(weight: usize) -> Tower {
        Tower { weight : weight }
    }

    pub fn weight(&self) -> usize {
        self.weight
    }
}

impl Display for Tower {
    fn fmt(&self, f : &mut fmt::Formatter) -> fmt::Result {
        f.write_fmt(format_args!("[{0}]", iter::repeat("#").take(self.weight()).collect::<String>()))
    }
}

struct Rod {
    stack : Vec<Tower>,
}

impl Rod {
    pub fn new(sizes : &[usize]) -> Rod {
        Rod { stack : sizes.iter().map(|x| Tower::new(*x) ).collect::<Vec<_>>() }
    }

    fn can_take_tower(&self, tower : &Tower) -> bool {
        match self.stack.last() {
            None => true,
            Some(t) => t.weight() > tower.weight(),
        }
    }

    pub fn can_take(&self, other: &Rod) -> bool {
        false // self.can_take_tower(&other.stack.last())
    }

    pub fn swap(& mut self, other: &mut Rod) -> &mut Self {
        self
    }

}

struct Desk {
    rods : Vec<Rod>,
}

impl Desk {
    pub fn new(rods: &[&[usize]]) -> Desk {
        Desk { rods : rods.iter().map(|x| Rod::new(*x) ).collect::<Vec<_>>() }
    }
}

impl Display for Desk {
    fn fmt(&self, f : &mut fmt::Formatter) -> fmt::Result {
        let max_weight : usize =
            self.rods.iter()
                .map(|r| r.stack.iter().map(|s| s.weight()).max().unwrap_or(0))
                .max().unwrap_or(0);
        let height = max_weight + 1;

        let dup = |s: &str, n: usize| iter::repeat(s).take(n).collect::<String>();
        let mut display : Vec<String> = Vec::new();

        for i in 0..height {
            let mut s : String = "  ".to_string();

            for rod in &self.rods {
                let stower: Option<&Tower> = rod.stack.get(i);
                let tmp = match stower {
                    None => format!("{0}|{0}", dup(" ", max_weight + 1)),
                    Some(tower) => format!("{1}[{0}|{0}]{1}", dup("#", tower.weight()), dup(" ", max_weight - tower.weight())),
                };
                s.push_str(" ");
                s.push_str(&tmp);
            }

            s.push_str("  ");
            s.push_str("\n");
            display.push(s);
        }

        display.reverse();
        f.write_fmt(format_args!("{0}", display.connect("")))
    }
}

fn main() {
    let desk1 = Desk::new(&[
        &[4, 3],
        &[2, 1]
    ]);
    println!("{0}", desk1);

}
<|endoftext|>


<commit_before>#[macro_use]
extern crate clap;
extern crate doapi;
#[cfg(feature = "color")]
extern crate ansi_term;

use clap::{App, Arg, ArgGroup, ArgMatches, SubCommand};

use doapi::request::DnsRecType;

mod cli;
mod config;
mod message;

use config::Config;
use cli::{list, account, dns, domains, droplet, droplets, image, ssh_keys};

fn get_auth_token(m: &ArgMatches) -> String {
    let tok = if let Some(auth_tok) = m.value_of("token") {
            auth_tok.to_owned()
    } else {
        std::env::vars().filter(|&(ref k, _)| k == "DO_AUTH_TOKEN")
                        .map(|(_, v)| v.clone() )
                        .next().unwrap_or("".to_owned())
    };
    if tok.len() != 64 {
        println!("No DigitalOcean Auth Token found.\n\n\
        Use `docli --token <token>` or set the DO_AUTH_TOKEN environment variable and try again");
        std::process::exit(1);
    }
    tok
}

fn main() {
    let dns_types = DnsRecType::variants();
    let dns_args = "-n --name [name]         'Name of the DNS record'
                    -d --data [data]         'Data for the DNS record'
                    -P --priority [priority] 'The priority to set'
                    -p --port [port]         'The port to use'
                    -w --weight [weight]     'The weight value'";
    let noconfirm = "--noconfirm 'Don't confirm, just do it'";
    let m = App::new("docli")
        .version(&format!("v{}", crate_version!()))
        .about("A utility for managing DigitalOcean infrastructure")
        .author("Kevin K. <kbknapp@gmail.com>")
        .subcommand_required(true)
        .args_from_usage("-t --token [token] 'Digital Ocean Auth Token (Defaults to contents \
                                              of DO_AUTH_TOKEN env var if omitted)'")
        .arg(Arg::from_usage("-v --verbose   'Displays the request being sent to server and JSON reply'")
            .global(true))
        .arg(Arg::from_usage("-n --nosend        'Does NOT send request over the network (useful \
                                                  with --verbose)'")
            .global(true))
        .subcommand(SubCommand::with_name("list")
            .about("Get information from DigitalOcean about various sections")
            .subcommand_required(true)
            .subcommand(SubCommand::with_name("regions")
                .about("Displays available regions"))
            .subcommand(SubCommand::with_name("sizes")
                .about("Displays available droplet sizes"))
            .subcommand(SubCommand::with_name("images")
                .about("Displays droplet images")
                .args_from_usage("--distributions   'Displays all distrobution images'
                                  --applications    'Displays all application images'
                                  --private         'Displays all private user images'
                                  --available       'Displays all available images (Default)'")
                .arg_group(ArgGroup::with_name("images").add_all(vec!["distrobutions",
                                                                      "applications",
                                                                      "private",
                                                                      "available"])))
            .subcommand(SubCommand::with_name("ssh-keys")
                .about("Displays available SSH keys"))
            .subcommand(SubCommand::with_name("droplets")
                .about("Displays available droplets"))
            .subcommand(SubCommand::with_name("domains")
                .about("Displays available domains"))
            .subcommand(SubCommand::with_name("account-actions")
                .about("Displays all current and previous account actions")))
        .subcommand(SubCommand::with_name("account")
            .about("Show account information and actions")
            .subcommand(SubCommand::with_name("actions")
                .about("Lists all the account actions"))
            .subcommand(SubCommand::with_name("action")
                .about("Gets information about a particular account action")
                .arg_from_usage("<id> 'The action ID to display'")))
        .subcommand(SubCommand::with_name("domains")
            .about("Manage domains")
            .subcommand(SubCommand::with_name("create")
                .about("Creates a new domain")
                .args_from_usage("<name> 'The name for the domain'
                                  <ip>   'The IP address of the domain'"))
            .subcommand(SubCommand::with_name("show-domain")
                .about("Gets information on a particular domain")
                .arg_from_usage("<name> 'The name of the domain to get'"))
            .subcommand(SubCommand::with_name("delete")
                .about("Deletes a domain")
                .arg_from_usage(noconfirm)
                .arg_from_usage("<name> 'The domain to delete'")))
        .subcommand(SubCommand::with_name("dns")
            .about("Manage DNS records on a specific domain")
            .subcommand_required(true)
            .arg_from_usage("<domain> 'The domain name tha the record applies to'")
            .subcommand(SubCommand::with_name("create-record")
                .about("Creates a new DNS record for the domain")
                .arg(Arg::from_usage("<type> 'The type of DNS record to create'")
                    .possible_values(dns_types.iter()))
                .args_from_usage(dns_args))
            .subcommand(SubCommand::with_name("records")
                .about("Lists all DNS records on the domain"))
            .subcommand(SubCommand::with_name("record")
                .about("Displays information on a specific DNS record")
                .arg_from_usage("<id>   'The DNS record ID to retrieve info on'"))
            .subcommand(SubCommand::with_name("update-record")
                .about("Updates a DNS record on the domain")
                .arg_from_usage(noconfirm)
                .arg_from_usage("<id> 'The DNS record ID to update'")
                .arg(Arg::from_usage("-t --type [type] 'The type of DNS record to update to'")
                    .possible_values(dns_types.iter()))
                .args_from_usage(dns_args))
            .subcommand(SubCommand::with_name("delete-record")
                .about("Deletes a DNS record")
                .arg_from_usage(noconfirm)
                .arg_from_usage("<id>   'The DSN record ID to delete'")))
        .subcommand(SubCommand::with_name("droplets")
            .about("Manage droplets")
            .subcommand(SubCommand::with_name("neighbors")
                .about("Displays all droplets running on the same physical hardware"))
            .subcommand(SubCommand::with_name("upgrades")
                .about("Displays all droplets with pending upgrades"))
            .subcommand(SubCommand::with_name("create")
                .about("Creates a new droplet")
                .args_from_usage("<name>                      'The name of the droplet'
                                  -r --region <region>        'The region of the droplet'
                                  -s --size <size>            'The size of the droplet'
                                  -i --image <image>          'The image to use'
                                  -k --ssh-keys [keys]...     'Any ssh keys to add'
                                  --backups                   'Allow backups'
                                  --ipv6                      'Use IPv6'
                                  --private-networking        'Use private networking'
                                  -u --user-data [data]       'User data'")))
        .subcommand(SubCommand::with_name("droplet")
            .about("Manage a specific droplet")
            .arg_from_usage("<id> 'The droplet ID to use'")
            .subcommand(SubCommand::with_name("kernels")
                .about("Display all available kernels"))
            .subcommand(SubCommand::with_name("snapshots")
                .about("Display all snapshots"))
            .subcommand(SubCommand::with_name("backups")
                .about("Display all backups"))
            .subcommand(SubCommand::with_name("actions")
                .about("Display all current and previous actions"))
            .subcommand(SubCommand::with_name("neighbors")
                .about("Display all droplets running on the same physical hardware"))
            .subcommand(SubCommand::with_name("delete")
                .about("Deletes a droplet")
                .arg_from_usage(noconfirm))
            .subcommand(SubCommand::with_name("disable-backups")
                .about("Disables backups for a droplet"))
            .subcommand(SubCommand::with_name("reboot")
                .about("Reboots a droplet"))
            .subcommand(SubCommand::with_name("power-cycle")
                .about("Performs a power cycle on a droplet"))
            .subcommand(SubCommand::with_name("shutdown")
                .about("Shutsdown a droplet"))
            .subcommand(SubCommand::with_name("power-off")
                .about("Powers a droplet off"))
            .subcommand(SubCommand::with_name("power-on")
                .about("Turns on a droplet"))
            .subcommand(SubCommand::with_name("restore")
                .about("Restores a droplet from an image")
                .arg_from_usage(noconfirm)
                .arg_from_usage("<image> 'The image to restore to'"))
            .subcommand(SubCommand::with_name("reset-password")
                .about("Resets the root password for a droplet"))
            .subcommand(SubCommand::with_name("resize")
                .about("Resizes a droplet")
                .args_from_usage("--disk 'Resizes the disk'
                                  <size> 'The new size to use (i.e. 15gb)'"))
            .subcommand(SubCommand::with_name("rebuild")
                .about("Rebuilds a droplet from an image")
                .arg_from_usage(noconfirm)
                .arg_from_usage("<image> 'The image ID or slug to use'"))
            .subcommand(SubCommand::with_name("rename")
                .about("Renames a droplet")
                .arg_from_usage("<name> 'The new name to use'")
                .arg_from_usage(noconfirm))
            .subcommand(SubCommand::with_name("change-kernel")
                .about("Changes the kernel of a droplet")
                .arg_from_usage(noconfirm)
                .arg_from_usage("<kernel_id> 'The kernel ID to use'"))
            .subcommand(SubCommand::with_name("enable-ipv6")
                .about("Enables IPv6 addresses"))
            .subcommand(SubCommand::with_name("enable-private-networking")
                .about("Enables private networking"))
            .subcommand(SubCommand::with_name("snapshot")
                .arg_from_usage("<name> 'What to name the new snapshot image'")
                .about("Creates a snapshot of a droplet"))
            .subcommand(SubCommand::with_name("action")
                .about("Displays a specific action for a droplet")
                .arg_from_usage("<action_id> 'The action ID to display'"))
            .subcommand(SubCommand::with_name("upgrade")
                .arg_from_usage(noconfirm)
                .about("Upgrades a droplet")))
        .subcommand(SubCommand::with_name("image")
            .about("Manage images")
            .arg_from_usage("<id> 'The image ID or slug to use (not all commands support using a slug)'")
            .subcommand(SubCommand::with_name("actions")
                .about("Lists all previous and current actions for an image"))
            .subcommand(SubCommand::with_name("rename")
                .arg_from_usage(noconfirm)
                .args_from_usage("<name> 'The new name of the image'")
                .about("Changes the name of a particular image"))
            .subcommand(SubCommand::with_name("delete")
                .about("Deletes an image")
                .arg_from_usage(noconfirm))
            .subcommand(SubCommand::with_name("transfer")
                .about("Transfers an image to a new region")
                .arg_from_usage(noconfirm)
                .arg_from_usage("<region> 'The region to transfer to'"))
            .subcommand(SubCommand::with_name("convert")
                .about("Converts a an image (i.e. from a snapshot to a backup)"))
            .subcommand(SubCommand::with_name("action")
                .about("Displays a particular action of an image")
                .arg_from_usage("<action_id> 'The action ID to display'")))
        .subcommand(SubCommand::with_name("ssh-keys")
            .about("Manage SSH keys")
            .subcommand(SubCommand::with_name("create")
                .about("Creatse a new SSH key")
                .args_from_usage("<name>       'The name of the SSH key'
                                  <public_key> 'The public key of the SSH key'"))
            .subcommand(SubCommand::with_name("key")
                .about("Displays information on a particular key")
                .args_from_usage("<id> 'The key ID or finger print of the key to display'"))
            .subcommand(SubCommand::with_name("rename")
                .about("Renames a particular SSH key")
                .arg_from_usage(noconfirm)
                .args_from_usage("<id>   'The key ID or finger print of the key to update'
                                  <name> 'The new name to use'"))
            .subcommand(SubCommand::with_name("destroy")
                .about("Destroys a particular SSH key")
                .arg_from_usage(noconfirm)
                .args_from_usage("<id> 'The key ID or finger print of the key to destroy'")))
        .get_matches();

    let mut cfg = Config {
        verbose: m.is_present("verbose"),
        no_send: m.is_present("nosend"),
        auth: get_auth_token(&m)
    };

    match m.subcommand() {
        ("account", Some(m))  => account::run(m, &mut cfg),
        ("domains", Some(m))  => domains::run(m, &mut cfg),
        ("dns", Some(m))      => dns::run(m, &mut cfg),
        ("droplets", Some(m)) => droplets::run(m, &mut cfg),
        ("droplet", Some(m))  => droplet::run(m, &mut cfg),
        ("image", Some(m))    => image::run(m, &mut cfg),
        ("ssh-keys", Some(m)) => ssh_keys::run(m, &mut cfg),
        ("list", Some(m))     => list::run(m, &mut cfg),
        _                     => ()
    }
}
<commit_msg>imp: fixes and verifies droplet commands<commit_after>#[macro_use]
extern crate clap;
extern crate doapi;
#[cfg(feature = "color")]
extern crate ansi_term;

use clap::{App, Arg, ArgGroup, ArgMatches, SubCommand};

use doapi::request::DnsRecType;

mod cli;
mod config;
mod message;

use config::Config;
use cli::{list, account, dns, domains, droplet, droplets, image, ssh_keys};

fn get_auth_token(m: &ArgMatches) -> String {
    let tok = if let Some(auth_tok) = m.value_of("token") {
            auth_tok.to_owned()
    } else {
        std::env::vars().filter(|&(ref k, _)| k == "DO_AUTH_TOKEN")
                        .map(|(_, v)| v.clone() )
                        .next().unwrap_or("".to_owned())
    };
    if tok.len() != 64 {
        println!("No DigitalOcean Auth Token found.\n\n\
        Use `docli --token <token>` or set the DO_AUTH_TOKEN environment variable and try again");
        std::process::exit(1);
    }
    tok
}

fn main() {
    let dns_types = DnsRecType::variants();
    let dns_args = "-n --name [name]         'Name of the DNS record'
                    -d --data [data]         'Data for the DNS record'
                    -P --priority [priority] 'The priority to set'
                    -p --port [port]         'The port to use'
                    -w --weight [weight]     'The weight value'";
    let noconfirm = "--noconfirm 'Don't confirm, just do it'";
    let m = App::new("docli")
        .version(&format!("v{}", crate_version!()))
        .about("A utility for managing DigitalOcean infrastructure")
        .author("Kevin K. <kbknapp@gmail.com>")
        .subcommand_required(true)
        .args_from_usage("-t --token [token] 'Digital Ocean Auth Token (Defaults to contents \
                                              of DO_AUTH_TOKEN env var if omitted)'")
        .arg(Arg::from_usage("-v --verbose   'Displays the request being sent to server and JSON reply'")
            .global(true))
        .arg(Arg::from_usage("-n --nosend        'Does NOT send request over the network (useful \
                                                  with --verbose)'")
            .global(true))
        .subcommand(SubCommand::with_name("list")
            .about("Get information from DigitalOcean about various sections")
            .subcommand_required(true)
            .subcommand(SubCommand::with_name("regions")
                .about("Displays available regions"))
            .subcommand(SubCommand::with_name("sizes")
                .about("Displays available droplet sizes"))
            .subcommand(SubCommand::with_name("images")
                .about("Displays droplet images")
                .args_from_usage("--distributions   'Displays all distrobution images'
                                  --applications    'Displays all application images'
                                  --private         'Displays all private user images'
                                  --available       'Displays all available images (Default)'")
                .arg_group(ArgGroup::with_name("images").add_all(vec!["distrobutions",
                                                                      "applications",
                                                                      "private",
                                                                      "available"])))
            .subcommand(SubCommand::with_name("ssh-keys")
                .about("Displays available SSH keys"))
            .subcommand(SubCommand::with_name("droplets")
                .about("Displays available droplets"))
            .subcommand(SubCommand::with_name("domains")
                .about("Displays available domains"))
            .subcommand(SubCommand::with_name("account-actions")
                .about("Displays all current and previous account actions")))
        .subcommand(SubCommand::with_name("account")
            .about("Show account information and actions")
            .subcommand(SubCommand::with_name("actions")
                .about("Lists all the account actions"))
            .subcommand(SubCommand::with_name("action")
                .about("Gets information about a particular account action")
                .arg_from_usage("<id> 'The action ID to display'")))
        .subcommand(SubCommand::with_name("domains")
            .about("Manage domains")
            .subcommand(SubCommand::with_name("create")
                .about("Creates a new domain")
                .args_from_usage("<name> 'The name for the domain'
                                  <ip>   'The IP address of the domain'"))
            .subcommand(SubCommand::with_name("show-domain")
                .about("Gets information on a particular domain")
                .arg_from_usage("<name> 'The name of the domain to get'"))
            .subcommand(SubCommand::with_name("delete")
                .about("Deletes a domain")
                .arg_from_usage(noconfirm)
                .arg_from_usage("<name> 'The domain to delete'")))
        .subcommand(SubCommand::with_name("dns")
            .about("Manage DNS records on a specific domain")
            .subcommand_required(true)
            .arg_from_usage("<domain> 'The domain name tha the record applies to'")
            .subcommand(SubCommand::with_name("create-record")
                .about("Creates a new DNS record for the domain")
                .arg(Arg::from_usage("<type> 'The type of DNS record to create'")
                    .possible_values(dns_types.iter()))
                .args_from_usage(dns_args))
            .subcommand(SubCommand::with_name("records")
                .about("Lists all DNS records on the domain"))
            .subcommand(SubCommand::with_name("record")
                .about("Displays information on a specific DNS record")
                .arg_from_usage("<id>   'The DNS record ID to retrieve info on'"))
            .subcommand(SubCommand::with_name("update-record")
                .about("Updates a DNS record on the domain")
                .arg_from_usage(noconfirm)
                .arg_from_usage("<id> 'The DNS record ID to update'")
                .arg(Arg::from_usage("-t --type [type] 'The type of DNS record to update to'")
                    .possible_values(dns_types.iter()))
                .args_from_usage(dns_args))
            .subcommand(SubCommand::with_name("delete-record")
                .about("Deletes a DNS record")
                .arg_from_usage(noconfirm)
                .arg_from_usage("<id>   'The DSN record ID to delete'")))
        .subcommand(SubCommand::with_name("droplets")
            .about("Manage droplets")
            .subcommand(SubCommand::with_name("neighbors")
                .about("Displays all droplets running on the same physical hardware"))
            .subcommand(SubCommand::with_name("upgrades")
                .about("Displays all droplets with pending upgrades"))
            .subcommand(SubCommand::with_name("create")
                .about("Creates a new droplet")
                .args_from_usage("<name>                      'The name of the droplet'
                                  -r --region <region>        'The region of the droplet'
                                  -s --size <size>            'The size of the droplet'
                                  -i --image <image>          'The image to use'
                                  -k --ssh-keys [keys]...     'Any ssh keys to add'
                                  --backups                   'Allow backups'
                                  --ipv6                      'Use IPv6'
                                  --private-networking        'Use private networking'
                                  -u --user-data [data]       'User data'")))
        .subcommand(SubCommand::with_name("droplet")
            .about("Manage a specific droplet")
            .arg_from_usage("<id> 'The droplet ID to use'")
            .subcommand(SubCommand::with_name("kernels")
                .about("Display all available kernels"))
            .subcommand(SubCommand::with_name("snapshots")
                .about("Display all snapshots"))
            .subcommand(SubCommand::with_name("backups")
                .about("Display all backups"))
            .subcommand(SubCommand::with_name("actions")
                .about("Display all current and previous actions"))
            .subcommand(SubCommand::with_name("neighbors")
                .about("Display all droplets running on the same physical hardware"))
            .subcommand(SubCommand::with_name("delete")
                .about("Deletes a droplet")
                .arg_from_usage(noconfirm))
            .subcommand(SubCommand::with_name("disable-backups")
                .about("Disables backups for a droplet"))
            .subcommand(SubCommand::with_name("reboot")
                .about("Reboots a droplet"))
            .subcommand(SubCommand::with_name("power-cycle")
                .about("Performs a power cycle on a droplet"))
            .subcommand(SubCommand::with_name("shutdown")
                .about("Shutsdown a droplet"))
            .subcommand(SubCommand::with_name("power-off")
                .about("Powers a droplet off"))
            .subcommand(SubCommand::with_name("power-on")
                .about("Turns on a droplet"))
            .subcommand(SubCommand::with_name("restore")
                .about("Restores a droplet from an image")
                .arg_from_usage(noconfirm)
                .arg_from_usage("<image> 'The image ID or slug to restore to'"))
            .subcommand(SubCommand::with_name("reset-password")
                .about("Resets the root password for a droplet"))
            .subcommand(SubCommand::with_name("resize")
                .about("Resizes a droplet")
                .args_from_usage("--disk 'Resizes the disk'
                                  <size> 'The new size to use (i.e. 15gb)'"))
            .subcommand(SubCommand::with_name("rebuild")
                .about("Rebuilds a droplet from an image")
                .arg_from_usage(noconfirm)
                .arg_from_usage("<image> 'The image ID or slug to use'"))
            .subcommand(SubCommand::with_name("rename")
                .about("Renames a droplet")
                .arg_from_usage("<name> 'The new name to use'")
                .arg_from_usage(noconfirm))
            .subcommand(SubCommand::with_name("change-kernel")
                .about("Changes the kernel of a droplet")
                .arg_from_usage(noconfirm)
                .arg_from_usage("<kernel_id> 'The kernel ID to use'"))
            .subcommand(SubCommand::with_name("enable-ipv6")
                .about("Enables IPv6 addresses"))
            .subcommand(SubCommand::with_name("enable-private-networking")
                .about("Enables private networking"))
            .subcommand(SubCommand::with_name("snapshot")
                .arg_from_usage("<name> 'What to name the new snapshot image'")
                .about("Creates a snapshot of a droplet"))
            .subcommand(SubCommand::with_name("action")
                .about("Displays a specific action for a droplet")
                .arg_from_usage("<action_id> 'The action ID to display'"))
            .subcommand(SubCommand::with_name("upgrade")
                .arg_from_usage(noconfirm)
                .about("Upgrades a droplet")))
        .subcommand(SubCommand::with_name("image")
            .about("Manage images")
            .arg_from_usage("<id> 'The image ID or slug to use (not all commands support using a slug)'")
            .subcommand(SubCommand::with_name("actions")
                .about("Lists all previous and current actions for an image"))
            .subcommand(SubCommand::with_name("rename")
                .arg_from_usage(noconfirm)
                .args_from_usage("<name> 'The new name of the image'")
                .about("Changes the name of a particular image"))
            .subcommand(SubCommand::with_name("delete")
                .about("Deletes an image")
                .arg_from_usage(noconfirm))
            .subcommand(SubCommand::with_name("transfer")
                .about("Transfers an image to a new region")
                .arg_from_usage(noconfirm)
                .arg_from_usage("<region> 'The region to transfer to'"))
            .subcommand(SubCommand::with_name("convert")
                .about("Converts a an image (i.e. from a snapshot to a backup)"))
            .subcommand(SubCommand::with_name("action")
                .about("Displays a particular action of an image")
                .arg_from_usage("<action_id> 'The action ID to display'")))
        .subcommand(SubCommand::with_name("ssh-keys")
            .about("Manage SSH keys")
            .subcommand(SubCommand::with_name("create")
                .about("Creatse a new SSH key")
                .args_from_usage("<name>       'The name of the SSH key'
                                  <public_key> 'The public key of the SSH key'"))
            .subcommand(SubCommand::with_name("key")
                .about("Displays information on a particular key")
                .args_from_usage("<id> 'The key ID or finger print of the key to display'"))
            .subcommand(SubCommand::with_name("rename")
                .about("Renames a particular SSH key")
                .arg_from_usage(noconfirm)
                .args_from_usage("<id>   'The key ID or finger print of the key to update'
                                  <name> 'The new name to use'"))
            .subcommand(SubCommand::with_name("destroy")
                .about("Destroys a particular SSH key")
                .arg_from_usage(noconfirm)
                .args_from_usage("<id> 'The key ID or finger print of the key to destroy'")))
        .get_matches();

    let mut cfg = Config {
        verbose: m.is_present("verbose"),
        no_send: m.is_present("nosend"),
        auth: get_auth_token(&m)
    };

    match m.subcommand() {
        ("account", Some(m))  => account::run(m, &mut cfg),
        ("domains", Some(m))  => domains::run(m, &mut cfg),
        ("dns", Some(m))      => dns::run(m, &mut cfg),
        ("droplets", Some(m)) => droplets::run(m, &mut cfg),
        ("droplet", Some(m))  => droplet::run(m, &mut cfg),
        ("image", Some(m))    => image::run(m, &mut cfg),
        ("ssh-keys", Some(m)) => ssh_keys::run(m, &mut cfg),
        ("list", Some(m))     => list::run(m, &mut cfg),
        _                     => ()
    }
}
<|endoftext|>


<commit_before>extern crate osmio;
extern crate image;
extern crate gif;
extern crate clap;

use clap::{Arg, App, AppSettings};

use std::fs;
use osmio::OSMReader;
use osmio::pbf::PBFReader;
use std::io::{Read, Write, BufReader, BufRead, BufWriter};
use std::collections::HashMap;

use gif::SetParameter;

// (frame_no, (pixel_index, num_changes))
type Frames = Vec<(u32, Vec<(u32, u16)>)>;

struct ColourRamp {
    empty_colour: (u8, u8, u8),
    steps: Vec<(u32, (u8, u8, u8))>,
}

impl ColourRamp {
    fn new_from_filename(filename: &str) -> Self {
        let mut contents = String::new();
        let mut file = fs::File::open(filename).unwrap();
        file.read_to_string(&mut contents).expect("Couldn't read colour ramp file");
        Self::new_from_text(&contents)

    }
    fn new_from_text(source: &str) -> Self {
        let lines: Vec<_> = source.lines().collect();
        let empty_vec = lines[0].split(",").filter_map(|x| x.parse::<u8>().ok()).take(3).collect::<Vec<_>>();
        let empty = (empty_vec[0], empty_vec[1], empty_vec[2]);

        let mut steps = Vec::new();
        for line in lines.iter().skip(1) {
            let line = line.split(",").filter_map(|x| x.parse::<u32>().ok()).take(4).collect::<Vec<_>>();
            let age = line[0];
            let colour = (line[1] as u8, line[2] as u8, line[3] as u8);
            steps.push((age, colour));
        }

        if steps.len() > 254 {
            panic!("Too many steps");
        }

        ColourRamp{ empty_colour: empty, steps: steps }
    }

    fn palette(&self) -> Vec<u8> {
        let mut results = Vec::with_capacity((self.steps.len()+1)*3);
        results.push(self.empty_colour.0);
        results.push(self.empty_colour.1);
        results.push(self.empty_colour.2);

        for &(_, (r, g, b)) in self.steps.iter() {
            results.push(r);
            results.push(g);
            results.push(b);
        }

        results
    }

    fn index_for_magnitude(&self, magnitude: Option<u32>) -> u8 {
        match magnitude {
            None => 0,
            Some(magnitude) => {
                if magnitude > 255 {
                    1
                } else {
                    (255 - magnitude) as u8
                }
            },
            //Some(age) => match self.steps.iter().position(|&(x, _)| x == age) {
            //    None => 0,
            //    Some(i) => (i+1) as u8,
            //}
        }
    }
}

fn read_pbf(filename: &str, height: u32, sec_per_frame: u32, bbox: &[f32; 4]) -> Frames {
    let file = BufReader::new(fs::File::open(&filename).unwrap());
    let mut node_reader = PBFReader::new(file);
    let node_reader = node_reader.nodes();

    let mut results: HashMap<u32, HashMap<u32, u16>> = HashMap::new();

    let left = bbox[0]; let bottom = bbox[1]; let right = bbox[2]; let top = bbox[3];
    let bbox_width = right - left;
    let bbox_height = top - bottom;
    let width = ((bbox_width / bbox_height) * (height as f32)) as u32;

    // 1st April 2005, midnight GMT. We presume no OSM editing before then.
    let osm_epoch = 1109635200;

    // FIXME use a btreemap?

    let mut first_frame_no = std::u32::MAX;
    let mut last_frame_no = 0;

    let mut num_nodes: u64 = 0;
    for node in node_reader {
        if let (Some(lat), Some(lon)) = (node.lat, node.lon) {
            // FIXME should be able to do non-equals but it fails for point at south pole
            if lat >= top || lat <= bottom || lon >= right || lon <= left {
                continue;
            }

            let timestamp = node.timestamp.to_epoch_number() as u64;
            if timestamp < osm_epoch {
                panic!("timestamp before epoch. Change code. {}", timestamp);
            }
            let offset = (timestamp - osm_epoch) as u32;
            // TODO double check that this rounds down
            let frame_no = offset / sec_per_frame;

            if frame_no < first_frame_no {
                first_frame_no = frame_no;
            }
            if frame_no > last_frame_no {
                last_frame_no = frame_no;
            }

            let pixel_idx = latlon_to_pixel_index(lat, lon, width, height, &bbox);
            let curr_val = results.entry(frame_no).or_insert(HashMap::new()).entry(pixel_idx).or_insert(0);
            if *curr_val < std::u16::MAX {
                *curr_val += 1;
            }

            num_nodes += 1;
            if num_nodes % 50_000_000 == 0 {
                println!("Done {} million points", num_nodes/1_000_000);
            }
        }
    }
    println!("Done {} points", num_nodes);
    let num_frames = last_frame_no - first_frame_no + 1;
    println!("There are {} frames, which is {} sec", num_frames, num_frames as f32/30.);

    let mut sorted_results = Vec::with_capacity((last_frame_no-first_frame_no+1) as usize);

    for frame_no in 0..num_frames {
        match results.remove(&(frame_no+first_frame_no)) {
            None => { sorted_results.push((frame_no, Vec::with_capacity(0))) },
            Some(pixels) => {
                let pixels = pixels.into_iter().collect();
                sorted_results.push((frame_no, pixels));
            }
        }
    }

    sorted_results
}

fn write_frames(frames: Frames, filename: &str, height: u32, sec_per_frame: u32, bbox: &[f32; 4]) {
    let left = bbox[0]; let bottom = bbox[1]; let right = bbox[2]; let top = bbox[3];
    let bbox_width = right - left;
    let bbox_height = top - bottom;
    let width = ((bbox_width / bbox_height) * (height as f32)) as u32;

    let mut file = BufWriter::new(fs::File::create(&filename).unwrap());

    writeln!(file, "metadata version {}", env!("CARGO_PKG_VERSION")).expect("Couldn't write metadata");
    writeln!(file, "metadata height {}", height).expect("Couldn't write metadata");
    writeln!(file, "metadata width {}", width).expect("Couldn't write metadata");
    writeln!(file, "metadata sec_per_frame {}", sec_per_frame).expect("Couldn't write metadata");
    writeln!(file, "metadata left {}", left).expect("Couldn't write metadata");
    writeln!(file, "metadata bottom {}", bottom).expect("Couldn't write metadata");
    writeln!(file, "metadata right {}", right).expect("Couldn't write metadata");
    writeln!(file, "metadata top {}", top).expect("Couldn't write metadata");
    writeln!(file, "").expect("Couldn't write metadata");

    for (frame_no, pixels) in frames.into_iter() {
        write!(file, "{}", frame_no).unwrap();
        for p in pixels {
            write!(file, ",{},{}", p.0, p.1).unwrap();
        }
        write!(file, "\n").unwrap()
    }
}

fn read_frames(filename: &str) -> Frames {
    let file = BufReader::new(fs::File::open(&filename).unwrap());
    let mut results = Frames::new();

    //let metadata: Vec<(String, String)> = file.lines().filter_map(|x| x.ok()).take_while(|x| x.len() > 0).map(|x| { let words: Vec<_> = x.split(" ").skip(1).take(2).collect(); (words[0].clone(), words[1].clone())}).collect();

    for line in file.lines().filter_map(|x| x.ok()).skip_while(|x| x.starts_with("metadata ") || x.len() == 0) {
        let frame_no = line.split(",").nth(0).unwrap().parse().unwrap();
        let pixels: Vec<(u32, u16)> = line.split(",").skip(1).collect::<Vec<_>>().chunks(2).map(|pair| (pair[0].parse().unwrap(), pair[1].parse().unwrap())).collect();
        results.push((frame_no, pixels))
    }

    results
}

fn latlon_to_pixel_index(lat: f32, lon: f32, width: u32, height: u32, bbox: &[f32; 4]) -> u32 {
    let left = bbox[0]; let bottom = bbox[1]; let right = bbox[2]; let top = bbox[3];
    let bbox_width = right - left;
    let bbox_height = top - bottom;

    let lat0 = top - lat;
    let lon0 = lon - left;

    let x = ((lon0/bbox_width)*(width as f32)) as u32;
    let y = ((lat0/bbox_height)*(height as f32)) as u32;

    let i = y * width + x;

    assert!(i < width*height, "{} L{}, lat = {} lon = {} width = {} height = {} bbox = {:?} x = {} y = {} i = {}", file!(), line!(), lat, lon, width, height, bbox, x, y, i);

    i
}

fn create_equirectangular_map(frames: Frames, output_image_filename: &str, height: u32, bbox: &[f32; 4], colour_ramp: &ColourRamp) {
    let mut output_file = fs::File::create(output_image_filename).expect("Can't create image");

    let left = bbox[0]; let bottom = bbox[1]; let right = bbox[2]; let top = bbox[3];
    let bbox_width = right - left;
    let bbox_height = top - bottom;
    let width = ((bbox_width / bbox_height) * (height as f32)) as u32;

    // FIXME change width/height to u16?
    let mut encoder = gif::Encoder::new(&mut output_file, width as u16, height as u16, &colour_ramp.palette()).expect("Couldn't create encoder");
    encoder.set(gif::Repeat::Infinite).expect("Couldn't get inf repeat");

    // TODO have a global palette

    let mut image = vec![None; (width*height) as usize];

    for (frame_no, pixels) in frames.into_iter() {

        for i in 0..image.len() {
            if image[i].is_some() && image[i].unwrap() > 0 {
                image[i] = image[i].map(|x| x-1);
            }
        }

        for (i, magnitude) in pixels {
            // FIXME sometimes the value is invalid
            //assert!(i < width*height, "{} L{}, width = {} height = {} i = {}", file!(), line!(), width, height, i);
            if i < width*height {
                image[i as usize] = Some(magnitude as u32);
            }
        }

        let mut pixels = Vec::with_capacity(image.len() * 4);
        for p in image.iter().cloned() {
            let index = colour_ramp.index_for_magnitude(p);
            pixels.push(index);
        }

        let mut frame = gif::Frame::from_indexed_pixels(width as u16, height as u16, pixels.as_mut_slice());
        // 30 fps, and delay is in units of 10ms.
        frame.delay = 100 / 30;

        encoder.write_frame(&frame).expect("Couldn't write frame");

        if frame_no % 30 == 0 {
            println!("Wrote frame {}", frame_no);
        }

    }

}

fn main() {
    let matches = App::new("osm-history-animation")
                          .arg(Arg::with_name("input").long("input").short("i")
                               .takes_value(true))
                          .arg(Arg::with_name("output") .long("output").short("o")
                               .takes_value(true))
                          .arg(Arg::with_name("height") .long("height").short("h")
                               .takes_value(true))
                          .arg(Arg::with_name("spf") .long("sec-per-frame").short("s")
                               .takes_value(true))
                          .arg(Arg::with_name("colour_ramp") .long("colour-ramp")
                               .takes_value(true))
                          .arg(Arg::with_name("save-intermediate").long("save-intermediate"))
                          .arg(Arg::with_name("load-intermediate").long("load-intermediate"))
                          .arg(Arg::with_name("bbox").long("bbox").takes_value(true).short("b"))
                          .setting(AppSettings::AllowLeadingHyphen)
                          .get_matches();

    let input_filename = matches.value_of("input").unwrap();
    let output_filename = matches.value_of("output").unwrap();
    let height: u32 = matches.value_of("height").unwrap().parse().unwrap();
    let sec_per_frame: u32 = matches.value_of("spf").unwrap().parse().unwrap();

    let bbox = match matches.value_of("bbox") {
        None => [-180., -90., 180., 90.],
        Some(text) => {
            let coords: Vec<f32> = text.split(",").map(|x| x.parse().unwrap() ).collect();
            [coords[0], coords[1], coords[2], coords[3]]
        }
    };

    let frames = if matches.is_present("load-intermediate") {
        println!("Reading frames from {}", input_filename);
        read_frames(&input_filename)
    } else {
        println!("Reading PBF file {}", input_filename);
        read_pbf(&input_filename, height, sec_per_frame, &bbox)
    };

    if matches.is_present("save-intermediate") {
        println!("Saving frame details to {}", output_filename);
        write_frames(frames, &output_filename, height, sec_per_frame, &bbox);
    } else {
        let colour_ramp = ColourRamp::new_from_filename(matches.value_of("colour_ramp").unwrap());
        println!("Creating image {}", output_filename);
        create_equirectangular_map(frames, &output_filename, height, &bbox, &colour_ramp);
    }
    println!("\nFinished");
}
<commit_msg>use proper signature<commit_after>extern crate osmio;
extern crate image;
extern crate gif;
extern crate clap;

use clap::{Arg, App, AppSettings};

use std::fs;
use osmio::OSMReader;
use osmio::pbf::PBFReader;
use std::io::{Read, Write, BufReader, BufRead, BufWriter};
use std::collections::HashMap;

use gif::SetParameter;

// (frame_no, (pixel_index, num_changes))
type Frames = Vec<(u32, Vec<(u32, u16)>)>;

struct ColourRamp {
    empty_colour: (u8, u8, u8),
    steps: Vec<(u32, (u8, u8, u8))>,
}

impl ColourRamp {
    fn new_from_filename(filename: &str) -> Self {
        let mut contents = String::new();
        let mut file = fs::File::open(filename).unwrap();
        file.read_to_string(&mut contents).expect("Couldn't read colour ramp file");
        Self::new_from_text(&contents)

    }
    fn new_from_text(source: &str) -> Self {
        let lines: Vec<_> = source.lines().collect();
        let empty_vec = lines[0].split(",").filter_map(|x| x.parse::<u8>().ok()).take(3).collect::<Vec<_>>();
        let empty = (empty_vec[0], empty_vec[1], empty_vec[2]);

        let mut steps = Vec::new();
        for line in lines.iter().skip(1) {
            let line = line.split(",").filter_map(|x| x.parse::<u32>().ok()).take(4).collect::<Vec<_>>();
            let age = line[0];
            let colour = (line[1] as u8, line[2] as u8, line[3] as u8);
            steps.push((age, colour));
        }

        if steps.len() > 254 {
            panic!("Too many steps");
        }

        ColourRamp{ empty_colour: empty, steps: steps }
    }

    fn palette(&self) -> Vec<u8> {
        let mut results = Vec::with_capacity((self.steps.len()+1)*3);
        results.push(self.empty_colour.0);
        results.push(self.empty_colour.1);
        results.push(self.empty_colour.2);

        for &(_, (r, g, b)) in self.steps.iter() {
            results.push(r);
            results.push(g);
            results.push(b);
        }

        results
    }

    fn index_for_magnitude(&self, magnitude: Option<u32>) -> u8 {
        match magnitude {
            None => 0,
            Some(magnitude) => {
                if magnitude > 255 {
                    1
                } else {
                    (255 - magnitude) as u8
                }
            },
            //Some(age) => match self.steps.iter().position(|&(x, _)| x == age) {
            //    None => 0,
            //    Some(i) => (i+1) as u8,
            //}
        }
    }
}

fn read_pbf(filename: &str, height: u32, sec_per_frame: u32, bbox: &[f32; 4]) -> Frames {
    let file = BufReader::new(fs::File::open(&filename).unwrap());
    let mut node_reader = PBFReader::new(file);
    let node_reader = node_reader.nodes();

    let mut results: HashMap<u32, HashMap<u32, u16>> = HashMap::new();

    let left = bbox[0]; let bottom = bbox[1]; let right = bbox[2]; let top = bbox[3];
    let bbox_width = right - left;
    let bbox_height = top - bottom;
    let width = ((bbox_width / bbox_height) * (height as f32)) as u32;

    // 1st April 2005, midnight GMT. We presume no OSM editing before then.
    let osm_epoch = 1109635200;

    // FIXME use a btreemap?

    let mut first_frame_no = std::u32::MAX;
    let mut last_frame_no = 0;

    let mut num_nodes: u64 = 0;
    for node in node_reader {
        if let (Some(lat), Some(lon)) = (node.lat, node.lon) {
            // FIXME should be able to do non-equals but it fails for point at south pole
            if lat >= top || lat <= bottom || lon >= right || lon <= left {
                continue;
            }

            let timestamp = node.timestamp.to_epoch_number() as u64;
            if timestamp < osm_epoch {
                panic!("timestamp before epoch. Change code. {}", timestamp);
            }
            let offset = (timestamp - osm_epoch) as u32;
            // TODO double check that this rounds down
            let frame_no = offset / sec_per_frame;

            if frame_no < first_frame_no {
                first_frame_no = frame_no;
            }
            if frame_no > last_frame_no {
                last_frame_no = frame_no;
            }

            let pixel_idx = latlon_to_pixel_index(lat, lon, width, height, &bbox);
            let curr_val = results.entry(frame_no).or_insert(HashMap::new()).entry(pixel_idx).or_insert(0);
            if *curr_val < std::u16::MAX {
                *curr_val += 1;
            }

            num_nodes += 1;
            if num_nodes % 50_000_000 == 0 {
                println!("Done {} million points", num_nodes/1_000_000);
            }
        }
    }
    println!("Done {} points", num_nodes);
    let num_frames = last_frame_no - first_frame_no + 1;
    println!("There are {} frames, which is {} sec", num_frames, num_frames as f32/30.);

    let mut sorted_results = Vec::with_capacity((last_frame_no-first_frame_no+1) as usize);

    for frame_no in 0..num_frames {
        match results.remove(&(frame_no+first_frame_no)) {
            None => { sorted_results.push((frame_no, Vec::with_capacity(0))) },
            Some(pixels) => {
                let pixels = pixels.into_iter().collect();
                sorted_results.push((frame_no, pixels));
            }
        }
    }

    sorted_results
}

fn write_frames(frames: Frames, filename: &str, height: u32, sec_per_frame: u32, bbox: &[f32; 4]) {
    let left = bbox[0]; let bottom = bbox[1]; let right = bbox[2]; let top = bbox[3];
    let bbox_width = right - left;
    let bbox_height = top - bottom;
    let width = ((bbox_width / bbox_height) * (height as f32)) as u32;

    let mut file = BufWriter::new(fs::File::create(&filename).unwrap());

    writeln!(file, "metadata version {}", env!("CARGO_PKG_VERSION")).expect("Couldn't write metadata");
    writeln!(file, "metadata height {}", height).expect("Couldn't write metadata");
    writeln!(file, "metadata width {}", width).expect("Couldn't write metadata");
    writeln!(file, "metadata sec_per_frame {}", sec_per_frame).expect("Couldn't write metadata");
    writeln!(file, "metadata left {}", left).expect("Couldn't write metadata");
    writeln!(file, "metadata bottom {}", bottom).expect("Couldn't write metadata");
    writeln!(file, "metadata right {}", right).expect("Couldn't write metadata");
    writeln!(file, "metadata top {}", top).expect("Couldn't write metadata");
    writeln!(file, "").expect("Couldn't write metadata");

    for (frame_no, pixels) in frames.into_iter() {
        write!(file, "{}", frame_no).unwrap();
        for p in pixels {
            write!(file, ",{},{}", p.0, p.1).unwrap();
        }
        write!(file, "\n").unwrap()
    }
}

fn read_frames(filename: &str) -> Frames {
    let file = BufReader::new(fs::File::open(&filename).unwrap());
    let mut results = Frames::new();

    //let metadata: Vec<(String, String)> = file.lines().filter_map(|x| x.ok()).take_while(|x| x.len() > 0).map(|x| { let words: Vec<_> = x.split(" ").skip(1).take(2).collect(); (words[0].clone(), words[1].clone())}).collect();

    for line in file.lines().filter_map(|x| x.ok()).skip_while(|x| x.starts_with("metadata ") || x.len() == 0) {
        let frame_no = line.split(",").nth(0).unwrap().parse().unwrap();
        let pixels: Vec<(u32, u16)> = line.split(",").skip(1).collect::<Vec<_>>().chunks(2).map(|pair| (pair[0].parse().unwrap(), pair[1].parse().unwrap())).collect();
        results.push((frame_no, pixels))
    }

    results
}

fn latlon_to_pixel_index(lat: f32, lon: f32, width: u32, height: u32, bbox: &[f32; 4]) -> u32 {
    let left = bbox[0]; let bottom = bbox[1]; let right = bbox[2]; let top = bbox[3];
    let bbox_width = right - left;
    let bbox_height = top - bottom;

    let lat0 = top - lat;
    let lon0 = lon - left;

    let x = ((lon0/bbox_width)*(width as f32)) as u32;
    let y = ((lat0/bbox_height)*(height as f32)) as u32;

    let i = y * width + x;

    assert!(i < width*height, "{} L{}, lat = {} lon = {} width = {} height = {} bbox = {:?} x = {} y = {} i = {}", file!(), line!(), lat, lon, width, height, bbox, x, y, i);

    i
}

fn create_equirectangular_map(frames: Frames, output_image_filename: &str, height: u32, bbox: &[f32; 4], colour_ramp: &ColourRamp) {
    let mut output_file = fs::File::create(output_image_filename).expect("Can't create image");

    let left = bbox[0]; let bottom = bbox[1]; let right = bbox[2]; let top = bbox[3];
    let bbox_width = right - left;
    let bbox_height = top - bottom;
    let width = ((bbox_width / bbox_height) * (height as f32)) as u32;

    // FIXME change width/height to u16?
    let mut encoder = gif::Encoder::new(&mut output_file, width as u16, height as u16, &colour_ramp.palette()).expect("Couldn't create encoder");
    encoder.set(gif::Repeat::Infinite).expect("Couldn't get inf repeat");

    // TODO have a global palette

    let mut image = vec![None; (width*height) as usize];

    for (frame_no, pixels) in frames.into_iter() {

        for i in 0..image.len() {
            if image[i].is_some() && image[i].unwrap() > 0 {
                image[i] = image[i].map(|x| x-1);
            }
        }

        for (i, magnitude) in pixels {
            // FIXME sometimes the value is invalid
            //assert!(i < width*height, "{} L{}, width = {} height = {} i = {}", file!(), line!(), width, height, i);
            if i < width*height {
                image[i as usize] = Some(magnitude as u32);
            }
        }

        let mut pixels = Vec::with_capacity(image.len() * 4);
        for p in image.iter().cloned() {
            let index = colour_ramp.index_for_magnitude(p);
            pixels.push(index);
        }

        let mut frame = gif::Frame::from_indexed_pixels(width as u16, height as u16, pixels.as_mut_slice(), None);
        // 30 fps, and delay is in units of 10ms.
        frame.delay = 100 / 30;

        encoder.write_frame(&frame).expect("Couldn't write frame");

        if frame_no % 30 == 0 {
            println!("Wrote frame {}", frame_no);
        }

    }

}

fn main() {
    let matches = App::new("osm-history-animation")
                          .arg(Arg::with_name("input").long("input").short("i")
                               .takes_value(true))
                          .arg(Arg::with_name("output") .long("output").short("o")
                               .takes_value(true))
                          .arg(Arg::with_name("height") .long("height").short("h")
                               .takes_value(true))
                          .arg(Arg::with_name("spf") .long("sec-per-frame").short("s")
                               .takes_value(true))
                          .arg(Arg::with_name("colour_ramp") .long("colour-ramp")
                               .takes_value(true))
                          .arg(Arg::with_name("save-intermediate").long("save-intermediate"))
                          .arg(Arg::with_name("load-intermediate").long("load-intermediate"))
                          .arg(Arg::with_name("bbox").long("bbox").takes_value(true).short("b"))
                          .setting(AppSettings::AllowLeadingHyphen)
                          .get_matches();

    let input_filename = matches.value_of("input").unwrap();
    let output_filename = matches.value_of("output").unwrap();
    let height: u32 = matches.value_of("height").unwrap().parse().unwrap();
    let sec_per_frame: u32 = matches.value_of("spf").unwrap().parse().unwrap();

    let bbox = match matches.value_of("bbox") {
        None => [-180., -90., 180., 90.],
        Some(text) => {
            let coords: Vec<f32> = text.split(",").map(|x| x.parse().unwrap() ).collect();
            [coords[0], coords[1], coords[2], coords[3]]
        }
    };

    let frames = if matches.is_present("load-intermediate") {
        println!("Reading frames from {}", input_filename);
        read_frames(&input_filename)
    } else {
        println!("Reading PBF file {}", input_filename);
        read_pbf(&input_filename, height, sec_per_frame, &bbox)
    };

    if matches.is_present("save-intermediate") {
        println!("Saving frame details to {}", output_filename);
        write_frames(frames, &output_filename, height, sec_per_frame, &bbox);
    } else {
        let colour_ramp = ColourRamp::new_from_filename(matches.value_of("colour_ramp").unwrap());
        println!("Creating image {}", output_filename);
        create_equirectangular_map(frames, &output_filename, height, &bbox, &colour_ramp);
    }
    println!("\nFinished");
}
<|endoftext|>