<commit_before>#[macro_use]
extern crate wayland_client;

extern crate tempfile;

extern crate byteorder;
extern crate image;

use std::env;
use std::process::exit;
use std::io::{BufReader};
use image::{load, ImageFormat};
use std::mem::transmute;
use std::os::unix::io::AsRawFd;
use std::io::Write;
use std::fs::File;

use wayland_client::wayland::get_display;
use wayland_client::wayland::compositor::{WlCompositor, WlSurface};
use wayland_client::wayland::shell::WlShell;
use wayland_client::wayland::shm::{WlShm, WlShmFormat};
use wayland_client::wayland::seat::{WlSeat, WlPointerEvent};
use wayland_client::cursor::load_theme;
use wayland_client::{EventIterator, Proxy};

use byteorder::{NativeEndian, WriteBytesExt};

wayland_env!(WaylandEnv,
             compositor: WlCompositor,
             shell: WlShell,
             shm: WlShm,
             seat: WlSeat
);

#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
/// Holds the bytes to represent a colored background.
/// To be written into a wayland surface.
pub struct Color(pub [u8; 4]);

impl Color {
    /// Generate a new color out of a u32.
    /// E.G: 0xFFFFFF
    pub fn from_u32(color: u32) -> Self {
        unsafe { Color(transmute(color)) }
    }

    pub fn as_u32(&self) -> u32 {
        unsafe { transmute(self.0)}
    }
}

fn main() {
    let args: Vec<_> = env::args().collect();
    if args.len() < 3 {
        println!("Please supply either a file path or a color (written in hex)");
        println!("Please also supply a path to a cursor image");
        exit(1);
    }
    let input = &args[1];
    let cursor_path = &args[2];
    if let Ok(color) = input.parse::<u32>() {
        let color = Color::from_u32(color);
        generate_solid_background(color, cursor_path.clone());
    } else {
        generate_image_background(input.clone(), cursor_path.clone());
    }
}

fn weird_math(num: u8, third_num: u32) -> u8 {
    let big_num = num as u32;
    ((big_num * third_num) / 255) as u8
}

/// Given a solid color, writes bytes associated with that color to
/// a special Wayland surface which is then rendered as a background for Way Cooler.
pub fn generate_solid_background(color: Color, cursor_path: String) {
    // Get shortcuts to the globals.
    let (display, iter) = get_display()
        .expect("Unable to connect to a wayland compositor");
    let (env, evt_iter) = WaylandEnv::init(display, iter);
    let compositor = env.compositor.as_ref().map(|o| &o.0).unwrap();
    let shell = env.shell.as_ref().map(|o| &o.0).unwrap();
    let shm = env.shm.as_ref().map(|o| &o.0).unwrap();

    // Create the surface we are going to write into
    let background_surface = compositor.create_surface();
    let shell_surface = shell.get_shell_surface(&background_surface);
    let mut tmp = tempfile::tempfile().ok().expect("Unable to create a tempfile.");

    // Calculate how big the buffer needs to be from the output resolution
    // TODO Get the output size from way cooler somehow...
    //let resolution = output.get_resolution()
    //    .expect("Couldn't get output resolution");
    //let (width, height) = (resolution.w as i32, resolution.h as i32);
    let width = 800; let height = 600;
    let size = (width * height) as i32;

    // Write in the color coding to the surface
    for _ in 0..size {
        unsafe {
            tmp.write_u32::<NativeEndian>(transmute(color.0))
                .expect("Could not write to file")
        }
    }
    tmp.flush()
        .expect("Could not flush buffer");

    // Create the buffer that is mem-mapped to the temp file descriptor
    let pool = shm.create_pool(tmp.as_raw_fd(), size);
    let buffer = pool.create_buffer(0, width, height, width, WlShmFormat::Argb8888);
    // Tell Way Cooler not to set put this in the tree, treat as background
    shell_surface.set_class("Background".into());
    shell_surface.set_title(format!("0x{:x}", color.as_u32()));

    // Attach the buffer to the surface
    background_surface.attach(Some(&buffer), 0, 0);

    let cursor_surface = cursor_surface(cursor_path.as_str(), &env);
    main_background_loop(background_surface, cursor_surface, evt_iter, &env);
}

/// Given the data from an image, writes it to a special Wayland surface
/// which is then rendered as a background for Way Cooler.
pub fn generate_image_background(path: String, cursor_path: String) {
    let image_file = File::open(path.clone())
        .unwrap_or_else(|_| {
            println!("Could not open \"{:?}\"", path);
            panic!("Could not open image file");
        });
    let image_reader = BufReader::new(image_file);
    // TODO support more formats, split into separate function
    let image = load(image_reader, ImageFormat::PNG)
        .expect("Image was not a png file!");
    let mut image = image.to_rgba();
    let width = image.width();
    let height = image.height();
    let stride = width * 4;
    let size = stride * height;
    // TODO Split this into its own function
    {
        let pixels = image.enumerate_pixels_mut();
        for (_x, _y, pixel) in pixels {
            let alpha = pixel[3] as u32;
            pixel[0] = weird_math(pixel[0], alpha);
            pixel[1] = weird_math(pixel[1], alpha);
            pixel[2] = weird_math(pixel[2], alpha);

            let tmp = pixel[2];
            pixel[2] = pixel[0];
            pixel[0] = tmp;
        }
    }
    let vec = image.into_vec();
    let mut tmp = tempfile::NamedTempFile::new().expect("Unable to create a tempfile.");
    tmp.set_len(size as u64).expect("Could not truncate length of file");
    tmp.write_all(&*vec).unwrap();

    // TODO I want this shit outta here
    let (display, iter) = get_display()
        .expect("Unable to connect to a wayland compositor");
    let (env, evt_iter) = WaylandEnv::init(display, iter);
    let compositor = env.compositor.as_ref().map(|o| &o.0).unwrap();
    let shell = env.shell.as_ref().map(|o| &o.0).unwrap();
    let shm = env.shm.as_ref().map(|o| &o.0).unwrap();

    // Create the surface we are going to write into
    let background_surface = compositor.create_surface();
    let shell_surface = shell.get_shell_surface(&background_surface);

    let pool = shm.create_pool(tmp.as_raw_fd(), size as i32);
    let background_buffer = pool.create_buffer(0, width as i32, height as i32, stride as i32, WlShmFormat::Argb8888);
    // Tell Way Cooler not to put this in the tree, treat as background
    // TODO Make this less hacky by actually giving way cooler access to this thing...
    shell_surface.set_class("Background".into());
    // TODO Actually give it the path or something idk
    shell_surface.set_title(format!("Image background yay"));

    // Attach the buffer to the surface
    background_surface.attach(Some(&background_buffer), 0, 0);
    background_surface.commit();
    background_surface.set_buffer_scale(1);
    background_surface.damage(0, 0, width as i32, height as i32);
    let cursor_surface = cursor_surface(cursor_path.as_str(), &env);
    main_background_loop(background_surface, cursor_surface, evt_iter, &env);

}

fn cursor_surface(cursor_path: &str, env: &WaylandEnv) -> WlSurface {
    let compositor = env.compositor.as_ref().map(|o| &o.0).unwrap();
    let shm = env.shm.as_ref().map(|o| &o.0).unwrap();

    let cursor_surface = compositor.create_surface();
    let cursor_theme = load_theme(None, 16, shm);
    /* If the theme has a predefined cursor, just use that */
    if let Some(cursor) = cursor_theme.get_cursor("default") {
        let cursor_buffer = cursor.frame_buffer(0).expect("Couldn't get frame_buffer");
        cursor_surface.attach(Some(&*cursor_buffer), 0, 0);
    } else {
        let cursor_file = File::open(cursor_path.clone())
            .unwrap_or_else(|_| {
                println!("Could not open \"{:?}\"", cursor_path);
                panic!("Could not open image file");
            });
        let image_reader = BufReader::new(cursor_file);
        // TODO support more formats, split into separate function
        let image = load(image_reader, ImageFormat::PNG)
            .expect("Image was not a png file!");
        let mut image = image.to_rgba();
        let width = image.width();
        let height = image.height();
        let stride = width * 4;
        let size = stride * height;
        // TODO Split this into its own function
        {
            let pixels = image.enumerate_pixels_mut();
            for (_x, _y, pixel) in pixels {
                let alpha = pixel[3] as u32;
                pixel[0] = weird_math(pixel[0], alpha);
                pixel[1] = weird_math(pixel[1], alpha);
                pixel[2] = weird_math(pixel[2], alpha);

                let tmp = pixel[2];
                pixel[2] = pixel[0];
                pixel[0] = tmp;
            }
        }
        let vec = image.into_vec();
        let mut tmp = tempfile::NamedTempFile::new().expect("Unable to create a tempfile.");
        tmp.set_len(size as u64).expect("Could not truncate length of file");
        tmp.write_all(&*vec).unwrap();
        let pool = shm.create_pool(tmp.as_raw_fd(), size as i32);
        let cursor_buffer = pool.create_buffer(0, width as i32, height as i32, stride as i32, WlShmFormat::Argb8888);
        cursor_surface.attach(Some(&cursor_buffer), 0, 0);
        ::std::mem::forget(cursor_buffer);
    }
    return cursor_surface
}

/// Main loop for rendering backgrounds.
/// Need to keep the surface alive, and update it if the
/// user wants to change the background.
#[allow(unused_variables)]
fn main_background_loop(background_surface: WlSurface, cursor_surface: WlSurface, mut event_iter: EventIterator, env: &WaylandEnv) {
    use wayland_client::wayland::WaylandProtocolEvent;
    use wayland_client::Event;
    let seat = env.seat.as_ref().map(|o| &o.0).unwrap();
    let mut pointer = seat.get_pointer();

    pointer.set_event_iterator(&event_iter);
    pointer.set_cursor(0, Some(&cursor_surface), 0, 0);
    background_surface.commit();
    event_iter.sync_roundtrip().unwrap();
    loop {
        for event in &mut event_iter {
            match event {
                Event::Wayland(wayland_event) => {
                    match wayland_event {
                        WaylandProtocolEvent::WlPointer(id, pointer_event) => {
                            match pointer_event {
                                WlPointerEvent::Enter(serial, background_surface, surface_x, surface_y) => {
                                    pointer.set_cursor(0, Some(&cursor_surface), 0, 0);
                                },
                                _ => {
                                }
                            }
                        },
                        _ => {/* unhandled events */}
                    }
                }
                _ => { /* unhandled events */ }
            }
        }
        event_iter.dispatch().expect("Connection with the compositor was lost.");
    }
}

#[test]
fn test_weird_math() {
    assert_eq!(weird_math(10, 254), 9);
    assert_eq!(weird_math(2, 255), 2);
    assert_eq!(weird_math(255, 500), 500);
}
<commit_msg>Cleanup of the cod<commit_after>#[macro_use]
extern crate wayland_client;

extern crate tempfile;

extern crate byteorder;
extern crate image;

use std::env;
use std::process::exit;
use std::io::{BufReader};
use image::{load, ImageFormat};
use std::mem::transmute;
use std::os::unix::io::AsRawFd;
use std::io::Write;
use std::fs::File;

use wayland_client::wayland::get_display;
use wayland_client::wayland::compositor::{WlCompositor, WlSurface};
use wayland_client::wayland::shell::WlShell;
use wayland_client::wayland::shm::{WlBuffer, WlShm, WlShmFormat};
use wayland_client::wayland::seat::{WlSeat, WlPointerEvent};
use wayland_client::cursor::load_theme;
use wayland_client::{EventIterator, Proxy};

use byteorder::{NativeEndian, WriteBytesExt};

wayland_env!(WaylandEnv,
             compositor: WlCompositor,
             shell: WlShell,
             shm: WlShm,
             seat: WlSeat
);

type BufferResult = Result<WlBuffer, ()>;

/// Hack to deal with edge case between the two cursor buffer types...
enum CursorBuffer {
    /// The buffer has been leaked into memory.
    /// This should eventually be fixed, but since it's just the cursor
    /// it's not a big issue.
    Null,
    /// The buffer for the cursor, this can be destroyed so it should last
    /// as long as the program (or until you replace it with another).
    Buf(WlBuffer)
}

#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
/// Holds the bytes to represent a colored background.
/// To be written into a wayland surface.
pub struct Color(pub [u8; 4]);

impl Color {
    /// Generate a new color out of a u32.
    /// E.G: 0xFFFFFF
    pub fn from_u32(color: u32) -> Self {
        unsafe { Color(transmute(color)) }
    }

    pub fn as_u32(&self) -> u32 {
        unsafe { transmute(self.0)}
    }
}

fn main() {
    let args: Vec<_> = env::args().collect();
    if args.len() < 3 {
        println!("Please supply either a file path or a color (written in hex)");
        println!("Please also supply a path to a cursor image");
        exit(1);
    }
    let input = &args[1];
    let cursor_path = &args[2];

    let (display, iter) = get_display()
        .expect("Unable to connect to a wayland compositor");
    let (env, evt_iter) = WaylandEnv::init(display, iter);
    let compositor = env.compositor.as_ref().map(|o| &o.0).unwrap();
    let mut background_surface = compositor.create_surface();

    // We need to hold on to this buffer, this holds the background image!
    let _background_buffer = if let Ok(color) = input.parse::<u32>() {
        let color = Color::from_u32(color);
        generate_solid_background(color, &mut background_surface, &env)
    } else {
        generate_image_background(input.clone(), &mut background_surface, &env)
    }.expect("could not generate image");
    let shell = env.shell.as_ref().map(|o| &o.0).unwrap();
    let shell_surface = shell.get_shell_surface(&background_surface);
    shell_surface.set_class("Background".into());
    // TODO Actually give it the path or something idk
    shell_surface.set_title(input.clone());

    background_surface.commit();
    background_surface.set_buffer_scale(1);
    let mut cursor_surface = compositor.create_surface();
    let _cursor_buffer = self::cursor_surface(cursor_path.as_str(), &mut cursor_surface, &env);
    main_background_loop(background_surface, cursor_surface, evt_iter, &env);
}

fn weird_math(num: u8, third_num: u32) -> u8 {
    let big_num = num as u32;
    ((big_num * third_num) / 255) as u8
}

/// Given a solid color, writes bytes associated with that color to
/// a special Wayland surface which is then rendered as a background for Way Cooler.
fn generate_solid_background(color: Color, background_surface: &mut WlSurface,
                                 env: &WaylandEnv) -> BufferResult {
    // Get shortcuts to the globals.
    let shm = env.shm.as_ref().map(|o| &o.0).unwrap();

    // Create the surface we are going to write into
    let mut tmp = tempfile::tempfile().ok().expect("Unable to create a tempfile.");

    // Calculate how big the buffer needs to be from the output resolution
    // TODO Get the output size from way cooler somehow...
    //let resolution = output.get_resolution()
    //    .expect("Couldn't get output resolution");
    //let (width, height) = (resolution.w as i32, resolution.h as i32);
    let width = 800; let height = 600;
    let size = (width * height) as i32;

    // Write in the color coding to the surface
    for _ in 0..size {
        unsafe {
            tmp.write_u32::<NativeEndian>(transmute(color.0))
                .expect("Could not write to file")
        }
    }
    tmp.flush()
        .expect("Could not flush buffer");

    // Create the buffer that is mem-mapped to the temp file descriptor
    let pool = shm.create_pool(tmp.as_raw_fd(), size);
    let background_buffer = pool.create_buffer(0, width, height, width, WlShmFormat::Argb8888);
    // Tell Way Cooler not to set put this in the tree, treat as background

    // Attach the buffer to the surface
    background_surface.attach(Some(&background_buffer), 0, 0);
    Ok(background_buffer)
}

/// Given the data from an image, writes it to a special Wayland surface
/// which is then rendered as a background for Way Cooler.
fn generate_image_background(path: String, background_surface: &mut WlSurface,
                                 env: &WaylandEnv) -> BufferResult {
    let image_file = File::open(path.clone())
        .unwrap_or_else(|_| {
            println!("Could not open \"{:?}\"", path);
            panic!("Could not open image file");
        });
    let image_reader = BufReader::new(image_file);
    // TODO support more formats, split into separate function
    let image = load(image_reader, ImageFormat::PNG)
        .expect("Image was not a png file!");
    let mut image = image.to_rgba();
    let width = image.width();
    let height = image.height();
    let stride = width * 4;
    let size = stride * height;
    // TODO Split this into its own function
    {
        let pixels = image.enumerate_pixels_mut();
        for (_x, _y, pixel) in pixels {
            let alpha = pixel[3] as u32;
            pixel[0] = weird_math(pixel[0], alpha);
            pixel[1] = weird_math(pixel[1], alpha);
            pixel[2] = weird_math(pixel[2], alpha);

            let tmp = pixel[2];
            pixel[2] = pixel[0];
            pixel[0] = tmp;
        }
    }
    let vec = image.into_vec();
    let mut tmp = tempfile::NamedTempFile::new().expect("Unable to create a tempfile.");
    tmp.set_len(size as u64).expect("Could not truncate length of file");
    tmp.write_all(&*vec).unwrap();

    // TODO I want this shit outta here
    let shm = env.shm.as_ref().map(|o| &o.0).unwrap();

    // Create the surface we are going to write into

    let pool = shm.create_pool(tmp.as_raw_fd(), size as i32);
    let background_buffer = pool.create_buffer(0, width as i32, height as i32, stride as i32, WlShmFormat::Argb8888);
    // Tell Way Cooler not to put this in the tree, treat as background
    // TODO Make this less hacky by actually giving way cooler access to this thing...

    // Attach the buffer to the surface
    background_surface.attach(Some(&background_buffer), 0, 0);
    background_surface.damage(0, 0, width as i32, height as i32);
    Ok(background_buffer)
}

fn cursor_surface(cursor_path: &str, cursor_surface: &mut WlSurface, env: &WaylandEnv)
                  -> Result<CursorBuffer, ()> {
    let shm = env.shm.as_ref().map(|o| &o.0).unwrap();

    let cursor_theme = load_theme(None, 16, shm);
    /* If the theme has a predefined cursor, just use that */
    let cursor_buffer: WlBuffer;
    if let Some(cursor) = cursor_theme.get_cursor("default") {
        let cursor_frame_buffer = &*cursor.frame_buffer(0).expect("Couldn't get frame_buffer");
        cursor_surface.attach(Some(cursor_frame_buffer), 0, 0);
        ::std::mem::forget(cursor_frame_buffer);
        return Ok(CursorBuffer::Null)
    } else {
        let cursor_file = File::open(cursor_path.clone())
            .unwrap_or_else(|_| {
                println!("Could not open \"{:?}\"", cursor_path);
                panic!("Could not open image file");
            });
        let image_reader = BufReader::new(cursor_file);
        // TODO support more formats, split into separate function
        let image = load(image_reader, ImageFormat::PNG)
            .expect("Image was not a png file!");
        let mut image = image.to_rgba();
        let width = image.width();
        let height = image.height();
        let stride = width * 4;
        let size = stride * height;
        // TODO Split this into its own function
        {
            let pixels = image.enumerate_pixels_mut();
            for (_x, _y, pixel) in pixels {
                let alpha = pixel[3] as u32;
                pixel[0] = weird_math(pixel[0], alpha);
                pixel[1] = weird_math(pixel[1], alpha);
                pixel[2] = weird_math(pixel[2], alpha);

                let tmp = pixel[2];
                pixel[2] = pixel[0];
                pixel[0] = tmp;
            }
        }
        let vec = image.into_vec();
        let mut tmp = tempfile::NamedTempFile::new().expect("Unable to create a tempfile.");
        tmp.set_len(size as u64).expect("Could not truncate length of file");
        tmp.write_all(&*vec).unwrap();
        let pool = shm.create_pool(tmp.as_raw_fd(), size as i32);
        cursor_buffer = pool.create_buffer(0, width as i32, height as i32, stride as i32, WlShmFormat::Argb8888);
        cursor_surface.attach(Some(&cursor_buffer), 0, 0);
    }
    Ok(CursorBuffer::Buf(cursor_buffer))
}

/// Main loop for rendering backgrounds.
/// Need to keep the surface alive, and update it if the
/// user wants to change the background.
#[allow(unused_variables)]
fn main_background_loop(background_surface: WlSurface, cursor_surface: WlSurface, mut event_iter: EventIterator, env: &WaylandEnv) {
    use wayland_client::wayland::WaylandProtocolEvent;
    use wayland_client::Event;
    let seat = env.seat.as_ref().map(|o| &o.0).unwrap();
    let mut pointer = seat.get_pointer();

    pointer.set_event_iterator(&event_iter);
    pointer.set_cursor(0, Some(&cursor_surface), 0, 0);
    background_surface.commit();
    event_iter.sync_roundtrip().unwrap();
    loop {
        for event in &mut event_iter {
            match event {
                Event::Wayland(wayland_event) => {
                    match wayland_event {
                        WaylandProtocolEvent::WlPointer(id, pointer_event) => {
                            match pointer_event {
                                WlPointerEvent::Enter(serial, background_surface, surface_x, surface_y) => {
                                    pointer.set_cursor(0, Some(&cursor_surface), 0, 0);
                                },
                                _ => {
                                }
                            }
                        },
                        _ => {/* unhandled events */}
                    }
                }
                _ => { /* unhandled events */ }
            }
        }
        event_iter.dispatch().expect("Connection with the compositor was lost.");
    }
}

#[test]
fn test_weird_math() {
    assert_eq!(weird_math(10, 254), 9);
    assert_eq!(weird_math(2, 255), 2);
    assert_eq!(weird_math(255, 500), 500);
}
<|endoftext|>


<commit_before>extern crate combine;

use std::io::prelude::*;

use combine::{between, many1, digit, letter, spaces, string, try, choice, parser, Parser, ParserExt, ParseError};
use combine::char as combine_char;
use combine::primitives::{Stream, State, ParseResult};

#[derive(Debug, PartialEq)]
enum LispVal {
    Atom(String),
    Bool(bool),
    Number(i32),
    Character(char),
    String(String)
}

fn symbol<I>(input: State<I>) -> ParseResult<String, I>
where I: Stream<Item=char>
{
    let lex_char = |c| combine_char(c).skip(spaces());

    let symbols = choice([lex_char('!'), lex_char('$'), lex_char('%'), lex_char('$'), lex_char('&'),
                    lex_char('|'), lex_char('*'), lex_char('+'), lex_char('-'), lex_char(':'),
                    lex_char('<'), lex_char('='), lex_char('>'), lex_char('?'), lex_char('@'),
                    lex_char('^'), lex_char('_'), lex_char('~')]);

    symbols.map(|c: char| {
            let mut s = String::new();
            s.push(c);
            return s;
        })
        .parse_state(input)
}

fn bool<I>(input: State<I>) -> ParseResult<bool, I>
where I: Stream<Item=char>
{
    let bewl = try(string("#t").map(|_| return true)).or(
            string("#f").map(|_| return false)
        )
       .parse_state(input);

    bewl
}

fn atom<I>(input: State<I>) -> ParseResult<String, I>
where I: Stream<Item=char>
{
    let atom = (parser(symbol::<I>))
        .parse_state(input);

    atom
}

fn string_parser<I>(input: State<I>) -> ParseResult<String, I>
where I: Stream<Item=char>
{
    let string = between(combine_char('"'), combine_char('"'), many1(letter()))
        .parse_state(input);
    string
}

fn char_parser<I>(input: State<I>) -> ParseResult<char, I>
where I: Stream<Item=char>
{
    let number = between(combine_char('\''), combine_char('\''), letter())
        .parse_state(input);
    number
}

fn number<I>(input: State<I>) -> ParseResult<i32, I>
where I: Stream<Item=char>
{
    let number = many1(digit())
        .map(|string: String| string.parse::<i32>().unwrap())
        .parse_state(input);
    number
}

fn rusty_lisp<I>(input: State<I>) -> ParseResult<LispVal, I>
where I: Stream<Item=char>
{
    let lispval = parser(atom::<I>)
                   .map(LispVal::Atom)
        .or(parser(bool::<I>)
            .map(LispVal::Bool))
        .or(parser(number::<I>)
            .map(LispVal::Number))
        .or(parser(char_parser::<I>)
            .map(LispVal::Character))
        .or(parser(string_parser::<I>)
            .map(LispVal::String))
        .parse_state(input);

    lispval
}

fn parse(program: String) {
    let program = program.as_ref();

    let result: Result<(LispVal, &str), ParseError<&str>> = parser(rusty_lisp).parse(program);
    match result {
        Ok((value, _remaining_input)) => println!("{:?}", value),
        Err(err) => println!("{}", err)
    }
}

fn main() {
    println!("rusty-lisp v0.1.0");
    println!("Press 'Ctrl+C' to exit");
    println!("");

    let mut stdin = std::io::stdin();
    let mut input = String::new();

    stdin.read_to_string(&mut input).unwrap();
    parse(input);

    println!("Thanks for using!");
}
<commit_msg>add "parse_" to functions to decrease confusion<commit_after>extern crate combine;

use std::io::prelude::*;

use combine::{between, many1, digit, letter, spaces, string, try, choice, parser, Parser, ParserExt, ParseError};
use combine::char as combine_char;
use combine::primitives::{Stream, State, ParseResult};

#[derive(Debug, PartialEq)]
enum LispVal {
    Atom(String),
    Bool(bool),
    Number(i32),
    Character(char),
    String(String)
}

fn parse_symbol<I>(input: State<I>) -> ParseResult<String, I>
where I: Stream<Item=char>
{
    let lex_char = |c| combine_char(c).skip(spaces());

    let symbols = choice([lex_char('!'), lex_char('$'), lex_char('%'), lex_char('$'), lex_char('&'),
                    lex_char('|'), lex_char('*'), lex_char('+'), lex_char('-'), lex_char(':'),
                    lex_char('<'), lex_char('='), lex_char('>'), lex_char('?'), lex_char('@'),
                    lex_char('^'), lex_char('_'), lex_char('~')]);

    symbols.map(|c: char| {
            let mut s = String::new();
            s.push(c);
            return s;
        })
        .parse_state(input)
}

fn parse_bool<I>(input: State<I>) -> ParseResult<bool, I>
where I: Stream<Item=char>
{
    let bewl = try(string("#t").map(|_| return true)).or(
            string("#f").map(|_| return false)
        )
       .parse_state(input);

    bewl
}

fn parse_atom<I>(input: State<I>) -> ParseResult<String, I>
where I: Stream<Item=char>
{
    let atom = (parser(parse_symbol::<I>))
        .parse_state(input);

    atom
}

fn parse_string<I>(input: State<I>) -> ParseResult<String, I>
where I: Stream<Item=char>
{
    let string = between(combine_char('"'), combine_char('"'), many1(letter()))
        .parse_state(input);
    string
}

fn parse_char<I>(input: State<I>) -> ParseResult<char, I>
where I: Stream<Item=char>
{
    between(combine_char('\''), combine_char('\''), letter())
        .parse_state(input)
}

fn parse_number<I>(input: State<I>) -> ParseResult<i32, I>
where I: Stream<Item=char>
{
    let number = many1(digit())
        .map(|string: String| string.parse::<i32>().unwrap())
        .parse_state(input);
    number
}

fn rusty_lisp<I>(input: State<I>) -> ParseResult<LispVal, I>
where I: Stream<Item=char>
{
    let lispval = parser(parse_atom::<I>)
                   .map(LispVal::Atom)
        .or(parser(parse_bool::<I>)
            .map(LispVal::Bool))
        .or(parser(parse_number::<I>)
            .map(LispVal::Number))
        .or(parser(parse_char::<I>)
            .map(LispVal::Character))
        .or(parser(parse_string::<I>)
            .map(LispVal::String))
        .parse_state(input);

    lispval
}

fn parse(program: String) {
    let program = program.as_ref();

    let result: Result<(LispVal, &str), ParseError<&str>> = parser(rusty_lisp).parse(program);
    match result {
        Ok((value, _remaining_input)) => println!("{:?}", value),
        Err(err) => println!("{}", err)
    }
}

fn main() {
    println!("rusty-lisp v0.1.0");
    println!("Press 'Ctrl+C' to exit");
    println!("");

    let mut stdin = std::io::stdin();
    let mut input = String::new();

    stdin.read_to_string(&mut input).unwrap();
    parse(input);

    println!("Thanks for using!");
}
<|endoftext|>


<commit_before>extern crate nalgebra as na;
extern crate rustracer as rt;
extern crate docopt;
extern crate rustc_serialize;
extern crate num_cpus;

use std::f32::consts;
use std::sync::Arc;
use std::num::ParseIntError;
use docopt::Docopt;

use rt::{Point, Vector, Transform, Dim};
use rt::scene::Scene;
use rt::spectrum::Spectrum;
use rt::camera::Camera;
use rt::integrator::{SamplerIntegrator, Whitted, Normal, AmbientOcclusion};
use rt::light::{Light, PointLight, DistantLight, DiffuseAreaLight};
use rt::material::matte::MatteMaterial;
use rt::material::plastic::Plastic;
use rt::primitive::{Primitive, GeometricPrimitive};
use rt::shapes::disk::Disk;
use rt::shapes::sphere::Sphere;
use rt::renderer;

const USAGE: &'static str =
    "
Toy Ray-Tracer in Rust

Usage:
  rustracer [options]

Options:
  -h, --help                                  Show this screen.
  -o <file>, --output=<file>                  \
     Output file name [default: image.png].
  -t N, --threads=N                           Number \
     of worker threads to start.
  -d <dim>, --dimension=<dim>                 \
     Dimension of the output image [default: 800x600].
  --spp N                                     \
     Samples per pixel [default: 4].
  --block-size=N                              Block size \
     used for rendering [default: 32].
  -i <integrator>, --integrator=<integrator>  SamplerIntegrator \
     to use [default: whitted].
                                              Valid values: \
     whitted, normal, ao.
  --whitted-max-ray-depth=N                   Maximum ray depth for \
     Whitted integrator. [default: 8].
  --ao-samples=N                              Number of \
     samples for ambient occlusion integrator [default: 16].
";

#[derive(RustcDecodable)]
struct Args {
    flag_output: String,
    flag_threads: Option<usize>,
    flag_integrator: SamplerIntegratorType,
    flag_whitted_max_ray_depth: u8,
    flag_ao_samples: usize,
    flag_dimension: String,
    flag_spp: usize,
    flag_block_size: usize,
}

#[derive(RustcDecodable)]
enum SamplerIntegratorType {
    Whitted,
    Ao,
    Normal,
}

fn main() {
    // Parse args
    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.help(true).decode())
        .unwrap_or_else(|e| e.exit());

    let parsed_dims: Result<Vec<usize>, ParseIntError> =
        args.flag_dimension.split('x').map(|s| s.parse::<usize>()).collect();
    let dims = parsed_dims.expect("Invalid dimensions");
    if dims.len() != 2 {
        panic!("Error: invalid dimension specification: {}",
               args.flag_dimension);

    }
    let dim = (dims[0], dims[1]);

    let scene = bunny_buddah(dim, &args);

    let start_time = std::time::Instant::now();
    let stats = renderer::render(Arc::new(scene),
                                 dim,
                                 &args.flag_output,
                                 args.flag_threads.unwrap_or_else(num_cpus::get),
                                 args.flag_spp,
                                 args.flag_block_size);
    let duration = start_time.elapsed();
    println!("Render time                : {}", duration.human_display());
    println!("Primary rays               : {}", stats.primary_rays);
    println!("Secondary rays             : {}", stats.secondary_rays);
    println!("Num triangles              : {}", stats.triangles);
    println!("Ray-triangle tests         : {}", stats.ray_triangle_tests);
    println!("Ray-triangle intersections : {}", stats.ray_triangle_isect);
    println!("Fast bounding-box test     : {}", stats.fast_bbox_isect);
}

fn bunny_buddah(dim: Dim, args: &Args) -> Scene {
    let camera = Camera::new(Point::new(0.0, 0.0, 5.0), dim, 50.0);
    let integrator: Box<SamplerIntegrator + Send + Sync> = match args.flag_integrator {
        SamplerIntegratorType::Whitted => {
            println!("Using Whitted integrator with max ray depth of {}",
                     args.flag_whitted_max_ray_depth);
            Box::new(Whitted::new(args.flag_whitted_max_ray_depth))
        }
        SamplerIntegratorType::Ao => {
            println!("Using Ambient Occlusion integrator with {} samples",
                     args.flag_ao_samples);
            Box::new(AmbientOcclusion::new(args.flag_ao_samples))
        }
        SamplerIntegratorType::Normal => {
            println!("Using normal facing ratio integrator");
            Box::new(Normal {})
        }
    };

    let mut lights: Vec<Arc<Light + Send + Sync>> = Vec::new();

    let disk = Arc::new(Disk::new(-2.0,
                                  1.0,
                                  0.0,
                                  360.0,
                                  Transform::new(na::zero(),
                                                 Vector::new(consts::FRAC_PI_2, 0.0, 0.0),
                                                 1.0)));
    let area_light =
        Arc::new(DiffuseAreaLight::new(Spectrum::rgb(1.0, 1.0, 1.0), disk.clone(), 16));
    let area_light_prim = Box::new(GeometricPrimitive {
        shape: disk.clone(),
        area_light: Some(area_light.clone()),
        material: Some(Arc::new(MatteMaterial::default())),
    });

    let primitives: Vec<Box<Primitive + Sync + Send>> =
        vec![Box::new(GeometricPrimitive {
                 shape: Arc::new(Sphere::default()),
                 area_light: None,
                 material: Some(Arc::new(MatteMaterial::new(Spectrum::red(), 0.0))), /* material: Some(Arc::new(MatteMaterial::new(Spectrum::red(), 20.0))), */
             }),
             Box::new(GeometricPrimitive {
                 shape: Arc::new(Disk::new(-1.0,
                                           20.0,
                                           0.0,
                                           360.0,
                                           Transform::new(na::zero(),
                                                          Vector::new(-consts::PI / 2.0,
                                                                      0.0,
                                                                      0.0),
                                                          1.0))),
                 area_light: None,
                 // material: Some(Arc::new(MatteMaterial::checkerboard(0.0))),
                 material: Some(Arc::new(MatteMaterial::new(Spectrum::blue(), 0.0))),
             }),
             area_light_prim];
    // Light
    lights.push(area_light);
    // lights.push(Box::new(PointLight::new(Point::new(-3.0, 3.0, 3.0),
    //                                      Spectrum::rgb(100.0, 100.0, 100.0))));
    // lights.push(Arc::new(DistantLight::new(-Vector::y() - Vector::z(),
    //                                        Spectrum::rgb(1.0, 1.0, 1.0))));

    Scene::new(camera, integrator, primitives, lights)
}

trait HumanDisplay {
    fn human_display(&self) -> String;
}
impl HumanDisplay for std::time::Duration {
    fn human_display(&self) -> String {
        let mut hours = 0;
        let mut minutes = 0;
        let mut seconds = self.as_secs();
        if seconds >= 60 {
            seconds %= 60;
            minutes = seconds / 60;
        }
        if minutes >= 60 {
            minutes %= 60;
            hours = minutes / 60;
        }
        let millis = self.subsec_nanos() / 1000000;
        format!("{}:{}:{}.{}", hours, minutes, seconds, millis)
    }
}
<commit_msg>Tweak test scene a little bit<commit_after>extern crate nalgebra as na;
extern crate rustracer as rt;
extern crate docopt;
extern crate rustc_serialize;
extern crate num_cpus;

use std::f32::consts;
use std::sync::Arc;
use std::num::ParseIntError;
use docopt::Docopt;

use rt::{Point, Vector, Transform, Dim};
use rt::scene::Scene;
use rt::spectrum::Spectrum;
use rt::camera::Camera;
use rt::integrator::{SamplerIntegrator, Whitted, Normal, AmbientOcclusion};
use rt::light::{Light, PointLight, DistantLight, DiffuseAreaLight};
use rt::material::matte::MatteMaterial;
use rt::material::plastic::Plastic;
use rt::primitive::{Primitive, GeometricPrimitive};
use rt::shapes::disk::Disk;
use rt::shapes::sphere::Sphere;
use rt::renderer;

const USAGE: &'static str =
    "
Toy Ray-Tracer in Rust

Usage:
  rustracer [options]

Options:
  -h, --help                                  Show this screen.
  -o <file>, --output=<file>                  \
     Output file name [default: image.png].
  -t N, --threads=N                           Number \
     of worker threads to start.
  -d <dim>, --dimension=<dim>                 \
     Dimension of the output image [default: 800x600].
  --spp N                                     \
     Samples per pixel [default: 4].
  --block-size=N                              Block size \
     used for rendering [default: 32].
  -i <integrator>, --integrator=<integrator>  SamplerIntegrator \
     to use [default: whitted].
                                              Valid values: \
     whitted, normal, ao.
  --whitted-max-ray-depth=N                   Maximum ray depth for \
     Whitted integrator. [default: 8].
  --ao-samples=N                              Number of \
     samples for ambient occlusion integrator [default: 16].
";

#[derive(RustcDecodable)]
struct Args {
    flag_output: String,
    flag_threads: Option<usize>,
    flag_integrator: SamplerIntegratorType,
    flag_whitted_max_ray_depth: u8,
    flag_ao_samples: usize,
    flag_dimension: String,
    flag_spp: usize,
    flag_block_size: usize,
}

#[derive(RustcDecodable)]
enum SamplerIntegratorType {
    Whitted,
    Ao,
    Normal,
}

fn main() {
    // Parse args
    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.help(true).decode())
        .unwrap_or_else(|e| e.exit());

    let parsed_dims: Result<Vec<usize>, ParseIntError> =
        args.flag_dimension.split('x').map(|s| s.parse::<usize>()).collect();
    let dims = parsed_dims.expect("Invalid dimensions");
    if dims.len() != 2 {
        panic!("Error: invalid dimension specification: {}",
               args.flag_dimension);

    }
    let dim = (dims[0], dims[1]);

    let scene = bunny_buddah(dim, &args);

    let start_time = std::time::Instant::now();
    let stats = renderer::render(Arc::new(scene),
                                 dim,
                                 &args.flag_output,
                                 args.flag_threads.unwrap_or_else(num_cpus::get),
                                 args.flag_spp,
                                 args.flag_block_size);
    let duration = start_time.elapsed();
    println!("Render time                : {}", duration.human_display());
    println!("Primary rays               : {}", stats.primary_rays);
    println!("Secondary rays             : {}", stats.secondary_rays);
    println!("Num triangles              : {}", stats.triangles);
    println!("Ray-triangle tests         : {}", stats.ray_triangle_tests);
    println!("Ray-triangle intersections : {}", stats.ray_triangle_isect);
    println!("Fast bounding-box test     : {}", stats.fast_bbox_isect);
}

fn bunny_buddah(dim: Dim, args: &Args) -> Scene {
    let camera = Camera::new(Point::new(0.0, 0.0, 5.0), dim, 50.0);
    let integrator: Box<SamplerIntegrator + Send + Sync> = match args.flag_integrator {
        SamplerIntegratorType::Whitted => {
            println!("Using Whitted integrator with max ray depth of {}",
                     args.flag_whitted_max_ray_depth);
            Box::new(Whitted::new(args.flag_whitted_max_ray_depth))
        }
        SamplerIntegratorType::Ao => {
            println!("Using Ambient Occlusion integrator with {} samples",
                     args.flag_ao_samples);
            Box::new(AmbientOcclusion::new(args.flag_ao_samples))
        }
        SamplerIntegratorType::Normal => {
            println!("Using normal facing ratio integrator");
            Box::new(Normal {})
        }
    };

    let mut lights: Vec<Arc<Light + Send + Sync>> = Vec::new();

    let disk = Arc::new(Disk::new(-2.0,
                                  0.5,
                                  0.0,
                                  360.0,
                                  Transform::new(na::zero(),
                                                 Vector::new(consts::FRAC_PI_2, 0.0, 0.0),
                                                 1.0)));
    let area_light =
        Arc::new(DiffuseAreaLight::new(Spectrum::rgb(2.0, 2.0, 2.0), disk.clone(), 16));
    let area_light_prim = Box::new(GeometricPrimitive {
        shape: disk.clone(),
        area_light: Some(area_light.clone()),
        material: Some(Arc::new(MatteMaterial::default())),
    });

    let primitives: Vec<Box<Primitive + Sync + Send>> = vec![Box::new(GeometricPrimitive {
                 shape: Arc::new(Sphere::default()),
                 area_light: None,
                 material: Some(Arc::new(Plastic::new(Spectrum::red(), Spectrum::white()))),
             }),
             Box::new(GeometricPrimitive {
                 shape: Arc::new(Disk::new(-1.0,
                                           20.0,
                                           0.0,
                                           360.0,
                                           Transform::new(na::zero(),
                                                          Vector::new(-consts::PI / 2.0,
                                                                      0.0,
                                                                      0.0),
                                                          1.0))),
                 area_light: None,
                 // material: Some(Arc::new(MatteMaterial::checkerboard(0.0))),
                 material: Some(Arc::new(MatteMaterial::new(Spectrum::white(), 0.0))),
             }),
             area_light_prim];
    // Light
    lights.push(area_light);
    // lights.push(Box::new(PointLight::new(Point::new(-3.0, 3.0, 3.0),
    //                                      Spectrum::rgb(100.0, 100.0, 100.0))));
    lights.push(Arc::new(DistantLight::new(Vector::new(0.0, -1.0, -5.0),
                                           Spectrum::rgb(1.0, 1.0, 1.0))));

    Scene::new(camera, integrator, primitives, lights)
}

trait HumanDisplay {
    fn human_display(&self) -> String;
}
impl HumanDisplay for std::time::Duration {
    fn human_display(&self) -> String {
        let mut hours = 0;
        let mut minutes = 0;
        let mut seconds = self.as_secs();
        if seconds >= 60 {
            seconds %= 60;
            minutes = seconds / 60;
        }
        if minutes >= 60 {
            minutes %= 60;
            hours = minutes / 60;
        }
        let millis = self.subsec_nanos() / 1000000;
        format!("{}:{}:{}.{}", hours, minutes, seconds, millis)
    }
}
<|endoftext|>


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

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

use clog::{LinkStyle, Clog};
use clog::fmt::ChangelogFormat;

#[macro_use]
mod macros;
mod error;
mod fmt;

use error::CliError;

pub type CliResult<T> = Result<T, CliError>;
const CLOG_CONFIG_FILE: &'static str = ".clog.toml";

fn main() {
    let styles = LinkStyle::variants();
    let matches = App::new("clog")
        // Pull version from Cargo.toml
        .version(&format!("v{}", crate_version!())[..])
        .about("a conventional changelog for the rest of us")
        .args_from_usage("-r, --repository=[repo]     'Repository used for generating commit and issue links{n}\
                                                       (without the .git, e.g. https://github.com/thoughtram/clog)'
                          -f, --from=[from]           'e.g. 12a8546'
                          -T, --format=[format]       'The output format, defaults to markdown{n}\
                                                       (valid values: markdown, json)'
                          -M, --major                 'Increment major version by one (Sets minor and patch to 0)'
                          -g, --git-dir=[gitdir]      'Local .git directory (defaults to current dir + \'.git\')*'
                          -w, --work-tree=[workdir]   'Local working tree of the git project{n}\
                                                       (defaults to current dir)*'
                          -m, --minor                 'Increment minor version by one (Sets patch to 0)'
                          -p, --patch                 'Increment patch version by one'
                          -s, --subtitle=[subtitle]   'e.g. \"Crazy Release Title\"'
                          -t, --to=[to]               'e.g. 8057684 (Defaults to HEAD when omitted)'
                          -o, --outfile=[outfile]     'Where to write the changelog (Defaults to stdout when omitted)'
                          -c, --config=[config]       'The Clog Configuration TOML file to use (Defaults to{n}\
                                                       \'.clog.toml\')**'
                          -i, --infile=[infile]       'A changelog to append to, but *NOT* write to (Useful in{n}\
                                                       conjunction with --outfile)'
                          --setversion=[ver]          'e.g. 1.0.1'")
        // Because --from-latest-tag can't be used with --from, we add it seperately so we can
        // specify a .conflicts_with()
        .arg(Arg::from_usage("-F, --from-latest-tag 'use latest tag as start (instead of --from)'")
                .conflicts_with("from"))
        // Because we may want to add more "flavors" at a later date, we can automate the process
        // of enumerating all possible values with clap
        .arg(Arg::from_usage("-l, --link-style=[style]     'The style of repository link to generate{n}(Defaults to github)'")
            .possible_values(&styles))
        // Because no one should use --changelog and either an --infile or --outfile, we add those
        // to conflicting lists
        .arg(Arg::from_usage("-C, --changelog=[changelog] 'A previous changelog to prepend new changes to (this is like{n}\
                                                           using the same file for both --infile and --outfile and{n}\
                                                           should not be used in conjuction with either)'")
            .conflicts_with("infile")
            .conflicts_with("outfile"))
        // Since --setversion shouldn't be used with any of the --major, --minor, or --match, we
        // set those as exclusions
        .arg_group(ArgGroup::with_name("setver")
                .add_all(&["major", "minor", "patch", "ver"]))
        .after_help("\
* If your .git directory is a child of your project directory (most common, such as\n\
/myproject/.git) AND not in the current working directory (i.e you need to use --work-tree or\n\
--git-dir) you only need to specify either the --work-tree (i.e. /myproject) OR --git-dir (i.e. \n\
/myproject/.git), you don't need to use both.\n\n\

** If using the --config to specify a clog configuration TOML file NOT in the current working\n\
directory (meaning you need to use --work-tree or --git-dir) AND the TOML file is inside your\n\
project directory (i.e. /myproject/.clog.toml) you do not need to use --work-tree or --git-dir.")
        .get_matches();

    let start_nsec = time::get_time().nsec;

    let clog = from_matches(&matches).unwrap_or_else(|e| e.exit());

    if let Some(ref file) = clog.outfile {
        clog.write_changelog_to(file).unwrap_or_else(|e| e.exit());

        let end_nsec = time::get_time().nsec;
        let elapsed_mssec = (end_nsec - start_nsec) / 1000000;
        println!("changelog written. (took {} ms)", elapsed_mssec);
    } else {
        clog.write_changelog().unwrap_or_else(|e| e.exit());
    }
}

/// Creates a `Clog` struct from command line `clap::ArgMatches`
///
/// # Example
///
/// ```ignore
/// # use clog::Clog;
///
/// let matches = // clap settings...
///
/// let clog = Clog::from_matches(matches).unwrap_or_else(|e| {
///     e.exit();
/// });
/// ```
pub fn from_matches(matches: &ArgMatches) -> CliResult<Clog> {
    debugln!("Creating clog from matches");
    let mut clog = if let Some(cfg) = matches.value_of("config") {
        debugln!("User passed in config file: {:?}", cfg);
        if matches.is_present("workdir") && matches.is_present("gitdir") {
            debugln!("User passed in both\n\tworking dir: {:?}\n\tgit dir: {:?}",
                     matches.value_of("workdir"),
                     matches.value_of("gitdir"));
           // use --config --work-tree --git-dir
            try!(Clog::with_all(matches.value_of("gitdir").unwrap(),
                                matches.value_of("workdir").unwrap(),
                                cfg))
        } else if let Some(dir) = matches.value_of("workdir") {
            debugln!("User passed in working dir: {:?}", dir);
           // use --config --work-tree
            try!(Clog::with_dir_and_file(dir, cfg))
        } else if let Some(dir) = matches.value_of("gitdir") {
            debugln!("User passed in git dir: {:?}", dir);
           // use --config --git-dir
            try!(Clog::with_dir_and_file(dir, cfg))
        } else {
            debugln!("User only passed config");
           // use --config only
            try!(Clog::from_file(cfg))
        }
    } else {
        debugln!("User didn't pass in a config");
        if matches.is_present("gitdir") && matches.is_present("workdir") {
            let wdir = matches.value_of("workdir").unwrap();
            let gdir = matches.value_of("gitdir").unwrap();
            debugln!("User passed in both\n\tworking dir: {:?}\n\tgit dir: {:?}",
                     wdir,
                     gdir);
            try!(Clog::with_dirs(gdir, wdir))
        } else if let Some(dir) = matches.value_of("gitdir") {
            debugln!("User passed in git dir: {:?}", dir);
            try!(Clog::with_dir(dir))
        } else if let Some(dir) = matches.value_of("workdir") {
            debugln!("User passed in working dir: {:?}", dir);
            try!(Clog::with_dir(dir))
        } else {
            debugln!("Trying the default config file");
            try!(Clog::from_file(CLOG_CONFIG_FILE))
        }
    };

    // compute version early, so we can exit on error
    clog.version = {
        // less typing later...
        let (major, minor, patch) = (matches.is_present("major"),
                                     matches.is_present("minor"),
                                     matches.is_present("patch"));
        if matches.is_present("ver") {
            matches.value_of("ver").unwrap().to_owned()
        } else if major || minor || patch {
            let mut had_v = false;
            let v_string = clog.get_latest_tag_ver();
            let first_char = v_string.chars().nth(0).unwrap_or(' ');
            let v_slice = if first_char == 'v' || first_char == 'V' {
                had_v = true;
                v_string.trim_left_matches(|c| c == 'v' || c == 'V')
            } else {
                &v_string[..]
            };
            match semver::Version::parse(v_slice) {
                Ok(ref mut v) => {
                    // if-else may be quicker, but it's longer mentally, and this isn't slow
                    match (major, minor, patch) {
                        (true,_,_) => {
                            v.major += 1;
                            v.minor = 0;
                            v.patch = 0;
                        }
                        (_,true,_) => {
                            v.minor += 1;
                            v.patch = 0;
                        }
                        (_,_,true) => {
                            v.patch += 1;
                            clog.patch_ver = true;
                        }
                        _ => unreachable!(),
                    }
                    format!("{}{}",
                            if had_v {
                                "v"
                            } else {
                                ""
                            },
                            v)
                }
                Err(e) => {
                    return Err(CliError::Semver(Box::new(e),
                                                String::from("Failed to parse version into \
                                                              valid SemVer. Ensure the version \
                                                              is in the X.Y.Z format.")));
                }
            }
        } else {
            clog.version
        }
    };

    if let Some(from) = matches.value_of("from") {
        clog.from = from.to_owned();
    } else if matches.is_present("from-latest-tag") {
        clog.from = clog.get_latest_tag();
    }

    if let Some(repo) = matches.value_of("repo") {
        clog.repo = repo.to_owned();
    }

    if matches.is_present("link-style") {
        clog.link_style = value_t!(matches.value_of("link-style"), LinkStyle)
                              .unwrap_or(LinkStyle::Github);
    }

    if let Some(subtitle) = matches.value_of("subtitle") {
        clog.subtitle = subtitle.to_owned();
    }

    if let Some(file) = matches.value_of("outfile") {
        clog.outfile = Some(file.to_owned());
    }

    if let Some(file) = matches.value_of("infile") {
        clog.infile = Some(file.to_owned());
    }

    if let Some(file) = matches.value_of("changelog") {
        clog.infile = Some(file.to_owned());
        clog.outfile = Some(file.to_owned());
    }

    if matches.is_present("format") {
        clog.out_format = value_t_or_exit!(matches.value_of("format"), ChangelogFormat);
    }

    debugln!("Returning clog:\n{:?}", clog);

    Ok(clog)
}
<commit_msg>fix: Support --to option<commit_after>#[macro_use]
extern crate clap;
extern crate time;
extern crate clog;
extern crate semver;
#[cfg(feature = "color")]
extern crate ansi_term;

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

use clog::{LinkStyle, Clog};
use clog::fmt::ChangelogFormat;

#[macro_use]
mod macros;
mod error;
mod fmt;

use error::CliError;

pub type CliResult<T> = Result<T, CliError>;
const CLOG_CONFIG_FILE: &'static str = ".clog.toml";

fn main() {
    let styles = LinkStyle::variants();
    let matches = App::new("clog")
        // Pull version from Cargo.toml
        .version(&format!("v{}", crate_version!())[..])
        .about("a conventional changelog for the rest of us")
        .args_from_usage("-r, --repository=[repo]     'Repository used for generating commit and issue links{n}\
                                                       (without the .git, e.g. https://github.com/thoughtram/clog)'
                          -f, --from=[from]           'e.g. 12a8546'
                          -T, --format=[format]       'The output format, defaults to markdown{n}\
                                                       (valid values: markdown, json)'
                          -M, --major                 'Increment major version by one (Sets minor and patch to 0)'
                          -g, --git-dir=[gitdir]      'Local .git directory (defaults to current dir + \'.git\')*'
                          -w, --work-tree=[workdir]   'Local working tree of the git project{n}\
                                                       (defaults to current dir)*'
                          -m, --minor                 'Increment minor version by one (Sets patch to 0)'
                          -p, --patch                 'Increment patch version by one'
                          -s, --subtitle=[subtitle]   'e.g. \"Crazy Release Title\"'
                          -t, --to=[to]               'e.g. 8057684 (Defaults to HEAD when omitted)'
                          -o, --outfile=[outfile]     'Where to write the changelog (Defaults to stdout when omitted)'
                          -c, --config=[config]       'The Clog Configuration TOML file to use (Defaults to{n}\
                                                       \'.clog.toml\')**'
                          -i, --infile=[infile]       'A changelog to append to, but *NOT* write to (Useful in{n}\
                                                       conjunction with --outfile)'
                          --setversion=[ver]          'e.g. 1.0.1'")
        // Because --from-latest-tag can't be used with --from, we add it seperately so we can
        // specify a .conflicts_with()
        .arg(Arg::from_usage("-F, --from-latest-tag 'use latest tag as start (instead of --from)'")
                .conflicts_with("from"))
        // Because we may want to add more "flavors" at a later date, we can automate the process
        // of enumerating all possible values with clap
        .arg(Arg::from_usage("-l, --link-style=[style]     'The style of repository link to generate{n}(Defaults to github)'")
            .possible_values(&styles))
        // Because no one should use --changelog and either an --infile or --outfile, we add those
        // to conflicting lists
        .arg(Arg::from_usage("-C, --changelog=[changelog] 'A previous changelog to prepend new changes to (this is like{n}\
                                                           using the same file for both --infile and --outfile and{n}\
                                                           should not be used in conjuction with either)'")
            .conflicts_with("infile")
            .conflicts_with("outfile"))
        // Since --setversion shouldn't be used with any of the --major, --minor, or --match, we
        // set those as exclusions
        .arg_group(ArgGroup::with_name("setver")
                .add_all(&["major", "minor", "patch", "ver"]))
        .after_help("\
* If your .git directory is a child of your project directory (most common, such as\n\
/myproject/.git) AND not in the current working directory (i.e you need to use --work-tree or\n\
--git-dir) you only need to specify either the --work-tree (i.e. /myproject) OR --git-dir (i.e. \n\
/myproject/.git), you don't need to use both.\n\n\

** If using the --config to specify a clog configuration TOML file NOT in the current working\n\
directory (meaning you need to use --work-tree or --git-dir) AND the TOML file is inside your\n\
project directory (i.e. /myproject/.clog.toml) you do not need to use --work-tree or --git-dir.")
        .get_matches();

    let start_nsec = time::get_time().nsec;

    let clog = from_matches(&matches).unwrap_or_else(|e| e.exit());

    if let Some(ref file) = clog.outfile {
        clog.write_changelog_to(file).unwrap_or_else(|e| e.exit());

        let end_nsec = time::get_time().nsec;
        let elapsed_mssec = (end_nsec - start_nsec) / 1000000;
        println!("changelog written. (took {} ms)", elapsed_mssec);
    } else {
        clog.write_changelog().unwrap_or_else(|e| e.exit());
    }
}

/// Creates a `Clog` struct from command line `clap::ArgMatches`
///
/// # Example
///
/// ```ignore
/// # use clog::Clog;
///
/// let matches = // clap settings...
///
/// let clog = Clog::from_matches(matches).unwrap_or_else(|e| {
///     e.exit();
/// });
/// ```
pub fn from_matches(matches: &ArgMatches) -> CliResult<Clog> {
    debugln!("Creating clog from matches");
    let mut clog = if let Some(cfg) = matches.value_of("config") {
        debugln!("User passed in config file: {:?}", cfg);
        if matches.is_present("workdir") && matches.is_present("gitdir") {
            debugln!("User passed in both\n\tworking dir: {:?}\n\tgit dir: {:?}",
                     matches.value_of("workdir"),
                     matches.value_of("gitdir"));
           // use --config --work-tree --git-dir
            try!(Clog::with_all(matches.value_of("gitdir").unwrap(),
                                matches.value_of("workdir").unwrap(),
                                cfg))
        } else if let Some(dir) = matches.value_of("workdir") {
            debugln!("User passed in working dir: {:?}", dir);
           // use --config --work-tree
            try!(Clog::with_dir_and_file(dir, cfg))
        } else if let Some(dir) = matches.value_of("gitdir") {
            debugln!("User passed in git dir: {:?}", dir);
           // use --config --git-dir
            try!(Clog::with_dir_and_file(dir, cfg))
        } else {
            debugln!("User only passed config");
           // use --config only
            try!(Clog::from_file(cfg))
        }
    } else {
        debugln!("User didn't pass in a config");
        if matches.is_present("gitdir") && matches.is_present("workdir") {
            let wdir = matches.value_of("workdir").unwrap();
            let gdir = matches.value_of("gitdir").unwrap();
            debugln!("User passed in both\n\tworking dir: {:?}\n\tgit dir: {:?}",
                     wdir,
                     gdir);
            try!(Clog::with_dirs(gdir, wdir))
        } else if let Some(dir) = matches.value_of("gitdir") {
            debugln!("User passed in git dir: {:?}", dir);
            try!(Clog::with_dir(dir))
        } else if let Some(dir) = matches.value_of("workdir") {
            debugln!("User passed in working dir: {:?}", dir);
            try!(Clog::with_dir(dir))
        } else {
            debugln!("Trying the default config file");
            try!(Clog::from_file(CLOG_CONFIG_FILE))
        }
    };

    // compute version early, so we can exit on error
    clog.version = {
        // less typing later...
        let (major, minor, patch) = (matches.is_present("major"),
                                     matches.is_present("minor"),
                                     matches.is_present("patch"));
        if matches.is_present("ver") {
            matches.value_of("ver").unwrap().to_owned()
        } else if major || minor || patch {
            let mut had_v = false;
            let v_string = clog.get_latest_tag_ver();
            let first_char = v_string.chars().nth(0).unwrap_or(' ');
            let v_slice = if first_char == 'v' || first_char == 'V' {
                had_v = true;
                v_string.trim_left_matches(|c| c == 'v' || c == 'V')
            } else {
                &v_string[..]
            };
            match semver::Version::parse(v_slice) {
                Ok(ref mut v) => {
                    // if-else may be quicker, but it's longer mentally, and this isn't slow
                    match (major, minor, patch) {
                        (true,_,_) => {
                            v.major += 1;
                            v.minor = 0;
                            v.patch = 0;
                        }
                        (_,true,_) => {
                            v.minor += 1;
                            v.patch = 0;
                        }
                        (_,_,true) => {
                            v.patch += 1;
                            clog.patch_ver = true;
                        }
                        _ => unreachable!(),
                    }
                    format!("{}{}",
                            if had_v {
                                "v"
                            } else {
                                ""
                            },
                            v)
                }
                Err(e) => {
                    return Err(CliError::Semver(Box::new(e),
                                                String::from("Failed to parse version into \
                                                              valid SemVer. Ensure the version \
                                                              is in the X.Y.Z format.")));
                }
            }
        } else {
            clog.version
        }
    };

    if let Some(from) = matches.value_of("from") {
        clog.from = from.to_owned();
    } else if matches.is_present("from-latest-tag") {
        clog.from = clog.get_latest_tag();
    }

    if let Some(to) = matches.value_of("to") {
        clog.to = to.to_owned();
    }

    if let Some(repo) = matches.value_of("repo") {
        clog.repo = repo.to_owned();
    }

    if matches.is_present("link-style") {
        clog.link_style = value_t!(matches.value_of("link-style"), LinkStyle)
                              .unwrap_or(LinkStyle::Github);
    }

    if let Some(subtitle) = matches.value_of("subtitle") {
        clog.subtitle = subtitle.to_owned();
    }

    if let Some(file) = matches.value_of("outfile") {
        clog.outfile = Some(file.to_owned());
    }

    if let Some(file) = matches.value_of("infile") {
        clog.infile = Some(file.to_owned());
    }

    if let Some(file) = matches.value_of("changelog") {
        clog.infile = Some(file.to_owned());
        clog.outfile = Some(file.to_owned());
    }

    if matches.is_present("format") {
        clog.out_format = value_t_or_exit!(matches.value_of("format"), ChangelogFormat);
    }

    debugln!("Returning clog:\n{:?}", clog);

    Ok(clog)
}
<|endoftext|>


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

extern crate qrcode;
extern crate termion;
extern crate image;
extern crate regex;

use clap::{App, Arg, SubCommand};
use qrcode::{QrCode, EcLevel};
use termion::color;

mod payloads;

const WIFI_COMMAND: &'static str = "wifi";
const MAIL_COMMAND: &'static str = "mail";
// const SMS_COMMAND: &'static str = "sms";
// const MMS_COMMAND: &'static str = "mms";
// const GEO_COMMAND: &'static str = "geo";
const PHONE_COMMAND: &'static str = "phone";
// const SKYPE_COMMAND: &'static str = "skype";
const URL_COMMAND: &'static str = "url";
// const BOOKMARK_COMMAND: &'static str = "bookmark";
// const BITCOIN_COMMAND: &'static str = "bitcoin";
// const GIRO_COMMAND: &'static str = "giro";
// const CALENDAR_COMMAND: &'static str = "calendar";

/*TODO: add arguments for:
- ec Level
- size of qr code
- implement different types of qr payloads
*/
fn main() {
        .arg(Arg::with_name("safe_zone") //TODO: change this into an off switch
    let app = App::new(crate_name!())
        .version(crate_version!())
        .author(crate_authors!("\n"))
        .about(crate_description!())
            .global(true)
            .required(false)
            .short("s")
            .long("safe-zone")
            .help("Sets wether the safe zone around the code should be drawn or not.")
            .takes_value(false)
            )
            //TODO: subcmds are not working like expected, if you have one the system assumes you have many
        .subcommand(SubCommand::with_name(WIFI_COMMAND)
            .about("formats to a wifi access string")
            .arg(Arg::with_name("ssid").required(true))
            .arg(Arg::with_name("pwd").required(true))
            .arg(Arg::with_name("mode")
                .possible_values(&["WEP", "WPA", "nopass"])
                .default_value("WPA")
                .value_name("MODE"))
            .arg(Arg::with_name("hidden")
                .required(false)
                .possible_values(&["true", "false"])
                .default_value("false")
                .value_name("HIDDEN")))
        .subcommand(SubCommand::with_name(MAIL_COMMAND)
            .about("formats to a mail adress string")
            .arg(Arg::with_name("receiver").required(true))
            .arg(Arg::with_name("subject"))
            .arg(Arg::with_name("message"))
            .arg(Arg::with_name("encoding")
                .possible_values(&["MAILTO", "MATMSG", "SMTP"])
                .default_value("MAILTO")
                .value_name("ENCODING")))
        .subcommand(SubCommand::with_name(URL_COMMAND)
            .about("formats to an URL")
            .arg(Arg::with_name("url")
                .required(true)
                .value_name("URL")))
        .subcommand(SubCommand::with_name(PHONE_COMMAND)
            .about("formats to a phone number")
            .arg(Arg::with_name("number")
                .required(true)
                .value_name("NUMBER")))
        .arg(Arg::with_name("INPUT")
            .help("The input string to use")
            .required_unless_one(&[WIFI_COMMAND, MAIL_COMMAND, URL_COMMAND, PHONE_COMMAND]));

    let matches = app.get_matches();

    let payload: String;
    if let Some(sub) = matches.subcommand_matches(WIFI_COMMAND) {
        let auth = match sub.value_of("mode") {
            Some("WEP") => payloads::Authentication::WEP,
            Some("WPA") => payloads::Authentication::WPA,
            _ => payloads::Authentication::nopass,
        };
        payload = payloads::wifi_string(sub.value_of("ssid").unwrap(),
                                        sub.value_of("pwd").unwrap(),
                                        &auth,
                                        sub.value_of("hidden").unwrap() == "true");
    } else if let Some(sub) = matches.subcommand_matches(MAIL_COMMAND) {
        let encoding = match sub.value_of("encoding") {
            Some("MAILTO") => payloads::MailEncoding::MAILTO,
            Some("MATMSG") => payloads::MailEncoding::MATMSG,
            Some("SMTP") => payloads::MailEncoding::SMTP,
            _ => payloads::MailEncoding::MAILTO,
        };
        payload = payloads::mail_string(sub.value_of("receiver").unwrap(),
                                        sub.value_of("subject").unwrap(),
                                        sub.value_of("message").unwrap(),
                                        &encoding);
    } else if let Some(sub) = matches.subcommand_matches(URL_COMMAND) {
        payload = payloads::url_string(sub.value_of("url").unwrap());
    } else if let Some(sub) = matches.subcommand_matches(PHONE_COMMAND) {
        payload = payloads::phone_string(sub.value_of("phone").unwrap());
    } else {
        payload = String::from(matches.value_of("INPUT").unwrap());
    }

    let safe: bool = match matches.occurrences_of("safe_zone") {
        0 => true,
        _ => false,
    };

    let code = QrCode::with_error_correction_level(payload, EcLevel::H).unwrap();

    let bit_array = code.to_colors();

    let w = code.width();
    let wide = w + 6;

    //Draw the first white safe zone
    if safe {
        for a in 1..(wide * 3) + 1 {
            print!("{}  ", color::Bg(color::LightWhite));
            if a % wide == 0 {
                println!("{}", color::Bg(color::Reset));
            }
        }
    }

    for (i, item) in bit_array.iter().enumerate() {
        //left safe zone
        if safe && i % w == 0 {
            print!("{}      ", color::Bg(color::LightWhite));
        }

        if *item == qrcode::types::Color::Dark {
            print!("{}  ", color::Bg(color::Black));
        } else {
            print!("{}  ", color::Bg(color::LightWhite));
        }

        if (i + 1) % w == 0 {
            if safe {
                //draw right safe zone
                println!("{}      {}",
                         color::Bg(color::LightWhite),
                         color::Bg(color::Reset));
            } else {
                println!("{}", color::Bg(color::Reset));
            }
        }
    }

    //Draw the last white safe zone
    if safe {
        for a in 1..(wide * 3) + 1 {
            print!("{}  ", color::Bg(color::LightWhite));
            if a % wide == 0 {
                println!("{}", color::Bg(color::Reset));
            }
        }
    }
}
<commit_msg>fix bug that the INPUT arg was needed when using a subcommand<commit_after>#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate clap;

extern crate qrcode;
extern crate termion;
extern crate image;
extern crate regex;

use clap::{App, Arg, SubCommand, AppSettings};
use qrcode::{QrCode, EcLevel};
use termion::color;

mod payloads;

const WIFI_COMMAND: &'static str = "wifi";
const MAIL_COMMAND: &'static str = "mail";
// const SMS_COMMAND: &'static str = "sms";
// const MMS_COMMAND: &'static str = "mms";
// const GEO_COMMAND: &'static str = "geo";
const PHONE_COMMAND: &'static str = "phone";
// const SKYPE_COMMAND: &'static str = "skype";
const URL_COMMAND: &'static str = "url";
// const BOOKMARK_COMMAND: &'static str = "bookmark";
// const BITCOIN_COMMAND: &'static str = "bitcoin";
// const GIRO_COMMAND: &'static str = "giro";
// const CALENDAR_COMMAND: &'static str = "calendar";

/*TODO: add arguments for:
- ec Level
- size of qr code
- implement different types of qr payloads
*/
fn main() {
        .arg(Arg::with_name("safe_zone") //TODO: change this into an off switch
    let app = App::new(crate_name!())
        .version(crate_version!())
        .author(crate_authors!("\n"))
        .about(crate_description!())
        .global_setting(AppSettings::SubcommandsNegateReqs)
            .global(true)
            .required(false)
            .short("s")
            .long("safe-zone")
            .help("Sets wether the safe zone around the code should be drawn or not.")
            .takes_value(false)
            )
            //TODO: subcmds are not working like expected, if you have one the system assumes you have many
        .subcommand(SubCommand::with_name(WIFI_COMMAND)
            .about("formats to a wifi access string")
            .arg(Arg::with_name("ssid").required(true))
            .arg(Arg::with_name("pwd").required(true))
            .arg(Arg::with_name("mode")
                .possible_values(&["WEP", "WPA", "nopass"])
                .default_value("WPA")
                .value_name("MODE"))
            .arg(Arg::with_name("hidden")
                .required(false)
                .possible_values(&["true", "false"])
                .default_value("false")
                .value_name("HIDDEN")))
        .subcommand(SubCommand::with_name(MAIL_COMMAND)
            .about("formats to a mail adress string")
            .arg(Arg::with_name("receiver").required(true))
            .arg(Arg::with_name("subject"))
            .arg(Arg::with_name("message"))
            .arg(Arg::with_name("encoding")
                .possible_values(&["MAILTO", "MATMSG", "SMTP"])
                .default_value("MAILTO")
                .value_name("ENCODING")))
        .subcommand(SubCommand::with_name(URL_COMMAND)
            .about("formats to an URL")
            .arg(Arg::with_name("url")
                .required(true)
                .value_name("URL")))
        .subcommand(SubCommand::with_name(PHONE_COMMAND)
            .about("formats to a phone number")
            .arg(Arg::with_name("number")
                .required(true)
                .value_name("NUMBER")))
        .arg(Arg::with_name("INPUT")
            .help("The input string to use")
            .required_unless_one(&[WIFI_COMMAND, MAIL_COMMAND, URL_COMMAND, PHONE_COMMAND]));

    let matches = app.get_matches();

    let payload: String;
    if let Some(sub) = matches.subcommand_matches(WIFI_COMMAND) {
        let auth = match sub.value_of("mode") {
            Some("WEP") => payloads::Authentication::WEP,
            Some("WPA") => payloads::Authentication::WPA,
            _ => payloads::Authentication::nopass,
        };
        payload = payloads::wifi_string(sub.value_of("ssid").unwrap(),
                                        sub.value_of("pwd").unwrap(),
                                        &auth,
                                        sub.value_of("hidden").unwrap() == "true");
    } else if let Some(sub) = matches.subcommand_matches(MAIL_COMMAND) {
        let encoding = match sub.value_of("encoding") {
            Some("MAILTO") => payloads::MailEncoding::MAILTO,
            Some("MATMSG") => payloads::MailEncoding::MATMSG,
            Some("SMTP") => payloads::MailEncoding::SMTP,
            _ => payloads::MailEncoding::MAILTO,
        };
        payload = payloads::mail_string(sub.value_of("receiver").unwrap(),
                                        sub.value_of("subject").unwrap(),
                                        sub.value_of("message").unwrap(),
                                        &encoding);
    } else if let Some(sub) = matches.subcommand_matches(URL_COMMAND) {
        payload = payloads::url_string(sub.value_of("url").unwrap());
    } else if let Some(sub) = matches.subcommand_matches(PHONE_COMMAND) {
        payload = payloads::phone_string(sub.value_of("phone").unwrap());
    } else {
        payload = String::from(matches.value_of("INPUT").unwrap());
    }

    let safe: bool = match matches.occurrences_of("safe_zone") {
        0 => true,
        _ => false,
    };

    let code = QrCode::with_error_correction_level(payload, EcLevel::H).unwrap();

    let bit_array = code.to_colors();

    let w = code.width();
    let wide = w + 6;

    //Draw the first white safe zone
    if safe {
        for a in 1..(wide * 3) + 1 {
            print!("{}  ", color::Bg(color::LightWhite));
            if a % wide == 0 {
                println!("{}", color::Bg(color::Reset));
            }
        }
    }

    for (i, item) in bit_array.iter().enumerate() {
        //left safe zone
        if safe && i % w == 0 {
            print!("{}      ", color::Bg(color::LightWhite));
        }

        if *item == qrcode::types::Color::Dark {
            print!("{}  ", color::Bg(color::Black));
        } else {
            print!("{}  ", color::Bg(color::LightWhite));
        }

        if (i + 1) % w == 0 {
            if safe {
                //draw right safe zone
                println!("{}      {}",
                         color::Bg(color::LightWhite),
                         color::Bg(color::Reset));
            } else {
                println!("{}", color::Bg(color::Reset));
            }
        }
    }

    //Draw the last white safe zone
    if safe {
        for a in 1..(wide * 3) + 1 {
            print!("{}  ", color::Bg(color::LightWhite));
            if a % wide == 0 {
                println!("{}", color::Bg(color::Reset));
            }
        }
    }
}
<|endoftext|>


<commit_before>extern crate libc;

use std::cmp::min;
use std::io::{BufRead, BufReader};
use std::time::Duration;

const USAGE_STRING: &'static str = "\
usage: gol genmap SIZE_V SIZE_H  (generates an empty map)
       gol play FILE [TIME_MS]   (plays the game)

       maps: must be rectangular
             '·' is a dead cell, anything else is a living cell
         ^C: exit
";

static mut exit: bool = false;

type Map = Vec<Vec<Cell>>;

struct Cell {
    alive: bool,
    lives: bool,
}

fn print_usage() {
    print!("{}", USAGE_STRING);
}

fn print_new_map(size_args: &[String]) {
    let sizev = size_args[0].parse::<usize>().unwrap();
    let sizeh = size_args[1].parse::<usize>().unwrap();
    let map_line = std::iter::repeat('·').take(sizeh).collect::<String>();
    for _ in 0..sizev {
        println!("{}", map_line);
    }
}

fn get_map(filename: &String) -> Option<Map> {
    let file = match std::fs::File::open(filename) {
        Ok(file) => file,
        Err(error) => {
            println!("{}: {}", filename, error);
            return None
        },
    };
    let reader = BufReader::new(file);
    Some(reader.lines().map(|l| l.unwrap().chars().map(|c| match c {
        '·' => Cell { alive: false, lives: false },
        _   => Cell { alive: true, lives: true },
    }).collect()).collect())
}

fn iterate(map: &mut Map) {
    let i_max = map.len();
    let j_max = map[0].len();
    let mut live_neighbours: usize;
    for i in 0..i_max {
        for j in 0..j_max {
            live_neighbours = 0;
            for m in i.saturating_sub(1)..min(i + 2, i_max) {
                for n in j.saturating_sub(1)..min(j + 2, j_max) {
                    if map[m][n].alive && (m != i || n !=  j) { live_neighbours += 1; }
                }
            }
            if !map[i][j].alive {
                if live_neighbours == 3  { map[i][j].lives = true; }
            }
            else if live_neighbours < 2 || 3 < live_neighbours { map[i][j].lives = false; }
        }
    }
    for i in 0..map.len() {
        for j in 0..map[0].len() {
            map[i][j].alive = map[i][j].lives;
        }
    }
}

fn display_map(map: &Map) {
    let mut screen = String::with_capacity(map.len() * (map[0].len() * 3 + 1) + 7);
    screen.push_str("\x1B[2J\x1B[H"); // clear screen and move cursor to 1;1
    for i in 0..map.len() {
        for j in 0..map[0].len() {
            if map[i][j].alive { screen.push_str(" X"); }
            else { screen.push_str(" ·"); }
        }
        screen.push('\n');
    }
    print!("{}{}x{}, ", screen, map.len(), map[0].len());
}

fn play_map(filename: &String, pause_time_ms: u64) {
    unsafe { libc::signal(libc::SIGINT, interrupt as libc::sighandler_t); }
    let mut map = get_map(filename).unwrap();
    let size_row = map[0].len();
    for row in map.iter() {
        if row.len() != size_row {
            println!("error: map is not a rectangle");
            return
        }
    }
    let mut niter = 0usize;
    print!("\x1B[?47h\x1B[?25l"); // save screen and hide cursor
    loop {
        display_map(&map);
        println!("iteration: {}", niter);
        iterate(&mut map);
        niter += 1;
        std::thread::sleep(Duration::from_millis(pause_time_ms));
        unsafe {
            if exit {
                print!("\x1B[?47l\x1B[?25h"); // restore screen and show cursor
                return
            }
        }
    }
}

fn interrupt(_: libc::c_int) {
    unsafe { exit = true; }
}

fn main() {
    let args: Vec<_> = std::env::args().collect();
    if args.len() == 4 && args[1] == "genmap" { print_new_map(&args[2..4]); }
    else if args.len() == 3 && args[1] == "play" { play_map(&args[2], 400); }
    else if args.len() == 4 && args[1] == "play" { play_map(&args[2], args[3].parse::<u64>().unwrap()); }
    else { print_usage(); }
}
<commit_msg>save and restore cursor position (well try, most terminals suck at that)<commit_after>extern crate libc;

use std::cmp::min;
use std::io::{BufRead, BufReader};
use std::time::Duration;

const USAGE_STRING: &'static str = "\
usage: gol genmap SIZE_V SIZE_H  (generates an empty map)
       gol play FILE [TIME_MS]   (plays the game)

       maps: must be rectangular
             '·' is a dead cell, anything else is a living cell
         ^C: exit
";

static mut exit: bool = false;

type Map = Vec<Vec<Cell>>;

struct Cell {
    alive: bool,
    lives: bool,
}

fn print_usage() {
    print!("{}", USAGE_STRING);
}

fn print_new_map(size_args: &[String]) {
    let sizev = size_args[0].parse::<usize>().unwrap();
    let sizeh = size_args[1].parse::<usize>().unwrap();
    let map_line = std::iter::repeat('·').take(sizeh).collect::<String>();
    for _ in 0..sizev {
        println!("{}", map_line);
    }
}

fn get_map(filename: &String) -> Option<Map> {
    let file = match std::fs::File::open(filename) {
        Ok(file) => file,
        Err(error) => {
            println!("{}: {}", filename, error);
            return None
        },
    };
    let reader = BufReader::new(file);
    Some(reader.lines().map(|l| l.unwrap().chars().map(|c| match c {
        '·' => Cell { alive: false, lives: false },
        _   => Cell { alive: true, lives: true },
    }).collect()).collect())
}

fn iterate(map: &mut Map) {
    let i_max = map.len();
    let j_max = map[0].len();
    let mut live_neighbours: usize;
    for i in 0..i_max {
        for j in 0..j_max {
            live_neighbours = 0;
            for m in i.saturating_sub(1)..min(i + 2, i_max) {
                for n in j.saturating_sub(1)..min(j + 2, j_max) {
                    if map[m][n].alive && (m != i || n !=  j) { live_neighbours += 1; }
                }
            }
            if !map[i][j].alive {
                if live_neighbours == 3  { map[i][j].lives = true; }
            }
            else if live_neighbours < 2 || 3 < live_neighbours { map[i][j].lives = false; }
        }
    }
    for i in 0..map.len() {
        for j in 0..map[0].len() {
            map[i][j].alive = map[i][j].lives;
        }
    }
}

fn display_map(map: &Map) {
    let mut screen = String::with_capacity(map.len() * (map[0].len() * 3 + 1) + 7);
    screen.push_str("\x1B[2J\x1B[H"); // clear screen and move cursor to 1;1
    for i in 0..map.len() {
        for j in 0..map[0].len() {
            if map[i][j].alive { screen.push_str(" X"); }
            else { screen.push_str(" ·"); }
        }
        screen.push('\n');
    }
    print!("{}{}x{}, ", screen, map.len(), map[0].len());
}

fn play_map(filename: &String, pause_time_ms: u64) {
    unsafe { libc::signal(libc::SIGINT, interrupt as libc::sighandler_t); }
    let mut map = get_map(filename).unwrap();
    let size_row = map[0].len();
    for row in map.iter() {
        if row.len() != size_row {
            println!("error: map is not a rectangle");
            return
        }
    }
    let mut niter = 0usize;
    print!("\x1B[?47h\x1B7\x1B[?25l"); // save screen and cursor position and hide cursor
    loop {
        display_map(&map);
        println!("iteration: {}", niter);
        iterate(&mut map);
        niter += 1;
        std::thread::sleep(Duration::from_millis(pause_time_ms));
        unsafe {
            if exit {
                print!("\x1B[?47l\x1B8\x1B[?25h"); // restore screen and cursor position and show cursor
                return
            }
        }
    }
}

fn interrupt(_: libc::c_int) {
    unsafe { exit = true; }
}

fn main() {
    let args: Vec<_> = std::env::args().collect();
    if args.len() == 4 && args[1] == "genmap" { print_new_map(&args[2..4]); }
    else if args.len() == 3 && args[1] == "play" { play_map(&args[2], 400); }
    else if args.len() == 4 && args[1] == "play" { play_map(&args[2], args[3].parse::<u64>().unwrap()); }
    else { print_usage(); }
}
<|endoftext|>