<commit_before>extern crate fern;
extern crate hyper;
extern crate irc;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate log;
extern crate regex;
extern crate serde;
extern crate serde_json;

mod bot;

use bot::Bot;
use bot::regexmatch::RegexMatch;
use hyper::client::Client;
use hyper::status::StatusCode;
use irc::client::data::Config;
use regex::Regex;
use serde_json::Value;

use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::fs::File;
use std::io::{Error, ErrorKind, Read};
use std::path::Path;

// Calls into the gfycat API to try to pull down gfycat titles, sizes, and NSFW status
fn gfycat_handler(cache: &mut HashMap<String, String>, id: &str) -> Result<String, Error> {
    Ok(match cache.entry(id.to_owned()) {
        Entry::Occupied(entry) => entry.get().clone(),
        Entry::Vacant(entry) => {
            let url = format!("http://gfycat.com/cajax/get/{}", id);

            let resp = &try!(http_get(&url))[..];
            let resp_json: Value = match serde_json::from_str(resp) {
                Ok(resp_json) => resp_json,
                Err(e) => return Err(Error::new(ErrorKind::Other, e))
            };

            match resp_json.find("error") {
                Some(e) => {
                    let e = e.as_string().expect("gfycat API error - 'error' was not a string");

                    return Err(Error::new(ErrorKind::Other, e))
                },
                None => {}
            }

            // Panic here if we don't see what we expect, since the API shouldn't change underneath us
            let item = resp_json
                .find("gfyItem").expect("gfycat API error - could not find 'gfyItem'")
                .as_object().expect("gfycat API error - 'gfyItem' was not an object");
            let framerate = item
                .get("frameRate").expect("gfycat API error - could not find 'frameRate'")
                .as_f64().expect("gfycat API error - 'framerate' was not a f64");
            let frames = item
                .get("numFrames").expect("gfycat API error - could not find 'numFrames'")
                .as_u64().expect("gfycat API error - 'numFrames' was not a u64");
            let nsfw = item
                .get("nsfw").expect("gfycat API error - could not find 'nsfw'")
                .as_string().map_or("", |s| if s == "1" { "NSFW " } else { "" });
            let size = item
                .get("webmSize").expect("gfycat API error - could not find 'webmSize'")
                .as_u64().expect("gfycat API error - 'webmSize' was not a u64");
            let title = item
                .get("title").expect("gfycat API error - could not find 'title'")
                .as_string().map_or("<unknown>".to_owned(), |s| format!("\"{}\"", s));

            let res = format!("{}{} ({:.2} MB, {:.1} seconds)", nsfw, title, size as f64 / (2.0 * 1024.0 * 1024.0), frames as f64 / framerate);
            entry.insert(res).clone()
        }
    })
}
// Convert Kelvin to Celcius
fn weather_k_to_c(k: f64) -> f64 {
    k - 273.15
}
// Convert Kelvin to Fahrenheit
fn weather_k_to_f(k: f64) -> f64 {
    let c = weather_k_to_c(k);
    1.8 * c + 32.0
}
// Calls into the OpenWeatherMap API to try and get the weather at `loc`
fn weather_handler(wapi_key: &str, loc: &str) -> Result<String, Error> {
    let url = format!("http://api.openweathermap.org/data/2.5/weather?q={}&APPID={}", loc, wapi_key);

    let resp = &try!(http_get(&url))[..];
    let resp_json: Value = match serde_json::from_str(resp) {
        Ok(resp_json) => resp_json,
        Err(e) => return Err(Error::new(ErrorKind::Other, e))
    };

    match resp_json.find("message") {
        Some(e) => {
            let e = e.as_string().expect("OpenWeatherMap API error - 'message' was not a string");

            return Err(Error::new(ErrorKind::Other, e))
        },
        None => {}
    };

    // Panic here if we don't see what we expect, since the API shouldn't change underneath us
    let name = resp_json
        .find("name").expect("OpenWeatherMap API error - could not find 'name'")
        .as_string().expect("OpenWeatherMap API error - 'name' was not a string");
    let description = resp_json
        .find("weather").expect("OpenWeatherMap API error - could not find 'weather'")
        .as_array().expect("OpenWeatherMap API error - 'weather' was not an array")[0]
        .find("description").expect("OpenWeatherMap API error - could not find 'description'")
        .as_string().expect("OpenWeatherMap API error - 'description' was not a string");
    let main = resp_json
        .find("main").expect("OpenWeatherMap API error - could not find 'main'");
    let temp_k = main
        .find("temp").expect("OpenWeatherMap API error - could not find 'temp'")
        .as_f64().expect("OpenWeatherMap API error - 'temp' was not a number");
    let humidity = main
        .find("humidity").expect("OpenWeatherMap API error - could not find 'humidity'")
        .as_f64().expect("OpenWeatherMap API error - 'humidity' was not a number");
    let wind_speed = resp_json
        .find("wind").expect("OpenWeatherMap API error - could not find 'wind'")
        .find("speed").expect("OpenWeatherMap API error - could not find 'speed'")
        .as_f64().expect("OpenWeatherMap API error - 'speed' was not a number");

    Ok(format!("{} weather: {:.2} \u{00B0}F ({:.2} \u{00B0}C), {}% humidity, {} and wind {:.2} m/s",
               name, weather_k_to_f(temp_k), weather_k_to_c(temp_k), humidity, description, wind_speed))
}
// Parses an ISO 8601 duration string into a human-readable duration
// e.g. "3M20S" -> 3 minutes 20 seconds
// TODO: Is there a package since I made this that handles ISO time well?
fn yt_parse_time(s: &str) -> String {
    let mut in_time = false;
    let mut start_idx: Option<usize> = None;
    let mut prev_char: char = '\u{0000}';
    let mut length = String::new();

    for (idx, c) in s.chars().enumerate() {
        match c {
            '0' ... '9' => {prev_char = c; continue}
            'P' => start_idx = Some(idx + 1),
            'T' => {
                start_idx = Some(idx + 1);
                in_time = true
            },
            _ => {
                if length.len() > 0 {
                    length.push_str(", ");
                }

                length.push_str(&s[start_idx.unwrap() .. idx]);
                length.push(' ');
                length.push_str(match c {
                    'Y' => "year",
                    'M' => if in_time {"minute"} else {"month"},
                    'D' => "day",
                    'H' => "hour",
                    'S' => "second",
                    _ => panic!("Unexpected character in ISO 8601 duration string")
                });

                if idx - start_idx.unwrap() > 1 || prev_char != '1' {
                    length.push('s');
                }

                start_idx = Some(idx + 1);
            }
        }
    }

    length
}
// Calls into Google's Youtube API to determine video duration, title, and channel title, or accesses that data from a cache
fn yt_handler(gapi_key: &str, cache: &mut HashMap<String, String>, id: &str) -> Result<String, Error> {
    Ok(match cache.entry(id.to_owned()) {
        Entry::Occupied(entry) => entry.get().clone(),
        Entry::Vacant(entry) => {
            let url = format!("https://www.googleapis.com/youtube/v3/videos?id={}&key={}&part=snippet,contentDetails&fields=items(snippet/title,snippet/channelTitle,contentDetails/duration)", id, gapi_key);

            let resp = &try!(http_get(&url))[..];
            let resp_json: Value = match serde_json::from_str(resp) {
                Ok(resp_json) => resp_json,
                Err(e) => return Err(Error::new(ErrorKind::Other, e))
            };

            // I chose to panic here on any errors, since if we get a response from Youtube
            // it should be of the correct form - their API shouldn't change underneath us
            let ref items = resp_json
                .find("items").expect("Youtube API error - could not find 'items'")
                .as_array().expect("Youtube API error - 'items' was not an array")[0];
            let snippet = items
                .find("snippet").expect("Youtube API error - could not find 'snippet'");
            let title = snippet
                .find("title").expect("Youtube API error - could not find 'title'")
                .as_string().expect("Youtube API error - 'title' was not a string");
            let channel_title = snippet
                .find("channelTitle").expect("Youtube API error - could not find 'channelTitle'")
                .as_string().expect("Youtube API error - 'channelTitle' was not a string");
            let duration = items
                .find("contentDetails").expect("Youtube API error - could not find 'contentDetails'")
                .find("duration").expect("Youtube API error - could not find 'duration'")
                .as_string().expect("Youtube API error - 'duration' was not a string");

            let res = format!("\"{}\" by {} ({})", title, channel_title, yt_parse_time(duration));
            entry.insert(res).clone()
        }
    })
}
// Calls into XKCD's API to determine comic name and date from a comic ID
fn xkcd_handler(cache: &mut HashMap<String, String>, id: &str) -> Result<String, Error> {
    Ok(match cache.entry(id.to_owned()) {
        Entry::Occupied(entry) => entry.get().clone(),
        Entry::Vacant(entry) => {
            let url = format!("https://xkcd.com/{}/info.0.json", id);

            let resp = &try!(http_get(&url))[..];
            let resp_json: Value = match serde_json::from_str(resp) {
                Ok(resp_json) => resp_json,
                Err(e) => return Err(Error::new(ErrorKind::Other, e))
            };

            let title = resp_json
                .find("title").expect("XKCD API error - could not find 'title'")
                .as_string().expect("XKCD API error - 'title' was not a string");
            let year = resp_json
                .find("year").expect("XKCD API error - could not find 'year'")
                .as_string().expect("XKCD API error - 'year' was not a string");
            let month = resp_json
                .find("month").expect("XKCD API error - could not find 'month'")
                .as_string().expect("XKCD API error - 'month' was not a string");
            let day = resp_json
                .find("day").expect("XKCD API error - could not find 'day'")
                .as_string().expect("XKCD API error - 'day' was not a string");

            let res = format!("{} ({}-{}-{})", title, year, month, day);
            entry.insert(res).clone()
        }
    })
}

fn file_get(p: &Path) -> Result<Vec<u8>, Error> {
    Ok(try!(File::open(p)).bytes().map(|b| b.unwrap()).collect::<Vec<u8>>())
}
// Pulls down the content of a request to a URL as a string
pub fn http_get(url: &str) -> Result<String, Error> {
    let client = Client::new();
    let mut resp = match client.get(url).send() {
        Ok(resp) => resp,
        Err(e) => return Err(Error::new(ErrorKind::Other, e))
    };

    match resp.status {
        StatusCode::Ok => {
            let mut body = String::new();
            try!(resp.read_to_string(&mut body));
            Ok(body)
        },
        StatusCode::BadRequest => Err(Error::new(ErrorKind::Other, "HTTP Bad Request")),
        StatusCode::NotFound => Err(Error::new(ErrorKind::Other, "HTTP Not Found")),
        _ => Err(Error::new(ErrorKind::Other, format!("{:?}", resp.status)))
    }
}

fn main() {
    let logger_config = fern::DispatchConfig {
        format: Box::new(|msg, level, _| format!("[{}] {}", level, msg)),
        output: vec![fern::OutputConfig::stdout(),
                     fern::OutputConfig::file(Path::new("log.log"))],
        level: log::LogLevelFilter::Warn,
    };
    if let Err(e) = fern::init_global_logger(logger_config, log::LogLevelFilter::Trace) {
        panic!("Failed to initialize logger: {}", e);
    }

    let mut regex_match = RegexMatch::new();

    match file_get(Path::new("gapi_key.dat")).map(String::from_utf8) {
        Ok(Ok(gapi_key)) => {
            let mut cache = HashMap::new();
            regex_match.add((Regex::new(r"(?:(?:youtube\.com/watch\?\S*?v=)|(?:youtu\.be/))([\w-]+)").unwrap(),
                             Box::new(move |res| yt_handler(&gapi_key, &mut cache, res))));
        },
        Ok(Err(utf_err)) => error!("gapi_key.dat formatting error: {}", utf_err),
        Err(io_err) => warn!("gapi_key.dat should contain a Google API key to use youtube functionality: {}", io_err),
    }
    match file_get(Path::new("wapi_key.dat")).map(String::from_utf8) {
        Ok(Ok(wapi_key)) => {
            regex_match.add((Regex::new(r"^!weather\s*(.+?)\s*$").unwrap(),
                             Box::new(move |res| weather_handler(&wapi_key, res))));
        },
        Ok(Err(utf_err)) => error!("wapi_key.dat formatting error: {}", utf_err),
        Err(io_err) => warn!("wapi_key.dat should contain an OpenWeatherMap API key to use weather functionality: {}", io_err),
    }

    let mut gfycat_cache = HashMap::new();
    regex_match.add((Regex::new(r"gfycat\.com/((?:[A-Z][a-z]+){3})").unwrap(),
                     Box::new(move |res| gfycat_handler(&mut gfycat_cache, res))));
    let mut xkcd_cache = HashMap::new();
    regex_match.add((Regex::new(r"xkcd\.com/(\d+)").unwrap(),
                     Box::new(move |res| xkcd_handler(&mut xkcd_cache, res))));

    let mut bot = Bot::new(Config::load(Path::new("config.json")).unwrap()).unwrap();

    bot.add_subscriber(&mut regex_match);
    bot.loop_forever();
}
<commit_msg>Simplify use of serde<commit_after>extern crate fern;
extern crate hyper;
extern crate irc;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate log;
extern crate regex;
extern crate serde;
extern crate serde_json;

mod bot;

use bot::Bot;
use bot::regexmatch::RegexMatch;
use hyper::client::Client;
use hyper::status::StatusCode;
use irc::client::data::Config;
use regex::Regex;
use serde_json::Value;

use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::fs::File;
use std::io::{Error, ErrorKind, Read};
use std::path::Path;

// Calls into the gfycat API to try to pull down gfycat titles, sizes, and NSFW status
fn gfycat_handler(cache: &mut HashMap<String, String>, id: &str) -> Result<String, Error> {
    Ok(match cache.entry(id.to_owned()) {
        Entry::Occupied(entry) => entry.get().clone(),
        Entry::Vacant(entry) => {
            let url = format!("http://gfycat.com/cajax/get/{}", id);

            let resp = &try!(http_get(&url))[..];
            let resp_json: Value = match serde_json::from_str(resp) {
                Ok(resp_json) => resp_json,
                Err(e) => return Err(Error::new(ErrorKind::Other, e))
            };

            match resp_json.find("error") {
                Some(e) => {
                    let e = e.as_string().expect("gfycat API error - 'error' was not a string");

                    return Err(Error::new(ErrorKind::Other, e))
                },
                None => {}
            }

            // Panic here if we don't see what we expect, since the API shouldn't change underneath us
            let framerate = resp_json
                .lookup("gfyItem.frameRate").expect("gfycat API error - could not find 'frameRate'")
                .as_f64().expect("gfycat API error - 'framerate' was not a f64");
            let frames = resp_json
                .lookup("gfyItem.numFrames").expect("gfycat API error - could not find 'numFrames'")
                .as_u64().expect("gfycat API error - 'numFrames' was not a u64");
            let nsfw = resp_json
                .lookup("gfyItem.nsfw").expect("gfycat API error - could not find 'nsfw'")
                .as_string().map_or("", |s| if s == "1" { "NSFW " } else { "" });
            let size = resp_json
                .lookup("gfyItem.webmSize").expect("gfycat API error - could not find 'webmSize'")
                .as_u64().expect("gfycat API error - 'webmSize' was not a u64");
            let title = resp_json
                .lookup("gfyItem.title").expect("gfycat API error - could not find 'title'")
                .as_string().map_or("<unknown>".to_owned(), |s| format!("\"{}\"", s));

            let res = format!("{}{} ({:.2} MB, {:.1} seconds)", nsfw, title, size as f64 / (2.0 * 1024.0 * 1024.0), frames as f64 / framerate);
            entry.insert(res).clone()
        }
    })
}
// Convert Kelvin to Celcius
fn weather_k_to_c(k: f64) -> f64 {
    k - 273.15
}
// Convert Kelvin to Fahrenheit
fn weather_k_to_f(k: f64) -> f64 {
    let c = weather_k_to_c(k);
    1.8 * c + 32.0
}
// Calls into the OpenWeatherMap API to try and get the weather at `loc`
fn weather_handler(wapi_key: &str, loc: &str) -> Result<String, Error> {
    let url = format!("http://api.openweathermap.org/data/2.5/weather?q={}&APPID={}", loc, wapi_key);

    let resp = &try!(http_get(&url))[..];
    let resp_json: Value = match serde_json::from_str(resp) {
        Ok(resp_json) => resp_json,
        Err(e) => return Err(Error::new(ErrorKind::Other, e))
    };

    match resp_json.find("message") {
        Some(e) => {
            let e = e.as_string().expect("OpenWeatherMap API error - 'message' was not a string");

            return Err(Error::new(ErrorKind::Other, e))
        },
        None => {}
    };

    // Panic here if we don't see what we expect, since the API shouldn't change underneath us
    let name = resp_json
        .find("name").expect("OpenWeatherMap API error - could not find 'name'")
        .as_string().expect("OpenWeatherMap API error - 'name' was not a string");
    let description = resp_json
        .find("weather").expect("OpenWeatherMap API error - could not find 'weather'")
        .as_array().expect("OpenWeatherMap API error - 'weather' was not an array")[0]
        .find("description").expect("OpenWeatherMap API error - could not find 'description'")
        .as_string().expect("OpenWeatherMap API error - 'description' was not a string");
    let temp_k = resp_json
        .lookup("main.temp").expect("OpenWeatherMap API error - could not find 'temp'")
        .as_f64().expect("OpenWeatherMap API error - 'temp' was not a number");
    let humidity = resp_json
        .lookup("main.humidity").expect("OpenWeatherMap API error - could not find 'humidity'")
        .as_f64().expect("OpenWeatherMap API error - 'humidity' was not a number");
    let wind_speed = resp_json
        .find("wind").expect("OpenWeatherMap API error - could not find 'wind'")
        .find("speed").expect("OpenWeatherMap API error - could not find 'speed'")
        .as_f64().expect("OpenWeatherMap API error - 'speed' was not a number");

    Ok(format!("{} weather: {:.2} \u{00B0}F ({:.2} \u{00B0}C), {}% humidity, {} and wind {:.2} m/s",
               name, weather_k_to_f(temp_k), weather_k_to_c(temp_k), humidity, description, wind_speed))
}
// Parses an ISO 8601 duration string into a human-readable duration
// e.g. "3M20S" -> 3 minutes 20 seconds
// TODO: Is there a package since I made this that handles ISO time well?
fn yt_parse_time(s: &str) -> String {
    let mut in_time = false;
    let mut start_idx: Option<usize> = None;
    let mut prev_char: char = '\u{0000}';
    let mut length = String::new();

    for (idx, c) in s.chars().enumerate() {
        match c {
            '0' ... '9' => {prev_char = c; continue}
            'P' => start_idx = Some(idx + 1),
            'T' => {
                start_idx = Some(idx + 1);
                in_time = true
            },
            _ => {
                if length.len() > 0 {
                    length.push_str(", ");
                }

                length.push_str(&s[start_idx.unwrap() .. idx]);
                length.push(' ');
                length.push_str(match c {
                    'Y' => "year",
                    'M' => if in_time {"minute"} else {"month"},
                    'D' => "day",
                    'H' => "hour",
                    'S' => "second",
                    _ => panic!("Unexpected character in ISO 8601 duration string")
                });

                if idx - start_idx.unwrap() > 1 || prev_char != '1' {
                    length.push('s');
                }

                start_idx = Some(idx + 1);
            }
        }
    }

    length
}
// Calls into Google's Youtube API to determine video duration, title, and channel title, or accesses that data from a cache
fn yt_handler(gapi_key: &str, cache: &mut HashMap<String, String>, id: &str) -> Result<String, Error> {
    Ok(match cache.entry(id.to_owned()) {
        Entry::Occupied(entry) => entry.get().clone(),
        Entry::Vacant(entry) => {
            let url = format!("https://www.googleapis.com/youtube/v3/videos?id={}&key={}&part=snippet,contentDetails&fields=items(snippet/title,snippet/channelTitle,contentDetails/duration)", id, gapi_key);

            let resp = &try!(http_get(&url))[..];
            let resp_json: Value = match serde_json::from_str(resp) {
                Ok(resp_json) => resp_json,
                Err(e) => return Err(Error::new(ErrorKind::Other, e))
            };

            // I chose to panic here on any errors, since if we get a response from Youtube
            // it should be of the correct form - their API shouldn't change underneath us
            let ref items = resp_json
                .find("items").expect("Youtube API error - could not find 'items'")
                .as_array().expect("Youtube API error - 'items' was not an array")[0];
            let snippet = items
                .find("snippet").expect("Youtube API error - could not find 'snippet'");
            let title = snippet
                .find("title").expect("Youtube API error - could not find 'title'")
                .as_string().expect("Youtube API error - 'title' was not a string");
            let channel_title = snippet
                .find("channelTitle").expect("Youtube API error - could not find 'channelTitle'")
                .as_string().expect("Youtube API error - 'channelTitle' was not a string");
            let duration = items
                .find("contentDetails").expect("Youtube API error - could not find 'contentDetails'")
                .find("duration").expect("Youtube API error - could not find 'duration'")
                .as_string().expect("Youtube API error - 'duration' was not a string");

            let res = format!("\"{}\" by {} ({})", title, channel_title, yt_parse_time(duration));
            entry.insert(res).clone()
        }
    })
}
// Calls into XKCD's API to determine comic name and date from a comic ID
fn xkcd_handler(cache: &mut HashMap<String, String>, id: &str) -> Result<String, Error> {
    Ok(match cache.entry(id.to_owned()) {
        Entry::Occupied(entry) => entry.get().clone(),
        Entry::Vacant(entry) => {
            let url = format!("https://xkcd.com/{}/info.0.json", id);

            let resp = &try!(http_get(&url))[..];
            let resp_json: Value = match serde_json::from_str(resp) {
                Ok(resp_json) => resp_json,
                Err(e) => return Err(Error::new(ErrorKind::Other, e))
            };

            let title = resp_json
                .find("title").expect("XKCD API error - could not find 'title'")
                .as_string().expect("XKCD API error - 'title' was not a string");
            let year = resp_json
                .find("year").expect("XKCD API error - could not find 'year'")
                .as_string().expect("XKCD API error - 'year' was not a string");
            let month = resp_json
                .find("month").expect("XKCD API error - could not find 'month'")
                .as_string().expect("XKCD API error - 'month' was not a string");
            let day = resp_json
                .find("day").expect("XKCD API error - could not find 'day'")
                .as_string().expect("XKCD API error - 'day' was not a string");

            let res = format!("{} ({}-{}-{})", title, year, month, day);
            entry.insert(res).clone()
        }
    })
}

fn file_get(p: &Path) -> Result<Vec<u8>, Error> {
    Ok(try!(File::open(p)).bytes().map(|b| b.unwrap()).collect::<Vec<u8>>())
}
// Pulls down the content of a request to a URL as a string
pub fn http_get(url: &str) -> Result<String, Error> {
    let client = Client::new();
    let mut resp = match client.get(url).send() {
        Ok(resp) => resp,
        Err(e) => return Err(Error::new(ErrorKind::Other, e))
    };

    match resp.status {
        StatusCode::Ok => {
            let mut body = String::new();
            try!(resp.read_to_string(&mut body));
            Ok(body)
        },
        StatusCode::BadRequest => Err(Error::new(ErrorKind::Other, "HTTP Bad Request")),
        StatusCode::NotFound => Err(Error::new(ErrorKind::Other, "HTTP Not Found")),
        _ => Err(Error::new(ErrorKind::Other, format!("{:?}", resp.status)))
    }
}

fn main() {
    let logger_config = fern::DispatchConfig {
        format: Box::new(|msg, level, _| format!("[{}] {}", level, msg)),
        output: vec![fern::OutputConfig::stdout(),
                     fern::OutputConfig::file(Path::new("log.log"))],
        level: log::LogLevelFilter::Warn,
    };
    if let Err(e) = fern::init_global_logger(logger_config, log::LogLevelFilter::Trace) {
        panic!("Failed to initialize logger: {}", e);
    }

    let mut regex_match = RegexMatch::new();

    match file_get(Path::new("gapi_key.dat")).map(String::from_utf8) {
        Ok(Ok(gapi_key)) => {
            let mut cache = HashMap::new();
            regex_match.add((Regex::new(r"(?:(?:youtube\.com/watch\?\S*?v=)|(?:youtu\.be/))([\w-]+)").unwrap(),
                             Box::new(move |res| yt_handler(&gapi_key, &mut cache, res))));
        },
        Ok(Err(utf_err)) => error!("gapi_key.dat formatting error: {}", utf_err),
        Err(io_err) => warn!("gapi_key.dat should contain a Google API key to use youtube functionality: {}", io_err),
    }
    match file_get(Path::new("wapi_key.dat")).map(String::from_utf8) {
        Ok(Ok(wapi_key)) => {
            regex_match.add((Regex::new(r"^!weather\s*(.+?)\s*$").unwrap(),
                             Box::new(move |res| weather_handler(&wapi_key, res))));
        },
        Ok(Err(utf_err)) => error!("wapi_key.dat formatting error: {}", utf_err),
        Err(io_err) => warn!("wapi_key.dat should contain an OpenWeatherMap API key to use weather functionality: {}", io_err),
    }

    let mut gfycat_cache = HashMap::new();
    regex_match.add((Regex::new(r"gfycat\.com/((?:[A-Z][a-z]+){3})").unwrap(),
                     Box::new(move |res| gfycat_handler(&mut gfycat_cache, res))));
    let mut xkcd_cache = HashMap::new();
    regex_match.add((Regex::new(r"xkcd\.com/(\d+)").unwrap(),
                     Box::new(move |res| xkcd_handler(&mut xkcd_cache, res))));

    let mut bot = Bot::new(Config::load(Path::new("config.json")).unwrap()).unwrap();

    bot.add_subscriber(&mut regex_match);
    bot.loop_forever();
}
<|endoftext|>


<commit_before>extern crate iron;
extern crate lettre;
extern crate openssl;
extern crate rand;
extern crate redis;
extern crate router;
extern crate rustc_serialize;
extern crate serde_json;
extern crate time;
extern crate url;
extern crate urlencoded;

use iron::headers::ContentType;
use iron::middleware::Handler;
use iron::prelude::*;
use iron::status;
use lettre::email::EmailBuilder;
use lettre::transport::smtp::SmtpTransportBuilder;
use lettre::transport::EmailTransport;
use urlencoded::{UrlEncodedBody, UrlEncodedQuery};
use openssl::bn::BigNum;
use openssl::crypto::pkey::PKey;
use openssl::crypto::hash;
use serde_json::builder::ObjectBuilder;
use serde_json::value::Value;
use redis::{Client, Commands, RedisResult};
use router::Router;
use rustc_serialize::base64::{self, ToBase64};
use std::collections::HashMap;
use std::error::Error;
use std::fs::File;
use std::io::BufReader;
use time::now_utc;
use url::percent_encoding::{utf8_percent_encode, QUERY_ENCODE_SET};

fn json_response(obj: &Value) -> IronResult<Response> {
    let content = serde_json::to_string(&obj).unwrap();
    let mut rsp = Response::with((status::Ok, content));
    rsp.headers.set(ContentType::json());
    return Ok(rsp);
}

struct WelcomeHandler { app: AppConfig }
impl Handler for WelcomeHandler {
    fn handle(&self, _: &mut Request) -> IronResult<Response> {
        return json_response(&ObjectBuilder::new()
            .insert("ladaemon", "Welcome")
            .insert("version", &self.app.version)
            .unwrap());
    }
}

struct OIDConfigHandler { app: AppConfig }
impl Handler for OIDConfigHandler {
    fn handle(&self, _: &mut Request) -> IronResult<Response> {
        return json_response(&ObjectBuilder::new()
            .insert("issuer", &self.app.base_url)
            .insert("authorization_endpoint",
                    format!("{}/auth", self.app.base_url))
            .insert("jwks_uri", format!("{}/keys.json", self.app.base_url))
            .insert("scopes_supported", vec!["openid", "email"])
            .insert("claims_supported",
                    vec!["aud", "email", "email_verified", "exp", "iat", "iss", "sub"])
            .insert("response_types_supported", vec!["id_token"])
            .insert("response_modes_supported", vec!["form_post"])
            .insert("grant_types_supported", vec!["implicit"])
            .insert("subject_types_supported", vec!["public"])
            .insert("id_token_signing_alg_values_supported", vec!["RS256"])
            .unwrap());
    }
}

#[derive(Clone)]
struct AppConfig {
    version: String,
    base_url: String,
    priv_key: PKey,
    store: Client,
    expire_keys: usize,
    sender: String,
    token_validity: i64,
}

fn json_big_num(n: &BigNum) -> String {
    return n.to_vec().to_base64(base64::URL_SAFE);
}

struct KeysHandler { app: AppConfig }
impl Handler for KeysHandler {
    fn handle(&self, _: &mut Request) -> IronResult<Response> {
        let rsa = self.app.priv_key.get_rsa();
        return json_response(&ObjectBuilder::new()
            .insert_array("keys", |builder| {
                builder.push_object(|builder| {
                    builder.insert("kty", "RSA")
                        .insert("alg", "RS256")
                        .insert("use", "sig")
                        .insert("kid", "base")
                        .insert("n", json_big_num(&rsa.n().unwrap()))
                        .insert("e", json_big_num(&rsa.e().unwrap()))
                })
            })
            .unwrap());
    }
}

const CODE_CHARS: &'static [char] = &[
    '2', '3', '4', '6', '7', '9', 'a', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k',
    'm', 'n', 'p', 'q', 'r', 't', 'v', 'w', 'x', 'y', 'z',
];

struct AuthHandler { app: AppConfig }
impl Handler for AuthHandler {
    fn handle(&self, req: &mut Request) -> IronResult<Response> {

        let chars: String = (0..6).map(|_| CODE_CHARS[rand::random::<usize>() % CODE_CHARS.len()]).collect();
        let params = req.get_ref::<UrlEncodedBody>().unwrap();
        let email_addr = &params.get("login_hint").unwrap()[0];
        let client_id = &params.get("client_id").unwrap()[0];
        let key = format!("{}:{}", email_addr, client_id);
        let set_res: RedisResult<String> = self.app.store.hset_multiple(key.clone(), &[
            ("code", chars.clone()),
            ("redirect", params.get("redirect_uri").unwrap()[0].clone()),
        ]);
        let exp_res: RedisResult<bool> = self.app.store.expire(key.clone(), self.app.expire_keys);

        let email = EmailBuilder::new()
            .to(email_addr.as_str())
            .from(self.app.sender.as_str())
            .body(&format!("code: {}", chars))
            .subject("Your login code")
            .build().unwrap();
        let mut mailer = SmtpTransportBuilder::localhost().unwrap().build();
        let result = mailer.send(email);
        mailer.close();

        let href = format!("{}/confirm?email={}&origin={}&code={}",
                           self.app.base_url,
                           utf8_percent_encode(email_addr, QUERY_ENCODE_SET),
                           utf8_percent_encode(client_id, QUERY_ENCODE_SET),
                           utf8_percent_encode(&chars, QUERY_ENCODE_SET));
        let mut obj = ObjectBuilder::new()
            .insert("code", chars)
            .insert("href", href);
        if !result.is_ok() {
            let error = result.unwrap_err();
            obj = obj.insert("error", error.to_string());
            obj = match error {
                lettre::transport::error::Error::IoError(inner) => {
                    obj.insert("cause", inner.description())
                }
                _ => obj,
            }
        }
        if !set_res.is_ok() {
            obj = obj.insert("hset_multiple", set_res.unwrap_err().to_string());
        }
        if !exp_res.is_ok() {
            obj = obj.insert("expire", exp_res.unwrap_err().to_string());
        }
        return json_response(&obj.unwrap());

    }
}

fn create_jwt(key: &PKey, header: &str, payload: &str) -> String {
    let mut input = Vec::<u8>::new();
    input.extend(header.as_bytes().to_base64(base64::URL_SAFE).into_bytes());
    input.push(b'.');
    input.extend(payload.as_bytes().to_base64(base64::URL_SAFE).into_bytes());
    let sha256 = hash::hash(hash::Type::SHA256, &input);
    let sig = key.sign(&sha256);
    input.push(b'.');
    input.extend(sig.to_base64(base64::URL_SAFE).into_bytes());
    return String::from_utf8(input).unwrap();
}

const FORWARD_TEMPLATE: &'static str = r#"<!DOCTYPE html>
<html>
  <head>
    <title>Let's Auth</title>
    <script>
      document.addEventListener('DOMContentLoaded', function() {
        document.getElementById('form').submit();
      });
    </script>
  </head>
  <body>
    <form id="form" action="{{ return_url }}" method="post">
      <input type="hidden" name="id_token" value="{{ jwt }}">
    </form>
  </body>
</html>"#;

struct ConfirmHandler { app: AppConfig }
impl Handler for ConfirmHandler {
    fn handle(&self, req: &mut Request) -> IronResult<Response> {

        let params = req.get_ref::<UrlEncodedQuery>().unwrap();
        let email = &params.get("email").unwrap()[0];
        let origin = &params.get("origin").unwrap()[0];
        let key = format!("{}:{}", email, origin);
        let stored: HashMap<String, String> = self.app.store.hgetall(key.clone()).unwrap();

        let req_code = &params.get("code").unwrap()[0];
        if stored.get("code").is_none() || req_code != stored.get("code").unwrap() {
            let mut obj = ObjectBuilder::new().insert("error", "code fail");
            obj = obj.insert("stored", stored);
            return json_response(&obj.unwrap());
        }

        let now = now_utc().to_timespec().sec;
        let payload = ObjectBuilder::new()
            .insert("aud", origin)
            .insert("email", email)
            .insert("email_verified", email)
            .insert("exp", now + self.app.token_validity)
            .insert("iat", now)
            .insert("iss", &self.app.base_url)
            .insert("sub", email)
            .unwrap();
        let headers = ObjectBuilder::new()
            .insert("kid", "base")
            .insert("alg", "RS256")
            .unwrap();
        let jwt = create_jwt(&self.app.priv_key,
                             &serde_json::to_string(&headers).unwrap(),
                             &serde_json::to_string(&payload).unwrap());

        let redirect = stored.get("redirect").unwrap();
        let html = FORWARD_TEMPLATE.replace("{{ return_url }}", redirect)
            .replace("{{ jwt }}", &jwt);

        let mut rsp = Response::with((status::Ok, html));
        rsp.headers.set(ContentType::html());
        return Ok(rsp);

    }
}

fn main() {

    let priv_key_file = File::open("private.pem").unwrap();
    let mut reader = BufReader::new(priv_key_file);
    let app = AppConfig {
        version: env!("CARGO_PKG_VERSION").to_string(),
        base_url: "https://letsauth.xavamedia.nl".to_string(),
        priv_key: PKey::private_key_from_pem(&mut reader).unwrap(),
        store: Client::open("redis://127.0.0.1/5").unwrap(),
        expire_keys: 60 * 15,
        sender: "Let's Auth <letsauth@xavamedia.nl>".to_string(),
        token_validity: 60 * 10,
    };

    let mut router = Router::new();
    router.get("/", WelcomeHandler { app: app.clone() });
    router.get("/.well-known/openid-configuration",
               OIDConfigHandler { app: app.clone() });
    router.get("/keys.json", KeysHandler { app: app.clone() });
    router.post("/auth", AuthHandler { app: app.clone() });
    router.get("/confirm", ConfirmHandler { app: app.clone() });
    Iron::new(router).http("0.0.0.0:8000").unwrap();

}
<commit_msg>Run on port 3333 for now<commit_after>extern crate iron;
extern crate lettre;
extern crate openssl;
extern crate rand;
extern crate redis;
extern crate router;
extern crate rustc_serialize;
extern crate serde_json;
extern crate time;
extern crate url;
extern crate urlencoded;

use iron::headers::ContentType;
use iron::middleware::Handler;
use iron::prelude::*;
use iron::status;
use lettre::email::EmailBuilder;
use lettre::transport::smtp::SmtpTransportBuilder;
use lettre::transport::EmailTransport;
use urlencoded::{UrlEncodedBody, UrlEncodedQuery};
use openssl::bn::BigNum;
use openssl::crypto::pkey::PKey;
use openssl::crypto::hash;
use serde_json::builder::ObjectBuilder;
use serde_json::value::Value;
use redis::{Client, Commands, RedisResult};
use router::Router;
use rustc_serialize::base64::{self, ToBase64};
use std::collections::HashMap;
use std::error::Error;
use std::fs::File;
use std::io::BufReader;
use time::now_utc;
use url::percent_encoding::{utf8_percent_encode, QUERY_ENCODE_SET};

fn json_response(obj: &Value) -> IronResult<Response> {
    let content = serde_json::to_string(&obj).unwrap();
    let mut rsp = Response::with((status::Ok, content));
    rsp.headers.set(ContentType::json());
    return Ok(rsp);
}

struct WelcomeHandler { app: AppConfig }
impl Handler for WelcomeHandler {
    fn handle(&self, _: &mut Request) -> IronResult<Response> {
        return json_response(&ObjectBuilder::new()
            .insert("ladaemon", "Welcome")
            .insert("version", &self.app.version)
            .unwrap());
    }
}

struct OIDConfigHandler { app: AppConfig }
impl Handler for OIDConfigHandler {
    fn handle(&self, _: &mut Request) -> IronResult<Response> {
        return json_response(&ObjectBuilder::new()
            .insert("issuer", &self.app.base_url)
            .insert("authorization_endpoint",
                    format!("{}/auth", self.app.base_url))
            .insert("jwks_uri", format!("{}/keys.json", self.app.base_url))
            .insert("scopes_supported", vec!["openid", "email"])
            .insert("claims_supported",
                    vec!["aud", "email", "email_verified", "exp", "iat", "iss", "sub"])
            .insert("response_types_supported", vec!["id_token"])
            .insert("response_modes_supported", vec!["form_post"])
            .insert("grant_types_supported", vec!["implicit"])
            .insert("subject_types_supported", vec!["public"])
            .insert("id_token_signing_alg_values_supported", vec!["RS256"])
            .unwrap());
    }
}

#[derive(Clone)]
struct AppConfig {
    version: String,
    base_url: String,
    priv_key: PKey,
    store: Client,
    expire_keys: usize,
    sender: String,
    token_validity: i64,
}

fn json_big_num(n: &BigNum) -> String {
    return n.to_vec().to_base64(base64::URL_SAFE);
}

struct KeysHandler { app: AppConfig }
impl Handler for KeysHandler {
    fn handle(&self, _: &mut Request) -> IronResult<Response> {
        let rsa = self.app.priv_key.get_rsa();
        return json_response(&ObjectBuilder::new()
            .insert_array("keys", |builder| {
                builder.push_object(|builder| {
                    builder.insert("kty", "RSA")
                        .insert("alg", "RS256")
                        .insert("use", "sig")
                        .insert("kid", "base")
                        .insert("n", json_big_num(&rsa.n().unwrap()))
                        .insert("e", json_big_num(&rsa.e().unwrap()))
                })
            })
            .unwrap());
    }
}

const CODE_CHARS: &'static [char] = &[
    '2', '3', '4', '6', '7', '9', 'a', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k',
    'm', 'n', 'p', 'q', 'r', 't', 'v', 'w', 'x', 'y', 'z',
];

struct AuthHandler { app: AppConfig }
impl Handler for AuthHandler {
    fn handle(&self, req: &mut Request) -> IronResult<Response> {

        let chars: String = (0..6).map(|_| CODE_CHARS[rand::random::<usize>() % CODE_CHARS.len()]).collect();
        let params = req.get_ref::<UrlEncodedBody>().unwrap();
        let email_addr = &params.get("login_hint").unwrap()[0];
        let client_id = &params.get("client_id").unwrap()[0];
        let key = format!("{}:{}", email_addr, client_id);
        let set_res: RedisResult<String> = self.app.store.hset_multiple(key.clone(), &[
            ("code", chars.clone()),
            ("redirect", params.get("redirect_uri").unwrap()[0].clone()),
        ]);
        let exp_res: RedisResult<bool> = self.app.store.expire(key.clone(), self.app.expire_keys);

        let email = EmailBuilder::new()
            .to(email_addr.as_str())
            .from(self.app.sender.as_str())
            .body(&format!("code: {}", chars))
            .subject("Your login code")
            .build().unwrap();
        let mut mailer = SmtpTransportBuilder::localhost().unwrap().build();
        let result = mailer.send(email);
        mailer.close();

        let href = format!("{}/confirm?email={}&origin={}&code={}",
                           self.app.base_url,
                           utf8_percent_encode(email_addr, QUERY_ENCODE_SET),
                           utf8_percent_encode(client_id, QUERY_ENCODE_SET),
                           utf8_percent_encode(&chars, QUERY_ENCODE_SET));
        let mut obj = ObjectBuilder::new()
            .insert("code", chars)
            .insert("href", href);
        if !result.is_ok() {
            let error = result.unwrap_err();
            obj = obj.insert("error", error.to_string());
            obj = match error {
                lettre::transport::error::Error::IoError(inner) => {
                    obj.insert("cause", inner.description())
                }
                _ => obj,
            }
        }
        if !set_res.is_ok() {
            obj = obj.insert("hset_multiple", set_res.unwrap_err().to_string());
        }
        if !exp_res.is_ok() {
            obj = obj.insert("expire", exp_res.unwrap_err().to_string());
        }
        return json_response(&obj.unwrap());

    }
}

fn create_jwt(key: &PKey, header: &str, payload: &str) -> String {
    let mut input = Vec::<u8>::new();
    input.extend(header.as_bytes().to_base64(base64::URL_SAFE).into_bytes());
    input.push(b'.');
    input.extend(payload.as_bytes().to_base64(base64::URL_SAFE).into_bytes());
    let sha256 = hash::hash(hash::Type::SHA256, &input);
    let sig = key.sign(&sha256);
    input.push(b'.');
    input.extend(sig.to_base64(base64::URL_SAFE).into_bytes());
    return String::from_utf8(input).unwrap();
}

const FORWARD_TEMPLATE: &'static str = r#"<!DOCTYPE html>
<html>
  <head>
    <title>Let's Auth</title>
    <script>
      document.addEventListener('DOMContentLoaded', function() {
        document.getElementById('form').submit();
      });
    </script>
  </head>
  <body>
    <form id="form" action="{{ return_url }}" method="post">
      <input type="hidden" name="id_token" value="{{ jwt }}">
    </form>
  </body>
</html>"#;

struct ConfirmHandler { app: AppConfig }
impl Handler for ConfirmHandler {
    fn handle(&self, req: &mut Request) -> IronResult<Response> {

        let params = req.get_ref::<UrlEncodedQuery>().unwrap();
        let email = &params.get("email").unwrap()[0];
        let origin = &params.get("origin").unwrap()[0];
        let key = format!("{}:{}", email, origin);
        let stored: HashMap<String, String> = self.app.store.hgetall(key.clone()).unwrap();

        let req_code = &params.get("code").unwrap()[0];
        if stored.get("code").is_none() || req_code != stored.get("code").unwrap() {
            let mut obj = ObjectBuilder::new().insert("error", "code fail");
            obj = obj.insert("stored", stored);
            return json_response(&obj.unwrap());
        }

        let now = now_utc().to_timespec().sec;
        let payload = ObjectBuilder::new()
            .insert("aud", origin)
            .insert("email", email)
            .insert("email_verified", email)
            .insert("exp", now + self.app.token_validity)
            .insert("iat", now)
            .insert("iss", &self.app.base_url)
            .insert("sub", email)
            .unwrap();
        let headers = ObjectBuilder::new()
            .insert("kid", "base")
            .insert("alg", "RS256")
            .unwrap();
        let jwt = create_jwt(&self.app.priv_key,
                             &serde_json::to_string(&headers).unwrap(),
                             &serde_json::to_string(&payload).unwrap());

        let redirect = stored.get("redirect").unwrap();
        let html = FORWARD_TEMPLATE.replace("{{ return_url }}", redirect)
            .replace("{{ jwt }}", &jwt);

        let mut rsp = Response::with((status::Ok, html));
        rsp.headers.set(ContentType::html());
        return Ok(rsp);

    }
}

fn main() {

    let priv_key_file = File::open("private.pem").unwrap();
    let mut reader = BufReader::new(priv_key_file);
    let app = AppConfig {
        version: env!("CARGO_PKG_VERSION").to_string(),
        base_url: "https://letsauth.xavamedia.nl".to_string(),
        priv_key: PKey::private_key_from_pem(&mut reader).unwrap(),
        store: Client::open("redis://127.0.0.1/5").unwrap(),
        expire_keys: 60 * 15,
        sender: "Let's Auth <letsauth@xavamedia.nl>".to_string(),
        token_validity: 60 * 10,
    };

    let mut router = Router::new();
    router.get("/", WelcomeHandler { app: app.clone() });
    router.get("/.well-known/openid-configuration",
               OIDConfigHandler { app: app.clone() });
    router.get("/keys.json", KeysHandler { app: app.clone() });
    router.post("/auth", AuthHandler { app: app.clone() });
    router.get("/confirm", ConfirmHandler { app: app.clone() });
    Iron::new(router).http("0.0.0.0:3333").unwrap();

}
<|endoftext|>


<commit_before><commit_msg>obey clippy<commit_after><|endoftext|>


<commit_before><commit_msg>Fixed indentation.<commit_after><|endoftext|>


<commit_before><commit_msg>Improved json decoding<commit_after><|endoftext|>


<commit_before><commit_msg>Shorter and even more brainfucking conversions with UFC<commit_after><|endoftext|>


<commit_before><commit_msg>Add a std ping pong benchmark for comparison<commit_after><|endoftext|>


<commit_before>// Copyright (c) 2018 Chef Software Inc. and/or applicable contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

include!("../generated/butterfly.swim.rs");

pub use self::{membership::Health, swim::Payload as SwimPayload, swim::Type as SwimType};

#[cfg(test)]
mod tests {
    use super::*;

    // These tests assure that we can properly compare Health values
    // along the spectrum of
    //
    //   Alive -> Suspect -> Confirmed -> Departed
    //
    // since that is important in our decision whether or not to
    // propagate membership rumors.

    #[test]
    fn alive_is_less_than_everything() {
        assert!(Health::Alive == Health::Alive);
        assert!(Health::Alive < Health::Suspect);
        assert!(Health::Alive < Health::Confirmed);
        assert!(Health::Alive < Health::Departed);
    }

    #[test]
    fn suspect_is_greater_than_alive_and_less_than_everything_else() {
        assert!(Health::Suspect > Health::Alive);
        assert!(Health::Suspect == Health::Suspect);
        assert!(Health::Suspect < Health::Confirmed);
        assert!(Health::Suspect < Health::Departed);
    }

    #[test]
    fn confirmed_is_greater_than_alive_and_suspect_but_less_than_departed() {
        assert!(Health::Confirmed > Health::Alive);
        assert!(Health::Confirmed > Health::Suspect);
        assert!(Health::Confirmed == Health::Confirmed);
        assert!(Health::Confirmed < Health::Departed);
    }

    #[test]
    fn departed_is_greater_than_everything() {
        assert!(Health::Departed > Health::Alive);
        assert!(Health::Departed > Health::Suspect);
        assert!(Health::Departed > Health::Confirmed);
        assert!(Health::Departed == Health::Departed);
    }
}
<commit_msg>[PR REVIEW]: Don't test ordering quite so exhaustively<commit_after>// Copyright (c) 2018 Chef Software Inc. and/or applicable contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

include!("../generated/butterfly.swim.rs");

pub use self::{membership::Health, swim::Payload as SwimPayload, swim::Type as SwimType};

#[cfg(test)]
mod tests {
    use super::*;

    // Theis test assures that we can properly compare Health values
    // along the spectrum of
    //
    //   Alive < Suspect < Confirmed < Departed
    //
    // since that is important in our decision whether or not to
    // propagate membership rumors.
    #[test]
    fn health_is_properly_ordered() {
        assert!(Health::Alive < Health::Suspect);
        assert!(Health::Suspect < Health::Confirmed);
        assert!(Health::Confirmed < Health::Departed);
    }
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! Element nodes.

use dom::bindings::utils::{DOMString, CacheableWrapper};
use dom::clientrect::ClientRect;
use dom::clientrectlist::ClientRectList;
use dom::node::{ElementNodeTypeId, Node, ScriptView};
use layout_interface::{ContentBoxQuery, ContentBoxResponse, ContentBoxesQuery};
use layout_interface::{ContentBoxesResponse};

use std::cell::Cell;
use std::comm::ChanOne;
use std::comm;
use std::uint;
use std::str::eq_slice;
use extra::net::url::Url;
use geom::size::Size2D;

pub struct Element {
    parent: Node<ScriptView>,
    tag_name: ~str,     // TODO: This should be an atom, not a ~str.
    attrs: ~[Attr],
}

#[deriving(Eq)]
pub enum ElementTypeId {
    HTMLAnchorElementTypeId,
    HTMLAsideElementTypeId,
    HTMLBRElementTypeId,
    HTMLBodyElementTypeId,
    HTMLBoldElementTypeId,
    HTMLDivElementTypeId,
    HTMLFontElementTypeId,
    HTMLFormElementTypeId,
    HTMLHRElementTypeId,
    HTMLHeadElementTypeId,
    HTMLHeadingElementTypeId,
    HTMLHtmlElementTypeId,
    HTMLIframeElementTypeId,
    HTMLImageElementTypeId,
    HTMLInputElementTypeId,
    HTMLItalicElementTypeId,
    HTMLLinkElementTypeId,
    HTMLListItemElementTypeId,
    HTMLMetaElementTypeId,
    HTMLOListElementTypeId,
    HTMLOptionElementTypeId,
    HTMLParagraphElementTypeId,
    HTMLScriptElementTypeId,
    HTMLSectionElementTypeId,
    HTMLSelectElementTypeId,
    HTMLSmallElementTypeId,
    HTMLSpanElementTypeId,
    HTMLStyleElementTypeId,
    HTMLTableBodyElementTypeId,
    HTMLTableCellElementTypeId,
    HTMLTableElementTypeId,
    HTMLTableRowElementTypeId,
    HTMLTitleElementTypeId,
    HTMLUListElementTypeId,
    UnknownElementTypeId,
}

//
// Regular old elements
//

pub struct HTMLAnchorElement    { parent: Element }
pub struct HTMLAsideElement     { parent: Element }
pub struct HTMLBRElement        { parent: Element }
pub struct HTMLBodyElement      { parent: Element }
pub struct HTMLBoldElement      { parent: Element }
pub struct HTMLDivElement       { parent: Element }
pub struct HTMLFontElement      { parent: Element }
pub struct HTMLFormElement      { parent: Element }
pub struct HTMLHRElement        { parent: Element }
pub struct HTMLHeadElement      { parent: Element }
pub struct HTMLHtmlElement      { parent: Element }
pub struct HTMLInputElement     { parent: Element }
pub struct HTMLItalicElement    { parent: Element }
pub struct HTMLLinkElement      { parent: Element }
pub struct HTMLListItemElement  { parent: Element }
pub struct HTMLMetaElement      { parent: Element }
pub struct HTMLOListElement     { parent: Element }
pub struct HTMLOptionElement    { parent: Element }
pub struct HTMLParagraphElement { parent: Element }
pub struct HTMLScriptElement    { parent: Element }
pub struct HTMLSectionElement   { parent: Element }
pub struct HTMLSelectElement    { parent: Element }
pub struct HTMLSmallElement     { parent: Element }
pub struct HTMLSpanElement      { parent: Element }
pub struct HTMLStyleElement     { parent: Element }
pub struct HTMLTableBodyElement { parent: Element }
pub struct HTMLTableCellElement { parent: Element }
pub struct HTMLTableElement     { parent: Element }
pub struct HTMLTableRowElement  { parent: Element }
pub struct HTMLTitleElement     { parent: Element }
pub struct HTMLUListElement     { parent: Element }
pub struct UnknownElement       { parent: Element }

//
// Fancier elements
//

pub struct HTMLHeadingElement {
    parent: Element,
    level: HeadingLevel,
}

pub struct HTMLIframeElement {
    parent: Element,
    frame: Option<Url>,
    size_future_chan: Option<ChanOne<Size2D<uint>>>,
}

pub struct HTMLImageElement {
    parent: Element,
    image: Option<Url>,
}

//
// Element methods
//

impl<'self> Element {
    pub fn new(type_id: ElementTypeId, tag_name: ~str) -> Element {
        Element {
            parent: Node::new(ElementNodeTypeId(type_id)),
            tag_name: tag_name,
            attrs: ~[]
        }
    }

    pub fn get_attr(&'self self, name: &str) -> Option<&'self str> {
        // FIXME: Need an each() that links lifetimes in Rust.
        for uint::range(0, self.attrs.len()) |i| {
            if eq_slice(self.attrs[i].name, name) {
                let val: &str = self.attrs[i].value;
                return Some(val);
            }
        }
        return None;
    }

    pub fn set_attr(&mut self, name: &DOMString, value: &DOMString) {
        let name = name.to_str();
        let value = value.to_str();
        // FIXME: We need a better each_mut in Rust; this is ugly.
        let value_cell = Cell::new(value);
        let mut found = false;
        for uint::range(0, self.attrs.len()) |i| {
            if eq_slice(self.attrs[i].name, name) {
                self.attrs[i].value = value_cell.take().clone();
                found = true;
                break;
            }
        }
        if !found {
            self.attrs.push(Attr::new(name.to_str(), value_cell.take().clone()));
        }

        match self.parent.owner_doc {
            Some(owner) => owner.content_changed(),
            None => {}
        }
    }

    pub fn getClientRects(&self) -> Option<@mut ClientRectList> {
        let (rects, cx, scope) = match self.parent.owner_doc {
            Some(doc) => {
                match doc.window {
                    Some(win) => {
                        let node = self.parent.abstract.get();
                        assert!(node.is_element());
                        let page = win.page;
                        let (port, chan) = comm::stream();
                        // TODO(tkuehn): currently just queries top-level page layout. Needs to query
                        // subframe layout if this element is in a subframe. Probably need an ID field.
                        match unsafe {(*page).query_layout(ContentBoxesQuery(node, chan), port)} {
                            Ok(ContentBoxesResponse(rects)) => {
                                let cx = unsafe {(*page).js_info.get_ref().js_compartment.cx.ptr};
                                let cache = win.get_wrappercache();
                                let scope = cache.get_wrapper();
                                let rects = do rects.map |r| {
                                    ClientRect::new(
                                         r.origin.y.to_f32(),
                                         (r.origin.y + r.size.height).to_f32(),
                                         r.origin.x.to_f32(),
                                         (r.origin.x + r.size.width).to_f32(),
                                         cx,
                                         scope)
                                };
                                Some((rects, cx, scope))
                            },
                            Err(()) => {
                                debug!("layout query error");
                                None
                            }
                        }
                    }
                    None => {
                        debug!("no window");
                        None
                    }
                }
            }
            None => {
                debug!("no document");
                None
            }
        }.get();

        Some(ClientRectList::new(rects, cx, scope))
    }

    pub fn getBoundingClientRect(&self) -> Option<@mut ClientRect> {
        match self.parent.owner_doc {
            Some(doc) => {
                match doc.window {
                    Some(win) => {
                        let page = win.page;
                        let node = self.parent.abstract.get();
                        assert!(node.is_element());
                        let (port, chan) = comm::stream();
                        match unsafe{(*page).query_layout(ContentBoxQuery(node, chan), port)} {
                            Ok(ContentBoxResponse(rect)) => {
                                let cx = unsafe {(*page).js_info.get_ref().js_compartment.cx.ptr};
                                let cache = win.get_wrappercache();
                                let scope = cache.get_wrapper();
                                Some(ClientRect::new(
                                         rect.origin.y.to_f32(),
                                         (rect.origin.y + rect.size.height).to_f32(),
                                         rect.origin.x.to_f32(),
                                         (rect.origin.x + rect.size.width).to_f32(),
                                         cx,
                                         scope))
                            },
                            Err(()) => {
                                debug!("error querying layout");
                                None
                            }
                        }
                    }
                    None => {
                        debug!("no window");
                        None
                    }
                }
            }
            None => {
                debug!("no document");
                None
            }
        }
    }
}

pub struct Attr {
    name: ~str,
    value: ~str,
}

impl Attr {
    pub fn new(name: ~str, value: ~str) -> Attr {
        Attr {
            name: name,
            value: value
        }
    }
}

pub enum HeadingLevel {
    Heading1,
    Heading2,
    Heading3,
    Heading4,
    Heading5,
    Heading6,
}

<commit_msg>FIXME: uint::range to vec`s iter().advance<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! Element nodes.

use dom::bindings::utils::{DOMString, CacheableWrapper};
use dom::clientrect::ClientRect;
use dom::clientrectlist::ClientRectList;
use dom::node::{ElementNodeTypeId, Node, ScriptView};
use layout_interface::{ContentBoxQuery, ContentBoxResponse, ContentBoxesQuery};
use layout_interface::{ContentBoxesResponse};

use std::cell::Cell;
use std::comm::ChanOne;
use std::comm;
use std::str::eq_slice;
use extra::net::url::Url;
use geom::size::Size2D;

pub struct Element {
    parent: Node<ScriptView>,
    tag_name: ~str,     // TODO: This should be an atom, not a ~str.
    attrs: ~[Attr],
}

#[deriving(Eq)]
pub enum ElementTypeId {
    HTMLAnchorElementTypeId,
    HTMLAsideElementTypeId,
    HTMLBRElementTypeId,
    HTMLBodyElementTypeId,
    HTMLBoldElementTypeId,
    HTMLDivElementTypeId,
    HTMLFontElementTypeId,
    HTMLFormElementTypeId,
    HTMLHRElementTypeId,
    HTMLHeadElementTypeId,
    HTMLHeadingElementTypeId,
    HTMLHtmlElementTypeId,
    HTMLIframeElementTypeId,
    HTMLImageElementTypeId,
    HTMLInputElementTypeId,
    HTMLItalicElementTypeId,
    HTMLLinkElementTypeId,
    HTMLListItemElementTypeId,
    HTMLMetaElementTypeId,
    HTMLOListElementTypeId,
    HTMLOptionElementTypeId,
    HTMLParagraphElementTypeId,
    HTMLScriptElementTypeId,
    HTMLSectionElementTypeId,
    HTMLSelectElementTypeId,
    HTMLSmallElementTypeId,
    HTMLSpanElementTypeId,
    HTMLStyleElementTypeId,
    HTMLTableBodyElementTypeId,
    HTMLTableCellElementTypeId,
    HTMLTableElementTypeId,
    HTMLTableRowElementTypeId,
    HTMLTitleElementTypeId,
    HTMLUListElementTypeId,
    UnknownElementTypeId,
}

//
// Regular old elements
//

pub struct HTMLAnchorElement    { parent: Element }
pub struct HTMLAsideElement     { parent: Element }
pub struct HTMLBRElement        { parent: Element }
pub struct HTMLBodyElement      { parent: Element }
pub struct HTMLBoldElement      { parent: Element }
pub struct HTMLDivElement       { parent: Element }
pub struct HTMLFontElement      { parent: Element }
pub struct HTMLFormElement      { parent: Element }
pub struct HTMLHRElement        { parent: Element }
pub struct HTMLHeadElement      { parent: Element }
pub struct HTMLHtmlElement      { parent: Element }
pub struct HTMLInputElement     { parent: Element }
pub struct HTMLItalicElement    { parent: Element }
pub struct HTMLLinkElement      { parent: Element }
pub struct HTMLListItemElement  { parent: Element }
pub struct HTMLMetaElement      { parent: Element }
pub struct HTMLOListElement     { parent: Element }
pub struct HTMLOptionElement    { parent: Element }
pub struct HTMLParagraphElement { parent: Element }
pub struct HTMLScriptElement    { parent: Element }
pub struct HTMLSectionElement   { parent: Element }
pub struct HTMLSelectElement    { parent: Element }
pub struct HTMLSmallElement     { parent: Element }
pub struct HTMLSpanElement      { parent: Element }
pub struct HTMLStyleElement     { parent: Element }
pub struct HTMLTableBodyElement { parent: Element }
pub struct HTMLTableCellElement { parent: Element }
pub struct HTMLTableElement     { parent: Element }
pub struct HTMLTableRowElement  { parent: Element }
pub struct HTMLTitleElement     { parent: Element }
pub struct HTMLUListElement     { parent: Element }
pub struct UnknownElement       { parent: Element }

//
// Fancier elements
//

pub struct HTMLHeadingElement {
    parent: Element,
    level: HeadingLevel,
}

pub struct HTMLIframeElement {
    parent: Element,
    frame: Option<Url>,
    size_future_chan: Option<ChanOne<Size2D<uint>>>,
}

pub struct HTMLImageElement {
    parent: Element,
    image: Option<Url>,
}

//
// Element methods
//

impl<'self> Element {
    pub fn new(type_id: ElementTypeId, tag_name: ~str) -> Element {
        Element {
            parent: Node::new(ElementNodeTypeId(type_id)),
            tag_name: tag_name,
            attrs: ~[]
        }
    }

    pub fn get_attr(&'self self, name: &str) -> Option<&'self str> {
        // FIXME: Need an each() that links lifetimes in Rust.
        for self.attrs.iter().advance |attr| {
            if eq_slice(attr.name, name) {
                let val: &str = attr.value;
                return Some(val);
            }
        }
        return None;
    }

    pub fn set_attr(&mut self, name: &DOMString, value: &DOMString) {
        let name = name.to_str();
        let value = value.to_str();
        let value_cell = Cell::new(value);
        let mut found = false;
        for self.attrs.mut_iter().advance |attr| {
            if eq_slice(attr.name, name) {
                attr.value = value_cell.take().clone();
                found = true;
                break;
            }
        }
        if !found {
            self.attrs.push(Attr::new(name.to_str(), value_cell.take().clone()));
        }

        match self.parent.owner_doc {
            Some(owner) => owner.content_changed(),
            None => {}
        }
    }

    pub fn getClientRects(&self) -> Option<@mut ClientRectList> {
        let (rects, cx, scope) = match self.parent.owner_doc {
            Some(doc) => {
                match doc.window {
                    Some(win) => {
                        let node = self.parent.abstract.get();
                        assert!(node.is_element());
                        let page = win.page;
                        let (port, chan) = comm::stream();
                        // TODO(tkuehn): currently just queries top-level page layout. Needs to query
                        // subframe layout if this element is in a subframe. Probably need an ID field.
                        match unsafe {(*page).query_layout(ContentBoxesQuery(node, chan), port)} {
                            Ok(ContentBoxesResponse(rects)) => {
                                let cx = unsafe {(*page).js_info.get_ref().js_compartment.cx.ptr};
                                let cache = win.get_wrappercache();
                                let scope = cache.get_wrapper();
                                let rects = do rects.map |r| {
                                    ClientRect::new(
                                         r.origin.y.to_f32(),
                                         (r.origin.y + r.size.height).to_f32(),
                                         r.origin.x.to_f32(),
                                         (r.origin.x + r.size.width).to_f32(),
                                         cx,
                                         scope)
                                };
                                Some((rects, cx, scope))
                            },
                            Err(()) => {
                                debug!("layout query error");
                                None
                            }
                        }
                    }
                    None => {
                        debug!("no window");
                        None
                    }
                }
            }
            None => {
                debug!("no document");
                None
            }
        }.get();

        Some(ClientRectList::new(rects, cx, scope))
    }

    pub fn getBoundingClientRect(&self) -> Option<@mut ClientRect> {
        match self.parent.owner_doc {
            Some(doc) => {
                match doc.window {
                    Some(win) => {
                        let page = win.page;
                        let node = self.parent.abstract.get();
                        assert!(node.is_element());
                        let (port, chan) = comm::stream();
                        match unsafe{(*page).query_layout(ContentBoxQuery(node, chan), port)} {
                            Ok(ContentBoxResponse(rect)) => {
                                let cx = unsafe {(*page).js_info.get_ref().js_compartment.cx.ptr};
                                let cache = win.get_wrappercache();
                                let scope = cache.get_wrapper();
                                Some(ClientRect::new(
                                         rect.origin.y.to_f32(),
                                         (rect.origin.y + rect.size.height).to_f32(),
                                         rect.origin.x.to_f32(),
                                         (rect.origin.x + rect.size.width).to_f32(),
                                         cx,
                                         scope))
                            },
                            Err(()) => {
                                debug!("error querying layout");
                                None
                            }
                        }
                    }
                    None => {
                        debug!("no window");
                        None
                    }
                }
            }
            None => {
                debug!("no document");
                None
            }
        }
    }
}

pub struct Attr {
    name: ~str,
    value: ~str,
}

impl Attr {
    pub fn new(name: ~str, value: ~str) -> Attr {
        Attr {
            name: name,
            value: value
        }
    }
}

pub enum HeadingLevel {
    Heading1,
    Heading2,
    Heading3,
    Heading4,
    Heading5,
    Heading6,
}

<|endoftext|>


<commit_before>use super::*;

#[test]
fn inner_item_smoke() {
    check_at(
        r#"
//- /lib.rs
struct inner {}
fn outer() {
    $0
    fn inner() {}
}
"#,
        expect![[r#"
            block scope
            inner: v
            crate
            inner: t
            outer: v
        "#]],
    );
}

#[test]
fn use_from_crate() {
    check_at(
        r#"
//- /lib.rs
struct Struct;
fn outer() {
    use Struct;
    use crate::Struct as CrateStruct;
    use self::Struct as SelfStruct;
    $0
}
"#,
        expect![[r#"
            block scope
            CrateStruct: t v
            SelfStruct: t v
            Struct: t v
            crate
            Struct: t v
            outer: v
        "#]],
    );
}

#[test]
fn merge_namespaces() {
    check_at(
        r#"
//- /lib.rs
struct name {}
fn outer() {
    fn name() {}

    use name as imported; // should import both `name`s

    $0
}
"#,
        expect![[r#"
            block scope
            imported: t v
            name: v
            crate
            name: t
            outer: v
        "#]],
    );
}
<commit_msg>Add test for nameres in nested blocks<commit_after>use super::*;

#[test]
fn inner_item_smoke() {
    check_at(
        r#"
//- /lib.rs
struct inner {}
fn outer() {
    $0
    fn inner() {}
}
"#,
        expect![[r#"
            block scope
            inner: v
            crate
            inner: t
            outer: v
        "#]],
    );
}

#[test]
fn use_from_crate() {
    check_at(
        r#"
//- /lib.rs
struct Struct;
fn outer() {
    use Struct;
    use crate::Struct as CrateStruct;
    use self::Struct as SelfStruct;
    $0
}
"#,
        expect![[r#"
            block scope
            CrateStruct: t v
            SelfStruct: t v
            Struct: t v
            crate
            Struct: t v
            outer: v
        "#]],
    );
}

#[test]
fn merge_namespaces() {
    check_at(
        r#"
//- /lib.rs
struct name {}
fn outer() {
    fn name() {}

    use name as imported; // should import both `name`s

    $0
}
"#,
        expect![[r#"
            block scope
            imported: t v
            name: v
            crate
            name: t
            outer: v
        "#]],
    );
}

#[test]
fn nested_blocks() {
    check_at(
        r#"
//- /lib.rs
fn outer() {
    struct inner1 {}
    fn inner() {
        use inner1;
        use outer;
        fn inner2() {}
        $0
    }
}
"#,
        expect![[r#"
            block scope
            inner1: t
            inner2: v
            outer: v
            block scope
            inner: v
            inner1: t
            crate
            outer: v
        "#]],
    );
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate clap;
#[macro_use]
extern crate serde_json;

extern crate difference;
extern crate meta;
extern crate serde;
extern crate term;

use std::fs::File;
use std::io::prelude::*;
use std::io;

use clap::{Arg, App};
use difference::{Changeset, Difference};
use term::Terminal;

use meta::{TaskIndex, Task};

const ABOUT: &'static str = r#"
Query differences between the rust-rosetta repository and the Rosetta Code wiki.

This script prints out the name of each task, followed by whether it is implemented online,
locally, or both.

If no tasks are specified, determines the status for all tasks."#;

arg_enum!{
    #[derive(Debug)]
    enum Filter {
        All,
        LocalOnly,
        RemoteOnly,
        Unimplemented
    }
}

impl Default for Filter {
    fn default() -> Self {
        Filter::All
    }
}

/// Prints a colored diff of two strings to the terminal.
fn print_diff<T: ?Sized>(t: &mut T, s1: &str, s2: &str) -> io::Result<()>
where
    T: Terminal,
{
    let changeset = Changeset::new(&s1, &s2, "\n");

    for change in changeset.diffs {
        match change {
            Difference::Same(ref x) => {
                try!(t.reset());
                try!(writeln!(t, " {}", x));
            }
            Difference::Add(ref x) => {
                try!(t.fg(term::color::GREEN));
                for line in x.split("\n") {
                    try!(writeln!(t, "+{}", line));
                }
            }
            Difference::Rem(ref x) => {
                try!(t.fg(term::color::RED));
                for line in x.split("\n") {
                    try!(writeln!(t, "-{}", line));
                }
            }
        }
    }
    try!(t.reset());
    try!(t.flush());
    Ok(())
}

/// Prints a task in a human-readable format.
fn print_task<T: ?Sized>(t: &mut T, task: &Task, diff: bool) -> io::Result<()>
where
    T: Terminal,
{
    try!(t.attr(term::Attr::Bold));
    try!(writeln!(t, "{}", task.title()));
    try!(t.reset());

    try!(write!(t, "Local:"));
    try!(write_status(t, task.local_code().is_some()));

    try!(write!(t, "Remote:"));
    try!(write_status(t, task.remote_code().is_some()));
    try!(writeln!(t, ""));

    if let (Some(ref local_code), Some(ref remote_code)) = (task.local_code(), task.remote_code()) {
        if diff {
            try!(print_diff(t, remote_code, local_code));
        }
    }

    Ok(())
}

/// Writes a boolean as a pretty, human-readable string.
fn write_status<T: ?Sized>(t: &mut T, boolean: bool) -> io::Result<()>
where
    T: Terminal,
{
    try!(t.attr(term::Attr::Bold));

    if boolean {
        try!(t.fg(term::color::GREEN));
        try!(write!(t, " ✔ "))
    } else {
        try!(t.fg(term::color::RED));
        try!(write!(t, " ✘ "));
    }

    try!(t.reset());
    Ok(())
}

fn main() {
    let matches = App::new("coverage")
        .about(ABOUT)
        .version(crate_version!())
        .max_term_width(100)
        .arg(
            Arg::with_name("task")
                .help("The name of a task on the wiki, such as 'K-d tree'")
                .multiple(true),
        )
        .arg(
            Arg::with_name("diff")
                .help("Print diffs of tasks between the local and remote version")
                .long("diff"),
        )
        .arg(
            Arg::with_name("filter")
                .help("Filter tasks printed by the program.")
                .possible_values(&["all", "local", "remote", "unimplemented"])
                .long("filter")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("json-file")
                .help("Dump json to the provided filename")
                .long("json")
                .takes_value(true),
        )
        .get_matches();

    let mut t = term::stdout().unwrap();

    let filter = value_t!(matches.value_of("filter"), Filter)
        .ok()
        .unwrap_or_default();

    let task_index = TaskIndex::create(env!("CARGO_MANIFEST_DIR")).unwrap();

    let tasks = if let Some(tasks) = matches.values_of("task") {
        let task_names = tasks.map(String::from).collect::<Vec<_>>();
        task_index.fetch_tasks(&task_names)
    } else {
        task_index.fetch_all_tasks()
    };

    let tasks = tasks
        .flat_map(|task| {
            let task = task.unwrap();

            match filter {
                Filter::LocalOnly if !task.is_local_only() => return None,
                Filter::RemoteOnly if !task.is_remote_only() => return None,
                Filter::Unimplemented if !task.is_unimplemented() => return None,
                Filter::All | _ => {}
            }

            print_task(&mut *t, &task, matches.is_present("diff")).unwrap();

            if matches.is_present("json-file") {
                let json = json!({
                    "title": task.title(),
                    "url": task.url().to_string(),
                    "local_code": task.local_code(),
                    "remote_code": task.remote_code(),
                });

                Some(json)
            } else {
                None
            }
        })
        .collect::<Vec<_>>();

    if let Some(filename) = matches.value_of("json-file") {
        let mut file = File::create(filename).unwrap();
        file.write_all(serde_json::to_string_pretty(&tasks).unwrap().as_bytes())
            .unwrap();
    }
}
<commit_msg>clippy & rustfmt<commit_after>#[macro_use]
extern crate clap;
#[macro_use]
extern crate serde_json;

extern crate difference;
extern crate meta;
extern crate serde;
extern crate term;

use std::fs::File;
use std::io::prelude::*;
use std::io;

use clap::{App, Arg};
use difference::{Changeset, Difference};
use term::Terminal;

use meta::{Task, TaskIndex};

const ABOUT: &str = r#"
Query differences between the rust-rosetta repository and the Rosetta Code wiki.

This script prints out the name of each task, followed by whether it is implemented online,
locally, or both.

If no tasks are specified, determines the status for all tasks."#;

arg_enum!{
    #[derive(Debug)]
    enum Filter {
        All,
        LocalOnly,
        RemoteOnly,
        Unimplemented
    }
}

impl Default for Filter {
    fn default() -> Self {
        Filter::All
    }
}

/// Prints a colored diff of two strings to the terminal.
fn print_diff<T: ?Sized>(t: &mut T, s1: &str, s2: &str) -> io::Result<()>
where
    T: Terminal,
{
    let changeset = Changeset::new(s1, s2, "\n");

    for change in changeset.diffs {
        match change {
            Difference::Same(ref x) => {
                try!(t.reset());
                try!(writeln!(t, " {}", x));
            }
            Difference::Add(ref x) => {
                try!(t.fg(term::color::GREEN));
                for line in x.split('\n') {
                    try!(writeln!(t, "+{}", line));
                }
            }
            Difference::Rem(ref x) => {
                try!(t.fg(term::color::RED));
                for line in x.split('\n') {
                    try!(writeln!(t, "-{}", line));
                }
            }
        }
    }
    try!(t.reset());
    try!(t.flush());
    Ok(())
}

/// Prints a task in a human-readable format.
fn print_task<T: ?Sized>(t: &mut T, task: &Task, diff: bool) -> io::Result<()>
where
    T: Terminal,
{
    try!(t.attr(term::Attr::Bold));
    try!(writeln!(t, "{}", task.title()));
    try!(t.reset());

    try!(write!(t, "Local:"));
    try!(write_status(t, task.local_code().is_some()));

    try!(write!(t, "Remote:"));
    try!(write_status(t, task.remote_code().is_some()));
    try!(writeln!(t, ""));

    if let (Some(ref local_code), Some(ref remote_code)) = (task.local_code(), task.remote_code()) {
        if diff {
            try!(print_diff(t, remote_code, local_code));
        }
    }

    Ok(())
}

/// Writes a boolean as a pretty, human-readable string.
fn write_status<T: ?Sized>(t: &mut T, boolean: bool) -> io::Result<()>
where
    T: Terminal,
{
    try!(t.attr(term::Attr::Bold));

    if boolean {
        try!(t.fg(term::color::GREEN));
        try!(write!(t, " ✔ "))
    } else {
        try!(t.fg(term::color::RED));
        try!(write!(t, " ✘ "));
    }

    try!(t.reset());
    Ok(())
}

fn main() {
    let matches = App::new("coverage")
        .about(ABOUT)
        .version(crate_version!())
        .max_term_width(100)
        .arg(
            Arg::with_name("task")
                .help("The name of a task on the wiki, such as 'K-d tree'")
                .multiple(true),
        )
        .arg(
            Arg::with_name("diff")
                .help("Print diffs of tasks between the local and remote version")
                .long("diff"),
        )
        .arg(
            Arg::with_name("filter")
                .help("Filter tasks printed by the program.")
                .possible_values(&["all", "local", "remote", "unimplemented"])
                .long("filter")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("json-file")
                .help("Dump json to the provided filename")
                .long("json")
                .takes_value(true),
        )
        .get_matches();

    let mut t = term::stdout().unwrap();

    let filter = value_t!(matches.value_of("filter"), Filter)
        .ok()
        .unwrap_or_default();

    let task_index = TaskIndex::create(env!("CARGO_MANIFEST_DIR")).unwrap();

    let tasks = if let Some(tasks) = matches.values_of("task") {
        let task_names = tasks.map(String::from).collect::<Vec<_>>();
        task_index.fetch_tasks(&task_names)
    } else {
        task_index.fetch_all_tasks()
    };

    let tasks = tasks
        .flat_map(|task| {
            let task = task.unwrap();

            match filter {
                Filter::LocalOnly if !task.is_local_only() => return None,
                Filter::RemoteOnly if !task.is_remote_only() => return None,
                Filter::Unimplemented if !task.is_unimplemented() => return None,
                Filter::All | _ => {}
            }

            print_task(&mut *t, &task, matches.is_present("diff")).unwrap();

            if matches.is_present("json-file") {
                let json = json!({
                    "title": task.title(),
                    "url": task.url().to_string(),
                    "local_code": task.local_code(),
                    "remote_code": task.remote_code(),
                });

                Some(json)
            } else {
                None
            }
        })
        .collect::<Vec<_>>();

    if let Some(filename) = matches.value_of("json-file") {
        let mut file = File::create(filename).unwrap();
        file.write_all(serde_json::to_string_pretty(&tasks).unwrap().as_bytes())
            .unwrap();
    }
}
<|endoftext|>


<commit_before>use clap::ArgMatches;
use snafu::{ResultExt, Snafu};
use std::net::ToSocketAddrs;
use tracing::{info, instrument};
use tracing_bunyan_formatter::{BunyanFormattingLayer, JsonStorageLayer};
use tracing_log::LogTracer;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::{EnvFilter, Registry};
use warp::Filter;

use super::settings::{Error as SettingsError, Settings as CSettings};
use mimir2::{
    adapters::primary::bragi::api::{forward_geocoder, reverse_geocoder, status},
    adapters::primary::bragi::{handlers, routes},
    adapters::secondary::elasticsearch::remote::connection_test_pool,
    domain::ports::secondary::remote::Remote,
};

#[derive(Debug, Snafu)]
pub enum Error {
    #[snafu(display("Could not get connection pool: {}", source))]
    Connection { source: Box<dyn std::error::Error> },

    #[snafu(display("Could not generate settings: {}", source))]
    Settings { source: SettingsError },

    #[snafu(display("Socket Addr Error {}", source))]
    SockAddr { source: std::io::Error },

    #[snafu(display("Addr Resolution Error {}", msg))]
    AddrResolution { msg: String },
}

#[allow(clippy::needless_lifetimes)]
pub async fn run<'a>(matches: &ArgMatches<'a>) -> Result<(), Error> {
    let settings = CSettings::new(matches).context(Settings)?;
    LogTracer::init().expect("Unable to setup log tracer!");

    println!("Config: {:?}", settings);

    // following code mostly from https://betterprogramming.pub/production-grade-logging-in-rust-applications-2c7fffd108a6
    let app_name = concat!(env!("CARGO_PKG_NAME"), "-", env!("CARGO_PKG_VERSION")).to_string();

    let file_appender = tracing_appender::rolling::daily(&settings.logging.path, "mimir.log");
    let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender);

    let bunyan_formatting_layer = BunyanFormattingLayer::new(app_name, non_blocking);
    let subscriber = Registry::default()
        .with(EnvFilter::new("INFO"))
        .with(JsonStorageLayer)
        .with(bunyan_formatting_layer);
    tracing::subscriber::set_global_default(subscriber).expect("tracing subscriber global default");

    run_server(settings).await
}

#[instrument(skip(settings))]
pub async fn run_server(settings: CSettings) -> Result<(), Error> {
    let pool = connection_test_pool()
        .await
        .expect("Elasticsearch Connection Pool");

    let client = pool
        .conn()
        .await
        .expect("Elasticsearch Connection Established");

    let api = reverse_geocoder!(client.clone(), settings.query.clone())
        .or(forward_geocoder!(client.clone(), settings.query))
        .or(status!(client, settings.elasticsearch.url))
        .recover(routes::report_invalid)
        .with(warp::trace::request());

    let host = settings.service.host;
    let port = settings.service.port;
    let addr = (host.as_str(), port);
    let addr = addr
        .to_socket_addrs()
        .context(SockAddr)?
        .next()
        .ok_or(Error::AddrResolution {
            msg: String::from("Cannot resolve addr"),
        })?;

    info!("Serving bragi on {}", addr);

    warp::serve(api).run(addr).await;

    Ok(())
}
<commit_msg>Use configuration's es url<commit_after>use clap::ArgMatches;
use snafu::{ResultExt, Snafu};
use std::net::ToSocketAddrs;
use tracing::{info, instrument};
use tracing_bunyan_formatter::{BunyanFormattingLayer, JsonStorageLayer};
use tracing_log::LogTracer;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::{EnvFilter, Registry};
use warp::Filter;

use super::settings::{Error as SettingsError, Settings as CSettings};
use mimir2::{
    adapters::primary::bragi::api::{forward_geocoder, reverse_geocoder, status},
    adapters::primary::bragi::{handlers, routes},
    adapters::secondary::elasticsearch::remote::connection_pool_url,
    domain::ports::secondary::remote::Remote,
};

#[derive(Debug, Snafu)]
pub enum Error {
    #[snafu(display("Could not get connection pool: {}", source))]
    Connection { source: Box<dyn std::error::Error> },

    #[snafu(display("Could not generate settings: {}", source))]
    Settings { source: SettingsError },

    #[snafu(display("Socket Addr Error {}", source))]
    SockAddr { source: std::io::Error },

    #[snafu(display("Addr Resolution Error {}", msg))]
    AddrResolution { msg: String },
}

#[allow(clippy::needless_lifetimes)]
pub async fn run<'a>(matches: &ArgMatches<'a>) -> Result<(), Error> {
    let settings = CSettings::new(matches).context(Settings)?;
    LogTracer::init().expect("Unable to setup log tracer!");

    // following code mostly from https://betterprogramming.pub/production-grade-logging-in-rust-applications-2c7fffd108a6
    let app_name = concat!(env!("CARGO_PKG_NAME"), "-", env!("CARGO_PKG_VERSION")).to_string();

    let file_appender = tracing_appender::rolling::daily(&settings.logging.path, "mimir.log");
    let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender);

    let bunyan_formatting_layer = BunyanFormattingLayer::new(app_name, non_blocking);
    let subscriber = Registry::default()
        .with(EnvFilter::new("INFO"))
        .with(JsonStorageLayer)
        .with(bunyan_formatting_layer);
    tracing::subscriber::set_global_default(subscriber).expect("tracing subscriber global default");

    run_server(settings).await
}

#[instrument(skip(settings))]
pub async fn run_server(settings: CSettings) -> Result<(), Error> {
    let pool = connection_pool_url(&settings.elasticsearch.url)
        .await
        .expect("Elasticsearch Connection Pool");

    let client = pool
        .conn()
        .await
        .expect("Elasticsearch Connection Established");

    let api = reverse_geocoder!(client.clone(), settings.query.clone())
        .or(forward_geocoder!(client.clone(), settings.query))
        .or(status!(client, settings.elasticsearch.url))
        .recover(routes::report_invalid)
        .with(warp::trace::request());

    let host = settings.service.host;
    let port = settings.service.port;
    let addr = (host.as_str(), port);
    let addr = addr
        .to_socket_addrs()
        .context(SockAddr)?
        .next()
        .ok_or(Error::AddrResolution {
            msg: String::from("Cannot resolve addr"),
        })?;

    info!("Serving bragi on {}", addr);

    warp::serve(api).run(addr).await;

    Ok(())
}
<|endoftext|>