<commit_before>extern crate hyper;
extern crate irc;
#[macro_use]
extern crate lazy_static;
extern crate regex;
extern crate rustc_serialize as serialize;

mod bot {
    pub mod util {
        use hyper::client::Client;
        use hyper::status::StatusCode;
        use irc::client::data::Message;
        use std::io::{Error, ErrorKind, Read};

        pub fn collect_errors(st: Result<String, String>, s: Result<String, String>) -> Result<String, String> {
            match st {
                Ok(mut st) => {
                    if let Ok(s) = s {
                        if st.len() > 0 {
                            st.push_str("; ");
                        }

                        st.push_str(&s);
                        Ok(st)
                    } else {
                        s
                    }
                },
                Err(mut st) => {
                    if let Err(e) = s {
                        st.push_str("; ");
                        st.push_str(&e);
                    }

                    Err(st)
                }
            }
        }
        pub fn get_reply_target(msg: &Message) -> Option<&str> {
            if is_channel(&msg.args[0][..]) {
                Some(&msg.args[0][..])
            } else {
                msg.get_source_nickname()
            }
        }
        pub fn http_get(url: &str) -> Result<String, Error> {
            let mut resp = match Client::new().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)))
            }
        }
        pub fn is_channel(s: &str) -> bool {
            s.chars().take(1).next().map(|c| c == '#' || c == '&').unwrap_or(false) &&
                s.chars().skip(1).filter(|c| *c == ' ' || *c == '\u{0007}' || *c == ',').next().is_none()
        }
    }
    pub mod regexmatch {
        use irc::client::data::{Command, Message};
        use regex::Regex;
        use serialize::json;
        use std::collections::HashMap;
        use std::collections::hash_map::Entry;
        use std::io::{Error, ErrorKind};
        use super::Subscriber;
        use super::util::{collect_errors, get_reply_target, http_get};

        pub struct RegexMatch {
            gapi_key: String,
            yt_cache: HashMap<String, String>
        }

        fn weather_k_to_c(k: f64) -> f64 {
            k - 273.15
        }
        fn weather_k_to_f(k: f64) -> f64 {
            let c = weather_k_to_c(k);
            1.8 * c + 32.0
        }
        fn weather_handler(_: &mut RegexMatch, loc: &str) -> Result<String, Error> {
            let url = format!("http://api.openweathermap.org/data/2.5/weather?q={}", loc);

            let resp = &try!(http_get(&url))[..];
            let resp_json = match json::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 => {}
            };

            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))
        }
        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
        }
        fn yt_handler(this: &mut RegexMatch, id: &str) -> Result<String, Error> {
            Ok(match this.yt_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, this.gapi_key);

                    let resp = &try!(http_get(&url))[..];
                    let resp_json = match json::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()
                }
            })
        }
        fn xkcd_handler(_: &mut RegexMatch, id: &str) -> Result<String, Error> {
            let url = format!("https://xkcd.com/{}/info.0.json", id);

            let resp = &try!(http_get(&url))[..];
            let resp_json = match json::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");

            Ok(format!("{} ({}-{}-{})", title, year, month, day))
        }

        lazy_static! {
            static ref URL_RES: Vec<(Regex, fn(&mut RegexMatch, &str) -> Result<String, Error>)> =
                vec![(Regex::new(r"^!weather\s*(.*)\s*$").unwrap(), weather_handler),
                     (Regex::new(r"(?:(?:youtube\.com/watch\?\S*?v=)|(?:youtu\.be/))([\w-]+)").unwrap(), yt_handler),
                     (Regex::new(r"xkcd\.com/(\d+)").unwrap(), xkcd_handler)];
        }

        impl RegexMatch {
            pub fn new(gapi_key: &str) -> RegexMatch {
                RegexMatch {
                    gapi_key: gapi_key.to_owned(),
                    yt_cache: HashMap::new()
                }
            }
        }

        impl Subscriber for RegexMatch {
            fn on_message<'a>(&mut self, msg: &'a Message) -> Result<Option<Command>, Error> {
                if msg.command == "PRIVMSG" {
                    if let Some(reply_target) = get_reply_target(msg) {
                        if let Some(ref suffix) = msg.suffix {
                            match URL_RES.iter().map(|&(ref re, handler)| {
                                re
                                    .captures_iter(suffix)
                                    .filter_map(|capture| capture.at(1))
                                    .map(|capture| handler(self, capture))
                                    .map(|res| match res {
                                        Ok(res) => Ok(res),
                                        Err(e) => Err(format!("{:?}", e))
                                    })
                                    .fold(Ok(String::new()), collect_errors)
                            }).fold(Ok(String::new()), collect_errors) {
                                Ok(res) => Ok(Some(Command::PRIVMSG(reply_target.to_owned(), res))),
                                Err(e) => Err(Error::new(ErrorKind::Other, e))
                            }
                        } else {
                            Err(Error::new(ErrorKind::Other, "No message text."))
                        }
                    } else {
                        Err(Error::new(ErrorKind::Other, "Unable to determine reply target."))
                    }
                } else {
                    Ok(None)
                }
            }
        }
    }

    use irc::client::data::{Command, Config, Message};
    use irc::client::server::{IrcServer, NetIrcServer, Server};
    use irc::client::server::utils::ServerExt;
    use std::io::Error;

    pub struct Bot<'a> {
        server: NetIrcServer,
        subscribers: Vec<&'a mut (Subscriber + 'a)>
    }
    pub trait Subscriber {
        fn on_message<'a>(&mut self, &'a Message) -> Result<Option<Command>, Error>;
    }

    impl<'a> Bot<'a> {
        pub fn new(config: Config) -> Result<Bot<'a>, Error> {
            let server = try!(IrcServer::from_config(config));
            try!(server.identify());

            Ok(Bot {
                server: server,
                subscribers: vec![]
            })
        }
        pub fn add_subscriber(&mut self, sub: &'a mut Subscriber) {
            self.subscribers.push(sub);
        }
        pub fn loop_forever(&mut self) -> Error {
            loop {
                if let Err(e) = self.loop_once() {
                    return e;
                }
            }
        }
        pub fn loop_once(&mut self) -> Result<(), Error> {
            let &mut Bot {ref mut server, ref mut subscribers} = self;

            match server.iter().next() {
                Some(Ok(msg)) => subscribers.iter_mut().map(|sub| sub.on_message(&msg)).map(|msg| match msg {
                    Ok(Some(msg)) => server.send(msg),
                    Ok(None) => Ok(()),
                    Err(e) => {
                        let targets = server.config().channels().iter().fold(String::new(), |mut st, chan| {
                            if st.len() > 0 {
                                st.push(',');
                            }
                            st.push_str(chan);
                            st
                        });

                        //TODO: Log here
                        println!("{:?} {:?}", targets, e);

                        if targets.len() > 0 {
                            server.send(Command::PRIVMSG(targets, format!("{}", e)))
                        } else {
                            Ok(())
                        }
                    }
                }).find(|res| res.is_err()).map_or(Ok(()), |r| r),
                Some(Err(e)) => Err(e),
                None => Ok(())
            }
        }
    }
}

use bot::Bot;
use bot::regexmatch::RegexMatch;
use irc::client::data::Config;
use std::fs::File;
use std::io::{Error, Read};
use std::path::Path;

fn file_get(p: &Path) -> Result<Vec<u8>, Error> {
    Ok(try!(File::open(p)).bytes().map(|b| b.unwrap()).collect::<Vec<u8>>())
}

fn main() {
    let mut regex_match = RegexMatch::new(&String::from_utf8(file_get(Path::new("gapi_key.dat"))
                                                             .ok().expect("gapi_key.dat should be on the path"))
                                          .ok().expect("gapi_key.dat should contain only the Google API key (no trailing newlines)"));

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

    bot.add_subscriber(&mut regex_match);
    bot.loop_forever();
}
<commit_msg>Clean up RegexMatch a bit. Prevent trailing "; "<commit_after>extern crate hyper;
extern crate irc;
#[macro_use]
extern crate lazy_static;
extern crate regex;
extern crate rustc_serialize as serialize;

mod bot {
    pub mod util {
        use hyper::client::Client;
        use hyper::status::StatusCode;
        use irc::client::data::Message;
        use std::io::{Error, ErrorKind, Read};

        pub fn collect_errors(st: Result<String, String>, s: Result<String, String>) -> Result<String, String> {
            match st {
                Ok(mut st) => {
                    if let Ok(s) = s {
                        if !s.is_empty() {
                            if !st.is_empty() {
                                st.push_str("; ");
                            }

                            st.push_str(&s);
                        }

                        Ok(st)
                    } else {
                        s
                    }
                },
                Err(mut st) => {
                    if let Err(e) = s {
                        st.push_str("; ");
                        st.push_str(&e);
                    }

                    Err(st)
                }
            }
        }
        pub fn get_reply_target(msg: &Message) -> Option<&str> {
            if is_channel(&msg.args[0][..]) {
                Some(&msg.args[0][..])
            } else {
                msg.get_source_nickname()
            }
        }
        pub fn http_get(url: &str) -> Result<String, Error> {
            let mut resp = match Client::new().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)))
            }
        }
        pub fn is_channel(s: &str) -> bool {
            s.chars().take(1).next().map(|c| c == '#' || c == '&').unwrap_or(false) &&
                s.chars().skip(1).filter(|c| *c == ' ' || *c == '\u{0007}' || *c == ',').next().is_none()
        }
    }
    pub mod regexmatch {
        use irc::client::data::{Command, Message};
        use regex::Regex;
        use serialize::json;
        use std::collections::HashMap;
        use std::collections::hash_map::Entry;
        use std::io::{Error, ErrorKind};
        use super::Subscriber;
        use super::util::{collect_errors, get_reply_target, http_get};

        pub struct RegexMatch {
            gapi_key: String,
            yt_cache: HashMap<String, String>
        }

        fn weather_k_to_c(k: f64) -> f64 {
            k - 273.15
        }
        fn weather_k_to_f(k: f64) -> f64 {
            let c = weather_k_to_c(k);
            1.8 * c + 32.0
        }
        fn weather_handler(_: &mut RegexMatch, loc: &str) -> Result<String, Error> {
            let url = format!("http://api.openweathermap.org/data/2.5/weather?q={}", loc);

            let resp = &try!(http_get(&url))[..];
            let resp_json = match json::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 => {}
            };

            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))
        }
        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
        }
        fn yt_handler(this: &mut RegexMatch, id: &str) -> Result<String, Error> {
            Ok(match this.yt_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, this.gapi_key);

                    let resp = &try!(http_get(&url))[..];
                    let resp_json = match json::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()
                }
            })
        }
        fn xkcd_handler(_: &mut RegexMatch, id: &str) -> Result<String, Error> {
            let url = format!("https://xkcd.com/{}/info.0.json", id);

            let resp = &try!(http_get(&url))[..];
            let resp_json = match json::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");

            Ok(format!("{} ({}-{}-{})", title, year, month, day))
        }

        lazy_static! {
            static ref URL_RES: Vec<(Regex, fn(&mut RegexMatch, &str) -> Result<String, Error>)> =
                vec![(Regex::new(r"^!weather\s*(.*)\s*$").unwrap(), weather_handler),
                     (Regex::new(r"(?:(?:youtube\.com/watch\?\S*?v=)|(?:youtu\.be/))([\w-]+)").unwrap(), yt_handler),
                     (Regex::new(r"xkcd\.com/(\d+)").unwrap(), xkcd_handler)];
        }

        impl RegexMatch {
            pub fn new(gapi_key: &str) -> RegexMatch {
                RegexMatch {
                    gapi_key: gapi_key.to_owned(),
                    yt_cache: HashMap::new()
                }
            }
        }

        impl Subscriber for RegexMatch {
            fn on_message<'a>(&mut self, msg: &'a Message) -> Result<Option<Command>, Error> {
                if msg.command == "PRIVMSG" {
                    if let Some(reply_target) = get_reply_target(msg) {
                        if let Some(ref suffix) = msg.suffix {
                            match URL_RES.iter().map(|&(ref re, handler)| {
                                re
                                    .captures_iter(suffix)
                                    .filter_map(|capture| capture.at(1))
                                    .map(|capture| match handler(self, capture) {
                                        Ok(res) => Ok(res),
                                        Err(e) => Err(format!("{:?}", e))
                                    })
                                    .fold(Ok(String::new()), collect_errors)
                            }).fold(Ok(String::new()), collect_errors) {
                                Ok(res) => Ok(Some(Command::PRIVMSG(reply_target.to_owned(), res))),
                                Err(e) => Err(Error::new(ErrorKind::Other, e))
                            }
                        } else {
                            Err(Error::new(ErrorKind::Other, "No message text."))
                        }
                    } else {
                        Err(Error::new(ErrorKind::Other, "Unable to determine reply target."))
                    }
                } else {
                    Ok(None)
                }
            }
        }
    }

    use irc::client::data::{Command, Config, Message};
    use irc::client::server::{IrcServer, NetIrcServer, Server};
    use irc::client::server::utils::ServerExt;
    use std::io::Error;

    pub struct Bot<'a> {
        server: NetIrcServer,
        subscribers: Vec<&'a mut (Subscriber + 'a)>
    }
    pub trait Subscriber {
        fn on_message<'a>(&mut self, &'a Message) -> Result<Option<Command>, Error>;
    }

    impl<'a> Bot<'a> {
        pub fn new(config: Config) -> Result<Bot<'a>, Error> {
            let server = try!(IrcServer::from_config(config));
            try!(server.identify());

            Ok(Bot {
                server: server,
                subscribers: vec![]
            })
        }
        pub fn add_subscriber(&mut self, sub: &'a mut Subscriber) {
            self.subscribers.push(sub);
        }
        pub fn loop_forever(&mut self) -> Error {
            loop {
                if let Err(e) = self.loop_once() {
                    return e;
                }
            }
        }
        pub fn loop_once(&mut self) -> Result<(), Error> {
            let &mut Bot {ref mut server, ref mut subscribers} = self;

            match server.iter().next() {
                Some(Ok(msg)) => subscribers.iter_mut().map(|sub| sub.on_message(&msg)).map(|msg| match msg {
                    Ok(Some(msg)) => server.send(msg),
                    Ok(None) => Ok(()),
                    Err(e) => {
                        let targets = server.config().channels().iter().fold(String::new(), |mut st, chan| {
                            if st.len() > 0 {
                                st.push(',');
                            }
                            st.push_str(chan);
                            st
                        });

                        //TODO: Log here
                        println!("{:?} {:?}", targets, e);

                        if targets.len() > 0 {
                            server.send(Command::PRIVMSG(targets, format!("{}", e)))
                        } else {
                            Ok(())
                        }
                    }
                }).find(|res| res.is_err()).map_or(Ok(()), |r| r),
                Some(Err(e)) => Err(e),
                None => Ok(())
            }
        }
    }
}

use bot::Bot;
use bot::regexmatch::RegexMatch;
use irc::client::data::Config;
use std::fs::File;
use std::io::{Error, Read};
use std::path::Path;

fn file_get(p: &Path) -> Result<Vec<u8>, Error> {
    Ok(try!(File::open(p)).bytes().map(|b| b.unwrap()).collect::<Vec<u8>>())
}

fn main() {
    let mut regex_match = RegexMatch::new(&String::from_utf8(file_get(Path::new("gapi_key.dat"))
                                                             .ok().expect("gapi_key.dat should be on the path"))
                                          .ok().expect("gapi_key.dat should contain only the Google API key (no trailing newlines)"));

    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>// TODO:
//  * add option to debug: print all errors
//  * show repo state
//  * colors

extern crate git2;

use std::io::{self, Write};

use std::collections::HashMap;

use git2::Error;
use git2::{Repository,Branch,BranchType,Oid,Reference,StatusShow};
use git2::{StatusOptions,Statuses,Status,RepositoryState};
use git2::{STATUS_WT_MODIFIED,STATUS_WT_DELETED,STATUS_WT_NEW,STATUS_WT_TYPECHANGE,STATUS_WT_RENAMED};
use git2::{STATUS_INDEX_MODIFIED,STATUS_INDEX_DELETED,STATUS_INDEX_NEW,STATUS_INDEX_TYPECHANGE,STATUS_INDEX_RENAMED};

struct Program {
    repo: Repository,
}

fn get_branch_remote(reference: Reference) -> Option<Oid> {
    let b = Branch::wrap(reference);
    let upstream = match b.upstream() {
        Ok(u) => u,
        Err(_) => return None,
    };
    upstream.get().target()
}

impl Program {
    fn get_head(&self) -> Option<Reference> {
        match self.repo.head() {
            Ok(head) => Some(head),
            Err(e) => None,
        }
    }

    fn get_current_branch_oid(&self) -> Option<Oid> {
        let head = self.get_head();
        match head {
            Some(v) => v.target(),
            None => None,
        }
    }

    fn get_current_branch_name(&self) -> String {
        let blank = String::from("");
        let h = match self.get_head() {
            Some(v) => v,
            None => return blank,
        };
        if h.is_branch() {
            match h.shorthand() {
                Some(v) => v.to_string(),
                None => blank,
            }
        } else {
            let hash = match h.target() {
                Some(v) => v.to_string(),
                None => blank,
            };
            let (s, _) = hash.split_at(7);
            s.to_string()
        }
    }

    // fn get_current_branch_remote(&self) -> String {
    //     let oid = self.get_current_branch_oid();
    //     let b = Branch::wrap(oid);
    //     let upstream = b.upstream().ok().unwrap();
    //     String::from(upstream.name().ok().unwrap().unwrap())
    // }

    fn get_current_branch_remote_oid(&self) -> Option<Oid> {
        match self.get_head() {
            Some(r) => get_branch_remote(r),
            None => None
        }
    }

    fn get_current_branch_ahead_behind(&self) -> Option<(usize, usize)> {
        let rm_oid = match self.get_current_branch_remote_oid() {
            Some(r) => r,
            None => return None
        };
        let oid = match self.get_current_branch_oid() {
            Some(r) => r,
            None => return None
        };
        let res = self.repo.graph_ahead_behind(oid, rm_oid);
        match res {
            Ok(r) => Some(r),
            Err(_) => None
        }
    }

    fn get_upstream_branch_ahead_behind(&self) -> Option<(usize, usize)>  {
        let upstream_reference = match self.find_upstream_repo_branch() {
            Ok(u) => u.into_reference(),
            Err(_) => return None,
        };
        let upstream_reference_oid = match upstream_reference.target() {
            Some(u) => u,
            None => return None,
        };
        let oid = match self.get_current_branch_oid() {
            Some(r) => r,
            None => return None
        };
        let res = self.repo.graph_ahead_behind(oid, upstream_reference_oid);
        match res {
            Ok(r) => Some(r),
            Err(_) => None
        }
    }

    fn find_upstream_repo_branch(&self) -> Result<Branch, Error> {
        let us_branch_name = format!("{}{}", "upstream/", self.get_current_branch_name());
        self.repo.find_branch(&us_branch_name, BranchType::Remote)
    }

    fn get_status(&self) -> Option<Statuses> {
        let mut so = StatusOptions::new();
        let mut opts = so.show(StatusShow::IndexAndWorkdir);
        opts.include_untracked(true);
        match self.repo.statuses(Some(&mut opts)) {
            Ok(s) => Some(s),
            Err(e) => None,
        }
    }

    fn get_repository_state(&self) -> RepositoryState {
        self.repo.state()
    }

    fn get_file_status(&self) -> Option<HashMap<&str, u32>> {
        let mut d = HashMap::new();

        let changed = STATUS_WT_MODIFIED | STATUS_WT_DELETED | STATUS_WT_TYPECHANGE | STATUS_WT_RENAMED;
        let staged = STATUS_INDEX_MODIFIED | STATUS_INDEX_DELETED | STATUS_INDEX_TYPECHANGE | STATUS_INDEX_RENAMED | STATUS_INDEX_NEW;

        let statuses = match self.get_status() {
            Some(x) => x,
            None => return None,
        };

        for s in statuses.iter() {
            let file_status = s.status();
            // println!("{}", s.path().unwrap());

            if file_status.intersects(changed) {
                let counter = d.entry("U").or_insert(0);
                *counter += 1;
            };
            if file_status.contains(STATUS_WT_NEW) {
                let counter = d.entry("N").or_insert(0);
                *counter += 1;
            };
            if file_status.intersects(staged) {
                let counter = d.entry("A").or_insert(0);
                *counter += 1;
            };
        }
        Some(d)
    }

    fn run(&self) {
        print!("{}|", self.get_current_branch_name());

        match self.get_current_branch_ahead_behind() {
            // FIXME: cover case behind && ahead
            Some((_, behind)) if behind > 0 => print!("-{}", behind),
            Some((ahead, _)) if ahead > 0 => print!("+{}", ahead),
            Some(_) => {}
            None => {}
        }

        match self.get_upstream_branch_ahead_behind() {
            Some((ahead, behind)) => print!("U+{}-{}|", ahead, behind),
            None => {}
        }

        let statuses = match self.get_file_status() {
            Some(s) => {
                for (k, v) in s {
                    if v > 0 {
                        print!("{}{}", k, v);
                    }
                }
            },
            None => {}
        };

        // println!("{:?}", program.get_repository_state());

        print!("\n");
    }
}

fn main() {
    let repo = match Repository::open(".") {
        Ok(repo) => repo,
        Err(_) => return (),
    };
    let program = Program{ repo: repo };
    program.run();
}
<commit_msg>remove unused code<commit_after>// TODO:
//  * add option to debug: print all errors
//  * show repo state
//  * colors

extern crate git2;

use std::io::{self, Write};

use std::collections::HashMap;

use git2::Error;
use git2::{Repository,Branch,BranchType,Oid,Reference,StatusShow};
use git2::{StatusOptions,Statuses,Status,RepositoryState};
use git2::{STATUS_WT_MODIFIED,STATUS_WT_DELETED,STATUS_WT_NEW,STATUS_WT_TYPECHANGE,STATUS_WT_RENAMED};
use git2::{STATUS_INDEX_MODIFIED,STATUS_INDEX_DELETED,STATUS_INDEX_NEW,STATUS_INDEX_TYPECHANGE,STATUS_INDEX_RENAMED};

struct Program {
    repo: Repository,
}

fn get_branch_remote(reference: Reference) -> Option<Oid> {
    let b = Branch::wrap(reference);
    let upstream = match b.upstream() {
        Ok(u) => u,
        Err(_) => return None,
    };
    upstream.get().target()
}

impl Program {
    fn get_head(&self) -> Option<Reference> {
        match self.repo.head() {
            Ok(head) => Some(head),
            Err(e) => None,
        }
    }

    fn get_current_branch_oid(&self) -> Option<Oid> {
        let head = self.get_head();
        match head {
            Some(v) => v.target(),
            None => None,
        }
    }

    fn get_current_branch_name(&self) -> String {
        let blank = String::from("");
        let h = match self.get_head() {
            Some(v) => v,
            None => return blank,
        };
        if h.is_branch() {
            match h.shorthand() {
                Some(v) => v.to_string(),
                None => blank,
            }
        } else {
            let hash = match h.target() {
                Some(v) => v.to_string(),
                None => blank,
            };
            let (s, _) = hash.split_at(7);
            s.to_string()
        }
    }

    fn get_current_branch_remote_oid(&self) -> Option<Oid> {
        match self.get_head() {
            Some(r) => get_branch_remote(r),
            None => None
        }
    }

    fn get_current_branch_ahead_behind(&self) -> Option<(usize, usize)> {
        let rm_oid = match self.get_current_branch_remote_oid() {
            Some(r) => r,
            None => return None
        };
        let oid = match self.get_current_branch_oid() {
            Some(r) => r,
            None => return None
        };
        let res = self.repo.graph_ahead_behind(oid, rm_oid);
        match res {
            Ok(r) => Some(r),
            Err(_) => None
        }
    }

    fn get_upstream_branch_ahead_behind(&self) -> Option<(usize, usize)>  {
        let upstream_reference = match self.find_upstream_repo_branch() {
            Ok(u) => u.into_reference(),
            Err(_) => return None,
        };
        let upstream_reference_oid = match upstream_reference.target() {
            Some(u) => u,
            None => return None,
        };
        let oid = match self.get_current_branch_oid() {
            Some(r) => r,
            None => return None
        };
        let res = self.repo.graph_ahead_behind(oid, upstream_reference_oid);
        match res {
            Ok(r) => Some(r),
            Err(_) => None
        }
    }

    fn find_upstream_repo_branch(&self) -> Result<Branch, Error> {
        let us_branch_name = format!("{}{}", "upstream/", self.get_current_branch_name());
        self.repo.find_branch(&us_branch_name, BranchType::Remote)
    }

    fn get_status(&self) -> Option<Statuses> {
        let mut so = StatusOptions::new();
        let mut opts = so.show(StatusShow::IndexAndWorkdir);
        opts.include_untracked(true);
        match self.repo.statuses(Some(&mut opts)) {
            Ok(s) => Some(s),
            Err(e) => None,
        }
    }

    fn get_repository_state(&self) -> RepositoryState {
        self.repo.state()
    }

    fn get_file_status(&self) -> Option<HashMap<&str, u32>> {
        let mut d = HashMap::new();

        let changed = STATUS_WT_MODIFIED | STATUS_WT_DELETED | STATUS_WT_TYPECHANGE | STATUS_WT_RENAMED;
        let staged = STATUS_INDEX_MODIFIED | STATUS_INDEX_DELETED | STATUS_INDEX_TYPECHANGE | STATUS_INDEX_RENAMED | STATUS_INDEX_NEW;

        let statuses = match self.get_status() {
            Some(x) => x,
            None => return None,
        };

        for s in statuses.iter() {
            let file_status = s.status();
            // println!("{}", s.path().unwrap());

            if file_status.intersects(changed) {
                let counter = d.entry("U").or_insert(0);
                *counter += 1;
            };
            if file_status.contains(STATUS_WT_NEW) {
                let counter = d.entry("N").or_insert(0);
                *counter += 1;
            };
            if file_status.intersects(staged) {
                let counter = d.entry("A").or_insert(0);
                *counter += 1;
            };
        }
        Some(d)
    }

    fn run(&self) {
        print!("{}|", self.get_current_branch_name());

        match self.get_current_branch_ahead_behind() {
            // FIXME: cover case behind && ahead
            Some((_, behind)) if behind > 0 => print!("-{}", behind),
            Some((ahead, _)) if ahead > 0 => print!("+{}", ahead),
            Some(_) => {}
            None => {}
        }

        match self.get_upstream_branch_ahead_behind() {
            Some((ahead, behind)) => print!("U+{}-{}|", ahead, behind),
            None => {}
        }

        let statuses = match self.get_file_status() {
            Some(s) => {
                for (k, v) in s {
                    if v > 0 {
                        print!("{}{}", k, v);
                    }
                }
            },
            None => {}
        };

        // println!("{:?}", program.get_repository_state());

        print!("\n");
    }
}

fn main() {
    let repo = match Repository::open(".") {
        Ok(repo) => repo,
        Err(_) => return (),
    };
    let program = Program{ repo: repo };
    program.run();
}
<|endoftext|>


<commit_before>#[derive(Clone, Copy, Debug)]
enum Const {
  Data,
  Codata,
}

impl ToString for Const {
  fn to_string(&self) -> String {
    use Const::*;
    match *self {
      Data => "data".to_string(),
      Codata => "codata".to_string(),
    }
  }
}

#[derive(Clone, Debug)]
struct Var {
  name: String,
  idx: i32,
}

impl Var {
  pub fn new(name: &str, idx: i32) -> Var {
    Var {
      name: name.to_string(),
      idx: idx,
    }
  }
}

impl ToString for Var {
  fn to_string(&self) -> String {
    self.name.clone()
    // + "@" + &self.idx.to_string()
  }
}

#[derive(Clone, Debug)]
enum Expr {
  // Type system constants
  Const(Const),
  // Bound variables
  Var(Var),
  // Lambda
  Lam(Var, Box<Expr>, Box<Expr>),
  // "forall"
  Pi(Var, Box<Expr>, Box<Expr>),
  // Function application
  App(Box<Expr>, Box<Expr>),
}

impl Expr {
  pub fn constant(constant: Const) -> Expr {
    Expr::Const(constant)
  }
  pub fn var(v: &Var) -> Expr {
    Expr::Var(v.clone())
  }
  pub fn lam(var: Var, ty: Expr, body: Expr) -> Expr {
    Expr::Lam(var, Box::new(ty), Box::new(body))
  }
  pub fn pi(var: Var, ty: Expr, body: Expr) -> Expr {
    Expr::Pi(var, Box::new(ty), Box::new(body))
  }
  pub fn app(f: Expr, arg: Expr) -> Expr {
    Expr::App(Box::new(f), Box::new(arg))
  }

  pub fn is_constant(&self) -> bool {
    use Expr::*;
    match *self {
      Const(_) => true,
      _ => false,
    }
  }
  pub fn is_var(&self) -> bool {
    use Expr::*;
    match *self {
      Var(_) => true,
      _ => false,
    }
  }
  pub fn is_lam(&self) -> bool {
    use Expr::*;
    match *self {
      Lam(_, _, _) => true,
      _ => false,
    }
  }
  pub fn is_pi(&self) -> bool {
    use Expr::*;
    match *self {
      Pi(_, _, _) => true,
      _ => false,
    }
  }
  pub fn is_app(&self) -> bool {
    use Expr::*;
    match *self {
      App(_, _) => true,
      _ => false,
    }
  }
}

impl ToString for Expr {
  fn to_string(&self) -> String {
    use Expr::*;
    match self {
      &Const(ref constant) => constant.to_string(),
      &Var(ref var) => var.to_string(),
      &Lam(ref var, ref ty, ref body) => {
        var.to_string() + ":" + &ty.to_string() + " -> " + &body.to_string()
      }
      &Pi(ref var, ref ty, ref body) => {
        "forall (".to_string() + &var.to_string() + ":" + &ty.to_string() +
        ") -> " + &body.to_string()
      }
      &App(ref f, ref arg) => {
        if f.is_lam() || f.is_pi() {
          "(".to_string() + &f.to_string() + ") " + &arg.to_string()
        } else {
          f.to_string() + " " + &arg.to_string()
        }
      }
    }
  }
}

fn main() {
  println!("{}", Const::Data.to_string());
  let a = Var::new("a", 0);
  let x = Var::new("x", 0);
  let expra = Expr::var(&a);
  let exprx = Expr::var(&x);
  println!("{}", x.to_string());
  let id = Expr::pi(a.clone(),
                    Expr::constant(Const::Data),
                    Expr::lam(x, expra, exprx));
  println!("{}", id.to_string());
  let apply_id = Expr::app(Expr::app(id, Expr::var(&Var::new("int", 0))),
                           Expr::var(&Var::new("1", 0)));
  println!("{}", apply_id.to_string());
}
<commit_msg>Match on *self instead of self.<commit_after>#[derive(Clone, Copy, Debug)]
enum Const {
  Data,
  Codata,
}

impl ToString for Const {
  fn to_string(&self) -> String {
    use Const::*;
    match *self {
      Data => "data".to_string(),
      Codata => "codata".to_string(),
    }
  }
}

#[derive(Clone, Debug)]
struct Var {
  name: String,
  idx: i32,
}

impl Var {
  pub fn new(name: &str, idx: i32) -> Var {
    Var {
      name: name.to_string(),
      idx: idx,
    }
  }
}

impl ToString for Var {
  fn to_string(&self) -> String {
    self.name.clone()
    // + "@" + &self.idx.to_string()
  }
}

#[derive(Clone, Debug)]
enum Expr {
  // Type system constants
  Const(Const),
  // Bound variables
  Var(Var),
  // Lambda
  Lam(Var, Box<Expr>, Box<Expr>),
  // "forall"
  Pi(Var, Box<Expr>, Box<Expr>),
  // Function application
  App(Box<Expr>, Box<Expr>),
}

impl Expr {
  pub fn constant(constant: Const) -> Expr {
    Expr::Const(constant)
  }
  pub fn var(v: &Var) -> Expr {
    Expr::Var(v.clone())
  }
  pub fn lam(var: Var, ty: Expr, body: Expr) -> Expr {
    Expr::Lam(var, Box::new(ty), Box::new(body))
  }
  pub fn pi(var: Var, ty: Expr, body: Expr) -> Expr {
    Expr::Pi(var, Box::new(ty), Box::new(body))
  }
  pub fn app(f: Expr, arg: Expr) -> Expr {
    Expr::App(Box::new(f), Box::new(arg))
  }

  pub fn is_constant(&self) -> bool {
    use Expr::*;
    match *self {
      Const(_) => true,
      _ => false,
    }
  }
  pub fn is_var(&self) -> bool {
    use Expr::*;
    match *self {
      Var(_) => true,
      _ => false,
    }
  }
  pub fn is_lam(&self) -> bool {
    use Expr::*;
    match *self {
      Lam(_, _, _) => true,
      _ => false,
    }
  }
  pub fn is_pi(&self) -> bool {
    use Expr::*;
    match *self {
      Pi(_, _, _) => true,
      _ => false,
    }
  }
  pub fn is_app(&self) -> bool {
    use Expr::*;
    match *self {
      App(_, _) => true,
      _ => false,
    }
  }
}

impl ToString for Expr {
  fn to_string(&self) -> String {
    use Expr::*;
    match *self {
      Const(ref constant) => constant.to_string(),
      Var(ref var) => var.to_string(),
      Lam(ref var, ref ty, ref body) => {
        var.to_string() + ":" + &ty.to_string() + " -> " + &body.to_string()
      }
      Pi(ref var, ref ty, ref body) => {
        "forall (".to_string() + &var.to_string() + ":" + &ty.to_string() +
        ") -> " + &body.to_string()
      }
      App(ref f, ref arg) => {
        if f.is_lam() || f.is_pi() {
          "(".to_string() + &f.to_string() + ") " + &arg.to_string()
        } else {
          f.to_string() + " " + &arg.to_string()
        }
      }
    }
  }
}

fn main() {
  println!("{}", Const::Data.to_string());
  let a = Var::new("a", 0);
  let x = Var::new("x", 0);
  let expra = Expr::var(&a);
  let exprx = Expr::var(&x);
  println!("{}", x.to_string());
  let id = Expr::pi(a.clone(),
                    Expr::constant(Const::Data),
                    Expr::lam(x, expra, exprx));
  println!("{}", id.to_string());
  let apply_id = Expr::app(Expr::app(id, Expr::var(&Var::new("int", 0))),
                           Expr::var(&Var::new("1", 0)));
  println!("{}", apply_id.to_string());
}
<|endoftext|>


<commit_before>use std::fs;
use std::env;
use std::fs::File;
use std::io::Read;
use std::hash::Hasher;
use std::path::PathBuf;
use std::collections::HashMap;
use std::os::unix::fs::MetadataExt;
use std::collections::hash_map::DefaultHasher;

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

struct FileEntry {
    inode: u64,
    dev:  u64,
    path : String,
}

fn collect_files(dir: &String, h: &mut HashMap<u64, Vec<FileEntry>>) {

    let entries = match fs::read_dir(dir) {
        Ok(x) => x,
        _ => {
            warn!("{}: invalid directory", dir);
            return
        },
    };

    for path in  entries.filter( |x| x.is_ok() ).map( |x| x.unwrap().path() )
                .collect::<Vec<PathBuf>>() {
        if let (Some(path_str), Ok(metadata)) =
                (path.to_str(), path.symlink_metadata()) {
            let ft = metadata.file_type();
            if ft.is_symlink() {
                continue;
            } else if ft.is_file() {
                match metadata.len() {
                    0 => continue,
                    i => h.entry(i).or_insert_with(Vec::new)
                          .push(
                           FileEntry{
                               inode : metadata.ino(),
                               dev   : metadata.dev(),
                               path  : String::from(path_str),
                           }),
                };
            } else if ft.is_dir() {
                collect_files(&(String::from(path_str)), h);
            }
        }
    }
}

fn find_duplicates(duplicates: &mut HashMap<u64, Vec<FileEntry>>,
                   vec: &Vec<FileEntry> ) {
    for file_entry in vec {
        if let Ok(mut file) = File::open(&file_entry.path) {
            let mut buf: [u8; 1024] = [0; 1024];
            if let Ok(nbytes) = file.read(&mut buf) {
                let mut hasher = DefaultHasher::new();
                hasher.write(&buf[..nbytes as usize]);
                let k = hasher.finish();
                duplicates.entry(k).or_insert_with(Vec::new)
                    .push(
                        FileEntry{
                            inode : file_entry.inode,
                            dev   : file_entry.dev,
                            path  : file_entry.path.to_string(),
                        });
            }
        }
    }
}

fn main() {
    env_logger::init().unwrap();

    let args: Vec<String> = env::args().collect();

    // Print out usage if no directories are given.
    if args.len() == 1 {
        println!("Usage: {} <dir1> [dir2 [dir3 ...]]", args[0]);
        ::std::process::exit(0);
    }

    let mut hmap: HashMap<u64, Vec<FileEntry> > = HashMap::new();

    for dir in &args[1..] {
        collect_files(dir, &mut hmap);
    }

    // Get rid of all the single entries.
    hmap.retain(|_, v| v.len() >= 2);

    let mut cluster = 1;

    for (key, val) in hmap.iter() {
        let mut duplicates: HashMap<u64, Vec<FileEntry> > = HashMap::new();
        find_duplicates(&mut duplicates, &val);

        for (hash, vec) in duplicates {
            if vec.len() >= 2 {
                println!("{} files in cluster {} (size: {}, digest: {})",
                         vec.len(), cluster, key, hash);
                // for_each becomes stable v1.22.0 onwards. Should uncomment then.
                //vec.iter().for_each(|f| println!("{}", f.path));
                for file_entry in vec {
                    println!("{}", file_entry.path);
                }
                cluster += 1;
            }
        }
    }
}
<commit_msg>Add support for thorough<commit_after>use std::fs;
use std::env;
use std::fs::File;
use std::io::Read;
use std::hash::Hasher;
use std::path::PathBuf;
use std::collections::HashMap;
use std::os::unix::fs::MetadataExt;
use std::collections::hash_map::DefaultHasher;

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

struct FileEntry {
    inode: u64,
    dev:  u64,
    path : String,
}

fn collect_files(dir: &String, h: &mut HashMap<u64, Vec<FileEntry>>) {

    let entries = match fs::read_dir(dir) {
        Ok(x) => x,
        _ => {
            warn!("{}: invalid directory", dir);
            return
        },
    };

    for path in  entries.filter( |x| x.is_ok() ).map( |x| x.unwrap().path() )
                .collect::<Vec<PathBuf>>() {
        if let (Some(path_str), Ok(metadata)) =
                (path.to_str(), path.symlink_metadata()) {
            let ft = metadata.file_type();
            if ft.is_symlink() {
                continue;
            } else if ft.is_file() {
                match metadata.len() {
                    0 => continue,
                    i => h.entry(i).or_insert_with(Vec::new)
                          .push(
                           FileEntry{
                               inode : metadata.ino(),
                               dev   : metadata.dev(),
                               path  : String::from(path_str),
                           }),
                };
            } else if ft.is_dir() {
                collect_files(&(String::from(path_str)), h);
            }
        }
    }
}

fn find_duplicates(duplicates: &mut HashMap<u64, Vec<FileEntry>>,
                   vec: &Vec<FileEntry> ) {
    for file_entry in vec {
        if let Ok(mut file) = File::open(&file_entry.path) {
            let mut buf: [u8; 1024] = [0; 1024];
            if let Ok(nbytes) = file.read(&mut buf) {
                let mut hasher = DefaultHasher::new();
                hasher.write(&buf[..nbytes as usize]);
                let k = hasher.finish();
                duplicates.entry(k).or_insert_with(Vec::new)
                    .push(
                        FileEntry{
                            inode : file_entry.inode,
                            dev   : file_entry.dev,
                            path  : file_entry.path.to_string(),
                        });
            }
        }
    }
}

fn main() {
    env_logger::init().unwrap();

    let args: Vec<String> = env::args().collect();

    // Print out usage if no directories are given.
    if args.len() == 1 || ( args.len() == 2 && args[1] == "--thorough" ) {
        println!("Usage: {} [--thorough] <dir1> [dir2 [dir3 ...]]", args[0]);
        ::std::process::exit(0);
    }

    let mut hmap: HashMap<u64, Vec<FileEntry> > = HashMap::new();

    let mut thorough = false;
    for dir in &args[1..] {
        if dir == "--thorough" {
            thorough = true;
            continue;
        }
        collect_files(dir, &mut hmap);
    }

    // Get rid of all the single entries.
    hmap.retain(|_, v| v.len() >= 2);

    let mut cluster = 1;

    for (key, val) in hmap.iter() {
        let mut duplicates: HashMap<u64, Vec<FileEntry> > = HashMap::new();
        find_duplicates(&mut duplicates, &val);

        for (hash, vec) in duplicates {
            if vec.len() >= 2 {
                println!("{} files in cluster {} (size: {}, digest: {})",
                         vec.len(), cluster, key, hash);
                // for_each becomes stable v1.22.0 onwards. Should uncomment then.
                //vec.iter().for_each(|f| println!("{}", f.path));
                for file_entry in vec {
                    println!("{}", file_entry.path);
                }
                cluster += 1;
            }
        }
    }
}
<|endoftext|>


<commit_before>#![feature(phase)]

#[phase(plugin)] extern crate scan;
extern crate scan_util;

use std::collections::HashMap;
use scan_util::{NothingMatched, OtherScanError, ScanIoError};

fn main() {
	let mut vars = HashMap::new();

	vars.insert("NaN".into_string(), Float::nan());
	vars.insert("inf".into_string(), Float::infinity());
	vars.insert("pi".into_string(), Float::pi());
	vars.insert("e".into_string(), Float::e());

	println!("Type `.help` or `.h` for brief instructions.");
	println!("Type `.quit` or `.q` to quit.");

    loop {
    	print!("> ");
    	let res = scanln! {
    		// Show brief help message.
    		".h" | ".help" => {
    			println!("Type expressions to be evaluated.");
    			println!("You can use +, -, *, /.");
    			println!("You can define variables like so: `let pi = 3.14159`.");
    			println!("Type .q or .quit to quit.");
    			continue;
    		},

    		// Exit.
    		".q" | ".quit" => {
    			println!("Bye!")
    			return;
    		},

    		// Fallback for unknown commands.
    		"." cmd:&str, .._ignore => {
    			println!("error: unknown command `{}`", cmd);
    			println!("Try `.help`.");
    			continue;
    		},

    		// Ignore empty lines.
    		_ignore:() => continue,

    		// Otherwise, try to scan a statement.
    		stmt:Stmt => stmt,
    	};

    	let stmt = match res {
    		Err(NothingMatched) => {
    			println!("error: sorry, I didn't understand that.");
    			println!("Try `.help`.");
    			continue;
    		},
    		Err(ScanIoError(err)) => {
    			println!("io error: {}", err);
    			std::os::set_exit_status(1);
    			return;
    		},
    		Err(err @ OtherScanError(..)) => {
    			println!("error: {:s}", err);
    			continue;
    		},
    		Ok(stmt) => stmt,
    	};

    	match stmt {
    		LetStmt(name, expr) => {
    			let vars = &mut vars;
    			eval(expr, vars).map(|v| vars.insert(name.clone(), v));
    		},
    		ExprStmt(expr) => {
    			eval(expr, &vars).map(|v| println!("= {}", v));
    		},
    	}
    }
}

fn eval(expr: Expr, vars: &HashMap<String, f64>) -> Option<f64> {
	match expr {
		Add(box lhs, box rhs) => eval(lhs, vars).zip(eval(rhs, vars)).map(|(l,r)| l+r),
		Sub(box lhs, box rhs) => eval(lhs, vars).zip(eval(rhs, vars)).map(|(l,r)| l-r),
		Mul(box lhs, box rhs) => eval(lhs, vars).zip(eval(rhs, vars)).map(|(l,r)| l*r),
		Div(box lhs, box rhs) => eval(lhs, vars).zip(eval(rhs, vars)).map(|(l,r)| l/r),
		Var(name) => {
			match vars.find_copy(&name) {
				Some(v) => Some(v),
				None => {
					println!("error: undefined variable `{}`.", name);
					None
				}
			}
		},
		Lit(v) => Some(v),
	}
}

#[deriving(PartialEq, Show)]
enum Stmt {
	LetStmt(String, Expr),
	ExprStmt(Expr),
}

scanner! { Stmt,
	#[tokenizer="IdentsAndInts"]
	"let" name "=" expr => LetStmt(name, expr),
	expr => ExprStmt(expr),
}

#[deriving(PartialEq, Show)]
enum Expr {
	Add(Box<Expr>, Box<Expr>),
	Sub(Box<Expr>, Box<Expr>),
	Mul(Box<Expr>, Box<Expr>),
	Div(Box<Expr>, Box<Expr>),
	Var(String),
	Lit(f64),
}

scanner! { Expr,
	expr:AddExpr => {
		let AddExpr(expr) = expr;
		expr
	}
}

struct AddExpr(Expr);

scanner! { AddExpr,
	lhs:MulExpr "+" rhs:AddExpr => {
		let (MulExpr(lhs), AddExpr(rhs)) = (lhs, rhs);
		AddExpr(Add(box lhs, box rhs))
	},
	lhs:MulExpr "-" rhs:AddExpr => {
		let (MulExpr(lhs), AddExpr(rhs)) = (lhs, rhs);
		AddExpr(Sub(box lhs, box rhs))
	},
	lhs:MulExpr => {
		let MulExpr(lhs) = lhs;
		AddExpr(lhs)
	}
}

struct MulExpr(Expr);

scanner! { MulExpr,
	lhs:AtomExpr "*" rhs:MulExpr => {
		let (AtomExpr(lhs), MulExpr(rhs)) = (lhs, rhs);
		MulExpr(Mul(box lhs, box rhs))
	},
	lhs:AtomExpr "/" rhs:MulExpr => {
		let (AtomExpr(lhs), MulExpr(rhs)) = (lhs, rhs);
		MulExpr(Div(box lhs, box rhs))
	},
	lhs:AtomExpr => {
		let AtomExpr(lhs) = lhs;
		MulExpr(lhs)
	}
}

struct AtomExpr(Expr);

scanner! { AtomExpr,
	"(" expr:AddExpr ")" => {
		let AddExpr(expr) = expr;
		AtomExpr(expr)
	},
	lit => AtomExpr(Lit(lit)),
	#[tokenizer="IdentsAndInts"]
	name => AtomExpr(Var(name)),
}

trait ZipOption<L> {
	fn zip<R>(self, other: Option<R>) -> Option<(L, R)>;
}

impl<L> ZipOption<L> for Option<L> {
	fn zip<R>(self, other: Option<R>) -> Option<(L, R)> {
		match (self, other) {
			(None, _) => None,
			(Some(_), None) => None,
			(Some(l), Some(r)) => Some((l, r))
		}
	}
}
<commit_msg>Added some comments.<commit_after>/*!
This crate implements a simple arithmetic calculator, using the [`rust-scan`](https://github.com/DanielKeep/rust-scan.git) package to do the parsing.
*/
#![feature(phase)]

#[phase(plugin)] extern crate scan;
extern crate scan_util;

use std::collections::HashMap;
use scan_util::{OtherScanError, ScanIoError};

fn main() {
	let mut vars = HashMap::new();

	vars.insert("NaN".into_string(), Float::nan());
	vars.insert("inf".into_string(), Float::infinity());
	vars.insert("pi".into_string(), Float::pi());
	vars.insert("e".into_string(), Float::e());

	println!("Type `.help` or `.h` for brief instructions.");
	println!("Type `.quit` or `.q` to quit.");

    loop {
    	print!("> ");
    	let res = scanln! {
    		// Show brief help message.
    		".h" | ".help" => {
    			println!("Type expressions to be evaluated.");
    			println!("You can use +, -, *, /.");
    			println!("You can define variables like so: `let pi = 3.14159`.");
    			println!("Type .q or .quit to quit.");
    			continue;
    		},

    		// Exit.
    		".q" | ".quit" => {
    			println!("Bye!")
    			return;
    		},

    		// Fallback for unknown commands.
			"." cmd:&str, .._ => {
    			println!("error: unknown command `{}`", cmd);
    			println!("Try `.help`.");
    			continue;
    		},

    		// Ignore empty lines.
			_:() => continue,

    		// Otherwise, try to scan a statement.
    		stmt:Stmt => stmt,
    	};

    	let stmt = match res {
    		Err(ScanIoError(err)) => {
    			println!("io error: {}", err);
    			std::os::set_exit_status(1);
    			return;
    		},
    		Err(err @ OtherScanError(..)) => {
    			println!("error: {:s}", err);
    			continue;
    		},
    		Ok(stmt) => stmt,
    	};

    	match stmt {
    		LetStmt(name, expr) => {
    			let vars = &mut vars;
    			eval(expr, vars).map(|v| vars.insert(name.clone(), v));
    		},
    		ExprStmt(expr) => {
    			eval(expr, &vars).map(|v| println!("= {}", v));
    		},
    	}
    }
}

/**
Evaluates an expression, returning `Some(f64)` if there were no errors.

If something *does* go wrong, it will just print the error message and return `None`.  This really should be using `Result`, but I'm too lazy for that.
*/
fn eval(expr: Expr, vars: &HashMap<String, f64>) -> Option<f64> {
	match expr {
		Add(box lhs, box rhs) => eval(lhs, vars).zip(eval(rhs, vars)).map(|(l,r)| l+r),
		Sub(box lhs, box rhs) => eval(lhs, vars).zip(eval(rhs, vars)).map(|(l,r)| l-r),
		Mul(box lhs, box rhs) => eval(lhs, vars).zip(eval(rhs, vars)).map(|(l,r)| l*r),
		Div(box lhs, box rhs) => eval(lhs, vars).zip(eval(rhs, vars)).map(|(l,r)| l/r),
		Var(name) => {
			match vars.find_copy(&name) {
				Some(v) => Some(v),
				None => {
					println!("error: undefined variable `{}`.", name);
					None
				}
			}
		},
		Lit(v) => Some(v),
	}
}

/*

The trick to using scan here is to map each grammar production to a type, then implement a scanner for that type.  As results are passed back up the call stack, we "unpack" the grammar production type into the actual semantic type.

This is actually something of a limitation of the `Scanner` trait; ideally, we could use UFCS (which isn't implemented yet) to have a capture like `lhs:MulExpr` which uses the scanner code defined for the `MulExpr` type, but which *results* in a `Expr` value.

The grammar we're parsing is given below.  Note that it's arranged this way to encode operator precedence directly into the grammar.

```{notrust}
<expr> := <add-expr>
<add-expr> := <mul-expr> ( ( "+" | "-" ) <add-expr> )?
<mul-expr> := <atom-expr> ( ( "*" | "/" ) <mul-expr> )?
<atom-expr> := "(" <add-expr> ")"
             | <literal>
             | <identifier>
```

*/

#[deriving(PartialEq, Show)]
enum Stmt {
	LetStmt(String, Expr),
	ExprStmt(Expr),
}

scanner! { Stmt,
	// We need to use a different tokeniser here to ensure that `name` can be any identifier.
	#[tokenizer="IdentsAndInts"]
	"let" name "=" expr => LetStmt(name, expr),

	expr => ExprStmt(expr),
}

#[deriving(PartialEq, Show)]
enum Expr {
	Add(Box<Expr>, Box<Expr>),
	Sub(Box<Expr>, Box<Expr>),
	Mul(Box<Expr>, Box<Expr>),
	Div(Box<Expr>, Box<Expr>),
	Var(String),
	Lit(f64),
}

scanner! { Expr,
	// Just forward on to the corresponding grammar production: AddExpr.
	expr:AddExpr => {
		let AddExpr(expr) = expr;
		expr
	}
}

struct AddExpr(Expr);

scanner! { AddExpr,
	// We *could* boil this down to a single arm, but it wouldn't be as neat.
	lhs:MulExpr "+" rhs:AddExpr => {
		// Again, we only need MulExpr and AddExpr in order to trigger the correct scanning rules.  What we actually *care* about are the Exprs inside.
		let (MulExpr(lhs), AddExpr(rhs)) = (lhs, rhs);
		AddExpr(Add(box lhs, box rhs))
	},
	lhs:MulExpr "-" rhs:AddExpr => {
		let (MulExpr(lhs), AddExpr(rhs)) = (lhs, rhs);
		AddExpr(Sub(box lhs, box rhs))
	},
	lhs:MulExpr => {
		let MulExpr(lhs) = lhs;
		AddExpr(lhs)
	}
}

struct MulExpr(Expr);

scanner! { MulExpr,
	lhs:AtomExpr "*" rhs:MulExpr => {
		let (AtomExpr(lhs), MulExpr(rhs)) = (lhs, rhs);
		MulExpr(Mul(box lhs, box rhs))
	},
	lhs:AtomExpr "/" rhs:MulExpr => {
		let (AtomExpr(lhs), MulExpr(rhs)) = (lhs, rhs);
		MulExpr(Div(box lhs, box rhs))
	},
	lhs:AtomExpr => {
		let AtomExpr(lhs) = lhs;
		MulExpr(lhs)
	}
}

struct AtomExpr(Expr);

scanner! { AtomExpr,
	"(" expr:AddExpr ")" => {
		let AddExpr(expr) = expr;
		AtomExpr(expr)
	},
	lit => AtomExpr(Lit(lit)),

	// Again, we use a different tokeniser.  Also note that it's not *entirely* correct.  This technically means we can have a variable called `*`, which means `***` is a perfectly valid expression.
	#[tokenizer="IdentsAndInts"]
	name => AtomExpr(Var(name)),
}

/**
This is just a helper trait used to simplify evaluating binary expressions.
*/
trait ZipOption<L> {
	fn zip<R>(self, other: Option<R>) -> Option<(L, R)>;
}

impl<L> ZipOption<L> for Option<L> {
	fn zip<R>(self, other: Option<R>) -> Option<(L, R)> {
		match (self, other) {
			(None, _) => None,
			(Some(_), None) => None,
			(Some(l), Some(r)) => Some((l, r))
		}
	}
}
<|endoftext|>


<commit_before>use self::binary::{builtins, InteractiveShell};
use atty::Stream;
use ion_shell::{BuiltinMap, Shell, Value};
use liner::KeyBindings;
use std::{
    io::{self, stdin, BufReader},
    process,
};

#[cfg(not(feature = "advanced_arg_parsing"))]
use crate::binary::MAN_ION;
#[cfg(not(feature = "advanced_arg_parsing"))]
use std::env;
#[cfg(feature = "advanced_arg_parsing")]
use std::str::FromStr;
#[cfg(feature = "advanced_arg_parsing")]
use structopt::StructOpt;

mod binary;

struct KeyBindingsWrapper(KeyBindings);

#[cfg(feature = "advanced_arg_parsing")]
impl FromStr for KeyBindingsWrapper {
    type Err = String;

    fn from_str(input: &str) -> Result<Self, Self::Err> {
        match input {
            "vi" => Ok(KeyBindingsWrapper(KeyBindings::Vi)),
            "emacs" => Ok(KeyBindingsWrapper(KeyBindings::Emacs)),
            _ => Err("unknown key bindings".to_string()),
        }
    }
}

/// Ion is a commandline shell created to be a faster and easier to use alternative to the
/// currently available shells. It is not POSIX compliant.
#[cfg_attr(feature = "advanced_arg_parsing", derive(StructOpt))]
#[cfg_attr(
    feature = "advanced_arg_parsing",
    structopt(
        name = "Ion - The Ion Shell",
        author = "",
        raw(setting = "structopt::clap::AppSettings::ColoredHelp")
    )
)]
struct CommandLineArgs {
    /// Shortcut layout. Valid options: "vi", "emacs"
    #[cfg_attr(feature = "advanced_arg_parsing", structopt(short = "-o"))]
    key_bindings: Option<KeyBindingsWrapper>,
    /// Print commands before execution
    #[cfg_attr(feature = "advanced_arg_parsing", structopt(short = "-x"))]
    print_commands: bool,
    /// Force interactive mode
    #[cfg_attr(feature = "advanced_arg_parsing", structopt(short = "-i", long = "--interactive"))]
    interactive: bool,
    /// Do not execute any commands, perform only syntax checking
    #[cfg_attr(feature = "advanced_arg_parsing", structopt(short = "-n", long = "--no-execute"))]
    no_execute: bool,
    /// Evaluate given commands instead of reading from the commandline
    #[cfg_attr(feature = "advanced_arg_parsing", structopt(short = "-c"))]
    command: Option<String>,
    /// Print the version, platform and revision of Ion then exit
    #[cfg_attr(feature = "advanced_arg_parsing", structopt(short = "-v", long = "--version"))]
    version: bool,
    /// Script arguments (@args). If the -c option is not specified,
    /// the first parameter is taken as a filename to execute
    #[cfg_attr(feature = "advanced_arg_parsing", structopt())]
    args: Vec<String>,
}

fn version() -> String { include!(concat!(env!("OUT_DIR"), "/version_string")).to_string() }

#[cfg(feature = "advanced_arg_parsing")]
fn parse_args() -> CommandLineArgs { CommandLineArgs::from_args() }

#[cfg(not(feature = "advanced_arg_parsing"))]
fn parse_args() -> CommandLineArgs {
    let mut args = env::args().skip(1);
    let mut command = None;
    let mut key_bindings = None;
    let mut no_execute = false;
    let mut print_commands = false;
    let mut interactive = false;
    let mut version = false;
    let mut additional_arguments = Vec::new();
    while let Some(arg) = args.next() {
        match arg.as_str() {
            "-o" => {
                key_bindings = match args.next().as_ref().map(|s| s.as_str()) {
                    Some("vi") => Some(KeyBindingsWrapper(KeyBindings::Vi)),
                    Some("emacs") => Some(KeyBindingsWrapper(KeyBindings::Emacs)),
                    Some(_) => {
                        eprintln!("ion: invalid option for option -o");
                        process::exit(1);
                    }
                    None => {
                        eprintln!("ion: no option given for option -o");
                        process::exit(1);
                    }
                }
            }
            "-x" => print_commands = true,
            "-n" | "--no-execute" => no_execute = true,
            "-c" => command = args.next(),
            "-v" | "--version" => version = true,
            "-h" | "--help" => {
                println!("{}", MAN_ION);
                process::exit(0);
            }
            "-i" | "--interactive" => interactive = true,
            _ => {
                additional_arguments.push(arg);
            }
        }
    }
    CommandLineArgs {
        key_bindings,
        print_commands,
        interactive,
        no_execute,
        command,
        version,
        args: additional_arguments,
    }
}

fn set_unique_pid() -> io::Result<()> {
    unsafe {
        let pgid = libc::getpid();
        libc::setpgid(0, pgid);
        libc::signal(libc::SIGTTOU, libc::SIG_IGN);
        libc::tcsetpgrp(libc::STDIN_FILENO, pgid);
    }
    Ok(())
}

fn main() {
    let command_line_args = parse_args();

    if command_line_args.version {
        println!("{}", version());
        return;
    }

    let mut builtins = BuiltinMap::default();
    builtins
        .with_unsafe()
        .add("exec", &builtins::builtin_exec, "Replace the shell with the given command.")
        .add("exit", &builtins::builtin_exit, "Exits the current session")
        .add("suspend", &builtins::builtin_suspend, "Suspends the shell with a SIGTSTOP signal");

    let stdin_is_a_tty = atty::is(Stream::Stdin);
    let mut shell = Shell::with_builtins(builtins);

    if stdin_is_a_tty {
        if let Err(err) = set_unique_pid() {
            println!("ion: could not bring shell to foreground: {}", err);
        }
    }

    shell.opts_mut().print_comms = command_line_args.print_commands;
    shell.opts_mut().no_exec = command_line_args.no_execute;
    shell.opts_mut().is_background_shell = !stdin_is_a_tty;

    let script_path = command_line_args.args.get(0).cloned();
    shell.variables_mut().set(
        "args",
        Value::Array(
            command_line_args.args.into_iter().map(|arg| Value::Str(arg.into())).collect(),
        ),
    );

    let err = if let Some(command) = command_line_args.command {
        shell.execute_command(command.as_bytes())
    } else if let Some(path) = script_path {
        shell.execute_file(path)
    } else if stdin_is_a_tty || command_line_args.interactive {
        let mut interactive = InteractiveShell::new(shell);
        if let Some(key_bindings) = command_line_args.key_bindings {
            interactive.set_keybindings(key_bindings.0);
        }
        interactive.add_callbacks();
        interactive.execute_interactive();
    } else {
        shell.execute_command(BufReader::new(stdin()))
    };
    if let Err(why) = err {
        eprintln!("ion: {}", why);
        process::exit(1);
    }
    if let Err(why) = shell.wait_for_background() {
        eprintln!("ion: {}", why);
        process::exit(1);
    }
    process::exit(shell.previous_status().as_os_code());
}
<commit_msg>Support the set_unique_pid function<commit_after>use self::binary::{builtins, InteractiveShell};
use atty::Stream;
use ion_shell::{BuiltinMap, Shell, Value};
use liner::KeyBindings;
use nix::{
    sys::signal::{self, SigHandler, Signal},
    unistd,
};
use std::{
    io::{stdin, BufReader},
    process,
};

#[cfg(not(feature = "advanced_arg_parsing"))]
use crate::binary::MAN_ION;
#[cfg(not(feature = "advanced_arg_parsing"))]
use std::env;
#[cfg(feature = "advanced_arg_parsing")]
use std::str::FromStr;
#[cfg(feature = "advanced_arg_parsing")]
use structopt::StructOpt;

mod binary;

struct KeyBindingsWrapper(KeyBindings);

#[cfg(feature = "advanced_arg_parsing")]
impl FromStr for KeyBindingsWrapper {
    type Err = String;

    fn from_str(input: &str) -> Result<Self, Self::Err> {
        match input {
            "vi" => Ok(KeyBindingsWrapper(KeyBindings::Vi)),
            "emacs" => Ok(KeyBindingsWrapper(KeyBindings::Emacs)),
            _ => Err("unknown key bindings".to_string()),
        }
    }
}

/// Ion is a commandline shell created to be a faster and easier to use alternative to the
/// currently available shells. It is not POSIX compliant.
#[cfg_attr(feature = "advanced_arg_parsing", derive(StructOpt))]
#[cfg_attr(
    feature = "advanced_arg_parsing",
    structopt(
        name = "Ion - The Ion Shell",
        author = "",
        raw(setting = "structopt::clap::AppSettings::ColoredHelp")
    )
)]
struct CommandLineArgs {
    /// Shortcut layout. Valid options: "vi", "emacs"
    #[cfg_attr(feature = "advanced_arg_parsing", structopt(short = "-o"))]
    key_bindings: Option<KeyBindingsWrapper>,
    /// Print commands before execution
    #[cfg_attr(feature = "advanced_arg_parsing", structopt(short = "-x"))]
    print_commands: bool,
    /// Force interactive mode
    #[cfg_attr(feature = "advanced_arg_parsing", structopt(short = "-i", long = "--interactive"))]
    interactive: bool,
    /// Do not execute any commands, perform only syntax checking
    #[cfg_attr(feature = "advanced_arg_parsing", structopt(short = "-n", long = "--no-execute"))]
    no_execute: bool,
    /// Evaluate given commands instead of reading from the commandline
    #[cfg_attr(feature = "advanced_arg_parsing", structopt(short = "-c"))]
    command: Option<String>,
    /// Print the version, platform and revision of Ion then exit
    #[cfg_attr(feature = "advanced_arg_parsing", structopt(short = "-v", long = "--version"))]
    version: bool,
    /// Script arguments (@args). If the -c option is not specified,
    /// the first parameter is taken as a filename to execute
    #[cfg_attr(feature = "advanced_arg_parsing", structopt())]
    args: Vec<String>,
}

fn version() -> String { include!(concat!(env!("OUT_DIR"), "/version_string")).to_string() }

#[cfg(feature = "advanced_arg_parsing")]
fn parse_args() -> CommandLineArgs { CommandLineArgs::from_args() }

#[cfg(not(feature = "advanced_arg_parsing"))]
fn parse_args() -> CommandLineArgs {
    let mut args = env::args().skip(1);
    let mut command = None;
    let mut key_bindings = None;
    let mut no_execute = false;
    let mut print_commands = false;
    let mut interactive = false;
    let mut version = false;
    let mut additional_arguments = Vec::new();
    while let Some(arg) = args.next() {
        match arg.as_str() {
            "-o" => {
                key_bindings = match args.next().as_ref().map(|s| s.as_str()) {
                    Some("vi") => Some(KeyBindingsWrapper(KeyBindings::Vi)),
                    Some("emacs") => Some(KeyBindingsWrapper(KeyBindings::Emacs)),
                    Some(_) => {
                        eprintln!("ion: invalid option for option -o");
                        process::exit(1);
                    }
                    None => {
                        eprintln!("ion: no option given for option -o");
                        process::exit(1);
                    }
                }
            }
            "-x" => print_commands = true,
            "-n" | "--no-execute" => no_execute = true,
            "-c" => command = args.next(),
            "-v" | "--version" => version = true,
            "-h" | "--help" => {
                println!("{}", MAN_ION);
                process::exit(0);
            }
            "-i" | "--interactive" => interactive = true,
            _ => {
                additional_arguments.push(arg);
            }
        }
    }
    CommandLineArgs {
        key_bindings,
        print_commands,
        interactive,
        no_execute,
        command,
        version,
        args: additional_arguments,
    }
}

fn set_unique_pid() -> nix::Result<()> {
    let pgid = unistd::getpid();
    unistd::setpgid(pgid, pgid)?;
    unsafe { signal::signal(Signal::SIGTTOU, SigHandler::SigIgn) }?;
    unistd::tcsetpgrp(nix::libc::STDIN_FILENO, pgid)
}

fn main() {
    let command_line_args = parse_args();

    if command_line_args.version {
        println!("{}", version());
        return;
    }

    let mut builtins = BuiltinMap::default();
    builtins
        .with_unsafe()
        .add("exec", &builtins::builtin_exec, "Replace the shell with the given command.")
        .add("exit", &builtins::builtin_exit, "Exits the current session")
        .add("suspend", &builtins::builtin_suspend, "Suspends the shell with a SIGTSTOP signal");

    let stdin_is_a_tty = atty::is(Stream::Stdin);
    let mut shell = Shell::with_builtins(builtins);

    if stdin_is_a_tty {
        if let Err(err) = set_unique_pid() {
            println!("ion: could not bring shell to foreground: {}", err);
        }
    }

    shell.opts_mut().print_comms = command_line_args.print_commands;
    shell.opts_mut().no_exec = command_line_args.no_execute;
    shell.opts_mut().is_background_shell = !stdin_is_a_tty;

    let script_path = command_line_args.args.get(0).cloned();
    shell.variables_mut().set(
        "args",
        Value::Array(
            command_line_args.args.into_iter().map(|arg| Value::Str(arg.into())).collect(),
        ),
    );

    let err = if let Some(command) = command_line_args.command {
        shell.execute_command(command.as_bytes())
    } else if let Some(path) = script_path {
        shell.execute_file(path)
    } else if stdin_is_a_tty || command_line_args.interactive {
        let mut interactive = InteractiveShell::new(shell);
        if let Some(key_bindings) = command_line_args.key_bindings {
            interactive.set_keybindings(key_bindings.0);
        }
        interactive.add_callbacks();
        interactive.execute_interactive();
    } else {
        shell.execute_command(BufReader::new(stdin()))
    };
    if let Err(why) = err {
        eprintln!("ion: {}", why);
        process::exit(1);
    }
    if let Err(why) = shell.wait_for_background() {
        eprintln!("ion: {}", why);
        process::exit(1);
    }
    process::exit(shell.previous_status().as_os_code());
}
<|endoftext|>


<commit_before>// Worklog is for recording your hours.
//
extern crate chrono;
extern crate csv;
extern crate clap;
extern crate rustc_serialize;

mod error;
mod timeclock;
mod util;
mod parsers;

use chrono::*;
use clap::{Arg, ArgGroup, App};
use error::WorklogError;
use std::env;
use std::fs::OpenOptions;
use std::io::prelude::*;
use std::path::PathBuf;
use timeclock::Direction;
use timeclock::now;

#[cfg(target_family = "unix")]
static CSV_FILE_NAME: &'static str = ".worklog.csv";
#[cfg(target_family = "windows")]
static CSV_FILE_NAME: &'static str = "worklog.csv";

// default rounding mode
static ROUNDING_DEFAULT: &'static str = "+15m";

// TODO This belongs in a config file somewhere
const WEEKSTART: i64 = Weekday::Sat as i64;

fn print_csv_entries<R: Read>(file: R) -> Result<(), WorklogError> {
    let csv_entries = try!(timeclock::read_timesheet(file));
    for rec in csv_entries {
        println!("{}", rec);
    }
    Ok(())
}

fn print_full_summary<R: Read>(file: R,
                               rounding: util::Rounding)
                               -> Result<(), WorklogError> {
    let csv_entries = try!(timeclock::read_timesheet(file));
    let records = timeclock::collect_date_records(csv_entries);

    let mut total_hours: f64 = 0.0;
    for rec in records {
        let hours = util::round(rec.seconds(), rounding) / 3600.0;
        total_hours += hours;
        println!("{} {:.2} {}", rec.date().format("%F"), hours, rec.memo());
    }

    println!("Total Hours: {:.2}", total_hours);
    Ok(())
}

fn print_short_summary<R: Read>(file: R,
                                start_date: Date<FixedOffset>,
                                end_date: Date<FixedOffset>,
                                rounding: util::Rounding)
                                -> Result<(), WorklogError> {
    let csv_entries = try!(timeclock::read_timesheet(file));
    let records = timeclock::collect_date_records(csv_entries);

    let mut total_hours: f64 = 0.0;
    for rec in records {
        if start_date <= rec.date() && rec.date() <= end_date {
            let hours = util::round(rec.seconds(), rounding) / 3600.0;
            total_hours += hours;
            println!("{} {:.2} {}", rec.date().format("%F"), hours, rec.memo());
        }
    }

    println!("Total Hours: {:.2}", total_hours);
    Ok(())
}

/// Get the path for the csv data file
fn get_csv_path() -> Result<PathBuf, WorklogError> {
    let mut data_path = match env::home_dir() {
        Some(path) => path,
        None => {
            return Err(env::VarError::NotPresent).map_err(WorklogError::Env)
        }
    };

    data_path.push(CSV_FILE_NAME);
    Ok(data_path)
}

fn main0() -> Result<(), WorklogError> {
    // Using std env macro rather than depending on clap's. No difference
    // as far as I can tell.
    // The current options aren't final and will definately change again soon
    let matches = App::new(env!("CARGO_PKG_NAME"))
        .version(env!("CARGO_PKG_VERSION"))
        .author(env!("CARGO_PKG_AUTHORS"))
        .about(env!("CARGO_PKG_DESCRIPTION"))
        .arg(Arg::from_usage("[in] -i, --in 'Record an In entry'"))
        .arg(Arg::from_usage("[out] -o, --out 'Record an Out entry'"))
        .arg(Arg::from_usage("[time] -t, --time <TIME> 'time'")
            .allow_hyphen_values(true)
            .requires("inout"))
        .arg(Arg::from_usage("[memo] -m, --memo <MEMO> 'Memo for the entry'")
            .requires("inout"))
        .arg(Arg::from_usage("[summary] -s, --summary 'Print a summary'")
            .conflicts_with("inout"))
        .arg(Arg::from_usage("[log] -l, --log 'Print the full log'")
            .conflicts_with("summary")
            .conflicts_with("inout"))
        .arg(Arg::from_usage("[round] -r, --round-up 'Round totals up to the next quarter hour'")
            .conflicts_with("log")
            .conflicts_with("inout"))
        .arg(Arg::from_usage("[round_ex] -R, --round <ROUNDING> 'Round totals up, down, half'")
            .allow_hyphen_values(true)
            .conflicts_with("log")
            .conflicts_with("inout"))
        .arg(Arg::from_usage("[range] --range <TIME> <TIME> 'range'")
            .conflicts_with("summary")
            .conflicts_with("log")
            .conflicts_with("inout"))
        .group(ArgGroup::with_name("inout").args(&["in", "out"]))
        .get_matches();

    let csv_path = try!(get_csv_path());

    let mut csv_file = try!(OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open(csv_path));

    let rounding = {
        if matches.occurrences_of("round") > 0 {
            try!(parsers::parse_rounding(ROUNDING_DEFAULT))
        } else if matches.occurrences_of("round_ex") > 0 {
            try!(parsers::parse_rounding(matches.value_of("round_ex").unwrap()))
        } else {
            util::Rounding::None
        }
    };

    if matches.is_present("in") || matches.is_present("out") {
        let dir = if matches.is_present("in") {
            Direction::In
        } else {
            Direction::Out
        };

        let time = match matches.value_of("time") {
            Some(a) => {
                try!(parsers::parse_datetime(&a, now())
                    .or(parsers::parse_offset(&a, now())))
            }
            None => now(),
        };

        let memo = matches.value_of("memo").unwrap_or("").to_owned();
        timeclock::mark_time(dir, time, memo, &mut csv_file);

        println!("Clocked {:#} at {}", dir, time.format("%F %I:%M %P"));

    } else if matches.is_present("summary") {
        try!(print_full_summary(&csv_file, rounding));

    } else if matches.is_present("log") {
        try!(print_csv_entries(&csv_file));

    } else if matches.is_present("range") {
        let time = now();
        let range = matches.values_of("range").unwrap();//.collect();
        let range: Vec<DateTime<FixedOffset>> =
            try!(range.map(|a| parsers::parse_datetime(a, time)).collect());
        try!(print_short_summary(&csv_file,
                                 range[0].date(),
                                 range[1].date(),
                                 rounding));

    } else {
        let today = now().date();
        let weekday = today.weekday() as i64;
        let days_back = (7 - WEEKSTART + weekday) % 7;
        let start_date = today - Duration::days(days_back);
        try!(print_short_summary(&csv_file, start_date, today, rounding));
    }

    Ok(())
}

fn main() {
    match main0() {
        Ok(_) => {}
        Err(err) => {
            let _ = writeln!(&mut std::io::stderr(), "Error: {}", err);
        }
    };
}
<commit_msg>Allow range as option to --summary<commit_after>// Worklog is for recording your hours.
//
extern crate chrono;
extern crate csv;
extern crate clap;
extern crate rustc_serialize;

mod error;
mod timeclock;
mod util;
mod parsers;

use chrono::*;
use clap::{Arg, ArgGroup, App};
use error::WorklogError;
use std::env;
use std::fs::OpenOptions;
use std::io::prelude::*;
use std::path::PathBuf;
use timeclock::Direction;
use timeclock::now;

#[cfg(target_family = "unix")]
static CSV_FILE_NAME: &'static str = ".worklog.csv";
#[cfg(target_family = "windows")]
static CSV_FILE_NAME: &'static str = "worklog.csv";

// default rounding mode
static ROUNDING_DEFAULT: &'static str = "+15m";

// TODO This belongs in a config file somewhere
const WEEKSTART: i64 = Weekday::Sat as i64;

fn print_csv_entries<R: Read>(file: R) -> Result<(), WorklogError> {
    let csv_entries = try!(timeclock::read_timesheet(file));
    for rec in csv_entries {
        println!("{}", rec);
    }
    Ok(())
}

fn print_full_summary<R: Read>(file: R,
                               rounding: util::Rounding)
                               -> Result<(), WorklogError> {
    let csv_entries = try!(timeclock::read_timesheet(file));
    let records = timeclock::collect_date_records(csv_entries);

    let mut total_hours: f64 = 0.0;
    for rec in records {
        let hours = util::round(rec.seconds(), rounding) / 3600.0;
        total_hours += hours;
        println!("{} {:.2} {}", rec.date().format("%F"), hours, rec.memo());
    }

    println!("Total Hours: {:.2}", total_hours);
    Ok(())
}

fn print_short_summary<R: Read>(file: R,
                                start_date: Date<FixedOffset>,
                                end_date: Date<FixedOffset>,
                                rounding: util::Rounding)
                                -> Result<(), WorklogError> {
    let csv_entries = try!(timeclock::read_timesheet(file));
    let records = timeclock::collect_date_records(csv_entries);

    let mut total_hours: f64 = 0.0;
    for rec in records {
        if start_date <= rec.date() && rec.date() <= end_date {
            let hours = util::round(rec.seconds(), rounding) / 3600.0;
            total_hours += hours;
            println!("{} {:.2} {}", rec.date().format("%F"), hours, rec.memo());
        }
    }

    println!("Total Hours: {:.2}", total_hours);
    Ok(())
}

/// Get the path for the csv data file
fn get_csv_path() -> Result<PathBuf, WorklogError> {
    let mut data_path = match env::home_dir() {
        Some(path) => path,
        None => {
            return Err(env::VarError::NotPresent).map_err(WorklogError::Env)
        }
    };

    data_path.push(CSV_FILE_NAME);
    Ok(data_path)
}

fn main0() -> Result<(), WorklogError> {
    // Using std env macro rather than depending on clap's. No difference
    // as far as I can tell.
    // The current options aren't final and will definately change again soon
    let matches = App::new(env!("CARGO_PKG_NAME"))
        .version(env!("CARGO_PKG_VERSION"))
        .author(env!("CARGO_PKG_AUTHORS"))
        .about(env!("CARGO_PKG_DESCRIPTION"))
        .arg(Arg::from_usage("[in] -i, --in 'Record an In entry'"))
        .arg(Arg::from_usage("[out] -o, --out 'Record an Out entry'"))
        .arg(Arg::from_usage("[time] -t, --time <TIME> 'time'")
            .allow_hyphen_values(true)
            .requires("inout"))
        .arg(Arg::from_usage("[memo] -m, --memo <MEMO> 'Memo for the entry'")
            .requires("inout"))
        .arg(Arg::from_usage("[summary] -s, --summary 'Print a summary'")
            .conflicts_with("inout"))
        .arg(Arg::from_usage("[log] -l, --log 'Print the full log'")
            .conflicts_with("summary")
            .conflicts_with("inout"))
        .arg(Arg::from_usage("[round] -r, --round-up 'Round totals up to the next quarter hour'")
            .conflicts_with("log")
            .conflicts_with("inout"))
        .arg(Arg::from_usage("[round_ex] -R, --round <ROUNDING> 'Round totals up, down, half'")
            .allow_hyphen_values(true)
            .conflicts_with("log")
            .conflicts_with("inout"))
        .arg(Arg::from_usage("[range] --range <TIME> <TIME> 'range'")
            .conflicts_with("log")
            .conflicts_with("inout"))
        .group(ArgGroup::with_name("inout").args(&["in", "out"]))
        .get_matches();

    let csv_path = try!(get_csv_path());

    let mut csv_file = try!(OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open(csv_path));

    let rounding = {
        if matches.occurrences_of("round") > 0 {
            try!(parsers::parse_rounding(ROUNDING_DEFAULT))
        } else if matches.occurrences_of("round_ex") > 0 {
            try!(parsers::parse_rounding(matches.value_of("round_ex").unwrap()))
        } else {
            util::Rounding::None
        }
    };

    let ctime = now();

    let (start_date, end_date): (Date<FixedOffset>, Date<FixedOffset>) = {
        if matches.is_present("range") {
            let range = matches.values_of("range").unwrap();
            let mut range: Vec<DateTime<FixedOffset>> =
                try!(range.map(|a| parsers::parse_datetime(a, ctime))
                    .collect()); // <Result<Vec<DateTime<FixedOffset>>, parsers::ParseError>>
            range.sort();
            (range[0].date(), range[1].date())
        } else {
            let ofst = ctime.offset().to_owned();
            (Date::from_utc(naive::date::MIN, ofst),
             Date::from_utc(naive::date::MAX, ofst))
        }
    };

    if matches.is_present("in") || matches.is_present("out") {
        let dir = if matches.is_present("in") {
            Direction::In
        } else {
            Direction::Out
        };

        let time = match matches.value_of("time") {
            Some(a) => {
                try!(parsers::parse_datetime(&a, now())
                    .or(parsers::parse_offset(&a, now())))
            }
            None => now(),
        };

        let memo = matches.value_of("memo").unwrap_or("").to_owned();
        timeclock::mark_time(dir, time, memo, &mut csv_file);

        println!("Clocked {:#} at {}", dir, time.format("%F %I:%M %P"));

    } else if matches.is_present("summary") {
        try!(print_short_summary(&csv_file, start_date, end_date, rounding));

    } else if matches.is_present("log") {
        try!(print_csv_entries(&csv_file));

    } else if matches.is_present("range") {
        try!(print_short_summary(&csv_file, start_date, end_date, rounding));

    } else {
        let today = now().date();
        let weekday = today.weekday() as i64;
        let days_back = (7 - WEEKSTART + weekday) % 7;
        let start_date = today - Duration::days(days_back);
        try!(print_short_summary(&csv_file, start_date, today, rounding));
    }

    Ok(())
}

fn main() {
    match main0() {
        Ok(_) => {}
        Err(err) => {
            let _ = writeln!(&mut std::io::stderr(), "Error: {}", err);
        }
    };
}
<|endoftext|>