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

#[macro_use]
extern crate clap;
extern crate hyper;
extern crate hyper_rustls;
extern crate pulldown_cmark;
extern crate rayon;
extern crate url;
extern crate walkdir;

mod document;
mod error;

use clap::{Arg, App};
use document::{Document, Error, Event};
use error::{DocumentError, DocumentLocation, LinkError, LocatedDocumentError};
use hyper::client::Client;
use hyper::header::UserAgent;
use hyper::net::HttpsConnector;
use hyper::status::StatusCode;
use hyper_rustls::TlsClient;
use rayon::prelude::*;
use std::collections::HashMap;
use std::ffi::OsStr;
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::process::exit;
use std::sync::Arc;
use url::{ParseError, Url};
use walkdir::WalkDir;

macro_rules! fail {
    ($($print:expr),+) => {
        {
            eprintln!($($print),+);
            exit(1);
        }
    };
}

macro_rules! printerror {
    ($error:expr, $flag:expr) => {
        {
            println!("{}", $error);
            $flag = true;
        }
    };
}

struct LinkContext {
    target: String,
    text: String,
    line: usize,
    file: PathBuf,
}

impl LinkContext {
    fn new_error(self, error: LinkError) -> LocatedDocumentError {
        LocatedDocumentError {
            location: DocumentLocation {
                path: self.file,
                line: self.line,
            },
            error: DocumentError::Link {
                text: self.text,
                target: self.target,
                error: error,
            },
        }
    }
}

fn main() {
    let matches = App::new("Marker")
        .version(crate_version!())
        .arg(
            Arg::with_name("root")
                .short("r")
                .long("root")
                .help("The path to the root of the documentation to be checked")
                .takes_value(true)
                .default_value("."),
        )
        .arg(Arg::with_name("skip-http").long("skip-http").help(
            "Skip validation of HTTP[S] URLs",
        ))
        .get_matches();

    let skip_http = matches.is_present("skip-http");
    let root = Path::new(matches.value_of("root").expect("default root"));
    let mut links = Vec::new();
    let mut found_error = false;

    for entry in WalkDir::new(root).into_iter().filter_map(|entry| {
        let entry = entry.unwrap_or_else(|error| {
            fail!("Failed to walk directory: {}", error);
        });
        if entry.file_type().is_file() && entry.path().extension() == Some(OsStr::new("md")) {
            Some(entry)
        } else {
            None
        }
    })
    {
        let contents = {
            let mut file = File::open(entry.path()).unwrap_or_else(|error| {
                fail!(
                    "Failed to open file ({}): {}",
                    entry.path().display(),
                    error
                );
            });
            let mut text = String::new();
            if let Err(error) = file.read_to_string(&mut text) {
                fail!(
                    "Failed to read file ({}): {}",
                    entry.path().display(),
                    error
                );
            };
            text
        };

        for event in Document::new(&contents) {
            match event.event {
                Event::Link { target, text } => {
                    links.push(LinkContext {
                        target: target,
                        text: text,
                        line: event.line,
                        file: entry.path().to_path_buf(),
                    })
                }
                Event::Error(Error::ReferenceBroken { target, text }) => {
                    printerror!(
                        LinkContext {
                            target: target,
                            text: text,
                            line: event.line,
                            file: entry.path().to_path_buf(),
                        }.new_error(LinkError::ReferenceBroken),
                        found_error
                    )
                }
            }
        }
    }

    let mut urls = HashMap::new();
    for link in links {
        match Url::parse(&link.target) {
            Ok(_) if skip_http => {}
            Ok(mut url) => {
                url.set_fragment(None);
                urls.entry(url).or_insert(Vec::new()).push(link)
            }
            Err(ParseError::RelativeUrlWithoutBase) => {
                if let Err(error) = check_path(&link.target, &link.file) {
                    printerror!(link.new_error(error), found_error)
                }
            }
            Err(error) => printerror!(link.new_error(LinkError::UrlMalformed(error)), found_error),
        }
    }

    for (result, links) in urls.into_par_iter()
        .map(|(url, links)| (check_url(url), links))
        .collect::<Vec<_>>()
    {
        if let Err(error) = result {
            for link in links {
                printerror!(link.new_error(error.clone()), found_error)
            }
        }
    }

    if found_error {
        exit(1)
    }
}

fn check_url(url: Url) -> Result<(), LinkError> {
    if url.scheme() != "http" && url.scheme() != "https" {
        return Ok(());
    }

    let client = Client::with_connector(HttpsConnector::new(TlsClient::new()));
    let agent = UserAgent(format!("marker/{}", crate_version!()));

    let res = client
        .head(url.clone())
        .header(agent.clone())
        .send()
        .and_then(|resp| if resp.status == StatusCode::MethodNotAllowed {
            client.get(url.clone()).header(agent.clone()).send()
        } else {
            Ok(resp)
        });
    match res {
        Ok(resp) => {
            match resp.status {
                StatusCode::Ok => Ok(()),
                status => Err(LinkError::HttpStatus(status)),
            }
        }
        Err(error) => Err(LinkError::HttpError(Arc::new(error))),
    }
}

fn check_path(target: &str, file: &Path) -> Result<(), LinkError> {
    let path = Path::new(OsStr::new(target.split('#').next().expect("string")));
    if path.is_absolute() {
        Err(LinkError::PathAbsolute)
    } else if !file.parent()
               .expect("non-root file path")
               .join(path)
               .exists()
    {
        Err(LinkError::PathNonExistant)
    } else {
        Ok(())
    }
}
<commit_msg>main: add timeout for http requests<commit_after>// Copyright 2016 Alex Crawford
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#[macro_use]
extern crate clap;
extern crate hyper;
extern crate hyper_rustls;
extern crate pulldown_cmark;
extern crate rayon;
extern crate url;
extern crate walkdir;

mod document;
mod error;

use clap::{Arg, App};
use document::{Document, Error, Event};
use error::{DocumentError, DocumentLocation, LinkError, LocatedDocumentError};
use hyper::client::Client;
use hyper::header::UserAgent;
use hyper::net::HttpsConnector;
use hyper::status::StatusCode;
use hyper_rustls::TlsClient;
use rayon::prelude::*;
use std::collections::HashMap;
use std::ffi::OsStr;
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::process::exit;
use std::sync::Arc;
use std::time::Duration;
use url::{ParseError, Url};
use walkdir::WalkDir;

macro_rules! fail {
    ($($print:expr),+) => {
        {
            eprintln!($($print),+);
            exit(1);
        }
    };
}

macro_rules! printerror {
    ($error:expr, $flag:expr) => {
        {
            println!("{}", $error);
            $flag = true;
        }
    };
}

struct LinkContext {
    target: String,
    text: String,
    line: usize,
    file: PathBuf,
}

impl LinkContext {
    fn new_error(self, error: LinkError) -> LocatedDocumentError {
        LocatedDocumentError {
            location: DocumentLocation {
                path: self.file,
                line: self.line,
            },
            error: DocumentError::Link {
                text: self.text,
                target: self.target,
                error: error,
            },
        }
    }
}

fn main() {
    let matches = App::new("Marker")
        .version(crate_version!())
        .arg(
            Arg::with_name("root")
                .short("r")
                .long("root")
                .help("The path to the root of the documentation to be checked")
                .takes_value(true)
                .default_value("."),
        )
        .arg(Arg::with_name("skip-http").long("skip-http").help(
            "Skip validation of HTTP[S] URLs",
        ))
        .get_matches();

    let skip_http = matches.is_present("skip-http");
    let root = Path::new(matches.value_of("root").expect("default root"));
    let mut links = Vec::new();
    let mut found_error = false;

    for entry in WalkDir::new(root).into_iter().filter_map(|entry| {
        let entry = entry.unwrap_or_else(|error| {
            fail!("Failed to walk directory: {}", error);
        });
        if entry.file_type().is_file() && entry.path().extension() == Some(OsStr::new("md")) {
            Some(entry)
        } else {
            None
        }
    })
    {
        let contents = {
            let mut file = File::open(entry.path()).unwrap_or_else(|error| {
                fail!(
                    "Failed to open file ({}): {}",
                    entry.path().display(),
                    error
                );
            });
            let mut text = String::new();
            if let Err(error) = file.read_to_string(&mut text) {
                fail!(
                    "Failed to read file ({}): {}",
                    entry.path().display(),
                    error
                );
            };
            text
        };

        for event in Document::new(&contents) {
            match event.event {
                Event::Link { target, text } => {
                    links.push(LinkContext {
                        target: target,
                        text: text,
                        line: event.line,
                        file: entry.path().to_path_buf(),
                    })
                }
                Event::Error(Error::ReferenceBroken { target, text }) => {
                    printerror!(
                        LinkContext {
                            target: target,
                            text: text,
                            line: event.line,
                            file: entry.path().to_path_buf(),
                        }.new_error(LinkError::ReferenceBroken),
                        found_error
                    )
                }
            }
        }
    }

    let mut urls = HashMap::new();
    for link in links {
        match Url::parse(&link.target) {
            Ok(_) if skip_http => {}
            Ok(mut url) => {
                url.set_fragment(None);
                urls.entry(url).or_insert(Vec::new()).push(link)
            }
            Err(ParseError::RelativeUrlWithoutBase) => {
                if let Err(error) = check_path(&link.target, &link.file) {
                    printerror!(link.new_error(error), found_error)
                }
            }
            Err(error) => printerror!(link.new_error(LinkError::UrlMalformed(error)), found_error),
        }
    }

    for (result, links) in urls.into_par_iter()
        .map(|(url, links)| (check_url(url), links))
        .collect::<Vec<_>>()
    {
        if let Err(error) = result {
            for link in links {
                printerror!(link.new_error(error.clone()), found_error)
            }
        }
    }

    if found_error {
        exit(1)
    }
}

fn check_url(url: Url) -> Result<(), LinkError> {
    if url.scheme() != "http" && url.scheme() != "https" {
        return Ok(());
    }

    println!("Checking {}", url);
    let mut client = Client::with_connector(HttpsConnector::new(TlsClient::new()));
    client.set_read_timeout(Some(Duration::from_secs(10)));
    let agent = UserAgent(format!("marker/{}", crate_version!()));

    let res = client
        .head(url.clone())
        .header(agent.clone())
        .send()
        .and_then(|resp| if resp.status == StatusCode::MethodNotAllowed {
            client.get(url.clone()).header(agent.clone()).send()
        } else {
            Ok(resp)
        });
    match res {
        Ok(resp) => {
            match resp.status {
                StatusCode::Ok => Ok(()),
                status => Err(LinkError::HttpStatus(status)),
            }
        }
        Err(error) => Err(LinkError::HttpError(Arc::new(error))),
    }
}

fn check_path(target: &str, file: &Path) -> Result<(), LinkError> {
    let path = Path::new(OsStr::new(target.split('#').next().expect("string")));
    if path.is_absolute() {
        Err(LinkError::PathAbsolute)
    } else if !file.parent()
               .expect("non-root file path")
               .join(path)
               .exists()
    {
        Err(LinkError::PathNonExistant)
    } else {
        Ok(())
    }
}
<|endoftext|>


<commit_before>extern crate hkg;

extern crate rustbox;

use std::default::Default;

use rustbox::{Color, RustBox};
use rustbox::Key;

fn main(){

    let rustbox = match RustBox::init(Default::default()) {
        Result::Ok(v) => v,
        Result::Err(e) => panic!("{}", e),
    };

    let w = rustbox.width();
    let h = rustbox.height();
    let title = String::from("高登");
    print_header(&rustbox, title, w, h);

    rustbox.print(1, 5, rustbox::RB_BOLD, Color::White, Color::Black, "Press 'q' to quit.");

    loop {
        rustbox.present();
        match rustbox.poll_event(false) {
            Ok(rustbox::Event::KeyEvent(key)) => {
                match key {
                    Key::Char('q') => { break; }
                    _ => { }
                }
            },
            Err(e) => panic!("{}", e),
            _ => { }
        }
    }
}

fn print_header(rustbox: &rustbox::RustBox, text: String, width: usize, height: usize)
{
    let padding = (width - text.len())/2;
    let header_bottom = (0..width).map(|i|"─").collect::<Vec<_>>().join("");
    rustbox.print(padding, 0, rustbox::RB_BOLD, Color::White, Color::Black, &text);
    rustbox.print(0, 1, rustbox::RB_BOLD, Color::Yellow, Color::Black, &header_bottom);
}
<commit_msg>print body<commit_after>extern crate hkg;

extern crate rustbox;

use std::default::Default;

use rustbox::{Color, RustBox};
use rustbox::Key;

fn main(){

    let rustbox = match RustBox::init(Default::default()) {
        Result::Ok(v) => v,
        Result::Err(e) => panic!("{}", e),
    };

    let w = rustbox.width();
    let h = rustbox.height();
    let title = String::from("高登");
    print_header(&rustbox, w, h, title);
    print_body(&rustbox, w, h, 2, 2);
    rustbox.print(1, 23, rustbox::RB_BOLD, Color::White, Color::Black, "Press 'q' to quit.");

    loop {
        rustbox.present();
        match rustbox.poll_event(false) {
            Ok(rustbox::Event::KeyEvent(key)) => {
                match key {
                    Key::Char('q') => { break; }
                    _ => { }
                }
            },
            Err(e) => panic!("{}", e),
            _ => { }
        }
    }
}

fn print_header(rustbox: &rustbox::RustBox, width: usize, height: usize, text: String)
{
    let padding = (width - text.len())/2;
    let header_bottom = (0..width).map(|i|"─").collect::<Vec<_>>().join("");
    rustbox.print(padding, 0, rustbox::RB_BOLD, Color::White, Color::Black, &text);
    rustbox.print(0, 1, rustbox::RB_BOLD, Color::Yellow, Color::Black, &header_bottom);
}

fn print_body(rustbox: &rustbox::RustBox, width: usize, height: usize, offset_x: usize, rows: usize)
{
    let titles = vec!["紅魔英超睇敢帥　十分之高招", "發覺好多後生仔女搭火車地鐵 有位都唔坐"];
    let authors = vec!["電超","程詠樂"];

    for i in (0..rows)
    {
        rustbox.print(0, i  + offset_x, rustbox::RB_BOLD, Color::White, Color::Black, &format!("{no:>2}|{title:<50}|{author}",
                                                                                no = i+1,
                                                                                title = &titles[i],
                                                                                author = &authors[i]));
    }
}
<|endoftext|>


<commit_before>//! This is part 1 of Ruslan Spivak's Let’s Build A Simple Interpreter series.

#[derive(Debug)]
enum Token {
    Integer(i32),
    Plus,
    Minus,
    EOF
}

#[derive(Debug)]
struct Interpreter {
    text: Vec<char>,
    position: usize,
}

impl Interpreter {
    fn new(source: String) -> Interpreter {
        Interpreter {
            text: source.chars().collect(),
            position: 0,
        }
    }

    ///
    /// Advance our position index
    ///
    fn advance(&mut self) {
        self.position += 1;
    }

    ///
    /// Check for the end of out input text
    ///
    fn done(&self) -> bool {
        self.text.len() == 0 || self.position > self.text.len() - 1
    }

    ///
    /// Evaluate expressions of the form:
    ///     Integer Plus Integer
    ///     Integer Minus Integer
    ///
    fn expression(&mut self) -> i32 {
        use ::Token::*;

        let lhs = match self.get_next_token() {
            Integer(i) => i,
            t => panic!("Error: expected an integer and found a '{:?}'", t),
        };
        let op = match self.get_next_token() {
            t @ Plus | t @ Minus => t,
            t => panic!("Error: expected an operator and found a '{:?}'", t),
        };
        let rhs = match self.get_next_token() {
            Integer(i) => i,
            t => panic!("Error: expected an integer and found a '{:?}'", t),
        };
        match op {
            Plus => lhs + rhs,
            Minus => lhs - rhs,
            _ => panic!("Should never reach here."),
        }
    }

    ///
    /// Tokenize the input.
    /// It accepts integers and the '+' and '-' signs.
    ///
    fn get_next_token(&mut self) -> Token {
        self.skip_ws();

        if self.done() {
            return Token::EOF;
        }

        let token = match self.text[self.position] {
            '0' ... '9' => self.get_integer(),
            '+' => Token::Plus,
            '-' => Token::Minus,
            c   => panic!("Error: unexpected character: {}.", c),
        };
        self.advance();
        token
    }

    ///
    /// Create a (potentially) multi-digit integer
    ///
    fn get_integer(&mut self) -> Token {
        let mut number = self.text[self.position].to_string();
        while let Some(c) = self.peek() {
            match c {
                '0' ... '9' => number.push(c),
                _ => break,
            }
            self.advance();
        }
        Token::Integer(number.parse::<i32>().unwrap())
    }

    ///
    /// Return the current character indexed by position. Note:
    /// this returns an option since we might be done with the
    /// source text.
    ///
    fn look(&self) -> Option<char> {
        if !self.done() {
            Some(self.text[self.position])
        } else {
            None
        }
    }

    ///
    /// Look at the next character. Note: this returns an option
    /// since we could potentially look past the end of the source
    /// text.
    ///
    fn peek(&self) -> Option<char> {
        if !self.done() && self.position + 1 < self.text.len() {
            Some(self.text[self.position + 1])
        } else {
            None
        }
    }

    ///
    /// Advance past any whitespace characters. This function does
    /// not use look or peek so that I can avoid unwrapping the options.
    ///
    fn skip_ws(&mut self) {
        while !self.done() && self.text[self.position].is_whitespace() {
            self.advance();
        }
    }
}

macro_rules! prompt(
    ($($arg:tt)*) => { {
        print!($($arg)*);
        stdout().flush().expect("Could not flush stdout");
    } }
);

fn main() {
    use std::io::{BufRead, stdin, stdout, Write};

    prompt!("calc> ");
    let stdin = stdin();
    for line in stdin.lock().lines() {
        let mut interpreter = Interpreter::new(line.unwrap());
        let result = interpreter.expression();
        println!("{}", result);
        prompt!("calc> ");
    }
}
<commit_msg>Finished Part 3.<commit_after>//! This is part 1 of Ruslan Spivak's Let’s Build A Simple Interpreter series.

#[derive(Debug)]
enum Token {
    Integer(i32),
    Plus,
    Minus,
    Star,
    Slash,
    EOF
}

#[derive(Debug)]
struct Interpreter {
    text: Vec<char>,
    position: usize,
}

impl Interpreter {
    fn new(source: String) -> Interpreter {
        Interpreter {
            text: source.chars().collect(),
            position: 0,
        }
    }

    ///
    /// Advance our position index
    ///
    fn advance(&mut self) {
        self.position += 1;
    }

    ///
    /// Check for the end of out input text
    ///
    fn done(&self) -> bool {
        self.text.len() == 0 || self.position > self.text.len() - 1
    }

    ///
    /// Tokenize the input.
    /// It accepts integers and the '+' and '-' signs.
    ///
    fn get_next_token(&mut self) -> Token {
        self.skip_ws();

        if self.done() {
            return Token::EOF;
        }

        let token = match self.text[self.position] {
            '0' ... '9' => self.get_integer(),
            '+' => Token::Plus,
            '-' => Token::Minus,
            c   => panic!("Error: unexpected character: {}.", c),
        };
        self.advance();
        token
    }

    ///
    /// Create a (potentially) multi-digit integer
    ///
    fn get_integer(&mut self) -> Token {
        let mut number = self.text[self.position].to_string();
        while let Some(c) = self.peek() {
            match c {
                '0' ... '9' => number.push(c),
                _ => break,
            }
            self.advance();
        }
        Token::Integer(number.parse::<i32>().unwrap())
    }

    ///
    /// Return the current character indexed by position. Note:
    /// this returns an option since we might be done with the
    /// source text.
    ///
    fn look(&self) -> Option<char> {
        if !self.done() {
            Some(self.text[self.position])
        } else {
            None
        }
    }

    ///
    /// Evaluate expressions of the form:
    ///     Integer Plus Integer
    ///     Integer Minus Integer
    ///
    fn parse_expression(&mut self) -> i32 {
        use ::Token::*;

        let mut result = self.parse_term();
        while !self.done() {
            match self.get_next_token() {
                EOF => break,
                Plus => result += self.parse_term(),
                Minus => result -= self.parse_term(),
                t => panic!("Error: expected an operator and found a '{:?}'", t),
            };
        }
        result

        // let lhs = match self.get_next_token() {
        //     Integer(i) => i,
        //     t => panic!("Error: expected an integer and found a '{:?}'", t),
        // };
        // let op = match self.get_next_token() {
        //     Plus => Plus,
        //     Minus => Minus,
        //     Star => Star,
        //     Slash => Slash,
        //     t => panic!("Error: expected an operator and found a '{:?}'", t),
        // };
        // let rhs = match self.get_next_token() {
        //     Integer(i) => i,
        //     t => panic!("Error: expected an integer and found a '{:?}'", t),
        // };
        // match op {
        //     Plus => lhs + rhs,
        //     Minus => lhs - rhs,
        //     Star => lhs * rhs,
        //     Slash => lhs / rhs,
        //     _ => panic!("Should never reach here."),
        // }
    }

    ///
    /// Parse a term, which can only be an integer so far.
    ///
    fn parse_term(&mut self) -> i32 {
        match self.get_next_token() {
            Token::Integer(i) => i,
            t => panic!("Error: expected an integer and found a '{:?}'", t),
        }
    }

    ///
    /// Look at the next character. Note: this returns an option
    /// since we could potentially look past the end of the source
    /// text.
    ///
    fn peek(&self) -> Option<char> {
        if !self.done() && self.position + 1 < self.text.len() {
            Some(self.text[self.position + 1])
        } else {
            None
        }
    }

    ///
    /// Advance past any whitespace characters. This function does
    /// not use look or peek so that I can avoid unwrapping the options.
    ///
    fn skip_ws(&mut self) {
        while !self.done() && self.text[self.position].is_whitespace() {
            self.advance();
        }
    }
}

macro_rules! prompt(
    ($($arg:tt)*) => { {
        print!($($arg)*);
        stdout().flush().expect("Could not flush stdout");
    } }
);

fn main() {
    use std::io::{BufRead, stdin, stdout, Write};

    prompt!("calc> ");
    let stdin = stdin();
    for line in stdin.lock().lines() {
        let mut interpreter = Interpreter::new(line.unwrap());
        let result = interpreter.parse_expression();
        println!("{}", result);
        prompt!("calc> ");
    }
}
<|endoftext|>


<commit_before>extern crate xcb;
extern crate cairo;
extern crate cairo_sys;
extern crate pango;
extern crate pangocairo;

use std::rc::Rc;

use cairo::{Context, Surface, XCBConnection, XCBDrawable, XCBSurface, XCBVisualType};
use pango::LayoutExt;
use pangocairo::CairoContextExt;
use xcb::ffi::*;

fn get_root_visual_type(conn: &xcb::Connection, screen: &xcb::Screen) -> xcb::Visualtype {
    for root in conn.get_setup().roots() {
        for allowed_depth in root.allowed_depths() {
            for visual in allowed_depth.visuals() {
                if visual.visual_id() == screen.root_visual() {
                    return visual;
                }
            }
        }
    }
    panic!("No visual type found");
}

#[derive(Clone, Debug)]
struct Color {
    red: f64,
    green: f64,
    blue: f64,
}

impl Color {
    fn red() -> Color {
        Color {
            red: 1.0,
            green: 0.0,
            blue: 0.0,
        }
    }

    fn blue() -> Color {
        Color {
            red: 0.0,
            green: 0.0,
            blue: 1.0,
        }
    }

    fn apply_to_context(&self, cr: &Context) {
        cr.set_source_rgb(self.red, self.green, self.blue);
    }
}

#[derive(Clone)]
struct TextAttributes {
    font: pango::FontDescription,
    fg_color: Color,
    bg_color: Option<Color>,
}

struct Text {
    attr: TextAttributes,
    text: String,
}

impl Text {
    fn layout(self, surface: &Surface) -> TextLayout {
        let context = Context::new(&surface);

        let layout = context.create_pango_layout();
        layout.set_text(&self.text, self.text.len() as i32);
        layout.set_font_description(Some(&self.attr.font));

        TextLayout {
            attr: self.attr.clone(),
            context: context,
            layout: layout,
        }
    }
}

struct TextLayout {
    attr: TextAttributes,
    context: Context,
    layout: pango::Layout,
}

impl TextLayout {
    fn width(&self) -> f64 {
        self.layout.get_pixel_size().0 as f64
    }

    fn height(&self) -> f64 {
        self.layout.get_pixel_size().1 as f64
    }

    fn render(&self, x: f64, y: f64) {
        self.context.save();
        self.context.translate(x, y);

        if let Some(ref bg_color) = self.attr.bg_color {
            bg_color.apply_to_context(&self.context);
            // FIXME: The use of `height` isnt' right here: we want to do the
            // full height of the bar, not the full height of the text. It
            // would be useful if we could do Surface.get_height(), but that
            // doesn't seem to be available in cairo-rs for some reason?
            self.context.rectangle(0.0, 0.0, self.width(), self.height());
            self.context.fill();
        }

        self.attr.fg_color.apply_to_context(&self.context);
        self.context.show_pango_layout(&self.layout);

        self.context.restore();
    }
}

// TODO: impl Drop?
struct Window {
    conn: Rc<xcb::Connection>,
    screen_idx: usize,
    id: u32,
    surface: Surface,
}

impl Window {
    fn new(conn: Rc<xcb::Connection>, screen_idx: usize) -> Window {
        let id = conn.generate_id();

        let surface = {
            let screen = conn.get_setup()
                .roots()
                .nth(screen_idx)
                .expect("invalid screen");
            let values = [(xcb::CW_BACK_PIXEL, screen.black_pixel()),
                          (xcb::CW_EVENT_MASK, xcb::EVENT_MASK_EXPOSURE)];

            let (width, height) = (screen.width_in_pixels(), 100);

            xcb::create_window(&conn,
                               xcb::COPY_FROM_PARENT as u8,
                               id,
                               screen.root(),
                               0,
                               0,
                               width,
                               height,
                               10,
                               xcb::WINDOW_CLASS_INPUT_OUTPUT as u16,
                               screen.root_visual(),
                               &values);

            let surface = unsafe {
                let cairo_conn = XCBConnection::from_raw_none(conn.get_raw_conn() as
                                                              *mut cairo_sys::xcb_connection_t);
                let visual =
                    XCBVisualType::from_raw_none(&mut get_root_visual_type(&conn, &screen).base as
                                                 *mut xcb::ffi::xcb_visualtype_t as
                                                 *mut cairo_sys::xcb_visualtype_t);
                let drawable = XCBDrawable(id);
                Surface::create(&cairo_conn, &drawable, &visual, width as i32, height as i32)
                // TODO: Update surface width/height when window size changes.
            };

            surface
        };

        xcb::map_window(&conn, id);
        conn.flush();

        Window {
            conn,
            screen_idx,
            id,
            surface,
        }
    }

    fn screen<'a>(&'a self) -> xcb::Screen<'a> {
        self.conn
            .get_setup()
            .roots()
            .nth(self.screen_idx)
            .expect("Invalid screen")
    }

    fn expose(&self) {
        let font = pango::FontDescription::from_string("Envy Code R 27");

        let attr1 = TextAttributes {
            font: font.clone(),
            fg_color: Color::red(),
            bg_color: None,
        };
        let attr2 = TextAttributes {
            font: font.clone(),
            fg_color: Color::blue(),
            bg_color: Some(Color::red()),
        };
        let texts = vec![
            Text { attr: attr1, text: "Hello, world!".to_owned() },
            Text { attr: attr2, text: "Again".to_owned(), },
        ];
        let layouts: Vec<_> = texts.into_iter().map(|t| t.layout(&self.surface)).collect();
        let mut x = 0.0;
        for layout in layouts {
            layout.render(x, 0.0);
            x += layout.width();
        }

        self.conn.flush();
    }
}

fn main() {
    let (conn, screen_idx) = xcb::Connection::connect_with_xlib_display().unwrap();
    let conn = Rc::new(conn);

    let w = Window::new(conn.clone(), screen_idx as usize);

    loop {
        let event = conn.wait_for_event();
        match event {
            None => {
                break;
            }
            Some(event) => {
                let r = event.response_type() & !0x80;
                match r {
                    xcb::EXPOSE => w.expose(),
                    _ => {}
                }
            }
        }
    }
}
<commit_msg>Support for padding around text.<commit_after>extern crate xcb;
extern crate cairo;
extern crate cairo_sys;
extern crate pango;
extern crate pangocairo;

use std::rc::Rc;

use cairo::{Context, Surface, XCBConnection, XCBDrawable, XCBSurface, XCBVisualType};
use pango::LayoutExt;
use pangocairo::CairoContextExt;
use xcb::ffi::*;

fn get_root_visual_type(conn: &xcb::Connection, screen: &xcb::Screen) -> xcb::Visualtype {
    for root in conn.get_setup().roots() {
        for allowed_depth in root.allowed_depths() {
            for visual in allowed_depth.visuals() {
                if visual.visual_id() == screen.root_visual() {
                    return visual;
                }
            }
        }
    }
    panic!("No visual type found");
}

#[derive(Clone, Debug)]
struct Color {
    red: f64,
    green: f64,
    blue: f64,
}

impl Color {
    fn red() -> Color {
        Color {
            red: 1.0,
            green: 0.0,
            blue: 0.0,
        }
    }

    fn blue() -> Color {
        Color {
            red: 0.0,
            green: 0.0,
            blue: 1.0,
        }
    }

    fn apply_to_context(&self, cr: &Context) {
        cr.set_source_rgb(self.red, self.green, self.blue);
    }
}

#[derive(Clone, Debug)]
struct Padding {
    left: f64,
    right: f64,
    top: f64,
    bottom: f64,
}

impl Padding {
    fn none() -> Padding {
        Padding {
            left: 0.0,
            right: 0.0,
            top: 0.0,
            bottom: 0.0,
        }
    }
    fn new(left: f64, right: f64, top: f64, bottom: f64) -> Padding {
        Padding {
            left,
            right,
            top,
            bottom,
        }
    }

    fn uniform(value: f64) -> Padding {
        Padding {
            left: value,
            right: value,
            top: value,
            bottom: value,
        }
    }
}

#[derive(Clone)]
struct TextAttributes {
    font: pango::FontDescription,
    fg_color: Color,
    bg_color: Option<Color>,
    padding: Padding,
}

struct Text {
    attr: TextAttributes,
    text: String,
}

impl Text {
    fn layout(self, surface: &Surface) -> TextLayout {
        let context = Context::new(&surface);

        let layout = context.create_pango_layout();
        layout.set_text(&self.text, self.text.len() as i32);
        layout.set_font_description(Some(&self.attr.font));

        TextLayout {
            attr: self.attr.clone(),
            context: context,
            layout: layout,
        }
    }
}

struct TextLayout {
    attr: TextAttributes,
    context: Context,
    layout: pango::Layout,
}

impl TextLayout {
    fn width(&self) -> f64 {
        let text_width = self.layout.get_pixel_size().0 as f64;
        text_width + self.attr.padding.left + self.attr.padding.right
    }

    fn height(&self) -> f64 {
        let text_height = self.layout.get_pixel_size().1 as f64;
        text_height + self.attr.padding.top + self.attr.padding.bottom
    }

    fn render(&self, x: f64, y: f64) {
        self.context.save();
        self.context.translate(x, y + self.attr.padding.top);

        if let Some(ref bg_color) = self.attr.bg_color {
            bg_color.apply_to_context(&self.context);
            // FIXME: The use of `height` isnt' right here: we want to do the
            // full height of the bar, not the full height of the text. It
            // would be useful if we could do Surface.get_height(), but that
            // doesn't seem to be available in cairo-rs for some reason?
            self.context
                .rectangle(0.0,
                           0.0,
                           self.width() + self.attr.padding.right,
                           self.height() + self.attr.padding.bottom);
            self.context.fill();
        }

        self.attr.fg_color.apply_to_context(&self.context);
        self.context
            .translate(self.attr.padding.left, self.attr.padding.right);
        self.context.show_pango_layout(&self.layout);

        self.context.restore();
    }
}

// TODO: impl Drop?
struct Window {
    conn: Rc<xcb::Connection>,
    screen_idx: usize,
    id: u32,
    surface: Surface,
}

impl Window {
    fn new(conn: Rc<xcb::Connection>, screen_idx: usize) -> Window {
        let id = conn.generate_id();

        let surface = {
            let screen = conn.get_setup()
                .roots()
                .nth(screen_idx)
                .expect("invalid screen");
            let values = [(xcb::CW_BACK_PIXEL, screen.black_pixel()),
                          (xcb::CW_EVENT_MASK, xcb::EVENT_MASK_EXPOSURE)];

            let (width, height) = (screen.width_in_pixels(), 100);

            xcb::create_window(&conn,
                               xcb::COPY_FROM_PARENT as u8,
                               id,
                               screen.root(),
                               0,
                               0,
                               width,
                               height,
                               10,
                               xcb::WINDOW_CLASS_INPUT_OUTPUT as u16,
                               screen.root_visual(),
                               &values);

            let surface = unsafe {
                let cairo_conn = XCBConnection::from_raw_none(conn.get_raw_conn() as
                                                              *mut cairo_sys::xcb_connection_t);
                let visual =
                    XCBVisualType::from_raw_none(&mut get_root_visual_type(&conn, &screen).base as
                                                 *mut xcb::ffi::xcb_visualtype_t as
                                                 *mut cairo_sys::xcb_visualtype_t);
                let drawable = XCBDrawable(id);
                Surface::create(&cairo_conn, &drawable, &visual, width as i32, height as i32)
                // TODO: Update surface width/height when window size changes.
            };

            surface
        };

        xcb::map_window(&conn, id);
        conn.flush();

        Window {
            conn,
            screen_idx,
            id,
            surface,
        }
    }

    fn screen<'a>(&'a self) -> xcb::Screen<'a> {
        self.conn
            .get_setup()
            .roots()
            .nth(self.screen_idx)
            .expect("Invalid screen")
    }

    fn expose(&self) {
        let font = pango::FontDescription::from_string("Envy Code R 27");

        let attr1 = TextAttributes {
            font: font.clone(),
            fg_color: Color::red(),
            bg_color: None,
            padding: Padding::uniform(5.0),
        };
        let attr2 = TextAttributes {
            font: font.clone(),
            fg_color: Color::blue(),
            bg_color: Some(Color::red()),
            padding: Padding::uniform(5.0),
        };
        let texts = vec![Text {
                             attr: attr1,
                             text: "Hello, world!".to_owned(),
                         },
                         Text {
                             attr: attr2,
                             text: "Again".to_owned(),
                         }];
        let layouts: Vec<_> = texts.into_iter().map(|t| t.layout(&self.surface)).collect();
        let mut x = 0.0;
        for layout in layouts {
            layout.render(x, 0.0);
            x += layout.width();
        }

        self.conn.flush();
    }
}

fn main() {
    let (conn, screen_idx) = xcb::Connection::connect_with_xlib_display().unwrap();
    let conn = Rc::new(conn);

    let w = Window::new(conn.clone(), screen_idx as usize);

    loop {
        let event = conn.wait_for_event();
        match event {
            None => {
                break;
            }
            Some(event) => {
                let r = event.response_type() & !0x80;
                match r {
                    xcb::EXPOSE => w.expose(),
                    _ => {}
                }
            }
        }
    }
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate serde_derive;
extern crate toml;
extern crate getopts;
use std::env;
use std::fs;
use std::io;
use std::io::prelude::*;
use std::io::Write;
use std::path;
use std::process;
use getopts::Options;
use getopts::ParsingStyle;

const DEFAULT_CONF_PATH: &str = "~/.config/openby/config";

#[derive(Debug)]
enum AppError {
    Io(io::Error),
    Toml(toml::de::Error),
}

#[allow(dead_code)]
#[derive(Deserialize)]
struct Config {
    version: f64,
    tools: Vec<Tools>,
}

#[allow(dead_code)]
#[derive(Deserialize)]
struct Tools {
    command: String,
    extentions: Vec<String>,
}

fn main() {
    let exit_code = match run() {
        Ok(_) => 0,
        Err(e) => {
            writeln!(&mut io::stderr(), "{:?}", e).unwrap();
            1
        }
    };
    process::exit(exit_code);
}

fn run() -> Result<(), AppError> {
    let args: Vec<String> = env::args().collect();
    let args_str: Vec<&str> = args.iter().map(|s| s.as_str()).collect();
    let (file_name, conf_name) = parse_options(args_str)?;
    println!("Ok: {} {}", file_name, conf_name);

    let conf = load_config(&conf_name)?;

    open_by(&conf, &file_name)?;

    Ok(())
}

fn parse_options(args: Vec<&str>) -> Result<(String, String), AppError> {
    let ref program = &args[0];

    let mut opts = Options::new();
    opts.parsing_style(ParsingStyle::FloatingFrees);
    opts.optopt("c", "config", "specify configure file name", "CONFIG");
    opts.optflag("h", "help", "print this help menu");

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

    if matches.opt_present("h") {
        print_usage(&program, &opts);
        process::exit(0);
    }

    let file_name = if !matches.free.is_empty() {
        matches.free[0].clone()
    } else {
        print_usage(&program, &opts);
        return Result::Err(AppError::Io(
            io::Error::new(io::ErrorKind::NotFound, "FILE is not found"),
        ));
    };

    let conf_name = if let Some(c) = matches.opt_str("c") {
        c
    } else {
        DEFAULT_CONF_PATH.to_string()
    };

    Ok((file_name, conf_name))
}

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

fn load_config(conf_name: &str) -> Result<Config, AppError> {
    let path = path::Path::new(conf_name);
    if path.exists() == false {
        return Result::Err(AppError::Io(io::Error::new(
            io::ErrorKind::NotFound,
            format!("{} is not exist", path.to_str().unwrap_or("")),
        )));
    }

    let mut reader = io::BufReader::new(fs::File::open(path).map_err(AppError::Io)?);
    let mut s = String::new();
    reader.read_to_string(&mut s).map_err(AppError::Io)?;

    let conf: Config = toml::from_str(s.as_str()).map_err(AppError::Toml)?;

    Ok(conf)
}

fn open_by(conf: &Config, file_name: &str) -> Result<(), AppError> {
    let file_path = path::Path::new(file_name);
    if file_path.exists() == false {
        return Result::Err(AppError::Io(io::Error::new(
            io::ErrorKind::NotFound,
            format!("{} is not exist", file_path.to_str().unwrap_or("")),
        )));
    }

    let ext = file_path.extension().ok_or(AppError::Io(io::Error::new(
        io::ErrorKind::NotFound,
        format!(
            "{} has not extension",
            file_path.to_str().unwrap_or("")
        ),
    )))?;

    match get_commnad_from_extension(conf, ext.to_str().unwrap()) {
        Some(cmdline) => {
            let cmd: Vec<&str> = cmdline.split_whitespace().collect();
            println!("exec {:?} {:?}", cmd, file_path);

            if cmd.len() > 1 {
                process::Command::new(cmd[0])
                    .args(cmd[1..].into_iter())
                    .arg(file_path)
                    .status()
                    .expect("failed to run");
            } else {
                process::Command::new(cmd[0])
                    .arg(file_path)
                    .status()
                    .expect("failed to run");
            }
        }
        None => {
            println!("command = None");
            // TODO: register command about this extension.
        }
    }

    Ok(())
}

fn get_commnad_from_extension(conf: &Config, extension: &str) -> Option<String> {
    let ext_string = extension.to_string();
    for t in conf.tools.iter() {
        if t.extentions.contains(&ext_string) {
            return Some(t.command.clone());
        }
    }
    None
}

#[test]
fn test_parse_options() {
    let no_arguments = vec!["openby", "input.file", "-c", "configure.file"];

    assert!(parse_options(no_arguments[0..1].to_vec()).is_err());
    assert_eq!(
        ("input.file".to_string(), DEFAULT_CONF_PATH.to_string()),
        parse_options(no_arguments[0..2].to_vec()).unwrap_or(("".to_string(), "".to_string()))
    );
    assert_eq!(
        ("input.file".to_string(), "configure.file".to_string()),
        parse_options(no_arguments).unwrap_or(("".to_string(), "".to_string()))
    );
}

#[test]
fn test_decode_toml() {
    let toml_config_str = r#"
    version = 0.0

    [[tools]]
    command = "apvlv"
    extentions = [ "pdf" ]

    [[tools]]
    command = "mirage"
    extentions = [ "jpg", "png", "gif" ]

    [[tools]]
    command = "vlc"
    extentions = [ "mp4", "mov", "avi" ]

    [[tools]]
    command = "vim -R"
    extentions = [ "conf" ]
    "#;

    let config: Config = toml::from_str(toml_config_str).unwrap();
    assert_eq!(config.version, 0.0);

    assert_eq!(config.tools[0].command, "apvlv".to_string());
    assert_eq!(config.tools[0].extentions[0], "pdf".to_string());

    assert_eq!(config.tools[1].command, "mirage".to_string());
    assert_eq!(config.tools[1].extentions[0], "jpg".to_string());
    assert_eq!(config.tools[1].extentions[1], "png".to_string());
    assert_eq!(config.tools[1].extentions[2], "gif".to_string());

    assert_eq!(config.tools[2].command, "vlc".to_string());
    assert_eq!(config.tools[2].extentions[0], "mp4".to_string());
    assert_eq!(config.tools[2].extentions[1], "mov".to_string());
    assert_eq!(config.tools[2].extentions[2], "avi".to_string());

    assert_eq!(config.tools[3].command, "vim -R".to_string());
    assert_eq!(config.tools[3].extentions[0], "conf".to_string());
}
<commit_msg>refactor the vector handling<commit_after>#[macro_use]
extern crate serde_derive;
extern crate toml;
extern crate getopts;
use std::env;
use std::fs;
use std::io;
use std::io::prelude::*;
use std::io::Write;
use std::path;
use std::process;
use getopts::Options;
use getopts::ParsingStyle;

const DEFAULT_CONF_PATH: &str = "~/.config/openby/config";

#[derive(Debug)]
enum AppError {
    Io(io::Error),
    Toml(toml::de::Error),
}

#[allow(dead_code)]
#[derive(Deserialize)]
struct Config {
    version: f64,
    tools: Vec<Tools>,
}

#[allow(dead_code)]
#[derive(Deserialize)]
struct Tools {
    command: String,
    extentions: Vec<String>,
}

fn main() {
    let exit_code = match run() {
        Ok(_) => 0,
        Err(e) => {
            writeln!(&mut io::stderr(), "{:?}", e).unwrap();
            1
        }
    };
    process::exit(exit_code);
}

fn run() -> Result<(), AppError> {
    let args: Vec<String> = env::args().collect();
    let args_str: Vec<&str> = args.iter().map(|s| s.as_str()).collect();
    let (file_name, conf_name) = parse_options(args_str)?;
    println!("Ok: {} {}", file_name, conf_name);

    let conf = load_config(&conf_name)?;

    open_by(&conf, &file_name)?;

    Ok(())
}

fn parse_options(args: Vec<&str>) -> Result<(String, String), AppError> {
    let ref program = &args[0];

    let mut opts = Options::new();
    opts.parsing_style(ParsingStyle::FloatingFrees);
    opts.optopt("c", "config", "specify configure file name", "CONFIG");
    opts.optflag("h", "help", "print this help menu");

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

    if matches.opt_present("h") {
        print_usage(&program, &opts);
        process::exit(0);
    }

    let file_name = if !matches.free.is_empty() {
        matches.free[0].clone()
    } else {
        print_usage(&program, &opts);
        return Result::Err(AppError::Io(
            io::Error::new(io::ErrorKind::NotFound, "FILE is not found"),
        ));
    };

    let conf_name = if let Some(c) = matches.opt_str("c") {
        c
    } else {
        DEFAULT_CONF_PATH.to_string()
    };

    Ok((file_name, conf_name))
}

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

fn load_config(conf_name: &str) -> Result<Config, AppError> {
    let path = path::Path::new(conf_name);
    if path.exists() == false {
        return Result::Err(AppError::Io(io::Error::new(
            io::ErrorKind::NotFound,
            format!("{} is not exist", path.to_str().unwrap_or("")),
        )));
    }

    let mut reader = io::BufReader::new(fs::File::open(path).map_err(AppError::Io)?);
    let mut s = String::new();
    reader.read_to_string(&mut s).map_err(AppError::Io)?;

    let conf: Config = toml::from_str(s.as_str()).map_err(AppError::Toml)?;

    Ok(conf)
}

fn open_by(conf: &Config, file_name: &str) -> Result<(), AppError> {
    let file_path = path::Path::new(file_name);
    if file_path.exists() == false {
        return Result::Err(AppError::Io(io::Error::new(
            io::ErrorKind::NotFound,
            format!("{} is not exist", file_path.to_str().unwrap_or("")),
        )));
    }

    let ext = file_path.extension().ok_or(AppError::Io(io::Error::new(
        io::ErrorKind::NotFound,
        format!(
            "{} has not extension",
            file_path.to_str().unwrap_or("")
        ),
    )))?;

    match get_commnad_from_extension(conf, ext.to_str().unwrap()) {
        Some(cmdline) => {
            let cmds: Vec<&str> = cmdline.split_whitespace().collect();
            let (cmd, option) = cmds.split_first().unwrap();
            println!("exec {:?} {:?} {:?}", cmd, option, file_path);

            process::Command::new(cmd)
                .args(option.into_iter())
                .arg(file_path)
                .status()
                .expect("failed to run");
        }
        None => {
            println!("command = None");
            // TODO: register command about this extension.
        }
    }

    Ok(())
}

fn get_commnad_from_extension(conf: &Config, extension: &str) -> Option<String> {
    let ext_string = extension.to_string();
    for t in conf.tools.iter() {
        if t.extentions.contains(&ext_string) {
            return Some(t.command.clone());
        }
    }
    None
}

#[test]
fn test_parse_options() {
    let no_arguments = vec!["openby", "input.file", "-c", "configure.file"];

    assert!(parse_options(no_arguments[0..1].to_vec()).is_err());
    assert_eq!(
        ("input.file".to_string(), DEFAULT_CONF_PATH.to_string()),
        parse_options(no_arguments[0..2].to_vec()).unwrap_or(("".to_string(), "".to_string()))
    );
    assert_eq!(
        ("input.file".to_string(), "configure.file".to_string()),
        parse_options(no_arguments).unwrap_or(("".to_string(), "".to_string()))
    );
}

#[test]
fn test_decode_toml() {
    let toml_config_str = r#"
    version = 0.0

    [[tools]]
    command = "apvlv"
    extentions = [ "pdf" ]

    [[tools]]
    command = "mirage"
    extentions = [ "jpg", "png", "gif" ]

    [[tools]]
    command = "vlc"
    extentions = [ "mp4", "mov", "avi" ]

    [[tools]]
    command = "vim -R"
    extentions = [ "conf" ]
    "#;

    let config: Config = toml::from_str(toml_config_str).unwrap();
    assert_eq!(config.version, 0.0);

    assert_eq!(config.tools[0].command, "apvlv".to_string());
    assert_eq!(config.tools[0].extentions[0], "pdf".to_string());

    assert_eq!(config.tools[1].command, "mirage".to_string());
    assert_eq!(config.tools[1].extentions[0], "jpg".to_string());
    assert_eq!(config.tools[1].extentions[1], "png".to_string());
    assert_eq!(config.tools[1].extentions[2], "gif".to_string());

    assert_eq!(config.tools[2].command, "vlc".to_string());
    assert_eq!(config.tools[2].extentions[0], "mp4".to_string());
    assert_eq!(config.tools[2].extentions[1], "mov".to_string());
    assert_eq!(config.tools[2].extentions[2], "avi".to_string());

    assert_eq!(config.tools[3].command, "vim -R".to_string());
    assert_eq!(config.tools[3].extentions[0], "conf".to_string());
}
<|endoftext|>


<commit_before>extern crate rustc_serialize;
extern crate semver;
extern crate docopt;
extern crate cargo;
#[macro_use]
extern crate log;
extern crate env_logger;
extern crate tempdir;
extern crate walker;
extern crate yaml_rust as yaml;

mod crates;
mod docs;

use std::path::PathBuf;
use std::error::Error;
use std::io::{self, Read};
use std::fs::{File, create_dir_all};
use docopt::Docopt;
use yaml::{YamlLoader, Yaml};
use semver::VersionReq;
use crates::Cargo;
use docs::{Docgen, PackageRequest, PackageSource};

#[derive(Clone, RustcDecodable, Debug)]
struct Args {
    flag_out: String,
    flag_ver: String,
    flag_url: String,
    flag_features: String,
    flag_no_default_features: bool,
    flag_no_deps: bool,
    arg_path: String,
    arg_package: String,
    cmd_doc: bool,
    cmd_konpaku: bool,
}

const USAGE: &'static str = "
Usage:
    youmu doc [options] <package>
    youmu konpaku [options] <path>
    youmu --help

Generates rust documentation.

Options:
    -h, --help              Show this message.
    -o PATH, --out PATH     The directory to store the output in [default: ./docs].
    --ver VERSION           Package version requirement [default: *].
    --url URL               URL for package repository.
    --no-default-features   Don't set default package features.
    --features FEATURES     Space-separated list of features to build with.
    --no-deps               Inhibit documentation of dependencies.
";

fn main() {
    if let Err(err) = env_logger::init() {
        println!("unable to log: {:?}", err);
    }

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

    main_impl(args).unwrap();
}

fn main_impl(args: Args) -> io::Result<()> {
    match args {
        Args { cmd_doc: true, arg_package, flag_out, flag_ver, flag_url, flag_no_default_features, flag_no_deps, flag_features, .. } => {
            let request = PackageRequest {
                name: arg_package.into(),
                source: if !flag_url.is_empty() {
                    PackageSource::Url(flag_url)
                } else {
                    PackageSource::CratesIo(try!(VersionReq::parse(&flag_ver[..]).map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))))
                },
                features: flag_features.split(' ').map(Into::into).collect(),
                default_features: !flag_no_default_features,
                include_deps: !flag_no_deps,
            };
            try!(create_dir_all(&flag_out));
            try!(Cargo::new().document(&request, flag_out).map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.description())));
        },
        Args { cmd_konpaku: true, arg_path, flag_out, .. } => {
            fn invalid_input(msg: &'static str) -> io::Error {
                io::Error::new(io::ErrorKind::InvalidInput, msg)
            }

            let mut gen = Cargo::new();

            let mut data = String::new();
            let mut f = try!(File::open(arg_path));
            try!(f.read_to_string(&mut data));
            let data = try!(YamlLoader::load_from_str(&data[..]).map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e)));
            let data = try!(data.get(0).and_then(Yaml::as_vec).ok_or_else(|| invalid_input("expected root array")));
            for entry in data {
                let entry = try!(entry.as_hash().ok_or_else(|| invalid_input("expected hash")));
                let request = PackageRequest {
                    name: try!(entry.get(&Yaml::String("package".into())).and_then(Yaml::as_str).ok_or_else(|| invalid_input("expected package name"))).into(),
                    source: if let Some(url) = entry.get(&Yaml::String("url".into())).and_then(Yaml::as_str) {
                        PackageSource::Url(url.into())
                    } else if let Some(version) = entry.get(&Yaml::String("version".into())).and_then(Yaml::as_str) {
                        PackageSource::CratesIo(try!(VersionReq::parse(version).map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))))
                    } else {
                        PackageSource::CratesIo(VersionReq::any())
                    },
                    features: entry.get(&Yaml::String("features".into())).and_then(Yaml::as_vec).map(|v| v.into_iter().filter_map(Yaml::as_str).map(Into::into).collect()).unwrap_or(Vec::new()),
                    default_features: entry.get(&Yaml::String("default-features".into())).and_then(Yaml::as_bool).unwrap_or(true),
                    include_deps: entry.get(&Yaml::String("include-deps".into())).and_then(Yaml::as_bool).unwrap_or(true),
                };
                let mut out = PathBuf::from(&flag_out);
                out.push(&request.name);
                try!(gen.document(&request, out).map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.description())));
            }
        },
        _ => docopt::Error::WithProgramUsage(Box::new(docopt::Error::Help), USAGE.into()).exit(),
    }

    Ok(())
}
<commit_msg>Change default include-deps to false<commit_after>extern crate rustc_serialize;
extern crate semver;
extern crate docopt;
extern crate cargo;
#[macro_use]
extern crate log;
extern crate env_logger;
extern crate tempdir;
extern crate walker;
extern crate yaml_rust as yaml;

mod crates;
mod docs;

use std::path::PathBuf;
use std::error::Error;
use std::io::{self, Read};
use std::fs::{File, create_dir_all};
use docopt::Docopt;
use yaml::{YamlLoader, Yaml};
use semver::VersionReq;
use crates::Cargo;
use docs::{Docgen, PackageRequest, PackageSource};

#[derive(Clone, RustcDecodable, Debug)]
struct Args {
    flag_out: String,
    flag_ver: String,
    flag_url: String,
    flag_features: String,
    flag_no_default_features: bool,
    flag_no_deps: bool,
    arg_path: String,
    arg_package: String,
    cmd_doc: bool,
    cmd_konpaku: bool,
}

const USAGE: &'static str = "
Usage:
    youmu doc [options] <package>
    youmu konpaku [options] <path>
    youmu --help

Generates rust documentation.

Options:
    -h, --help              Show this message.
    -o PATH, --out PATH     The directory to store the output in [default: ./docs].
    --ver VERSION           Package version requirement [default: *].
    --url URL               URL for package repository.
    --no-default-features   Don't set default package features.
    --features FEATURES     Space-separated list of features to build with.
    --no-deps               Inhibit documentation of dependencies.
";

fn main() {
    if let Err(err) = env_logger::init() {
        println!("unable to log: {:?}", err);
    }

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

    main_impl(args).unwrap();
}

fn main_impl(args: Args) -> io::Result<()> {
    match args {
        Args { cmd_doc: true, arg_package, flag_out, flag_ver, flag_url, flag_no_default_features, flag_no_deps, flag_features, .. } => {
            let request = PackageRequest {
                name: arg_package.into(),
                source: if !flag_url.is_empty() {
                    PackageSource::Url(flag_url)
                } else {
                    PackageSource::CratesIo(try!(VersionReq::parse(&flag_ver[..]).map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))))
                },
                features: flag_features.split(' ').map(Into::into).collect(),
                default_features: !flag_no_default_features,
                include_deps: !flag_no_deps,
            };
            try!(create_dir_all(&flag_out));
            try!(Cargo::new().document(&request, flag_out).map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.description())));
        },
        Args { cmd_konpaku: true, arg_path, flag_out, .. } => {
            fn invalid_input(msg: &'static str) -> io::Error {
                io::Error::new(io::ErrorKind::InvalidInput, msg)
            }

            let mut gen = Cargo::new();

            let mut data = String::new();
            let mut f = try!(File::open(arg_path));
            try!(f.read_to_string(&mut data));
            let data = try!(YamlLoader::load_from_str(&data[..]).map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e)));
            let data = try!(data.get(0).and_then(Yaml::as_vec).ok_or_else(|| invalid_input("expected root array")));
            for entry in data {
                let entry = try!(entry.as_hash().ok_or_else(|| invalid_input("expected hash")));
                let request = PackageRequest {
                    name: try!(entry.get(&Yaml::String("package".into())).and_then(Yaml::as_str).ok_or_else(|| invalid_input("expected package name"))).into(),
                    source: if let Some(url) = entry.get(&Yaml::String("url".into())).and_then(Yaml::as_str) {
                        PackageSource::Url(url.into())
                    } else if let Some(version) = entry.get(&Yaml::String("version".into())).and_then(Yaml::as_str) {
                        PackageSource::CratesIo(try!(VersionReq::parse(version).map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))))
                    } else {
                        PackageSource::CratesIo(VersionReq::any())
                    },
                    features: entry.get(&Yaml::String("features".into())).and_then(Yaml::as_vec).map(|v| v.into_iter().filter_map(Yaml::as_str).map(Into::into).collect()).unwrap_or(Vec::new()),
                    default_features: entry.get(&Yaml::String("default-features".into())).and_then(Yaml::as_bool).unwrap_or(true),
                    include_deps: entry.get(&Yaml::String("include-deps".into())).and_then(Yaml::as_bool).unwrap_or(false),
                };
                let mut out = PathBuf::from(&flag_out);
                out.push(&request.name);
                try!(gen.document(&request, out).map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.description())));
            }
        },
        _ => docopt::Error::WithProgramUsage(Box::new(docopt::Error::Help), USAGE.into()).exit(),
    }

    Ok(())
}
<|endoftext|>


<commit_before>
extern crate clap;
use clap::{Arg, App};

use std::ops::Index;
use std::fmt;
use std::io::{self, Read, Write};
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::visit::EdgeRef;
use petgraph::prelude::NodeIndex;
use petgraph::dot::{Dot, Config};

extern crate walkdir;
use walkdir::{WalkDir, WalkDirIterator};

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

use regex::Regex;

mod path_utils;

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

#[derive(Debug,Clone,PartialEq,Eq,Hash)]
struct FileNode {
    path: PathBuf, // Can use is_absolute() and is_relative() to check status.
    is_system: bool,
}

impl fmt::Display for FileNode {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:?}", self.path.as_path()
            .file_name()
            .unwrap_or(OsStr::new("[error getting filename]")))
    }
}

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

#[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,
                              //local_search_path: &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!("with normalized path {:?}", normalized_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)
}

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

fn main() {
    // TODO: accept extra include paths.
    let args = App::new("IncludeGraph-rs")
        .version("0.1.0")
        .about("Generates an include graph from a tree of C++ files")
//        .arg(Arg::with_name("output_format")
//            .help("The output format to produce: PDF, DOT, PNG")
//            .value_name("OUTPUT")
//            .short("o"))
        .arg(Arg::with_name("source")
            .help("The source directory to scan")
            .value_name("PATH")
            .required(true)
            .multiple(false)
            .index(1))
        .get_matches();

    let expand_system_includes = false;

    let root_dir_string = match args.value_of("source") {
        Some(path) => path,
        None => panic!("Unable to parse 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("h"));
    extensions.insert(OsStr::new("cpp"));
    extensions.insert(OsStr::new("hpp"));

    let mut input_queue: HashSet<PathBuf> = HashSet::new();

    // Collect all the files to scan
    let walker = WalkDir::new(root_dir).into_iter();
    // Note: is_hidden() is currently hiding paths that start with './', and should be fixed.
    //for entry in walker.filter_entry(|e| !path_utils::is_hidden(e)) {
    for entry in walker {
        match entry {
            Ok(entry) => {
                let path_buf = PathBuf::from(entry.path());

                // If the file extension matches our set, queue full path for processing.
                let has_matching_ext = path_buf.extension().map_or(false, |ext| extensions.contains(ext));
                if has_matching_ext {
                    //println!("Found file {}", &path_buf.display());
                    input_queue.insert(path_buf);
                }
            },
            Err(what) => {
                println!("Error reading file: {}", what.description());
            },
        }
    }

    // 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) => {

                //println!("Processing file {}", parent_file.display());

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

                //println!("Found {} absolute includes", absolute_includes.len());

                for inc in absolute_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)
                        .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);
            }
        };
    }

    // Print all the includes
//    for node_idx in graph.node_indices() {
//        for edge in graph.edges(node_idx) {
//            let src_str = graph.index(edge.source());
//            let dst_str = graph.index(edge.target());
//            println!("{:?} -> {:?}", src_str, dst_str);
//        }
//    }

    // Write output to file graph.dot
    let out_path = Path::new("./graph.dot");
    let out_path_display = out_path.display();

    let mut dotfile = match File::create(&out_path) {
        Ok(file) => file,
        Err(why) => panic!("couldn't create {}: {}", out_path_display, why.description()),
    };

    writeln!(&mut dotfile,
             "{}",
             Dot::with_config(&graph, &[Config::EdgeNoLabel]));

    println!("Wrote graph to {}", out_path_display);
    println!("Run 'dot -Tpdf graph.dot > graph.pdf' to create a graph.");
}
<commit_msg>Added note about needing exclusion filters<commit_after>
extern crate clap;
use clap::{Arg, App};

use std::ops::Index;
use std::fmt;
use std::io::{self, Read, Write};
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::visit::EdgeRef;
use petgraph::prelude::NodeIndex;
use petgraph::dot::{Dot, Config};

extern crate walkdir;
use walkdir::{WalkDir, WalkDirIterator};

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

use regex::Regex;

mod path_utils;

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

#[derive(Debug,Clone,PartialEq,Eq,Hash)]
struct FileNode {
    path: PathBuf, // Can use is_absolute() and is_relative() to check status.
    is_system: bool,
}

impl fmt::Display for FileNode {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:?}", self.path.as_path()
            .file_name()
            .unwrap_or(OsStr::new("[error getting filename]")))
    }
}

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

#[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,
                              //local_search_path: &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!("with normalized path {:?}", normalized_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)
}

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

fn main() {
    // TODO: accept extra include paths.
    // TODO: accept regex exclusion filters (e.g. stdafx.h)
    let args = App::new("IncludeGraph-rs")
        .version("0.1.0")
        .about("Generates an include graph from a tree of C++ files")
//        .arg(Arg::with_name("output_format")
//            .help("The output format to produce: PDF, DOT, PNG")
//            .value_name("OUTPUT")
//            .short("o"))
        .arg(Arg::with_name("source")
            .help("The source directory to scan")
            .value_name("PATH")
            .required(true)
            .multiple(false)
            .index(1))
        .get_matches();

    let expand_system_includes = false;

    let root_dir_string = match args.value_of("source") {
        Some(path) => path,
        None => panic!("Unable to parse 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("h"));
    extensions.insert(OsStr::new("cpp"));
    extensions.insert(OsStr::new("hpp"));

    let mut input_queue: HashSet<PathBuf> = HashSet::new();

    // Collect all the files to scan
    let walker = WalkDir::new(root_dir).into_iter();
    // Note: is_hidden() is currently hiding paths that start with './', and should be fixed.
    //for entry in walker.filter_entry(|e| !path_utils::is_hidden(e)) {
    for entry in walker {
        match entry {
            Ok(entry) => {
                let path_buf = PathBuf::from(entry.path());

                // If the file extension matches our set, queue full path for processing.
                let has_matching_ext = path_buf.extension().map_or(false, |ext| extensions.contains(ext));
                if has_matching_ext {
                    //println!("Found file {}", &path_buf.display());
                    input_queue.insert(path_buf);
                }
            },
            Err(what) => {
                println!("Error reading file: {}", what.description());
            },
        }
    }

    // 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) => {

                //println!("Processing file {}", parent_file.display());

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

                //println!("Found {} absolute includes", absolute_includes.len());

                for inc in absolute_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)
                        .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);
            }
        };
    }

    // Print all the includes
//    for node_idx in graph.node_indices() {
//        for edge in graph.edges(node_idx) {
//            let src_str = graph.index(edge.source());
//            let dst_str = graph.index(edge.target());
//            println!("{:?} -> {:?}", src_str, dst_str);
//        }
//    }

    // Write output to file graph.dot
    let out_path = Path::new("./graph.dot");
    let out_path_display = out_path.display();

    let mut dotfile = match File::create(&out_path) {
        Ok(file) => file,
        Err(why) => panic!("couldn't create {}: {}", out_path_display, why.description()),
    };

    writeln!(&mut dotfile,
             "{}",
             Dot::with_config(&graph, &[Config::EdgeNoLabel]));

    println!("Wrote graph to {}", out_path_display);
    println!("Run 'dot -Tpdf graph.dot > graph.pdf' to create a graph.");
}
<|endoftext|>


<commit_before>// Copyright 2020 Google LLC
//
// Use of this source code is governed by an MIT-style license that can be found
// in the LICENSE file or at https://opensource.org/licenses/MIT.

mod action;
mod message;
mod metadata;
mod opts;
mod session;

use std::fs::File;
use std::io::Result;

use log::error;
use opts::{Opts};

fn main() -> Result<()> {
    let opts = opts::from_args();
    init(&opts);

    fleetspeak::startup(env!("CARGO_PKG_VERSION"))?;

    match action::startup::handle(&mut session::Adhoc, ()) {
        Err(error) => {
            error!("failed to collect startup information: {}", error);
        }
        Ok(()) => (),
    }

    loop {
        if let Some(message) = message::collect(&opts) {
            session::handle(message);
        }
    }
}

fn init(opts: &Opts) {
    init_log(opts);
}

fn init_log(opts: &Opts) {
    let level = opts.log_verbosity.level();

    let mut loggers = Vec::<Box<dyn simplelog::SharedLogger>>::new();

    if let Some(std) = &opts.log_std {
        let config = Default::default();
        let logger = simplelog::TermLogger::new(level, config, std.mode())
            .expect("failed to create a terminal logger");

        loggers.push(logger);
    }

    if let Some(path) = &opts.log_file {
        let file = File::create(path)
            .expect("failed to create the log file");

        let config = Default::default();
        let logger = simplelog::WriteLogger::new(level, config, file);

        loggers.push(logger);
    }

    simplelog::CombinedLogger::init(loggers)
        .expect("failed to init logging");
}
<commit_msg>Add logging for successful startup.<commit_after>// Copyright 2020 Google LLC
//
// Use of this source code is governed by an MIT-style license that can be found
// in the LICENSE file or at https://opensource.org/licenses/MIT.

mod action;
mod message;
mod metadata;
mod opts;
mod session;

use std::fs::File;
use std::io::Result;

use log::{error, info};
use opts::{Opts};

fn main() -> Result<()> {
    let opts = opts::from_args();
    init(&opts);

    fleetspeak::startup(env!("CARGO_PKG_VERSION"))?;

    match action::startup::handle(&mut session::Adhoc, ()) {
        Err(error) => {
            error!("failed to collect startup information: {}", error);
        }
        Ok(()) => {
            info!("successfully sent startup information");
        }
    }

    loop {
        if let Some(message) = message::collect(&opts) {
            session::handle(message);
        }
    }
}

fn init(opts: &Opts) {
    init_log(opts);
}

fn init_log(opts: &Opts) {
    let level = opts.log_verbosity.level();

    let mut loggers = Vec::<Box<dyn simplelog::SharedLogger>>::new();

    if let Some(std) = &opts.log_std {
        let config = Default::default();
        let logger = simplelog::TermLogger::new(level, config, std.mode())
            .expect("failed to create a terminal logger");

        loggers.push(logger);
    }

    if let Some(path) = &opts.log_file {
        let file = File::create(path)
            .expect("failed to create the log file");

        let config = Default::default();
        let logger = simplelog::WriteLogger::new(level, config, file);

        loggers.push(logger);
    }

    simplelog::CombinedLogger::init(loggers)
        .expect("failed to init logging");
}
<|endoftext|>