<commit_before>extern crate rustwlc;
#[macro_use]
extern crate wayland_client;

extern crate tempfile;

extern crate byteorder;
extern crate gdk_pixbuf;
extern crate cairo;
extern crate gdk_sys;
extern crate memmap;

use std::env;
use std::process::exit;
use cairo::{ImageSurface, Format};
//use gdk_sys;
use memmap::Mmap;

fn main() {
    let args: Vec<_> = env::args().collect();
    if args.len() < 2 {
        println!("Please supply either a file path or a color (written in hex)");
        exit(1);
    }
    let input = &args[1];
    // TODO Don't hard code this, get it from the server...
    let output = unsafe {rustwlc::WlcOutput::dummy(0)};
    if let Ok(color) = input.parse::<u32>() {
        let color = Color::from_u32(color);
        generate_solid_background(color, output);
    } else {
        generate_image_background(input.clone(), output);
    }
}

use std::mem::transmute;
use std::os::unix::io::AsRawFd;
use std::io::{Read, Write};
use std::fs::File;
use std::path::PathBuf;

//use gdk_pixbuf::{Pixbuf, InterpType};

use wayland_client::wayland::get_display;
use wayland_client::wayland::compositor::{WlCompositor, WlSurface};
use wayland_client::wayland::shell::{WlShellSurface, 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 rustwlc::WlcOutput;

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)}
    }
}

/// 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, output: WlcOutput) {
    // 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();
    let seat = env.seat.as_ref().map(|o| &o.0).unwrap();

    // Create the surface we are going to write into
    let surface = compositor.create_surface();
    let shell_surface = shell.get_shell_surface(&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
    surface.attach(Some(&buffer), 0, 0);

    main_background_loop(compositor, shell, shm, seat, surface,
                         shell_surface, buffer, evt_iter);
}

/// 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, output: WlcOutput) {
    //let pix_buf = Pixbuf::new_from_file(path.as_str())
    //    .expect("Could not read the file");
    let image_file = File::open(path.clone())
    /*let image_file = ::std::fs::OpenOptions::new()
        .write(true)
        .create_new(false)
        .open(path.clone())*/
        .unwrap_or_else(|_| {
            println!("Could not open \"{:?}\"", path);
            panic!("Could not open image file");
        });
    let background_surface = ImageSurface::create_from_png(image_file)
        .expect("could not create surface from png");
    //let cairo_surface_from_pixbuf = gdk_sys::gdk_cairo_surface_create_from_pixbuf();
    //let data = unsafe {pix_buf.get_pixels()};
    //let data = read_image_data(path);
    // 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();
    let seat = env.seat.as_ref().map(|o| &o.0).unwrap();

    // Create the surface we are going to write into
    let surface = compositor.create_surface();
    let shell_surface = shell.get_shell_surface(&surface);
    // 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 size = (width * height) as i32;
    /*let resolution = output.get_resolution()
        .expect("Couldn't get output resolution");
    let (width, height) = (resolution.w as i32, resolution.h as i32);*/
    //let size = width * height as i32;

    let width = background_surface.get_width();
    let height = background_surface.get_height();
    let stride = background_surface.get_stride();
/*
    let width = 800; let height = 600;
    let stride = width * 4;
*/
    let size: i32 = stride * height;
    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");
    let mut map = Mmap::open(&tmp, memmap::Protection::ReadWrite)
        .expect("Could not memory map the temp file");
    let mem_slice = unsafe { map.as_mut_slice() };
    let boxed_mem: Box<[u8]> = mem_slice.to_vec().into_boxed_slice();

    //let mut index = 0;
    //println!("size of image (bytes): {:?}, rows: {:?}, height: {:?}, skip per row: {:?}",
    //       pix_buf.get_byte_length(), pix_buf.get_width(), pix_buf.get_height(), pix_buf.get_rowstride());
    //for index in 0..size as usize {
    /*let mut counter = 0;
    while index < size as usize {
        if counter >= pix_buf.get_rowstride() / 4 {
            for _ in 0..(width - counter) {
                tmp.write_u32::<NativeEndian>(::std::u32::MAX)
                    .expect("Could not write to temp file to generate image background");
            }
            counter = 0;
        }
        unsafe {
            let bytes = if index < data.len() && index + 3 < data.len(){
                let mut stuff: [u8; 4] = [0; 4];
                // bgra
                // eg, blue, green, red, something else
                stuff[0] = data[index + 2];
                stuff[1] = data[index + 1];
                stuff[2] = data[index];
                stuff[3] = data[index + 3];
                //warn!("stuff: {:#?}", stuff);
                transmute(stuff)
            } else {
                ::std::u32::MAX
            };
            tmp.write_u32::<NativeEndian>(bytes )
                .expect("Could not write to temp file to generate image background");

        }
        index += 4;
        counter += 1;
    }
    println!("pix buf stuff: width: {:?}, stride: {:?}", pix_buf.get_width(), pix_buf.get_rowstride());
    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, stride, WlShmFormat::Argb8888);
    let cairo_surface = ImageSurface::create_for_data(boxed_mem, free_function, Format::ARgb32,
                                                      width, height, stride);
    let cairo_context = cairo::Context::new(&cairo_surface);
    cairo_context.scale(1.0, 1.0);
    cairo_context.set_source_surface(&background_surface, 0.0, 0.0);
    // TODO Needed??
    cairo_context.paint();
    // Tell Way Cooler not to put this in the tree, treat as background
    shell_surface.set_class("Background".into());
    // TODO Actually give it the path or something idk
    shell_surface.set_title(format!("Image background yay"));
    //let mut string = String::new();
    /*for _ in 0..size {
        unsafe {
            tmp.write_u32::<NativeEndian>(6565656)
                .expect("Could not write to file")
        }
    }*/
    //tmp.read_to_string(&mut string).unwrap();
    //println!("Buffer: {:?}", string);

    // Attach the buffer to the surface
    surface.attach(Some(&buffer), 0, 0);
    surface.set_buffer_scale(1);
    surface.damage(0, 0, width, height);

    main_background_loop(compositor, shell, shm, seat, surface,
                         shell_surface, buffer, evt_iter);
}

fn free_function(_: Box<[u8]>) {

}

fn read_image_data(path: PathBuf) -> Vec<u32> {
    let image_file = File::open(path.clone())
        .unwrap_or_else(|_| {
            println!("Could not open \"{:?}\"", path);
            panic!("Could not open image file");
        });
    // Most common screen size
    let mut buffer: Vec<u32> = Vec::with_capacity(1366 * 768);
    // TODO, while clever looking, is Ok(byte) here correct?
    let mut bytes = image_file.bytes();
    'outer: loop {
        let result: u32;
        let mut data_chunk: [u8; 4] = [0; 4];
        for i in 0..3 {
            if let Some(Ok(next_chunk)) = bytes.next() {
                data_chunk[i] = next_chunk;
            } else {
                break 'outer;
            }
        }
        /*let data_chunk: Vec<u8> = next_chunk
            .flat_map(|maybe_bytes| maybe_bytes.into_iter())
            .collect();*/
        result = unsafe {::std::mem::transmute(data_chunk)};
        println!("{:X}", result);
        buffer.push(result);
    }
    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(compositor: &WlCompositor, shell: &WlShell, shm: &WlShm,
                        seat: &WlSeat,
                        surface: WlSurface, shell_surface: WlShellSurface,
                        buffer: WlBuffer, mut event_iter: EventIterator) {
    use wayland_client::wayland::WaylandProtocolEvent;
    use wayland_client::Event;
    println!("Entering main loop");
    // For now just loop and do nothing
    // Eventually need to query the background state and update
    let cursor_surface = compositor.create_surface();
    let mut pointer = seat.get_pointer();
    let cursor_theme = load_theme(None, 16, shm);
    let maybe_cursor = cursor_theme.get_cursor("default");
    /*if maybe_cursor.is_none() {
        println!("Could not load cursor theme properly, cannot load background");
        println!("Please consult the developers about this issue with your distro version, this is a known issue");
        return;
    }
    let cursor = maybe_cursor.unwrap();
    let cursor_buffer = cursor.frame_buffer(0).expect("Couldn't get frame_buffer");
    cursor_surface.attach(Some(&*cursor_buffer), 0, 0);
     */
    pointer.set_event_iterator(&event_iter);
    pointer.set_cursor(0, Some(&cursor_surface), 0, 0);
    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, 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.");
    }
}
<commit_msg>Got it memmapped, now protocol is failing...<commit_after>extern crate rustwlc;
#[macro_use]
extern crate wayland_client;

extern crate tempfile;

extern crate byteorder;
extern crate gdk_pixbuf;
extern crate cairo;
extern crate gdk_sys;
extern crate memmap;

use std::env;
use std::process::exit;
use cairo::{ImageSurface};
//use gdk_sys;
use memmap::Mmap;

fn main() {
    let args: Vec<_> = env::args().collect();
    if args.len() < 2 {
        println!("Please supply either a file path or a color (written in hex)");
        exit(1);
    }
    let input = &args[1];
    // TODO Don't hard code this, get it from the server...
    let output = unsafe {rustwlc::WlcOutput::dummy(0)};
    if let Ok(color) = input.parse::<u32>() {
        let color = Color::from_u32(color);
        generate_solid_background(color, output);
    } else {
        generate_image_background(input.clone(), output);
    }
}

use std::mem::transmute;
use std::os::unix::io::AsRawFd;
use std::io::{Read, Write};
use std::fs::File;
use std::path::PathBuf;

//use gdk_pixbuf::{Pixbuf, InterpType};

use wayland_client::wayland::get_display;
use wayland_client::wayland::compositor::{WlCompositor, WlSurface};
use wayland_client::wayland::shell::{WlShellSurface, 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 rustwlc::WlcOutput;

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)}
    }
}

/// 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, _output: WlcOutput) {
    // 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();
    let seat = env.seat.as_ref().map(|o| &o.0).unwrap();

    // Create the surface we are going to write into
    let surface = compositor.create_surface();
    let shell_surface = shell.get_shell_surface(&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
    surface.attach(Some(&buffer), 0, 0);

    main_background_loop(compositor, shell, shm, seat, surface,
                         shell_surface, buffer, evt_iter);
}

/// 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, _output: WlcOutput) {
    //let pix_buf = Pixbuf::new_from_file(path.as_str())
    //    .expect("Could not read the file");
    //let image_file = File::open(path.clone())
    let image_file = ::std::fs::OpenOptions::new()
        .read(true)
        .write(true)
        .create_new(false)
        .open(path.clone())
        .unwrap_or_else(|_| {
            println!("Could not open \"{:?}\"", path);
            panic!("Could not open image file");
        });
    let raw_fd = image_file.as_raw_fd();
    let map = Mmap::open(&image_file, memmap::Protection::ReadWrite)
        .expect("Could not memory map the temp file");
    let background_surface = ImageSurface::create_from_png(image_file)
        .expect("could not create surface from png");
    // 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();
    let seat = env.seat.as_ref().map(|o| &o.0).unwrap();

    // Create the surface we are going to write into
    let surface = compositor.create_surface();
    let shell_surface = shell.get_shell_surface(&surface);
    // Calculate how big the buffer needs to be from the output resolution
    // TODO Get the output size from way cooler somehow...

    let width = background_surface.get_width();
    let height = background_surface.get_height();
    let stride = background_surface.get_stride();
    let size: i32 = stride * height;
    println!("Size: {}, mmap len: {}", size, map.len());
    let size = map.len() as i32;
    //let tmp = tempfile::NamedTempFile::new().expect("Unable to create a tempfile.");
    //tmp.set_len(size as u64).expect("Could not truncate length of file");
    // Create the buffer that is mem-mapped to the temp file descriptor
    let pool = shm.create_pool(raw_fd, size);
    let buffer = pool.create_buffer(0, width, height, stride, 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
    surface.attach(Some(&buffer), 0, 0);
    surface.commit();
    surface.set_buffer_scale(1);
    surface.damage(0, 0, width, height);

    println!("{:?}", unsafe{map.as_slice()});
    main_background_loop(compositor, shell, shm, seat, surface,
                         shell_surface, buffer, evt_iter);
}

#[allow(dead_code)]
fn read_image_data(path: PathBuf) -> Vec<u32> {
    let image_file = File::open(path.clone())
        .unwrap_or_else(|_| {
            println!("Could not open \"{:?}\"", path);
            panic!("Could not open image file");
        });
    // Most common screen size
    let mut buffer: Vec<u32> = Vec::with_capacity(1366 * 768);
    // TODO, while clever looking, is Ok(byte) here correct?
    let mut bytes = image_file.bytes();
    'outer: loop {
        let result: u32;
        let mut data_chunk: [u8; 4] = [0; 4];
        for i in 0..3 {
            if let Some(Ok(next_chunk)) = bytes.next() {
                data_chunk[i] = next_chunk;
            } else {
                break 'outer;
            }
        }
        /*let data_chunk: Vec<u8> = next_chunk
            .flat_map(|maybe_bytes| maybe_bytes.into_iter())
            .collect();*/
        result = unsafe {::std::mem::transmute(data_chunk)};
        println!("{:X}", result);
        buffer.push(result);
    }
    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(compositor: &WlCompositor, shell: &WlShell, shm: &WlShm,
                        seat: &WlSeat,
                        surface: WlSurface, shell_surface: WlShellSurface,
                        buffer: WlBuffer, mut event_iter: EventIterator) {
    use wayland_client::wayland::WaylandProtocolEvent;
    use wayland_client::Event;
    println!("Entering main loop");
    // For now just loop and do nothing
    // Eventually need to query the background state and update
    let cursor_surface = compositor.create_surface();
    let mut pointer = seat.get_pointer();
    let cursor_theme = load_theme(None, 16, shm);
    let maybe_cursor = cursor_theme.get_cursor("default");
    // TODO Uncomment
    /*if maybe_cursor.is_none() {
        println!("Could not load cursor theme properly, cannot load background");
        println!("Please consult the developers about this issue with your distro version, this is a known issue");
        return;
    }
    let cursor = maybe_cursor.unwrap();
    let cursor_buffer = cursor.frame_buffer(0).expect("Couldn't get frame_buffer");
    cursor_surface.attach(Some(&*cursor_buffer), 0, 0);
     */
    pointer.set_event_iterator(&event_iter);
    pointer.set_cursor(0, Some(&cursor_surface), 0, 0);
    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, 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.");
    }
}
<|endoftext|>


<commit_before>#![feature(phase)]
extern crate serialize;
#[phase(plugin)] extern crate docopt_macros;
extern crate docopt;
extern crate core;
extern crate collections;
extern crate regex;
#[phase(plugin)] extern crate regex_macros;

use docopt::FlagParser;
use std::io::BufferedReader;
use std::io::BufferedWriter;
use std::io::File;
use core::result::Result;
use collections::string::String;
use collections::vec::Vec;
use collections::PriorityQueue;

// Interleaver takes N files (e.g., i_1.txt ... i_N.txt) and transforms them into N output files
// named o_1.txt ... o_N.txt.  The lines of the input files must be prefixed with time stamps.  The
// output files contain the same content as the input files but only one file is allowed to make
// progress on a given line.  The other files are empty.

docopt!(Args, "
Usage:
  interleaver <file>...

Options:
  -h, --help    Show this screen.
", )

type BrType = BufferedReader<core::result::Result<std::io::fs::File,std::io::IoError>>;
type BwType = BufferedWriter<core::result::Result<std::io::fs::File,std::io::IoError>>;

#[deriving(Show,PartialEq,PartialOrd,Eq,Ord)]
enum Month { Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec }

fn string_to_month (s : &str) -> Option<Month> {
  match s {
    "Jan" => Some (Jan),
    "Feb" => Some (Feb),
    "Mar" => Some (Mar),
    "Apr" => Some (Apr),
    "May" => Some (May),
    "Jun" => Some (Jun),
    "Jul" => Some (Jul),
    "Aug" => Some (Aug),
    "Sep" => Some (Sep),
    "Oct" => Some (Oct),
    "Nov" => Some (Nov),
    "Dec" => Some (Dec),
    _     => None
  }
}

#[deriving(Show,PartialEq,Eq)]
struct TimedLine {
  l       : String,
  month   : Month,
  day     : u8,
  hour    : u8,
  minute  : u8,
  second  : u8,
  usecond : u32,
  target  : uint
}

impl TimedLine {
  fn new (l : &str, target : uint) -> TimedLine {
    let re = regex! (r"^(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) +(\d+) (\d+):(\d+):(\d+) (\d+):.*$");
    let caps = match re.captures (l) {
      None => fail! ("Could not analyze line: \"{}\"", l),
      Some (caps) => caps
    };
    let (month, day, hour, minute, second, usecond) =
      match (string_to_month (caps.at(1)), from_str::<u8>(caps.at(2)), from_str::<u8> (caps.at(3)), from_str::<u8> (caps.at(4)), from_str::<u8> (caps.at(5)), from_str::<u32> (caps.at(6))
      ) {
        (Some (month), Some (day), Some (hour), Some (minute), Some (second), Some (usecond)) => (month, day, hour, minute, second, usecond*100),
        _ => fail! ("Could not extract fields from line: \"{}\"", l)
      };

    TimedLine {
      l       : String::from_str (l),
      month   : month,
      day     : day,
      hour    : hour,
      minute  : minute,
      second  : second,
      usecond : usecond,
      target  : target
    }
  }
}

impl Ord for TimedLine {
  fn cmp(&self, other: &TimedLine) -> Ordering {
    let res = {
      let month_cmp = self.month.cmp (&other.month);
      if month_cmp == Equal {
        let day_cmp = self.day.cmp (&other.day);
        if day_cmp == Equal {
          let hour_cmp = self.hour.cmp (&other.hour);
          if hour_cmp == Equal {
            let minute_cmp = self.minute.cmp (&other.minute);
            if minute_cmp == Equal {
              let second_cmp = self.second.cmp (&other.second);
              if second_cmp == Equal {
                self.usecond.cmp (&other.usecond)
              }
              else { second_cmp }
            }
            else { minute_cmp }
          }
          else { hour_cmp }
        }
        else { day_cmp }
      }
      else { month_cmp }
    };

    match res {
      Greater => Less,
      Equal => Equal,
      Less => Greater
    }
  }
}

impl PartialOrd for TimedLine {
  fn partial_cmp(&self, other: &TimedLine) -> Option<Ordering> {
    Some (self.cmp (other))
  }
}

struct TimedLineQueue<'a> {
  q : PriorityQueue<TimedLine>,
  readers : &'a mut Vec<BrType>
}

impl<'a> TimedLineQueue<'a> {
  fn fill_que (&mut self, target : uint) {
    let reader = self.readers.get_mut (target);
    let mut lines = reader.lines ();
    let line_opt = lines.next ();
    match line_opt {
      Some (line) => {
        let line = line.unwrap();
        let line = line.as_slice();
        let line = line.trim_chars('\n');
        let timed_line = TimedLine::new (line, target);
        self.q.push (timed_line);
      }
      None => ()
    }
  }

  fn new (readers : & 'a mut Vec<BrType>) -> TimedLineQueue {
    let readers_count = readers.len ();

    let mut res = TimedLineQueue {
      q : PriorityQueue::new (),
      readers : readers
    };

    for i in range (0, readers_count) {
      res.fill_que(i);
    }

    res
  }
}

#[test]
fn test_priority_queue () {
  let mut q = PriorityQueue::new ();

  let s1 = String::from_str ("Sep  2 14:25:02 8993: (main|info): --- NODE STARTED ---");
  let s2 = String::from_str ("Sep  2 14:25:02 9488: (main|info): --- NODE STARTED ---");

  let tl_1 = TimedLine::new (s1.as_slice(), 5u);
  let tl_2 = TimedLine::new (s2.as_slice(), 5u);

  q.push (s1.clone ());
  q.push (s2.clone ());

  assert_eq! (q.pop(), Some(s2.clone ()));
  assert_eq! (q.pop(), Some(s1.clone ()));

  q.push (s2.clone ());
  q.push (s1.clone ());

  assert_eq! (q.pop(), Some(s2.clone ()));
  assert_eq! (q.pop(), Some(s1.clone ()));
}

impl<'a> Iterator<TimedLine> for TimedLineQueue<'a> {
  fn next (&mut self) -> Option<TimedLine> {
    let timed_line_opt = self.q.pop ();
    match timed_line_opt {
      None => (),
      Some (ref timed_line) => {
        self.fill_que(timed_line.target);
      }
    }

    timed_line_opt
  }
}

#[test]
fn test_timed_line_ordering () {
  let s1 = String::from_str ("Sep  2 14:25:02 8993: (main|info): --- NODE STARTED ---");
  let s2 = String::from_str ("Sep  2 14:25:02 9488: (main|info): --- NODE STARTED ---");

  let tl_1 = TimedLine::new (s1.as_slice(), 5u);
  let tl_2 = TimedLine::new (s2.as_slice(), 5u);

  assert! (tl_1.cmp (&tl_2) == Less);
  assert! (tl_2.cmp (&tl_1) == Greater);
  assert! (tl_1.cmp (&tl_1) == Equal);
  assert! (tl_2.cmp (&tl_2) == Equal);
}

fn main() {
  let args: Args = FlagParser::parse().unwrap_or_else(|e| e.exit());

  let file_names = args.arg_file;
  let file_count = file_names.len ();
  let paths_it = file_names.iter ().map (|n| Path::new(n.clone ()));
  let files_it = paths_it.map (|path| File::open(&path));
  let mut buffered_readers_it = files_it.map (|file| BufferedReader::new (file));
  let mut buffered_readers_vec : Vec<BrType> = buffered_readers_it.collect ();

  let paths_out_it = range (0u, file_names.len ()).map (|i| Path::new (format!("{}.txt", i)));
  let files_out_it = paths_out_it.map (|path_out| {
    let res = File::create(&path_out);
    match res {
      Ok(_)  => (),
      Err(e) => fail!("Could not open output file: {}", e)
    };
    res
  }
  );
  let mut buffered_writers_it = files_out_it.map (|file_out| BufferedWriter::new (file_out) );

  let mut buffered_writers_vec : Vec<BwType> = buffered_writers_it.collect ();

  let mut tlq = TimedLineQueue::new (& mut buffered_readers_vec);
  for timed_line in tlq {
    let target = timed_line.target;
    {
      let writer = buffered_writers_vec.get_mut (target);
      let res = writer.write_line (format!("{}", timed_line.l).as_slice ());
      match res {
        Ok(_) => (),
        Err(e) => {
          let ref file_name = file_names[target];
          fail! ("Could not write to {}: {}", file_name, e);
        }
      };
    }

    for i in range (0, file_count).filter (|i| *i != target) {
      let writer = buffered_writers_vec.get_mut (i);
      let res = writer.write_line ("");
      match res {
        Ok(_) => (),
        Err(e) => {
          let ref file_name = file_names[i];
          fail! ("Could not write to {}: {}", file_name, e);
        }
      };
    }
  }
}
<commit_msg>Fixed test and made comparison code smaller<commit_after>#![feature(phase)]
extern crate serialize;
#[phase(plugin)] extern crate docopt_macros;
extern crate docopt;
extern crate core;
extern crate collections;
extern crate regex;
#[phase(plugin)] extern crate regex_macros;

use docopt::FlagParser;
use std::io::BufferedReader;
use std::io::BufferedWriter;
use std::io::File;
use core::result::Result;
use collections::string::String;
use collections::vec::Vec;
use collections::PriorityQueue;

// Interleaver takes N files (e.g., i_1.txt ... i_N.txt) and transforms them into N output files
// named o_1.txt ... o_N.txt.  The lines of the input files must be prefixed with time stamps.  The
// output files contain the same content as the input files but only one file is allowed to make
// progress on a given line.  The other files are empty.

docopt!(Args, "
Usage:
  interleaver <file>...

Options:
  -h, --help    Show this screen.
", )

type BrType = BufferedReader<core::result::Result<std::io::fs::File,std::io::IoError>>;
type BwType = BufferedWriter<core::result::Result<std::io::fs::File,std::io::IoError>>;

#[deriving(Show,PartialEq,PartialOrd,Eq,Ord)]
enum Month { Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec }

fn string_to_month (s : &str) -> Option<Month> {
  match s {
    "Jan" => Some (Jan),
    "Feb" => Some (Feb),
    "Mar" => Some (Mar),
    "Apr" => Some (Apr),
    "May" => Some (May),
    "Jun" => Some (Jun),
    "Jul" => Some (Jul),
    "Aug" => Some (Aug),
    "Sep" => Some (Sep),
    "Oct" => Some (Oct),
    "Nov" => Some (Nov),
    "Dec" => Some (Dec),
    _     => None
  }
}

#[deriving(Show,PartialEq,Eq)]
struct TimedLine {
  l       : String,
  month   : Month,
  day     : u8,
  hour    : u8,
  minute  : u8,
  second  : u8,
  usecond : u32,
  target  : uint
}

impl TimedLine {
  fn new (l : &str, target : uint) -> TimedLine {
    let re = regex! (r"^(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) +(\d+) (\d+):(\d+):(\d+) (\d+):.*$");
    let caps = match re.captures (l) {
      None => fail! ("Could not analyze line: \"{}\"", l),
      Some (caps) => caps
    };
    let (month, day, hour, minute, second, usecond) =
      match (string_to_month (caps.at(1)), from_str::<u8>(caps.at(2)), from_str::<u8> (caps.at(3)), from_str::<u8> (caps.at(4)), from_str::<u8> (caps.at(5)), from_str::<u32> (caps.at(6))
      ) {
        (Some (month), Some (day), Some (hour), Some (minute), Some (second), Some (usecond)) => (month, day, hour, minute, second, usecond*100),
        _ => fail! ("Could not extract fields from line: \"{}\"", l)
      };

    TimedLine {
      l       : String::from_str (l),
      month   : month,
      day     : day,
      hour    : hour,
      minute  : minute,
      second  : second,
      usecond : usecond,
      target  : target
    }
  }
}

impl Ord for TimedLine {
  fn cmp(&self, other: &TimedLine) -> Ordering {
    let month_comparator = |tl1: &TimedLine, tl2: &TimedLine| { tl1.month.cmp (&tl2.month) };
    let day_comparator = |tl1: &TimedLine, tl2: &TimedLine| { tl1.day.cmp (&tl2.day) };
    let hour_comparator = |tl1: &TimedLine, tl2: &TimedLine| { tl1.hour.cmp (&tl2.hour) };
    let minute_comparator = |tl1: &TimedLine, tl2: &TimedLine| { tl1.minute.cmp (&tl2.minute) };
    let second_comparator = |tl1: &TimedLine, tl2: &TimedLine| { tl1.second.cmp (&tl2.second) };
    let usecond_comparator = |tl1: &TimedLine, tl2: &TimedLine| { tl1.usecond.cmp (&tl2.usecond) };

    let mut comparators = vec! (month_comparator, day_comparator, hour_comparator, minute_comparator, second_comparator, usecond_comparator);

    let mut res = Equal;

    for comparator in comparators.iter_mut() {
      let comparison = (*comparator) (self, other);
      if comparison != Equal {
        res = comparison;
        break;
      }
    }

    match res {
      Greater => Less,
      Equal => Equal,
      Less => Greater
    }
  }
}

impl PartialOrd for TimedLine {
  fn partial_cmp(&self, other: &TimedLine) -> Option<Ordering> {
    Some (self.cmp (other))
  }
}

struct TimedLineQueue<'a> {
  q : PriorityQueue<TimedLine>,
  readers : &'a mut Vec<BrType>
}

impl<'a> TimedLineQueue<'a> {
  fn fill_que (&mut self, target : uint) {
    let reader = self.readers.get_mut (target);
    let mut lines = reader.lines ();
    let line_opt = lines.next ();
    match line_opt {
      Some (line) => {
        let line = line.unwrap();
        let line = line.as_slice();
        let line = line.trim_chars('\n');
        let timed_line = TimedLine::new (line, target);
        self.q.push (timed_line);
      }
      None => ()
    }
  }

  fn new (readers : & 'a mut Vec<BrType>) -> TimedLineQueue {
    let readers_count = readers.len ();

    let mut res = TimedLineQueue {
      q : PriorityQueue::new (),
      readers : readers
    };

    for i in range (0, readers_count) {
      res.fill_que(i);
    }

    res
  }
}

#[test]
fn test_priority_queue () {
  let mut q = PriorityQueue::new ();

  let s1 = String::from_str ("Sep  2 14:25:02 8993: (main|info): --- NODE STARTED ---");
  let s2 = String::from_str ("Sep  2 14:25:02 9488: (main|info): --- NODE STARTED ---");

  q.push (s1.clone ());
  q.push (s2.clone ());

  assert_eq! (q.pop(), Some(s2.clone ()));
  assert_eq! (q.pop(), Some(s1.clone ()));

  q.push (s2.clone ());
  q.push (s1.clone ());

  assert_eq! (q.pop(), Some(s2.clone ()));
  assert_eq! (q.pop(), Some(s1.clone ()));
}

impl<'a> Iterator<TimedLine> for TimedLineQueue<'a> {
  fn next (&mut self) -> Option<TimedLine> {
    let timed_line_opt = self.q.pop ();
    match timed_line_opt {
      None => (),
      Some (ref timed_line) => {
        self.fill_que(timed_line.target);
      }
    }

    timed_line_opt
  }
}

#[test]
fn test_timed_line_ordering () {
  let s1 = String::from_str ("Sep  2 14:25:02 8993: (main|info): --- NODE STARTED ---");
  let s2 = String::from_str ("Sep  2 14:25:02 9488: (main|info): --- NODE STARTED ---");

  let tl_1 = TimedLine::new (s1.as_slice(), 5u);
  let tl_2 = TimedLine::new (s2.as_slice(), 5u);

  assert! (tl_1.cmp (&tl_2) == Greater);
  assert! (tl_2.cmp (&tl_1) == Less);
  assert! (tl_1.cmp (&tl_1) == Equal);
  assert! (tl_2.cmp (&tl_2) == Equal);
}

fn main() {
  let args: Args = FlagParser::parse().unwrap_or_else(|e| e.exit());

  let file_names = args.arg_file;
  let file_count = file_names.len ();
  let paths_it = file_names.iter ().map (|n| Path::new(n.clone ()));
  let files_it = paths_it.map (|path| File::open(&path));
  let mut buffered_readers_it = files_it.map (|file| BufferedReader::new (file));
  let mut buffered_readers_vec : Vec<BrType> = buffered_readers_it.collect ();

  let paths_out_it = range (0u, file_names.len ()).map (|i| Path::new (format!("{}.txt", i)));
  let files_out_it = paths_out_it.map (|path_out| {
    let res = File::create(&path_out);
    match res {
      Ok(_)  => (),
      Err(e) => fail!("Could not open output file: {}", e)
    };
    res
  }
  );
  let mut buffered_writers_it = files_out_it.map (|file_out| BufferedWriter::new (file_out) );

  let mut buffered_writers_vec : Vec<BwType> = buffered_writers_it.collect ();

  let mut tlq = TimedLineQueue::new (& mut buffered_readers_vec);
  for timed_line in tlq {
    let target = timed_line.target;
    {
      let writer = buffered_writers_vec.get_mut (target);
      let res = writer.write_line (format!("{}", timed_line.l).as_slice ());
      match res {
        Ok(_) => (),
        Err(e) => {
          let ref file_name = file_names[target];
          fail! ("Could not write to {}: {}", file_name, e);
        }
      };
    }

    for i in range (0, file_count).filter (|i| *i != target) {
      let writer = buffered_writers_vec.get_mut (i);
      let res = writer.write_line ("");
      match res {
        Ok(_) => (),
        Err(e) => {
          let ref file_name = file_names[i];
          fail! ("Could not write to {}: {}", file_name, e);
        }
      };
    }
  }
}
<|endoftext|>


<commit_before>// Copyright 2016 Joe Wilm, The Alacritty Project Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//! Alacritty - The GPU Enhanced Terminal
#![cfg_attr(feature = "clippy", plugin(clippy))]
#![cfg_attr(feature = "clippy", feature(plugin))]
#![feature(alloc_system)] extern crate alloc_system;

#[macro_use]
extern crate alacritty;

#[macro_use]
extern crate log;

use std::error::Error;
use std::sync::Arc;

use alacritty::cli;
use alacritty::config::{self, Config};
use alacritty::display::Display;
use alacritty::event;
use alacritty::event_loop::{self, EventLoop, Msg};
use alacritty::logging;
use alacritty::sync::FairMutex;
use alacritty::term::{Term};
use alacritty::tty::{self, process_should_exit};
use alacritty::util::fmt::Red;

fn main() {
    // Load command line options and config
    let options = cli::Options::load();
    let config = load_config(&options);

    // Run alacritty
    if let Err(err) = run(config, options) {
        die!("Alacritty encountered an unrecoverable error:\n\n\t{}\n", Red(err));
    }

    info!("Goodbye.");
}

/// Load configuration
///
/// If a configuration file is given as a command line argument we don't
/// generate a default file. If an empty configuration file is given, i.e.
/// /dev/null, we load the compiled-in defaults.
fn load_config(options: &cli::Options) -> Config {
    let config_path = options.config_path()
        .or_else(|| Config::installed_config())
        .unwrap_or_else(|| {
            Config::write_defaults()
                .unwrap_or_else(|err| die!("Write defaults config failure: {}", err))
        });

    Config::load_from(&*config_path).unwrap_or_else(|err| {
        match err {
            config::Error::NotFound => {
                die!("Config file not found at: {}", config_path.display());
            },
            config::Error::Empty => {
                err_println!("Empty config; Loading defaults");
                Config::default()
            },
            _ => die!("{}", err),
        }
    })
}

/// Run Alacritty
///
/// Creates a window, the terminal state, pty, I/O event loop, input processor,
/// config change monitor, and runs the main display loop.
fn run(mut config: Config, options: cli::Options) -> Result<(), Box<Error>> {
    // Initialize the logger first as to capture output from other subsystems
    logging::initialize(&options)?;

    info!("Welcome to Alacritty.");
    config.path().map(|config_path| {
        info!("Configuration loaded from {}", config_path.display());
    });

    // Create a display.
    //
    // The display manages a window and can draw the terminal
    let mut display = Display::new(&config, &options)?;

    info!(
        "PTY Dimensions: {:?} x {:?}",
        display.size().lines(),
        display.size().cols()
    );

    // Create the terminal
    //
    // This object contains all of the state about what's being displayed. It's
    // wrapped in a clonable mutex since both the I/O loop and display need to
    // access it.
    let terminal = Term::new(&config, display.size().to_owned());
    let terminal = Arc::new(FairMutex::new(terminal));

    // Find the window ID for setting $WINDOWID
    let window_id = display.get_window_id();

    // Create the pty
    //
    // The pty forks a process to run the shell on the slave side of the
    // pseudoterminal. A file descriptor for the master side is retained for
    // reading/writing to the shell.
    let mut pty = tty::new(&config, &options, display.size(), window_id);

    // Create the pseudoterminal I/O loop
    //
    // pty I/O is ran on another thread as to not occupy cycles used by the
    // renderer and input processing. Note that access to the terminal state is
    // synchronized since the I/O loop updates the state, and the display
    // consumes it periodically.
    let event_loop = EventLoop::new(
        Arc::clone(&terminal),
        display.notifier(),
        pty.reader(),
        options.ref_test,
    );

    // The event loop channel allows write requests from the event processor
    // to be sent to the loop and ultimately written to the pty.
    let loop_tx = event_loop.channel();

    // Event processor
    //
    // Need the Rc<RefCell<_>> here since a ref is shared in the resize callback
    let mut processor = event::Processor::new(
        event_loop::Notifier(event_loop.channel()),
        display.resize_channel(),
        &options,
        &config,
        options.ref_test,
        display.size().to_owned(),
    );

    // Create a config monitor when config was loaded from path
    //
    // The monitor watches the config file for changes and reloads it. Pending
    // config changes are processed in the main loop.
    let config_monitor = match (options.live_config_reload, config.live_config_reload()) {
        // Start monitor if CLI flag says yes
        (Some(true), _) |
        // Or if no CLI flag was passed and the config says yes
        (None, true) => config.path()
                .map(|path| config::Monitor::new(path, display.notifier())),
        // Otherwise, don't start the monitor
        _ => None,
    };

    // Kick off the I/O thread
    let io_thread = event_loop.spawn(None);

    // Main display loop
    loop {
        // Process input and window events
        let mut terminal = processor.process_events(&terminal, display.window());

        // Handle config reloads
        config_monitor.as_ref()
            .and_then(|monitor| monitor.pending_config())
            .map(|new_config| {
                config = new_config;
                display.update_config(&config);
                processor.update_config(&config);
                terminal.update_config(&config);
                terminal.dirty = true;
            });

        // Maybe draw the terminal
        if terminal.needs_draw() {
            // Try to update the position of the input method editor
            display.update_ime_position(&terminal);
            // Handle pending resize events
            //
            // The second argument is a list of types that want to be notified
            // of display size changes.
            display.handle_resize(&mut terminal, &config, &mut [&mut pty, &mut processor]);

            // Draw the current state of the terminal
            display.draw(terminal, &config, processor.selection.as_ref());
        }

        // Begin shutdown if the flag was raised.
        if process_should_exit() {
            break;
        }
    }

    loop_tx.send(Msg::Shutdown).expect("Error sending shutdown to event loop");

    // FIXME patch notify library to have a shutdown method
    // config_reloader.join().ok();

    // Wait for the I/O thread thread to finish
    let _ = io_thread.join();

    Ok(())
}
<commit_msg>Drop nightly feature<commit_after>// Copyright 2016 Joe Wilm, The Alacritty Project Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//! Alacritty - The GPU Enhanced Terminal
#![cfg_attr(feature = "clippy", plugin(clippy))]
#![cfg_attr(feature = "clippy", feature(plugin))]

#[macro_use]
extern crate alacritty;

#[macro_use]
extern crate log;

use std::error::Error;
use std::sync::Arc;

use alacritty::cli;
use alacritty::config::{self, Config};
use alacritty::display::Display;
use alacritty::event;
use alacritty::event_loop::{self, EventLoop, Msg};
use alacritty::logging;
use alacritty::sync::FairMutex;
use alacritty::term::{Term};
use alacritty::tty::{self, process_should_exit};
use alacritty::util::fmt::Red;

fn main() {
    // Load command line options and config
    let options = cli::Options::load();
    let config = load_config(&options);

    // Run alacritty
    if let Err(err) = run(config, options) {
        die!("Alacritty encountered an unrecoverable error:\n\n\t{}\n", Red(err));
    }

    info!("Goodbye.");
}

/// Load configuration
///
/// If a configuration file is given as a command line argument we don't
/// generate a default file. If an empty configuration file is given, i.e.
/// /dev/null, we load the compiled-in defaults.
fn load_config(options: &cli::Options) -> Config {
    let config_path = options.config_path()
        .or_else(|| Config::installed_config())
        .unwrap_or_else(|| {
            Config::write_defaults()
                .unwrap_or_else(|err| die!("Write defaults config failure: {}", err))
        });

    Config::load_from(&*config_path).unwrap_or_else(|err| {
        match err {
            config::Error::NotFound => {
                die!("Config file not found at: {}", config_path.display());
            },
            config::Error::Empty => {
                err_println!("Empty config; Loading defaults");
                Config::default()
            },
            _ => die!("{}", err),
        }
    })
}

/// Run Alacritty
///
/// Creates a window, the terminal state, pty, I/O event loop, input processor,
/// config change monitor, and runs the main display loop.
fn run(mut config: Config, options: cli::Options) -> Result<(), Box<Error>> {
    // Initialize the logger first as to capture output from other subsystems
    logging::initialize(&options)?;

    info!("Welcome to Alacritty.");
    config.path().map(|config_path| {
        info!("Configuration loaded from {}", config_path.display());
    });

    // Create a display.
    //
    // The display manages a window and can draw the terminal
    let mut display = Display::new(&config, &options)?;

    info!(
        "PTY Dimensions: {:?} x {:?}",
        display.size().lines(),
        display.size().cols()
    );

    // Create the terminal
    //
    // This object contains all of the state about what's being displayed. It's
    // wrapped in a clonable mutex since both the I/O loop and display need to
    // access it.
    let terminal = Term::new(&config, display.size().to_owned());
    let terminal = Arc::new(FairMutex::new(terminal));

    // Find the window ID for setting $WINDOWID
    let window_id = display.get_window_id();

    // Create the pty
    //
    // The pty forks a process to run the shell on the slave side of the
    // pseudoterminal. A file descriptor for the master side is retained for
    // reading/writing to the shell.
    let mut pty = tty::new(&config, &options, display.size(), window_id);

    // Create the pseudoterminal I/O loop
    //
    // pty I/O is ran on another thread as to not occupy cycles used by the
    // renderer and input processing. Note that access to the terminal state is
    // synchronized since the I/O loop updates the state, and the display
    // consumes it periodically.
    let event_loop = EventLoop::new(
        Arc::clone(&terminal),
        display.notifier(),
        pty.reader(),
        options.ref_test,
    );

    // The event loop channel allows write requests from the event processor
    // to be sent to the loop and ultimately written to the pty.
    let loop_tx = event_loop.channel();

    // Event processor
    //
    // Need the Rc<RefCell<_>> here since a ref is shared in the resize callback
    let mut processor = event::Processor::new(
        event_loop::Notifier(event_loop.channel()),
        display.resize_channel(),
        &options,
        &config,
        options.ref_test,
        display.size().to_owned(),
    );

    // Create a config monitor when config was loaded from path
    //
    // The monitor watches the config file for changes and reloads it. Pending
    // config changes are processed in the main loop.
    let config_monitor = match (options.live_config_reload, config.live_config_reload()) {
        // Start monitor if CLI flag says yes
        (Some(true), _) |
        // Or if no CLI flag was passed and the config says yes
        (None, true) => config.path()
                .map(|path| config::Monitor::new(path, display.notifier())),
        // Otherwise, don't start the monitor
        _ => None,
    };

    // Kick off the I/O thread
    let io_thread = event_loop.spawn(None);

    // Main display loop
    loop {
        // Process input and window events
        let mut terminal = processor.process_events(&terminal, display.window());

        // Handle config reloads
        config_monitor.as_ref()
            .and_then(|monitor| monitor.pending_config())
            .map(|new_config| {
                config = new_config;
                display.update_config(&config);
                processor.update_config(&config);
                terminal.update_config(&config);
                terminal.dirty = true;
            });

        // Maybe draw the terminal
        if terminal.needs_draw() {
            // Try to update the position of the input method editor
            display.update_ime_position(&terminal);
            // Handle pending resize events
            //
            // The second argument is a list of types that want to be notified
            // of display size changes.
            display.handle_resize(&mut terminal, &config, &mut [&mut pty, &mut processor]);

            // Draw the current state of the terminal
            display.draw(terminal, &config, processor.selection.as_ref());
        }

        // Begin shutdown if the flag was raised.
        if process_should_exit() {
            break;
        }
    }

    loop_tx.send(Msg::Shutdown).expect("Error sending shutdown to event loop");

    // FIXME patch notify library to have a shutdown method
    // config_reloader.join().ok();

    // Wait for the I/O thread thread to finish
    let _ = io_thread.join();

    Ok(())
}
<|endoftext|>


<commit_before>//! Alacritty - The GPU Enhanced Terminal
#![feature(question_mark)]
#![feature(range_contains)]
#![feature(inclusive_range_syntax)]
#![feature(io)]
#![feature(unicode)]

extern crate fontconfig;
extern crate freetype;
extern crate libc;
extern crate glutin;
extern crate cgmath;
extern crate euclid;
extern crate notify;
extern crate arrayvec;

#[macro_use]
extern crate bitflags;

#[macro_use]
mod macros;

mod list_fonts;
mod text;
mod renderer;
mod grid;
mod meter;
mod tty;
mod ansi;
mod term;
mod util;

use std::sync::mpsc::TryRecvError;
use std::collections::HashMap;
use std::io::{BufReader, Read, BufRead, Write, BufWriter};
use std::sync::Arc;
use std::fs::File;

use std::os::unix::io::{FromRawFd, AsRawFd};

use renderer::{QuadRenderer, GlyphCache, LoadGlyph};
use text::FontDesc;
use grid::Grid;
use term::Term;
use meter::Meter;
use util::thread;

#[derive(Debug, Eq, PartialEq, Copy, Clone, Default)]
pub struct Rgb {
    r: u8,
    g: u8,
    b: u8,
}

mod gl {
    include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs"));
}

#[derive(Debug)]
struct TermProps {
    width: f32,
    height: f32,
    cell_width: f32,
    cell_height: f32,
    sep_x: f32,
    sep_y: f32,
}

fn main() {
    let window = glutin::WindowBuilder::new()
                     .with_title("alacritty".into())
                     .build()
                     .unwrap();

    let (width, height) = window.get_inner_size_pixels().unwrap();
    unsafe {
        window.make_current().unwrap();
    }

    unsafe {
        gl::load_with(|symbol| window.get_proc_address(symbol) as *const _);
        gl::Viewport(0, 0, width as i32, height as i32);
    }

    let (dpi_x, dpi_y) = window.get_dpi().unwrap();
    let dpr = window.hidpi_factor();

    let font_size = 11.;

    let sep_x = 2;
    let sep_y = 5;

    let desc = FontDesc::new("DejaVu Sans Mono", "Book");
    let mut rasterizer = text::Rasterizer::new(dpi_x, dpi_y, dpr);

    let (cell_width, cell_height) = rasterizer.box_size_for_font(&desc, font_size);

    let num_cols = grid::num_cells_axis(cell_width, sep_x, width);
    let num_rows = grid::num_cells_axis(cell_height, sep_y, height);

    let tty = tty::new(num_rows as u8, num_cols as u8);
    tty.resize(num_rows as usize, num_cols as usize, width as usize, height as usize);
    let mut reader = tty.reader();
    let mut writer = tty.writer();

    println!("num_cols, num_rows = {}, {}", num_cols, num_rows);

    let mut grid = Grid::new(num_rows as usize, num_cols as usize);

    let props = TermProps {
        cell_width: cell_width as f32,
        sep_x: sep_x as f32,
        cell_height: cell_height as f32,
        sep_y: sep_y as f32,
        height: height as f32,
        width: width as f32,
    };

    let mut renderer = QuadRenderer::new(width, height);

    let mut glyph_cache = GlyphCache::new(rasterizer, desc, font_size);
    renderer.with_api(&props, |mut api| {
        glyph_cache.init(&mut api);
    });

    unsafe {
        gl::Enable(gl::BLEND);
        gl::BlendFunc(gl::SRC1_COLOR, gl::ONE_MINUS_SRC1_COLOR);
        gl::Enable(gl::MULTISAMPLE);
    }

    let (chars_tx, chars_rx) = ::std::sync::mpsc::channel();
    thread::spawn_named("TTY Reader", move || {
        for c in reader.chars() {
            let c = c.unwrap();
            chars_tx.send(c).unwrap();
        }
    });

    let mut terminal = Term::new(tty, grid);
    let mut meter = Meter::new();

    let mut pty_parser = ansi::Parser::new();

    'main_loop: loop {
        // Handle keyboard/mouse input and other window events
        {
            let mut writer = BufWriter::new(&writer);
            for event in window.poll_events() {
                match event {
                    glutin::Event::Closed => break 'main_loop,
                    glutin::Event::ReceivedCharacter(c) => {
                        let encoded = c.encode_utf8();
                        writer.write(encoded.as_slice()).unwrap();
                    },
                    glutin::Event::KeyboardInput(state, _code, key) => {
                        match state {
                            glutin::ElementState::Pressed => {
                                match key {
                                    Some(glutin::VirtualKeyCode::Up) => {
                                        writer.write("\x1b[A".as_bytes()).unwrap();
                                    },
                                    Some(glutin::VirtualKeyCode::Down) => {
                                        writer.write("\x1b[B".as_bytes()).unwrap();
                                    },
                                    Some(glutin::VirtualKeyCode::Left) => {
                                        writer.write("\x1b[D".as_bytes()).unwrap();
                                    },
                                    Some(glutin::VirtualKeyCode::Right) => {
                                        writer.write("\x1b[C".as_bytes()).unwrap();
                                    },
                                    _ => (),
                                }
                            },
                            _ => (),
                        }
                    },
                    _ => ()
                }
            }
        }

        while let Ok(c) = chars_rx.try_recv() {
            pty_parser.advance(&mut terminal, c);
        }

        unsafe {
            gl::ClearColor(0.0, 0.0, 0.00, 1.0);
            gl::Clear(gl::COLOR_BUFFER_BIT);
        }

        {
            let _sampler = meter.sampler();

            renderer.with_api(&props, |mut api| {
                // Draw the grid
                api.render_grid(terminal.grid(), &mut glyph_cache);

                // Also draw the cursor
                api.render_cursor(terminal.cursor(), &mut glyph_cache);
            })
        }

        // Draw render timer
        let timing = format!("{:.3} usec", meter.average());
        let color = Rgb { r: 0xd5, g: 0x4e, b: 0x53 };
        renderer.with_api(&props, |mut api| {
            api.render_string(&timing[..], &mut glyph_cache, &color);
        });

        window.swap_buffers().unwrap();
    }
}

<commit_msg>Handle pty char recv errors<commit_after>//! Alacritty - The GPU Enhanced Terminal
#![feature(question_mark)]
#![feature(range_contains)]
#![feature(inclusive_range_syntax)]
#![feature(io)]
#![feature(unicode)]

extern crate fontconfig;
extern crate freetype;
extern crate libc;
extern crate glutin;
extern crate cgmath;
extern crate euclid;
extern crate notify;
extern crate arrayvec;

#[macro_use]
extern crate bitflags;

#[macro_use]
mod macros;

mod list_fonts;
mod text;
mod renderer;
mod grid;
mod meter;
mod tty;
mod ansi;
mod term;
mod util;

use std::sync::mpsc::TryRecvError;
use std::collections::HashMap;
use std::io::{BufReader, Read, BufRead, Write, BufWriter};
use std::sync::Arc;
use std::fs::File;

use std::os::unix::io::{FromRawFd, AsRawFd};

use renderer::{QuadRenderer, GlyphCache, LoadGlyph};
use text::FontDesc;
use grid::Grid;
use term::Term;
use meter::Meter;
use util::thread;

#[derive(Debug, Eq, PartialEq, Copy, Clone, Default)]
pub struct Rgb {
    r: u8,
    g: u8,
    b: u8,
}

mod gl {
    include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs"));
}

#[derive(Debug)]
struct TermProps {
    width: f32,
    height: f32,
    cell_width: f32,
    cell_height: f32,
    sep_x: f32,
    sep_y: f32,
}

fn main() {
    let window = glutin::WindowBuilder::new()
                     .with_title("alacritty".into())
                     .build()
                     .unwrap();

    let (width, height) = window.get_inner_size_pixels().unwrap();
    unsafe {
        window.make_current().unwrap();
    }

    unsafe {
        gl::load_with(|symbol| window.get_proc_address(symbol) as *const _);
        gl::Viewport(0, 0, width as i32, height as i32);
    }

    let (dpi_x, dpi_y) = window.get_dpi().unwrap();
    let dpr = window.hidpi_factor();

    let font_size = 11.;

    let sep_x = 2;
    let sep_y = 5;

    let desc = FontDesc::new("DejaVu Sans Mono", "Book");
    let mut rasterizer = text::Rasterizer::new(dpi_x, dpi_y, dpr);

    let (cell_width, cell_height) = rasterizer.box_size_for_font(&desc, font_size);

    let num_cols = grid::num_cells_axis(cell_width, sep_x, width);
    let num_rows = grid::num_cells_axis(cell_height, sep_y, height);

    let tty = tty::new(num_rows as u8, num_cols as u8);
    tty.resize(num_rows as usize, num_cols as usize, width as usize, height as usize);
    let mut reader = tty.reader();
    let mut writer = tty.writer();

    println!("num_cols, num_rows = {}, {}", num_cols, num_rows);

    let mut grid = Grid::new(num_rows as usize, num_cols as usize);

    let props = TermProps {
        cell_width: cell_width as f32,
        sep_x: sep_x as f32,
        cell_height: cell_height as f32,
        sep_y: sep_y as f32,
        height: height as f32,
        width: width as f32,
    };

    let mut renderer = QuadRenderer::new(width, height);

    let mut glyph_cache = GlyphCache::new(rasterizer, desc, font_size);
    renderer.with_api(&props, |mut api| {
        glyph_cache.init(&mut api);
    });

    unsafe {
        gl::Enable(gl::BLEND);
        gl::BlendFunc(gl::SRC1_COLOR, gl::ONE_MINUS_SRC1_COLOR);
        gl::Enable(gl::MULTISAMPLE);
    }

    let (chars_tx, chars_rx) = ::std::sync::mpsc::channel();
    thread::spawn_named("TTY Reader", move || {
        for c in reader.chars() {
            let c = c.unwrap();
            chars_tx.send(c).unwrap();
        }
    });

    let mut terminal = Term::new(tty, grid);
    let mut meter = Meter::new();

    let mut pty_parser = ansi::Parser::new();

    'main_loop: loop {
        // Handle keyboard/mouse input and other window events
        {
            let mut writer = BufWriter::new(&writer);
            for event in window.poll_events() {
                match event {
                    glutin::Event::Closed => break 'main_loop,
                    glutin::Event::ReceivedCharacter(c) => {
                        let encoded = c.encode_utf8();
                        writer.write(encoded.as_slice()).unwrap();
                    },
                    glutin::Event::KeyboardInput(state, _code, key) => {
                        match state {
                            glutin::ElementState::Pressed => {
                                match key {
                                    Some(glutin::VirtualKeyCode::Up) => {
                                        writer.write("\x1b[A".as_bytes()).unwrap();
                                    },
                                    Some(glutin::VirtualKeyCode::Down) => {
                                        writer.write("\x1b[B".as_bytes()).unwrap();
                                    },
                                    Some(glutin::VirtualKeyCode::Left) => {
                                        writer.write("\x1b[D".as_bytes()).unwrap();
                                    },
                                    Some(glutin::VirtualKeyCode::Right) => {
                                        writer.write("\x1b[C".as_bytes()).unwrap();
                                    },
                                    _ => (),
                                }
                            },
                            _ => (),
                        }
                    },
                    _ => ()
                }
            }
        }

        loop {
            match chars_rx.try_recv() {
                Ok(c) => pty_parser.advance(&mut terminal, c),
                Err(TryRecvError::Disconnected) => break 'main_loop,
                Err(TryRecvError::Empty) => break,
            }
        }

        unsafe {
            gl::ClearColor(0.0, 0.0, 0.00, 1.0);
            gl::Clear(gl::COLOR_BUFFER_BIT);
        }

        {
            let _sampler = meter.sampler();

            renderer.with_api(&props, |mut api| {
                // Draw the grid
                api.render_grid(terminal.grid(), &mut glyph_cache);

                // Also draw the cursor
                api.render_cursor(terminal.cursor(), &mut glyph_cache);
            })
        }

        // Draw render timer
        let timing = format!("{:.3} usec", meter.average());
        let color = Rgb { r: 0xd5, g: 0x4e, b: 0x53 };
        renderer.with_api(&props, |mut api| {
            api.render_string(&timing[..], &mut glyph_cache, &color);
        });

        window.swap_buffers().unwrap();
    }

    // TODO handle child cleanup
}

<|endoftext|>


<commit_before>// Copyright (C) 2016 Mark Lee

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.

// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

extern crate env_logger;
extern crate hyper;
#[macro_use] extern crate log;
extern crate redis;
extern crate regex;
extern crate rquery;
extern crate semver;
extern crate serde;
extern crate serde_json;

use hyper::{Client as HTTPClient, Get, Server};
use hyper::header::{Accept, ContentType};
use hyper::server::{Request, Response};
use hyper::uri::RequestUri::AbsolutePath;
use redis::Commands;
use regex::Regex;
use rquery::Document;
use semver::Version;
use std::collections::HashMap;
use std::env;
use std::fs::File;
use std::io;
use std::io::Read;

const DEFAULT_SERVER_PORT: u32 = 9000;
const MIN_BUNDLER_VERSION: &'static str = "1.11.0";
const RUBY_LANGPACK_RELEASES_URL: &'static str = "https://github.\
                                                  com/heroku/heroku-buildpack-ruby/releases.atom";

fn download_url(http: &HTTPClient, url: &str) -> io::Result<String> {
    let mut resp = http.get(url).send().expect("Could not send HTTP request");
    let mut body = String::new();
    match resp.read_to_string(&mut body) {
        Err(error) => Err(error),
        _ => Ok(body),
    }
}

fn download_latest_buildpack_release(http: &HTTPClient) -> String {
    let xml = download_url(&http, RUBY_LANGPACK_RELEASES_URL)
                  .expect("Could not download Atom releases!");
    let atom_doc = Document::new_from_xml_string(&xml[..]).expect("Could not parse Atom releases!");
    let latest_tag = atom_doc.select("entry title")
                             .expect("Could not find latest buildpack tag!");
    latest_tag.text().clone()
}

fn redis_cache_value(redis: &redis::Connection, key: &str, value: &str) -> String {
    let set_result: redis::RedisResult<()> = redis.set_ex(key, value, 3600);
    if let Err(_) = set_result {
        warn!("Cannot set {} in Redis, ignoring", key);
    }
    value.to_owned()
}

fn redis_cache_hash_value(redis: &redis::Connection, hash_name: &str, key: &str, value: &str) {
    let set_result: redis::RedisResult<()> = redis.hset_nx(hash_name, key, value);
    if let Err(_) = set_result {
        warn!("Cannot set {} in Redis, ignoring", key);
    }
}

fn latest_buildpack_release(http: &HTTPClient, maybe_redis: &Option<redis::Connection>) -> String {
    if let Some(ref redis) = *maybe_redis {
        if let Ok(buildpack_release) = redis.get("latest_buildpack_release") {
            buildpack_release
        } else {
            let buildpack_release = download_latest_buildpack_release(http);
            redis_cache_value(redis, "latest_buildpack_release", &buildpack_release[..])
        }
    } else {
        download_latest_buildpack_release(http)
    }
}

fn cached_version_from_buildpack_release(buildpack_release: &str,
                                         maybe_redis: &Option<redis::Connection>)
                                         -> Option<String> {
    if let Some(ref redis) = *maybe_redis {
        if let Ok(version) = redis.hget("bundler_version", buildpack_release) {
            Some(version)
        } else {
            None
        }
    } else {
        None
    }
}

fn bundler_version_from_ruby_buildpack(http: &HTTPClient,
                                       maybe_redis: Option<redis::Connection>)
                                       -> Option<String> {
    let buildpack_release = latest_buildpack_release(&http, &maybe_redis);
    if let Some(cached_version) = cached_version_from_buildpack_release(&buildpack_release[..],
                                                                        &maybe_redis) {
        return Some(cached_version);
    }
    let ruby_langpack_url = format!("https://raw.githubusercontent.com/\
                                     heroku/heroku-buildpack-ruby/{}/lib/language_pack/\
                                     ruby.rb",
                                    buildpack_release);
    let ruby_file = &download_url(&http, &ruby_langpack_url[..])
                         .expect("Could not download ruby.rb!")[..];
    let regex = Regex::new(r#"BUNDLER_VERSION += "(.+?)""#).expect("Invalid regular expression!");
    if regex.is_match(ruby_file) {
        let captures = regex.captures(ruby_file).expect("Could not match?!");
        let version = captures.at(1).expect("Capture not found?!");
        if let Some(redis) = maybe_redis {
            redis_cache_hash_value(&redis, "bundler_version", &buildpack_release[..], version);
            redis_cache_value(&redis, "latest_supported_bundler_version", version);
        }
        Some(version.to_owned())
    } else {
        None
    }
}

fn is_bundler_upgraded(http: &HTTPClient,
                       redis_result: redis::RedisResult<redis::Connection>)
                       -> bool {
    let bundler_version_result = if let Ok(redis) = redis_result {
        match redis.get("latest_supported_bundler_version") {
            Ok(version) => Some(version),
            _ => bundler_version_from_ruby_buildpack(&http, Some(redis)),
        }
    } else {
        bundler_version_from_ruby_buildpack(&http, None)
    };
    if let Some(buildpack_bundler_version_str) = bundler_version_result {
        let min_version = Version::parse(MIN_BUNDLER_VERSION)
                              .expect("Could not parse min version!");
        let new_version = Version::parse(&buildpack_bundler_version_str[..])
                              .expect("Could not parse new version!");
        min_version < new_version
    } else {
        false
    }
}

fn determine_content_type(req: &Request) -> ContentType {
    if let Some(accept) = req.headers.get::<Accept>() {
        let mut use_json = false;
        for qitem in &accept.0 {
            let qitem_str = &format!("{}", qitem)[..];
            if qitem_str == "application/json" {
                use_json = true;
                break;
            }
        }

        if use_json {
            ContentType::json()
        } else {
            ContentType::html()
        }
    } else {
        ContentType::html()
    }
}

fn result_to_json(result: bool) -> String {
    let mut map = HashMap::new();
    map.insert("result".to_owned(), result);
    serde_json::to_string(&map).expect("Could not serialize result!")
}

fn result_to_html(result: bool) -> String {
    let result_str = if result {
        r#"
Yes
"#
    } else {
        r#"
No
"#
    };
    match File::open("index.html") {
        Ok(mut html_file) => {
            let mut html = String::new();
            html_file.read_to_string(&mut html).expect("Could not read HTML file!");
            html.replace("{{ is_bundler_upgraded }}", result_str)
                .replace("{{ MIN_BUNDLER_VERSION }}", MIN_BUNDLER_VERSION)
        }
        Err(error) => format!("HTML NOT FOUND: {}", error),
    }
}

fn server_port() -> String {
    match env::var("PORT") {
        Ok(port) => port,
        _ => format!("{}", DEFAULT_SERVER_PORT),
    }
}

fn connect_to_redis() -> redis::RedisResult<redis::Connection> {
    if let Ok(redis_url) = env::var("REDIS_URL") {
        let client = redis::Client::open(&redis_url[..]).expect("Cannot connect to Redis");
        client.get_connection()
    } else {
        Err(redis::RedisError::from(io::Error::new(io::ErrorKind::Other, "REDIS_URL not found")))
    }
}

fn main() {
    env_logger::init().expect("Could not initialize env_logger!");
    let server = Server::http(&format!("0.0.0.0:{}", server_port())[..])
                     .expect("Could not create server!");
    server.handle(|req: Request, mut res: Response| {
              if let AbsolutePath(ref path) = req.uri {
                  if *path == "/" {
                      if req.method == Get {
                          let http = HTTPClient::new();
                          let redis = connect_to_redis();
                          let result = is_bundler_upgraded(&http, redis);
                          let content_type = determine_content_type(&req);
                          let data = match &format!("{}", content_type)[..] {
                              "application/json; charset=utf-8" => result_to_json(result),
                              _ => result_to_html(result),
                          };
                          res.headers_mut().set(content_type);
                          res.send(data.as_bytes()).expect("Could not set response body!");
                      } else {
                          *res.status_mut() = hyper::status::StatusCode::MethodNotAllowed;
                      }
                  } else {
                      *res.status_mut() = hyper::NotFound;
                  }
              } else {
                  *res.status_mut() = hyper::BadRequest;
              }
          })
          .expect("Could not set up HTTP request handler!");
}
<commit_msg>Rearrange code for less indentation<commit_after>// Copyright (C) 2016 Mark Lee

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.

// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

extern crate env_logger;
extern crate hyper;
#[macro_use]
extern crate log;
extern crate redis;
extern crate regex;
extern crate rquery;
extern crate semver;
extern crate serde;
extern crate serde_json;

use hyper::{Client as HTTPClient, Get, Server};
use hyper::header::{Accept, ContentType};
use hyper::server::{Request, Response};
use hyper::uri::RequestUri::AbsolutePath;
use redis::Commands;
use regex::Regex;
use rquery::Document;
use semver::Version;
use std::collections::HashMap;
use std::env;
use std::fs::File;
use std::io;
use std::io::Read;

const DEFAULT_SERVER_PORT: u32 = 9000;
const MIN_BUNDLER_VERSION: &'static str = "1.11.0";
const RUBY_LANGPACK_RELEASES_URL: &'static str = "https://github.\
                                                  com/heroku/heroku-buildpack-ruby/releases.atom";

fn download_url(http: &HTTPClient, url: &str) -> io::Result<String> {
    let mut resp = http.get(url).send().expect("Could not send HTTP request");
    let mut body = String::new();
    match resp.read_to_string(&mut body) {
        Err(error) => Err(error),
        _ => Ok(body),
    }
}

fn download_latest_buildpack_release(http: &HTTPClient) -> String {
    let xml = download_url(&http, RUBY_LANGPACK_RELEASES_URL)
                  .expect("Could not download Atom releases!");
    let atom_doc = Document::new_from_xml_string(&xml[..]).expect("Could not parse Atom releases!");
    let latest_tag = atom_doc.select("entry title")
                             .expect("Could not find latest buildpack tag!");
    latest_tag.text().clone()
}

fn redis_cache_value(redis: &redis::Connection, key: &str, value: &str) -> String {
    let set_result: redis::RedisResult<()> = redis.set_ex(key, value, 3600);
    if let Err(_) = set_result {
        warn!("Cannot set {} in Redis, ignoring", key);
    }
    value.to_owned()
}

fn redis_cache_hash_value(redis: &redis::Connection, hash_name: &str, key: &str, value: &str) {
    let set_result: redis::RedisResult<()> = redis.hset_nx(hash_name, key, value);
    if let Err(_) = set_result {
        warn!("Cannot set {} in Redis, ignoring", key);
    }
}

fn latest_buildpack_release(http: &HTTPClient, maybe_redis: &Option<redis::Connection>) -> String {
    if let Some(ref redis) = *maybe_redis {
        if let Ok(buildpack_release) = redis.get("latest_buildpack_release") {
            buildpack_release
        } else {
            let buildpack_release = download_latest_buildpack_release(http);
            redis_cache_value(redis, "latest_buildpack_release", &buildpack_release[..])
        }
    } else {
        download_latest_buildpack_release(http)
    }
}

fn cached_version_from_buildpack_release(buildpack_release: &str,
                                         maybe_redis: &Option<redis::Connection>)
                                         -> Option<String> {
    if let Some(ref redis) = *maybe_redis {
        if let Ok(version) = redis.hget("bundler_version", buildpack_release) {
            Some(version)
        } else {
            None
        }
    } else {
        None
    }
}

fn bundler_version_from_ruby_buildpack(http: &HTTPClient,
                                       maybe_redis: Option<redis::Connection>)
                                       -> Option<String> {
    let buildpack_release = latest_buildpack_release(&http, &maybe_redis);
    if let Some(cached_version) = cached_version_from_buildpack_release(&buildpack_release[..],
                                                                        &maybe_redis) {
        return Some(cached_version);
    }
    let ruby_langpack_url = format!("https://raw.githubusercontent.com/\
                                     heroku/heroku-buildpack-ruby/{}/lib/language_pack/\
                                     ruby.rb",
                                    buildpack_release);
    let ruby_file = &download_url(&http, &ruby_langpack_url[..])
                         .expect("Could not download ruby.rb!")[..];
    let regex = Regex::new(r#"BUNDLER_VERSION += "(.+?)""#).expect("Invalid regular expression!");
    if regex.is_match(ruby_file) {
        let captures = regex.captures(ruby_file).expect("Could not match?!");
        let version = captures.at(1).expect("Capture not found?!");
        if let Some(redis) = maybe_redis {
            redis_cache_hash_value(&redis, "bundler_version", &buildpack_release[..], version);
            redis_cache_value(&redis, "latest_supported_bundler_version", version);
        }
        Some(version.to_owned())
    } else {
        None
    }
}

fn is_bundler_upgraded(http: &HTTPClient,
                       redis_result: redis::RedisResult<redis::Connection>)
                       -> bool {
    let bundler_version_result = if let Ok(redis) = redis_result {
        match redis.get("latest_supported_bundler_version") {
            Ok(version) => Some(version),
            _ => bundler_version_from_ruby_buildpack(&http, Some(redis)),
        }
    } else {
        bundler_version_from_ruby_buildpack(&http, None)
    };
    if let Some(buildpack_bundler_version_str) = bundler_version_result {
        let min_version = Version::parse(MIN_BUNDLER_VERSION)
                              .expect("Could not parse min version!");
        let new_version = Version::parse(&buildpack_bundler_version_str[..])
                              .expect("Could not parse new version!");
        min_version < new_version
    } else {
        false
    }
}

fn determine_content_type(req: &Request) -> ContentType {
    if let Some(accept) = req.headers.get::<Accept>() {
        let mut use_json = false;
        for qitem in &accept.0 {
            let qitem_str = &format!("{}", qitem)[..];
            if qitem_str == "application/json" {
                use_json = true;
                break;
            }
        }

        if use_json {
            ContentType::json()
        } else {
            ContentType::html()
        }
    } else {
        ContentType::html()
    }
}

fn result_to_json(result: bool) -> String {
    let mut map = HashMap::new();
    map.insert("result".to_owned(), result);
    serde_json::to_string(&map).expect("Could not serialize result!")
}

fn result_to_html(result: bool) -> String {
    let result_str = if result {
        r#"
Yes
"#
    } else {
        r#"
No
"#
    };
    match File::open("index.html") {
        Ok(mut html_file) => {
            let mut html = String::new();
            html_file.read_to_string(&mut html).expect("Could not read HTML file!");
            html.replace("{{ is_bundler_upgraded }}", result_str)
                .replace("{{ MIN_BUNDLER_VERSION }}", MIN_BUNDLER_VERSION)
        }
        Err(error) => format!("HTML NOT FOUND: {}", error),
    }
}

fn server_port() -> String {
    match env::var("PORT") {
        Ok(port) => port,
        _ => format!("{}", DEFAULT_SERVER_PORT),
    }
}

fn connect_to_redis() -> redis::RedisResult<redis::Connection> {
    if let Ok(redis_url) = env::var("REDIS_URL") {
        let client = redis::Client::open(&redis_url[..]).expect("Cannot connect to Redis");
        client.get_connection()
    } else {
        Err(redis::RedisError::from(io::Error::new(io::ErrorKind::Other, "REDIS_URL not found")))
    }
}

fn request_handler(req: Request, mut res: Response) {
    if let AbsolutePath(ref path) = req.uri {
        if *path == "/" {
            if req.method == Get {
                let http = HTTPClient::new();
                let redis = connect_to_redis();
                let result = is_bundler_upgraded(&http, redis);
                let content_type = determine_content_type(&req);
                let data = match &format!("{}", content_type)[..] {
                    "application/json; charset=utf-8" => result_to_json(result),
                    _ => result_to_html(result),
                };
                res.headers_mut().set(content_type);
                res.send(data.as_bytes()).expect("Could not set response body!");
            } else {
                *res.status_mut() = hyper::status::StatusCode::MethodNotAllowed;
            }
        } else {
            *res.status_mut() = hyper::NotFound;
        }
    } else {
        *res.status_mut() = hyper::BadRequest;
    }
}

fn main() {
    env_logger::init().expect("Could not initialize env_logger!");
    let server = Server::http(&format!("0.0.0.0:{}", server_port())[..])
                     .expect("Could not create server!");
    server.handle(request_handler).expect("Could not set up HTTP request handler!");
}
<|endoftext|>


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

//! A Scheme implementation, in Rust.

#![feature(collections)]
#![feature(core)]
#![feature(env)]
#![feature(hash)]
#![feature(io)]
#![feature(os)]
#![feature(path)]
#![feature(test)]
#![feature(unicode)]
#![feature(unsafe_destructor)]

use std::old_io;
use std::env;

pub mod environment;
pub mod eval;
pub mod heap;
pub mod primitives;
pub mod read;
pub mod value;

/// Start a Read -> Evaluate -> Print loop.
pub fn repl(heap: &mut heap::Heap) {
    println!("Welcome to oxischeme!");
    println!("C-c to exit.");
    println!("");

    loop {
        let stdin = old_io::stdio::stdin();
        let reader = read::Read::new(stdin, heap, "stdin".to_string());

        print!("oxischeme> ");
        for (_, read_result) in reader {
            match read_result {
                Err(msg) => {
                    println!("{}", msg);
                    break;
                },
                Ok(form) => {
                    match eval::evaluate(heap, &form) {
                        Ok(val) => println!("{}", *val),
                        Err(e)  => println!("Error: {}", e),
                    };

                }
            }

            heap.collect_garbage();
            print!("oxischeme> ");
        }
    }
}

/// Given no arguments, start the REPL. Otherwise, treat each argument as a file
/// path and read and evaluate each of them in turn.
pub fn main() {
    let heap = &mut heap::Heap::new();

    let mut args_were_passed = false;

    for file_path in env::args().skip(1) {
        args_were_passed = true;

        let file_path_str = file_path.into_string().ok()
            .expect("Expect command line arguments to be valid strings");

        match eval::evaluate_file(heap, file_path_str.as_slice()) {
            Ok(_) => { },
            Err(msg) => {
                let mut stderr = old_io::stdio::stderr();
                (write!(&mut stderr, "Error: {}", msg)).ok().expect("IO ERROR!");
                return;
            }
        }
    }

    if !args_were_passed {
        repl(heap);
    }
}
<commit_msg>Upgrade to latest rustc nightly<commit_after>// Copyright 2014 Nick Fitzgerald
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! A Scheme implementation, in Rust.

#![feature(collections)]
#![feature(core)]
#![feature(env)]
#![feature(hash)]
#![feature(io)]
#![feature(path)]
#![feature(test)]
#![feature(unicode)]
#![feature(unsafe_destructor)]

use std::old_io;
use std::env;

pub mod environment;
pub mod eval;
pub mod heap;
pub mod primitives;
pub mod read;
pub mod value;

/// Start a Read -> Evaluate -> Print loop.
pub fn repl(heap: &mut heap::Heap) {
    println!("Welcome to oxischeme!");
    println!("C-c to exit.");
    println!("");

    loop {
        let stdin = old_io::stdio::stdin();
        let reader = read::Read::new(stdin, heap, "stdin".to_string());

        print!("oxischeme> ");
        for (_, read_result) in reader {
            match read_result {
                Err(msg) => {
                    println!("{}", msg);
                    break;
                },
                Ok(form) => {
                    match eval::evaluate(heap, &form) {
                        Ok(val) => println!("{}", *val),
                        Err(e)  => println!("Error: {}", e),
                    };

                }
            }

            heap.collect_garbage();
            print!("oxischeme> ");
        }
    }
}

/// Given no arguments, start the REPL. Otherwise, treat each argument as a file
/// path and read and evaluate each of them in turn.
pub fn main() {
    let heap = &mut heap::Heap::new();

    let mut args_were_passed = false;

    for file_path in env::args().skip(1) {
        args_were_passed = true;

        match eval::evaluate_file(heap, file_path.as_slice()) {
            Ok(_) => { },
            Err(msg) => {
                let mut stderr = old_io::stdio::stderr();
                (write!(&mut stderr, "Error: {}", msg)).ok().expect("IO ERROR!");
                return;
            }
        }
    }

    if !args_were_passed {
        repl(heap);
    }
}
<|endoftext|>