<commit_before>#![feature(globs)]

extern crate graphics;
extern crate piston;
extern crate rand;
extern crate native;
extern crate sdl2_game_window;
extern crate opengl_graphics;
extern crate gfx_graphics;
extern crate gfx;

use opengl_graphics::Gl;
use gfx_graphics::{
    Gfx2d,
};
use gfx::{Device, DeviceHelper};
use gfx_graphics::RenderContext;
use sdl2_game_window::WindowSDL2;
use graphics::*;
use piston::{
    EventIterator,
    EventSettings,
    WindowSettings,
    Render,
    Update,
    Input,
};
use piston::input;

mod snakeapp;
mod object;
mod settings;
mod text;
mod bar;
mod player;
mod snake;
mod action;
mod game_state;
mod character;
mod air_bottle;

pub enum GraphicsBackEnd {
    Gfx,
    OpenGL,
}

fn main() {
    use snakeapp::SnakeApp;

    let mut backend = Gfx;
    let opengl = piston::shader_version::opengl::OpenGL_3_2;
    let mut window = WindowSDL2::new(
        opengl,
        WindowSettings {
            title: "Sea Snake Escape".to_string(),
            size: [512, 512],
            fullscreen: false,
            exit_on_esc: true,
            samples: 4,
        }
    );

    let (mut device, frame) = window.gfx();
    let mut renderer = device.create_renderer();

    let mut app = SnakeApp::new();
    app.load();

    let mut event_iterator = EventIterator::new(&mut window,
        &EventSettings {
            updates_per_second: 120,
            max_frames_per_second: 60
        });
    let ref mut gl = Gl::new(opengl);
    let mut gfx2d = Gfx2d::new(&mut device);
    let mut fps_counter = piston::FPSCounter::new();
    for e in event_iterator {
        match e {
            Render(args) => {
                match backend {
                    Gfx => {
                        {
                            let ref mut gl = RenderContext::new(&mut renderer, &frame, &mut gfx2d);
                            let c = graphics::Context::abs(
                                args.width as f64,
                                args.height as f64
                            );
                            c.color(settings::WATER_COLOR).draw(gl);
                            app.render(&c, gl);
                        }
                        device.submit(renderer.as_buffer());
                        renderer.reset();
                    }
                    OpenGL => {
                        gl.viewport(0, 0, args.width as i32, args.height as i32);
                        let c = graphics::Context::abs(
                            args.width as f64,
                            args.height as f64
                        );
                        c.color(settings::WATER_COLOR).draw(gl);
                        app.render(&c, gl);
                    }
                };

                event_iterator.window.window.set_title(fps_counter.tick().to_string().as_slice());
            },
            Update(args) => {
                app.update(args.dt);
            },
            Input(input::Press(input::Keyboard(input::keyboard::S))) => {
                backend = match backend {
                        Gfx => { println!("Swapped to OpenGL"); OpenGL }
                        OpenGL => { println!("Swapped to Gfx"); Gfx }
                    };
            }
            Input(input::Press(input::Keyboard(key))) => {
                app.key_press(key);
            },
            Input(input::Release(input::Keyboard(key))) => {
                app.key_release(key);
            },
            _ => {},
        }
    }
}

<commit_msg>Added description of how to swap<commit_after>#![feature(globs)]

extern crate graphics;
extern crate piston;
extern crate rand;
extern crate native;
extern crate sdl2_game_window;
extern crate opengl_graphics;
extern crate gfx_graphics;
extern crate gfx;

use opengl_graphics::Gl;
use gfx_graphics::{
    Gfx2d,
};
use gfx::{Device, DeviceHelper};
use gfx_graphics::RenderContext;
use sdl2_game_window::WindowSDL2;
use graphics::*;
use piston::{
    EventIterator,
    EventSettings,
    WindowSettings,
    Render,
    Update,
    Input,
};
use piston::input;

mod snakeapp;
mod object;
mod settings;
mod text;
mod bar;
mod player;
mod snake;
mod action;
mod game_state;
mod character;
mod air_bottle;

#[deriving(Show)]
pub enum GraphicsBackEnd {
    Gfx,
    OpenGL,
}

fn main() {
    use snakeapp::SnakeApp;

    let mut backend = Gfx;
    println!("Running with graphics backend {}", backend);
    println!("Use 'S' to swap back-end");

    let opengl = piston::shader_version::opengl::OpenGL_3_2;
    let mut window = WindowSDL2::new(
        opengl,
        WindowSettings {
            title: "Sea Snake Escape".to_string(),
            size: [512, 512],
            fullscreen: false,
            exit_on_esc: true,
            samples: 4,
        }
    );

    let (mut device, frame) = window.gfx();
    let mut renderer = device.create_renderer();

    let mut app = SnakeApp::new();
    app.load();

    let mut event_iterator = EventIterator::new(&mut window,
        &EventSettings {
            updates_per_second: 120,
            max_frames_per_second: 60
        });
    let ref mut gl = Gl::new(opengl);
    let mut gfx2d = Gfx2d::new(&mut device);
    let mut fps_counter = piston::FPSCounter::new();
    for e in event_iterator {
        match e {
            Render(args) => {
                match backend {
                    Gfx => {
                        {
                            let ref mut gl = RenderContext::new(&mut renderer, &frame, &mut gfx2d);
                            let c = graphics::Context::abs(
                                args.width as f64,
                                args.height as f64
                            );
                            c.color(settings::WATER_COLOR).draw(gl);
                            app.render(&c, gl);
                        }
                        device.submit(renderer.as_buffer());
                        renderer.reset();
                    }
                    OpenGL => {
                        gl.viewport(0, 0, args.width as i32, args.height as i32);
                        let c = graphics::Context::abs(
                            args.width as f64,
                            args.height as f64
                        );
                        c.color(settings::WATER_COLOR).draw(gl);
                        app.render(&c, gl);
                    }
                };

                event_iterator.window.window.set_title(fps_counter.tick().to_string().as_slice());
            },
            Update(args) => {
                app.update(args.dt);
            },
            Input(input::Press(input::Keyboard(input::keyboard::S))) => {
                backend = match backend {
                        Gfx => { println!("Swapped to OpenGL"); OpenGL }
                        OpenGL => { println!("Swapped to Gfx"); Gfx }
                    };
            }
            Input(input::Press(input::Keyboard(key))) => {
                app.key_press(key);
            },
            Input(input::Release(input::Keyboard(key))) => {
                app.key_release(key);
            },
            _ => {},
        }
    }
}

<|endoftext|>


<commit_before>extern crate image;

use image::{
    ImageBuffer,
    Pixel,
};
use image::imageops::flip_vertical;
use std::ops::{
    Deref,
    DerefMut,
};

trait Painting<P> where P: Pixel {
    fn line(&mut self, x0: u32, y0: u32, x1: u32, y1: u32, color: P) -> &mut Self;
}

impl<P, Container> Painting<P> for ImageBuffer<P, Container>
where P: Pixel + 'static,
      Container: Deref<Target=[P::Subpixel]> + DerefMut,
      P::Subpixel: 'static {

    fn line(&mut self, x0: u32, y0: u32, x1: u32, y1: u32, color: P) -> &mut Self {
        if x0 == x1 && y0 == y1 {
            self.put_pixel(x0, y0, color);
        } else {
            let transpose = (x0 as i32 - x1 as i32).abs() < (y0 as i32 - y1 as i32).abs();
            let reverse = (!transpose && x0 > x1) || (transpose && y0 > y1);
            let a0 = if transpose {if reverse {y1} else {y0}} else {if reverse {x1} else {x0}};
            let a1 = if transpose {if reverse {y0} else {y1}} else {if reverse {x0} else {x1}};
            let b0 = if transpose {if reverse {x1} else {x0}} else {if reverse {y1} else {y0}};
            let b1 = if transpose {if reverse {x0} else {x1}} else {if reverse {y0} else {y1}};

            for a in a0..a1+1 {
                let t = (a - a0) as f64 / (a1 - a0) as f64;
                let b = (b0 as f64 * (1.0 - t) + b1 as f64 * t) as u32;

                if transpose {
                    self.put_pixel(b, a, color);
                } else {
                    self.put_pixel(a, b, color);
                }
            }
        }

        self
    }
}

fn main() {
    // load/create image
    let mut img = ImageBuffer::new(100, 100);

    // image processing
    let red = image::Rgb([0xff, 0x00, 0x00]);
    let white = image::Rgb([0xff, 0xff, 0xff]);

    for _ in 0..1000000 {
        img.line(13, 20, 80, 40, white);
        img.line(20, 13, 40, 80, red);
        img.line(80, 40, 13, 20, red);
    }

    img = flip_vertical(&img);

    // save image
    let _ = img.save("output.png");
}
<commit_msg>save image to ppm for profiling<commit_after>extern crate image;

use image::{
    ImageBuffer,
    Pixel,
};
use image::imageops::flip_vertical;
use std::ops::{
    Deref,
    DerefMut,
};

trait Painting<P> where P: Pixel {
    fn line(&mut self, x0: u32, y0: u32, x1: u32, y1: u32, color: P) -> &mut Self;
}

impl<P, Container> Painting<P> for ImageBuffer<P, Container>
where P: Pixel + 'static,
      Container: Deref<Target=[P::Subpixel]> + DerefMut,
      P::Subpixel: 'static {

    fn line(&mut self, x0: u32, y0: u32, x1: u32, y1: u32, color: P) -> &mut Self {
        if x0 == x1 && y0 == y1 {
            self.put_pixel(x0, y0, color);
        } else {
            let transpose = (x0 as i32 - x1 as i32).abs() < (y0 as i32 - y1 as i32).abs();
            let reverse = (!transpose && x0 > x1) || (transpose && y0 > y1);
            let a0 = if transpose {if reverse {y1} else {y0}} else {if reverse {x1} else {x0}};
            let a1 = if transpose {if reverse {y0} else {y1}} else {if reverse {x0} else {x1}};
            let b0 = if transpose {if reverse {x1} else {x0}} else {if reverse {y1} else {y0}};
            let b1 = if transpose {if reverse {x0} else {x1}} else {if reverse {y0} else {y1}};

            for a in a0..a1+1 {
                let t = (a - a0) as f64 / (a1 - a0) as f64;
                let b = (b0 as f64 * (1.0 - t) + b1 as f64 * t) as u32;

                if transpose {
                    self.put_pixel(b, a, color);
                } else {
                    self.put_pixel(a, b, color);
                }
            }
        }

        self
    }
}

fn main() {
    // load/create image
    let mut img = ImageBuffer::new(100, 100);

    // image processing
    let red = image::Rgb([0xff, 0x00, 0x00]);
    let white = image::Rgb([0xff, 0xff, 0xff]);

    for _ in 0..1000000 {
        img.line(13, 20, 80, 40, white);
        img.line(20, 13, 40, 80, red);
        img.line(80, 40, 13, 20, red);
    }

    img = flip_vertical(&img);

    // save image
    let _ = img.save("output.ppm");
}
<|endoftext|>


<commit_before>pub extern crate rusty_sword;
use rusty_sword::*;

fn main() {
    let mut rng = rand::thread_rng();

    // To avoid lock contention for this group of objects, we'll follow the rule:
    // - You must have a lock on floor before trying to lock anything else
    // - You must unlock all other locks before (or when) floor gets unlocked
    let floor        = Arc::new(Mutex::new(Floor::new(60, 30)));
    let player       = Arc::new(Mutex::new(Player::new(Coord::new(30, 15))));
    let dirty_coords = Arc::new(Mutex::new(Vec::<Coord>::new()));
    let monsters     = Arc::new(Mutex::new(Vec::<Monster>::new()));

    // To avoid lock contention, we'll follow the rule:
    // - stop should be locked only when no other objects are locked
    let stop = Arc::new(Mutex::new(false));

    // Render Thread
    let render_thread = {
        let stop = stop.clone();
        let floor = floor.clone();
        let player = player.clone();
        let dirty_coords = dirty_coords.clone();
        let monsters = monsters.clone();
        spawn(move || { render_loop(stop, floor, player, dirty_coords, monsters) })
    };

    // Sound Thread
    let (sound_tx, sound_rx) = mpsc::channel::<&str>();
    let sound_thread = {
        let stop = stop.clone();
        spawn(move || { sound_loop(stop, sound_rx) } )
    };

    //-------------------------------------------------------------------------
    // Game Loop
    let mut quit = false;
    let mut astdin = async_stdin();
    let mut spawn_timer = Timer::from_millis(1000);
    let mut last_instant = Instant::now();
    loop {
        sleep(Duration::from_millis(10));
        if quit {
            *stop.lock().unwrap() = true;
        }
        {
            if *stop.lock().unwrap() {
                break;
            }
        }
        // Once we can lock floor, we can lock anything else we want in this thread.
        let floor = floor.lock().unwrap();
        let mut dirty_coords = dirty_coords.lock().unwrap();
        let mut player = player.lock().unwrap();
        let mut monsters = monsters.lock().unwrap();

        let current_instant = Instant::now();
        let delta = current_instant - last_instant;

        // Player moves?
        let mut player_moved = false;
        let mut bytebuf : [u8; 1] = [0];
        while let Ok(amount) = astdin.read(&mut bytebuf) {
            if amount == 1 { // input was available, written to bytebuf
                match bytebuf[0] {
                    27|b'q' => {
                        quit = true;
                    },
                    _ => {
                        if let Some(direction) = byte_to_direction(bytebuf[0]) {
                            player_moved = player.travel(direction, &floor, &mut dirty_coords);
                        }
                    },
                }
            } else { // no more input
                break;
            }
        }

        // Update monster timers
        for monster in monsters.iter_mut() {
            monster.move_timer.update(delta);
        }

        // Monsters move?
        if !player_moved {
            for monster in monsters.iter_mut() {
                monster.try_travel(player.coord, &mut dirty_coords);
            }
        }

        // Did a monster die?
        let num_monsters = monsters.len();
        monsters.retain(|monster| monster.coord != player.sword_coord);
        let num_killed = num_monsters - monsters.len();
        if num_killed > 0 {
            for _ in 0..num_killed {
                sound_tx.send("monster_dies").unwrap();
            }
            player.score += num_killed as u64;
        }

        // Spawn a new monster!
        spawn_timer.update(delta);
        if spawn_timer.ready {
            spawn_timer = Timer::from_millis(sample(&mut rng, 1000..5000, 1)[0]);
            let to_coord = Coord::new(
                sample(&mut rng, 1..59, 1)[0],
                sample(&mut rng, 1..29, 1)[0],
            );
            if to_coord != player.coord {
                let monster = Monster::new(to_coord, &mut rng);
                sound_tx.send("monster_spawns").unwrap();
                monsters.push(monster);
            }
        }

        // Did the player die?
        if monsters.iter().any(|monster| monster.coord == player.coord) {
            sound_tx.send("player_dies").unwrap();
            quit = true;
        }

        // Take care of loop variables
        last_instant = current_instant;
    }
    // End game loop

    // Wait for other threads to stop before exiting
    render_thread.join().unwrap();
    println!("Thanks for playing!");
    sound_thread.join().unwrap();
}
<commit_msg>Give the render thread a chance to render the final frame before telling it to quit.<commit_after>pub extern crate rusty_sword;
use rusty_sword::*;

fn main() {
    let mut rng = rand::thread_rng();

    // To avoid lock contention for this group of objects, we'll follow the rule:
    // - You must have a lock on floor before trying to lock anything else
    // - You must unlock all other locks before (or when) floor gets unlocked
    let floor        = Arc::new(Mutex::new(Floor::new(60, 30)));
    let player       = Arc::new(Mutex::new(Player::new(Coord::new(30, 15))));
    let dirty_coords = Arc::new(Mutex::new(Vec::<Coord>::new()));
    let monsters     = Arc::new(Mutex::new(Vec::<Monster>::new()));

    // To avoid lock contention, we'll follow the rule:
    // - stop should be locked only when no other objects are locked
    let stop = Arc::new(Mutex::new(false));

    // Render Thread
    let render_thread = {
        let stop = stop.clone();
        let floor = floor.clone();
        let player = player.clone();
        let dirty_coords = dirty_coords.clone();
        let monsters = monsters.clone();
        spawn(move || { render_loop(stop, floor, player, dirty_coords, monsters) })
    };

    // Sound Thread
    let (sound_tx, sound_rx) = mpsc::channel::<&str>();
    let sound_thread = {
        let stop = stop.clone();
        spawn(move || { sound_loop(stop, sound_rx) } )
    };

    //-------------------------------------------------------------------------
    // Game Loop
    let mut quit = false;
    let mut astdin = async_stdin();
    let mut spawn_timer = Timer::from_millis(1000);
    let mut last_instant = Instant::now();
    loop {
        sleep(Duration::from_millis(10));
        if quit {
            sleep(Duration::from_millis(50));
            *stop.lock().unwrap() = true;
        }
        {
            if *stop.lock().unwrap() {
                break;
            }
        }
        // Once we can lock floor, we can lock anything else we want in this thread.
        let floor = floor.lock().unwrap();
        let mut dirty_coords = dirty_coords.lock().unwrap();
        let mut player = player.lock().unwrap();
        let mut monsters = monsters.lock().unwrap();

        let current_instant = Instant::now();
        let delta = current_instant - last_instant;

        // Player moves?
        let mut player_moved = false;
        let mut bytebuf : [u8; 1] = [0];
        while let Ok(amount) = astdin.read(&mut bytebuf) {
            if amount == 1 { // input was available, written to bytebuf
                match bytebuf[0] {
                    27|b'q' => {
                        quit = true;
                    },
                    _ => {
                        if let Some(direction) = byte_to_direction(bytebuf[0]) {
                            player_moved = player.travel(direction, &floor, &mut dirty_coords);
                        }
                    },
                }
            } else { // no more input
                break;
            }
        }

        // Update monster timers
        for monster in monsters.iter_mut() {
            monster.move_timer.update(delta);
        }

        // Monsters move?
        if !player_moved {
            for monster in monsters.iter_mut() {
                monster.try_travel(player.coord, &mut dirty_coords);
            }
        }

        // Did a monster die?
        let num_monsters = monsters.len();
        monsters.retain(|monster| monster.coord != player.sword_coord);
        let num_killed = num_monsters - monsters.len();
        if num_killed > 0 {
            for _ in 0..num_killed {
                sound_tx.send("monster_dies").unwrap();
            }
            player.score += num_killed as u64;
        }

        // Spawn a new monster!
        spawn_timer.update(delta);
        if spawn_timer.ready {
            spawn_timer = Timer::from_millis(sample(&mut rng, 1000..5000, 1)[0]);
            let to_coord = Coord::new(
                sample(&mut rng, 1..59, 1)[0],
                sample(&mut rng, 1..29, 1)[0],
            );
            if to_coord != player.coord {
                let monster = Monster::new(to_coord, &mut rng);
                sound_tx.send("monster_spawns").unwrap();
                monsters.push(monster);
            }
        }

        // Did the player die?
        if monsters.iter().any(|monster| monster.coord == player.coord) {
            sound_tx.send("player_dies").unwrap();
            quit = true;
        }

        // Take care of loop variables
        last_instant = current_instant;
    }
    // End game loop

    // Wait for other threads to stop before exiting
    render_thread.join().unwrap();
    println!("Thanks for playing!");
    sound_thread.join().unwrap();
}
<|endoftext|>


<commit_before>use std::io::Write;
use std::sync::{mpsc, Arc};
use std::{env, process, thread};

use otp_cop::service::{CreateServiceResult, ServiceFactory};

struct ParallelIter<T> {
    count: usize,
    pos: usize,
    rx: mpsc::Receiver<T>,
}

impl<T> Iterator for ParallelIter<T> {
    type Item = T;

    fn next(&mut self) -> Option<T> {
        if self.pos < self.count {
            self.count += 1;
            self.rx.recv().ok()
        } else {
            None
        }
    }
}

fn parallel<T, U, F1>(objs: Vec<T>, f1: F1) -> ParallelIter<U>
where
    F1: 'static + Fn(T) -> U + Send + Sync,
    T: 'static + Send,
    U: 'static + Send,
{
    let (tx, rx) = mpsc::channel();
    let count = objs.len();
    let shared_f1 = Arc::new(f1);
    for o in objs {
        let f1 = shared_f1.clone();
        let tx = tx.clone();
        thread::spawn(move || {
            tx.send(f1(o)).unwrap();
        });
    }

    ParallelIter { count, pos: 0, rx }
}

fn main() {
    let service_factories = vec![
        Box::new(otp_cop::SlackServiceFactory) as Box<dyn ServiceFactory>,
        Box::new(otp_cop::GithubServiceFactory) as Box<dyn ServiceFactory>,
    ];

    let mut opts = getopts::Options::new();

    for factory in service_factories.iter() {
        factory.add_options(&mut opts);
    }

    let matches = match opts.parse(env::args().skip(1)) {
        Ok(matches) => matches,
        Err(e) => {
            eprintln!("{}", e.to_string());
            process::exit(1);
        }
    };

    let mut services = vec![];

    for factory in service_factories.iter() {
        match factory.create_service(&matches) {
            CreateServiceResult::Service(s) => services.push(s),
            CreateServiceResult::MissingArguments(args) => {
                eprintln!("Missing arguments: {:?}", args);
                process::exit(1);
            }
            CreateServiceResult::None => continue,
        }
    }

    if services.is_empty() {
        eprintln!("{}", opts.usage("otp-cop: <args>"));
        process::exit(1);
    }

    let count = services.len();
    let mut error_exit = false;
    for (i, result) in parallel(services, |service| service.get_users()).enumerate() {
        match result {
            Ok(result) => {
                let header = format!("{} ({})", result.service_name, result.users.len());
                println!("{}", header);
                println!("{}", repeat_char("=", header.len()));
                println!();
                if !result.users.is_empty() {
                    error_exit = true;
                }
                for user in result.users {
                    let email = match user.email {
                        Some(email) => format!(" ({})", email),
                        None => "".to_string(),
                    };
                    let details = match user.details {
                        Some(details) => format!(" -- {}", details),
                        None => "".to_string(),
                    };
                    println!("@{}{}{}", user.name, email, details);
                }
                if i + 1 != count {
                    println!();
                    println!();
                }
            }
            Err(e) => {
                error_exit = true;
                let mut t = term::stderr().unwrap();
                writeln!(t, "{}", e.service_name).unwrap();
                writeln!(t, "{}", repeat_char("=", e.service_name.len())).unwrap();
                writeln!(t).unwrap();
                t.fg(term::color::RED).unwrap();
                writeln!(t, "{}", e.error_message).unwrap();
                t.reset().unwrap();
            }
        }
    }

    if error_exit {
        process::exit(2);
    }
}

fn repeat_char(c: &'static str, length: usize) -> String {
    c.chars().cycle().take(length).collect::<String>()
}
<commit_msg>heed clippy warning (#1002)<commit_after>use std::io::Write;
use std::sync::{mpsc, Arc};
use std::{env, process, thread};

use otp_cop::service::{CreateServiceResult, ServiceFactory};

struct ParallelIter<T> {
    count: usize,
    pos: usize,
    rx: mpsc::Receiver<T>,
}

impl<T> Iterator for ParallelIter<T> {
    type Item = T;

    fn next(&mut self) -> Option<T> {
        if self.pos < self.count {
            self.count += 1;
            self.rx.recv().ok()
        } else {
            None
        }
    }
}

fn parallel<T, U, F1>(objs: Vec<T>, f1: F1) -> ParallelIter<U>
where
    F1: 'static + Fn(T) -> U + Send + Sync,
    T: 'static + Send,
    U: 'static + Send,
{
    let (tx, rx) = mpsc::channel();
    let count = objs.len();
    let shared_f1 = Arc::new(f1);
    for o in objs {
        let f1 = shared_f1.clone();
        let tx = tx.clone();
        thread::spawn(move || {
            tx.send(f1(o)).unwrap();
        });
    }

    ParallelIter { count, pos: 0, rx }
}

fn main() {
    let service_factories = vec![
        Box::new(otp_cop::SlackServiceFactory) as Box<dyn ServiceFactory>,
        Box::new(otp_cop::GithubServiceFactory) as Box<dyn ServiceFactory>,
    ];

    let mut opts = getopts::Options::new();

    for factory in service_factories.iter() {
        factory.add_options(&mut opts);
    }

    let matches = match opts.parse(env::args().skip(1)) {
        Ok(matches) => matches,
        Err(e) => {
            eprintln!("{}", e);
            process::exit(1);
        }
    };

    let mut services = vec![];

    for factory in service_factories.iter() {
        match factory.create_service(&matches) {
            CreateServiceResult::Service(s) => services.push(s),
            CreateServiceResult::MissingArguments(args) => {
                eprintln!("Missing arguments: {:?}", args);
                process::exit(1);
            }
            CreateServiceResult::None => continue,
        }
    }

    if services.is_empty() {
        eprintln!("{}", opts.usage("otp-cop: <args>"));
        process::exit(1);
    }

    let count = services.len();
    let mut error_exit = false;
    for (i, result) in parallel(services, |service| service.get_users()).enumerate() {
        match result {
            Ok(result) => {
                let header = format!("{} ({})", result.service_name, result.users.len());
                println!("{}", header);
                println!("{}", repeat_char("=", header.len()));
                println!();
                if !result.users.is_empty() {
                    error_exit = true;
                }
                for user in result.users {
                    let email = match user.email {
                        Some(email) => format!(" ({})", email),
                        None => "".to_string(),
                    };
                    let details = match user.details {
                        Some(details) => format!(" -- {}", details),
                        None => "".to_string(),
                    };
                    println!("@{}{}{}", user.name, email, details);
                }
                if i + 1 != count {
                    println!();
                    println!();
                }
            }
            Err(e) => {
                error_exit = true;
                let mut t = term::stderr().unwrap();
                writeln!(t, "{}", e.service_name).unwrap();
                writeln!(t, "{}", repeat_char("=", e.service_name.len())).unwrap();
                writeln!(t).unwrap();
                t.fg(term::color::RED).unwrap();
                writeln!(t, "{}", e.error_message).unwrap();
                t.reset().unwrap();
            }
        }
    }

    if error_exit {
        process::exit(2);
    }
}

fn repeat_char(c: &'static str, length: usize) -> String {
    c.chars().cycle().take(length).collect::<String>()
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate lazy_static;

use diffus::{
    edit::{self, string},
    Diffable,
};
use isatty::{stdin_isatty, stdout_isatty};
use regex::Regex;
use std::io::{self, BufRead, BufReader, BufWriter, Write};
use std::process::exit;
use std::process::{Command, Stdio};
use std::str;

const ADD: &str = "\x1b[32m"; // Green
const REMOVE: &str = "\x1b[31m"; // Red
const HUNK_HEADER: &str = "\x1b[36m"; // Cyan

const NO_EOF_NEWLINE_COLOR: &str = "\x1b[2m"; // Faint
const NO_EOF_NEWLINE_MARKER: &str = "\\ No newline at end of file";

const INVERSE_VIDEO: &str = "\x1b[7m";
const NOT_INVERSE_VIDEO: &str = "\x1b[27m";

const BOLD: &str = "\x1b[1m";

const NORMAL: &str = "\x1b[0m";

lazy_static! {
    static ref STATIC_HEADERS: Vec<(Regex, &'static str)> = vec![
        (Regex::new("^diff ").unwrap(), BOLD),
        (Regex::new("^index ").unwrap(), BOLD),
        (Regex::new("^--- ").unwrap(), BOLD),
        (Regex::new("^\\+\\+\\+ ").unwrap(), BOLD),
        (Regex::new("^@@ ").unwrap(), HUNK_HEADER),
    ];
    static ref ANSI_COLOR_REGEX: Regex = Regex::new("\x1b[^m]*m").unwrap();
}

enum LastLineKind {
    Initial,
    Add,
    Remove,
}

/// Format add and remove lines in ADD and REMOVE colors.
///
/// No intra-line refinement.
#[must_use]
fn simple_format_adds_and_removes(adds: &String, removes: &String) -> Vec<String> {
    let mut lines: Vec<String> = Vec::new();

    for remove_line in removes.lines() {
        lines.push(format!("{}-{}{}", REMOVE, remove_line, NORMAL));
    }
    if (!removes.is_empty()) && removes.chars().last().unwrap() != '\n' {
        lines.push(format!(
            "{}{}{}",
            NO_EOF_NEWLINE_COLOR, NO_EOF_NEWLINE_MARKER, NORMAL
        ));
    }

    for add_line in adds.lines() {
        lines.push(format!("{}+{}{}", ADD, add_line, NORMAL))
    }
    if (!adds.is_empty()) && adds.chars().last().unwrap() != '\n' {
        lines.push(format!(
            "{}{}{}",
            NO_EOF_NEWLINE_COLOR, NO_EOF_NEWLINE_MARKER, NORMAL
        ));
    }

    return lines;
}

#[must_use]
fn format_adds_and_removes(adds: &String, removes: &String) -> Vec<String> {
    if adds.is_empty() {
        return simple_format_adds_and_removes(adds, removes);
    }

    if removes.is_empty() {
        return simple_format_adds_and_removes(adds, removes);
    }

    // Find diffs between adds and removals
    let mut highlighted_adds = String::new();
    let mut highlighted_removes = String::new();
    let mut adds_is_inverse = false;
    let mut removes_is_inverse = false;
    let diff = removes.diff(&adds);
    match diff {
        edit::Edit::Copy(unchanged) => {
            highlighted_adds.push_str(unchanged);
            highlighted_removes.push_str(unchanged);
        }
        edit::Edit::Change(diff) => {
            diff.into_iter()
                .map(|edit| {
                    match edit {
                        string::Edit::Copy(elem) => {
                            if adds_is_inverse {
                                highlighted_adds.push_str(NOT_INVERSE_VIDEO);
                            }
                            adds_is_inverse = false;

                            if removes_is_inverse {
                                highlighted_removes.push_str(NOT_INVERSE_VIDEO);
                            }
                            removes_is_inverse = false;

                            highlighted_adds.push(elem);
                            highlighted_removes.push(elem);
                        }
                        string::Edit::Insert(elem) => {
                            if !adds_is_inverse {
                                highlighted_adds.push_str(INVERSE_VIDEO);
                            }
                            adds_is_inverse = true;

                            if elem == '\n' {
                                // Make sure the highlighted linefeed is visible
                                highlighted_adds.push('⏎');

                                // This will be reset by the linefeed, so we need to re-inverse on the next line
                                adds_is_inverse = false;
                            }
                            highlighted_adds.push(elem);
                        }
                        string::Edit::Remove(elem) => {
                            if !removes_is_inverse {
                                highlighted_removes.push_str(INVERSE_VIDEO);
                            }
                            removes_is_inverse = true;

                            if elem == '\n' {
                                // Make sure the highlighted linefeed is visible
                                highlighted_removes.push('⏎');

                                // This will be reset by the linefeed, so we need to re-inverse on the next line
                                removes_is_inverse = false;
                            }
                            highlighted_removes.push(elem);
                        }
                    };
                })
                .for_each(drop);
        }
    }

    let mut lines: Vec<String> = Vec::new();
    for highlighted_remove in highlighted_removes.lines() {
        lines.push(format!("{}-{}{}", REMOVE, highlighted_remove, NORMAL));
    }
    if (!removes.is_empty()) && removes.chars().last().unwrap() != '\n' {
        lines.push(format!(
            "{}{}{}",
            NO_EOF_NEWLINE_COLOR, NO_EOF_NEWLINE_MARKER, NORMAL
        ));
    }

    for highlighted_add in highlighted_adds.lines() {
        lines.push(format!("{}+{}{}", ADD, highlighted_add, NORMAL));
    }
    if (!adds.is_empty()) && adds.chars().last().unwrap() != '\n' {
        lines.push(format!(
            "{}{}{}",
            NO_EOF_NEWLINE_COLOR, NO_EOF_NEWLINE_MARKER, NORMAL
        ));
    }

    return lines;
}

#[must_use]
fn get_fixed_highlight(line: &str) -> &str {
    for static_header in STATIC_HEADERS.iter() {
        let re = &static_header.0;
        if re.is_match(line) {
            return static_header.1;
        }
    }

    return "";
}

fn print(stream: &mut BufWriter<&mut dyn Write>, text: &str) {
    stream.write(text.as_bytes()).unwrap();
}

fn println(stream: &mut BufWriter<&mut dyn Write>, text: &str) {
    print(stream, &text);
    print(stream, "\n");
}

fn highlight_diff(input: &mut dyn io::Read, output: &mut dyn io::Write) {
    let mut adds = String::new();
    let mut removes = String::new();
    let input = BufReader::new(input);
    let output = &mut BufWriter::new(output);
    let mut last_line_kind = LastLineKind::Initial;
    for line in input.lines() {
        let line = line.unwrap();

        // Strip out incoming ANSI formatting. This enables us to highlight
        // already-colored input.
        let line = ANSI_COLOR_REGEX.replace_all(&line, "");

        let fixed_highlight = get_fixed_highlight(&line);
        if !fixed_highlight.is_empty() {
            // Drain outstanding adds / removes
            for line in format_adds_and_removes(&adds, &removes) {
                println(output, &line);
            }
            adds.clear();
            removes.clear();

            print(output, fixed_highlight);
            print(output, &line);
            println(output, NORMAL);
            continue;
        }

        // Collect adds / removes
        if !line.is_empty() && line.chars().next().unwrap() == '+' {
            adds.push_str(&line[1..]);
            adds.push('\n');
            last_line_kind = LastLineKind::Add;
            continue;
        } else if !line.is_empty() && line.chars().next().unwrap() == '-' {
            removes.push_str(&line[1..]);
            removes.push('\n');
            last_line_kind = LastLineKind::Remove;
            continue;
        }

        if line == NO_EOF_NEWLINE_MARKER {
            match last_line_kind {
                LastLineKind::Add => {
                    assert!(adds.pop().unwrap() == '\n');
                    continue;
                }
                LastLineKind::Remove => {
                    assert!(removes.pop().unwrap() == '\n');
                    continue;
                }
                LastLineKind::Initial => {
                    // This block intentionally left blank
                }
            }
        }

        last_line_kind = LastLineKind::Initial;

        // Drain outstanding adds / removes
        for line in format_adds_and_removes(&adds, &removes) {
            println(output, &line);
        }
        adds.clear();
        removes.clear();

        // Print current line
        if line == NO_EOF_NEWLINE_MARKER {
            print(output, NO_EOF_NEWLINE_COLOR);
            print(output, &line);
            println(output, NORMAL);
        } else {
            println(output, &line);
        }
    }
    for line in format_adds_and_removes(&adds, &removes) {
        println(output, &line);
    }
}

/// Try paging using the named pager (`$PATH` will be searched).
///
/// Returns `true` if the pager was found, `false` otherwise.
fn try_pager(pager_name: &str) -> bool {
    // FIXME: Prepare env vars LESS and LV for the pager process, just like git does

    match Command::new(pager_name).stdin(Stdio::piped()).spawn() {
        Ok(mut pager) => {
            let pager_stdin = pager.stdin.as_mut().unwrap();
            highlight_diff(&mut io::stdin().lock(), pager_stdin);

            // FIXME: Report pager exit status if non-zero, together with
            // contents of pager stderr as well if possible.
            pager.wait().expect("Waiting for pager failed");

            return true;
        }
        Err(_) => {
            return false;
        }
    }
}

fn main() {
    if stdin_isatty() {
        eprintln!("Error: Expected input from a pipe");
        exit(1);
    }

    if !stdout_isatty() {
        // We're being piped, just do stdin -> stdout
        highlight_diff(&mut io::stdin().lock(), &mut io::stdout());
        return;
    }

    try_pager("moar");

    // FIXME: Print warning at the end if $PAGER was set to something that
    // doesn't exist.
}

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

    #[cfg(test)]
    use pretty_assertions::assert_eq;

    #[test]
    fn test_simple_format_adds_and_removes() {
        let empty: Vec<String> = Vec::new();
        assert_eq!(
            simple_format_adds_and_removes(&"".to_string(), &"".to_string()),
            empty
        );

        // Test adds-only
        assert_eq!(
            simple_format_adds_and_removes(&"a\n".to_string(), &"".to_string()),
            ["".to_string() + ADD + "+a" + NORMAL]
        );
        assert_eq!(
            simple_format_adds_and_removes(&"a\nb\n".to_string(), &"".to_string()),
            [
                "".to_string() + ADD + "+a" + NORMAL,
                "".to_string() + ADD + "+b" + NORMAL,
            ]
        );

        // Test removes-only
        assert_eq!(
            simple_format_adds_and_removes(&"".to_string(), &"a\n".to_string()),
            ["".to_string() + REMOVE + "-a" + NORMAL]
        );
        assert_eq!(
            simple_format_adds_and_removes(&"".to_string(), &"a\nb\n".to_string()),
            [
                "".to_string() + REMOVE + "-a" + NORMAL,
                "".to_string() + REMOVE + "-b" + NORMAL,
            ]
        );
    }

    #[test]
    fn test_quote_change() {
        assert_eq!(
            format_adds_and_removes(&"[quotes]\n".to_string(), &"<quotes>\n".to_string()),
            [
                format!(
                    "{}-{}<{}quotes{}>{}{}",
                    REMOVE,
                    INVERSE_VIDEO,
                    NOT_INVERSE_VIDEO,
                    INVERSE_VIDEO,
                    NOT_INVERSE_VIDEO,
                    NORMAL
                ),
                format!(
                    "{}+{}[{}quotes{}]{}{}",
                    ADD, INVERSE_VIDEO, NOT_INVERSE_VIDEO, INVERSE_VIDEO, NOT_INVERSE_VIDEO, NORMAL
                ),
            ]
        )
    }

    fn remove(text: &str) -> String {
        return format!("{}{}{}", REMOVE, text, NORMAL);
    }

    fn add(text: &str) -> String {
        return format!("{}{}{}", ADD, text, NORMAL);
    }

    #[test]
    fn test_remove_trailing_newline() {
        let mut input = "-hej\n\
            +hej\n\
            \\ No newline at end of file\n\
            "
        .as_bytes();

        let expected = format!(
            "{}\n{}\n{}\n",
            remove(&format!("-hej{}⏎", INVERSE_VIDEO)),
            add("+hej"),
            format!(
                "{}\\ No newline at end of file{}",
                NO_EOF_NEWLINE_COLOR, NORMAL
            )
        );

        let mut output: Vec<u8> = Vec::new();
        highlight_diff(&mut input, &mut output);
        assert_eq!(std::str::from_utf8(&output).unwrap(), expected);
    }

    #[test]
    fn test_trailing_newline_context() {
        let mut input = "+bepa\n apa\n\\ No newline at end of file\n".as_bytes();

        let expected = format!(
            "{}\n{}\n{}\n",
            add("+bepa"),
            " apa",
            format!(
                "{}\\ No newline at end of file{}",
                NO_EOF_NEWLINE_COLOR, NORMAL
            )
        );

        let mut output: Vec<u8> = Vec::new();
        highlight_diff(&mut input, &mut output);
        assert_eq!(std::str::from_utf8(&output).unwrap(), expected);
    }
}
<commit_msg>Try different pagers, just like git does<commit_after>#[macro_use]
extern crate lazy_static;

use diffus::{
    edit::{self, string},
    Diffable,
};
use isatty::{stdin_isatty, stdout_isatty};
use regex::Regex;
use std::env;
use std::io::{self, BufRead, BufReader, BufWriter, Write};
use std::process::exit;
use std::process::{Command, Stdio};
use std::str;

const ADD: &str = "\x1b[32m"; // Green
const REMOVE: &str = "\x1b[31m"; // Red
const HUNK_HEADER: &str = "\x1b[36m"; // Cyan

const NO_EOF_NEWLINE_COLOR: &str = "\x1b[2m"; // Faint
const NO_EOF_NEWLINE_MARKER: &str = "\\ No newline at end of file";

const INVERSE_VIDEO: &str = "\x1b[7m";
const NOT_INVERSE_VIDEO: &str = "\x1b[27m";

const BOLD: &str = "\x1b[1m";

const NORMAL: &str = "\x1b[0m";

lazy_static! {
    static ref STATIC_HEADERS: Vec<(Regex, &'static str)> = vec![
        (Regex::new("^diff ").unwrap(), BOLD),
        (Regex::new("^index ").unwrap(), BOLD),
        (Regex::new("^--- ").unwrap(), BOLD),
        (Regex::new("^\\+\\+\\+ ").unwrap(), BOLD),
        (Regex::new("^@@ ").unwrap(), HUNK_HEADER),
    ];
    static ref ANSI_COLOR_REGEX: Regex = Regex::new("\x1b[^m]*m").unwrap();
}

enum LastLineKind {
    Initial,
    Add,
    Remove,
}

/// Format add and remove lines in ADD and REMOVE colors.
///
/// No intra-line refinement.
#[must_use]
fn simple_format_adds_and_removes(adds: &String, removes: &String) -> Vec<String> {
    let mut lines: Vec<String> = Vec::new();

    for remove_line in removes.lines() {
        lines.push(format!("{}-{}{}", REMOVE, remove_line, NORMAL));
    }
    if (!removes.is_empty()) && removes.chars().last().unwrap() != '\n' {
        lines.push(format!(
            "{}{}{}",
            NO_EOF_NEWLINE_COLOR, NO_EOF_NEWLINE_MARKER, NORMAL
        ));
    }

    for add_line in adds.lines() {
        lines.push(format!("{}+{}{}", ADD, add_line, NORMAL))
    }
    if (!adds.is_empty()) && adds.chars().last().unwrap() != '\n' {
        lines.push(format!(
            "{}{}{}",
            NO_EOF_NEWLINE_COLOR, NO_EOF_NEWLINE_MARKER, NORMAL
        ));
    }

    return lines;
}

#[must_use]
fn format_adds_and_removes(adds: &String, removes: &String) -> Vec<String> {
    if adds.is_empty() {
        return simple_format_adds_and_removes(adds, removes);
    }

    if removes.is_empty() {
        return simple_format_adds_and_removes(adds, removes);
    }

    // Find diffs between adds and removals
    let mut highlighted_adds = String::new();
    let mut highlighted_removes = String::new();
    let mut adds_is_inverse = false;
    let mut removes_is_inverse = false;
    let diff = removes.diff(&adds);
    match diff {
        edit::Edit::Copy(unchanged) => {
            highlighted_adds.push_str(unchanged);
            highlighted_removes.push_str(unchanged);
        }
        edit::Edit::Change(diff) => {
            diff.into_iter()
                .map(|edit| {
                    match edit {
                        string::Edit::Copy(elem) => {
                            if adds_is_inverse {
                                highlighted_adds.push_str(NOT_INVERSE_VIDEO);
                            }
                            adds_is_inverse = false;

                            if removes_is_inverse {
                                highlighted_removes.push_str(NOT_INVERSE_VIDEO);
                            }
                            removes_is_inverse = false;

                            highlighted_adds.push(elem);
                            highlighted_removes.push(elem);
                        }
                        string::Edit::Insert(elem) => {
                            if !adds_is_inverse {
                                highlighted_adds.push_str(INVERSE_VIDEO);
                            }
                            adds_is_inverse = true;

                            if elem == '\n' {
                                // Make sure the highlighted linefeed is visible
                                highlighted_adds.push('⏎');

                                // This will be reset by the linefeed, so we need to re-inverse on the next line
                                adds_is_inverse = false;
                            }
                            highlighted_adds.push(elem);
                        }
                        string::Edit::Remove(elem) => {
                            if !removes_is_inverse {
                                highlighted_removes.push_str(INVERSE_VIDEO);
                            }
                            removes_is_inverse = true;

                            if elem == '\n' {
                                // Make sure the highlighted linefeed is visible
                                highlighted_removes.push('⏎');

                                // This will be reset by the linefeed, so we need to re-inverse on the next line
                                removes_is_inverse = false;
                            }
                            highlighted_removes.push(elem);
                        }
                    };
                })
                .for_each(drop);
        }
    }

    let mut lines: Vec<String> = Vec::new();
    for highlighted_remove in highlighted_removes.lines() {
        lines.push(format!("{}-{}{}", REMOVE, highlighted_remove, NORMAL));
    }
    if (!removes.is_empty()) && removes.chars().last().unwrap() != '\n' {
        lines.push(format!(
            "{}{}{}",
            NO_EOF_NEWLINE_COLOR, NO_EOF_NEWLINE_MARKER, NORMAL
        ));
    }

    for highlighted_add in highlighted_adds.lines() {
        lines.push(format!("{}+{}{}", ADD, highlighted_add, NORMAL));
    }
    if (!adds.is_empty()) && adds.chars().last().unwrap() != '\n' {
        lines.push(format!(
            "{}{}{}",
            NO_EOF_NEWLINE_COLOR, NO_EOF_NEWLINE_MARKER, NORMAL
        ));
    }

    return lines;
}

#[must_use]
fn get_fixed_highlight(line: &str) -> &str {
    for static_header in STATIC_HEADERS.iter() {
        let re = &static_header.0;
        if re.is_match(line) {
            return static_header.1;
        }
    }

    return "";
}

fn print(stream: &mut BufWriter<&mut dyn Write>, text: &str) {
    stream.write(text.as_bytes()).unwrap();
}

fn println(stream: &mut BufWriter<&mut dyn Write>, text: &str) {
    print(stream, &text);
    print(stream, "\n");
}

fn highlight_diff(input: &mut dyn io::Read, output: &mut dyn io::Write) {
    let mut adds = String::new();
    let mut removes = String::new();
    let input = BufReader::new(input);
    let output = &mut BufWriter::new(output);
    let mut last_line_kind = LastLineKind::Initial;
    for line in input.lines() {
        let line = line.unwrap();

        // Strip out incoming ANSI formatting. This enables us to highlight
        // already-colored input.
        let line = ANSI_COLOR_REGEX.replace_all(&line, "");

        let fixed_highlight = get_fixed_highlight(&line);
        if !fixed_highlight.is_empty() {
            // Drain outstanding adds / removes
            for line in format_adds_and_removes(&adds, &removes) {
                println(output, &line);
            }
            adds.clear();
            removes.clear();

            print(output, fixed_highlight);
            print(output, &line);
            println(output, NORMAL);
            continue;
        }

        // Collect adds / removes
        if !line.is_empty() && line.chars().next().unwrap() == '+' {
            adds.push_str(&line[1..]);
            adds.push('\n');
            last_line_kind = LastLineKind::Add;
            continue;
        } else if !line.is_empty() && line.chars().next().unwrap() == '-' {
            removes.push_str(&line[1..]);
            removes.push('\n');
            last_line_kind = LastLineKind::Remove;
            continue;
        }

        if line == NO_EOF_NEWLINE_MARKER {
            match last_line_kind {
                LastLineKind::Add => {
                    assert!(adds.pop().unwrap() == '\n');
                    continue;
                }
                LastLineKind::Remove => {
                    assert!(removes.pop().unwrap() == '\n');
                    continue;
                }
                LastLineKind::Initial => {
                    // This block intentionally left blank
                }
            }
        }

        last_line_kind = LastLineKind::Initial;

        // Drain outstanding adds / removes
        for line in format_adds_and_removes(&adds, &removes) {
            println(output, &line);
        }
        adds.clear();
        removes.clear();

        // Print current line
        if line == NO_EOF_NEWLINE_MARKER {
            print(output, NO_EOF_NEWLINE_COLOR);
            print(output, &line);
            println(output, NORMAL);
        } else {
            println(output, &line);
        }
    }
    for line in format_adds_and_removes(&adds, &removes) {
        println(output, &line);
    }
}

/// Try paging using the named pager (`$PATH` will be searched).
///
/// Returns `true` if the pager was found, `false` otherwise.
fn try_pager(pager_name: &str) -> bool {
    // FIXME: Prepare env vars LESS and LV for the pager process, just like git does

    match Command::new(pager_name).stdin(Stdio::piped()).spawn() {
        Ok(mut pager) => {
            let pager_stdin = pager.stdin.as_mut().unwrap();
            highlight_diff(&mut io::stdin().lock(), pager_stdin);

            // FIXME: Report pager exit status if non-zero, together with
            // contents of pager stderr as well if possible.
            pager.wait().expect("Waiting for pager failed");

            return true;
        }
        Err(_) => {
            return false;
        }
    }
}

fn main() {
    if stdin_isatty() {
        eprintln!("Error: Expected input from a pipe");
        exit(1);
    }

    if !stdout_isatty() {
        // We're being piped, just do stdin -> stdout
        highlight_diff(&mut io::stdin().lock(), &mut io::stdout());
        return;
    }

    let pager_env_var = env::var("PAGER");
    if pager_env_var.is_ok() {
        let pager_value = pager_env_var.unwrap();
        if try_pager(&pager_value) {
            return;
        }

        // FIXME: Print warning at the end if $PAGER was set to something that
        // doesn't exist.
    }

    if try_pager("moar") {
        return;
    }

    if try_pager("less") {
        return;
    }
}

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

    #[cfg(test)]
    use pretty_assertions::assert_eq;

    #[test]
    fn test_simple_format_adds_and_removes() {
        let empty: Vec<String> = Vec::new();
        assert_eq!(
            simple_format_adds_and_removes(&"".to_string(), &"".to_string()),
            empty
        );

        // Test adds-only
        assert_eq!(
            simple_format_adds_and_removes(&"a\n".to_string(), &"".to_string()),
            ["".to_string() + ADD + "+a" + NORMAL]
        );
        assert_eq!(
            simple_format_adds_and_removes(&"a\nb\n".to_string(), &"".to_string()),
            [
                "".to_string() + ADD + "+a" + NORMAL,
                "".to_string() + ADD + "+b" + NORMAL,
            ]
        );

        // Test removes-only
        assert_eq!(
            simple_format_adds_and_removes(&"".to_string(), &"a\n".to_string()),
            ["".to_string() + REMOVE + "-a" + NORMAL]
        );
        assert_eq!(
            simple_format_adds_and_removes(&"".to_string(), &"a\nb\n".to_string()),
            [
                "".to_string() + REMOVE + "-a" + NORMAL,
                "".to_string() + REMOVE + "-b" + NORMAL,
            ]
        );
    }

    #[test]
    fn test_quote_change() {
        assert_eq!(
            format_adds_and_removes(&"[quotes]\n".to_string(), &"<quotes>\n".to_string()),
            [
                format!(
                    "{}-{}<{}quotes{}>{}{}",
                    REMOVE,
                    INVERSE_VIDEO,
                    NOT_INVERSE_VIDEO,
                    INVERSE_VIDEO,
                    NOT_INVERSE_VIDEO,
                    NORMAL
                ),
                format!(
                    "{}+{}[{}quotes{}]{}{}",
                    ADD, INVERSE_VIDEO, NOT_INVERSE_VIDEO, INVERSE_VIDEO, NOT_INVERSE_VIDEO, NORMAL
                ),
            ]
        )
    }

    fn remove(text: &str) -> String {
        return format!("{}{}{}", REMOVE, text, NORMAL);
    }

    fn add(text: &str) -> String {
        return format!("{}{}{}", ADD, text, NORMAL);
    }

    #[test]
    fn test_remove_trailing_newline() {
        let mut input = "-hej\n\
            +hej\n\
            \\ No newline at end of file\n\
            "
        .as_bytes();

        let expected = format!(
            "{}\n{}\n{}\n",
            remove(&format!("-hej{}⏎", INVERSE_VIDEO)),
            add("+hej"),
            format!(
                "{}\\ No newline at end of file{}",
                NO_EOF_NEWLINE_COLOR, NORMAL
            )
        );

        let mut output: Vec<u8> = Vec::new();
        highlight_diff(&mut input, &mut output);
        assert_eq!(std::str::from_utf8(&output).unwrap(), expected);
    }

    #[test]
    fn test_trailing_newline_context() {
        let mut input = "+bepa\n apa\n\\ No newline at end of file\n".as_bytes();

        let expected = format!(
            "{}\n{}\n{}\n",
            add("+bepa"),
            " apa",
            format!(
                "{}\\ No newline at end of file{}",
                NO_EOF_NEWLINE_COLOR, NORMAL
            )
        );

        let mut output: Vec<u8> = Vec::new();
        highlight_diff(&mut input, &mut output);
        assert_eq!(std::str::from_utf8(&output).unwrap(), expected);
    }
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate log;
extern crate env_logger;
extern crate rustc_serialize;
extern crate docopt;
extern crate glob;
extern crate toml;

use docopt::Docopt;
use std::path::Path;
use std::fs::File;
use std::fs::metadata;
use std::io::prelude::*;
use glob::Pattern;

const USAGE: &'static str = "
enforcer for code rules

Usage:
  enforcer <glob> [-c|--clean]
  enforcer (-h | --help)
  enforcer --version

Options:
  -h --help     Show this screen.
  --version     Show version.
  --count       only count found entries
  -c --clean    clean up trailing whitespaces
";

#[derive(Debug, RustcDecodable)]
struct Args {
    flag_count: bool,
    flag_clean: bool,
    arg_glob: String,
}
const HAS_TABS: u8               = 1 << 0;
const TRAILING_SPACES: u8        = 1 << 1;
const HAS_ILLEGAL_CHARACTERS: u8 = 1 << 2;

fn check_content<'a>(input: &'a str) -> std::io::Result<u8> {
    let mut result = 0;
    for line in input.lines_any() {
        if line.ends_with(' ') {
            result |= TRAILING_SPACES;
        }
        if line.contains("\t") {
            result |= HAS_TABS;
        }
        if line.as_bytes().iter().any(|x| *x > 127) {
            result |= HAS_ILLEGAL_CHARACTERS;
        }
    }
    Ok(result)
}

fn clean_string(input: &str) -> String {
    let v: Vec<&str> = input
        .lines_any()
        .map(|line| line.trim_right())
        .collect();

    if input.ends_with("\n") {
        v.join("\n") + "\n"
    }
    else {
        v.join("\n")
    }
}

fn is_dir(path: &Path) -> bool {
    if let Ok(result) = metadata(&path) {
        result.is_dir()
    } else {
        false
    }
}

fn check_path(path: &Path, clean: bool) -> std::io::Result<()> {
    use std::io::ErrorKind;

    let mut f = try!(File::open(path));
    let mut buffer = String::new();
    let mut check = 0;
    if let Err(e) = f.read_to_string(&mut buffer) {
        match e.kind() {
            ErrorKind::InvalidData => check = check | HAS_ILLEGAL_CHARACTERS,
            _ => return Err(e),
        }
    }
    // only check content if we could read the file
    if check == 0 { check = try!(check_content(&buffer)); }
    if (check & HAS_ILLEGAL_CHARACTERS) > 0 {
        println!("HAS_ILLEGAL_CHARACTERS:[{}]", path.display());
    }
    if (check & HAS_TABS) > 0 {
        println!("HAS_TABS:[{}]", path.display());
    }
    if (check & TRAILING_SPACES) > 0 {
        println!("TRAILING_SPACES:[{}]", path.display());
        if clean {
            println!("cleaning trailing whitespaces");
            let res_string = clean_string(&buffer);
            let mut file = try!(File::create(path));
            try!(file.write_all(res_string.as_bytes()));
        }
    }
    Ok(())
}

struct EnforcerCfg {
    unwanted: Vec<String>,
}
impl std::fmt::Debug for EnforcerCfg {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "[unwanted]: {:?}", self.unwanted)
    }
}

fn is_unwanted(path_elem: &str, unwanted_cfg: &EnforcerCfg) -> bool {
    unwanted_cfg.unwanted.iter()
        .any(|x| Pattern::new(x)
            .ok()
            .expect(&format!("{} seems not to be a valid pattern", x)[..])
            .matches(path_elem))
}

fn load_config<'a>(input: &'a str) -> std::io::Result<EnforcerCfg> {
    use std::io::{Error, ErrorKind};

    let mut parser = toml::Parser::new(input);
    let default_cfg = EnforcerCfg { unwanted: vec![".git".to_string(), ".bake".to_string()]};
    match parser.parse() {
        Some(toml_) => {
            match toml_.get("ignore") {
                Some(entry) => {
                    match entry.as_slice() {
                        Some(val) => {
                            let xs = val.iter()
                                            .filter_map(|x| x.as_str())
                                            .map(|v| v.to_string())
                                            .collect();
                            Ok(EnforcerCfg { unwanted: xs })
                        }
                        None => Ok(default_cfg)
                    }
                }
                None => Ok(default_cfg)
            }
        }
        None => Err(Error::new(ErrorKind::InvalidData, "could not parse the config"))
    }
}

#[allow(dead_code)]
fn main() {
    use glob::glob;
    env_logger::init().unwrap();

    fn get_cfg() -> EnforcerCfg {
        fn read_unwanted() -> std::io::Result<EnforcerCfg> {
            let mut cfg_file = try!(File::open(".enforcer"));
            let mut buffer = String::new();
            try!(cfg_file.read_to_string(&mut buffer));
            load_config(&buffer[..])
        }
        let default_cfg = EnforcerCfg { unwanted: vec![".git".to_string(), ".bake".to_string()]};
        let unwanted_cfg = read_unwanted()
            .unwrap_or(default_cfg);
        println!("loaded ignores: {:?}", unwanted_cfg.unwanted);
        unwanted_cfg
    }

    let args: Args = Docopt::new(USAGE)
                            .and_then(|d| d.decode())
                            .unwrap_or_else(|e| e.exit());

    let pat = args.arg_glob.to_string();

    let unwanted_cfg = get_cfg();
    for path in glob(&*pat)
        .ok()
        .expect(&format!("glob has problems with {}", pat)[..])
        .filter_map(Result::ok)
        .filter(|x| !x.components()
                        .any(|y| is_unwanted(y.as_os_str()
                                                .to_str()
                                                .expect("blablalblalbla"), &unwanted_cfg))) {
            if !is_dir(path.as_path()) {
                check_path(path.as_path(), args.flag_clean)
                    .ok()
                    .expect(&format!("check_path for {:?} should work", path));
            }
    }
}

#[cfg(test)]
mod tests {
    use super::clean_string;
    use super::load_config;
    use super::is_unwanted;
    use glob::Pattern;
    use super::EnforcerCfg;

    #[test]
    fn test_clean_does_not_remove_trailing_newline() {
        let content = "1\n2\n3\n4\n5\n";
        let cleaned = clean_string(content);
        assert!(cleaned.eq(content));
    }
    #[test]
    fn test_clean_trailing_whitespace() {
        let content = "1 \n2";
        let cleaned = clean_string(content);
        println!("{:?}", cleaned);
        assert!(cleaned.eq("1\n2"));
    }
    #[test]
    fn test_load_simple_config() {
        let c = include_str!("../samples/.enforcer");
        let cfg = load_config(c).unwrap();
        assert_eq!(cfg.unwanted.len(), 2);
    }
    #[test]
    #[should_panic]
    fn test_load_broken_config() {
        let c = include_str!("../samples/.enforcer_broken");
        let _ = load_config(c).unwrap();
    }
    #[test]
    fn test_glob() {
        assert!(Pattern::new("build_*").unwrap().matches("build_Debug"));
    }
    #[test]
    fn test_is_unwanted() {
        let cfg = EnforcerCfg { unwanted: vec!["build_*".to_string(), ".git".to_string()]};
        assert!(is_unwanted("build_Debug", &cfg));
        assert!(is_unwanted(".git", &cfg));
        assert!(!is_unwanted("bla", &cfg));
    }
}

<commit_msg>better error handling, dealing with optional values<commit_after>#[macro_use]
extern crate log;
extern crate env_logger;
extern crate rustc_serialize;
extern crate docopt;
extern crate glob;
extern crate toml;

use docopt::Docopt;
use std::path::Path;
use std::fs::File;
use std::fs::metadata;
use std::io::prelude::*;
use glob::Pattern;

const USAGE: &'static str = "
enforcer for code rules

Usage:
  enforcer <glob> [-c|--clean]
  enforcer (-h | --help)
  enforcer --version

Options:
  -h --help     Show this screen.
  --version     Show version.
  --count       only count found entries
  -c --clean    clean up trailing whitespaces
";

#[derive(Debug, RustcDecodable)]
struct Args {
    flag_count: bool,
    flag_clean: bool,
    arg_glob: String,
}
const HAS_TABS: u8               = 1 << 0;
const TRAILING_SPACES: u8        = 1 << 1;
const HAS_ILLEGAL_CHARACTERS: u8 = 1 << 2;

fn check_content<'a>(input: &'a str) -> std::io::Result<u8> {
    let mut result = 0;
    for line in input.lines_any() {
        if line.ends_with(' ') {
            result |= TRAILING_SPACES;
        }
        if line.contains("\t") {
            result |= HAS_TABS;
        }
        if line.as_bytes().iter().any(|x| *x > 127) {
            result |= HAS_ILLEGAL_CHARACTERS;
        }
    }
    Ok(result)
}

fn clean_string(input: &str) -> String {
    let v: Vec<&str> = input
        .lines_any()
        .map(|line| line.trim_right())
        .collect();

    if input.ends_with("\n") {
        v.join("\n") + "\n"
    }
    else {
        v.join("\n")
    }
}

fn is_dir(path: &Path) -> bool {
    if let Ok(result) = metadata(&path) {
        result.is_dir()
    } else {
        false
    }
}

fn check_path(path: &Path, clean: bool) -> std::io::Result<()> {
    use std::io::ErrorKind;

    let mut f = try!(File::open(path));
    let mut buffer = String::new();
    let mut check = 0;
    if let Err(e) = f.read_to_string(&mut buffer) {
        match e.kind() {
            ErrorKind::InvalidData => check = check | HAS_ILLEGAL_CHARACTERS,
            _ => return Err(e),
        }
    }
    // only check content if we could read the file
    if check == 0 { check = try!(check_content(&buffer)); }
    if (check & HAS_ILLEGAL_CHARACTERS) > 0 {
        println!("HAS_ILLEGAL_CHARACTERS:[{}]", path.display());
    }
    if (check & HAS_TABS) > 0 {
        println!("HAS_TABS:[{}]", path.display());
    }
    if (check & TRAILING_SPACES) > 0 {
        println!("TRAILING_SPACES:[{}]", path.display());
        if clean {
            println!("cleaning trailing whitespaces");
            let res_string = clean_string(&buffer);
            let mut file = try!(File::create(path));
            try!(file.write_all(res_string.as_bytes()));
        }
    }
    Ok(())
}

struct EnforcerCfg {
    unwanted: Vec<String>,
}
impl std::fmt::Debug for EnforcerCfg {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "[unwanted]: {:?}", self.unwanted)
    }
}
fn default_cfg() -> EnforcerCfg {
    EnforcerCfg { unwanted: vec![".git".to_string(), ".bake".to_string()]}
}

fn is_unwanted(path_elem: &str, unwanted_cfg: &EnforcerCfg) -> bool {
    unwanted_cfg.unwanted.iter()
        .any(|x| Pattern::new(x)
            .ok()
            .expect(&format!("{} seems not to be a valid pattern", x)[..])
            .matches(path_elem))
}

fn load_config<'a>(input: &'a str) -> std::io::Result<EnforcerCfg> {
    use std::io::{Error, ErrorKind};
    let mut parser = toml::Parser::new(input);
    let default_err = Err(Error::new(ErrorKind::InvalidData, "could not parse the config"));
    parser.parse().map_or(default_err, |toml_|
            toml_.get("ignore").map_or(Ok(default_cfg()), |entry|
                entry.as_slice().map_or(Ok(default_cfg()), |val| {
                    let xs = val.iter()
                        .filter_map(|x| x.as_str())
                        .map(|v| v.to_string())
                        .collect();
                    Ok(EnforcerCfg { unwanted: xs })
                })))
}

#[allow(dead_code)]
fn main() {
    use glob::glob;
    env_logger::init().unwrap();

    fn get_cfg() -> EnforcerCfg {
        fn read_unwanted() -> std::io::Result<EnforcerCfg> {
            let mut cfg_file = try!(File::open(".enforcer"));
            let mut buffer = String::new();
            try!(cfg_file.read_to_string(&mut buffer));
            load_config(&buffer[..])
        }
        let unwanted_cfg = read_unwanted()
            .unwrap_or(default_cfg());
        println!("loaded ignores: {:?}", unwanted_cfg.unwanted);
        unwanted_cfg
    }

    let args: Args = Docopt::new(USAGE)
                            .and_then(|d| d.decode())
                            .unwrap_or_else(|e| e.exit());

    let pat = args.arg_glob.to_string();

    let unwanted_cfg = get_cfg();
    for path in glob(&*pat)
        .ok()
        .expect(&format!("glob has problems with {}", pat)[..])
        .filter_map(Result::ok)
        .filter(|x| !x.components()
                        .any(|y| is_unwanted(y.as_os_str()
                                                .to_str()
                                                .expect("blablalblalbla"), &unwanted_cfg))) {
            if !is_dir(path.as_path()) {
                check_path(path.as_path(), args.flag_clean)
                    .ok()
                    .expect(&format!("check_path for {:?} should work", path));
            }
    }
}

#[cfg(test)]
mod tests {
    use super::clean_string;
    use super::load_config;
    use super::is_unwanted;
    use glob::Pattern;
    use super::EnforcerCfg;
    use super::default_cfg;

    #[test]
    fn test_clean_does_not_remove_trailing_newline() {
        let content = "1\n2\n3\n4\n5\n";
        let cleaned = clean_string(content);
        assert!(cleaned.eq(content));
    }
    #[test]
    fn test_clean_trailing_whitespace() {
        let content = "1 \n2";
        let cleaned = clean_string(content);
        println!("{:?}", cleaned);
        assert!(cleaned.eq("1\n2"));
    }
    #[test]
    fn test_load_simple_config() {
        let c = include_str!("../samples/.enforcer");
        let cfg = load_config(c).unwrap();
        assert_eq!(cfg.unwanted.len(), 2);
    }
    #[test]
    fn test_load_broken_config() {
        let c = include_str!("../samples/.enforcer_broken");
        let cfg = load_config(c).unwrap();
        assert_eq!(default_cfg().unwanted, cfg.unwanted);
    }
    #[test]
    fn test_glob() {
        assert!(Pattern::new("build_*").unwrap().matches("build_Debug"));
    }
    #[test]
    fn test_is_unwanted() {
        let cfg = EnforcerCfg { unwanted: vec!["build_*".to_string(), ".git".to_string()]};
        assert!(is_unwanted("build_Debug", &cfg));
        assert!(is_unwanted(".git", &cfg));
        assert!(!is_unwanted("bla", &cfg));
    }
}

<|endoftext|>


<commit_before>extern crate wm_daemons;
use wm_daemons::config::{load_config, load_config_path};
use wm_daemons::dbus_listen::{CallbackMap, match_method};

#[macro_use]
extern crate clap;
use clap::{Arg, App};

extern crate config;
use self::config::types::Config;

extern crate dbus;
use self::dbus::{Connection, BusType};

use std::error::Error;
use std::path::Path;

struct Context {
    conf: &Config,
}

fn try_main() -> Result<(), Box<Error>> {
    let matches = App::new("ruskey")
        .version(&crate_version!()[..])
        .author("Ben Boeckel <mathstuf@gmail.com>")
        .about("Implement the SecretService API for keepass databases")
        .arg(Arg::with_name("CONFIG")
                .short("c")
                .long("config")
                .help("Path to the configuration file")
                .takes_value(true))
        .arg(Arg::with_name("DATABASE_NAME")
                .short("n")
                .long("name")
                .help("Name of the database")
                .takes_value(true))
        .arg(Arg::with_name("DATABASE")
                .short("d")
                .long("database")
                .help("Path to the database")
                .takes_value(true))
        .get_matches();

    let conf = try!(if matches.is_present("CONFIG") {
            load_config_path(Path::new(matches.value_of("CONFIG").unwrap()))
        } else {
            load_config("ruskey", "config")
        });

    let conn = try!(Connection::get_private(BusType::Session));

    let cbs: CallbackMap<Context> = vec![
        // TODO
    ];
    let ctx = Context {
        conf: &conf,
    };

    let match_str = "";
    try!(conn.add_match(match_str));

    conn.iter(100).fold(ctx, |inner_ctx, item| {
        match_method(inner_ctx, &cbs, item)
    });

    Ok(())
}

fn main() {
    if let Err(err) = try_main() {
        panic!("{}", err.description());
    }
}
<commit_msg>main: use &String rather than &String[..]<commit_after>extern crate wm_daemons;
use wm_daemons::config::{load_config, load_config_path};
use wm_daemons::dbus_listen::{CallbackMap, match_method};

#[macro_use]
extern crate clap;
use clap::{Arg, App};

extern crate config;
use self::config::types::Config;

extern crate dbus;
use self::dbus::{Connection, BusType};

use std::error::Error;
use std::path::Path;

struct Context {
    conf: &Config,
}

fn try_main() -> Result<(), Box<Error>> {
    let matches = App::new("ruskey")
        .version(&crate_version!())
        .author("Ben Boeckel <mathstuf@gmail.com>")
        .about("Implement the SecretService API for keepass databases")
        .arg(Arg::with_name("CONFIG")
                .short("c")
                .long("config")
                .help("Path to the configuration file")
                .takes_value(true))
        .arg(Arg::with_name("DATABASE_NAME")
                .short("n")
                .long("name")
                .help("Name of the database")
                .takes_value(true))
        .arg(Arg::with_name("DATABASE")
                .short("d")
                .long("database")
                .help("Path to the database")
                .takes_value(true))
        .get_matches();

    let conf = try!(if matches.is_present("CONFIG") {
            load_config_path(Path::new(matches.value_of("CONFIG").unwrap()))
        } else {
            load_config("ruskey", "config")
        });

    let conn = try!(Connection::get_private(BusType::Session));

    let cbs: CallbackMap<Context> = vec![
        // TODO
    ];
    let ctx = Context {
        conf: &conf,
    };

    let match_str = "";
    try!(conn.add_match(match_str));

    conn.iter(100).fold(ctx, |inner_ctx, item| {
        match_method(inner_ctx, &cbs, item)
    });

    Ok(())
}

fn main() {
    if let Err(err) = try_main() {
        panic!("{}", err.description());
    }
}
<|endoftext|>