<commit_before>/// Reader will read the entries from stdin or command output
/// And send the entries to controller, the controller will save it into model.

extern crate libc;

use std::process::{Command, Stdio};
use std::sync::{Arc, RwLock};
use std::io::{stdin, BufRead, BufReader};
use std::error::Error;
use util::eventbox::EventBox;
use event::Event;
use item::Item;

pub struct Reader {
    cmd: String, // command to invoke
    eb: Arc<EventBox<Event>>,         // eventbox
    items: Arc<RwLock<Vec<Item>>>, // all items
}

impl Reader {

    pub fn new(cmd: String, eb: Arc<EventBox<Event>>, items: Arc<RwLock<Vec<Item>>>) -> Self {
        Reader{cmd: cmd, eb: eb, items: items}
    }

    // invoke find comand.
    fn get_command_output(&self) -> Result<Box<BufRead>, Box<Error>> {
        let command = try!(Command::new("sh")
                           .arg("-c")
                           .arg(&self.cmd)
                           .stdout(Stdio::piped())
                           .stderr(Stdio::null())
                           .spawn());
        let stdout = try!(command.stdout.ok_or("command output: unwrap failed".to_owned()));
        Ok(Box::new(BufReader::new(stdout)))
    }

    pub fn run(&mut self) {
        // check if the input is TTY
        let istty = unsafe { libc::isatty(libc::STDIN_FILENO as i32) } != 0;

        let mut read;
        if istty {
            read = self.get_command_output().expect("command not found");
        } else {
            read = Box::new(BufReader::new(stdin()))
        };

        loop {
            let mut input = String::new();
            match read.read_line(&mut input) {
                Ok(n) => {
                    if n <= 0 { break; }

                    if input.ends_with("\n") {
                        input.pop();
                        if input.ends_with("\r") {
                            input.pop();
                        }
                    }
                    let mut items = self.items.write().unwrap();
                    items.push(Item::new(input));
                }
                Err(_err) => { break; }
            }
            self.eb.set(Event::EvReaderNewItem, Box::new(true));
        }
        self.eb.set(Event::EvReaderNewItem, Box::new(false));
    }
}

<commit_msg>[reader] limit the speed of sending new items.<commit_after>/// Reader will read the entries from stdin or command output
/// And send the entries to controller, the controller will save it into model.

extern crate libc;

use std::process::{Command, Stdio};
use std::sync::{Arc, RwLock};
use std::io::{stdin, BufRead, BufReader};
use std::error::Error;
use util::eventbox::EventBox;
use event::Event;
use item::Item;

const READER_EVENT_DURATION: u64 = 30;

pub struct Reader {
    cmd: String, // command to invoke
    eb: Arc<EventBox<Event>>,         // eventbox
    items: Arc<RwLock<Vec<Item>>>, // all items
}

impl Reader {

    pub fn new(cmd: String, eb: Arc<EventBox<Event>>, items: Arc<RwLock<Vec<Item>>>) -> Self {
        Reader{cmd: cmd, eb: eb, items: items}
    }

    // invoke find comand.
    fn get_command_output(&self) -> Result<Box<BufRead>, Box<Error>> {
        let command = try!(Command::new("sh")
                           .arg("-c")
                           .arg(&self.cmd)
                           .stdout(Stdio::piped())
                           .stderr(Stdio::null())
                           .spawn());
        let stdout = try!(command.stdout.ok_or("command output: unwrap failed".to_owned()));
        Ok(Box::new(BufReader::new(stdout)))
    }

    pub fn run(&mut self) {
        // check if the input is TTY
        let istty = unsafe { libc::isatty(libc::STDIN_FILENO as i32) } != 0;

        let mut read;
        if istty {
            read = self.get_command_output().expect("command not found");
        } else {
            read = Box::new(BufReader::new(stdin()))
        };

        loop {
            let mut input = String::new();
            match read.read_line(&mut input) {
                Ok(n) => {
                    if n <= 0 { break; }

                    if input.ends_with("\n") {
                        input.pop();
                        if input.ends_with("\r") {
                            input.pop();
                        }
                    }
                    let mut items = self.items.write().unwrap();
                    items.push(Item::new(input));
                }
                Err(_err) => { break; }
            }

            self.eb.set_throttle(Event::EvReaderNewItem, Box::new(true), READER_EVENT_DURATION);
        }
        self.eb.set_throttle(Event::EvReaderNewItem, Box::new(false), READER_EVENT_DURATION);
    }
}

<|endoftext|>


<commit_before>
use std::num::Float;

use world::{World, RayCastResult};
use math::{LineSeg, Vec2, Mat3};
use game::{Game};

const MAP_SCALE: f32 = 2.0;
const FOV_DIV: f32 = 300.0;
const VISPLANE_DIST: f32 = 300.0;
const PERSON_HEIGHT: f32 = 5.0;

struct RenderContext<'a> {
    pub pixels: &'a mut [u8],
    pub width: usize,
    pub height: usize,
}

impl<'a> Game<'a> {
    pub fn render(&self, pixels: &mut [u8], w :usize, h: usize) {
        let mut ctx = RenderContext {
            pixels: pixels,
            width: w,
            height: h,
        };

        if self.show_map {
            self.render_map(&mut ctx);
        } else {
            self.render_game(&mut ctx);
        }
    }

    fn render_map(&self, ctx: &mut RenderContext) {
        let trans = Mat3::rotation(-self.face_angle)
                  * Mat3::translation(-self.pos * MAP_SCALE)
                  * Mat3::scale(Vec2::new(MAP_SCALE, MAP_SCALE));

        ctx.clear();
        let (player_x, player_y) = (ctx.width / 2, ctx.height / 2);
        ctx.put_px(player_x, player_y, 0xFF, 0x00, 0x00);

        for wall in self.world.get_walls().iter() {
            ctx.draw_seg(wall.seg.transform(trans), 0xFF, 0x00, 0x00);
        }
    }

    fn render_game(&self, ctx: &mut RenderContext) {
        let person_height = PERSON_HEIGHT + Float::abs(Float::sin(self.t)) * 10.0;
        let w = ctx.width;
        let h = ctx.height;

        for x in 0..w {
            let offset = ((x as f32) - (w as f32) / 2.0) / FOV_DIV;

            let cast = self.world.cast_ray(0, self.pos, self.face_angle + offset);
            if cast.is_empty() { continue; }
            let RayCastResult {dist, along, hit_pos, in_info, out_info} = cast[0];

            let height = match out_info {
                Some(i) => i.floor_elev - in_info.floor_elev,
                None    => in_info.ceiling_elev - in_info.floor_elev,
            };

            let cast_dist = dist * Float::cos(offset);
            let pxheight = (VISPLANE_DIST * height / cast_dist) as isize;

            let bottompx = h as isize/2 + (VISPLANE_DIST * person_height / cast_dist) as isize;
            let toppx_ = bottompx - pxheight;
            let toppx = if toppx_ >= h as isize { h as isize - 1 } else { toppx_ };

            for y in 0..toppx {
                ctx.put_px(x, y as usize, 0x00, 0x00, 0x00);
            }

            if toppx >= h as isize || bottompx < 0 {
                continue;
            }

            let top = if toppx >= 0 { toppx as usize } else { 0 };
            let bottom = if bottompx < h as isize { bottompx as usize } else { h };

            let brightness = brightness_from_dist(cast_dist);

            for y in top..bottom {
                let yy = (bottompx as usize - y) as f32 / pxheight as f32 * height / 15.0;
                let tex_lookup = (along * 25.0) as u8 ^ (512.0*yy) as u8;
                let color = ((tex_lookup as f32) * brightness) as u8;
                ctx.put_px(x, y, color / 2, color / 2, color);
            }

            ctx.draw_floor(x, bottom, h, person_height, self.pos, hit_pos, cast_dist);
        }
    }
}

impl<'a> RenderContext<'a> {
    pub fn put_px(&mut self, x: usize, y: usize, r: u8, g: u8, b: u8) {
        self.pixels[3*(self.width*y+x) + 0] = r;
        self.pixels[3*(self.width*y+x) + 1] = g;
        self.pixels[3*(self.width*y+x) + 2] = b;
    }

    pub fn clear(&mut self) {
        for x in 0..self.width {
            for y in 0..self.height {
                self.put_px(x, y, 0x00, 0x00, 0x00);
            }
        }
    }

    pub fn draw_seg(&mut self, seg: LineSeg, r: u8, g: u8, b: u8) {
        let len = seg.get_length();

        for t in 0..(len as usize) {
            let pt = seg.at(t as f32 / len);
            let ux = (pt.x + (self.width /2) as f32) as usize;
            let uy = (pt.y + (self.height/2) as f32) as usize;

            if ux < self.width && uy < self.height {
                self.put_px(ux, uy, r, g, b);
            }
        }
    }

    pub fn draw_floor(&mut self, x: usize, top: usize, bottom: usize, elevation: f32, pos: Vec2, hit_pos: Vec2, cast_dist: f32) {
        for y in top..bottom {
            let dist_floor = VISPLANE_DIST * elevation / (y as f32 - self.height as f32 / 2.0);
            let floor_pos = pos + (hit_pos - pos) * dist_floor / cast_dist;
            let tex_lookup = (floor_pos.x * 10.0) as u8 ^ (floor_pos.y * 10.0) as u8;
            let color = (tex_lookup as f32 * brightness_from_dist(dist_floor)) as u8;
            self.put_px(x, y, 0x00, color, 0x00);
        }
    }
}

fn brightness_from_dist(dist: f32) -> f32 {
    (20.0 / dist).min(1.0).max(0.0)
}

<commit_msg>Moved wall rendering in to RenderContext, added support for looking up and down<commit_after>
use std::num::Float;

use world::{World, RayCastResult};
use math::{LineSeg, Vec2, Mat3};
use game::{Game};

const MAP_SCALE: f32 = 2.0;
const FOV_DIV: f32 = 300.0;
const VISPLANE_DIST: f32 = 300.0;
const PERSON_HEIGHT: f32 = 5.0;

struct RenderContext<'a> {
    pub pixels: &'a mut [u8],
    pub width: isize,
    pub height: isize,
}

impl<'a> Game<'a> {
    pub fn render(&self, pixels: &mut [u8], w :usize, h: usize) {
        let mut ctx = RenderContext {
            pixels: pixels,
            width: w as isize,
            height: h as isize,
        };

        if self.show_map {
            self.render_map(&mut ctx);
        } else {
            self.render_game(&mut ctx);
        }
    }

    fn render_map(&self, ctx: &mut RenderContext) {
        let trans = Mat3::rotation(-self.face_angle)
                  * Mat3::translation(-self.pos * MAP_SCALE)
                  * Mat3::scale(Vec2::new(MAP_SCALE, MAP_SCALE));

        ctx.clear();
        let (player_x, player_y) = (ctx.width / 2, ctx.height / 2);
        ctx.put_px(player_x as usize, player_y as usize, 0xFF, 0x00, 0x00);

        for wall in self.world.get_walls().iter() {
            ctx.draw_seg(wall.seg.transform(trans), 0xFF, 0x00, 0x00);
        }
    }

    fn render_game(&self, ctx: &mut RenderContext) {
        let person_height = PERSON_HEIGHT; //+ Float::abs(Float::sin(self.t)) * 10.0;
        let looking_offset = -(self.t * 10.0) as isize;
        let w = ctx.width as usize;
        let h = ctx.height as usize;

        ctx.clear();

        for x in 0..w {
            let offset = ((x as f32) - (w as f32) / 2.0) / FOV_DIV;

            let cast = self.world.cast_ray(0, self.pos, self.face_angle + offset);
            if cast.is_empty() { continue; }
            let RayCastResult {dist, along, hit_pos, in_info, out_info} = cast[0];

            let height = match out_info {
                Some(i) => i.floor_elev - in_info.floor_elev,
                None    => in_info.ceiling_elev - in_info.floor_elev,
            };

            let cast_dist = dist * Float::cos(offset);
            let pxheight = (VISPLANE_DIST * height / cast_dist) as isize;

            let bottom = h as isize / 2 + (VISPLANE_DIST * person_height / cast_dist) as isize + looking_offset;
            let top = bottom - pxheight;

            ctx.draw_wall(x, top, bottom, along, cast_dist);
            ctx.draw_floor(x, bottom, h as isize, person_height, self.pos, hit_pos, cast_dist, -looking_offset);
        }
    }
}

impl<'a> RenderContext<'a> {
    pub fn put_px(&mut self, x: usize, y: usize, r: u8, g: u8, b: u8) {
        self.pixels[3*(self.width as usize*y+x) + 0] = r;
        self.pixels[3*(self.width as usize*y+x) + 1] = g;
        self.pixels[3*(self.width as usize*y+x) + 2] = b;
    }

    pub fn clear(&mut self) {
        for x in 0..self.width as usize {
            for y in 0..self.height as usize {
                self.put_px(x, y, 0x00, 0x00, 0x00);
            }
        }
    }

    pub fn draw_seg(&mut self, seg: LineSeg, r: u8, g: u8, b: u8) {
        let len = seg.get_length();

        for t in 0..(len as usize) {
            let pt = seg.at(t as f32 / len);
            let ux = (pt.x + (self.width /2) as f32) as usize;
            let uy = (pt.y + (self.height/2) as f32) as usize;

            if ux < self.width as usize && uy < self.height as usize {
                self.put_px(ux, uy, r, g, b);
            }
        }
    }

    pub fn draw_wall(&mut self, x: usize, top: isize, bottom: isize, along:f32, cast_dist: f32) {
        let cut_top = if top < 0 { 0 } else { top } as usize;
        let cut_bottom = if bottom > self.height { self.height } else { bottom } as usize;

        for y in cut_top..cut_bottom {
            let yy = (bottom as usize - y) as f32 * cast_dist / 5000.0;
            let tex_lookup = (along * 25.0) as u8 ^ (512.0*yy) as u8;
            let color = ((tex_lookup as f32) * brightness_from_dist(cast_dist)) as u8;
            self.put_px(x, y, color / 2, color / 2, color);
        }
    }

    pub fn draw_floor(&mut self, x: usize, top: isize, bottom: isize, elevation: f32, pos: Vec2, hit_pos: Vec2, cast_dist: f32, look: isize) {
        let cut_top = if top < self.height / 2 { self.height / 2 } else { top } as usize;
        let cut_bottom = if bottom > self.height { self.height } else { bottom } as usize;

        for y in cut_top..cut_bottom {
            let dist_floor = VISPLANE_DIST * elevation / ((y as isize + look) as f32 - self.height as f32 / 2.0);
            let floor_pos = pos + (hit_pos - pos) * dist_floor / cast_dist;
            let tex_lookup = (floor_pos.x * 10.0) as u8 ^ (floor_pos.y * 10.0) as u8;
            let color = (tex_lookup as f32 * brightness_from_dist(dist_floor)) as u8;
            self.put_px(x, y, 0x00, color, 0x00);
        }
    }
}

fn brightness_from_dist(dist: f32) -> f32 {
    (20.0 / dist).min(1.0).max(0.0)
}

<|endoftext|>


<commit_before>use std::path::Path;
use std::io;
use std::fs::File;
use std::convert;

use serde_json;

use gerrit;
use spark;

#[derive(Debug, Clone, Serialize, Deserialize)]
struct User {
    spark_person_id: spark::PersonId,
    email: spark::Email,
    enabled: bool,
}

impl User {
    fn new(person_id: spark::PersonId, email: spark::Email) -> User {
        User {
            spark_person_id: person_id,
            email: email,
            enabled: true,
        }
    }
}

/// Describes a state of the bot
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Bot {
    gerrit_username: Option<String>, // TODO: this should not be part of the state
    users: Vec<User>,
}

#[derive(Debug)]
pub enum BotError {
    Io(io::Error),
    Serialization(serde_json::Error),
}

impl convert::From<io::Error> for BotError {
    fn from(err: io::Error) -> BotError {
        BotError::Io(err)
    }
}

impl convert::From<serde_json::Error> for BotError {
    fn from(err: serde_json::Error) -> BotError {
        BotError::Serialization(err)
    }
}

impl Bot {
    pub fn new(gerrit_username: gerrit::Username) -> Bot {
        Bot {
            gerrit_username: Some(gerrit_username),
            users: Vec::new(),
        }
    }

    pub fn default() -> Bot {
        Bot {
            gerrit_username: None,
            users: Vec::new(),
        }
    }

    fn enable<'a>(&'a mut self, person_id: &str, email: &str, enabled: bool) -> &'a User {
        // FIXME: Replace linear search
        let pos = self.users.iter().position(
            |u| u.spark_person_id == person_id,
        );
        let user: &'a mut User = match pos {
            Some(pos) => &mut self.users[pos],
            None => {
                self.users.push(User::new(
                    String::from(person_id),
                    String::from(email),
                ));
                self.users.iter_mut().last().unwrap()
            }
        };
        user.enabled = enabled;
        user
    }

    fn get_approvals_msg(&self, event: gerrit::Event) -> Option<(&User, String)> {
        println!("[D] Incoming approvals: {:?}", event);

        let author = event.author;
        let change = event.change;
        let approvals = event.approvals;

        let approver = author.unwrap().username.clone();
        if approver == change.owner.username {
            // No need to notify about user's own approvals.
            return None;
        }

        if let Some(ref owner_email) = change.owner.email {
            // TODO: Fix linear search
            let users = &self.users;
            for user in users.iter() {
                if &user.email == owner_email {
                    if !user.enabled {
                        break;
                    }

                    if let Some(approvals) = approvals {
                        let msgs: Vec<String> = approvals
                            .iter()
                            .filter(|approval| {
                                let filtered = if let Some(ref old_value) = approval.old_value {
                                    old_value != &approval.value && approval.value != "0"
                                } else {
                                    approval.value != "0"
                                };
                                println!("Filtered approval: {:?}", !filtered);
                                filtered
                            })
                            .map(|approval| {
                                let value: i8 = approval.value.parse().unwrap_or(0);
                                format!(
                                    "{}: {}{} ({}) from {}",
                                    change.subject,
                                    if value > 0 { "+" } else { "" },
                                    value,
                                    approval.approval_type,
                                    approver
                                )
                            })
                            .collect();
                        return if !msgs.is_empty() {
                            Some((user, msgs.join("\n")))
                        } else {
                            None
                        };
                    }
                }
            }
        }
        None
    }

    pub fn save<P>(self, filename: P) -> Result<(), BotError>
    where
        P: AsRef<Path>,
    {
        let f = File::create(filename)?;
        serde_json::to_writer(f, &self)?;
        Ok(())
    }

    pub fn load<P>(filename: P) -> Result<Self, BotError>
    where
        P: AsRef<Path>,
    {
        let f = File::open(filename)?;
        let bot: Bot = serde_json::from_reader(f)?;
        Ok(bot)
    }

    pub fn num_users(&self) -> usize {
        self.users.len()
    }
}

#[derive(Debug)]
pub enum Action {
    Enable(spark::PersonId, spark::Email),
    Disable(spark::PersonId, spark::Email),
    UpdateApprovals(gerrit::Event),
    Help(spark::PersonId),
    Unknown(spark::PersonId),
    NoOp,
}

#[derive(Debug)]
pub struct Response {
    // TODO: Switch to a reference, since it should be alive inside of the Bot state
    pub person_id: spark::PersonId,
    pub message: String,
}

impl Response {
    pub fn new(person_id: spark::PersonId, message: String) -> Response {
        Response {
            person_id: person_id,
            message: message,
        }
    }
}

#[derive(Debug)]
pub enum Task {
    Reply(Response),
    ReplyAndSave(Response),
}

const GREETINGS_MSG: &'static str =
    r#"Hi. I am GerritBot (**in a very early alpha**). I can watch Gerrit reviews for you, and notify you about new +1/-1's.

To enable notifications, just type in 'enable'. A small note: your email in Spark and in Gerrit has to be the same. Otherwise, I can't match your accounts.

For more information, type in **help**.

By the way, my icon is made by
[ Madebyoliver ](http://www.flaticon.com/authors/madebyoliver)
from
[ www.flaticon.com ](http://www.flaticon.com)
and is licensed by
[ CC 3.0 BY](http://creativecommons.org/licenses/by/3.0/).
"#;

const HELP_MSG: &'static str = r#"Commands:

`enable` I will start notifying you.

`disable` I will stop notifying you.

`help` This message
"#;

/// Action controller
/// Return new bot state and an optional message to send to the user
pub fn update(action: Action, bot: Bot) -> (Bot, Option<Task>) {
    let mut bot = bot;
    let task = match action {
        Action::Enable(person_id, email) => {
            bot.enable(&person_id, &email, true);
            let task = Task::ReplyAndSave(Response::new(
                person_id,
                String::from("Got it! Happy reviewing!"),
            ));
            Some(task)
        }
        Action::Disable(person_id, email) => {
            bot.enable(&person_id, &email, false);
            let task = Task::ReplyAndSave(Response::new(
                person_id,
                String::from("Got it! I will stay silent."),
            ));
            Some(task)
        }
        Action::UpdateApprovals(event) => {
            bot.get_approvals_msg(event).map(|(user, message)| {
                Task::Reply(Response::new(user.spark_person_id.clone(), message))
            })
        }
        Action::Help(person_id) => {
            Some(Task::Reply(
                Response::new(person_id, String::from(HELP_MSG)),
            ))
        }
        Action::Unknown(person_id) => {
            Some(Task::Reply(
                Response::new(person_id, String::from(GREETINGS_MSG)),
            ))
        }
        _ => None,
    };
    (bot, task)
}
<commit_msg>Function for formatting approval color color.<commit_after>use std::path::Path;
use std::io;
use std::fs::File;
use std::convert;

use serde_json;

use gerrit;
use spark;

#[derive(Debug, Clone, Serialize, Deserialize)]
struct User {
    spark_person_id: spark::PersonId,
    email: spark::Email,
    enabled: bool,
}

impl User {
    fn new(person_id: spark::PersonId, email: spark::Email) -> User {
        User {
            spark_person_id: person_id,
            email: email,
            enabled: true,
        }
    }
}

/// Describes a state of the bot
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Bot {
    gerrit_username: Option<String>, // TODO: this should not be part of the state
    users: Vec<User>,
}

#[derive(Debug)]
pub enum BotError {
    Io(io::Error),
    Serialization(serde_json::Error),
}

impl convert::From<io::Error> for BotError {
    fn from(err: io::Error) -> BotError {
        BotError::Io(err)
    }
}

impl convert::From<serde_json::Error> for BotError {
    fn from(err: serde_json::Error) -> BotError {
        BotError::Serialization(err)
    }
}

fn format_approval_value(value: &str) -> String {
    let value: i8 = value.parse().unwrap_or(0);
    // TODO: when Spark will be allow to format text with different colors,
    // set green resp. red color here.
    if value > 0 {
        format!("+{}", value)
    } else {
        format!("{}", value)
    }
}

impl Bot {
    pub fn new(gerrit_username: gerrit::Username) -> Bot {
        Bot {
            gerrit_username: Some(gerrit_username),
            users: Vec::new(),
        }
    }

    pub fn default() -> Bot {
        Bot {
            gerrit_username: None,
            users: Vec::new(),
        }
    }

    fn enable<'a>(&'a mut self, person_id: &str, email: &str, enabled: bool) -> &'a User {
        // FIXME: Replace linear search
        let pos = self.users.iter().position(
            |u| u.spark_person_id == person_id,
        );
        let user: &'a mut User = match pos {
            Some(pos) => &mut self.users[pos],
            None => {
                self.users.push(User::new(
                    String::from(person_id),
                    String::from(email),
                ));
                self.users.iter_mut().last().unwrap()
            }
        };
        user.enabled = enabled;
        user
    }

    fn get_approvals_msg(&self, event: gerrit::Event) -> Option<(&User, String)> {
        println!("[D] Incoming approvals: {:?}", event);

        let author = event.author;
        let change = event.change;
        let approvals = event.approvals;

        let approver = author.unwrap().username.clone();
        if approver == change.owner.username {
            // No need to notify about user's own approvals.
            return None;
        }

        if let Some(ref owner_email) = change.owner.email {
            // TODO: Fix linear search
            let users = &self.users;
            for user in users.iter() {
                if &user.email == owner_email {
                    if !user.enabled {
                        break;
                    }

                    if let Some(approvals) = approvals {
                        let msgs: Vec<String> = approvals
                            .iter()
                            .filter(|approval| {
                                let filtered = if let Some(ref old_value) = approval.old_value {
                                    old_value != &approval.value && approval.value != "0"
                                } else {
                                    approval.value != "0"
                                };
                                println!("Filtered approval: {:?}", !filtered);
                                filtered
                            })
                            .map(|approval| {
                                format!(
                                    "{}: {} ({}) from {}",
                                    change.subject,
                                    format_approval_value(&approval.value),
                                    approval.approval_type,
                                    approver
                                )
                            })
                            .collect();
                        return if !msgs.is_empty() {
                            Some((user, msgs.join("\n")))
                        } else {
                            None
                        };
                    }
                }
            }
        }
        None
    }

    pub fn save<P>(self, filename: P) -> Result<(), BotError>
    where
        P: AsRef<Path>,
    {
        let f = File::create(filename)?;
        serde_json::to_writer(f, &self)?;
        Ok(())
    }

    pub fn load<P>(filename: P) -> Result<Self, BotError>
    where
        P: AsRef<Path>,
    {
        let f = File::open(filename)?;
        let bot: Bot = serde_json::from_reader(f)?;
        Ok(bot)
    }

    pub fn num_users(&self) -> usize {
        self.users.len()
    }
}

#[derive(Debug)]
pub enum Action {
    Enable(spark::PersonId, spark::Email),
    Disable(spark::PersonId, spark::Email),
    UpdateApprovals(gerrit::Event),
    Help(spark::PersonId),
    Unknown(spark::PersonId),
    NoOp,
}

#[derive(Debug)]
pub struct Response {
    // TODO: Switch to a reference, since it should be alive inside of the Bot state
    pub person_id: spark::PersonId,
    pub message: String,
}

impl Response {
    pub fn new(person_id: spark::PersonId, message: String) -> Response {
        Response {
            person_id: person_id,
            message: message,
        }
    }
}

#[derive(Debug)]
pub enum Task {
    Reply(Response),
    ReplyAndSave(Response),
}

const GREETINGS_MSG: &'static str =
    r#"Hi. I am GerritBot (**in a very early alpha**). I can watch Gerrit reviews for you, and notify you about new +1/-1's.

To enable notifications, just type in 'enable'. A small note: your email in Spark and in Gerrit has to be the same. Otherwise, I can't match your accounts.

For more information, type in **help**.

By the way, my icon is made by
[ Madebyoliver ](http://www.flaticon.com/authors/madebyoliver)
from
[ www.flaticon.com ](http://www.flaticon.com)
and is licensed by
[ CC 3.0 BY](http://creativecommons.org/licenses/by/3.0/).
"#;

const HELP_MSG: &'static str = r#"Commands:

`enable` I will start notifying you.

`disable` I will stop notifying you.

`help` This message
"#;

/// Action controller
/// Return new bot state and an optional message to send to the user
pub fn update(action: Action, bot: Bot) -> (Bot, Option<Task>) {
    let mut bot = bot;
    let task = match action {
        Action::Enable(person_id, email) => {
            bot.enable(&person_id, &email, true);
            let task = Task::ReplyAndSave(Response::new(
                person_id,
                String::from("Got it! Happy reviewing!"),
            ));
            Some(task)
        }
        Action::Disable(person_id, email) => {
            bot.enable(&person_id, &email, false);
            let task = Task::ReplyAndSave(Response::new(
                person_id,
                String::from("Got it! I will stay silent."),
            ));
            Some(task)
        }
        Action::UpdateApprovals(event) => {
            bot.get_approvals_msg(event).map(|(user, message)| {
                Task::Reply(Response::new(user.spark_person_id.clone(), message))
            })
        }
        Action::Help(person_id) => {
            Some(Task::Reply(
                Response::new(person_id, String::from(HELP_MSG)),
            ))
        }
        Action::Unknown(person_id) => {
            Some(Task::Reply(
                Response::new(person_id, String::from(GREETINGS_MSG)),
            ))
        }
        _ => None,
    };
    (bot, task)
}
<|endoftext|>


<commit_before>extern crate hyper;
extern crate hyper_native_tls;
extern crate serde_json;

use std::io::*;
use std::result::Result;
use std::fs::File;
use std::fs::OpenOptions;
use self::serde_json::Value;

use hyper::Client;
use hyper::net::HttpsConnector;
use hyper_native_tls::NativeTlsClient;

pub struct TTVEmoteData {
    pub template: String,
    pub data: Vec<(String, String)>,
    sub_emotes: Option<Value>,
}

pub struct BTTVEmoteData {
    pub template: String,
    pub data: Vec<(String, String)>,
}

pub struct Config {
    pub global_ttv: bool,
    pub global_bttv: bool,
    pub ttv_channels: Vec<String>,
    pub bttv_channels: Vec<String>,
}

impl TTVEmoteData {
    pub fn new() -> TTVEmoteData {
        TTVEmoteData {
            template: String::new(),
            data: Vec::new(),
            sub_emotes: None,
        }
    }

    pub fn get_global_emotes(&mut self) -> Result<(), String> {

        let obj = match download_json("https://twitchemotes.com/api_cache/v2/global.json") {
            Ok(v) => v,
            Err(e) => return Err(e),
        };

        let templates = obj.get("template").unwrap().as_object().unwrap();
        self.template = templates
            .get("small")
            .unwrap()
            .as_str()
            .unwrap()
            .to_owned();

        let emotes = obj.get("emotes").unwrap();

        for (name, value) in emotes.as_object().unwrap().iter() {
            let id = value
                .as_object()
                .unwrap()
                .get("image_id")
                .unwrap()
                .as_u64()
                .unwrap();
            self.data.push((name.to_owned(), id.to_string()));
        }

        Ok(())
    }

    pub fn update_sub_emote_data(&mut self) -> Result<(), String> {

        println!("Downloading subscriber emote data");
        let obj = match download_json("https://twitchemotes.com/api_cache/v2/subscriber.json") {
            Ok(v) => v,
            Err(e) => return Err(e),
        };

        let templates = obj.get("template").unwrap().as_object().unwrap();

        self.template = templates
            .get("small")
            .unwrap()
            .as_str()
            .unwrap()
            .to_owned();
        self.sub_emotes = obj.get("channels").cloned();

        Ok(())
    }

    pub fn get_subscriber_emotes(&mut self, channel: &str) -> Result<(), String> {

        if self.sub_emotes == None {
            if let Err(e) = self.update_sub_emote_data() {
                return Err(e);
            }
        }

        let mut count = 0;

        for (channel_name, value) in
            self.sub_emotes
                .as_ref()
                .unwrap()
                .as_object()
                .unwrap()
                .iter() {

            if channel.to_lowercase() != channel_name.to_lowercase() { continue; }

            let emotes = value
                .as_object()
                .unwrap()
                .get("emotes")
                .unwrap()
                .as_array()
                .unwrap();

            for ref emote in emotes.iter() {
                let object = emote.as_object().unwrap();

                let name = object.get("code").unwrap().as_str().unwrap();
                let id = object.get("image_id").unwrap().as_u64().unwrap();

                self.data.push((name.to_owned(), id.to_string()));
                count += 1;
            }
        }
        match count {
            0 => Err("No emotes found".to_owned()),
            _ => Ok(()),
        }
    }
}

impl BTTVEmoteData {
    pub fn new() -> BTTVEmoteData {
        BTTVEmoteData {
            template: String::new(),
            data: Vec::new(),
        }
    }

    pub fn get_global_bttv_emotes(&mut self) -> Result<(), String> {

        let obj = match download_json("https://api.betterttv.net/2/emotes") {
            Ok(v) => v,
            Err(e) => return Err(e),
        };

        self.template = obj.get("urlTemplate")
            .unwrap()
            .as_str()
            .unwrap()
            .to_owned()
            .replace("{{image}}", "1x");
        self.template.insert_str(0, "https:");

        let emotes = obj.get("emotes").unwrap().as_array().unwrap();

        for ref emote in emotes.iter() {

            let object = emote.as_object().unwrap();

            let name = object.get("code").unwrap().as_str().unwrap();
            let id = object.get("id").unwrap().as_str().unwrap();
            let img_type = object.get("imageType").unwrap().as_str().unwrap();

            match img_type {
                "png" => self.data.push((name.to_owned(), id.to_string())),
                "gif" => {},
                _     => println!("Unexpected image type: {}", img_type),
            };
        }

        Ok(())
    }

    pub fn get_channel_bttv_emotes(&mut self, channel: &str) -> Result<(), String> {

        let obj = match download_json(&format!("https://api.betterttv.net/2/channels/{}",
                                               channel)) {
            Ok(v) => v,
            Err(e) => return Err(e),
        };

        self.template = obj.get("urlTemplate")
            .unwrap()
            .as_str()
            .unwrap()
            .to_owned()
            .replace("{{image}}", "1x");
        self.template.insert_str(0, "https:");

        let emotes = obj.get("emotes").unwrap().as_array().unwrap();

        for ref emote in emotes.iter() {
            let object = emote.as_object().unwrap();

            let name = object.get("code").unwrap().as_str().unwrap();
            let id = object.get("id").unwrap().as_str().unwrap();
            let img_type = object.get("imageType").unwrap().as_str().unwrap();

            match img_type {
                "png" => self.data.push((name.to_owned(), id.to_string())),
                "gif" => {},
                _     => println!("Unexpected image type: {}", img_type),
            };
        }

        Ok(())
    }
}

impl Config {
    pub fn new() -> Config {
        Config {
            global_ttv: false,
            global_bttv: false,
            ttv_channels: Vec::new(),
            bttv_channels: Vec::new(),
        }
    }

    pub fn create_from_file(mut self, path: &str) -> Config {

        let cfgfile = match OpenOptions::new().read(true).open(path) {
            Ok(v) => v,
            Err(e) => {
                println!("{}", e);
                return self;
            }
        };
        let buf_reader = BufReader::new(cfgfile);

        for v in buf_reader.lines() {

            let line = v.unwrap();
            let s: Vec<&str> = line.split(":").collect();
            let data = *s.last().unwrap();

            match *s.first().unwrap() {
                "TTV-Global"   => self.global_ttv = data == "true",
                "TTV-Channel"  => self.ttv_channels.push(String::from(data)),
                "BTTV-Global"  => self.global_bttv = data == "true",
                "BTTV-Channel" => self.bttv_channels.push(String::from(data)),
                ""             => {},
                _              => println!("Invalid Line!({})", *s.first().unwrap()),
            }
        }
        self
    }

    pub fn write_to_file(&mut self, path: &str) {

        let mut cfgfile = match OpenOptions::new().open(path) {
            Ok(v) => v,
            Err(_) => {
                match File::create(path) {
                    Ok(v) => v,
                    Err(e) => {
                        println!("{}", e);
                        return;
                    }
                }
            }
        };

        if let Err(e) = writeln!(cfgfile, "TTV-Global:{}", self.global_ttv.to_string()) {
            println!("{}", e);
        }

        for channel in self.ttv_channels.clone() {
            if let Err(e) = writeln!(cfgfile, "TTV-Channel:{}", channel) {
                println!("{}", e);
            }
        }

        if let Err(e) = writeln!(cfgfile, "BTTV-Global:{}", self.global_ttv.to_string()) {
            println!("{}", e);
        }

        for channel in self.bttv_channels.clone() {
            if let Err(e) = writeln!(cfgfile, "BTTV-Channel:{}", channel) {
                println!("{}", e);
            }
        }
    }
}

fn download_json(url: &str) -> Result<Value, String> {

    let ssl = NativeTlsClient::new().unwrap();
    let connector = HttpsConnector::new(ssl);
    let client = Client::with_connector(connector);

    let mut res = match client.get(url).send() {
        Ok(v) => v,
        Err(e) => return Err(format!("{}", e)),
    };

    let mut s = String::new();
    if let Err(e) = res.read_to_string(&mut s) {
        return Err(format!("{}", e));
    }

    if s.is_empty() {
        return Err(format!("Response from {} was empty", url));
    }

    let data: Value = serde_json::from_str(&s).unwrap();

    match data.get("status") {
        Some(v) => {
            match v.as_u64().unwrap() {
                200 => return Ok(data.clone()),
                _ => {
                    Err(data.get("message")
                            .unwrap()
                            .as_str()
                            .unwrap()
                            .to_owned())
                }
            }
        }
        None => return Ok(data.clone()),
    }
}
<commit_msg>Remove two clones<commit_after>extern crate serde_json;

use std::io::*;
use std::result::Result;
use std::fs::File;
use std::fs::OpenOptions;
use self::serde_json::Value;

use hyper::Client;
use hyper::net::HttpsConnector;
use hyper_native_tls::NativeTlsClient;

pub struct TTVEmoteData {
    pub template: String,
    pub data: Vec<(String, String)>,
    sub_emotes: Option<Value>,
}

pub struct BTTVEmoteData {
    pub template: String,
    pub data: Vec<(String, String)>,
}

pub struct Config {
    pub global_ttv: bool,
    pub global_bttv: bool,
    pub ttv_channels: Vec<String>,
    pub bttv_channels: Vec<String>,
}

impl TTVEmoteData {
    pub fn new() -> TTVEmoteData {
        TTVEmoteData {
            template: String::new(),
            data: Vec::new(),
            sub_emotes: None,
        }
    }

    pub fn get_global_emotes(&mut self) -> Result<(), String> {

        let obj = match download_json("https://twitchemotes.com/api_cache/v2/global.json") {
            Ok(v) => v,
            Err(e) => return Err(e),
        };

        let templates = obj.get("template").unwrap().as_object().unwrap();
        self.template = templates
            .get("small")
            .unwrap()
            .as_str()
            .unwrap()
            .to_owned();

        let emotes = obj.get("emotes").unwrap();

        for (name, value) in emotes.as_object().unwrap().iter() {
            let id = value
                .as_object()
                .unwrap()
                .get("image_id")
                .unwrap()
                .as_u64()
                .unwrap();
            self.data.push((name.to_owned(), id.to_string()));
        }

        Ok(())
    }

    pub fn update_sub_emote_data(&mut self) -> Result<(), String> {

        println!("Downloading subscriber emote data");
        let obj = match download_json("https://twitchemotes.com/api_cache/v2/subscriber.json") {
            Ok(v) => v,
            Err(e) => return Err(e),
        };

        let templates = obj.get("template").unwrap().as_object().unwrap();

        self.template = templates
            .get("small")
            .unwrap()
            .as_str()
            .unwrap()
            .to_owned();
        self.sub_emotes = obj.get("channels").cloned();

        Ok(())
    }

    pub fn get_subscriber_emotes(&mut self, channel: &str) -> Result<(), String> {

        if self.sub_emotes == None {
            if let Err(e) = self.update_sub_emote_data() {
                return Err(e);
            }
        }

        let mut count = 0;

        for (channel_name, value) in
            self.sub_emotes
                .as_ref()
                .unwrap()
                .as_object()
                .unwrap()
                .iter() {

            if channel.to_lowercase() != channel_name.to_lowercase() { continue; }

            let emotes = value
                .as_object()
                .unwrap()
                .get("emotes")
                .unwrap()
                .as_array()
                .unwrap();

            for ref emote in emotes.iter() {
                let object = emote.as_object().unwrap();

                let name = object.get("code").unwrap().as_str().unwrap();
                let id = object.get("image_id").unwrap().as_u64().unwrap();

                self.data.push((name.to_owned(), id.to_string()));
                count += 1;
            }
        }
        match count {
            0 => Err("No emotes found".to_owned()),
            _ => Ok(()),
        }
    }
}

impl BTTVEmoteData {
    pub fn new() -> BTTVEmoteData {
        BTTVEmoteData {
            template: String::new(),
            data: Vec::new(),
        }
    }

    pub fn get_global_bttv_emotes(&mut self) -> Result<(), String> {

        let obj = match download_json("https://api.betterttv.net/2/emotes") {
            Ok(v) => v,
            Err(e) => return Err(e),
        };

        self.template = obj.get("urlTemplate")
            .unwrap()
            .as_str()
            .unwrap()
            .to_owned()
            .replace("{{image}}", "1x");
        self.template.insert_str(0, "https:");

        let emotes = obj.get("emotes").unwrap().as_array().unwrap();

        for ref emote in emotes.iter() {

            let object = emote.as_object().unwrap();

            let name = object.get("code").unwrap().as_str().unwrap();
            let id = object.get("id").unwrap().as_str().unwrap();
            let img_type = object.get("imageType").unwrap().as_str().unwrap();

            match img_type {
                "png" => self.data.push((name.to_owned(), id.to_string())),
                "gif" => {},
                _     => println!("Unexpected image type: {}", img_type),
            };
        }

        Ok(())
    }

    pub fn get_channel_bttv_emotes(&mut self, channel: &str) -> Result<(), String> {

        let obj = match download_json(&format!("https://api.betterttv.net/2/channels/{}",
                                               channel)) {
            Ok(v) => v,
            Err(e) => return Err(e),
        };

        self.template = obj.get("urlTemplate")
            .unwrap()
            .as_str()
            .unwrap()
            .to_owned()
            .replace("{{image}}", "1x");
        self.template.insert_str(0, "https:");

        let emotes = obj.get("emotes").unwrap().as_array().unwrap();

        for ref emote in emotes.iter() {
            let object = emote.as_object().unwrap();

            let name = object.get("code").unwrap().as_str().unwrap();
            let id = object.get("id").unwrap().as_str().unwrap();
            let img_type = object.get("imageType").unwrap().as_str().unwrap();

            match img_type {
                "png" => self.data.push((name.to_owned(), id.to_string())),
                "gif" => {},
                _     => println!("Unexpected image type: {}", img_type),
            };
        }

        Ok(())
    }
}

impl Config {
    pub fn new() -> Config {
        Config {
            global_ttv: false,
            global_bttv: false,
            ttv_channels: Vec::new(),
            bttv_channels: Vec::new(),
        }
    }

    pub fn create_from_file(mut self, path: &str) -> Config {

        let cfgfile = match OpenOptions::new().read(true).open(path) {
            Ok(v) => v,
            Err(e) => {
                println!("{}", e);
                return self;
            }
        };
        let buf_reader = BufReader::new(cfgfile);

        for v in buf_reader.lines() {

            let line = v.unwrap();
            let s: Vec<&str> = line.split(":").collect();
            let data = *s.last().unwrap();

            match *s.first().unwrap() {
                "TTV-Global"   => self.global_ttv = data == "true",
                "TTV-Channel"  => self.ttv_channels.push(String::from(data)),
                "BTTV-Global"  => self.global_bttv = data == "true",
                "BTTV-Channel" => self.bttv_channels.push(String::from(data)),
                ""             => {},
                _              => println!("Invalid Line!({})", *s.first().unwrap()),
            }
        }
        self
    }

    pub fn write_to_file(&mut self, path: &str) {

        let mut cfgfile = match OpenOptions::new().open(path) {
            Ok(v) => v,
            Err(_) => {
                match File::create(path) {
                    Ok(v) => v,
                    Err(e) => {
                        println!("{}", e);
                        return;
                    }
                }
            }
        };

        if let Err(e) = writeln!(cfgfile, "TTV-Global:{}", self.global_ttv.to_string()) {
            println!("{}", e);
        }

        for channel in &self.ttv_channels {
            if let Err(e) = writeln!(cfgfile, "TTV-Channel:{}", channel) {
                println!("{}", e);
            }
        }

        if let Err(e) = writeln!(cfgfile, "BTTV-Global:{}", self.global_ttv.to_string()) {
            println!("{}", e);
        }

        for channel in &self.bttv_channels {
            if let Err(e) = writeln!(cfgfile, "BTTV-Channel:{}", channel) {
                println!("{}", e);
            }
        }
    }
}

fn download_json(url: &str) -> Result<Value, String> {

    let ssl = NativeTlsClient::new().unwrap();
    let connector = HttpsConnector::new(ssl);
    let client = Client::with_connector(connector);

    let mut res = match client.get(url).send() {
        Ok(v) => v,
        Err(e) => return Err(format!("{}", e)),
    };

    let mut s = String::new();
    if let Err(e) = res.read_to_string(&mut s) {
        return Err(format!("{}", e));
    }

    if s.is_empty() {
        return Err(format!("Response from {} was empty", url));
    }

    let data: Value = serde_json::from_str(&s).unwrap();

    match data.get("status") {
        Some(v) => {
            match v.as_u64().unwrap() {
                200 => return Ok(data.clone()),
                _ => {
                    Err(data.get("message")
                            .unwrap()
                            .as_str()
                            .unwrap()
                            .to_owned())
                }
            }
        }
        None => return Ok(data.clone()),
    }
}
<|endoftext|>


<commit_before>extern crate url;
extern crate hyper;
extern crate xml;

use self::url::{Url, ParseError};
use self::hyper::server::response::Response;
use self::hyper::status::StatusCode;
use self::hyper::header::Location;
use self::hyper::Client;
use self::hyper::error::Error as HyperError;
use self::xml::reader::{EventReader, XmlEvent};
use self::xml::reader::Error as XmlError;

/// The username returned by `verify_ticket` on success
pub type Name = String;
/// The error returned by `verify_ticket` on failure
pub type TicketError = String;

/// The details of a CAS server.  All URLs are the full urls
#[derive(Debug)]
pub struct CasClient {
    /// Login url (such as https://login.case.edu/cas/login)
    login_url: Url,
    /// Logout url (such as https://login.case.edu/cas/logout)
    logout_url: Url,
    /// Verify url, accessed by the server
    /// (such as https://login.case.edu/cas/serviceValidate)
    verify_url: Url,
    /// The URL of your service, which is used in the login sequence and
    /// so the login server knows where to redirect you back to
    service_url: Url,
}

/// The response from the server from `verify_ticket`
#[derive(Debug)]
pub enum ServiceResponse {
    /// Returned on successful login
    Success(Name),
    /// Returned on unsuccessful login
    Failure(TicketError),
}

#[derive(Debug)]
enum XmlMatchStatus {
    None,
    ExpectSuccess,
}

/// Errors that can happen when verifying.  Xml is unlikely.
#[derive(Debug)]
pub enum VerifyError {
    Hyper(HyperError),
    Xml(XmlError),
}

impl From<HyperError> for VerifyError {
    fn from(err: HyperError) -> VerifyError {
        VerifyError::Hyper(err)
    }
}
impl From<XmlError> for VerifyError {
    fn from(err: XmlError) -> VerifyError {
        VerifyError::Xml(err)
    }
}

impl CasClient {
    /// Construct a new CasClient. The for each url except service_url, the
    /// \<thing\>_path is concatinated onto base_url to form the Url
    pub fn new(
        base_url: &str, login_path: &str, logout_path: &str, verify_path: &str,
        service_url: &str) -> Result<CasClient, ParseError> {
        Ok(CasClient {
            login_url: try!(
                Url::parse(&format!("{}{}", base_url, login_path))),
            logout_url: try!(
                Url::parse(&format!("{}{}", base_url, logout_path))),
            verify_url: try!(
                Url::parse(&format!("{}{}", base_url, verify_path))),
            service_url: try!(Url::parse(service_url)),
        })
    }

    /// Get the URL to redirect to for login.  Use this if you are not using
    /// Hyper as your web server
    pub fn get_login_url(&self) -> String {
        let mut url = self.login_url.clone();
        let param = vec![("service", self.service_url.serialize())];
        url.set_query_from_pairs(param);
        url.serialize()
    }

    /// Consumes a hyper::server::response::Response to return a 302 redirect
    /// to the CAS login url.  Use this if you're using Hyper as you web
    /// server
    pub fn login_redirect(&self, mut res: Response) {
        {
            let mut s = res.status_mut();
            *s = StatusCode::Found;
        }
        {
            let mut h = res.headers_mut();
            h.set::<Location>(Location(self.get_login_url()));
        }
        res.send(b"").unwrap();
    }

    /// Get the URL to redirect to for logout.  Use this if you are not using
    /// Hyper as your web server
    pub fn get_logout_url(&self) -> String {
        self.logout_url.serialize()
    }

    /// Consumes a hyper::server::response::Response to return a 302 redirect
    /// to the CAS logout url.  use this if you're using Hyper as your web
    /// server
    pub fn logout_redirect(&self, mut res: Response) {
        {
            let mut s = res.status_mut();
            *s = StatusCode::Found;
        }
        {
            let mut h = res.headers_mut();
            h.set::<Location>(Location(self.get_logout_url()));
        }
        res.send(b"").unwrap();
    }

    /// When login completes, the CAS server will redirec to your service_url
    /// with the added parameter ticket=\<ticket\>.  You pass \<ticket\> here,
    /// and it checks with the CAS server whether or not the login was
    /// successful.  On success, this will return
    /// `Ok(ServiceResponse::Success(username))`, where username is the username
    /// from the CAS server.  On failure it returns
    /// `Ok(ServiceResponse::Failure(reason))`, where reason is the reason for
    /// the failure.  In the event of an http error or an xml error, this
    /// returns Err(VerifyError)
    pub fn verify_ticket(&self, ticket: &str)
        -> Result<ServiceResponse, VerifyError> {
        let mut url: Url = self.verify_url.clone();
        let param = vec![
            ("service", self.service_url.serialize()),
            ("ticket", ticket.to_string()),
        ];
        url.set_query_from_pairs(param);

        let res = try!(Client::new().get(&url.serialize()).send());

        let parser = EventReader::new(res);
        let mut status = XmlMatchStatus::None;
        for e in parser {
            match try!(e) {
                XmlEvent::StartElement { name, attributes, .. } => {
                    if name.local_name == "authenticationSuccess".to_string() {
                        status = XmlMatchStatus::ExpectSuccess;
                    } else if name.local_name == "authenticationFailure"
                        .to_string() {
                            let reason = attributes[0].value.clone();
                            return Ok(ServiceResponse::Failure(reason));
                    }
                }
                XmlEvent::Characters(s) => {
                    match status {
                        XmlMatchStatus::None => {}
                        XmlMatchStatus::ExpectSuccess => {
                            return Ok(ServiceResponse::Success(s));
                        }
                    }
                }
                _ => {}
            }
        }

        let error = "did not detect authentication reply from CAS server"
            .to_string();
        Ok(ServiceResponse::Failure(error))
    }
}
<commit_msg>Fix block-scope terribleness stuff<commit_after>extern crate url;
extern crate hyper;
extern crate xml;

use self::url::{Url, ParseError};
use self::hyper::server::response::Response;
use self::hyper::status::StatusCode;
use self::hyper::header::Location;
use self::hyper::Client;
use self::hyper::error::Error as HyperError;
use self::xml::reader::{EventReader, XmlEvent};
use self::xml::reader::Error as XmlError;

/// The username returned by `verify_ticket` on success
pub type Name = String;
/// The error returned by `verify_ticket` on failure
pub type TicketError = String;

/// The details of a CAS server.  All URLs are the full urls
#[derive(Debug)]
pub struct CasClient {
    /// Login url (such as https://login.case.edu/cas/login)
    login_url: Url,
    /// Logout url (such as https://login.case.edu/cas/logout)
    logout_url: Url,
    /// Verify url, accessed by the server
    /// (such as https://login.case.edu/cas/serviceValidate)
    verify_url: Url,
    /// The URL of your service, which is used in the login sequence and
    /// so the login server knows where to redirect you back to
    service_url: Url,
}

/// The response from the server from `verify_ticket`
#[derive(Debug)]
pub enum ServiceResponse {
    /// Returned on successful login
    Success(Name),
    /// Returned on unsuccessful login
    Failure(TicketError),
}

#[derive(Debug)]
enum XmlMatchStatus {
    None,
    ExpectSuccess,
}

/// Errors that can happen when verifying.  Xml is unlikely.
#[derive(Debug)]
pub enum VerifyError {
    Hyper(HyperError),
    Xml(XmlError),
}

impl From<HyperError> for VerifyError {
    fn from(err: HyperError) -> VerifyError {
        VerifyError::Hyper(err)
    }
}
impl From<XmlError> for VerifyError {
    fn from(err: XmlError) -> VerifyError {
        VerifyError::Xml(err)
    }
}

impl CasClient {
    /// Construct a new CasClient. The for each url except service_url, the
    /// \<thing\>_path is concatinated onto base_url to form the Url
    pub fn new(
        base_url: &str, login_path: &str, logout_path: &str, verify_path: &str,
        service_url: &str) -> Result<CasClient, ParseError> {
        Ok(CasClient {
            login_url: try!(
                Url::parse(&format!("{}{}", base_url, login_path))),
            logout_url: try!(
                Url::parse(&format!("{}{}", base_url, logout_path))),
            verify_url: try!(
                Url::parse(&format!("{}{}", base_url, verify_path))),
            service_url: try!(Url::parse(service_url)),
        })
    }

    /// Get the URL to redirect to for login.  Use this if you are not using
    /// Hyper as your web server
    pub fn get_login_url(&self) -> String {
        let mut url = self.login_url.clone();
        let param = vec![("service", self.service_url.serialize())];
        url.set_query_from_pairs(param);
        url.serialize()
    }

    /// Consumes a hyper::server::response::Response to return a 302 redirect
    /// to the CAS login url.  Use this if you're using Hyper as you web
    /// server
    pub fn login_redirect(&self, mut res: Response) {
        *res.status_mut() = StatusCode::Found;
        res.headers_mut().set::<Location>(Location(self.get_login_url()));
        res.send(b"").unwrap();
    }

    /// Get the URL to redirect to for logout.  Use this if you are not using
    /// Hyper as your web server
    pub fn get_logout_url(&self) -> String {
        self.logout_url.serialize()
    }

    /// Consumes a hyper::server::response::Response to return a 302 redirect
    /// to the CAS logout url.  use this if you're using Hyper as your web
    /// server
    pub fn logout_redirect(&self, mut res: Response) {
        *res.status_mut() = StatusCode::Found;
        res.headers_mut().set::<Location>(Location(self.get_logout_url()));
        res.send(b"").unwrap();
    }

    /// When login completes, the CAS server will redirec to your service_url
    /// with the added parameter ticket=\<ticket\>.  You pass \<ticket\> here,
    /// and it checks with the CAS server whether or not the login was
    /// successful.  On success, this will return
    /// `Ok(ServiceResponse::Success(username))`, where username is the username
    /// from the CAS server.  On failure it returns
    /// `Ok(ServiceResponse::Failure(reason))`, where reason is the reason for
    /// the failure.  In the event of an http error or an xml error, this
    /// returns Err(VerifyError)
    pub fn verify_ticket(&self, ticket: &str)
        -> Result<ServiceResponse, VerifyError> {
        let mut url: Url = self.verify_url.clone();
        let param = vec![
            ("service", self.service_url.serialize()),
            ("ticket", ticket.to_string()),
        ];
        url.set_query_from_pairs(param);

        let res = try!(Client::new().get(&url.serialize()).send());

        let parser = EventReader::new(res);
        let mut status = XmlMatchStatus::None;
        for e in parser {
            match try!(e) {
                XmlEvent::StartElement { name, attributes, .. } => {
                    if name.local_name == "authenticationSuccess".to_string() {
                        status = XmlMatchStatus::ExpectSuccess;
                    } else if name.local_name == "authenticationFailure"
                        .to_string() {
                            let reason = attributes[0].value.clone();
                            return Ok(ServiceResponse::Failure(reason));
                    }
                }
                XmlEvent::Characters(s) => {
                    match status {
                        XmlMatchStatus::None => {}
                        XmlMatchStatus::ExpectSuccess => {
                            return Ok(ServiceResponse::Success(s));
                        }
                    }
                }
                _ => {}
            }
        }

        let error = "did not detect authentication reply from CAS server"
            .to_string();
        Ok(ServiceResponse::Failure(error))
    }
}
<|endoftext|>


<commit_before>extern crate rand;

use atty::Stream;
use std;

use rand::Rng;

use std::thread::sleep;
use std::time::Duration;

use std::io::Write;
use std::io::stdout;

// A struct to contain info we need to print with every character
pub struct Control {
    pub seed: f64,
    pub spread: f64,
    pub frequency: f64,
    pub background_mode: bool,
    pub dialup_mode: bool,
}

// This used to have more of a reason to exist, however now all its functionality is in
// print_chars_lol(). Anyway, it takes in an iterator over lines and prints them all.
pub fn print_lines_lol<I: Iterator<Item=S>, S: AsRef<str>>(lines: I, c: &mut Control) {
    for line in lines {
        print_chars_lol(line.as_ref().chars().chain(Some('\n')), c, false);
    }
}

// Takes in s an iterator over characters
// duplicates escape sequences, otherwise prints printable characters with colored_print
// Print newlines correctly, resetting background
// If constantly_flush is on, it won't wait till a newline to flush stdout
// TODO Adds the color to a color escape sequence
pub fn print_chars_lol<I: Iterator<Item=char>>(mut iter: I, c: &mut Control, constantly_flush: bool) {
    let original_seed = c.seed;
    let mut ignore_whitespace = c.background_mode;

    if !atty::is(Stream::Stdout) {
        for s in iter {
            print!("{}", s);
        }
        return;
    }

    while let Some(character) = iter.next() {
        match character {
        // Consume escape sequences
        '\x1b' => {
            // Escape sequences seem to be one of many different categories
            // CSI sequences are \e \[ [bytes in 0x30-0x3F] [bytes in 0x20-0x2F] [final byte in 0x40-0x7E]
            // nF Escape seq are \e [bytes in 0x20-0x2F] [byte in 0x30-0x7E]
            // Fp Escape seq are \e [byte in 0x30-0x3F] [I have no idea, but `sl` creates one where the next byte is the end of the escape sequence, so assume that]
            // Fe Escape seq are \e [byte in 0x40-0x5F] [I have no idea, '' though sl doesn't make one]
            // Fs Escape seq are \e [byte in 0x60-0x7E] [I have no idea, '' though sl doesn't make one]
            // Otherwise the next byte is the whole escape sequence (maybe? I can't exactly tell, but I will go with it)
            // We will consume up to, but not through, the next printable character
            // In addition, we print everything in the escape sequence, even if it is a color (that will be overriden)
            print!("\x1b");
            let mut escape_sequence_character = iter.next().expect("Escape character with no escape sequence after it");
            print!("{}", escape_sequence_character);
            match escape_sequence_character {
            '[' => {
                loop {
                    escape_sequence_character = iter.next().expect("CSI escape sequence did not terminate");
                    print!("{}", escape_sequence_character);
                    match escape_sequence_character {
                    '\x30' ..= '\x3F' => continue,
                    '\x20' ..= '\x2F' => {
                        loop {
                            escape_sequence_character = iter.next().expect("CSI escape sequence did not terminate");
                            print!("{}", escape_sequence_character);
                            match escape_sequence_character {
                            '\x20' ..= '\x2F' => continue,
                            '\x40' ..= '\x7E' => break,
                            _ => panic!("CSI escape sequence terminated with an incorrect value"),
                            }
                        }
                        break;
                    },
                    '\x40' ..= '\x7E' => break,
                    _ => panic!("CSI escape sequence terminated with an incorrect value"),
                    }
                }
            },
            '\x20' ..= '\x2F' => {
                loop {
                    escape_sequence_character = iter.next().expect("nF escape sequence did not terminate");
                    print!("{}", escape_sequence_character);
                    match escape_sequence_character {
                    '\x20' ..= '\x2F' => continue,
                    '\x30' ..= '\x7E' => break,
                    _ => panic!("nF escape sequence terminated with an incorrect value"),
                    }
                }
            },
//            '\x30' ..= '\x3F' => panic!("Fp escape sequences are not supported"),
//            '\x40' ..= '\x5F' => panic!("Fe escape sequences are not supported"),
//            '\x60' ..= '\x7E' => panic!("Fs escape sequences are not supported"),
            // be lazy and assume in all other cases we consume exactly 1 byte
            _ => (),
            }
            continue;
        },
        // Newlines print escape sequences to end background prints, and in dialup mode sleep, and
        // reset the seed of the coloring
        '\n' => {
            if atty::is(Stream::Stdout) {
                // Reset the background color if we should, since otherwise it will bleed all the way to the
                // end of the terminal
                if c.background_mode {
                    print!("\x1b[49m");
                }

                // Reset the foreground color, since we are at the end of the output, and print the last newline
                // This has to happen here, rather than only at the end of the program, because otherwise
                // newlines become weird
                print!("\x1b[39m");
            }
            println!();
            if c.dialup_mode {
                let stall = Duration::from_millis(rand::thread_rng().gen_range(30, 700));
                sleep(stall);
            }
            c.seed = original_seed + 1.0; // Reset the seed, but bump it a bit
        },
        // If not an escape sequence or a newline, print a colorful escape sequence and then the
        // character
        _ => {
            // In background mode, don't print colorful whitespace until the first printable character
            if ignore_whitespace && character.is_whitespace() {
                print!("{}", character);
                continue;
            } else {
                ignore_whitespace = false;
            }

            colored_print(character, c);
            c.seed += 1.0;
        },
        }

        // If we should constantly flush, flush after each completed sequence
        if constantly_flush {
            stdout().flush().unwrap();
        }
    }
}

fn calc_fg_color(bg: (u8, u8, u8)) -> (u8, u8, u8) {
    // Currently, it only computes the forground clolor based on some threshold
    // on grayscale value.
    // TODO: Add a better algorithm for computing forground color
    if conv_grayscale(bg) > 0xA0_u8 {
        (0u8, 0u8, 0u8)
    } else {
        (0xffu8, 0xffu8, 0xffu8)
    }
}

fn linear_to_srgb(intensity: f64) -> f64 {
    if intensity <= 0.003_130_8 {
        12.92 * intensity
    } else {
        1.055 * intensity.powf(1.0 / 2.4) - 0.055
    }
}

fn srgb_to_linear(intensity: f64) -> f64 {
    if intensity < 0.04045 {
        intensity / 12.92
    } else {
        ((intensity + 0.055) / 1.055).powf(2.4)
    }
}

fn conv_grayscale(color: (u8, u8, u8)) -> u8 {
    // See https://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
    const SCALE: f64 = 256.0;

    // Changing SRGB to Linear for gamma correction
    let red = srgb_to_linear(f64::from(color.0) / SCALE);
    let green = srgb_to_linear(f64::from(color.1) / SCALE);
    let blue = srgb_to_linear(f64::from(color.2) / SCALE);

    // Converting to grayscale
    let gray_linear = red * 0.299 + green * 0.587 + blue * 0.114;

    // Gamma correction
    let gray_srgb = linear_to_srgb(gray_linear);

    (gray_srgb * SCALE) as u8
}

fn colored_print(character: char, c: &mut Control) {
    if c.background_mode {
        let bg = get_color_tuple(c);
        let fg = calc_fg_color(bg);
        print!(
            "\x1b[38;2;{};{};{};48;2;{};{};{}m{}",
            fg.0, fg.1, fg.2, bg.0, bg.1, bg.2, character
        );
    } else {
        let fg = get_color_tuple(c);
        print!("\x1b[38;2;{};{};{}m{}", fg.0, fg.1, fg.2, character);
    }
}

fn get_color_tuple(c: &Control) -> (u8, u8, u8) {
    let i = c.frequency * c.seed / c.spread;
    let red = i.sin() * 127.00 + 128.00;
    let green = (i + (std::f64::consts::PI * 2.00 / 3.00)).sin() * 127.00 + 128.00;
    let blue = (i + (std::f64::consts::PI * 4.00 / 3.00)).sin() * 127.00 + 128.00;

    (red as u8, green as u8, blue as u8)
}
<commit_msg>Be more careful about when we reset colors so that lolcat won't ever mess up your prompt for example, even if stdin is weird<commit_after>extern crate rand;

use atty::Stream;
use std;

use rand::Rng;

use std::thread::sleep;
use std::time::Duration;

use std::io::Write;
use std::io::stdout;

// A struct to contain info we need to print with every character
pub struct Control {
    pub seed: f64,
    pub spread: f64,
    pub frequency: f64,
    pub background_mode: bool,
    pub dialup_mode: bool,
}

// This used to have more of a reason to exist, however now all its functionality is in
// print_chars_lol(). It takes in an iterator over lines and prints them all.
// At the end, it resets the foreground color
pub fn print_lines_lol<I: Iterator<Item=S>, S: AsRef<str>>(lines: I, c: &mut Control) {
    for line in lines {
        print_chars_lol(line.as_ref().chars().chain(Some('\n')), c, false);
    }
    print!("\x1b[39m");
}

// Takes in s an iterator over characters
// duplicates escape sequences, otherwise prints printable characters with colored_print
// Print newlines correctly, resetting background
// If constantly_flush is on, it won't wait till a newline to flush stdout
pub fn print_chars_lol<I: Iterator<Item=char>>(mut iter: I, c: &mut Control, constantly_flush: bool) {
    let original_seed = c.seed;
    let mut ignore_whitespace = c.background_mode;

    if !atty::is(Stream::Stdout) {
        for s in iter {
            print!("{}", s);
        }
        return;
    }

    while let Some(character) = iter.next() {
        match character {
        // Consume escape sequences
        '\x1b' => {
            // Escape sequences seem to be one of many different categories
            // CSI sequences are \e \[ [bytes in 0x30-0x3F] [bytes in 0x20-0x2F] [final byte in 0x40-0x7E]
            // nF Escape seq are \e [bytes in 0x20-0x2F] [byte in 0x30-0x7E]
            // Fp Escape seq are \e [byte in 0x30-0x3F] [I have no idea, but `sl` creates one where the next byte is the end of the escape sequence, so assume that]
            // Fe Escape seq are \e [byte in 0x40-0x5F] [I have no idea, '' though sl doesn't make one]
            // Fs Escape seq are \e [byte in 0x60-0x7E] [I have no idea, '' though sl doesn't make one]
            // Otherwise the next byte is the whole escape sequence (maybe? I can't exactly tell, but I will go with it)
            // We will consume up to, but not through, the next printable character
            // In addition, we print everything in the escape sequence, even if it is a color (that will be overriden)
            print!("\x1b");
            let mut escape_sequence_character = iter.next().expect("Escape character with no escape sequence after it");
            print!("{}", escape_sequence_character);
            match escape_sequence_character {
            '[' => {
                loop {
                    escape_sequence_character = iter.next().expect("CSI escape sequence did not terminate");
                    print!("{}", escape_sequence_character);
                    match escape_sequence_character {
                    '\x30' ..= '\x3F' => continue,
                    '\x20' ..= '\x2F' => {
                        loop {
                            escape_sequence_character = iter.next().expect("CSI escape sequence did not terminate");
                            print!("{}", escape_sequence_character);
                            match escape_sequence_character {
                            '\x20' ..= '\x2F' => continue,
                            '\x40' ..= '\x7E' => break,
                            _ => panic!("CSI escape sequence terminated with an incorrect value"),
                            }
                        }
                        break;
                    },
                    '\x40' ..= '\x7E' => break,
                    _ => panic!("CSI escape sequence terminated with an incorrect value"),
                    }
                }
            },
            '\x20' ..= '\x2F' => {
                loop {
                    escape_sequence_character = iter.next().expect("nF escape sequence did not terminate");
                    print!("{}", escape_sequence_character);
                    match escape_sequence_character {
                    '\x20' ..= '\x2F' => continue,
                    '\x30' ..= '\x7E' => break,
                    _ => panic!("nF escape sequence terminated with an incorrect value"),
                    }
                }
            },
//            '\x30' ..= '\x3F' => panic!("Fp escape sequences are not supported"),
//            '\x40' ..= '\x5F' => panic!("Fe escape sequences are not supported"),
//            '\x60' ..= '\x7E' => panic!("Fs escape sequences are not supported"),
            // be lazy and assume in all other cases we consume exactly 1 byte
            _ => (),
            }
        },
        // Newlines print escape sequences to end background prints, and in dialup mode sleep, and
        // reset the seed of the coloring and the value of ignore_whitespace
        '\n' => {
            if atty::is(Stream::Stdout) {
                // Reset the background color only, as we don't have to reset the foreground till
                // the end of the program
                // We reset the background here because otherwise it bleeds all the way to the next line
                if c.background_mode {
                    print!("\x1b[49m");
                }
            }
            println!();
            if c.dialup_mode {
                let stall = Duration::from_millis(rand::thread_rng().gen_range(30, 700));
                sleep(stall);
            }

            c.seed = original_seed + 1.0; // Reset the seed, but bump it a bit
            ignore_whitespace = c.background_mode;
        },
        // If not an escape sequence or a newline, print a colorful escape sequence and then the
        // character
        _ => {
            // In background mode, don't print colorful whitespace until the first printable character
            if ignore_whitespace && character.is_whitespace() {
                print!("{}", character);
                continue;
            } else {
                ignore_whitespace = false;
            }

            colored_print(character, c);
            c.seed += 1.0;
        },
        }

        // If we should constantly flush, flush after each completed sequence, and also reset
        // colors because otherwise weird things happen
        if constantly_flush {
            reset_colors(c);
            stdout().flush().unwrap();
        }
    }
}

fn reset_colors(c: &Control) {
    if atty::is(Stream::Stdout) {
        // Reset the background color
        if c.background_mode {
            print!("\x1b[49m");
        }

        // Reset the foreground color
        print!("\x1b[39m");
    }
}

fn colored_print(character: char, c: &mut Control) {
    if c.background_mode {
        let bg = get_color_tuple(c);
        let fg = calc_fg_color(bg);
        print!(
            "\x1b[38;2;{};{};{};48;2;{};{};{}m{}",
            fg.0, fg.1, fg.2, bg.0, bg.1, bg.2, character
        );
    } else {
        let fg = get_color_tuple(c);
        print!("\x1b[38;2;{};{};{}m{}", fg.0, fg.1, fg.2, character);
    }
}

fn calc_fg_color(bg: (u8, u8, u8)) -> (u8, u8, u8) {
    // Currently, it only computes the forground clolor based on some threshold
    // on grayscale value.
    // TODO: Add a better algorithm for computing forground color
    if conv_grayscale(bg) > 0xA0_u8 {
        (0u8, 0u8, 0u8)
    } else {
        (0xffu8, 0xffu8, 0xffu8)
    }
}

fn linear_to_srgb(intensity: f64) -> f64 {
    if intensity <= 0.003_130_8 {
        12.92 * intensity
    } else {
        1.055 * intensity.powf(1.0 / 2.4) - 0.055
    }
}

fn srgb_to_linear(intensity: f64) -> f64 {
    if intensity < 0.04045 {
        intensity / 12.92
    } else {
        ((intensity + 0.055) / 1.055).powf(2.4)
    }
}

fn conv_grayscale(color: (u8, u8, u8)) -> u8 {
    // See https://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
    const SCALE: f64 = 256.0;

    // Changing SRGB to Linear for gamma correction
    let red = srgb_to_linear(f64::from(color.0) / SCALE);
    let green = srgb_to_linear(f64::from(color.1) / SCALE);
    let blue = srgb_to_linear(f64::from(color.2) / SCALE);

    // Converting to grayscale
    let gray_linear = red * 0.299 + green * 0.587 + blue * 0.114;

    // Gamma correction
    let gray_srgb = linear_to_srgb(gray_linear);

    (gray_srgb * SCALE) as u8
}

fn get_color_tuple(c: &Control) -> (u8, u8, u8) {
    let i = c.frequency * c.seed / c.spread;
    let red = i.sin() * 127.00 + 128.00;
    let green = (i + (std::f64::consts::PI * 2.00 / 3.00)).sin() * 127.00 + 128.00;
    let blue = (i + (std::f64::consts::PI * 4.00 / 3.00)).sin() * 127.00 + 128.00;

    (red as u8, green as u8, blue as u8)
}
<|endoftext|>