<commit_before>#[macro_use]
extern crate glium;
use glium::glutin;

use std::collections::HashMap;

#[macro_use]
mod support;

pub type Color = [f32; 4];

enum Shape {
    Clear(Color),
    Polygon(Vec<(f32, f32)>, Color),
    Line(f32, f32, f32, f32, Color),
}

pub struct Window {
    display: glium::Display,
    events_loop: glium::glutin::EventsLoop,
    color: [f32; 4],
    shapes: Vec<Shape>,
    keys: HashMap<glutin::VirtualKeyCode, bool>,
}

impl Window {
    pub fn new(title: &str, width: u32, height: u32) -> Self {
        let events_loop = glutin::EventsLoop::new();
        let window = glutin::WindowBuilder::new()
            .with_title(title)
            .with_dimensions(width, height);
        let context = glutin::ContextBuilder::new();
        let display = glium::Display::new(window, context, &events_loop).unwrap();

        Window {
            display: display,
            events_loop: events_loop,
            color: [1.0, 1.0, 1.0, 1.0],
            shapes: Vec::new(),
            keys: HashMap::new(),
        }
    }

    pub fn start_loop<F>(&mut self, mut callback: F)
    where
        F: FnMut(&mut Window, f32) -> support::Action,
    {
        let program = support::shaders::load_program(&self.display).unwrap();
        let params = glium::DrawParameters {
            blend: glium::draw_parameters::Blend::alpha_blending(),
            ..Default::default()
        };

        use glium::Surface;
        support::start_loop(|delta| {

            let mut target = self.display.draw();

            let mut rtn = callback(self, delta);
            for shape in self.shapes.iter() {
                match shape {
                    &Shape::Clear(color) => {
                        target.clear_color(color[0], color[1], color[2], color[3])
                    }
                    &Shape::Line(x1, y1, x2, y2, color) => {
                        let vert_buff =
                            support::buffer::line_vert_buff(&self.display, x1, y1, x2, y2, color)
                                .unwrap();
                        let indices =
                            glium::index::NoIndices(glium::index::PrimitiveType::LineStrip);
                        target
                            .draw(
                                &vert_buff,
                                &indices,
                                &program,
                                &glium::uniforms::EmptyUniforms,
                                &params,
                            )
                            .unwrap();
                    }
                    &Shape::Polygon(ref points, color) => {
                        let vert_buff =
                            support::buffer::poly_vert_buffer(&self.display, &points, color)
                                .unwrap();
                        let indices =
                            glium::index::NoIndices(glium::index::PrimitiveType::TriangleFan);
                        target
                            .draw(
                                &vert_buff,
                                &indices,
                                &program,
                                &glium::uniforms::EmptyUniforms,
                                &params,
                            )
                            .unwrap();
                    }
                }
            }

            self.shapes.clear();

            target.finish().unwrap();

            let mut keys: HashMap<glutin::VirtualKeyCode, bool> = HashMap::new();
            self.events_loop.poll_events(|ev| match ev {
                glium::glutin::Event::WindowEvent { event, .. } => {
                    match event {
                        glutin::WindowEvent::Closed => rtn = support::Action::Stop,
                        glutin::WindowEvent::KeyboardInput { input, .. } => {
                            match input.virtual_keycode {
                                Some(key) => {
                                    keys.insert(
                                        key,
                                        match input.state {
                                            glutin::ElementState::Pressed => true,
                                            glutin::ElementState::Released => false,
                                        },
                                    );
                                }
                                None => (),
                            }
                        }
                        _ => (),
                    }
                }
                _ => (),
            });

            for (key, value) in keys {
                self.keys.insert(key, value);
            }

            rtn
        });
    }

    pub fn clear_color(&mut self, red: f32, green: f32, blue: f32, alpha: f32) {
        let shape = Shape::Clear([red, green, blue, alpha]);
        self.shapes.push(shape);
    }

    pub fn set_color(&mut self, red: f32, green: f32, blue: f32, alpha: f32) {
        self.color = [red, green, blue, alpha];
    }

    pub fn set_color_html(&mut self, color: &str) {
        let mut bytes = color.as_bytes();
        if bytes[0] == b'#' {
            bytes = &bytes[1..];
        }

        use std::str::from_utf8;
        use std::u8;

        if bytes.len() == 3 {
            let r = u8::from_str_radix(from_utf8(&vec![bytes[0], bytes[0]]).unwrap(), 16).unwrap();
            let g = u8::from_str_radix(from_utf8(&vec![bytes[1], bytes[1]]).unwrap(), 16).unwrap();
            let b = u8::from_str_radix(from_utf8(&vec![bytes[2], bytes[2]]).unwrap(), 16).unwrap();
            self.set_color(
                (r as f32) / 255.0,
                (g as f32) / 255.0,
                (b as f32) / 255.0,
                1.0,
            );
        } else if bytes.len() == 6 {
            let r = u8::from_str_radix(from_utf8(&bytes[0..2]).unwrap(), 16).unwrap();
            let g = u8::from_str_radix(from_utf8(&bytes[2..4]).unwrap(), 16).unwrap();
            let b = u8::from_str_radix(from_utf8(&bytes[4..6]).unwrap(), 16).unwrap();
            self.set_color(
                (r as f32) / 255.0,
                (g as f32) / 255.0,
                (b as f32) / 255.0,
                1.0,
            );
        }
    }

    pub fn draw_rect(&mut self, p1: (f32, f32), p2: (f32, f32)) {
        let shape = Shape::Polygon(
            vec![(p1.0, p1.1), (p1.0, p2.1), (p2.0, p2.1), (p2.0, p1.1)],
            self.color,
        );
        self.shapes.push(shape);
    }

    pub fn draw_line(&mut self, x1: f32, y1: f32, x2: f32, y2: f32) {
        let shape = Shape::Line(x1, y1, x2, y2, self.color);
        self.shapes.push(shape);
    }

    pub fn draw(&mut self, points: &[(f32, f32)]) {
        let shape = Shape::Polygon(Vec::from(points), self.color);
        self.shapes.push(shape);
    }
}

fn main() {
    let mut window = Window::new("Hello World", 640, 480);

    window.start_loop(|app, delta| {
        app.clear_color(0.1, 0.3, 0.2, 1.0);

        app.set_color_html("312");
        app.draw_rect((-0.5, -0.5), (0.5, 0.5));

        app.set_color_html("#033112");
        app.draw_rect((0.0, 0.0), (-1.0, -1.0));

        app.set_color(1.0, 0.0, 0.0, 1.0);
        app.draw_line(0.0, 0.0, 1.0, 1.0);

        app.set_color(0.0, 0.0, 1.0, 0.3);
        app.draw(&[(0.0, 0.0), (1.0, 1.0), (1.0, 0.0)]);

        app.set_color(0.0, 1.0, 0.0, 0.3);
        app.draw(&[(0.0, 0.0), (0.0, 1.0), (1.0, 1.0)]);

        let circle: Vec<(f32, f32)> = (0..360)
            .map(|d| {
                let r = (d as f32).to_radians();
                (r.cos(), r.sin())
            })
            .collect();
        app.set_color(1.0, 0.0, 0.0, 0.3);
        app.draw(&circle);

        support::Action::Continue
    });
}
<commit_msg>Added draw_circle method<commit_after>#[macro_use]
extern crate glium;
use glium::glutin;

use std::collections::HashMap;

#[macro_use]
mod support;

pub type Color = [f32; 4];

enum Shape {
    Clear(Color),
    Polygon(Vec<(f32, f32)>, Color),
    Line(f32, f32, f32, f32, Color),
}

pub struct Window {
    display: glium::Display,
    events_loop: glium::glutin::EventsLoop,
    color: [f32; 4],
    shapes: Vec<Shape>,
    keys: HashMap<glutin::VirtualKeyCode, bool>,
}

impl Window {
    pub fn new(title: &str, width: u32, height: u32) -> Self {
        let events_loop = glutin::EventsLoop::new();
        let window = glutin::WindowBuilder::new()
            .with_title(title)
            .with_dimensions(width, height);
        let context = glutin::ContextBuilder::new();
        let display = glium::Display::new(window, context, &events_loop).unwrap();

        Window {
            display: display,
            events_loop: events_loop,
            color: [1.0, 1.0, 1.0, 1.0],
            shapes: Vec::new(),
            keys: HashMap::new(),
        }
    }

    pub fn start_loop<F>(&mut self, mut callback: F)
    where
        F: FnMut(&mut Window, f32) -> support::Action,
    {
        let program = support::shaders::load_program(&self.display).unwrap();
        let params = glium::DrawParameters {
            blend: glium::draw_parameters::Blend::alpha_blending(),
            ..Default::default()
        };

        use glium::Surface;
        support::start_loop(|delta| {

            let mut target = self.display.draw();

            let mut rtn = callback(self, delta);
            for shape in self.shapes.iter() {
                match shape {
                    &Shape::Clear(color) => {
                        target.clear_color(color[0], color[1], color[2], color[3])
                    }
                    &Shape::Line(x1, y1, x2, y2, color) => {
                        let vert_buff =
                            support::buffer::line_vert_buff(&self.display, x1, y1, x2, y2, color)
                                .unwrap();
                        let indices =
                            glium::index::NoIndices(glium::index::PrimitiveType::LineStrip);
                        target
                            .draw(
                                &vert_buff,
                                &indices,
                                &program,
                                &glium::uniforms::EmptyUniforms,
                                &params,
                            )
                            .unwrap();
                    }
                    &Shape::Polygon(ref points, color) => {
                        let vert_buff =
                            support::buffer::poly_vert_buffer(&self.display, &points, color)
                                .unwrap();
                        let indices =
                            glium::index::NoIndices(glium::index::PrimitiveType::TriangleFan);
                        target
                            .draw(
                                &vert_buff,
                                &indices,
                                &program,
                                &glium::uniforms::EmptyUniforms,
                                &params,
                            )
                            .unwrap();
                    }
                }
            }

            self.shapes.clear();

            target.finish().unwrap();

            let mut keys: HashMap<glutin::VirtualKeyCode, bool> = HashMap::new();
            self.events_loop.poll_events(|ev| match ev {
                glium::glutin::Event::WindowEvent { event, .. } => {
                    match event {
                        glutin::WindowEvent::Closed => rtn = support::Action::Stop,
                        glutin::WindowEvent::KeyboardInput { input, .. } => {
                            match input.virtual_keycode {
                                Some(key) => {
                                    keys.insert(
                                        key,
                                        match input.state {
                                            glutin::ElementState::Pressed => true,
                                            glutin::ElementState::Released => false,
                                        },
                                    );
                                }
                                None => (),
                            }
                        }
                        _ => (),
                    }
                }
                _ => (),
            });

            for (key, value) in keys {
                self.keys.insert(key, value);
            }

            rtn
        });
    }

    pub fn clear_color(&mut self, red: f32, green: f32, blue: f32, alpha: f32) {
        let shape = Shape::Clear([red, green, blue, alpha]);
        self.shapes.push(shape);
    }

    pub fn set_color(&mut self, red: f32, green: f32, blue: f32, alpha: f32) {
        self.color = [red, green, blue, alpha];
    }

    pub fn set_color_html(&mut self, color: &str) {
        let mut bytes = color.as_bytes();
        if bytes[0] == b'#' {
            bytes = &bytes[1..];
        }

        use std::str::from_utf8;
        use std::u8;

        if bytes.len() == 3 {
            let r = u8::from_str_radix(from_utf8(&vec![bytes[0], bytes[0]]).unwrap(), 16).unwrap();
            let g = u8::from_str_radix(from_utf8(&vec![bytes[1], bytes[1]]).unwrap(), 16).unwrap();
            let b = u8::from_str_radix(from_utf8(&vec![bytes[2], bytes[2]]).unwrap(), 16).unwrap();
            self.set_color(
                (r as f32) / 255.0,
                (g as f32) / 255.0,
                (b as f32) / 255.0,
                1.0,
            );
        } else if bytes.len() == 6 {
            let r = u8::from_str_radix(from_utf8(&bytes[0..2]).unwrap(), 16).unwrap();
            let g = u8::from_str_radix(from_utf8(&bytes[2..4]).unwrap(), 16).unwrap();
            let b = u8::from_str_radix(from_utf8(&bytes[4..6]).unwrap(), 16).unwrap();
            self.set_color(
                (r as f32) / 255.0,
                (g as f32) / 255.0,
                (b as f32) / 255.0,
                1.0,
            );
        }
    }

    pub fn draw(&mut self, points: &[(f32, f32)]) {
        let shape = Shape::Polygon(Vec::from(points), self.color);
        self.shapes.push(shape);
    }

    pub fn draw_line(&mut self, x1: f32, y1: f32, x2: f32, y2: f32) {
        let shape = Shape::Line(x1, y1, x2, y2, self.color);
        self.shapes.push(shape);
    }

    pub fn draw_rect(&mut self, p1: (f32, f32), p2: (f32, f32)) {
        let shape = Shape::Polygon(
            vec![(p1.0, p1.1), (p1.0, p2.1), (p2.0, p2.1), (p2.0, p1.1)],
            self.color,
        );
        self.shapes.push(shape);
    }

    pub fn draw_circle(&mut self, x: f32, y: f32, rx: f32, ry: f32, step_size: u32) {
        let circle: Vec<(f32, f32)> = (0u32..360)
            .filter(|d| d % step_size == 0)
            .map(|d| {
                let r = (d as f32).to_radians();
                (r.cos() * rx, r.sin() * ry)
            })
            .collect();
        let shape = Shape::Polygon(circle, self.color);
        self.shapes.push(shape);
    }
}

fn main() {
    let mut window = Window::new("Hello World", 640, 480);

    window.start_loop(|app, delta| {
        app.clear_color(0.1, 0.3, 0.2, 1.0);

        app.set_color_html("312");
        app.draw_rect((-0.5, -0.5), (0.5, 0.5));

        app.set_color_html("#033112");
        app.draw_rect((0.0, 0.0), (-1.0, -1.0));

        app.set_color(1.0, 0.0, 0.0, 1.0);
        app.draw_line(0.0, 0.0, 1.0, 1.0);

        app.set_color(0.0, 0.0, 1.0, 0.3);
        app.draw(&[(0.0, 0.0), (1.0, 1.0), (1.0, 0.0)]);

        app.set_color(0.0, 1.0, 0.0, 0.3);
        app.draw(&[(0.0, 0.0), (0.0, 1.0), (1.0, 1.0)]);

        app.set_color(1.0, 0.0, 0.0, 0.3);
        app.draw_circle(0.0, 0.0, 0.75, 0.25, 10);

        support::Action::Continue
    });
}
<|endoftext|>


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

use std::io::{self, Read};
use std::fs::File;
use std::path::{Path, PathBuf};
use std::ffi::OsStr;
use std::error::Error;

use std::collections::HashSet;
use std::collections::HashMap;

extern crate petgraph;
use petgraph::Graph;
use petgraph::prelude::NodeIndex;

extern crate walkdir;
use walkdir::WalkDir;

#[macro_use]
extern crate lazy_static;
extern crate regex;

use regex::Regex;

mod path_utils;

mod dot_writer;

mod file_node;
use file_node::FileNode;

// -----------------------------------------------------------------------------

#[derive(Debug)]
enum IncludeStatus {
    Relative, // e.g. <vector>
    Absolute, // e.g. /usr/include/c++/4.8/vector
    FailedLookup, // Failed to resolve an absolute file path.
}

#[derive(Debug)]
struct Include {
    path: PathBuf,
    is_system_include: bool,
    status: IncludeStatus,
}

impl Include {
    fn new_relative(name: &str, is_sys: bool) -> Include {
        Include {
            path: PathBuf::from(name),
            is_system_include: is_sys,
            status: IncludeStatus::Relative,
        }
    }

    fn as_absolute(&self, absolute_path: &Path) -> Include {
        Include {
            path: PathBuf::from(absolute_path),
            is_system_include: self.is_system_include,
            status: IncludeStatus::Absolute,
        }
    }

    fn as_failed_lookup(&self) -> Include {
        Include {
            path: PathBuf::from(&self.path),
            is_system_include: self.is_system_include,
            status: IncludeStatus::FailedLookup,
        }
    }
}

// ----------------------------------------------------------------------------

// Convert a relative include path (e.g. <Windows.h>) into an absolute path.
fn find_absolute_include_path(include: &Include,
                              parent_file: &Path,
                              system_search_paths: &[PathBuf])
                              -> Include {

    let local_dir = parent_file.parent().unwrap(); // strip the file name

    let normalized_path = path_utils::normalize_path_separators(&include.path);

    match path_utils::convert_to_absolute_path(&normalized_path, local_dir, system_search_paths) {
        None => {
            println!("In file {:?}", parent_file);
            println!("Unable to locate include {:?}", &include.path);
            println!("");
            include.as_failed_lookup()
        }
        Some(path_buf) => include.as_absolute(path_buf.as_path()),
    }
}

// -----------------------------------------------------------------------------

// Return a list of #include statements found in the file
fn scan_file_for_includes(file: &Path) -> Result<Vec<Include>, io::Error> {
    let mut f = File::open(file)?;
    let mut text = String::new();
    f.read_to_string(&mut text)?;

    let mut includes = Vec::new();

    // Use a regex to search for '#include ...' lines.
    // The second (...) capture group isolates just the text, not the "" or <> symbols.
    lazy_static! {
        static ref RE: Regex = Regex::new(r##"#include ([<|"])(.*)[>|"]"##).unwrap();
    }

    // cap.at(1) is an angle brace or double quote, to determine user or system include.
    // cap.at(2) is the include file name.
    for cap in RE.captures_iter(&text) {
        let inc_symbol = cap.at(1).unwrap_or("<");
        let is_system_include = inc_symbol == "<";
        match cap.at(2) {
            Some(include_name) => {
                includes.push(Include::new_relative(include_name, is_system_include))
            }
            None => {}
        }
    }

    // println!("Found {} includes in {}", includes.len(), &file.display());

    Ok(includes)
}

// -----------------------------------------------------------------------------

// TODO: implement the command line arguments from the original 'cinclude2dot' project.
// --debug       Display various debug info
// --exclude     Specify a regular expression of filenames to ignore
//              For example, ignore your test harnesses.
// --merge       Granularity of the diagram:
//              file - the default, treats each file as separate
//              module - merges .c/.cc/.cpp/.cxx and .h/.hpp/.hxx pairs
//              directory - merges directories into one node
// --groups      Cluster files or modules into directory groups
// --help        Display this help page.
// --include     Followed by a comma separated list of include search paths.
// --paths       Leaves relative paths in displayed filenames.
// --quotetypes  Select for parsing the files included by strip quotes or angle brackets:
//              both - the default, parse all headers.
//              angle - include only "system" headers included by anglebrackets (<>)
//              quote - include only "user" headers included by strip quotes ("")
// --src         Followed by a path to the source code, defaults to current directory

arg_enum! {
    #[derive(Debug)]
    #[allow(non_camel_case_types)]
    enum MergeType {
        file,
        module,
        directory
    }
}

arg_enum! {
    #[derive(Debug)]
    #[allow(non_camel_case_types)]
    enum QuoteTypes {
        both,
        angle,
        quote
    }
}

fn main() {
    // TODO: accept extra include paths.
    let args = App::new("IncludeGraph-rs")
        .version(crate_version!())
        .author(crate_authors!())
        .about("Generates an include graph from a tree of C++ files")
        .arg(Arg::with_name("debug")
            .long("debug")
            .help("Display extra debug info")
            .takes_value(true))
        .arg(Arg::with_name("exclude")
            .long("exclude")
            .help("Specify a regular expression of filenames to ignore. \nRust/RE2 \
                   syntax.\n\tExample: --exclude=\"test_|noisyFile\"")
            .takes_value(true))
        .arg(Arg::with_name("merge")
            .long("merge")
            .help("Granularity of the diagram: \nfile - the default, treats each file as \
                   separate \nmodule - merges .c/.cc/.cpp/.cxx and .h/.hpp/.hxx pairs \
                   \ndirectory - merges directories into one node\n")
            .possible_values(&MergeType::variants())
            .default_value("file")
            .takes_value(true))
        .arg(Arg::with_name("groups")
            .long("groups")
            .help("Cluster files or modules into directory groups")
            .takes_value(true))
        .arg(Arg::with_name("include")
            .long("include")
            .help("Comma separated list of include search paths.")
            .takes_value(true))
        .arg(Arg::with_name("paths")
            .long("paths")
            .help("Leaves relative paths in displayed filenames.")
            .takes_value(true))
        .arg(Arg::with_name("quotetypes")
            .long("quotetypes")
            .help("Select which type of includes to parse:\nboth - the default, parse all \
                   includes. \nangle - parse only \"system\" includes (<>) \nquote - parse only \
                   \"user\" includes (\"\")\n")
            .possible_values(&QuoteTypes::variants())
            .default_value("both")
            .takes_value(true))
        .arg(Arg::with_name("src")
            .help("Path to the source code, defaults to current directory.")
            .required(true)
            .multiple(false)
            .index(1))
        .get_matches();

    let expand_system_includes = false;

    let root_dir_string = match args.value_of("src") {
        Some(path) => path,
        None => panic!("Unable to parse source directory argument."),
    };

    println!("Scanning path: {}", root_dir_string);
    let root_dir = Path::new(root_dir_string);

    if !root_dir.exists() {
        println!("Unable to access directory: {}", root_dir.display());
    }

    // Create a list of default system include paths.
    let mut search_paths = Vec::new();
    if expand_system_includes {
        search_paths.push(PathBuf::from(r"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include"));
    }

    // Restrict the file extensions to search.
    let mut extensions = HashSet::new();
    extensions.insert(OsStr::new("c"));
    extensions.insert(OsStr::new("cc"));
    extensions.insert(OsStr::new("cpp"));
    extensions.insert(OsStr::new("cxx"));
    extensions.insert(OsStr::new("h"));
    extensions.insert(OsStr::new("hpp"));
    extensions.insert(OsStr::new("hxx"));

    // Regular expression of files to exclude. Skip if exclude string is empty.
    let exclude_regex = args.value_of("exclude")
        .and_then(|ref regex_str| {
            Regex::new(regex_str)
                .map_err(|err| panic!("Unable to parse exclude regex: {}", err.description()))
                .ok() // Converts successful result to Some(), discarding errors.
        });

    // Collect all the files to scan in a HashSet
    // Note: is_hidden() is currently hiding paths that start with './', so don't use it yet.
    let input_queue = WalkDir::new(root_dir).into_iter()
        //.filter_entry(|e| !path_utils::is_hidden(e))
        .filter_map(|entry| entry.ok())                     // This discards errors.
        .map(|entry| PathBuf::from(entry.path()))
        .filter(|ref path| path.extension().map_or(false, |ext| extensions.contains(ext)))
        .filter(|ref path| !path_utils::filename_matches_regex(&exclude_regex, &path))
        .collect::<HashSet<_>>();

    // Graph of all the tracked files
    let mut graph = Graph::<FileNode, bool>::new();
    let mut indices = HashMap::<FileNode, NodeIndex>::new();

    for path_buf in input_queue {
        let parent_file = path_buf.as_path();
        let includes_result = scan_file_for_includes(parent_file);
        match includes_result {
            Ok(includes) => {

                // Convert relative includes to absolute includes
                let user_includes: Vec<_> = includes.iter()
                    .filter(|inc| !inc.is_system_include || expand_system_includes)
                    .filter(|inc| !path_utils::name_matches_regex(&exclude_regex, &inc.path))
                    .map(|inc| find_absolute_include_path(inc, parent_file, &search_paths))
                    .collect();

                for inc in user_includes {
                    // Get an existing NodeIndex from the graph, on create a new node.
                    let src_node = FileNode {
                        path: PathBuf::from(&parent_file),
                        is_system: false,
                    };
                    let dst_node = FileNode {
                        path: PathBuf::from(&inc.path),
                        is_system: false,
                    };

                    // These functions should work, but currently create duplicate nodes.
                    let src_node_idx = indices.entry(src_node.clone())
                        .or_insert_with(|| graph.add_node(src_node))
                        .clone();
                    let dst_node_idx = indices.entry(dst_node.clone())
                        .or_insert_with(|| graph.add_node(dst_node))
                        .clone();

                    // println!("Adding edge {:?} -> {:?}", src_node_idx, dst_node_idx);
                    graph.add_edge(src_node_idx, dst_node_idx, true);
                }

                if !expand_system_includes {
                    let system_includes: Vec<_> = includes.iter()
                        .filter(|inc| inc.is_system_include && !expand_system_includes)
                        .filter(|inc| !path_utils::name_matches_regex(&exclude_regex, &inc.path))
                        .collect();

                    for inc in system_includes {

                        // Get an existing NodeIndex from the graph, on create a new node.
                        let src_node = FileNode {
                            path: PathBuf::from(&parent_file),
                            is_system: false,
                        };
                        let dst_node = FileNode {
                            path: PathBuf::from(&inc.path),
                            is_system: true,
                        };

                        // These functions should work, but currently create duplicate nodes.
                        let src_node_idx = indices.entry(src_node.clone())
                            .or_insert_with(|| graph.add_node(src_node))
                            .clone();
                        let dst_node_idx = indices.entry(dst_node.clone())
                            .or_insert_with(|| graph.add_node(dst_node))
                            .clone();

                        // println!("Adding edge {:?} -> {:?}", src_node_idx, dst_node_idx);
                        graph.add_edge(src_node_idx, dst_node_idx, true);
                    }

                }
            }
            Err(err) => {
                println!("Unable to process file {:?}: {}", parent_file, err);
            }
        };
    }

    // Write the graph to a dot file.
    let _ = dot_writer::write_dot_with_header("./graph.dot", &graph);

    println!("Now run \"dot -Tpdf graph.dot > graph.pdf\" to render the graph.");
}
<commit_msg>More code condensing<commit_after>
#[macro_use]
extern crate clap;
use clap::{Arg, App};

use std::io::{self, Read};
use std::fs::File;
use std::path::{Path, PathBuf};
use std::ffi::OsStr;
use std::error::Error;

use std::collections::HashSet;
use std::collections::HashMap;

extern crate petgraph;
use petgraph::Graph;
use petgraph::prelude::NodeIndex;

extern crate walkdir;
use walkdir::WalkDir;

#[macro_use]
extern crate lazy_static;
extern crate regex;

use regex::Regex;

mod path_utils;

mod dot_writer;

mod file_node;
use file_node::FileNode;

// -----------------------------------------------------------------------------

#[derive(Debug)]
enum IncludeStatus {
    Relative, // e.g. <vector>
    Absolute, // e.g. /usr/include/c++/4.8/vector
    FailedLookup, // Failed to resolve an absolute file path.
}

#[derive(Debug)]
struct Include {
    path: PathBuf,
    is_system_include: bool,
    status: IncludeStatus,
}

impl Include {
    fn new_relative(name: &str, is_sys: bool) -> Include {
        Include {
            path: PathBuf::from(name),
            is_system_include: is_sys,
            status: IncludeStatus::Relative,
        }
    }

    fn as_absolute(&self, absolute_path: &Path) -> Include {
        Include {
            path: PathBuf::from(absolute_path),
            is_system_include: self.is_system_include,
            status: IncludeStatus::Absolute,
        }
    }

    fn as_failed_lookup(&self) -> Include {
        Include {
            path: PathBuf::from(&self.path),
            is_system_include: self.is_system_include,
            status: IncludeStatus::FailedLookup,
        }
    }
}

// ----------------------------------------------------------------------------

// Convert a relative include path (e.g. <Windows.h>) into an absolute path.
fn find_absolute_include_path(include: &Include,
                              parent_file: &Path,
                              system_search_paths: &[PathBuf])
                              -> Include {

    let local_dir = parent_file.parent().unwrap(); // strip the file name

    let normalized_path = path_utils::normalize_path_separators(&include.path);

    match path_utils::convert_to_absolute_path(&normalized_path, local_dir, system_search_paths) {
        None => {
            println!("In file {:?}", parent_file);
            println!("Unable to locate include {:?}", &include.path);
            println!("");
            include.as_failed_lookup()
        }
        Some(path_buf) => include.as_absolute(path_buf.as_path()),
    }
}

// -----------------------------------------------------------------------------

// Return a list of #include statements found in the file
fn scan_file_for_includes(file: &Path) -> Result<Vec<Include>, io::Error> {
    let mut f = File::open(file)?;
    let mut text = String::new();
    f.read_to_string(&mut text)?;

    let mut includes = Vec::new();

    // Use a regex to search for '#include ...' lines.
    // The second (...) capture group isolates just the text, not the "" or <> symbols.
    lazy_static! {
        static ref RE: Regex = Regex::new(r##"#include ([<|"])(.*)[>|"]"##).unwrap();
    }

    // cap.at(1) is an angle brace or double quote, to determine user or system include.
    // cap.at(2) is the include file name.
    for cap in RE.captures_iter(&text) {
        let is_system_include = cap.at(1).map_or(false, |sym| sym == "<");

        if let Some(include_name) = cap.at(2) {
            includes.push(Include::new_relative(include_name, is_system_include));
        }
    }

    // println!("Found {} includes in {}", includes.len(), &file.display());

    Ok(includes)
}

// -----------------------------------------------------------------------------

// TODO: implement the command line arguments from the original 'cinclude2dot' project.
// --debug       Display various debug info
// --exclude     Specify a regular expression of filenames to ignore
//              For example, ignore your test harnesses.
// --merge       Granularity of the diagram:
//              file - the default, treats each file as separate
//              module - merges .c/.cc/.cpp/.cxx and .h/.hpp/.hxx pairs
//              directory - merges directories into one node
// --groups      Cluster files or modules into directory groups
// --help        Display this help page.
// --include     Followed by a comma separated list of include search paths.
// --paths       Leaves relative paths in displayed filenames.
// --quotetypes  Select for parsing the files included by strip quotes or angle brackets:
//              both - the default, parse all headers.
//              angle - include only "system" headers included by anglebrackets (<>)
//              quote - include only "user" headers included by strip quotes ("")
// --src         Followed by a path to the source code, defaults to current directory

arg_enum! {
    #[derive(Debug)]
    #[allow(non_camel_case_types)]
    enum MergeType {
        file,
        module,
        directory
    }
}

arg_enum! {
    #[derive(Debug)]
    #[allow(non_camel_case_types)]
    enum QuoteTypes {
        both,
        angle,
        quote
    }
}

fn main() {
    // TODO: accept extra include paths.
    let args = App::new("IncludeGraph-rs")
        .version(crate_version!())
        .author(crate_authors!())
        .about("Generates an include graph from a tree of C++ files")
        .arg(Arg::with_name("debug")
            .long("debug")
            .help("Display extra debug info")
            .takes_value(true))
        .arg(Arg::with_name("exclude")
            .long("exclude")
            .help("Specify a regular expression of filenames to ignore. \nRust/RE2 \
                   syntax.\n\tExample: --exclude=\"test_|noisyFile\"")
            .takes_value(true))
        .arg(Arg::with_name("merge")
            .long("merge")
            .help("Granularity of the diagram: \nfile - the default, treats each file as \
                   separate \nmodule - merges .c/.cc/.cpp/.cxx and .h/.hpp/.hxx pairs \
                   \ndirectory - merges directories into one node\n")
            .possible_values(&MergeType::variants())
            .default_value("file")
            .takes_value(true))
        .arg(Arg::with_name("groups")
            .long("groups")
            .help("Cluster files or modules into directory groups")
            .takes_value(true))
        .arg(Arg::with_name("include")
            .long("include")
            .help("Comma separated list of include search paths.")
            .takes_value(true))
        .arg(Arg::with_name("paths")
            .long("paths")
            .help("Leaves relative paths in displayed filenames.")
            .takes_value(true))
        .arg(Arg::with_name("quotetypes")
            .long("quotetypes")
            .help("Select which type of includes to parse:\nboth - the default, parse all \
                   includes. \nangle - parse only \"system\" includes (<>) \nquote - parse only \
                   \"user\" includes (\"\")\n")
            .possible_values(&QuoteTypes::variants())
            .default_value("both")
            .takes_value(true))
        .arg(Arg::with_name("src")
            .help("Path to the source code, defaults to current directory.")
            .required(true)
            .multiple(false)
            .index(1))
        .get_matches();

    let expand_system_includes = false;

    let root_dir_string = match args.value_of("src") {
        Some(path) => path,
        None => panic!("Unable to parse source directory argument."),
    };

    println!("Scanning path: {}", root_dir_string);
    let root_dir = Path::new(root_dir_string);

    if !root_dir.exists() {
        println!("Unable to access directory: {}", root_dir.display());
    }

    // Create a list of default system include paths.
    let mut search_paths = Vec::new();
    if expand_system_includes {
        search_paths.push(PathBuf::from(r"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include"));
    }

    // Restrict the file extensions to search.
    let mut extensions = HashSet::new();
    extensions.insert(OsStr::new("c"));
    extensions.insert(OsStr::new("cc"));
    extensions.insert(OsStr::new("cpp"));
    extensions.insert(OsStr::new("cxx"));
    extensions.insert(OsStr::new("h"));
    extensions.insert(OsStr::new("hpp"));
    extensions.insert(OsStr::new("hxx"));

    // Regular expression of files to exclude. Skip if exclude string is empty.
    let exclude_regex = args.value_of("exclude")
        .and_then(|ref regex_str| {
            Regex::new(regex_str)
                .map_err(|err| panic!("Unable to parse exclude regex: {}", err.description()))
                .ok() // Converts successful result to Some(), discarding errors.
        });

    // Collect all the files to scan in a HashSet
    // Note: is_hidden() is currently hiding paths that start with './', so don't use it yet.
    let input_queue = WalkDir::new(root_dir).into_iter()
        //.filter_entry(|e| !path_utils::is_hidden(e))
        .filter_map(|entry| entry.ok())                     // This discards errors.
        .map(|entry| PathBuf::from(entry.path()))
        .filter(|ref path| path.extension().map_or(false, |ext| extensions.contains(ext)))
        .filter(|ref path| !path_utils::filename_matches_regex(&exclude_regex, &path))
        .collect::<HashSet<_>>();

    // Graph of all the tracked files
    let mut graph = Graph::<FileNode, bool>::new();
    let mut indices = HashMap::<FileNode, NodeIndex>::new();

    for path_buf in input_queue {
        let parent_file = path_buf.as_path();
        let includes_result = scan_file_for_includes(parent_file);
        match includes_result {
            Ok(includes) => {

                // Convert relative includes to absolute includes
                let user_includes = includes.iter()
                    .filter(|inc| !inc.is_system_include || expand_system_includes)
                    .filter(|inc| !path_utils::name_matches_regex(&exclude_regex, &inc.path))
                    .map(|inc| find_absolute_include_path(inc, parent_file, &search_paths))
                    .collect::<Vec<_>>();

                for inc in user_includes {
                    // Get an existing NodeIndex from the graph, on create a new node.
                    let src_node = FileNode {
                        path: PathBuf::from(&parent_file),
                        is_system: false,
                    };
                    let dst_node = FileNode {
                        path: PathBuf::from(&inc.path),
                        is_system: false,
                    };

                    // These functions should work, but currently create duplicate nodes.
                    let src_node_idx = indices.entry(src_node.clone())
                        .or_insert_with(|| graph.add_node(src_node))
                        .clone();
                    let dst_node_idx = indices.entry(dst_node.clone())
                        .or_insert_with(|| graph.add_node(dst_node))
                        .clone();

                    // println!("Adding edge {:?} -> {:?}", src_node_idx, dst_node_idx);
                    graph.add_edge(src_node_idx, dst_node_idx, true);
                }

                // We still add system includes to the graph, but skip the file lookup.
                if !expand_system_includes {
                    let system_includes: Vec<_> = includes.iter()
                        .filter(|inc| inc.is_system_include && !expand_system_includes)
                        .filter(|inc| !path_utils::name_matches_regex(&exclude_regex, &inc.path))
                        .collect();

                    for inc in system_includes {

                        // Get an existing NodeIndex from the graph, on create a new node.
                        let src_node = FileNode {
                            path: PathBuf::from(&parent_file),
                            is_system: false,
                        };
                        let dst_node = FileNode {
                            path: PathBuf::from(&inc.path),
                            is_system: true,
                        };

                        // These functions should work, but currently create duplicate nodes.
                        let src_node_idx = indices.entry(src_node.clone())
                            .or_insert_with(|| graph.add_node(src_node))
                            .clone();
                        let dst_node_idx = indices.entry(dst_node.clone())
                            .or_insert_with(|| graph.add_node(dst_node))
                            .clone();

                        // println!("Adding edge {:?} -> {:?}", src_node_idx, dst_node_idx);
                        graph.add_edge(src_node_idx, dst_node_idx, true);
                    }

                }
            }
            Err(err) => {
                println!("Unable to process file {:?}: {}", parent_file, err);
            }
        };
    }

    // Write the graph to a dot file.
    let _ = dot_writer::write_dot_with_header("./graph.dot", &graph);

    println!("Now run \"dot -Tpdf graph.dot > graph.pdf\" to render the graph.");
}
<|endoftext|>


<commit_before>mod chip8;

extern crate sdl2;
extern crate time;

use chip8::{GFX_H,GFX_W};
use chip8::emu::Emu;
use chip8::ui::Ui;
use chip8::metro::Metronome;
use sdl2::event::Event;
use sdl2::keycode::KeyCode;
use std::env;
use std::io::Read;
use std::path::Path;
use std::fs::File;
use std::sync::mpsc::{self, Sender, Receiver};
use std::thread;

// Load the emulator with the indicated ROM.
fn load_rom(emu: &mut Emu, path_to_rom: &Path) {
    let mut file = File::open(&path_to_rom).unwrap();
    let mut rom: Vec<u8> = Vec::new();
    file.read_to_end(&mut rom).unwrap();
    emu.load_rom(rom);
}

// Messages that get passed from the ui to the emulator.
enum UiToEmuMsg { Keys([bool; 16]), Paused(bool), Quit, Reset }

// Messages that get passed from the emulator to the ui.
enum EmuToUiMsg { Beeping(bool), Draw([[bool; GFX_H]; GFX_W]), QuitAck }

// Drives user interaction. Responsible for processing keypresses, updating
// the screen and playing audible beeps. Communicates with the emulator by
// exchanging messages across a two way channel.
//
// Runs on the main thread.
// Allows the UI to be agnostic of the emulator (and the channels by
// which we communicate with the emulator).
fn ui_exec(mut ui: Ui, tx: Sender<UiToEmuMsg>, rx: Receiver<EmuToUiMsg>) {
    let mut refresh_gfx_rate = Metronome::new(120);
    let mut paused = false;
    'ui_exec_loop: loop {
        process_key_presses(&mut ui, &tx, &mut paused);
        if process_emu_events(&mut ui, &rx, &paused, &mut refresh_gfx_rate) {
            break 'ui_exec_loop;
        }
        // Short sleep to free up cpu cycles
        thread::sleep_ms(1);
    }
}

// Poll for and handle key press events.
fn process_key_presses(ui: &mut Ui, tx: &Sender<UiToEmuMsg>,
                    paused: &mut bool) {
    match ui.sdl_ctx.event_pump().poll_event() {
        None => {},
        Some(event) => {
            match event {
                Event::Quit{..} => {
                    tx.send(UiToEmuMsg::Paused(*paused)).unwrap();
                },
                Event::KeyDown{keycode,..} => match keycode {
                    KeyCode::Escape => {
                        // Signal emulator with intention to quit
                        // and allow it to shutdown gracefully.
                        tx.send(UiToEmuMsg::Quit).unwrap();
                    },
                    KeyCode::Return => {
                        // Signal emulator to pause.
                        *paused ^= true;
                        tx.send(UiToEmuMsg::Paused(*paused)).unwrap();
                    },
                    KeyCode::Backspace => {
                        // Signal emulator to reset.
                        tx.send(UiToEmuMsg::Reset).unwrap();
                        *paused = false;
                        tx.send(UiToEmuMsg::Paused(*paused)).unwrap();
                    },
                    _ => if !*paused {
                        // A key was pressed, signal emulator with updated
                        // key states.
                        tx.send(UiToEmuMsg::Keys(
                                Ui::get_updated_keys())).unwrap();
                    },
                },
                Event::KeyUp{..} => if !*paused {
                    // A key was released, signal emulator with updated
                    // key states.
                    tx.send(UiToEmuMsg::Keys(
                            Ui::get_updated_keys())).unwrap();
                },
                _ => {}
            }
        }
    }
}

// Poll for and handle emulator events. Returns true if emulator acknowledged
// earlier quit signal.
fn process_emu_events(ui: &mut Ui, rx: &Receiver<EmuToUiMsg>, paused: &bool,
                   refresh_gfx_rate: &mut Metronome) -> bool {
    match rx.try_recv() {
        Ok(emu_event) => {
            match emu_event {
                // Handle beeb state change signalled by emulator.
                EmuToUiMsg::Beeping(on) => ui.beep(on),
                // Handle draw event signalled by emulator.
                EmuToUiMsg::Draw(ref gfx) => {
                    refresh_gfx_rate.on_tick(|| {
                        if !*paused { ui.refresh_gfx(gfx); }
                    });
                },
                // Emulator has acknowledged the earlier quit signal.
                // The ui thread may shutdown in response.
                EmuToUiMsg::QuitAck => return true,
            }
        },
        _ => {},
    }
    false
}

// Drives the emulator. Communicates with the user interface by exchanging
// messages across a two way channel.
//
// Assigned its own thread.
// Allows the emulator to be agnostic of the ui (and the channels by
// which we communicate with the ui).
fn emu_exec(mut emu: Emu, tx: Sender<EmuToUiMsg>, rx: Receiver<UiToEmuMsg>) {
    let mut clock_rate = Metronome::new(500);
    let mut update_timers_rate = Metronome::new(60);
    let mut paused = false;
    let mut beeping = false;
    'emu_exec_loop: loop {
        if process_ui_events(&mut emu, &tx, &rx, &mut paused) {
            break 'emu_exec_loop;
        }
        signal_draw_event(&mut emu, &tx, &paused, &mut clock_rate);
        update_timers(&mut emu, &tx, &paused, &mut beeping,
                      &mut update_timers_rate);
        // Short sleep to free up cpu cycles
        thread::sleep_ms(1);
    }
}

// Poll for and handle UI events. Returns true if Quit signal received from UI.
fn process_ui_events(emu: &mut Emu, tx: &Sender<EmuToUiMsg>,
                     rx: &Receiver<UiToEmuMsg>, paused: &mut bool) -> bool {
    match rx.try_recv() {
        Ok(ui_to_emu_msg) =>
            match ui_to_emu_msg {
                // New key press states.
                UiToEmuMsg::Keys(new_keys) => emu.keys = new_keys,
                // Reset everything.
                UiToEmuMsg::Reset => emu.reset(),
                // Pause or unpause.
                UiToEmuMsg::Paused(p) => *paused = p,
                // Acknowledge quit and shut down gracefully.
                UiToEmuMsg::Quit => {
                    tx.send(EmuToUiMsg::QuitAck).unwrap();
                    return true;
                },
            },
        _ => {},
    }
    false
}

// Signal the ui with a draw event.
fn signal_draw_event(emu: &mut Emu, tx: &Sender<EmuToUiMsg>, paused: &bool,
                     clock_rate: &mut Metronome) {
    clock_rate.on_tick(|| {
        if !paused {
            &mut emu.execute_cycle();
            if emu.draw {
                tx.send(EmuToUiMsg::Draw(emu.gfx)).unwrap();
                emu.draw = false;
            }
         }
    });
}

// Update the emulator timers and signal the ui if the beep state changed.
fn update_timers(emu: &mut Emu, tx: &Sender<EmuToUiMsg>, paused: &bool,
                 beeping: &mut bool, update_timers_rate: &mut Metronome) {
    update_timers_rate.on_tick(|| {
        if !paused {
            emu.update_timers();
            if *beeping != emu.beeping() {
                *beeping ^= true;
                tx.send(EmuToUiMsg::Beeping(*beeping)).unwrap();
            }
        }
    });
}

// Entry point into the program. Takes care of basic setup such as reading
// the rom path from the command line and kicking off the ui and emulator.
fn main() {
    let args: Vec<String> = env::args().collect();
    if args.len() != 2 {
        print!("Usage: chip8 PATH_TO_ROM");
        return;
    }
    let path_to_rom = Path::new(&args[1]);
    let ui = Ui::new();
    let mut emu = Emu::new();
    load_rom(&mut emu, path_to_rom);
    // The channels through which the ui and emulator will communicate.
    let (tx1, rx1) = mpsc::channel::<UiToEmuMsg>();
    let (tx2, rx2) = mpsc::channel::<EmuToUiMsg>();
    // The emulator run in its own thread.
    thread::spawn(move || {
        emu_exec(emu, tx2, rx1);
    });
    // The ui runs on the main thread.
    ui_exec(ui, tx1, rx2);
}
<commit_msg>Non functional changes. Removed redundant comments<commit_after>mod chip8;

extern crate sdl2;
extern crate time;

use chip8::{GFX_H,GFX_W};
use chip8::emu::Emu;
use chip8::ui::Ui;
use chip8::metro::Metronome;
use sdl2::event::Event;
use sdl2::keycode::KeyCode;
use std::env;
use std::io::Read;
use std::path::Path;
use std::fs::File;
use std::sync::mpsc::{self, Sender, Receiver};
use std::thread;

// Load the emulator with the indicated ROM.
fn load_rom(emu: &mut Emu, path_to_rom: &Path) {
    let mut file = File::open(&path_to_rom).unwrap();
    let mut rom: Vec<u8> = Vec::new();
    file.read_to_end(&mut rom).unwrap();
    emu.load_rom(rom);
}

// Messages that get passed from the ui to the emulator.
enum UiToEmuMsg { Keys([bool; 16]), Paused(bool), Quit, Reset }

// Messages that get passed from the emulator to the ui.
enum EmuToUiMsg { Beeping(bool), Draw([[bool; GFX_H]; GFX_W]), QuitAck }

// Drives user interaction. Responsible for processing keypresses, updating
// the screen and playing audible beeps. Communicates with the emulator by
// exchanging messages across a two way channel.
//
// Runs on the main thread.
fn ui_exec(mut ui: Ui, tx: Sender<UiToEmuMsg>, rx: Receiver<EmuToUiMsg>) {
    let mut refresh_gfx_rate = Metronome::new(120);
    let mut paused = false;
    'ui_exec_loop: loop {
        process_key_presses(&mut ui, &tx, &mut paused);
        if process_emu_events(&mut ui, &rx, &paused, &mut refresh_gfx_rate) {
            break 'ui_exec_loop;
        }
        // Short sleep to free up cpu cycles
        thread::sleep_ms(1);
    }
}

// Poll for and handle key press events.
fn process_key_presses(ui: &mut Ui, tx: &Sender<UiToEmuMsg>,
                    paused: &mut bool) {
    match ui.sdl_ctx.event_pump().poll_event() {
        None => {},
        Some(event) => {
            match event {
                Event::Quit{..} => {
                    tx.send(UiToEmuMsg::Paused(*paused)).unwrap();
                },
                Event::KeyDown{keycode,..} => match keycode {
                    KeyCode::Escape => {
                        // Signal emulator with intention to quit
                        // and allow it to shutdown gracefully.
                        tx.send(UiToEmuMsg::Quit).unwrap();
                    },
                    KeyCode::Return => {
                        // Signal emulator to pause.
                        *paused ^= true;
                        tx.send(UiToEmuMsg::Paused(*paused)).unwrap();
                    },
                    KeyCode::Backspace => {
                        // Signal emulator to reset.
                        tx.send(UiToEmuMsg::Reset).unwrap();
                        *paused = false;
                        tx.send(UiToEmuMsg::Paused(*paused)).unwrap();
                    },
                    _ => if !*paused {
                        // A key was pressed, signal emulator with updated
                        // key states.
                        tx.send(UiToEmuMsg::Keys(
                                Ui::get_updated_keys())).unwrap();
                    },
                },
                Event::KeyUp{..} => if !*paused {
                    // A key was released, signal emulator with updated
                    // key states.
                    tx.send(UiToEmuMsg::Keys(
                            Ui::get_updated_keys())).unwrap();
                },
                _ => {}
            }
        }
    }
}

// Poll for and handle emulator events. Returns true if emulator acknowledged
// earlier quit signal.
fn process_emu_events(ui: &mut Ui, rx: &Receiver<EmuToUiMsg>, paused: &bool,
                      refresh_gfx_rate: &mut Metronome) -> bool {
    match rx.try_recv() {
        Ok(emu_event) => {
            match emu_event {
                // Handle beeb state change signalled by emulator.
                EmuToUiMsg::Beeping(on) => ui.beep(on),
                // Handle draw event signalled by emulator.
                EmuToUiMsg::Draw(ref gfx) => {
                    refresh_gfx_rate.on_tick(|| {
                        if !*paused { ui.refresh_gfx(gfx); }
                    });
                },
                // Emulator has acknowledged the earlier quit signal.
                // The ui thread may shutdown in response.
                EmuToUiMsg::QuitAck => return true,
            }
        },
        _ => {},
    }
    false
}

// Drives the emulator. Communicates with the user interface by exchanging
// messages across a two way channel.
//
// Assigned its own thread.
fn emu_exec(mut emu: Emu, tx: Sender<EmuToUiMsg>, rx: Receiver<UiToEmuMsg>) {
    let mut clock_rate = Metronome::new(500);
    let mut update_timers_rate = Metronome::new(60);
    let mut paused = false;
    let mut beeping = false;
    'emu_exec_loop: loop {
        if process_ui_events(&mut emu, &tx, &rx, &mut paused) {
            break 'emu_exec_loop;
        }
        signal_draw_event(&mut emu, &tx, &paused, &mut clock_rate);
        update_timers(&mut emu, &tx, &paused, &mut beeping,
                      &mut update_timers_rate);
        // Short sleep to free up cpu cycles
        thread::sleep_ms(1);
    }
}

// Poll for and handle UI events. Returns true if Quit signal received from UI.
fn process_ui_events(emu: &mut Emu, tx: &Sender<EmuToUiMsg>,
                     rx: &Receiver<UiToEmuMsg>, paused: &mut bool) -> bool {
    match rx.try_recv() {
        Ok(ui_to_emu_msg) =>
            match ui_to_emu_msg {
                // New key press states.
                UiToEmuMsg::Keys(new_keys) => emu.keys = new_keys,
                // Reset everything.
                UiToEmuMsg::Reset => emu.reset(),
                // Pause or unpause.
                UiToEmuMsg::Paused(p) => *paused = p,
                // Acknowledge quit and shut down gracefully.
                UiToEmuMsg::Quit => {
                    tx.send(EmuToUiMsg::QuitAck).unwrap();
                    return true;
                },
            },
        _ => {},
    }
    false
}

// Signal the ui with a draw event.
fn signal_draw_event(emu: &mut Emu, tx: &Sender<EmuToUiMsg>, paused: &bool,
                     clock_rate: &mut Metronome) {
    clock_rate.on_tick(|| {
        if !paused {
            &mut emu.execute_cycle();
            if emu.draw {
                tx.send(EmuToUiMsg::Draw(emu.gfx)).unwrap();
                emu.draw = false;
            }
         }
    });
}

// Update the emulator timers and signal the ui if the beep state changed.
fn update_timers(emu: &mut Emu, tx: &Sender<EmuToUiMsg>, paused: &bool,
                 beeping: &mut bool, update_timers_rate: &mut Metronome) {
    update_timers_rate.on_tick(|| {
        if !paused {
            emu.update_timers();
            if *beeping != emu.beeping() {
                *beeping ^= true;
                tx.send(EmuToUiMsg::Beeping(*beeping)).unwrap();
            }
        }
    });
}

// Entry point into the program. Takes care of basic setup such as reading
// the rom path from the command line and kicking off the ui and emulator.
fn main() {
    let args: Vec<String> = env::args().collect();
    if args.len() != 2 {
        print!("Usage: chip8 PATH_TO_ROM");
        return;
    }
    let path_to_rom = Path::new(&args[1]);
    let ui = Ui::new();
    let mut emu = Emu::new();
    load_rom(&mut emu, path_to_rom);
    // The channels through which the ui and emulator will communicate.
    let (tx1, rx1) = mpsc::channel::<UiToEmuMsg>();
    let (tx2, rx2) = mpsc::channel::<EmuToUiMsg>();
    // The emulator run in its own thread.
    thread::spawn(move || {
        emu_exec(emu, tx2, rx1);
    });
    // The ui runs on the main thread.
    ui_exec(ui, tx1, rx2);
}
<|endoftext|>


<commit_before>/*
 * File   : main.rs
 * Purpose: reimplementation of the classic `ed` in Rust
 * Program: red
 * About  : command-line text editor
 * Authors: Tommy Lincoln <pajamapants3000@gmail.com>
 * License: MIT; See LICENSE!
 * Notes  : Notes on successful compilation
 * Created: 10/16/2016
 * Updated: 10/16/2016
 */

//! A re-implementation of the classic `ed` program in Rust
//!
//! Current functionality will be to simply open the file
//! passed on invocation, and allow the user to execute print
//! commands. These commands will output specified lines from
//! the opened file.

// Bring in to namespace {{{
//extern crate clap;

use std::env;
use std::fs::{File, OpenOptions};
use::std::io::Error;
// }}}

// Define messages
// Some of these may be removed if builtin error descriptions work
const S_FOPEN: &'static str = "successfully opened file!";
//const E_FOPEN: &'static str = "unable to open file";

// Define file-mode flags, to be set by user options and commands
// I would prefer using 2.pow(x) but function calls not allowed in const def
//const F_EXEC: usize = 0b00000001;   // execute
const F_WRIT: u8 = 0b00000010;   // write
const F_READ: u8 = 0b00000100;   // read
const F_APPE: u8 = 0b00001000;   // append
const F_TRUN: u8 = 0b00010000;   // truncate
const F_CREA: u8 = 0b00100000;   // create
const F_CNEW: u8 = 0b01000000;   // create new
//const F_RESE: usize = 0b10000000;   // reserved

fn main() {
    // take as direct arg; will later be arg to flag
//    let file_name: &str = env::args[1].clone();

    // quick'n''dirty - will process one by one later
    let args: Vec<String> = env::args().collect();

    let file_mode: u8 = F_READ;

    let file_opened: File;

    match file_opener( &args[1], file_mode ) {
        Ok(f) => {
            file_opened = f;
            println!( "{}", S_FOPEN );
            println!( "our file is: {:?}", file_opened );
        },
        Err(e) => {
            println!( "error: {}", e );

        },
    };

}

/// Opens file with user-specified name in user-specified mode
///
/// Uses global definitions of mode flags in this file
///
/// Returns direct result of call to OpenOptions::new()
/// Will return an error if the file could not be opened
/// Otherwise, returns a File object
fn file_opener( name: &str, mode: u8 ) -> Result<File, Error> {

    // let's introduce OpenOptions now, though we don't need it
    // until we introduce more functionality
    OpenOptions::new()
        .read(          ( mode | F_READ ) == F_READ )
        .write(         ( mode | F_WRIT ) == F_WRIT )
        .append(        ( mode | F_APPE ) == F_APPE )
        .truncate(      ( mode | F_TRUN ) == F_TRUN )
        .create(        ( mode | F_CREA ) == F_CREA )
        .create_new(    ( mode | F_CNEW ) == F_CNEW )
        .open( name )

}

<commit_msg>Add Command and FileMode structs<commit_after>/*
 * File   : main.rs
 * Purpose: reimplementation of the classic `ed` in Rust
 * Program: red
 * About  : command-line text editor
 * Authors: Tommy Lincoln <pajamapants3000@gmail.com>
 * License: MIT; See LICENSE!
 * Notes  : Notes on successful compilation
 * Created: 10/16/2016
 */

// TODO: Change all instances of `unwrap` to proper error handling

//! A re-implementation of the classic `ed` program in Rust
//!
//! Current functionality will be to simply open the file
//! passed on invocation, and allow the user to execute print
//! commands. These commands will output specified lines from
//! the opened file.

// Bring in to namespace {{{
//extern crate clap;

use std::env;
use std::fs::{File, OpenOptions};
use::std::io::prelude::*;
// Use LineWriter instead of, or in addition to, BufWriter?
use::std::io::{self,BufReader,BufWriter,Error};

// }}}

// *** Constants *** {{{
// Define messages
// Some of these may be removed if builtin error descriptions work
const S_FOPEN: &'static str = "successfully opened file!";
//const E_FOPEN: &'static str = "unable to open file";

// Additional string constants
const LINE_CONT: &'static str = "\\\n";
const PROMPT: &'static str = "%";
const PROMPT_CONT: &'static str = ">";

// ^^^ Constants ^^^ }}}

// *** Data Structures *** {{{
#[derive(Default)]
struct FileMode {
    f_write:        bool,
    f_read:         bool,
    f_append:       bool,
    f_truncate:     bool,
    f_create:       bool,
    f_create_new:   bool,
}

struct Command<'a> {
    address_initial: u32,
    address_final: u32,
    operation: char,
    parameters: &'a str,
}

// ^^^ Data Structures ^^^ }}}

// Main {{{
fn main() {
    // quick'n''dirty - will process one by one later; clap?
    let args: Vec<String> = env::args().collect();

    // take as direct arg; will later be arg to flag
    let file_name: &str = &args[0];
    let file_mode = FileMode { f_read: true, ..Default::default() };
    let file_opened: File;

    match file_opener( file_name, file_mode ) {
        Ok(f) => {
            file_opened = f;
            println!( "{}", S_FOPEN );
            println!( "our file is: {:?}", file_opened );
        },
        Err(e) => {
            println!( "error: {}", e );

        },
    };

    //let mut file_buffer = BufReader::new(file_opened);
    //let mut file_writer = LineWriter::new(file_opened);
    let mut cli_reader = BufReader::new(io::stdin());
    let mut cli_writer = BufWriter::new(io::stdout());
    let mut cmd_input = String::new();
    let mut prompt = PROMPT.to_string();

    cli_writer.write(format!("{}", prompt).as_bytes()).unwrap();
    cli_writer.flush().unwrap();
    // Main interaction loop {{{
    loop {
        cli_reader.read_line(&mut cmd_input).unwrap();

        if cmd_input.ends_with(LINE_CONT) {  // continue
            prompt = PROMPT_CONT.to_string();
        } else {
            {                                            // Execute command {{{
                let command: Command = parse_command( &cmd_input );
                // just some test output
                cli_writer.write(command.parameters.as_bytes()).unwrap();
                cli_writer.write(command.address_initial.to_string()
                                 .as_bytes()).unwrap();
                cli_writer.write(b"\n").unwrap();
                cli_writer.write(command.address_final.to_string()
                                 .as_bytes()).unwrap();
                cli_writer.write(b"\n").unwrap();
                cli_writer.write(command.operation.to_string()
                                 .as_bytes()).unwrap();
                cli_writer.write(b"\n").unwrap();

            }                                           // Done executing }}}
            // ready for a new command
            cmd_input.clear();
            // in case of continuation, return prompt to standard
            prompt = PROMPT.to_string();
        }

        // prompt for the next round
        cli_writer.write(format!("{}", prompt).as_bytes()).unwrap();
        // put it all to the screen
        cli_writer.flush().unwrap();
    }
    //}}}

}
//}}}

/// Opens file with user-specified name and mode {{{
///
/// Uses global definitions of mode flags in this file
///
/// Returns direct result of call to OpenOptions::new()
/// This is of type Result<File, Error>
fn file_opener( name: &str, mode: FileMode ) -> Result<File, Error> {

    // let's introduce OpenOptions now, though we don't need it
    // until we introduce more functionality
    OpenOptions::new()
        .read(mode.f_read)
        .write(mode.f_write)
        .append(mode.f_append)
        .truncate(mode.f_truncate)
        .create(mode.f_create)
        .create_new(mode.f_create_new)
        .open( name )
}
//}}}

/// Parses command-mode input {{{
///
fn parse_command( _cmd_input: &str ) -> Command {
    Command {
        address_initial: 1,
        address_final: 1,
        operation: 'p',
        parameters: _cmd_input,
    }
}
//]]]
<|endoftext|>


<commit_before>//! goto :: Flexible Working Directory Shortcuts
//!
//! Copyright (c) 2016 by William R. Fraser

extern crate docopt;
extern crate rustc_serialize;
extern crate toml;

use std::collections::btree_map::*;
use std::env;
use std::fs::File;
use std::io::{self, Read, Write};
use std::path::{Path, PathBuf};
use docopt::Docopt;

const CONFIG_FILENAME: &'static str = ".goto.toml";

// 79 columns:
// ----------------------------------------------------------------------------
const USAGE: &'static str = r#"
Usage:
    goto [<name>]
    goto (--help | --version)

Configuration is stored in ~/.goto.toml, with the following format:

    name = "/some/path"             # 'goto name' takes you here
    othername = "~/some/other/path" # $HOME expansion will happen

    ["/somewhere/specific"]         # Only in effect when in this location:
    "*" = "default/under/specific"  # With no arguments, this is used.
    "name" = "somewhere/else"       # Overshadows the one above.

Relative paths under a context header are resolved relative to the path in that
header. In the above example, when your current directory is under
/somewhere/specific, running 'goto name' takes you to
/somewhere/specific/somewhere/else.

goto is meant to be used as the argument to your shell's 'eval' builtin, like:
    function goto() {
        eval $(/usr/local/bin/goto $*)  # or wherever the 'goto' binary is
    }
"#;

#[derive(Debug, RustcDecodable)]
struct Args {
    arg_name: Option<String>,
}

fn read_config(config_path: &Path) -> io::Result<toml::Table> {
    let mut config_text = String::new();
    let mut file = try!(File::open(config_path));
    try!(file.read_to_string(&mut config_text));
    let mut parser = toml::Parser::new(&config_text);
    let config = match parser.parse() {
        Some(v) => v,
        None => {
            let mut msg = String::from("failed to parse TOML:");
            for err in &parser.errors {
                let linecol_lo = parser.to_linecol(err.lo);
                let linecol_hi = parser.to_linecol(err.hi);
                msg.push_str(&format!("\n\t{} at {}:{} to {}:{}",
                        err.desc,
                        linecol_lo.0 + 1,
                        linecol_lo.1 + 1,
                        linecol_hi.0 + 1,
                        linecol_hi.1 + 1));
            }
            return Err(io::Error::new(io::ErrorKind::Other, msg));
        }
    };

    Ok(config)
}

type PathMapping = BTreeMap<String, PathBuf>;

#[derive(Debug)]
struct Configuration {
    global: PathMapping,
    contexts: BTreeMap<PathBuf, PathMapping>,
}

fn parse_toml_as_path(t: &toml::Value, cwd: Option<&Path>) -> Result<PathBuf, String> {
    if let &toml::Value::String(ref s) = t {
        let path: PathBuf = if s.starts_with("~/") || s.starts_with("~\\") {
            env::home_dir().unwrap().join(&Path::new(&s[2..]))
        } else if cwd.is_some() {
            // note: this handles absolute paths correctly, i.e. by not using cwd at a all (except
            // for Windows, where the drive letter of cwd is considered.)
            cwd.unwrap().join(Path::new(&s))
        } else {
            return Err(format!("expected an absolute path"));
        };
        Ok(path)
    } else {
        Err(format!("type error: expected a string, not {}", t.type_str()))
    }
}

/// Process the parsed configuration TOML into goto's configuration struct.
/// if `cwd` is specified, all relative paths will be interpreted relative to that path.
fn process_config(config_toml: toml::Table, cwd: Option<&Path>) -> Result<Configuration, String> {
    let mut config = Configuration {
        global: PathMapping::new(),
        contexts: BTreeMap::new(),
    };

    for (k, v) in config_toml.into_iter() {
        if let toml::Value::Table(t) = v {
            // A path context.

            let context_path = match parse_toml_as_path(&toml::Value::String(k), cwd) {
                Ok(path) => path,
                Err(msg) => { return Err(format!("error: {}", msg)); }
            };

            let mut context_map = PathMapping::new();

            for (name, path) in t.into_iter() {
                let mapped_path: PathBuf = match parse_toml_as_path(&path, Some(&context_path)) {
                    Ok(path) => path,
                    Err(msg) => {
                        return Err(format!("error at {:?}.{}: {}", context_path, name, msg));
                    }
                };

                context_map.insert(name, mapped_path);
            }

            config.contexts.insert(context_path, context_map);
        } else {
            // A top-level entry. Attempt to parse as a path and insert into the global table.
            match parse_toml_as_path(&v, cwd) {
                Ok(path) => { config.global.insert(k, path); },
                Err(msg) => {
                    return Err(format!(
                        "error at {}: expected a table or a path string, not {} ({})",
                         k, v.type_str(), msg));
                },
            }
        }
    }

    Ok(config)
}

fn exit(msg: String, fatal: bool) -> ! {
    io::stderr().write_all(msg.as_bytes()).unwrap();
    if !msg.ends_with("\n") {
        io::stderr().write_all(b"\n").unwrap();
    }
    let exit_code = if fatal { 1 } else { 0 };
    ::std::process::exit(exit_code);
}

fn main() {
    let args: Args = Docopt::new(USAGE)
                            .and_then(|d| {
                                d.version(Some(format!("goto {}", env!("CARGO_PKG_VERSION"))))
                                 .decode()
                            })
                            .unwrap_or_else(|e| {
                                exit(format!("{}", e), e.fatal());
                            });

    let name = args.arg_name.unwrap_or("*".to_owned());

    let home = env::home_dir().unwrap_or_else(|| {
        exit(format!("unable to determine home directory"), true);
    });

    let cwd = PathBuf::from(env::current_dir().unwrap_or_else(|e| {
        exit(format!("unable to get current working directory: {}", e), true);
    }));

    let config_toml = read_config(&home.join(Path::new(CONFIG_FILENAME))).map_err(|e| {
        exit(format!("failed to read configuration ~/{}: {}", CONFIG_FILENAME, e), true);
    }).unwrap();

    let config = process_config(config_toml, Some(&home)).map_err(|msg| {
        exit(format!("invalid configuration in ~/{}: {}", CONFIG_FILENAME, msg), true);
    }).unwrap();

    let mut matched = false;
    let mut context_paths_by_len: Vec<&PathBuf> = config.contexts.keys().collect();
    context_paths_by_len.sort_by_key(|p| p.as_os_str().len());
    for context_path in context_paths_by_len.iter().rev() {
        if cwd.starts_with(context_path) {
            let map = config.contexts.get(*context_path).unwrap();
            if let Some(path) = map.get(&name) {
                println!("pushd {:?}", path);
                matched = true;
                break;
            }
        }
    }

    if !matched {
        if let Some(path) = config.global.get(&name) {
            println!("pushd {:?}", path);
            matched = true;
        }
    }

    if !matched {
        exit(format!("not sure where to go"), false);
    }
}
<commit_msg>print the full config path in error messages<commit_after>//! goto :: Flexible Working Directory Shortcuts
//!
//! Copyright (c) 2016 by William R. Fraser

extern crate docopt;
extern crate rustc_serialize;
extern crate toml;

use std::collections::btree_map::*;
use std::env;
use std::fs::File;
use std::io::{self, Read, Write};
use std::path::{Path, PathBuf};
use docopt::Docopt;

const CONFIG_FILENAME: &'static str = ".goto.toml";

// 79 columns:
// ----------------------------------------------------------------------------
const USAGE: &'static str = r#"
Usage:
    goto [<name>]
    goto (--help | --version)

Configuration is stored in ~/.goto.toml, with the following format:

    name = "/some/path"             # 'goto name' takes you here
    othername = "~/some/other/path" # $HOME expansion will happen

    ["/somewhere/specific"]         # Only in effect when in this location:
    "*" = "default/under/specific"  # With no arguments, this is used.
    "name" = "somewhere/else"       # Overshadows the one above.

Relative paths under a context header are resolved relative to the path in that
header. In the above example, when your current directory is under
/somewhere/specific, running 'goto name' takes you to
/somewhere/specific/somewhere/else.

goto is meant to be used as the argument to your shell's 'eval' builtin, like:
    function goto() {
        eval $(/usr/local/bin/goto $*)  # or wherever the 'goto' binary is
    }
"#;

#[derive(Debug, RustcDecodable)]
struct Args {
    arg_name: Option<String>,
}

fn read_config(config_path: &Path) -> io::Result<toml::Table> {
    let mut config_text = String::new();
    let mut file = try!(File::open(config_path));
    try!(file.read_to_string(&mut config_text));
    let mut parser = toml::Parser::new(&config_text);
    let config = match parser.parse() {
        Some(v) => v,
        None => {
            let mut msg = String::from("failed to parse TOML:");
            for err in &parser.errors {
                let linecol_lo = parser.to_linecol(err.lo);
                let linecol_hi = parser.to_linecol(err.hi);
                msg.push_str(&format!("\n\t{} at {}:{} to {}:{}",
                        err.desc,
                        linecol_lo.0 + 1,
                        linecol_lo.1 + 1,
                        linecol_hi.0 + 1,
                        linecol_hi.1 + 1));
            }
            return Err(io::Error::new(io::ErrorKind::Other, msg));
        }
    };

    Ok(config)
}

type PathMapping = BTreeMap<String, PathBuf>;

#[derive(Debug)]
struct Configuration {
    global: PathMapping,
    contexts: BTreeMap<PathBuf, PathMapping>,
}

fn parse_toml_as_path(t: &toml::Value, cwd: Option<&Path>) -> Result<PathBuf, String> {
    if let &toml::Value::String(ref s) = t {
        let path: PathBuf = if s.starts_with("~/") || s.starts_with("~\\") {
            env::home_dir().unwrap().join(&Path::new(&s[2..]))
        } else if cwd.is_some() {
            // note: this handles absolute paths correctly, i.e. by not using cwd at a all (except
            // for Windows, where the drive letter of cwd is considered.)
            cwd.unwrap().join(Path::new(&s))
        } else {
            return Err(format!("expected an absolute path"));
        };
        Ok(path)
    } else {
        Err(format!("type error: expected a string, not {}", t.type_str()))
    }
}

/// Process the parsed configuration TOML into goto's configuration struct.
/// if `cwd` is specified, all relative paths will be interpreted relative to that path.
fn process_config(config_toml: toml::Table, cwd: Option<&Path>) -> Result<Configuration, String> {
    let mut config = Configuration {
        global: PathMapping::new(),
        contexts: BTreeMap::new(),
    };

    for (k, v) in config_toml.into_iter() {
        if let toml::Value::Table(t) = v {
            // A path context.

            let context_path = match parse_toml_as_path(&toml::Value::String(k), cwd) {
                Ok(path) => path,
                Err(msg) => { return Err(format!("error: {}", msg)); }
            };

            let mut context_map = PathMapping::new();

            for (name, path) in t.into_iter() {
                let mapped_path: PathBuf = match parse_toml_as_path(&path, Some(&context_path)) {
                    Ok(path) => path,
                    Err(msg) => {
                        return Err(format!("error at {:?}.{}: {}", context_path, name, msg));
                    }
                };

                context_map.insert(name, mapped_path);
            }

            config.contexts.insert(context_path, context_map);
        } else {
            // A top-level entry. Attempt to parse as a path and insert into the global table.
            match parse_toml_as_path(&v, cwd) {
                Ok(path) => { config.global.insert(k, path); },
                Err(msg) => {
                    return Err(format!(
                        "error at {}: expected a table or a path string, not {} ({})",
                         k, v.type_str(), msg));
                },
            }
        }
    }

    Ok(config)
}

fn exit(msg: String, fatal: bool) -> ! {
    io::stderr().write_all(msg.as_bytes()).unwrap();
    if !msg.ends_with("\n") {
        io::stderr().write_all(b"\n").unwrap();
    }
    let exit_code = if fatal { 1 } else { 0 };
    ::std::process::exit(exit_code);
}

fn main() {
    let args: Args = Docopt::new(USAGE)
                            .and_then(|d| {
                                d.version(Some(format!("goto {}", env!("CARGO_PKG_VERSION"))))
                                 .decode()
                            })
                            .unwrap_or_else(|e| {
                                exit(format!("{}", e), e.fatal());
                            });

    let name = args.arg_name.unwrap_or("*".to_owned());

    let home = env::home_dir().unwrap_or_else(|| {
        exit(format!("unable to determine home directory"), true);
    });
    let config_path = home.join(Path::new(CONFIG_FILENAME));

    let cwd = PathBuf::from(env::current_dir().unwrap_or_else(|e| {
        exit(format!("unable to get current working directory: {}", e), true);
    }));

    let config_toml = read_config(&config_path).map_err(|e| {
        exit(format!("failed to read configuration {:?}: {}", config_path, e), true);
    }).unwrap();

    let config = process_config(config_toml, Some(&home)).map_err(|msg| {
        exit(format!("invalid configuration in {:?}: {}", config_path, msg), true);
    }).unwrap();

    let mut matched = false;
    let mut context_paths_by_len: Vec<&PathBuf> = config.contexts.keys().collect();
    context_paths_by_len.sort_by_key(|p| p.as_os_str().len());
    for context_path in context_paths_by_len.iter().rev() {
        if cwd.starts_with(context_path) {
            let map = config.contexts.get(*context_path).unwrap();
            if let Some(path) = map.get(&name) {
                println!("pushd {:?}", path);
                matched = true;
                break;
            }
        }
    }

    if !matched {
        if let Some(path) = config.global.get(&name) {
            println!("pushd {:?}", path);
            matched = true;
        }
    }

    if !matched {
        exit(format!("not sure where to go"), false);
    }
}
<|endoftext|>