<commit_before>use std::cmp::min;
use std::io;
use std::io::prelude::*;
use std::slice::bytes;

use lzw;

use traits::WriteBytesExt;
use types::{Block, Frame, Extension, DisposalMethod};

pub enum ExtensionData {
	Control { flags: u8, delay: u16, trns: u8 }
}

impl ExtensionData {
	pub fn new_control_ext(delay: u16, dispose: DisposalMethod,
						   needs_user_input: bool, trns: Option<u8>) -> ExtensionData {
		let mut flags = 0;
		let trns = match trns {
			Some(trns) => {
				flags |= 1;
				trns as u8
			},
			None => 0
		};
		flags |= (needs_user_input as u8) << 1;
		flags |= (dispose as u8) << 2;
		ExtensionData::Control {
			flags: flags,
			delay: delay,
			trns: trns
		}
	}
}

struct BlockWriter<'a, W: Write + 'a> {
	w: &'a mut W,
	bytes: usize,
	buf: [u8; 0xFF]
}

impl<'a, W: Write + 'a> BlockWriter<'a, W> {
	fn new(w: &'a mut W) -> BlockWriter<'a, W> {
		BlockWriter {
			w: w,
			bytes: 0,
			buf: [0; 0xFF]
		}
	}
}

impl<'a, W: Write + 'a> Write for BlockWriter<'a, W> {

	fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
		let to_copy = min(buf.len(), 0xFF - self.bytes);
		bytes::copy_memory(&buf[..to_copy], &mut self.buf[self.bytes..]);
		self.bytes += to_copy;
		if self.bytes == 0xFF {
			self.bytes = 0;
			try!(self.w.write_le(0xFFu8));
			try!(self.w.write_all(&self.buf));
		}
		Ok(to_copy)
	}
	fn flush(&mut self) -> io::Result<()> {
		return Err(io::Error::new(
			io::ErrorKind::Other,
			"Cannot flush a BlockWriter, use `drop` instead."
		))
	}
}

impl<'a, W: Write + 'a> Drop for BlockWriter<'a, W> {

    #[cfg(feature = "raii_no_panic")]
	fn drop(&mut self) {
		if self.bytes > 0 {
			let _ = self.w.write_le(self.bytes as u8);
			let _ = self.w.write_all(&self.buf[..self.bytes]);
		}
	}

    #[cfg(not(feature = "raii_no_panic"))]
	fn drop(&mut self) {
		if self.bytes > 0 {
			self.w.write_le(self.bytes as u8).unwrap();
			self.w.write_all(&self.buf[..self.bytes]).unwrap();
		}
	}
}

pub struct Encoder<'a, W: Write + 'a> {
	w: &'a mut W,
	global_palette: bool,
	width: u16,
	height: u16
}

pub struct HeaderWritten<'a, W: Write + 'a> {
	enc: Encoder<'a, W>
}

impl<'a, W: Write + 'a> HeaderWritten<'a, W> {
	/// Writes a complete frame to the image
	///
	/// Note: This function also writes a control extention if necessary.
	pub fn write_frame(&mut self, frame: &Frame) -> io::Result<()> {
		self.enc.write_frame(frame)
	}

	/// Writes an extension to the image
	pub fn write_extension(&mut self, extension: ExtensionData) -> io::Result<()> {
		self.enc.write_extension(extension)
	}

	/// Writes a raw extension to the image
	pub fn write_raw_extension(&mut self, func: u8, data: &[u8]) -> io::Result<()> {
		self.enc.write_raw_extension(func, data)
	}
}

impl<'a, W: Write + 'a> Drop for HeaderWritten<'a, W> {

    #[cfg(feature = "raii_no_panic")]
	fn drop(&mut self) {
		let _ = self.enc.w.write_le(Block::Trailer as u8);
	}

    #[cfg(not(feature = "raii_no_panic"))]
	fn drop(&mut self) {
		self.enc.w.write_le(Block::Trailer as u8).unwrap()
	}
}

/*

pub struct Frame {
    pub delay: u16,
    pub dispose: DisposalMethod,
    pub transparent: Option<usize>,
    pub needs_user_input: bool,
    pub top: u16,
    pub left: u16,
    pub width: u16,
    pub height: u16,
    pub interlaced: bool,
    pub palette: Option<Vec<u8>>,
    pub buffer: Vec<u8>
}

*/
impl<'a, W: Write + 'a> Encoder<'a, W> {
	pub fn new(w: &'a mut W, width: u16, height: u16) -> Self {
		Encoder {
			w: w,
			global_palette: false,
			width: width,
			height: height
		}
	}

	/// Writes the global color palette
	pub fn write_global_palette(mut self, palette: &[u8]) -> io::Result<HeaderWritten<'a, W>> {
		self.global_palette = true;
		let mut flags = 0;
		flags |= 0b1000_0000;
		let num_colors = palette.len() / 3;
		flags |= flag_size(num_colors);
		flags |= flag_size(num_colors) << 4; // wtf flag
		try!(self.write_screen_desc(flags));
		try!(self.write_color_table(palette));
		Ok(HeaderWritten {
			enc: self
		})
	}

	/// Writes a complete frame to the image
	///
	/// Note: This function also writes a control extension if necessary.
	fn write_frame(&mut self, frame: &Frame) -> io::Result<()> {
		// TODO commented off to pass test in lib.rs
		//if frame.delay > 0 || frame.transparent.is_some() {
			try!(self.write_extension(ExtensionData::new_control_ext(
				frame.delay,
				frame.dispose,
				frame.needs_user_input,
				frame.transparent

			)));
		//}
		try!(self.w.write_le(Block::Image as u8));
		try!(self.w.write_le(frame.left));
		try!(self.w.write_le(frame.top));
		try!(self.w.write_le(frame.width));
		try!(self.w.write_le(frame.height));
		let mut flags = 0;
		try!(match frame.palette {
			Some(ref palette) => {
				flags |= 0b1000_0000;
				let num_colors = palette.len() / 3;
				flags |= flag_size(num_colors);
				try!(self.w.write_le(flags));
				self.write_color_table(palette)
			},
			None => if !self.global_palette {
				return Err(io::Error::new(
					io::ErrorKind::InvalidInput,
					"The GIF format requires a color palette but none was given."
				))
			} else {
				self.w.write_le(flags)
			}
		});
		self.write_image_block(&frame.buffer)
	}

	fn write_image_block(&mut self, data: &[u8]) -> io::Result<()> {
		{
			let min_code_size: u8 = flag_size((*data.iter().max().unwrap_or(&0) + 1) as usize) + 1;
			try!(self.w.write_le(min_code_size));
			let mut bw = BlockWriter::new(&mut self.w);
			let mut enc = try!(lzw::Encoder::new(lzw::LsbWriter::new(&mut bw), min_code_size));
			try!(enc.encode_bytes(data));
		}
		self.w.write_le(0u8)
	}

	fn write_color_table(&mut self, table: &[u8]) -> io::Result<()> {
		let num_colors = table.len() / 3;
        let size = flag_size(num_colors);
		try!(self.w.write_all(&table[..num_colors * 3]));
        // Waste some space as of gif spec
        for _ in 0..((2 << size) - num_colors) {
            try!(self.w.write_all(&[0, 0, 0]))
        }
        Ok(())
	}

	/// Writes an extension to the image
	fn write_extension(&mut self, extension: ExtensionData) -> io::Result<()> {
		use self::ExtensionData::*;
		try!(self.w.write_le(Block::Extension as u8));
		match extension {
			Control { flags, delay, trns } => {
				try!(self.w.write_le(Extension::Control as u8));
				try!(self.w.write_le(4u8));
				try!(self.w.write_le(flags));
				try!(self.w.write_le(delay));
				try!(self.w.write_le(trns));
			}
		}
		self.w.write_le(0u8)
	}

	/// Writes a raw extension to the image
	fn write_raw_extension(&mut self, func: u8, data: &[u8]) -> io::Result<()> {
		try!(self.w.write_le(Block::Extension as u8));
		try!(self.w.write_le(func as u8));
		for chunk in data.chunks(0xFF) {
			try!(self.w.write_le(chunk.len() as u8));
			try!(self.w.write_all(chunk));
		}
		self.w.write_le(0u8)
	}

	/// Writes the logical screen desriptor
	fn write_screen_desc(&mut self, flags: u8) -> io::Result<()> {
		try!(self.w.write_all(b"GIF89a"));
		try!(self.w.write_le(self.width));
		try!(self.w.write_le(self.height));
		try!(self.w.write_le(flags)); // packed field
		try!(self.w.write_le(0u8)); // bg index
		self.w.write_le(0u8) // aspect ratio
	}
}

// Color table size converted to flag bits
fn flag_size(size: usize) -> u8 {
    match size {
        0  ...2   => 0,
        3  ...4   => 1,
        5  ...8   => 2,
        7  ...16  => 3,
        17 ...32  => 4,
        33 ...64  => 5,
        65 ...128 => 6,
        129...256 => 7,
        _ => 7
    }
}<commit_msg>fix overflow<commit_after>use std::cmp::min;
use std::io;
use std::io::prelude::*;
use std::slice::bytes;

use lzw;

use traits::WriteBytesExt;
use types::{Block, Frame, Extension, DisposalMethod};

pub enum ExtensionData {
	Control { flags: u8, delay: u16, trns: u8 }
}

impl ExtensionData {
	pub fn new_control_ext(delay: u16, dispose: DisposalMethod,
						   needs_user_input: bool, trns: Option<u8>) -> ExtensionData {
		let mut flags = 0;
		let trns = match trns {
			Some(trns) => {
				flags |= 1;
				trns as u8
			},
			None => 0
		};
		flags |= (needs_user_input as u8) << 1;
		flags |= (dispose as u8) << 2;
		ExtensionData::Control {
			flags: flags,
			delay: delay,
			trns: trns
		}
	}
}

struct BlockWriter<'a, W: Write + 'a> {
	w: &'a mut W,
	bytes: usize,
	buf: [u8; 0xFF]
}

impl<'a, W: Write + 'a> BlockWriter<'a, W> {
	fn new(w: &'a mut W) -> BlockWriter<'a, W> {
		BlockWriter {
			w: w,
			bytes: 0,
			buf: [0; 0xFF]
		}
	}
}

impl<'a, W: Write + 'a> Write for BlockWriter<'a, W> {

	fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
		let to_copy = min(buf.len(), 0xFF - self.bytes);
		bytes::copy_memory(&buf[..to_copy], &mut self.buf[self.bytes..]);
		self.bytes += to_copy;
		if self.bytes == 0xFF {
			self.bytes = 0;
			try!(self.w.write_le(0xFFu8));
			try!(self.w.write_all(&self.buf));
		}
		Ok(to_copy)
	}
	fn flush(&mut self) -> io::Result<()> {
		return Err(io::Error::new(
			io::ErrorKind::Other,
			"Cannot flush a BlockWriter, use `drop` instead."
		))
	}
}

impl<'a, W: Write + 'a> Drop for BlockWriter<'a, W> {

    #[cfg(feature = "raii_no_panic")]
	fn drop(&mut self) {
		if self.bytes > 0 {
			let _ = self.w.write_le(self.bytes as u8);
			let _ = self.w.write_all(&self.buf[..self.bytes]);
		}
	}

    #[cfg(not(feature = "raii_no_panic"))]
	fn drop(&mut self) {
		if self.bytes > 0 {
			self.w.write_le(self.bytes as u8).unwrap();
			self.w.write_all(&self.buf[..self.bytes]).unwrap();
		}
	}
}

pub struct Encoder<'a, W: Write + 'a> {
	w: &'a mut W,
	global_palette: bool,
	width: u16,
	height: u16
}

pub struct HeaderWritten<'a, W: Write + 'a> {
	enc: Encoder<'a, W>
}

impl<'a, W: Write + 'a> HeaderWritten<'a, W> {
	/// Writes a complete frame to the image
	///
	/// Note: This function also writes a control extention if necessary.
	pub fn write_frame(&mut self, frame: &Frame) -> io::Result<()> {
		self.enc.write_frame(frame)
	}

	/// Writes an extension to the image
	pub fn write_extension(&mut self, extension: ExtensionData) -> io::Result<()> {
		self.enc.write_extension(extension)
	}

	/// Writes a raw extension to the image
	pub fn write_raw_extension(&mut self, func: u8, data: &[u8]) -> io::Result<()> {
		self.enc.write_raw_extension(func, data)
	}
}

impl<'a, W: Write + 'a> Drop for HeaderWritten<'a, W> {

    #[cfg(feature = "raii_no_panic")]
	fn drop(&mut self) {
		let _ = self.enc.w.write_le(Block::Trailer as u8);
	}

    #[cfg(not(feature = "raii_no_panic"))]
	fn drop(&mut self) {
		self.enc.w.write_le(Block::Trailer as u8).unwrap()
	}
}

/*

pub struct Frame {
    pub delay: u16,
    pub dispose: DisposalMethod,
    pub transparent: Option<usize>,
    pub needs_user_input: bool,
    pub top: u16,
    pub left: u16,
    pub width: u16,
    pub height: u16,
    pub interlaced: bool,
    pub palette: Option<Vec<u8>>,
    pub buffer: Vec<u8>
}

*/
impl<'a, W: Write + 'a> Encoder<'a, W> {
	pub fn new(w: &'a mut W, width: u16, height: u16) -> Self {
		Encoder {
			w: w,
			global_palette: false,
			width: width,
			height: height
		}
	}

	/// Writes the global color palette
	pub fn write_global_palette(mut self, palette: &[u8]) -> io::Result<HeaderWritten<'a, W>> {
		self.global_palette = true;
		let mut flags = 0;
		flags |= 0b1000_0000;
		let num_colors = palette.len() / 3;
		flags |= flag_size(num_colors);
		flags |= flag_size(num_colors) << 4; // wtf flag
		try!(self.write_screen_desc(flags));
		try!(self.write_color_table(palette));
		Ok(HeaderWritten {
			enc: self
		})
	}

	/// Writes a complete frame to the image
	///
	/// Note: This function also writes a control extension if necessary.
	fn write_frame(&mut self, frame: &Frame) -> io::Result<()> {
		// TODO commented off to pass test in lib.rs
		//if frame.delay > 0 || frame.transparent.is_some() {
			try!(self.write_extension(ExtensionData::new_control_ext(
				frame.delay,
				frame.dispose,
				frame.needs_user_input,
				frame.transparent

			)));
		//}
		try!(self.w.write_le(Block::Image as u8));
		try!(self.w.write_le(frame.left));
		try!(self.w.write_le(frame.top));
		try!(self.w.write_le(frame.width));
		try!(self.w.write_le(frame.height));
		let mut flags = 0;
		try!(match frame.palette {
			Some(ref palette) => {
				flags |= 0b1000_0000;
				let num_colors = palette.len() / 3;
				flags |= flag_size(num_colors);
				try!(self.w.write_le(flags));
				self.write_color_table(palette)
			},
			None => if !self.global_palette {
				return Err(io::Error::new(
					io::ErrorKind::InvalidInput,
					"The GIF format requires a color palette but none was given."
				))
			} else {
				self.w.write_le(flags)
			}
		});
		self.write_image_block(&frame.buffer)
	}

	fn write_image_block(&mut self, data: &[u8]) -> io::Result<()> {
		{
			let min_code_size: u8 = flag_size((*data.iter().max().unwrap_or(&0) as usize + 1)) + 1;
			try!(self.w.write_le(min_code_size));
			let mut bw = BlockWriter::new(&mut self.w);
			let mut enc = try!(lzw::Encoder::new(lzw::LsbWriter::new(&mut bw), min_code_size));
			try!(enc.encode_bytes(data));
		}
		self.w.write_le(0u8)
	}

	fn write_color_table(&mut self, table: &[u8]) -> io::Result<()> {
		let num_colors = table.len() / 3;
        let size = flag_size(num_colors);
		try!(self.w.write_all(&table[..num_colors * 3]));
        // Waste some space as of gif spec
        for _ in 0..((2 << size) - num_colors) {
            try!(self.w.write_all(&[0, 0, 0]))
        }
        Ok(())
	}

	/// Writes an extension to the image
	fn write_extension(&mut self, extension: ExtensionData) -> io::Result<()> {
		use self::ExtensionData::*;
		try!(self.w.write_le(Block::Extension as u8));
		match extension {
			Control { flags, delay, trns } => {
				try!(self.w.write_le(Extension::Control as u8));
				try!(self.w.write_le(4u8));
				try!(self.w.write_le(flags));
				try!(self.w.write_le(delay));
				try!(self.w.write_le(trns));
			}
		}
		self.w.write_le(0u8)
	}

	/// Writes a raw extension to the image
	fn write_raw_extension(&mut self, func: u8, data: &[u8]) -> io::Result<()> {
		try!(self.w.write_le(Block::Extension as u8));
		try!(self.w.write_le(func as u8));
		for chunk in data.chunks(0xFF) {
			try!(self.w.write_le(chunk.len() as u8));
			try!(self.w.write_all(chunk));
		}
		self.w.write_le(0u8)
	}

	/// Writes the logical screen desriptor
	fn write_screen_desc(&mut self, flags: u8) -> io::Result<()> {
		try!(self.w.write_all(b"GIF89a"));
		try!(self.w.write_le(self.width));
		try!(self.w.write_le(self.height));
		try!(self.w.write_le(flags)); // packed field
		try!(self.w.write_le(0u8)); // bg index
		self.w.write_le(0u8) // aspect ratio
	}
}

// Color table size converted to flag bits
fn flag_size(size: usize) -> u8 {
    match size {
        0  ...2   => 0,
        3  ...4   => 1,
        5  ...8   => 2,
        7  ...16  => 3,
        17 ...32  => 4,
        33 ...64  => 5,
        65 ...128 => 6,
        129...256 => 7,
        _ => 7
    }
}<|endoftext|>


<commit_before>#![deny(warnings)]

extern crate cobalt;
extern crate clap;
extern crate env_logger;
extern crate notify;
extern crate glob;
extern crate ghp;

extern crate hyper;

#[macro_use]
extern crate log;

use clap::{Arg, App, SubCommand, AppSettings};
use std::fs;
use cobalt::Config;
use log::{LogRecord, LogLevelFilter};
use env_logger::LogBuilder;
use hyper::server::{Server, Request, Response};
use hyper::uri::RequestUri;
use ghp::import_dir;
use glob::Pattern;
use cobalt::create_new_project;

use notify::{RecommendedWatcher, Error, Watcher};
use std::sync::mpsc::channel;
use std::thread;
use std::path::PathBuf;
use std::io::prelude::*;
use std::io::Result as IoResult;
use std::fs::File;

fn main() {
    let matches = App::new("Cobalt")
        .version("0.3.0")
        .author("Benny Klotz <r3qnbenni@gmail.com>, Johann Hofmann")
        .about("A static site generator written in Rust.")
        .setting(AppSettings::SubcommandRequired)
        .setting(AppSettings::GlobalVersion)
        .arg(Arg::with_name("config")
            .short("c")
            .long("config")
            .value_name("FILE")
            .help("Config file to use")
            .default_value(".cobalt.yml")
            .takes_value(true))
        .arg(Arg::with_name("source")
            .short("s")
            .long("source")
            .value_name("DIR")
            .help("Source folder [default: ./]")
            .takes_value(true))
        .arg(Arg::with_name("destination")
            .short("d")
            .long("destination")
            .value_name("DIR")
            .help("Destination folder [default: ./]")
            .takes_value(true))
        .arg(Arg::with_name("layouts")
            .short("l")
            .long("layouts")
            .value_name("DIR")
            .help("Layout templates folder [default: ./_layouts]")
            .takes_value(true))
        .arg(Arg::with_name("posts")
            .short("p")
            .long("posts")
            .value_name("DIR")
            .help("Posts folder [default: ./posts]")
            .takes_value(true))
        .arg(Arg::with_name("log-level")
            .short("L")
            .long("log-level")
            .possible_values(&["info", "debug", "trace", "off"])
            .help("Log level")
            .default_value("info")
            .takes_value(true))
        .arg(Arg::with_name("trace")
            .long("trace")
            .help("Log ultra-verbose (trace level) information")
            .takes_value(false))
        .arg(Arg::with_name("silent")
            .long("silent")
            .help("Suppress all output")
            .takes_value(false))
        .subcommand(SubCommand::with_name("new")
            .about("create a new cobalt project")
            .arg(Arg::with_name("DIRECTORY")
                .help("Suppress all output")
                .default_value("./")
                .index(1)))
        .subcommand(SubCommand::with_name("build")
            .about("build the cobalt project at the source dir")
            .arg(Arg::with_name("import")
                .short("i")
                .long("import")
                .help("Import after build to gh-pages branch")
                .takes_value(false))
            .arg(Arg::with_name("branch")
                .short("b")
                .long("branch")
                .value_name("BRANCH")
                .help("Branch that will be used to import the site to")
                .default_value("gh-pages")
                .takes_value(true))
            .arg(Arg::with_name("message")
                .short("m")
                .long("message")
                .value_name("COMMIT-MESSAGE")
                .help("Commit message that will be used on import")
                .default_value("cobalt site import")
                .takes_value(true)))
        .subcommand(SubCommand::with_name("serve")
            .about("build and serve the cobalt project at the source dir")
            .arg(Arg::with_name("port")
                .short("P")
                .long("port")
                .value_name("INT")
                .help("Port to serve from")
                .default_value("3000")
                .takes_value(true)))
        .subcommand(SubCommand::with_name("watch")
            .about("build, serve, and watch the project at the source dir")
            .arg(Arg::with_name("port")
                .short("P")
                .long("port")
                .value_name("INT")
                .help("Port to serve from")
                .default_value("3000")
                .takes_value(true)))
        .subcommand(SubCommand::with_name("import")
            .about("moves the contents of the dest folder to the gh-pages branch")
            .arg(Arg::with_name("branch")
                .short("b")
                .long("branch")
                .value_name("BRANCH")
                .help("Branch that will be used to import the site to")
                .default_value("gh-pages")
                .takes_value(true))
            .arg(Arg::with_name("message")
                .short("m")
                .long("message")
                .value_name("COMMIT-MESSAGE")
                .help("Commit message that will be used on import")
                .default_value("cobalt site import")
                .takes_value(true)))
        .get_matches();

    let format = |record: &LogRecord| {
        let level = format!("[{}]", record.level()).to_lowercase();
        format!("{:8} {}", level, record.args())
    };

    let mut builder = LogBuilder::new();
    builder.format(format);

    match matches.value_of("log-level") {
        Some("info") => {
            builder.filter(None, LogLevelFilter::Info);
        }
        Some("debug") => {
            builder.filter(None, LogLevelFilter::Debug);
        }
        Some("trace") => {
            builder.filter(None, LogLevelFilter::Trace);
        }
        Some("off") => {
            builder.filter(None, LogLevelFilter::Off);
        }
        _ => {
            builder.filter(None, LogLevelFilter::Info);
        }
    }

    builder.init().unwrap();

    let config_path = matches.value_of("config").unwrap().to_string();

    // Fetch config information if available
    let mut config: Config = if fs::metadata(&config_path).is_ok() {
        info!("Using config file {}", &config_path);

        match Config::from_file(&config_path) {
            Ok(config) => config,
            Err(e) => {
                error!("Error reading config file:");
                error!("{}", e);
                std::process::exit(1);
            }
        }
    } else {
        warn!("No .cobalt.yml file found in current directory, using default config.");
        Default::default()
    };

    config.source = matches.value_of("source")
        .map(str::to_string)
        .unwrap_or(config.source);

    config.dest = matches.value_of("destination")
        .map(str::to_string)
        .unwrap_or(config.dest);

    config.layouts = matches.value_of("layouts")
        .map(str::to_string)
        .unwrap_or(config.layouts);

    config.posts = matches.value_of("posts")
        .map(str::to_string)
        .unwrap_or(config.posts);

    match matches.subcommand() {
        ("new", Some(submatches)) => {
            let directory = submatches.value_of("DIRECTORY").unwrap();

            match create_new_project(&directory.to_string()) {
                Ok(_) => info!("Created new project at {}", directory),
                Err(e) => {
                    error!("{}", e);
                    error!("Could not create a new cobalt project");
                    std::process::exit(1);
                }
            }
        }

        ("build", Some(submatches)) => {
            build(&config);
            if submatches.is_present("import") {
                let branch = submatches.value_of("branch").unwrap().to_string();
                let message = submatches.value_of("message").unwrap().to_string();
                import(&config, &branch, &message);
            }
        }

        ("serve", Some(submatches)) => {
            build(&config);
            let port = submatches.value_of("port").unwrap().to_string();
            serve(&config.dest, &port);
        }

        ("watch", Some(submatches)) => {
            build(&config);

            let dest = config.dest.clone();
            let port = submatches.value_of("port").unwrap().to_string();
            thread::spawn(move || {
                serve(&dest, &port);
            });

            let (tx, rx) = channel();
            let w: Result<RecommendedWatcher, Error> = Watcher::new(tx);

            match w {
                Ok(mut watcher) => {
                    // TODO: clean up this unwrap
                    watcher.watch(&config.source).unwrap();
                    info!("Watching {:?} for changes", &config.source);

                    loop {
                        match rx.recv() {
                            Ok(val) => {
                                trace!("file changed {:?}", val);
                                if let Some(path) = val.path {
                                    if path.is_absolute() {
                                        // get where process was run from
                                        let cwd = std::env::current_dir().unwrap_or(PathBuf::new());
                                        // strip absolute path
                                        let rel_path = path.strip_prefix(&cwd).unwrap_or(&cwd);

                                        // check if path starts with the build folder.
                                        if !&config.ignore.iter().any(|pattern| {
                                            Pattern::matches_path(pattern, rel_path)
                                        }) {
                                            build(&config);
                                        }

                                    } else {
                                        // check if path starts with build folder.
                                        // TODO: may want to check if it starts `./`
                                        if !&config.ignore
                                            .iter()
                                            .any(|pattern| Pattern::matches_path(pattern, &path)) {
                                            build(&config);
                                        }
                                    }
                                }
                            }

                            Err(e) => {
                                error!("[Notify Error]: {}", e);
                                std::process::exit(1);
                            }
                        }
                    }
                }
                Err(e) => {
                    error!("[Notify Error]: {}", e);
                    std::process::exit(1);
                }
            }
        }

        ("import", Some(submatches)) => {
            let branch = submatches.value_of("branch").unwrap().to_string();
            let message = submatches.value_of("message").unwrap().to_string();
            import(&config, &branch, &message);
        }

        _ => {
            println!("{}", matches.usage());
            return;
        }
    }
}

fn build(config: &Config) {
    info!("Building from {} into {}", config.source, config.dest);
    match cobalt::build(&config) {
        Ok(_) => info!("Build successful"),
        Err(e) => {
            error!("{}", e);
            error!("Build not successful");
            std::process::exit(1);
        }
    };
}

fn static_file_handler(dest: &str, req: Request, mut res: Response) -> IoResult<()> {
    // grab the requested path
    let req_path = match req.uri {
        RequestUri::AbsolutePath(p) => p,
        _ => {
            // return a 400 and exit from this request
            *res.status_mut() = hyper::status::StatusCode::BadRequest;
            let body = b"<h1> <center> 400: Bad request </center> </h1>";
            try!(res.send(body));
            return Ok(());
        }
    };

    // find the path of the file in the local system
    // (this gets rid of the '/' in `p`, so the `join()` will not replace the
    // path)
    let path = PathBuf::from(dest).join(&req_path[1..]);

    let serve_path = if path.is_file() {
        // try to point the serve path to `path` if it corresponds to a file
        path
    } else {
        // try to point the serve path into a "index.html" file in the requested
        // path
        path.join("index.html")
    };

    // if the request points to a file and it exists, read and serve it
    if serve_path.exists() {
        let mut file = try!(File::open(serve_path));

        // buffer to store the file
        let mut buffer: Vec<u8> = vec![];

        try!(file.read_to_end(&mut buffer));

        try!(res.send(&buffer));
    } else {
        // return a 404 status
        *res.status_mut() = hyper::status::StatusCode::NotFound;

        // write a simple body for the 404 page
        let body = b"<h1> <center> 404: Page not found </center> </h1>";

        try!(res.send(body));
    }

    Ok(())
}

fn serve(dest: &str, port: &str) {
    info!("Serving {:?} through static file server", dest);

    let ip = format!("127.0.0.1:{}", port);
    info!("Server Listening on {}", &ip);
    info!("Ctrl-c to stop the server");

    // attempts to create a server
    let http_server = match Server::http(&*ip) {
        Ok(server) => server,
        Err(e) => {
            error!("{}", e);
            std::process::exit(1);
        }
    };

    // need a clone because of closure's lifetime
    let dest_clone = dest.to_owned();

    // bind the handle function and start serving
    if let Err(e) = http_server.handle(move |req: Request, res: Response| {
        if let Err(e) = static_file_handler(&dest_clone, req, res) {
            error!("{}", e);
            std::process::exit(1);
        }
    }) {
        error!("{}", e);
        std::process::exit(1);
    };
}

fn import(config: &Config, branch: &str, message: &str) {
    info!("Importing {} to {}", config.dest, branch);

    let meta = match fs::metadata(&config.dest) {
        Ok(data) => data,

        Err(e) => {
            error!("{}", e);
            error!("Import not successful");
            std::process::exit(1);
        }
    };

    if meta.is_dir() {
        match import_dir(&config.dest, branch, message) {
            Ok(_) => info!("Import successful"),
            Err(e) => {
                error!("{}", e);
                error!("Import not successful");
                std::process::exit(1);
            }
        }
    } else {
        error!("Build dir is not a directory: {}", config.dest);
        error!("Import not successful");
        std::process::exit(1);
    }
}
<commit_msg>Make global parameters available in subcommands<commit_after>#![deny(warnings)]

extern crate cobalt;
extern crate clap;
extern crate env_logger;
extern crate notify;
extern crate glob;
extern crate ghp;

extern crate hyper;

#[macro_use]
extern crate log;

use clap::{Arg, App, SubCommand, AppSettings};
use std::fs;
use cobalt::Config;
use log::{LogRecord, LogLevelFilter};
use env_logger::LogBuilder;
use hyper::server::{Server, Request, Response};
use hyper::uri::RequestUri;
use ghp::import_dir;
use glob::Pattern;
use cobalt::create_new_project;

use notify::{RecommendedWatcher, Error, Watcher};
use std::sync::mpsc::channel;
use std::thread;
use std::path::PathBuf;
use std::io::prelude::*;
use std::io::Result as IoResult;
use std::fs::File;

fn main() {
    let global_matches = App::new("Cobalt")
        .version("0.3.0")
        .author("Benny Klotz <r3qnbenni@gmail.com>, Johann Hofmann")
        .about("A static site generator written in Rust.")
        .setting(AppSettings::SubcommandRequired)
        .setting(AppSettings::GlobalVersion)
        .arg(Arg::with_name("config")
            .short("c")
            .long("config")
            .value_name("FILE")
            .help("Config file to use [default: .cobalt.yml]")
            .global(true)
            .takes_value(true))
        .arg(Arg::with_name("source")
            .short("s")
            .long("source")
            .value_name("DIR")
            .help("Source folder [default: ./]")
            .global(true)
            .takes_value(true))
        .arg(Arg::with_name("destination")
            .short("d")
            .long("destination")
            .value_name("DIR")
            .help("Destination folder [default: ./]")
            .global(true)
            .takes_value(true))
        .arg(Arg::with_name("layouts")
            .short("l")
            .long("layouts")
            .value_name("DIR")
            .help("Layout templates folder [default: ./_layouts]")
            .global(true)
            .takes_value(true))
        .arg(Arg::with_name("posts")
            .short("p")
            .long("posts")
            .value_name("DIR")
            .help("Posts folder [default: ./posts]")
            .global(true)
            .takes_value(true))
        .arg(Arg::with_name("log-level")
            .short("L")
            .long("log-level")
            .possible_values(&["error", "warn", "info", "debug", "trace", "off"])
            .help("Log level [default: info]")
            .global(true)
            .takes_value(true))
        .arg(Arg::with_name("trace")
            .long("trace")
            .help("Log ultra-verbose (trace level) information")
            .global(true)
            .takes_value(false))
        .arg(Arg::with_name("silent")
            .long("silent")
            .help("Suppress all output")
            .global(true)
            .takes_value(false))
        .subcommand(SubCommand::with_name("new")
            .about("create a new cobalt project")
            .arg(Arg::with_name("DIRECTORY")
                .help("Suppress all output")
                .default_value("./")
                .index(1)))
        .subcommand(SubCommand::with_name("build")
            .about("build the cobalt project at the source dir")
            .arg(Arg::with_name("import")
                .short("i")
                .long("import")
                .help("Import after build to gh-pages branch")
                .takes_value(false))
            .arg(Arg::with_name("branch")
                .short("b")
                .long("branch")
                .value_name("BRANCH")
                .help("Branch that will be used to import the site to")
                .default_value("gh-pages")
                .takes_value(true))
            .arg(Arg::with_name("message")
                .short("m")
                .long("message")
                .value_name("COMMIT-MESSAGE")
                .help("Commit message that will be used on import")
                .default_value("cobalt site import")
                .takes_value(true)))
        .subcommand(SubCommand::with_name("serve")
            .about("build and serve the cobalt project at the source dir")
            .arg(Arg::with_name("port")
                .short("P")
                .long("port")
                .value_name("INT")
                .help("Port to serve from")
                .default_value("3000")
                .takes_value(true)))
        .subcommand(SubCommand::with_name("watch")
            .about("build, serve, and watch the project at the source dir")
            .arg(Arg::with_name("port")
                .short("P")
                .long("port")
                .value_name("INT")
                .help("Port to serve from")
                .default_value("3000")
                .takes_value(true)))
        .subcommand(SubCommand::with_name("import")
            .about("moves the contents of the dest folder to the gh-pages branch")
            .arg(Arg::with_name("branch")
                .short("b")
                .long("branch")
                .value_name("BRANCH")
                .help("Branch that will be used to import the site to")
                .default_value("gh-pages")
                .takes_value(true))
            .arg(Arg::with_name("message")
                .short("m")
                .long("message")
                .value_name("COMMIT-MESSAGE")
                .help("Commit message that will be used on import")
                .default_value("cobalt site import")
                .takes_value(true)))
        .get_matches();

    let (command, matches) = match global_matches.subcommand() {
        (command, Some(matches)) => (command, matches),
        (_, None) => unreachable!(),
    };

    let format = |record: &LogRecord| {
        let level = format!("[{}]", record.level()).to_lowercase();
        format!("{:8} {}", level, record.args())
    };

    let mut builder = LogBuilder::new();
    builder.format(format);

    match matches.value_of("log-level").or(global_matches.value_of("log-level")) {
        Some("error") => builder.filter(None, LogLevelFilter::Error),
        Some("warn") => builder.filter(None, LogLevelFilter::Warn),
        Some("info") => builder.filter(None, LogLevelFilter::Info),
        Some("debug") => builder.filter(None, LogLevelFilter::Debug),
        Some("trace") => builder.filter(None, LogLevelFilter::Trace),
        Some("off") => builder.filter(None, LogLevelFilter::Off),
        _ => builder.filter(None, LogLevelFilter::Info),
    };

    builder.init().unwrap();

    let config_path = matches.value_of("config")
        .or(global_matches.value_of("config"))
        .unwrap_or(".cobalt.yml")
        .to_string();

    // Fetch config information if available
    let mut config: Config = if fs::metadata(&config_path).is_ok() {
        info!("Using config file {}", &config_path);

        match Config::from_file(&config_path) {
            Ok(config) => config,
            Err(e) => {
                error!("Error reading config file:");
                error!("{}", e);
                std::process::exit(1);
            }
        }
    } else {
        warn!("No .cobalt.yml file found in current directory, using default config.");
        Default::default()
    };

    config.source = matches.value_of("source")
        .or(global_matches.value_of("source"))
        .map(str::to_string)
        .unwrap_or(config.source);

    config.dest = matches.value_of("destination")
        .or(global_matches.value_of("destination"))
        .map(str::to_string)
        .unwrap_or(config.dest);

    config.layouts = matches.value_of("layouts")
        .or(global_matches.value_of("layouts"))
        .map(str::to_string)
        .unwrap_or(config.layouts);

    config.posts = matches.value_of("posts")
        .or(global_matches.value_of("posts"))
        .map(str::to_string)
        .unwrap_or(config.posts);

    match command {
        "new" => {
            let directory = matches.value_of("DIRECTORY").unwrap();

            match create_new_project(&directory.to_string()) {
                Ok(_) => info!("Created new project at {}", directory),
                Err(e) => {
                    error!("{}", e);
                    error!("Could not create a new cobalt project");
                    std::process::exit(1);
                }
            }
        }

        "build" => {
            build(&config);
            if matches.is_present("import") {
                let branch = matches.value_of("branch").unwrap().to_string();
                let message = matches.value_of("message").unwrap().to_string();
                import(&config, &branch, &message);
            }
        }

        "serve" => {
            build(&config);
            let port = matches.value_of("port").unwrap().to_string();
            serve(&config.dest, &port);
        }

        "watch" => {
            build(&config);

            let dest = config.dest.clone();
            let port = matches.value_of("port").unwrap().to_string();
            thread::spawn(move || {
                serve(&dest, &port);
            });

            let (tx, rx) = channel();
            let w: Result<RecommendedWatcher, Error> = Watcher::new(tx);

            match w {
                Ok(mut watcher) => {
                    // TODO: clean up this unwrap
                    watcher.watch(&config.source).unwrap();
                    info!("Watching {:?} for changes", &config.source);

                    loop {
                        match rx.recv() {
                            Ok(val) => {
                                trace!("file changed {:?}", val);
                                if let Some(path) = val.path {
                                    if path.is_absolute() {
                                        // get where process was run from
                                        let cwd = std::env::current_dir().unwrap_or(PathBuf::new());
                                        // strip absolute path
                                        let rel_path = path.strip_prefix(&cwd).unwrap_or(&cwd);

                                        // check if path starts with the build folder.
                                        if !&config.ignore.iter().any(|pattern| {
                                            Pattern::matches_path(pattern, rel_path)
                                        }) {
                                            build(&config);
                                        }

                                    } else {
                                        // check if path starts with build folder.
                                        // TODO: may want to check if it starts `./`
                                        if !&config.ignore
                                            .iter()
                                            .any(|pattern| Pattern::matches_path(pattern, &path)) {
                                            build(&config);
                                        }
                                    }
                                }
                            }

                            Err(e) => {
                                error!("[Notify Error]: {}", e);
                                std::process::exit(1);
                            }
                        }
                    }
                }
                Err(e) => {
                    error!("[Notify Error]: {}", e);
                    std::process::exit(1);
                }
            }
        }

        "import" => {
            let branch = matches.value_of("branch").unwrap().to_string();
            let message = matches.value_of("message").unwrap().to_string();
            import(&config, &branch, &message);
        }

        _ => {
            println!("{}", global_matches.usage());
            return;
        }
    }
}

fn build(config: &Config) {
    info!("Building from {} into {}", config.source, config.dest);
    match cobalt::build(&config) {
        Ok(_) => info!("Build successful"),
        Err(e) => {
            error!("{}", e);
            error!("Build not successful");
            std::process::exit(1);
        }
    };
}

fn static_file_handler(dest: &str, req: Request, mut res: Response) -> IoResult<()> {
    // grab the requested path
    let req_path = match req.uri {
        RequestUri::AbsolutePath(p) => p,
        _ => {
            // return a 400 and exit from this request
            *res.status_mut() = hyper::status::StatusCode::BadRequest;
            let body = b"<h1> <center> 400: Bad request </center> </h1>";
            try!(res.send(body));
            return Ok(());
        }
    };

    // find the path of the file in the local system
    // (this gets rid of the '/' in `p`, so the `join()` will not replace the
    // path)
    let path = PathBuf::from(dest).join(&req_path[1..]);

    let serve_path = if path.is_file() {
        // try to point the serve path to `path` if it corresponds to a file
        path
    } else {
        // try to point the serve path into a "index.html" file in the requested
        // path
        path.join("index.html")
    };

    // if the request points to a file and it exists, read and serve it
    if serve_path.exists() {
        let mut file = try!(File::open(serve_path));

        // buffer to store the file
        let mut buffer: Vec<u8> = vec![];

        try!(file.read_to_end(&mut buffer));

        try!(res.send(&buffer));
    } else {
        // return a 404 status
        *res.status_mut() = hyper::status::StatusCode::NotFound;

        // write a simple body for the 404 page
        let body = b"<h1> <center> 404: Page not found </center> </h1>";

        try!(res.send(body));
    }

    Ok(())
}

fn serve(dest: &str, port: &str) {
    info!("Serving {:?} through static file server", dest);

    let ip = format!("127.0.0.1:{}", port);
    info!("Server Listening on {}", &ip);
    info!("Ctrl-c to stop the server");

    // attempts to create a server
    let http_server = match Server::http(&*ip) {
        Ok(server) => server,
        Err(e) => {
            error!("{}", e);
            std::process::exit(1);
        }
    };

    // need a clone because of closure's lifetime
    let dest_clone = dest.to_owned();

    // bind the handle function and start serving
    if let Err(e) = http_server.handle(move |req: Request, res: Response| {
        if let Err(e) = static_file_handler(&dest_clone, req, res) {
            error!("{}", e);
            std::process::exit(1);
        }
    }) {
        error!("{}", e);
        std::process::exit(1);
    };
}

fn import(config: &Config, branch: &str, message: &str) {
    info!("Importing {} to {}", config.dest, branch);

    let meta = match fs::metadata(&config.dest) {
        Ok(data) => data,

        Err(e) => {
            error!("{}", e);
            error!("Import not successful");
            std::process::exit(1);
        }
    };

    if meta.is_dir() {
        match import_dir(&config.dest, branch, message) {
            Ok(_) => info!("Import successful"),
            Err(e) => {
                error!("{}", e);
                error!("Import not successful");
                std::process::exit(1);
            }
        }
    } else {
        error!("Build dir is not a directory: {}", config.dest);
        error!("Import not successful");
        std::process::exit(1);
    }
}
<|endoftext|>


<commit_before>// Worklog is for recording your hours.
//

extern crate chrono;
extern crate csv;
extern crate getopts;
extern crate rustc_serialize;

mod error;
mod timeclock;
mod util;

use chrono::*;
use error::WorklogError;
use getopts::Options;
use std::env;
use std::error::Error;
use std::fs::OpenOptions;
use std::io::prelude::*;
use std::path::PathBuf;
use timeclock::direction::Direction;

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

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

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

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

    let mut total_hours: f64 = 0.0;
    for rec in records {
        total_hours += rec.hours();
        println!("{}", rec);
    }

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

fn print_short_summary<R: Read>(file: R,
                                since: Date<FixedOffset>)
                                -> Result<(), WorklogError> {
    let csv_entries = try!(timeclock::read_timesheet(file));
    let entries = timeclock::pair_time_entries(csv_entries);
    let records = timeclock::collect_date_records(entries);

    let mut total_hours: f64 = 0.0;
    for rec in records {
        if rec.date >= since {
            total_hours += rec.hours();
            println!("{}", rec);
        }
    }

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

fn print_usage(program: &str, opts: Options) {
    let brief = format!("Usage: {} [options]", program);
    print!("{}", opts.usage(&brief));
}

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

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

fn main0() -> Result<i8, WorklogError> {
    let args: Vec<String> = env::args().collect();
    let program = args[0].clone();

    let mut opts = Options::new();
    opts.optflagopt("i", "in", "in time", "TIME");
    opts.optflagopt("o", "out", "out time", "TIME");
    opts.optopt("m", "memo", "the memo", "MEMO");
    opts.optflag("s", "summary", "show the full summary");
    opts.optflag("e", "entries", "show all entries");
    opts.optflag("h", "help", "print this help menu");

    let matches = try!(opts.parse(&args[1..]));

    let csv_path = try!(get_csv_path());

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

    if matches.opt_present("h") {
        print_usage(&program, opts);

    } else if matches.opts_present(&["i".to_owned(), "o".to_owned()]) {
        // The ridiculous array above isn't my fault.

        // Reject --in and --out if present at the same time
        if matches.opt_present("i") && matches.opt_present("o") {
            println!("--in or --out, not both");
            return Ok(1);
        }

        let dir = match matches.opt_present("i") {
            true => Direction::In,
            false => Direction::Out,
        };

        let time = matches.opt_str("i")
            .or(matches.opt_str("o"))
            .or(Some("now".to_owned()))
            .unwrap();
        let time = try!(util::parse_multi_time_fmt(&time));

        let memo = matches.opt_str("m").or(Some("".to_owned())).unwrap();
        timeclock::mark_time(dir, time, memo, csv_file);

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

    } else if matches.opt_present("s") {
        try!(print_full_summary(&csv_file));

    } else if matches.opt_present("e") {
        try!(print_csv_entries(&csv_file));

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

    Ok(0)
}

fn main() {
    match main0() {
        Ok(_) => {}
        Err(err) => println!("Whoops! {}", err.description()),
    };
}
<commit_msg>Use alternate representation of Direction<commit_after>// Worklog is for recording your hours.
//

extern crate chrono;
extern crate csv;
extern crate getopts;
extern crate rustc_serialize;

mod error;
mod timeclock;
mod util;

use chrono::*;
use error::WorklogError;
use getopts::Options;
use std::env;
use std::error::Error;
use std::fs::OpenOptions;
use std::io::prelude::*;
use std::path::PathBuf;
use timeclock::direction::Direction;

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

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

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

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

    let mut total_hours: f64 = 0.0;
    for rec in records {
        total_hours += rec.hours();
        println!("{}", rec);
    }

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

fn print_short_summary<R: Read>(file: R,
                                since: Date<FixedOffset>)
                                -> Result<(), WorklogError> {
    let csv_entries = try!(timeclock::read_timesheet(file));
    let entries = timeclock::pair_time_entries(csv_entries);
    let records = timeclock::collect_date_records(entries);

    let mut total_hours: f64 = 0.0;
    for rec in records {
        if rec.date >= since {
            total_hours += rec.hours();
            println!("{}", rec);
        }
    }

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

fn print_usage(program: &str, opts: Options) {
    let brief = format!("Usage: {} [options]", program);
    print!("{}", opts.usage(&brief));
}

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

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

fn main0() -> Result<i8, WorklogError> {
    let args: Vec<String> = env::args().collect();
    let program = args[0].clone();

    let mut opts = Options::new();
    opts.optflagopt("i", "in", "in time", "TIME");
    opts.optflagopt("o", "out", "out time", "TIME");
    opts.optopt("m", "memo", "the memo", "MEMO");
    opts.optflag("s", "summary", "show the full summary");
    opts.optflag("e", "entries", "show all entries");
    opts.optflag("h", "help", "print this help menu");

    let matches = try!(opts.parse(&args[1..]));

    let csv_path = try!(get_csv_path());

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

    if matches.opt_present("h") {
        print_usage(&program, opts);

    } else if matches.opts_present(&["i".to_owned(), "o".to_owned()]) {
        // The ridiculous array above isn't my fault.

        // Reject --in and --out if present at the same time
        if matches.opt_present("i") && matches.opt_present("o") {
            println!("--in or --out, not both");
            return Ok(1);
        }

        let dir = match matches.opt_present("i") {
            true => Direction::In,
            false => Direction::Out,
        };

        let time = matches.opt_str("i")
            .or(matches.opt_str("o"))
            .or(Some("now".to_owned()))
            .unwrap();
        let time = try!(util::parse_multi_time_fmt(&time));

        let memo = matches.opt_str("m").or(Some("".to_owned())).unwrap();
        timeclock::mark_time(dir, time, memo, csv_file);

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

    } else if matches.opt_present("s") {
        try!(print_full_summary(&csv_file));

    } else if matches.opt_present("e") {
        try!(print_csv_entries(&csv_file));

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

    Ok(0)
}

fn main() {
    match main0() {
        Ok(_) => {}
        Err(err) => println!("Whoops! {}", err.description()),
    };
}
<|endoftext|>


<commit_before>#[macro_use] extern crate log;
extern crate env_logger;
extern crate rustylnk;
extern crate clap;
extern crate seek_bufread;
extern crate jmespath;
extern crate serde_json;
extern crate serde;
use clap::{App, Arg, ArgMatches};
use jmespath::{Expression};
use seek_bufread::BufReader;
use rustylnk::lnkpkg::lnk::{Lnk};
use std::fs;

fn process_directory(directory: &str, options: &ArgMatches) {
    for dir_reader in fs::read_dir(directory) {
        for entry_result in dir_reader {
            match entry_result {
                Ok(entry) => {
                    let path = entry.path();
                    if path.is_file() {
                        let path_string = path.into_os_string().into_string().unwrap();
                        if path_string.to_lowercase().ends_with(".lnk"){
                            process_file(&path_string, &options);
                        }
                    } else if path.is_dir(){
                        let path_string = path.into_os_string().into_string().unwrap();
                        process_directory(&path_string, &options);
                    }
                },
                Err(error) => {
                    error!("Error reading {} [{:?}]",directory,error);
                }
            }
        }
    }
}

fn process_file(filename: &str, options: &ArgMatches) -> bool {
    // JMES Expression if needed
    let mut expr: Option<Expression> = None;
    if options.is_present("query") {
        expr = Some(jmespath::compile(
            options.value_of("query").unwrap()
        ).unwrap());
    }

    // Expression bool flag
    let mut expr_as_bool = false;
    if options.is_present("bool_expr"){
        expr_as_bool = true;
    }

    let file = match fs::File::open(filename) {
        Ok(file) => file,
        Err(error) => {
            error!("Could not open file: {} [error: {}]", filename, error);
            return false;
        }
    };

    let reader = BufReader::new(file);

    info!("Parsing file: {}",filename);
    let lnk = match Lnk::new(reader) {
        Ok(lnk) => lnk,
        Err(error) => {
            error!("Could not parse file: {} [{}]", filename, error);
            return false;
        }
    };

    let json_str = serde_json::to_string(&lnk).unwrap();
    match expr {
        Some(ref j_expr) => {
            let data = jmespath::Variable::from_json(&json_str).unwrap();
            let result = j_expr.search(data).unwrap();
            if expr_as_bool {
                match result.as_boolean() {
                    Some(bool_value) => {
                        match bool_value {
                            true => println!("{}",json_str),
                            false => {}
                        }
                    },
                    None => {
                        error!("Query expression is not a bool expression! [{}]",options.value_of("query").unwrap());
                    }
                }
            } else {
                println!("{}",result)
            }
        },
        None => {
            println!("{}",json_str);
        }
    }

    return true;
}

fn is_directory(source: &str)->bool{
    let metadata = fs::metadata(source).unwrap();

    let file_type = metadata.file_type();

    file_type.is_dir()
}

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

    let source_arg = Arg::with_name("source")
        .short("s")
        .long("source")
        .value_name("PATH")
        .help("The LNK file or folder with LNK files to parse.")
        .takes_value(true);

    let jmes_arg = Arg::with_name("query")
        .short("q")
        .long("query")
        .value_name("QUERY")
        .help("JMES Query")
        .takes_value(true);

    let bool_arg = Arg::with_name("bool_expr")
        .short("b")
        .long("bool_expr")
        .help("JMES Query as bool only. (Prints whole record if true.)");

    let options = App::new("RusyLnk")
        .version("0.0.0")
        .author("Matthew Seyer <https://github.com/forensicmatt/RustyUsn>")
        .about("LNK Parser written in Rust.")
        .arg(source_arg)
        .arg(jmes_arg)
        .arg(bool_arg)
        .get_matches();

    let source = options.value_of("source").unwrap();

    if is_directory(source) {
        process_directory(source,&options);
    } else {
        process_file(source,&options);
    }
}
<commit_msg>upped version<commit_after>#[macro_use] extern crate log;
extern crate env_logger;
extern crate rustylnk;
extern crate clap;
extern crate seek_bufread;
extern crate jmespath;
extern crate serde_json;
extern crate serde;
use clap::{App, Arg, ArgMatches};
use jmespath::{Expression};
use seek_bufread::BufReader;
use rustylnk::lnkpkg::lnk::{Lnk};
use std::fs;

fn process_directory(directory: &str, options: &ArgMatches) {
    for dir_reader in fs::read_dir(directory) {
        for entry_result in dir_reader {
            match entry_result {
                Ok(entry) => {
                    let path = entry.path();
                    if path.is_file() {
                        let path_string = path.into_os_string().into_string().unwrap();
                        if path_string.to_lowercase().ends_with(".lnk"){
                            process_file(&path_string, &options);
                        }
                    } else if path.is_dir(){
                        let path_string = path.into_os_string().into_string().unwrap();
                        process_directory(&path_string, &options);
                    }
                },
                Err(error) => {
                    error!("Error reading {} [{:?}]",directory,error);
                }
            }
        }
    }
}

fn process_file(filename: &str, options: &ArgMatches) -> bool {
    // JMES Expression if needed
    let mut expr: Option<Expression> = None;
    if options.is_present("query") {
        expr = Some(jmespath::compile(
            options.value_of("query").unwrap()
        ).unwrap());
    }

    // Expression bool flag
    let mut expr_as_bool = false;
    if options.is_present("bool_expr"){
        expr_as_bool = true;
    }

    let file = match fs::File::open(filename) {
        Ok(file) => file,
        Err(error) => {
            error!("Could not open file: {} [error: {}]", filename, error);
            return false;
        }
    };

    let reader = BufReader::new(file);

    info!("Parsing file: {}",filename);
    let lnk = match Lnk::new(reader) {
        Ok(lnk) => lnk,
        Err(error) => {
            error!("Could not parse file: {} [{}]", filename, error);
            return false;
        }
    };

    let json_str = serde_json::to_string(&lnk).unwrap();
    match expr {
        Some(ref j_expr) => {
            let data = jmespath::Variable::from_json(&json_str).unwrap();
            let result = j_expr.search(data).unwrap();
            if expr_as_bool {
                match result.as_boolean() {
                    Some(bool_value) => {
                        match bool_value {
                            true => println!("{}",json_str),
                            false => {}
                        }
                    },
                    None => {
                        error!("Query expression is not a bool expression! [{}]",options.value_of("query").unwrap());
                    }
                }
            } else {
                println!("{}",result)
            }
        },
        None => {
            println!("{}",json_str);
        }
    }

    return true;
}

fn is_directory(source: &str)->bool{
    let metadata = fs::metadata(source).unwrap();

    let file_type = metadata.file_type();

    file_type.is_dir()
}

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

    let source_arg = Arg::with_name("source")
        .short("s")
        .long("source")
        .value_name("PATH")
        .help("The LNK file or folder with LNK files to parse.")
        .takes_value(true);

    let jmes_arg = Arg::with_name("query")
        .short("q")
        .long("query")
        .value_name("QUERY")
        .help("JMES Query")
        .takes_value(true);

    let bool_arg = Arg::with_name("bool_expr")
        .short("b")
        .long("bool_expr")
        .help("JMES Query as bool only. (Prints whole record if true.)");

    let options = App::new("RusyLnk")
        .version("0.1.0")
        .author("Matthew Seyer <https://github.com/forensicmatt/RustyUsn>")
        .about("LNK Parser written in Rust.")
        .arg(source_arg)
        .arg(jmes_arg)
        .arg(bool_arg)
        .get_matches();

    let source = options.value_of("source").unwrap();

    if is_directory(source) {
        process_directory(source,&options);
    } else {
        process_file(source,&options);
    }
}
<|endoftext|>


<commit_before>#![crate_name = "rboy"]

extern crate clap;
extern crate cpal;
extern crate glium;
extern crate rboy;

use glium::DisplayBuild;
use rboy::device::Device;
use std::sync::mpsc::{self, Receiver, SyncSender, TryRecvError};
use std::thread;
use std::error::Error;

const EXITCODE_SUCCESS : i32 = 0;
const EXITCODE_CPULOADFAILS : i32 = 2;

#[derive(Default)]
struct RenderOptions {
    pub linear_interpolation: bool,
}

enum GBEvent {
    KeyUp(rboy::KeypadKey),
    KeyDown(rboy::KeypadKey),
    SpeedUp,
    SpeedDown,
}

fn main() {
    let exit_status = real_main();
    if exit_status != EXITCODE_SUCCESS {
        std::process::exit(exit_status);
    }
}

fn real_main() -> i32 {
    let matches = clap::App::new("rboy")
        .version("0.1")
        .author("Mathijs van de Nes")
        .about("A Gameboy Colour emulator written in Rust")
        .arg(clap::Arg::with_name("filename")
             .help("Sets the ROM file to load")
             .required(true))
        .arg(clap::Arg::with_name("serial")
             .help("Prints the data from the serial port to stdout")
             .short("s")
             .long("serial"))
        .arg(clap::Arg::with_name("classic")
             .help("Forces the emulator to run in classic Gameboy mode")
             .short("c")
             .long("classic"))
        .arg(clap::Arg::with_name("scale")
             .help("Sets the scale of the interface. Default: 2")
             .short("x")
             .long("scale")
             .validator(|s|
                 match s.parse::<u32>() {
                     Err(e) => Err(format!("Could not parse scale: {}", e.description())),
                     Ok(s) if s < 1 => Err("Scale must be at least 1".to_owned()),
                     Ok(s) if s > 8 => Err("Scale may be at most 8".to_owned()),
                     Ok(..) => Ok(()),
                 })
             .takes_value(true))
        .arg(clap::Arg::with_name("audio")
             .help("Enables audio")
             .short("a")
             .long("audio"))
        .get_matches();

    let opt_serial = matches.is_present("serial");
    let opt_classic = matches.is_present("classic");
    let opt_audio = matches.is_present("audio");
    let filename = matches.value_of("filename").unwrap();
    let scale = matches.value_of("scale").unwrap_or("2").parse::<u32>().unwrap();

    let cpu = construct_cpu(filename, opt_classic, opt_serial);
    if cpu.is_none() { return EXITCODE_CPULOADFAILS; }
    let mut cpu = cpu.unwrap();
    if opt_audio {
        let player = CpalPlayer::get();
        match player {
            Some(v) => cpu.enable_audio(Box::new(v) as Box<rboy::AudioPlayer>),
            None => { warn("Could not open audio device"); return EXITCODE_CPULOADFAILS; },
        }
    }
    let romname = cpu.romname();

    let (sender1, receiver1) = mpsc::channel();
    let (sender2, receiver2) = mpsc::sync_channel(2);

    let display = glium::glutin::WindowBuilder::new()
        .with_dimensions(rboy::SCREEN_W as u32 * scale, rboy::SCREEN_H as u32 * scale)
        .with_title("RBoy - ".to_owned() + &romname)
        .build_glium()
        .unwrap();

    let mut texture = glium::texture::texture2d::Texture2d::empty_with_format(
            &display,
            glium::texture::UncompressedFloatFormat::U8U8U8,
            glium::texture::MipmapsOption::NoMipmap,
            rboy::SCREEN_W as u32,
            rboy::SCREEN_H as u32)
        .unwrap();

    let mut renderoptions = <RenderOptions as Default>::default();

    let cputhread = thread::spawn(move|| run_cpu(cpu, sender2, receiver1));

    'main : loop {
        for ev in display.poll_events() {
            use glium::glutin::Event;
            use glium::glutin::ElementState::{Pressed, Released};
            use glium::glutin::VirtualKeyCode;

            match ev {
                Event::Closed
                    => break 'main,
                Event::KeyboardInput(Pressed, _, Some(VirtualKeyCode::Escape))
                    => break 'main,
                Event::KeyboardInput(Pressed, _, Some(VirtualKeyCode::Key1))
                    => display.get_window().unwrap().set_inner_size(rboy::SCREEN_W as u32, rboy::SCREEN_H as u32),
                Event::KeyboardInput(Pressed, _, Some(VirtualKeyCode::R))
                    => display.get_window().unwrap().set_inner_size(rboy::SCREEN_W as u32 * scale, rboy::SCREEN_H as u32 * scale),
                Event::KeyboardInput(Pressed, _, Some(VirtualKeyCode::LShift))
                    => { let _ = sender1.send(GBEvent::SpeedUp); },
                Event::KeyboardInput(Released, _, Some(VirtualKeyCode::LShift))
                    => { let _ = sender1.send(GBEvent::SpeedDown); },
                Event::KeyboardInput(Pressed, _, Some(VirtualKeyCode::T))
                    => { renderoptions.linear_interpolation = !renderoptions.linear_interpolation; }
                Event::KeyboardInput(Pressed, _, Some(glutinkey)) => {
                    if let Some(key) = glutin_to_keypad(glutinkey) {
                        let _ = sender1.send(GBEvent::KeyDown(key));
                    }
                },
                Event::KeyboardInput(Released, _, Some(glutinkey)) => {
                    if let Some(key) = glutin_to_keypad(glutinkey) {
                        let _ = sender1.send(GBEvent::KeyUp(key));
                    }
                },
                _ => (),
            }
        }

        'recv: loop {
            match receiver2.try_recv() {
                Ok(data) => recalculate_screen(&display, &mut texture, &*data, &renderoptions),
                Err(TryRecvError::Empty) => break 'recv,
                Err(TryRecvError::Disconnected) => break 'main,
            }
        }
    }

    drop(sender1);
    let _ = cputhread.join();

    EXITCODE_SUCCESS
}

fn glutin_to_keypad(key: glium::glutin::VirtualKeyCode) -> Option<rboy::KeypadKey> {
    use glium::glutin::VirtualKeyCode;
    match key {
        VirtualKeyCode::Z => Some(rboy::KeypadKey::A),
        VirtualKeyCode::X => Some(rboy::KeypadKey::B),
        VirtualKeyCode::Up => Some(rboy::KeypadKey::Up),
        VirtualKeyCode::Down => Some(rboy::KeypadKey::Down),
        VirtualKeyCode::Left => Some(rboy::KeypadKey::Left),
        VirtualKeyCode::Right => Some(rboy::KeypadKey::Right),
        VirtualKeyCode::Space => Some(rboy::KeypadKey::Select),
        VirtualKeyCode::Return => Some(rboy::KeypadKey::Start),
        _ => None,
    }
}

fn recalculate_screen(display: &glium::backend::glutin_backend::GlutinFacade,
                      texture: &mut glium::texture::texture2d::Texture2d,
                      datavec: &[u8],
                      renderoptions: &RenderOptions)
{
    use glium::Surface;

    let interpolation_type = if renderoptions.linear_interpolation {
        glium::uniforms::MagnifySamplerFilter::Linear
    }
    else {
        glium::uniforms::MagnifySamplerFilter::Nearest
    };

    let rawimage2d = glium::texture::RawImage2d {
        data: std::borrow::Cow::Borrowed(datavec),
        width: rboy::SCREEN_W as u32,
        height: rboy::SCREEN_H as u32,
        format: glium::texture::ClientFormat::U8U8U8,
    };
    texture.write(
        glium::Rect {
            left: 0,
            bottom: 0,
            width: rboy::SCREEN_W as u32,
            height: rboy::SCREEN_H as u32
        },
        rawimage2d);

    // We use a custom BlitTarget to transform OpenGL coordinates to row-column coordinates
    let target = display.draw();
    let (target_w, target_h) = target.get_dimensions();
    texture.as_surface().blit_whole_color_to(
        &target,
        &glium::BlitTarget {
            left: 0,
            bottom: target_h,
            width: target_w as i32,
            height: -(target_h as i32)
        },
        interpolation_type);
    target.finish().unwrap();
}

fn warn(message: &'static str) {
    use std::io::Write;
    let _ = write!(&mut std::io::stderr(), "{}\n", message);
}

fn construct_cpu(filename: &str, classic_mode: bool, output_serial: bool) -> Option<Device> {
    let opt_c = match classic_mode {
        true => Device::new(filename),
        false => Device::new_cgb(filename),
    };
    let mut c = match opt_c
    {
        Ok(cpu) => { cpu },
        Err(message) => { warn(message); return None; },
    };
    c.set_stdout(output_serial);
    Some(c)
}

fn run_cpu(mut cpu: Device, sender: SyncSender<Vec<u8>>, receiver: Receiver<GBEvent>) {
    let periodic = timer_periodic(16);
    let mut limit_speed = true;

    let waitticks = (4194304f64 / 1000.0 * 16.0).round() as u32;
    let mut ticks = 0;

    'outer: loop {
        while ticks < waitticks {
            ticks += cpu.do_cycle();
            if cpu.check_and_reset_gpu_updated() {
                let data = cpu.get_gpu_data().to_vec();
                if let Err(..) = sender.send(data) {
                    break 'outer;
                }
            }
        }

        ticks -= waitticks;

        'recv: loop {
            match receiver.try_recv() {
                Ok(event) => {
                    match event {
                        GBEvent::KeyUp(key) => cpu.keyup(key),
                        GBEvent::KeyDown(key) => cpu.keydown(key),
                        GBEvent::SpeedUp => limit_speed = false,
                        GBEvent::SpeedDown => { limit_speed = true; cpu.sync_audio(); }
                    }
                },
                Err(TryRecvError::Empty) => break 'recv,
                Err(TryRecvError::Disconnected) => break 'outer,
            }
        }

        if limit_speed { let _ = periodic.recv(); }
    }
}

fn timer_periodic(ms: u32) -> Receiver<()> {
    let (tx, rx) = std::sync::mpsc::sync_channel(1);
    std::thread::spawn(move || {
        loop {
            std::thread::sleep_ms(ms);
            if tx.send(()).is_err() {
                break;
            }
        }
    });
    rx
}

struct CpalPlayer {
    voice: cpal::Voice,
}

impl CpalPlayer {
    fn get() -> Option<CpalPlayer> {
        if cpal::get_endpoints_list().count() == 0 { return None; }

        let endpoint = match cpal::get_default_endpoint() {
            Some(e) => e,
            None => return None,
        };

        let format = match endpoint.get_supported_formats_list().ok().and_then(|mut v| v.next()) {
            Some(f) => f,
            None => return None,
        };

        cpal::Voice::new(&endpoint, &format).ok().map(|v| CpalPlayer { voice: v })
    }
}

impl rboy::AudioPlayer for CpalPlayer {
    fn play(&mut self, buf_left: &[f32], buf_right: &[f32]) {
        debug_assert!(buf_left.len() == buf_right.len());

        let left_idx = self.voice.format().channels.iter().position(|c| *c == cpal::ChannelPosition::FrontLeft);
        let right_idx = self.voice.format().channels.iter().position(|c| *c == cpal::ChannelPosition::FrontRight);

        let channel_count = self.voice.format().channels.len();

        let count = buf_left.len();
        let mut done = 0;
        let mut lastdone = count;

        while lastdone != done && done < count {
            lastdone = done;
            let buf_left_next = &buf_left[done..];
            let buf_right_next = &buf_right[done..];
            match self.voice.append_data(count - done) {
                cpal::UnknownTypeBuffer::U16(mut buffer) => {
                    for (i, sample) in buffer.chunks_mut(channel_count).enumerate() {
                        if let Some(idx) = left_idx {
                            sample[idx] = (buf_left_next[i] * (std::i16::MAX as f32) + (std::i16::MAX as f32)) as u16;
                        }
                        if let Some(idx) = right_idx {
                            sample[idx] = (buf_right_next[i] * (std::i16::MAX as f32) + (std::i16::MAX as f32)) as u16;
                        }
                        done += 1;
                    }
                }
                cpal::UnknownTypeBuffer::I16(mut buffer) => {
                    for (i, sample) in buffer.chunks_mut(channel_count).enumerate() {
                        if let Some(idx) = left_idx {
                            sample[idx] = (buf_left_next[i] * std::i16::MAX as f32) as i16;
                        }
                        if let Some(idx) = right_idx {
                            sample[idx] = (buf_right_next[i] * std::i16::MAX as f32) as i16;
                        }
                        done += 1;
                    }
                }
                cpal::UnknownTypeBuffer::F32(mut buffer) => {
                    for (i, sample) in buffer.chunks_mut(channel_count).enumerate() {
                        if let Some(idx) = left_idx {
                            sample[idx] = buf_left_next[i];
                        }
                        if let Some(idx) = right_idx {
                            sample[idx] = buf_right_next[i];
                        }
                        done += 1;
                    }
                }
            }
        }
        self.voice.play();
    }

    fn samples_rate(&self) -> u32 {
        self.voice.format().samples_rate.0
    }

    fn underflowed(&self) -> bool {
        self.voice.underflowed()
    }
}
<commit_msg>Fix GPU data send loop<commit_after>#![crate_name = "rboy"]

extern crate clap;
extern crate cpal;
extern crate glium;
extern crate rboy;

use glium::DisplayBuild;
use rboy::device::Device;
use std::sync::mpsc::{self, Receiver, SyncSender, TryRecvError, TrySendError};
use std::thread;
use std::error::Error;

const EXITCODE_SUCCESS : i32 = 0;
const EXITCODE_CPULOADFAILS : i32 = 2;

#[derive(Default)]
struct RenderOptions {
    pub linear_interpolation: bool,
}

enum GBEvent {
    KeyUp(rboy::KeypadKey),
    KeyDown(rboy::KeypadKey),
    SpeedUp,
    SpeedDown,
}

fn main() {
    let exit_status = real_main();
    if exit_status != EXITCODE_SUCCESS {
        std::process::exit(exit_status);
    }
}

fn real_main() -> i32 {
    let matches = clap::App::new("rboy")
        .version("0.1")
        .author("Mathijs van de Nes")
        .about("A Gameboy Colour emulator written in Rust")
        .arg(clap::Arg::with_name("filename")
             .help("Sets the ROM file to load")
             .required(true))
        .arg(clap::Arg::with_name("serial")
             .help("Prints the data from the serial port to stdout")
             .short("s")
             .long("serial"))
        .arg(clap::Arg::with_name("classic")
             .help("Forces the emulator to run in classic Gameboy mode")
             .short("c")
             .long("classic"))
        .arg(clap::Arg::with_name("scale")
             .help("Sets the scale of the interface. Default: 2")
             .short("x")
             .long("scale")
             .validator(|s|
                 match s.parse::<u32>() {
                     Err(e) => Err(format!("Could not parse scale: {}", e.description())),
                     Ok(s) if s < 1 => Err("Scale must be at least 1".to_owned()),
                     Ok(s) if s > 8 => Err("Scale may be at most 8".to_owned()),
                     Ok(..) => Ok(()),
                 })
             .takes_value(true))
        .arg(clap::Arg::with_name("audio")
             .help("Enables audio")
             .short("a")
             .long("audio"))
        .get_matches();

    let opt_serial = matches.is_present("serial");
    let opt_classic = matches.is_present("classic");
    let opt_audio = matches.is_present("audio");
    let filename = matches.value_of("filename").unwrap();
    let scale = matches.value_of("scale").unwrap_or("2").parse::<u32>().unwrap();

    let cpu = construct_cpu(filename, opt_classic, opt_serial);
    if cpu.is_none() { return EXITCODE_CPULOADFAILS; }
    let mut cpu = cpu.unwrap();
    if opt_audio {
        let player = CpalPlayer::get();
        match player {
            Some(v) => cpu.enable_audio(Box::new(v) as Box<rboy::AudioPlayer>),
            None => { warn("Could not open audio device"); return EXITCODE_CPULOADFAILS; },
        }
    }
    let romname = cpu.romname();

    let (sender1, receiver1) = mpsc::channel();
    let (sender2, receiver2) = mpsc::sync_channel(1);

    let display = glium::glutin::WindowBuilder::new()
        .with_dimensions(rboy::SCREEN_W as u32 * scale, rboy::SCREEN_H as u32 * scale)
        .with_title("RBoy - ".to_owned() + &romname)
        .build_glium()
        .unwrap();

    let mut texture = glium::texture::texture2d::Texture2d::empty_with_format(
            &display,
            glium::texture::UncompressedFloatFormat::U8U8U8,
            glium::texture::MipmapsOption::NoMipmap,
            rboy::SCREEN_W as u32,
            rboy::SCREEN_H as u32)
        .unwrap();

    let mut renderoptions = <RenderOptions as Default>::default();

    let cputhread = thread::spawn(move|| run_cpu(cpu, sender2, receiver1));

    'main : loop {
        for ev in display.poll_events() {
            use glium::glutin::Event;
            use glium::glutin::ElementState::{Pressed, Released};
            use glium::glutin::VirtualKeyCode;

            match ev {
                Event::Closed
                    => break 'main,
                Event::KeyboardInput(Pressed, _, Some(VirtualKeyCode::Escape))
                    => break 'main,
                Event::KeyboardInput(Pressed, _, Some(VirtualKeyCode::Key1))
                    => display.get_window().unwrap().set_inner_size(rboy::SCREEN_W as u32, rboy::SCREEN_H as u32),
                Event::KeyboardInput(Pressed, _, Some(VirtualKeyCode::R))
                    => display.get_window().unwrap().set_inner_size(rboy::SCREEN_W as u32 * scale, rboy::SCREEN_H as u32 * scale),
                Event::KeyboardInput(Pressed, _, Some(VirtualKeyCode::LShift))
                    => { let _ = sender1.send(GBEvent::SpeedUp); },
                Event::KeyboardInput(Released, _, Some(VirtualKeyCode::LShift))
                    => { let _ = sender1.send(GBEvent::SpeedDown); },
                Event::KeyboardInput(Pressed, _, Some(VirtualKeyCode::T))
                    => { renderoptions.linear_interpolation = !renderoptions.linear_interpolation; }
                Event::KeyboardInput(Pressed, _, Some(glutinkey)) => {
                    if let Some(key) = glutin_to_keypad(glutinkey) {
                        let _ = sender1.send(GBEvent::KeyDown(key));
                    }
                },
                Event::KeyboardInput(Released, _, Some(glutinkey)) => {
                    if let Some(key) = glutin_to_keypad(glutinkey) {
                        let _ = sender1.send(GBEvent::KeyUp(key));
                    }
                },
                _ => (),
            }
        }

        match receiver2.try_recv() {
            Ok(data) => recalculate_screen(&display, &mut texture, &*data, &renderoptions),
            Err(TryRecvError::Empty) => (),
            Err(TryRecvError::Disconnected) => break 'main,
        }
    }

    drop(sender1);
    let _ = cputhread.join();

    EXITCODE_SUCCESS
}

fn glutin_to_keypad(key: glium::glutin::VirtualKeyCode) -> Option<rboy::KeypadKey> {
    use glium::glutin::VirtualKeyCode;
    match key {
        VirtualKeyCode::Z => Some(rboy::KeypadKey::A),
        VirtualKeyCode::X => Some(rboy::KeypadKey::B),
        VirtualKeyCode::Up => Some(rboy::KeypadKey::Up),
        VirtualKeyCode::Down => Some(rboy::KeypadKey::Down),
        VirtualKeyCode::Left => Some(rboy::KeypadKey::Left),
        VirtualKeyCode::Right => Some(rboy::KeypadKey::Right),
        VirtualKeyCode::Space => Some(rboy::KeypadKey::Select),
        VirtualKeyCode::Return => Some(rboy::KeypadKey::Start),
        _ => None,
    }
}

fn recalculate_screen(display: &glium::backend::glutin_backend::GlutinFacade,
                      texture: &mut glium::texture::texture2d::Texture2d,
                      datavec: &[u8],
                      renderoptions: &RenderOptions)
{
    use glium::Surface;

    let interpolation_type = if renderoptions.linear_interpolation {
        glium::uniforms::MagnifySamplerFilter::Linear
    }
    else {
        glium::uniforms::MagnifySamplerFilter::Nearest
    };

    let rawimage2d = glium::texture::RawImage2d {
        data: std::borrow::Cow::Borrowed(datavec),
        width: rboy::SCREEN_W as u32,
        height: rboy::SCREEN_H as u32,
        format: glium::texture::ClientFormat::U8U8U8,
    };
    texture.write(
        glium::Rect {
            left: 0,
            bottom: 0,
            width: rboy::SCREEN_W as u32,
            height: rboy::SCREEN_H as u32
        },
        rawimage2d);

    // We use a custom BlitTarget to transform OpenGL coordinates to row-column coordinates
    let target = display.draw();
    let (target_w, target_h) = target.get_dimensions();
    texture.as_surface().blit_whole_color_to(
        &target,
        &glium::BlitTarget {
            left: 0,
            bottom: target_h,
            width: target_w as i32,
            height: -(target_h as i32)
        },
        interpolation_type);
    target.finish().unwrap();
}

fn warn(message: &'static str) {
    use std::io::Write;
    let _ = write!(&mut std::io::stderr(), "{}\n", message);
}

fn construct_cpu(filename: &str, classic_mode: bool, output_serial: bool) -> Option<Device> {
    let opt_c = match classic_mode {
        true => Device::new(filename),
        false => Device::new_cgb(filename),
    };
    let mut c = match opt_c
    {
        Ok(cpu) => { cpu },
        Err(message) => { warn(message); return None; },
    };
    c.set_stdout(output_serial);
    Some(c)
}

fn run_cpu(mut cpu: Device, sender: SyncSender<Vec<u8>>, receiver: Receiver<GBEvent>) {
    let periodic = timer_periodic(16);
    let mut limit_speed = true;

    let waitticks = (4194304f64 / 1000.0 * 16.0).round() as u32;
    let mut ticks = 0;

    'outer: loop {
        while ticks < waitticks {
            ticks += cpu.do_cycle();
            if cpu.check_and_reset_gpu_updated() {
                let data = cpu.get_gpu_data().to_vec();
                if let Err(TrySendError::Disconnected(..)) = sender.try_send(data) {
                    break 'outer;
                }
            }
        }

        ticks -= waitticks;

        'recv: loop {
            match receiver.try_recv() {
                Ok(event) => {
                    match event {
                        GBEvent::KeyUp(key) => cpu.keyup(key),
                        GBEvent::KeyDown(key) => cpu.keydown(key),
                        GBEvent::SpeedUp => limit_speed = false,
                        GBEvent::SpeedDown => { limit_speed = true; cpu.sync_audio(); }
                    }
                },
                Err(TryRecvError::Empty) => break 'recv,
                Err(TryRecvError::Disconnected) => break 'outer,
            }
        }

        if limit_speed { let _ = periodic.recv(); }
    }
}

fn timer_periodic(ms: u32) -> Receiver<()> {
    let (tx, rx) = std::sync::mpsc::sync_channel(1);
    std::thread::spawn(move || {
        loop {
            std::thread::sleep_ms(ms);
            if tx.send(()).is_err() {
                break;
            }
        }
    });
    rx
}

struct CpalPlayer {
    voice: cpal::Voice,
}

impl CpalPlayer {
    fn get() -> Option<CpalPlayer> {
        if cpal::get_endpoints_list().count() == 0 { return None; }

        let endpoint = match cpal::get_default_endpoint() {
            Some(e) => e,
            None => return None,
        };

        let format = match endpoint.get_supported_formats_list().ok().and_then(|mut v| v.next()) {
            Some(f) => f,
            None => return None,
        };

        cpal::Voice::new(&endpoint, &format).ok().map(|v| CpalPlayer { voice: v })
    }
}

impl rboy::AudioPlayer for CpalPlayer {
    fn play(&mut self, buf_left: &[f32], buf_right: &[f32]) {
        debug_assert!(buf_left.len() == buf_right.len());

        let left_idx = self.voice.format().channels.iter().position(|c| *c == cpal::ChannelPosition::FrontLeft);
        let right_idx = self.voice.format().channels.iter().position(|c| *c == cpal::ChannelPosition::FrontRight);

        let channel_count = self.voice.format().channels.len();

        let count = buf_left.len();
        let mut done = 0;
        let mut lastdone = count;

        while lastdone != done && done < count {
            lastdone = done;
            let buf_left_next = &buf_left[done..];
            let buf_right_next = &buf_right[done..];
            match self.voice.append_data(count - done) {
                cpal::UnknownTypeBuffer::U16(mut buffer) => {
                    for (i, sample) in buffer.chunks_mut(channel_count).enumerate() {
                        if let Some(idx) = left_idx {
                            sample[idx] = (buf_left_next[i] * (std::i16::MAX as f32) + (std::i16::MAX as f32)) as u16;
                        }
                        if let Some(idx) = right_idx {
                            sample[idx] = (buf_right_next[i] * (std::i16::MAX as f32) + (std::i16::MAX as f32)) as u16;
                        }
                        done += 1;
                    }
                }
                cpal::UnknownTypeBuffer::I16(mut buffer) => {
                    for (i, sample) in buffer.chunks_mut(channel_count).enumerate() {
                        if let Some(idx) = left_idx {
                            sample[idx] = (buf_left_next[i] * std::i16::MAX as f32) as i16;
                        }
                        if let Some(idx) = right_idx {
                            sample[idx] = (buf_right_next[i] * std::i16::MAX as f32) as i16;
                        }
                        done += 1;
                    }
                }
                cpal::UnknownTypeBuffer::F32(mut buffer) => {
                    for (i, sample) in buffer.chunks_mut(channel_count).enumerate() {
                        if let Some(idx) = left_idx {
                            sample[idx] = buf_left_next[i];
                        }
                        if let Some(idx) = right_idx {
                            sample[idx] = buf_right_next[i];
                        }
                        done += 1;
                    }
                }
            }
        }
        self.voice.play();
    }

    fn samples_rate(&self) -> u32 {
        self.voice.format().samples_rate.0
    }

    fn underflowed(&self) -> bool {
        self.voice.underflowed()
    }
}
<|endoftext|>


<commit_before>use std::thread;
use std::net;
use std::env;

fn socket(listen_on: net::SocketAddr) -> net::UdpSocket {
    let attempt = net::UdpSocket::bind(listen_on);
    let mut socket;
    match attempt {
        Ok(sock) => {
            socket = sock;
        },
    Err(err) => panic!("Could not bind: {}", err)
    }
    socket
}

fn read_message(socket: net::UdpSocket) -> Vec<u8> {
    let mut buf: [u8; 1] = [0; 1];
    let result = socket.recv_from(&mut buf);
    drop(socket);
    let mut data;
    match result {
        Ok((amt, _)) => {
        data = Vec::from(&buf[0..amt]);
        },
    Err(err) => panic!("Read error: {}", err)
    }
    data
}

pub fn send_message(send_addr: net::SocketAddr, target: net::SocketAddr, data: Vec<u8>) {
    let socket = socket(send_addr);
    let _ = socket.send_to(&data, target);
    drop(socket);
    // match result {
    //     Ok(amt) => println!("Sent {} bytes", amt),
    //     Err(err) => panic!("Write error: {}", err)
    // }
}

pub fn listen(listen_on: net::SocketAddr) -> thread::JoinHandle<Vec<u8>> {
    let socket = socket(listen_on);
    let handle = thread::spawn(move || {
        read_message(socket)
    });
    handle
}

#[cfg(test)]
mod test {
    use std::net;
    use std::thread;
    use super::*;

    #[test]
    fn test_udp() {
        println!("UDP");
        let ip = net::Ipv4Addr::new(127, 0, 0, 1);
        let listen_addr = net::SocketAddrV4::new(ip, 8888);
        let send_addr = net::SocketAddrV4::new(ip, 8889);
        let future = listen(net::SocketAddr::V4(listen_addr));
        let message: Vec<u8> = vec![10];
        // give the thread 3s to open the socket
        thread::sleep_ms(3000);
        send_message(
            net::SocketAddr::V4(send_addr),
            net::SocketAddr::V4(listen_addr),
            message);
        println!("Waiting");
        let received = future.join().unwrap();
        println!("Got {} bytes", received.len());
        assert_eq!(1, received.len());
        assert_eq!(10, received[0]);
    }
}

fn main() {
    // get command-line input

    // this waits on a prompt, so
    // http://stackoverflow.com/a/27973038/416626
    // use std::io;
    // use std::io::prelude::*;
    // let stdin = io::stdin();
    // let inp = stdin
    //     .lock()
    //     .lines()
    //     .next()
    //     .unwrap()
    //     .unwrap()
    //     .trim()
    //     .to_string();

    // Strings do not live for the entire life of your program
    // http://stackoverflow.com/a/23977218/416626
    let args: Vec<String> = env::args().collect();
    let arg = args[1].clone();
    let intermediate: &str = &*arg;

    // match input or bail out horribly
    let to_send = match intermediate {
        "white" => "white".to_string(),
        "red" => "red".to_string(),
        "orange" => "orange".to_string(),
        "yellow" => "yellow".to_string(),
        "green" => "green".to_string(),
        "cyan" => "cyan".to_string(),
        "blue" => "blue".to_string(),
        "purple" => "purple".to_string(),
        "black" => "black".to_string(),
        "?" => "question".to_string(),
        "!" => "exclamation".to_string(),
        "quit" => "quit".to_string(),
        _ => panic!("Unknown option!")
    };

    let mut message: Vec<u8> = Vec::new();
    message.extend(to_send.as_bytes().iter().cloned());
    let ip = net::Ipv4Addr::new(127, 0, 0, 1);
    let listen_addr = net::SocketAddrV4::new(ip, 1738);
    let send_addr = net::SocketAddrV4::new(ip, 0);
    send_message(
        net::SocketAddr::V4(send_addr),
        net::SocketAddr::V4(listen_addr),
        message);
}
<commit_msg>Reformat faffing<commit_after>use std::thread;
use std::net;
use std::env;

fn socket(listen_on: net::SocketAddr) -> net::UdpSocket {
    let attempt = net::UdpSocket::bind(listen_on);
    let mut socket;
    match attempt {
        Ok(sock) => {
            socket = sock;
        },
    Err(err) => panic!("Could not bind: {}", err)
    }
    socket
}

fn read_message(socket: net::UdpSocket) -> Vec<u8> {
    let mut buf: [u8; 1] = [0; 1];
    let result = socket.recv_from(&mut buf);
    drop(socket);
    let mut data;
    match result {
        Ok((amt, _)) => {
        data = Vec::from(&buf[0..amt]);
        },
    Err(err) => panic!("Read error: {}", err)
    }
    data
}

pub fn send_message(send_addr: net::SocketAddr, target: net::SocketAddr, data: Vec<u8>) {
    let socket = socket(send_addr);
    let _ = socket.send_to(&data, target);
    drop(socket);
    // match result {
    //     Ok(amt) => println!("Sent {} bytes", amt),
    //     Err(err) => panic!("Write error: {}", err)
    // }
}

pub fn listen(listen_on: net::SocketAddr) -> thread::JoinHandle<Vec<u8>> {
    let socket = socket(listen_on);
    let handle = thread::spawn(move || {
        read_message(socket)
    });
    handle
}

#[cfg(test)]
mod test {
    use std::net;
    use std::thread;
    use super::*;

    #[test]
    fn test_udp() {
        println!("UDP");
        let ip = net::Ipv4Addr::new(127, 0, 0, 1);
        let listen_addr = net::SocketAddrV4::new(ip, 8888);
        let send_addr = net::SocketAddrV4::new(ip, 8889);
        let future = listen(net::SocketAddr::V4(listen_addr));
        let message: Vec<u8> = vec![10];
        // give the thread 3s to open the socket
        thread::sleep_ms(3000);
        send_message(
            net::SocketAddr::V4(send_addr),
            net::SocketAddr::V4(listen_addr),
            message);
        println!("Waiting");
        let received = future.join().unwrap();
        println!("Got {} bytes", received.len());
        assert_eq!(1, received.len());
        assert_eq!(10, received[0]);
    }
}

fn main() {
    // get command-line input

    // this waits on a prompt, so
    // http://stackoverflow.com/a/27973038/416626
    // use std::io;
    // use std::io::prelude::*;
    // let stdin = io::stdin();
    // let inp = stdin
    //     .lock()
    //     .lines()
    //     .next()
    //     .unwrap()
    //     .unwrap()
    //     .trim()
    //     .to_string();

    // Strings do not live for the entire life of your program
    // http://stackoverflow.com/a/23977218/416626
    let args: Vec<String> = env::args().collect();
    let arg = args[1].clone();
    let intermediate: &str = &*arg;

    // match input or bail out horribly
    let to_send = match intermediate {
        "white" => "white".to_string(),
        "red" => "red".to_string(),
        "orange" => "orange".to_string(),
        "yellow" => "yellow".to_string(),
        "green" => "green".to_string(),
        "cyan" => "cyan".to_string(),
        "blue" => "blue".to_string(),
        "purple" => "purple".to_string(),
        "black" => "black".to_string(),
        "?" => "question".to_string(),
        "!" => "exclamation".to_string(),
        "quit" => "quit".to_string(),
        _ => panic!("Unknown option!")
    };

    let mut message: Vec<u8> = Vec::new();
    message.extend(to_send
        .as_bytes()
        .iter()
        .cloned()
    );
    let ip = net::Ipv4Addr::new(127, 0, 0, 1);
    let listen_addr = net::SocketAddrV4::new(ip, 1738);
    let send_addr = net::SocketAddrV4::new(ip, 0);
    send_message(
        net::SocketAddr::V4(send_addr),
        net::SocketAddr::V4(listen_addr),
        message);
}
<|endoftext|>