<commit_before>#![feature(globs)]
#![feature(if_let)]

extern crate neovim;
extern crate rgtk;
extern crate serialize;

//use neovim::*;
use rgtk::*;
use std::collections::HashSet;

mod projects;
mod ui;
mod utils;

fn main() {
    gtk::init();

    // constants

    let width = 1242;
    let height = 768;
    let editor_height = ((height as f32) * 0.75) as i32;

    // create the window

    let mut window = gtk::Window::new(gtk::WindowType::TopLevel).unwrap();
    window.set_title("SolidOak");
    window.set_window_position(gtk::WindowPosition::Center);
    window.set_default_size(width, height);

    window.connect(gtk::signals::DeleteEvent::new(|_| {
        gtk::main_quit();
        true
    }));

    // create the panes

    let new_button = gtk::Button::new_with_label("New Project").unwrap();
    let import_button = gtk::Button::new_with_label("Import").unwrap();
    let rename_button = gtk::Button::new_with_label("Rename").unwrap();
    let remove_button = gtk::Button::new_with_label("Remove").unwrap();

    let mut proj_btns = gtk::Box::new(gtk::Orientation::Horizontal, 0).unwrap();
    proj_btns.set_size_request(-1, -1);
    proj_btns.add(&new_button);
    proj_btns.add(&import_button);
    proj_btns.add(&rename_button);
    proj_btns.add(&remove_button);

    let mut proj_tree = gtk::TreeView::new().unwrap();
    let selection = proj_tree.get_selection().unwrap();
    let column_types = [glib::ffi::g_type_string, glib::ffi::g_type_string];
    let store = gtk::TreeStore::new(&column_types).unwrap();
    let model = store.get_model().unwrap();
    proj_tree.set_model(&model);
    proj_tree.set_headers_visible(false);

    let mut scroll_pane = gtk::ScrolledWindow::new(None, None).unwrap();
    scroll_pane.add(&proj_tree);

    let column = gtk::TreeViewColumn::new().unwrap();
    let cell = gtk::CellRendererText::new().unwrap();
    column.pack_start(&cell, true);
    column.add_attribute(&cell, "text", 0);
    proj_tree.append_column(&column);

    let mut proj_pane = gtk::Box::new(gtk::Orientation::Vertical, 0).unwrap();
    proj_pane.set_size_request(-1, -1);
    proj_pane.pack_start(&proj_btns, false, true, 0);
    proj_pane.pack_start(&scroll_pane, true, true, 0);

    let editor_pane = gtk::VteTerminal::new().unwrap();
    editor_pane.set_size_request(-1, editor_height);

    let run_button = gtk::Button::new_with_label("Run").unwrap();
    let build_button = gtk::Button::new_with_label("Build").unwrap();
    let test_button = gtk::Button::new_with_label("Test").unwrap();
    let clean_button = gtk::Button::new_with_label("Clean").unwrap();
    let stop_button = gtk::Button::new_with_label("Stop").unwrap();

    let mut build_btns = gtk::Box::new(gtk::Orientation::Horizontal, 0).unwrap();
    build_btns.set_size_request(-1, -1);
    build_btns.add(&run_button);
    build_btns.add(&build_button);
    build_btns.add(&test_button);
    build_btns.add(&clean_button);
    build_btns.add(&stop_button);

    let build_term = gtk::VteTerminal::new().unwrap();

    let mut build_pane = gtk::Box::new(gtk::Orientation::Vertical, 0).unwrap();
    build_pane.pack_start(&build_btns, false, true, 0);
    build_pane.pack_start(&build_term, true, true, 0);

    let mut content = gtk::Box::new(gtk::Orientation::Vertical, 0).unwrap();
    content.pack_start(&editor_pane, false, true, 0);
    content.pack_start(&build_pane, true, true, 0);

    let mut hbox = gtk::Box::new(gtk::Orientation::Horizontal, 0).unwrap();
    hbox.pack_start(&proj_pane, false, true, 0);
    hbox.pack_start(&content, true, true, 0);
    window.add(&hbox);

    // populate the project tree

    let mut state = ::utils::State{
        projects: HashSet::new(),
        expansions: HashSet::new(),
        selection: None,
        tree_model: &model,
        tree_store: &store,
        tree_selection: &selection,
        rename_button: &rename_button,
        remove_button: &remove_button,
    };

    ::utils::create_data_dir();
    ::utils::read_prefs(&mut state);
    ::ui::update_project_tree(&mut state, &mut proj_tree);

    // connect to the signals

    new_button.connect(gtk::signals::Clicked::new(|| {
        ::projects::new_project(&mut state, &mut proj_tree);
    }));
    import_button.connect(gtk::signals::Clicked::new(|| {
        ::projects::import_project(&mut state, &mut proj_tree);
    }));
    rename_button.connect(gtk::signals::Clicked::new(|| {
        ::projects::rename_file(&mut state);
    }));
    remove_button.connect(gtk::signals::Clicked::new(|| {
        ::projects::remove_item(&mut state);
    }));
    selection.connect(gtk::signals::Changed::new(|| {
        ::projects::update_selection(&mut state);
    }));
    proj_tree.connect(gtk::signals::RowCollapsed::new(|iter_raw, _| {
        let iter = gtk::TreeIter::wrap_pointer(iter_raw);
        ::projects::remove_expansion(&mut state, &iter);
    }));
    proj_tree.connect(gtk::signals::RowExpanded::new(|iter_raw, _| {
        let iter = gtk::TreeIter::wrap_pointer(iter_raw);
        ::projects::add_expansion(&mut state, &iter);
    }));

    // show the window

    window.show_all();
    gtk::main();
}
<commit_msg>Fork Vim and display it in the editor pane<commit_after>#![feature(globs)]
#![feature(if_let)]

extern crate libc;
extern crate neovim;
extern crate rgtk;
extern crate serialize;

use libc::{c_int};
use neovim::*;
use rgtk::*;
use std::collections::HashSet;

mod projects;
mod ui;
mod utils;

extern "C" {
    fn fork () -> c_int;
}

fn start_gui(pty: &mut gtk::VtePty, pid: i32) {
    gtk::init();

    // constants

    let width = 1242;
    let height = 768;
    let editor_height = ((height as f32) * 0.75) as i32;

    // create the window

    let mut window = gtk::Window::new(gtk::WindowType::TopLevel).unwrap();
    window.set_title("SolidOak");
    window.set_window_position(gtk::WindowPosition::Center);
    window.set_default_size(width, height);

    window.connect(gtk::signals::DeleteEvent::new(|_| {
        gtk::main_quit();
        true
    }));

    // create the panes

    let new_button = gtk::Button::new_with_label("New Project").unwrap();
    let import_button = gtk::Button::new_with_label("Import").unwrap();
    let rename_button = gtk::Button::new_with_label("Rename").unwrap();
    let remove_button = gtk::Button::new_with_label("Remove").unwrap();

    let mut proj_btns = gtk::Box::new(gtk::Orientation::Horizontal, 0).unwrap();
    proj_btns.set_size_request(-1, -1);
    proj_btns.add(&new_button);
    proj_btns.add(&import_button);
    proj_btns.add(&rename_button);
    proj_btns.add(&remove_button);

    let mut proj_tree = gtk::TreeView::new().unwrap();
    let selection = proj_tree.get_selection().unwrap();
    let column_types = [glib::ffi::g_type_string, glib::ffi::g_type_string];
    let store = gtk::TreeStore::new(&column_types).unwrap();
    let model = store.get_model().unwrap();
    proj_tree.set_model(&model);
    proj_tree.set_headers_visible(false);

    let mut scroll_pane = gtk::ScrolledWindow::new(None, None).unwrap();
    scroll_pane.add(&proj_tree);

    let column = gtk::TreeViewColumn::new().unwrap();
    let cell = gtk::CellRendererText::new().unwrap();
    column.pack_start(&cell, true);
    column.add_attribute(&cell, "text", 0);
    proj_tree.append_column(&column);

    let mut proj_pane = gtk::Box::new(gtk::Orientation::Vertical, 0).unwrap();
    proj_pane.set_size_request(-1, -1);
    proj_pane.pack_start(&proj_btns, false, true, 0);
    proj_pane.pack_start(&scroll_pane, true, true, 0);

    let mut editor_pane = gtk::VteTerminal::new().unwrap();
    editor_pane.set_size_request(-1, editor_height);
    editor_pane.set_pty(pty);
    editor_pane.watch_child(pid);

    let run_button = gtk::Button::new_with_label("Run").unwrap();
    let build_button = gtk::Button::new_with_label("Build").unwrap();
    let test_button = gtk::Button::new_with_label("Test").unwrap();
    let clean_button = gtk::Button::new_with_label("Clean").unwrap();
    let stop_button = gtk::Button::new_with_label("Stop").unwrap();

    let mut build_btns = gtk::Box::new(gtk::Orientation::Horizontal, 0).unwrap();
    build_btns.set_size_request(-1, -1);
    build_btns.add(&run_button);
    build_btns.add(&build_button);
    build_btns.add(&test_button);
    build_btns.add(&clean_button);
    build_btns.add(&stop_button);

    let build_term = gtk::VteTerminal::new().unwrap();

    let mut build_pane = gtk::Box::new(gtk::Orientation::Vertical, 0).unwrap();
    build_pane.pack_start(&build_btns, false, true, 0);
    build_pane.pack_start(&build_term, true, true, 0);

    let mut content = gtk::Box::new(gtk::Orientation::Vertical, 0).unwrap();
    content.pack_start(&editor_pane, false, true, 0);
    content.pack_start(&build_pane, true, true, 0);

    let mut hbox = gtk::Box::new(gtk::Orientation::Horizontal, 0).unwrap();
    hbox.pack_start(&proj_pane, false, true, 0);
    hbox.pack_start(&content, true, true, 0);
    window.add(&hbox);

    // populate the project tree

    let mut state = ::utils::State{
        projects: HashSet::new(),
        expansions: HashSet::new(),
        selection: None,
        tree_model: &model,
        tree_store: &store,
        tree_selection: &selection,
        rename_button: &rename_button,
        remove_button: &remove_button,
    };

    ::utils::create_data_dir();
    ::utils::read_prefs(&mut state);
    ::ui::update_project_tree(&mut state, &mut proj_tree);

    // connect to the signals

    new_button.connect(gtk::signals::Clicked::new(|| {
        ::projects::new_project(&mut state, &mut proj_tree);
    }));
    import_button.connect(gtk::signals::Clicked::new(|| {
        ::projects::import_project(&mut state, &mut proj_tree);
    }));
    rename_button.connect(gtk::signals::Clicked::new(|| {
        ::projects::rename_file(&mut state);
    }));
    remove_button.connect(gtk::signals::Clicked::new(|| {
        ::projects::remove_item(&mut state);
    }));
    selection.connect(gtk::signals::Changed::new(|| {
        ::projects::update_selection(&mut state);
    }));
    proj_tree.connect(gtk::signals::RowCollapsed::new(|iter_raw, _| {
        let iter = gtk::TreeIter::wrap_pointer(iter_raw);
        ::projects::remove_expansion(&mut state, &iter);
    }));
    proj_tree.connect(gtk::signals::RowExpanded::new(|iter_raw, _| {
        let iter = gtk::TreeIter::wrap_pointer(iter_raw);
        ::projects::add_expansion(&mut state, &iter);
    }));

    // show the window

    window.show_all();
    gtk::main();
}

fn main() {
    let mut pty = gtk::VtePty::new().unwrap();
    let pid = unsafe { fork() };

    if pid > 0 {
        start_gui(&mut pty, pid);
    } else {
        pty.child_setup();
        nvim_main();
    }
}
<|endoftext|>


<commit_before>extern crate libc;
extern crate neovim;
extern crate rgtk;
extern crate "rustc-serialize" as rustc_serialize;

use libc::c_int;
use rgtk::*;
use std::collections::HashSet;
use std::env;
use std::io::Write;
use std::fs;
use std::fs::PathExt;
use std::old_io::timer;
use std::path::Path;
use std::time::duration::Duration;

mod ffi;
mod projects;
mod ui;
mod utils;

fn gui_main(pty: &mut gtk::VtePty, read_fd: c_int, write_fd: c_int, pid: c_int) {
    gtk::init();

    // constants

    let width = 1242;
    let height = 768;
    let editor_height = ((height as f32) * 0.75) as i32;
    let mut quit_app = false;

    // create the window

    let title = format!("SolidOak {}.{}.{}",
                        option_env!("CARGO_PKG_VERSION_MAJOR").unwrap(),
                        option_env!("CARGO_PKG_VERSION_MINOR").unwrap(),
                        option_env!("CARGO_PKG_VERSION_PATCH").unwrap());
    let mut window = gtk::Window::new(gtk::WindowType::TopLevel).unwrap();
    window.set_title(title.as_slice());
    window.set_window_position(gtk::WindowPosition::Center);
    window.set_default_size(width, height);

    window.connect(gtk::signals::DeleteEvent::new(&mut |_| {
        ffi::send_message(write_fd, "qall!");
        ffi::close_fd(read_fd);
        ffi::close_fd(write_fd);
        quit_app = true;
        true
    }));

    // create the panes

    let new_button = gtk::Button::new_with_label("New Project").unwrap();
    let import_button = gtk::Button::new_with_label("Import").unwrap();
    let rename_button = gtk::Button::new_with_label("Rename").unwrap();
    let remove_button = gtk::Button::new_with_label("Remove").unwrap();

    let mut project_buttons = gtk::Box::new(gtk::Orientation::Horizontal, 0).unwrap();
    project_buttons.set_size_request(-1, -1);
    project_buttons.add(&new_button);
    project_buttons.add(&import_button);
    project_buttons.add(&rename_button);
    project_buttons.add(&remove_button);

    let mut project_tree = gtk::TreeView::new().unwrap();
    let selection = project_tree.get_selection().unwrap();
    let column_types = [glib::ffi::g_type_string, glib::ffi::g_type_string];
    let store = gtk::TreeStore::new(&column_types).unwrap();
    let model = store.get_model().unwrap();
    project_tree.set_model(&model);
    project_tree.set_headers_visible(false);

    let mut scroll_pane = gtk::ScrolledWindow::new(None, None).unwrap();
    scroll_pane.add(&project_tree);

    let column = gtk::TreeViewColumn::new().unwrap();
    let cell = gtk::CellRendererText::new().unwrap();
    column.pack_start(&cell, true);
    column.add_attribute(&cell, "text", 0);
    project_tree.append_column(&column);

    let mut project_pane = gtk::Box::new(gtk::Orientation::Vertical, 0).unwrap();
    project_pane.set_size_request(-1, -1);
    project_pane.pack_start(&project_buttons, false, true, 0);
    project_pane.pack_start(&scroll_pane, true, true, 0);

    let mut editor_pane = gtk::VteTerminal::new().unwrap();
    editor_pane.set_size_request(-1, editor_height);
    editor_pane.set_pty(pty);
    editor_pane.watch_child(pid);

    let run_button = gtk::Button::new_with_label("Run").unwrap();
    let build_button = gtk::Button::new_with_label("Build").unwrap();
    let test_button = gtk::Button::new_with_label("Test").unwrap();
    let clean_button = gtk::Button::new_with_label("Clean").unwrap();
    let stop_button = gtk::Button::new_with_label("Stop").unwrap();

    let mut build_buttons = gtk::Box::new(gtk::Orientation::Horizontal, 0).unwrap();
    build_buttons.set_size_request(-1, -1);
    build_buttons.add(&run_button);
    build_buttons.add(&build_button);
    build_buttons.add(&test_button);
    build_buttons.add(&clean_button);
    build_buttons.add(&stop_button);

    let build_term = gtk::VteTerminal::new().unwrap();

    let mut build_pane = gtk::Box::new(gtk::Orientation::Vertical, 0).unwrap();
    build_pane.pack_start(&build_buttons, false, true, 0);
    build_pane.pack_start(&build_term, true, true, 0);

    let mut content = gtk::Box::new(gtk::Orientation::Vertical, 0).unwrap();
    content.pack_start(&editor_pane, false, true, 0);
    content.pack_start(&build_pane, true, true, 0);

    let mut hbox = gtk::Box::new(gtk::Orientation::Horizontal, 0).unwrap();
    hbox.pack_start(&project_pane, false, true, 0);
    hbox.pack_start(&content, true, true, 0);
    window.add(&hbox);

    // show the window

    window.show_all();

    // populate the project tree

    let mut state = ::utils::State{
        projects: HashSet::new(),
        expansions: HashSet::new(),
        selection: None,
        window: &window,
        tree_model: &model,
        tree_store: &store,
        tree_selection: &selection,
        rename_button: &rename_button,
        remove_button: &remove_button,
        is_refreshing_tree: false
    };

    ::utils::read_prefs(&mut state);
    ::ui::update_project_tree(&mut state, &mut project_tree);
    ::projects::set_selection(&mut state, &mut project_tree, write_fd);

    // connect to the signals

    new_button.connect(gtk::signals::Clicked::new(&mut || {
        ::projects::new_project(&mut state, &mut project_tree);
    }));
    import_button.connect(gtk::signals::Clicked::new(&mut || {
        ::projects::import_project(&mut state, &mut project_tree);
    }));
    rename_button.connect(gtk::signals::Clicked::new(&mut || {
        ::projects::rename_file(&mut state, write_fd);
    }));
    remove_button.connect(gtk::signals::Clicked::new(&mut || {
        ::projects::remove_item(&mut state, &mut project_tree, write_fd);
    }));
    selection.connect(gtk::signals::Changed::new(&mut || {
        ::projects::set_selection(&mut state, &mut project_tree, write_fd);
    }));
    project_tree.connect(gtk::signals::RowCollapsed::new(&mut |iter_raw, _| {
        let iter = gtk::TreeIter::wrap_pointer(iter_raw);
        ::projects::remove_expansion(&mut state, &iter);
    }));
    project_tree.connect(gtk::signals::RowExpanded::new(&mut |iter_raw, _| {
        let iter = gtk::TreeIter::wrap_pointer(iter_raw);
        ::projects::add_expansion(&mut state, &iter);
    }));

    // listen for bufenter events

    ffi::send_message(write_fd, "au BufEnter * call rpcnotify(1, 'bufenter', fnamemodify(bufname(''), ':p'))");

    // make read_fd non-blocking so we can check it while also checking for GUI events

    ffi::set_non_blocking(read_fd);

    // loop over GUI events and respond to messages from nvim

    loop {
        gtk::main_iteration_do(false);

        if let Some(recv_arr) = ffi::recv_message(read_fd) {
            if let Some(neovim::Object::String(event_name)) = recv_arr.get(1) {
                match event_name.as_slice() {
                    "bufenter" => {
                        if let Some(neovim::Object::Array(event_args)) = recv_arr.get(2) {
                            if let Some(neovim::Object::String(path_str)) = event_args.get(0) {
                                state.selection = Some(path_str);
                                ::utils::write_prefs(&state);
                            }
                        }
                    },
                    _ => (),
                }
            }
            ::ui::update_project_tree(&mut state, &mut project_tree);
        }

        if quit_app {
            break;
        }

        timer::sleep(Duration::milliseconds(10));
    }
}

fn main() {
    // create data dir
    let home_dir_str = ::utils::get_home_dir();
    let home_dir = Path::new(&home_dir_str);
    let data_dir = home_dir.join(::utils::DATA_DIR);
    if !data_dir.exists() {
        match fs::create_dir(&data_dir) {
            Ok(_) => {
                for res in ::utils::DATA_CONTENT.iter() {
                    let mut res_path = data_dir.clone();
                    for part in res.path {
                        res_path.push(part);
                    }
                    if let Some(parent) = res_path.parent() {
                        fs::create_dir_all(parent).ok();
                    }
                    if let Some(mut f) = fs::File::create(&res_path).ok() {
                        f.write_all(res.data.as_bytes()).ok();
                    }
                }
                if let Some(path_str) = data_dir.to_str() {
                    println!("Created data dir at {}", path_str);
                }
            },
            Err(e) => { println!("Error creating data dir: {}", e) }
        }
    }

    // set $VIM to the data dir if it isn't already set
    if env::var("VIM").is_err() {
        if let Some(path_str) = data_dir.to_str() {
            env::set_var("VIM", path_str);
        }
    }

    // create config file
    let config_file = home_dir.join(::utils::CONFIG_FILE);
    if !config_file.exists() {
        match fs::File::create(&config_file) {
            Ok(mut f) => {
                f.write_all(::utils::CONFIG_CONTENT.as_bytes()).ok();
                if let Some(path_str) = config_file.to_str() {
                    println!("Created config file at {}", path_str);
                }
            },
            Err(e) => { println!("Error creating config file: {}", e) }
        }
    }

    // collect the args into a vector and add the config file path
    let mut args_vec : Vec<String> = env::args().collect();
    if let Some(path_str) = config_file.to_str() {
        args_vec.push_all(&["-u".to_string(), path_str.to_string()]);
    }

    // if the no window flag was used, start up neovim without a gui
    let args_set : HashSet<String> = env::args().collect();
    if args_set.contains(::utils::NO_WINDOW_FLAG) {
        args_vec.retain(|arg| arg.as_slice() != ::utils::NO_WINDOW_FLAG);
        neovim::main_setup(&args_vec);
        neovim::main_loop();
        return;
    }

    // takes care of piping stdin/stdout between the gui and nvim
    let mut pty = gtk::VtePty::new().unwrap();

    // two anonymous pipes for msgpack-rpc between the gui and nvim
    let nvim_gui = ffi::new_pipe(); // to nvim from gui
    let gui_nvim = ffi::new_pipe(); // to gui from nvim

    // split into two processes
    let pid = ffi::fork_process();

    if pid > 0 { // the gui process
        gui_main(&mut pty, gui_nvim[0], nvim_gui[1], pid);
    } else { // the nvim process
        // prepare this process to be piped into the gui
        pty.child_setup();

        // start nvim
        neovim::main_setup(&args_vec);
        neovim::channel_from_fds(nvim_gui[0], gui_nvim[1]);
        neovim::main_loop();
    }
}
<commit_msg>Fix window expanding behavior<commit_after>extern crate libc;
extern crate neovim;
extern crate rgtk;
extern crate "rustc-serialize" as rustc_serialize;

use libc::c_int;
use rgtk::*;
use std::collections::HashSet;
use std::env;
use std::io::Write;
use std::fs;
use std::fs::PathExt;
use std::old_io::timer;
use std::path::Path;
use std::time::duration::Duration;

mod ffi;
mod projects;
mod ui;
mod utils;

fn gui_main(pty: &mut gtk::VtePty, read_fd: c_int, write_fd: c_int, pid: c_int) {
    gtk::init();

    // constants

    let width = 1242;
    let height = 768;
    let editor_height = ((height as f32) * 0.75) as i32;
    let mut quit_app = false;

    // create the window

    let title = format!("SolidOak {}.{}.{}",
                        option_env!("CARGO_PKG_VERSION_MAJOR").unwrap(),
                        option_env!("CARGO_PKG_VERSION_MINOR").unwrap(),
                        option_env!("CARGO_PKG_VERSION_PATCH").unwrap());
    let mut window = gtk::Window::new(gtk::WindowType::TopLevel).unwrap();
    window.set_title(title.as_slice());
    window.set_window_position(gtk::WindowPosition::Center);
    window.set_default_size(width, height);

    window.connect(gtk::signals::DeleteEvent::new(&mut |_| {
        ffi::send_message(write_fd, "qall!");
        ffi::close_fd(read_fd);
        ffi::close_fd(write_fd);
        quit_app = true;
        true
    }));

    // create the panes

    let new_button = gtk::Button::new_with_label("New Project").unwrap();
    let import_button = gtk::Button::new_with_label("Import").unwrap();
    let rename_button = gtk::Button::new_with_label("Rename").unwrap();
    let remove_button = gtk::Button::new_with_label("Remove").unwrap();

    let mut project_buttons = gtk::Box::new(gtk::Orientation::Horizontal, 0).unwrap();
    project_buttons.set_size_request(-1, -1);
    project_buttons.add(&new_button);
    project_buttons.add(&import_button);
    project_buttons.add(&rename_button);
    project_buttons.add(&remove_button);

    let mut project_tree = gtk::TreeView::new().unwrap();
    let selection = project_tree.get_selection().unwrap();
    let column_types = [glib::ffi::g_type_string, glib::ffi::g_type_string];
    let store = gtk::TreeStore::new(&column_types).unwrap();
    let model = store.get_model().unwrap();
    project_tree.set_model(&model);
    project_tree.set_headers_visible(false);

    let mut scroll_pane = gtk::ScrolledWindow::new(None, None).unwrap();
    scroll_pane.add(&project_tree);

    let column = gtk::TreeViewColumn::new().unwrap();
    let cell = gtk::CellRendererText::new().unwrap();
    column.pack_start(&cell, true);
    column.add_attribute(&cell, "text", 0);
    project_tree.append_column(&column);

    let mut project_pane = gtk::Box::new(gtk::Orientation::Vertical, 0).unwrap();
    project_pane.set_size_request(-1, -1);
    project_pane.pack_start(&project_buttons, false, true, 0);
    project_pane.pack_start(&scroll_pane, true, true, 0);

    let mut editor_pane = gtk::VteTerminal::new().unwrap();
    editor_pane.set_size_request(-1, editor_height);
    editor_pane.set_pty(pty);
    editor_pane.watch_child(pid);

    let run_button = gtk::Button::new_with_label("Run").unwrap();
    let build_button = gtk::Button::new_with_label("Build").unwrap();
    let test_button = gtk::Button::new_with_label("Test").unwrap();
    let clean_button = gtk::Button::new_with_label("Clean").unwrap();
    let stop_button = gtk::Button::new_with_label("Stop").unwrap();

    let mut build_buttons = gtk::Box::new(gtk::Orientation::Horizontal, 0).unwrap();
    build_buttons.set_size_request(-1, -1);
    build_buttons.add(&run_button);
    build_buttons.add(&build_button);
    build_buttons.add(&test_button);
    build_buttons.add(&clean_button);
    build_buttons.add(&stop_button);

    let build_term = gtk::VteTerminal::new().unwrap();

    let mut build_pane = gtk::Box::new(gtk::Orientation::Vertical, 0).unwrap();
    build_pane.pack_start(&build_buttons, false, true, 0);
    build_pane.pack_start(&build_term, true, true, 0);

    let mut content = gtk::Box::new(gtk::Orientation::Vertical, 0).unwrap();
    content.pack_start(&editor_pane, true, true, 0);
    content.pack_start(&build_pane, false, true, 0);

    let mut hbox = gtk::Box::new(gtk::Orientation::Horizontal, 0).unwrap();
    hbox.pack_start(&project_pane, false, true, 0);
    hbox.pack_start(&content, true, true, 0);
    window.add(&hbox);

    // show the window

    window.show_all();

    // populate the project tree

    let mut state = ::utils::State{
        projects: HashSet::new(),
        expansions: HashSet::new(),
        selection: None,
        window: &window,
        tree_model: &model,
        tree_store: &store,
        tree_selection: &selection,
        rename_button: &rename_button,
        remove_button: &remove_button,
        is_refreshing_tree: false
    };

    ::utils::read_prefs(&mut state);
    ::ui::update_project_tree(&mut state, &mut project_tree);
    ::projects::set_selection(&mut state, &mut project_tree, write_fd);

    // connect to the signals

    new_button.connect(gtk::signals::Clicked::new(&mut || {
        ::projects::new_project(&mut state, &mut project_tree);
    }));
    import_button.connect(gtk::signals::Clicked::new(&mut || {
        ::projects::import_project(&mut state, &mut project_tree);
    }));
    rename_button.connect(gtk::signals::Clicked::new(&mut || {
        ::projects::rename_file(&mut state, write_fd);
    }));
    remove_button.connect(gtk::signals::Clicked::new(&mut || {
        ::projects::remove_item(&mut state, &mut project_tree, write_fd);
    }));
    selection.connect(gtk::signals::Changed::new(&mut || {
        ::projects::set_selection(&mut state, &mut project_tree, write_fd);
    }));
    project_tree.connect(gtk::signals::RowCollapsed::new(&mut |iter_raw, _| {
        let iter = gtk::TreeIter::wrap_pointer(iter_raw);
        ::projects::remove_expansion(&mut state, &iter);
    }));
    project_tree.connect(gtk::signals::RowExpanded::new(&mut |iter_raw, _| {
        let iter = gtk::TreeIter::wrap_pointer(iter_raw);
        ::projects::add_expansion(&mut state, &iter);
    }));

    // listen for bufenter events

    ffi::send_message(write_fd, "au BufEnter * call rpcnotify(1, 'bufenter', fnamemodify(bufname(''), ':p'))");

    // make read_fd non-blocking so we can check it while also checking for GUI events

    ffi::set_non_blocking(read_fd);

    // loop over GUI events and respond to messages from nvim

    loop {
        gtk::main_iteration_do(false);

        if let Some(recv_arr) = ffi::recv_message(read_fd) {
            if let Some(neovim::Object::String(event_name)) = recv_arr.get(1) {
                match event_name.as_slice() {
                    "bufenter" => {
                        if let Some(neovim::Object::Array(event_args)) = recv_arr.get(2) {
                            if let Some(neovim::Object::String(path_str)) = event_args.get(0) {
                                state.selection = Some(path_str);
                                ::utils::write_prefs(&state);
                            }
                        }
                    },
                    _ => (),
                }
            }
            ::ui::update_project_tree(&mut state, &mut project_tree);
        }

        if quit_app {
            break;
        }

        timer::sleep(Duration::milliseconds(10));
    }
}

fn main() {
    // create data dir
    let home_dir_str = ::utils::get_home_dir();
    let home_dir = Path::new(&home_dir_str);
    let data_dir = home_dir.join(::utils::DATA_DIR);
    if !data_dir.exists() {
        match fs::create_dir(&data_dir) {
            Ok(_) => {
                for res in ::utils::DATA_CONTENT.iter() {
                    let mut res_path = data_dir.clone();
                    for part in res.path {
                        res_path.push(part);
                    }
                    if let Some(parent) = res_path.parent() {
                        fs::create_dir_all(parent).ok();
                    }
                    if let Some(mut f) = fs::File::create(&res_path).ok() {
                        f.write_all(res.data.as_bytes()).ok();
                    }
                }
                if let Some(path_str) = data_dir.to_str() {
                    println!("Created data dir at {}", path_str);
                }
            },
            Err(e) => { println!("Error creating data dir: {}", e) }
        }
    }

    // set $VIM to the data dir if it isn't already set
    if env::var("VIM").is_err() {
        if let Some(path_str) = data_dir.to_str() {
            env::set_var("VIM", path_str);
        }
    }

    // create config file
    let config_file = home_dir.join(::utils::CONFIG_FILE);
    if !config_file.exists() {
        match fs::File::create(&config_file) {
            Ok(mut f) => {
                f.write_all(::utils::CONFIG_CONTENT.as_bytes()).ok();
                if let Some(path_str) = config_file.to_str() {
                    println!("Created config file at {}", path_str);
                }
            },
            Err(e) => { println!("Error creating config file: {}", e) }
        }
    }

    // collect the args into a vector and add the config file path
    let mut args_vec : Vec<String> = env::args().collect();
    if let Some(path_str) = config_file.to_str() {
        args_vec.push_all(&["-u".to_string(), path_str.to_string()]);
    }

    // if the no window flag was used, start up neovim without a gui
    let args_set : HashSet<String> = env::args().collect();
    if args_set.contains(::utils::NO_WINDOW_FLAG) {
        args_vec.retain(|arg| arg.as_slice() != ::utils::NO_WINDOW_FLAG);
        neovim::main_setup(&args_vec);
        neovim::main_loop();
        return;
    }

    // takes care of piping stdin/stdout between the gui and nvim
    let mut pty = gtk::VtePty::new().unwrap();

    // two anonymous pipes for msgpack-rpc between the gui and nvim
    let nvim_gui = ffi::new_pipe(); // to nvim from gui
    let gui_nvim = ffi::new_pipe(); // to gui from nvim

    // split into two processes
    let pid = ffi::fork_process();

    if pid > 0 { // the gui process
        gui_main(&mut pty, gui_nvim[0], nvim_gui[1], pid);
    } else { // the nvim process
        // prepare this process to be piped into the gui
        pty.child_setup();

        // start nvim
        neovim::main_setup(&args_vec);
        neovim::channel_from_fds(nvim_gui[0], gui_nvim[1]);
        neovim::main_loop();
    }
}
<|endoftext|>


<commit_before>// UDP stuff adapted from http://illegalargumentexception.blogspot.co.uk/2015/05/rust-send-and-receive-on-localhost-with.html
use std::net;
#[macro_use]
extern crate clap;
use clap::{Arg, App};

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

pub fn send_message(send_addr: net::SocketAddr, target: net::SocketAddr, data: Vec<u8>) {
    let socket = socket(send_addr);
    let _ = socket.send_to(&data, target);
    drop(socket);
}

fn convert(inp: String) -> String {
    let outp = inp.to_lowercase();
    outp
}

fn main() {
    let command_vals = ["white", "red", "orange", "yellow", "green", "cyan", "blue", "purple",
                        "black", "?", "!", "quit"];
    let matches = App::new("rust_anybar")
                      .version(&crate_version!()[..])
                      .author("Stephan Hügel <urschrei@gmail.com>")
                      .about("A Rust command-line client for Anybar")
                      .args_from_usage("-p --port=[PORT] 'Set destination UDP port. Input must \
                                        be 0 – 65535, and defaults to 17388")
                      .arg(Arg::with_name("COMMAND")
                               .help("The command you wish to send to Anybar")
                               .index(1)
                               .possible_values(&command_vals)
                               .required(true))
                      .get_matches();
    let numeric_port = value_t!(matches.value_of("PORT"), u16).unwrap_or(1738);
    let to_send = convert(matches.value_of("COMMAND").unwrap().to_string());
    // blam our control message into a vector
    let mut message: Vec<u8> = Vec::new();
    message.extend(to_send.as_bytes()
                          .iter()
                          .cloned());
    // bind to the correct UDP port
    let ip = net::Ipv4Addr::new(127, 0, 0, 1);
    // parse() gives us Result which we need to unwrap
    // it's safe to unwrap here, cos we already checked success
    let listen_addr = net::SocketAddrV4::new(ip, numeric_port);
    let send_addr = net::SocketAddrV4::new(ip, 0);
    // and send our message
    send_message(net::SocketAddr::V4(send_addr),
                 net::SocketAddr::V4(listen_addr),
                 message);
}
<commit_msg>Remove convert function<commit_after>// UDP stuff adapted from http://illegalargumentexception.blogspot.co.uk/2015/05/rust-send-and-receive-on-localhost-with.html
use std::net;
#[macro_use]
extern crate clap;
use clap::{Arg, App};

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

pub fn send_message(send_addr: net::SocketAddr, target: net::SocketAddr, data: Vec<u8>) {
    let socket = socket(send_addr);
    let _ = socket.send_to(&data, target);
    drop(socket);
}

fn main() {
    let command_vals = ["white", "red", "orange", "yellow", "green", "cyan", "blue", "purple",
                        "black", "?", "!", "quit"];
    let matches = App::new("rust_anybar")
                      .version(&crate_version!()[..])
                      .author("Stephan Hügel <urschrei@gmail.com>")
                      .about("A Rust command-line client for Anybar")
                      .args_from_usage("-p --port=[PORT] 'Set destination UDP port. Input must \
                                        be 0 – 65535, and defaults to 17388")
                      .arg(Arg::with_name("COMMAND")
                               .help("The command you wish to send to Anybar")
                               .index(1)
                               .possible_values(&command_vals)
                               .required(true))
                      .get_matches();
    let numeric_port = value_t!(matches.value_of("PORT"), u16).unwrap_or(1738);
    let to_send = matches.value_of("COMMAND").unwrap().to_string().to_lowercase();
    // blam our control message into a vector
    let mut message: Vec<u8> = Vec::new();
    message.extend(to_send.as_bytes()
                          .iter()
                          .cloned());
    // bind to the correct UDP port
    let ip = net::Ipv4Addr::new(127, 0, 0, 1);
    // parse() gives us Result which we need to unwrap
    // it's safe to unwrap here, cos we already checked success
    let listen_addr = net::SocketAddrV4::new(ip, numeric_port);
    let send_addr = net::SocketAddrV4::new(ip, 0);
    // and send our message
    send_message(net::SocketAddr::V4(send_addr),
                 net::SocketAddr::V4(listen_addr),
                 message);
}
<|endoftext|>


<commit_before>extern crate chrono;
extern crate clap;
extern crate csv;
#[macro_use]
extern crate lazy_static;
extern crate regex;
extern crate safe_unwrap;
extern crate try_from;

mod err;

use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
use clap::{App, Arg, SubCommand};
use err::{ColumnConstraintsError, ColumnTypeError, SchemaLoadError, ValidationError, ValueError};
use std::{io, path};
use regex::Regex;
use safe_unwrap::SafeUnwrap;
use try_from::TryFrom;

lazy_static! {
    static ref IDENT_UNDERSCORE_RE: Regex = Regex::new(
        r"^[a-z][a-z0-9_]*$"
    ).safe_unwrap("built-in Regex is broken. Please file a bug");
}

lazy_static! {
    static ref ENUM_EXPR_RE: Regex = Regex::new(
        r"^ENUM.*\(((?:[A-Z][A-Z0-9]*,?)*)\)$"
    ).safe_unwrap("built-in Regex is broken. Please file a bug");
}

lazy_static! {
    static ref CONSTRAINT_RE: Regex = Regex::new(
        r"^(:?(?:NULLABLE|UNIQUE),?)*$"
    ).safe_unwrap("built-in Regex is broken. Please file a bug");
}

lazy_static! {
    static ref DECIMAL_RE: Regex = Regex::new(
        r"^\d+(?:\.\d+)?$"
    ).safe_unwrap("built-in Regex is broken. Please file a bug");
}

lazy_static! {
    static ref DATE_RE: Regex = Regex::new(
        r"^(\d{4})(\d{2})(\d{2})$"
    ).safe_unwrap("built-in Regex is broken. Please file a bug");
}

lazy_static! {
    static ref DATETIME_RE: Regex = Regex::new(
        r"^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$"
    ).safe_unwrap("built-in Regex is broken. Please file a bug");
}

lazy_static! {
    static ref TIME_RE: Regex = Regex::new(
        r"^(\d{2})(\d{2})(\d{2})$"
    ).safe_unwrap("built-in Regex is broken. Please file a bug");
}

lazy_static! {
    // `tablename_date_schema-schemaversion_csvxversion.csvx`
    static ref FN_RE: Regex = Regex::new(
        r"^([a-z][a-z0-9-]*)_(\d{4})(\d{2})(\d{2})_([a-z][a-z0-9-]*).csv$"
    ).expect("built-in Regex is broken. Please file a bug");
}

#[derive(Clone, Debug, Eq, PartialEq)]
struct CsvxMetadata {
    pub table_name: String,
    pub date: NaiveDate,
    pub schema: String,
}

impl CsvxMetadata {
    fn is_schema(&self) -> bool {
        self.schema.starts_with("csvx-schema-")
    }
}

impl From<csv::Error> for ValidationError {
    fn from(e: csv::Error) -> ValidationError {
        ValidationError::Csv(e)
    }
}

#[derive(Clone, Debug)]
enum ColumnType {
    String,
    Bool,
    Integer,
    Enum(Vec<String>),
    Decimal,
    Date,
    DateTime,
    Time,
}

#[derive(Clone, Debug)]
struct ColumnConstraints {
    nullable: bool,
    unique: bool,
}

impl Default for ColumnConstraints {
    fn default() -> ColumnConstraints {
        ColumnConstraints {
            nullable: false,
            unique: false,
        }
    }
}

impl<S> TryFrom<S> for ColumnConstraints
    where S: AsRef<str>
{
    type Err = ColumnConstraintsError;

    fn try_from(s: S) -> Result<ColumnConstraints, Self::Err> {
        if !CONSTRAINT_RE.is_match(s.as_ref()) {
            return Err(ColumnConstraintsError::MalformedConstraint);
        }

        let mut ccs = ColumnConstraints::default();

        if s.as_ref() == "" {
            return Ok(ccs);
        }

        for fragment in s.as_ref().split(',') {
            match fragment.as_ref() {
                "NULLABLE" => {
                    ccs.nullable = true;
                }
                "UNIQUE" => {
                    ccs.unique = true;
                }
                _ => return Err(ColumnConstraintsError::UnknownConstraint(s.as_ref().to_string())),
            }

        }

        Ok(ccs)
    }
}

impl<S> TryFrom<S> for ColumnType
    where S: AsRef<str>
{
    type Err = ColumnTypeError;

    fn try_from(s: S) -> Result<ColumnType, Self::Err> {
        match s.as_ref() {
            "STRING" => Ok(ColumnType::String),
            "BOOL" => Ok(ColumnType::Bool),
            "INTEGER" => Ok(ColumnType::Integer),
            "DECIMAL" => Ok(ColumnType::Decimal),
            "DATE" => Ok(ColumnType::Date),
            "DATETIME" => Ok(ColumnType::DateTime),
            "TIME" => Ok(ColumnType::Time),
            _ if ENUM_EXPR_RE.is_match(s.as_ref()) => {
                let variants: Vec<_> = ENUM_EXPR_RE
                    .captures(s.as_ref())
                    .safe_unwrap("match already exists")
                    .get(1)
                    .safe_unwrap("group 1 exists in regex")
                    .as_str()
                    .split(',')
                    .map(|s| s.to_owned())
                    .collect();

                Ok(ColumnType::Enum(variants))
            }
            _ => return Err(ColumnTypeError::UnknownType),

        }
    }
}

#[derive(Clone, Debug)]
struct CsvxColumnType {
    id: String,
    ty: ColumnType,
    constraints: ColumnConstraints,
    description: String,
}

#[derive(Debug)]
enum Value {
    String(String),
    Bool(bool),
    Integer(i64),
    Enum(String),
    Decimal(String),
    Date(NaiveDate),
    DateTime(NaiveDateTime),
    Time(NaiveTime),
}

impl From<std::num::ParseIntError> for ValueError {
    fn from(_: std::num::ParseIntError) -> ValueError {
        ValueError::InvalidInt
    }
}

impl CsvxColumnType {
    fn validate_value<S: AsRef<str>>(&self, s: &S) -> Result<Option<Value>, ValueError> {
        // FIXME: check UNIQUE

        // null check
        if s.as_ref() == "" {
            if self.constraints.nullable {
                return Ok(None);
            } else {
                return Err(ValueError::NonNullable);
            }
        }

        match self.ty {
            ColumnType::String => Ok(Some(Value::String(s.as_ref().to_string()))),
            ColumnType::Bool => {
                match s.as_ref() {
                    "TRUE" => Ok(Some(Value::Bool(true))),
                    "FALSE" => Ok(Some(Value::Bool(false))),
                    _ => Err(ValueError::InvalidBool),
                }
            }
            ColumnType::Integer => {
                // FIXME: check for leading zeros
                Ok(Some(Value::Integer(s.as_ref().parse()?)))
            }
            ColumnType::Enum(ref variants) => {
                let v = s.as_ref().to_owned();
                if variants.contains(&v) {
                    Ok(Some(Value::Enum(v)))
                } else {
                    Err(ValueError::InvalidEnum)
                }
            }
            ColumnType::Decimal => {
                if DECIMAL_RE.is_match(s.as_ref()) {
                    Ok(Some(Value::Decimal(s.as_ref().to_owned())))
                } else {
                    Err(ValueError::InvalidDecimal)
                }
            }
            ColumnType::Date => {
                match DATE_RE.captures(s.as_ref()) {
                    Some(ref c) => {
                        Ok(Some(Value::Date(NaiveDate::from_ymd_opt(cap(c, 1),
                                                                    cap(c, 2),
                                                                    cap(c, 3))
                                                    .ok_or(ValueError::InvalidDate)?)))
                    }
                    None => Err(ValueError::InvalidDate),
                }
            }
            ColumnType::DateTime => {
                match DATETIME_RE.captures(s.as_ref()) {
                    Some(ref c) => {
                        let dt = NaiveDate::from_ymd_opt(cap(c, 1), cap(c, 2), cap(c, 3))
                            .ok_or(ValueError::InvalidDate)?;
                        Ok(Some(Value::DateTime(dt.and_hms_opt(cap(c, 4), cap(c, 5), cap(c, 6))
                                                    .ok_or(ValueError::InvalidTime)?)))
                    }
                    None => Err(ValueError::InvalidDateTime),
                }
            }
            ColumnType::Time => {
                match TIME_RE.captures(s.as_ref()) {
                    Some(ref c) => {
                        Ok(Some(Value::Time(NaiveTime::from_hms_opt(cap(c, 1),
                                                                    cap(c, 2),
                                                                    cap(c, 3))
                                                    .ok_or(ValueError::InvalidTime)?)))
                    }
                    None => Err(ValueError::InvalidTime),
                }
            }
        }
    }
}

#[derive(Clone, Debug)]
struct CsvxSchema {
    columns: Vec<CsvxColumnType>,
}

impl CsvxSchema {
    fn from_file<P: AsRef<path::Path>>(filename: P) -> Result<CsvxSchema, SchemaLoadError> {
        let mut rdr = csv::Reader::from_file(filename)?.has_headers(false);

        let mut it = rdr.decode();
        let header: Option<Result<(String, String, String, String), _>> = it.next();

        let mut columns = Vec::new();

        match header {
            None => return Err(SchemaLoadError::MissingHeader),
            Some(res) => {
                let fields = res?;

                if fields.0 != "id" || fields.1 != "type" || fields.2 != "constraints" ||
                   fields.3 != "description" {

                    return Err(SchemaLoadError::BadHeader);
                }

                for (recno, rec) in it.enumerate() {
                    let (id, ty, constraints, desc) = rec?;
                    let lineno = recno + 2;

                    // check identifier
                    if !IDENT_UNDERSCORE_RE.is_match(&id.as_str()) {
                        return Err(SchemaLoadError::BadIdentifier(lineno, id));
                    }

                    // create type
                    let col_type = match ColumnType::try_from(ty.as_str()) {
                        Ok(v) => v,
                        Err(e) => return Err(SchemaLoadError::BadType(lineno, e)),
                    };

                    // create constraints
                    let col_constraints = match ColumnConstraints::try_from(constraints.as_str()) {
                        Ok(v) => v,
                        Err(e) => return Err(SchemaLoadError::BadConstraints(lineno, e)),
                    };

                    let col = CsvxColumnType {
                        id: id,
                        ty: col_type,
                        constraints: col_constraints,
                        description: desc,
                    };

                    columns.push(col)
                }

                Ok(CsvxSchema { columns: columns })
            }
        }
    }

    fn validate_file<P: AsRef<path::Path>>(&self, filename: P) -> Result<(), ValidationError> {
        let mut rdr = csv::Reader::from_file(filename)?.has_headers(true);

        let headers = rdr.headers()?;

        if headers.len() != self.columns.len() {
            return Err(ValidationError::MissingHeaders);
        }

        for (idx, (spec, actual)) in self.columns.iter().zip(headers.iter()).enumerate() {
            if spec.id.as_str() != actual {
                return Err(ValidationError::HeaderMismatch(idx + 1, actual.to_string()));
            }
        }

        for (rowid, row) in rdr.records().enumerate() {
            let lineno = rowid + 2;

            let fields = row?;

            if fields.len() != self.columns.len() {
                return Err(ValidationError::RowLengthMismatch(lineno));
            }

            for (idx, (col, value)) in self.columns.iter().zip(fields.iter()).enumerate() {
                if let Err(e) = col.validate_value(value) {
                    let col_idx = idx + 1;
                    println!("Value Error in line {}, column {}. Value: {}. Error: {:?}",
                             lineno,
                             col_idx,
                             value,
                             e);
                    return Err(ValidationError::ValueError(lineno, col_idx, e));
                }
            }
        }

        Ok(())
    }
}

#[inline]
fn cap<T>(c: &regex::Captures, idx: usize) -> T
    where T: std::str::FromStr,
          T::Err: std::fmt::Debug
{
    c.get(idx)
        .safe_unwrap("valid group")
        .as_str()
        .parse()
        .safe_unwrap("already validated through regex")

}

fn parse_filename<S: AsRef<str>>(filename: S) -> Option<CsvxMetadata> {
    match FN_RE.captures(filename.as_ref()) {
        Some(caps) => {
            let table_name = caps.get(1)
                .safe_unwrap("known group")
                .as_str()
                .to_string();
            let year = cap(&caps, 2);
            let month = cap(&caps, 3);
            let day = cap(&caps, 4);
            let schema = caps.get(5)
                .safe_unwrap("known group")
                .as_str()
                .to_string();

            Some(CsvxMetadata {
                     table_name: table_name,
                     date: match NaiveDate::from_ymd_opt(year, month, day) {
                         Some(d) => d,
                         None => return None,
                     },
                     schema: schema,
                 })
        }
        None => None,
    }
}

fn main() {
    let app = App::new("csvx")
        .version("5.0.2")
        .about("csvx utility")
        .subcommand(SubCommand::with_name("check")
                        .about("Check csvx files for conformance")
                        .arg(Arg::with_name("schema_file")
                                 .help("Schema file to check against")
                                 .required(true)
                                 .takes_value(true))
                        .arg(Arg::with_name("input_files")
                                 .help("Input files to check")
                                 .multiple(true)
                                 .takes_value(true)));
    let m = app.clone().get_matches();

    match m.subcommand {
        Some(ref cmd) if cmd.name == "check" => {
            let schema_file = cmd.matches
                .value_of("schema_file")
                .safe_unwrap("required argument");

            let meta_fn = path::Path::new(schema_file)
                .file_name()
                .expect("Not a valid filename")
                .to_str()
                .safe_unwrap("From valid UTF8");
            println!("Loading schema {:?}", meta_fn);
            let meta = parse_filename(meta_fn).expect("schema filename is not in valid format");

            if !meta.is_schema() {
                println!("The supplied file {} is not a csvx schema (wrong filename)",
                         schema_file);
                return;
            }

            // load schema
            let schema = CsvxSchema::from_file(schema_file).unwrap();

            let input_files = cmd.matches.values_of("input_files");

            if let Some(ifs) = input_files {
                for input_file in ifs {
                    println!("Validating {}", input_file);
                    schema.validate_file(input_file).unwrap();
                }
            }
        }
        _ => app.write_help(&mut io::stdout()).unwrap(),
    }
}

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

    use chrono::NaiveDate;

    #[test]
    fn filename_parsing_rejects_invalid() {
        assert_eq!(parse_filename("asdf"), None);
        assert_eq!(parse_filename(""), None);
        assert_eq!(parse_filename("test.csv"), None);
        assert_eq!(parse_filename("test.csv"), None);
    }

    #[test]
    fn filename_parsing_parses_valid() {
        assert_eq!(parse_filename("zoo-nyc_20170401_animals-2.csv").unwrap(),
                   CsvxMetadata {
                       table_name: "zoo-nyc".to_owned(),
                       date: NaiveDate::from_ymd(2017, 04, 01),
                       schema: "animals-2".to_owned(),
                   });
    }

}
<commit_msg>Moved checking into function.<commit_after>extern crate chrono;
extern crate clap;
extern crate csv;
#[macro_use]
extern crate lazy_static;
extern crate regex;
extern crate safe_unwrap;
extern crate try_from;

mod err;

use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
use clap::{App, Arg, SubCommand};
use err::{ColumnConstraintsError, ColumnTypeError, SchemaLoadError, ValidationError, ValueError};
use std::{io, path};
use regex::Regex;
use safe_unwrap::SafeUnwrap;
use try_from::TryFrom;

lazy_static! {
    static ref IDENT_UNDERSCORE_RE: Regex = Regex::new(
        r"^[a-z][a-z0-9_]*$"
    ).safe_unwrap("built-in Regex is broken. Please file a bug");
}

lazy_static! {
    static ref ENUM_EXPR_RE: Regex = Regex::new(
        r"^ENUM.*\(((?:[A-Z][A-Z0-9]*,?)*)\)$"
    ).safe_unwrap("built-in Regex is broken. Please file a bug");
}

lazy_static! {
    static ref CONSTRAINT_RE: Regex = Regex::new(
        r"^(:?(?:NULLABLE|UNIQUE),?)*$"
    ).safe_unwrap("built-in Regex is broken. Please file a bug");
}

lazy_static! {
    static ref DECIMAL_RE: Regex = Regex::new(
        r"^\d+(?:\.\d+)?$"
    ).safe_unwrap("built-in Regex is broken. Please file a bug");
}

lazy_static! {
    static ref DATE_RE: Regex = Regex::new(
        r"^(\d{4})(\d{2})(\d{2})$"
    ).safe_unwrap("built-in Regex is broken. Please file a bug");
}

lazy_static! {
    static ref DATETIME_RE: Regex = Regex::new(
        r"^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$"
    ).safe_unwrap("built-in Regex is broken. Please file a bug");
}

lazy_static! {
    static ref TIME_RE: Regex = Regex::new(
        r"^(\d{2})(\d{2})(\d{2})$"
    ).safe_unwrap("built-in Regex is broken. Please file a bug");
}

lazy_static! {
    // `tablename_date_schema-schemaversion_csvxversion.csvx`
    static ref FN_RE: Regex = Regex::new(
        r"^([a-z][a-z0-9-]*)_(\d{4})(\d{2})(\d{2})_([a-z][a-z0-9-]*).csv$"
    ).expect("built-in Regex is broken. Please file a bug");
}

#[derive(Clone, Debug, Eq, PartialEq)]
struct CsvxMetadata {
    pub table_name: String,
    pub date: NaiveDate,
    pub schema: String,
}

impl CsvxMetadata {
    fn is_schema(&self) -> bool {
        self.schema.starts_with("csvx-schema-")
    }
}

impl From<csv::Error> for ValidationError {
    fn from(e: csv::Error) -> ValidationError {
        ValidationError::Csv(e)
    }
}

#[derive(Clone, Debug)]
enum ColumnType {
    String,
    Bool,
    Integer,
    Enum(Vec<String>),
    Decimal,
    Date,
    DateTime,
    Time,
}

#[derive(Clone, Debug)]
struct ColumnConstraints {
    nullable: bool,
    unique: bool,
}

impl Default for ColumnConstraints {
    fn default() -> ColumnConstraints {
        ColumnConstraints {
            nullable: false,
            unique: false,
        }
    }
}

impl<S> TryFrom<S> for ColumnConstraints
    where S: AsRef<str>
{
    type Err = ColumnConstraintsError;

    fn try_from(s: S) -> Result<ColumnConstraints, Self::Err> {
        if !CONSTRAINT_RE.is_match(s.as_ref()) {
            return Err(ColumnConstraintsError::MalformedConstraint);
        }

        let mut ccs = ColumnConstraints::default();

        if s.as_ref() == "" {
            return Ok(ccs);
        }

        for fragment in s.as_ref().split(',') {
            match fragment.as_ref() {
                "NULLABLE" => {
                    ccs.nullable = true;
                }
                "UNIQUE" => {
                    ccs.unique = true;
                }
                _ => return Err(ColumnConstraintsError::UnknownConstraint(s.as_ref().to_string())),
            }

        }

        Ok(ccs)
    }
}

impl<S> TryFrom<S> for ColumnType
    where S: AsRef<str>
{
    type Err = ColumnTypeError;

    fn try_from(s: S) -> Result<ColumnType, Self::Err> {
        match s.as_ref() {
            "STRING" => Ok(ColumnType::String),
            "BOOL" => Ok(ColumnType::Bool),
            "INTEGER" => Ok(ColumnType::Integer),
            "DECIMAL" => Ok(ColumnType::Decimal),
            "DATE" => Ok(ColumnType::Date),
            "DATETIME" => Ok(ColumnType::DateTime),
            "TIME" => Ok(ColumnType::Time),
            _ if ENUM_EXPR_RE.is_match(s.as_ref()) => {
                let variants: Vec<_> = ENUM_EXPR_RE
                    .captures(s.as_ref())
                    .safe_unwrap("match already exists")
                    .get(1)
                    .safe_unwrap("group 1 exists in regex")
                    .as_str()
                    .split(',')
                    .map(|s| s.to_owned())
                    .collect();

                Ok(ColumnType::Enum(variants))
            }
            _ => return Err(ColumnTypeError::UnknownType),

        }
    }
}

#[derive(Clone, Debug)]
struct CsvxColumnType {
    id: String,
    ty: ColumnType,
    constraints: ColumnConstraints,
    description: String,
}

#[derive(Debug)]
enum Value {
    String(String),
    Bool(bool),
    Integer(i64),
    Enum(String),
    Decimal(String),
    Date(NaiveDate),
    DateTime(NaiveDateTime),
    Time(NaiveTime),
}

impl From<std::num::ParseIntError> for ValueError {
    fn from(_: std::num::ParseIntError) -> ValueError {
        ValueError::InvalidInt
    }
}

impl CsvxColumnType {
    fn validate_value<S: AsRef<str>>(&self, s: &S) -> Result<Option<Value>, ValueError> {
        // FIXME: check UNIQUE

        // null check
        if s.as_ref() == "" {
            if self.constraints.nullable {
                return Ok(None);
            } else {
                return Err(ValueError::NonNullable);
            }
        }

        match self.ty {
            ColumnType::String => Ok(Some(Value::String(s.as_ref().to_string()))),
            ColumnType::Bool => {
                match s.as_ref() {
                    "TRUE" => Ok(Some(Value::Bool(true))),
                    "FALSE" => Ok(Some(Value::Bool(false))),
                    _ => Err(ValueError::InvalidBool),
                }
            }
            ColumnType::Integer => {
                // FIXME: check for leading zeros
                Ok(Some(Value::Integer(s.as_ref().parse()?)))
            }
            ColumnType::Enum(ref variants) => {
                let v = s.as_ref().to_owned();
                if variants.contains(&v) {
                    Ok(Some(Value::Enum(v)))
                } else {
                    Err(ValueError::InvalidEnum)
                }
            }
            ColumnType::Decimal => {
                if DECIMAL_RE.is_match(s.as_ref()) {
                    Ok(Some(Value::Decimal(s.as_ref().to_owned())))
                } else {
                    Err(ValueError::InvalidDecimal)
                }
            }
            ColumnType::Date => {
                match DATE_RE.captures(s.as_ref()) {
                    Some(ref c) => {
                        Ok(Some(Value::Date(NaiveDate::from_ymd_opt(cap(c, 1),
                                                                    cap(c, 2),
                                                                    cap(c, 3))
                                                    .ok_or(ValueError::InvalidDate)?)))
                    }
                    None => Err(ValueError::InvalidDate),
                }
            }
            ColumnType::DateTime => {
                match DATETIME_RE.captures(s.as_ref()) {
                    Some(ref c) => {
                        let dt = NaiveDate::from_ymd_opt(cap(c, 1), cap(c, 2), cap(c, 3))
                            .ok_or(ValueError::InvalidDate)?;
                        Ok(Some(Value::DateTime(dt.and_hms_opt(cap(c, 4), cap(c, 5), cap(c, 6))
                                                    .ok_or(ValueError::InvalidTime)?)))
                    }
                    None => Err(ValueError::InvalidDateTime),
                }
            }
            ColumnType::Time => {
                match TIME_RE.captures(s.as_ref()) {
                    Some(ref c) => {
                        Ok(Some(Value::Time(NaiveTime::from_hms_opt(cap(c, 1),
                                                                    cap(c, 2),
                                                                    cap(c, 3))
                                                    .ok_or(ValueError::InvalidTime)?)))
                    }
                    None => Err(ValueError::InvalidTime),
                }
            }
        }
    }
}

#[derive(Clone, Debug)]
struct CsvxSchema {
    columns: Vec<CsvxColumnType>,
}

impl CsvxSchema {
    fn from_file<P: AsRef<path::Path>>(filename: P) -> Result<CsvxSchema, SchemaLoadError> {
        let mut rdr = csv::Reader::from_file(filename)?.has_headers(false);

        let mut it = rdr.decode();
        let header: Option<Result<(String, String, String, String), _>> = it.next();

        let mut columns = Vec::new();

        match header {
            None => return Err(SchemaLoadError::MissingHeader),
            Some(res) => {
                let fields = res?;

                if fields.0 != "id" || fields.1 != "type" || fields.2 != "constraints" ||
                   fields.3 != "description" {

                    return Err(SchemaLoadError::BadHeader);
                }

                for (recno, rec) in it.enumerate() {
                    let (id, ty, constraints, desc) = rec?;
                    let lineno = recno + 2;

                    // check identifier
                    if !IDENT_UNDERSCORE_RE.is_match(&id.as_str()) {
                        return Err(SchemaLoadError::BadIdentifier(lineno, id));
                    }

                    // create type
                    let col_type = match ColumnType::try_from(ty.as_str()) {
                        Ok(v) => v,
                        Err(e) => return Err(SchemaLoadError::BadType(lineno, e)),
                    };

                    // create constraints
                    let col_constraints = match ColumnConstraints::try_from(constraints.as_str()) {
                        Ok(v) => v,
                        Err(e) => return Err(SchemaLoadError::BadConstraints(lineno, e)),
                    };

                    let col = CsvxColumnType {
                        id: id,
                        ty: col_type,
                        constraints: col_constraints,
                        description: desc,
                    };

                    columns.push(col)
                }

                Ok(CsvxSchema { columns: columns })
            }
        }
    }

    fn validate_file<P: AsRef<path::Path>>(&self, filename: P) -> Result<(), ValidationError> {
        let mut rdr = csv::Reader::from_file(filename)?.has_headers(true);

        let headers = rdr.headers()?;

        if headers.len() != self.columns.len() {
            return Err(ValidationError::MissingHeaders);
        }

        for (idx, (spec, actual)) in self.columns.iter().zip(headers.iter()).enumerate() {
            if spec.id.as_str() != actual {
                return Err(ValidationError::HeaderMismatch(idx + 1, actual.to_string()));
            }
        }

        for (rowid, row) in rdr.records().enumerate() {
            let lineno = rowid + 2;

            let fields = row?;

            if fields.len() != self.columns.len() {
                return Err(ValidationError::RowLengthMismatch(lineno));
            }

            for (idx, (col, value)) in self.columns.iter().zip(fields.iter()).enumerate() {
                if let Err(e) = col.validate_value(value) {
                    let col_idx = idx + 1;
                    println!("Value Error in line {}, column {}. Value: {}. Error: {:?}",
                             lineno,
                             col_idx,
                             value,
                             e);
                    return Err(ValidationError::ValueError(lineno, col_idx, e));
                }
            }
        }

        Ok(())
    }
}

#[inline]
fn cap<T>(c: &regex::Captures, idx: usize) -> T
    where T: std::str::FromStr,
          T::Err: std::fmt::Debug
{
    c.get(idx)
        .safe_unwrap("valid group")
        .as_str()
        .parse()
        .safe_unwrap("already validated through regex")

}

fn parse_filename<S: AsRef<str>>(filename: S) -> Option<CsvxMetadata> {
    match FN_RE.captures(filename.as_ref()) {
        Some(caps) => {
            let table_name = caps.get(1)
                .safe_unwrap("known group")
                .as_str()
                .to_string();
            let year = cap(&caps, 2);
            let month = cap(&caps, 3);
            let day = cap(&caps, 4);
            let schema = caps.get(5)
                .safe_unwrap("known group")
                .as_str()
                .to_string();

            Some(CsvxMetadata {
                     table_name: table_name,
                     date: match NaiveDate::from_ymd_opt(year, month, day) {
                         Some(d) => d,
                         None => return None,
                     },
                     schema: schema,
                 })
        }
        None => None,
    }
}

fn cmd_check<P: AsRef<path::Path>, Q: AsRef<path::Path>>(schema_path: P, input_files: Vec<Q>) {
    let meta_fn = schema_path
        .as_ref()
        .to_owned()
        .file_name()
        .expect("Not a valid filename")
        .to_str()
        .safe_unwrap("From valid UTF8")
        .to_owned();

    println!("Loading schema {:?}", meta_fn);
    let meta = parse_filename(meta_fn).expect("schema filename is not in valid format");

    if !meta.is_schema() {
        println!("The supplied file {:?} is not a csvx schema (wrong
            filename)",
                 schema_path.as_ref());
        return;
    }

    // load schema
    let schema = CsvxSchema::from_file(schema_path).unwrap();

    for input_file in input_files {
        println!("Validating {:?}", input_file.as_ref());
        schema.validate_file(input_file).unwrap();
    }
}

fn main() {
    let app = App::new("csvx")
        .version("5.0.2")
        .about("csvx utility")
        .subcommand(SubCommand::with_name("check")
                        .about("Check csvx files for conformance")
                        .arg(Arg::with_name("schema_path")
                                 .help("Schema file to check against")
                                 .required(true)
                                 .takes_value(true))
                        .arg(Arg::with_name("input_files")
                                 .help("Input files to check")
                                 .multiple(true)
                                 .takes_value(true)));
    let m = app.clone().get_matches();

    match m.subcommand {
        Some(ref cmd) if cmd.name == "check" => {
            cmd_check(cmd.matches
                          .value_of("schema_path")
                          .safe_unwrap("required argument"),
                      cmd.matches
                          .values_of("input_files")
                          .map(|v| v.collect())
                          .unwrap_or_else(|| Vec::new()))
        }
        _ => app.write_help(&mut io::stdout()).unwrap(),
    }
}

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

    use chrono::NaiveDate;

    #[test]
    fn filename_parsing_rejects_invalid() {
        assert_eq!(parse_filename("asdf"), None);
        assert_eq!(parse_filename(""), None);
        assert_eq!(parse_filename("test.csv"), None);
        assert_eq!(parse_filename("test.csv"), None);
    }

    #[test]
    fn filename_parsing_parses_valid() {
        assert_eq!(parse_filename("zoo-nyc_20170401_animals-2.csv").unwrap(),
                   CsvxMetadata {
                       table_name: "zoo-nyc".to_owned(),
                       date: NaiveDate::from_ymd(2017, 04, 01),
                       schema: "animals-2".to_owned(),
                   });
    }

}
<|endoftext|>


<commit_before>use std::env;
use std::ffi::OsString;
use std::fmt::{self, Display};
use std::fs::{self, File};
use std::io::{self, BufRead, Read, Write};
use std::path::{Path, PathBuf};
use std::process::{self, Command, Stdio};

use atty::Stream::{Stderr, Stdout};
use prettyprint::{PrettyPrinter, PrettyPrintError};
use quote::quote;
use structopt::clap::AppSettings;
use structopt::StructOpt;

#[derive(StructOpt)]
#[structopt(bin_name = "cargo")]
enum Opts {
    /// Show the result of macro expansion.
    #[structopt(
        name = "expand",
        raw(
            setting = "AppSettings::UnifiedHelpMessage",
            setting = "AppSettings::DeriveDisplayOrder",
            setting = "AppSettings::DontCollapseArgsInUsage"
        )
    )]
    Expand(Args),
}

#[derive(StructOpt, Debug)]
#[structopt(rename_all = "kebab-case")]
struct Args {
    /// Space-separated list of features to activate
    #[structopt(long, value_name = "FEATURES")]
    features: Option<String>,

    /// Activate all available features
    #[structopt(long)]
    all_features: bool,

    /// Do not activate the `default` feature
    #[structopt(long)]
    no_default_features: bool,

    /// Build only this package's library
    #[structopt(long)]
    lib: bool,

    /// Build only the specified binary
    #[structopt(long, value_name = "NAME")]
    bin: Option<String>,

    /// Build only the specified example
    #[structopt(long, value_name = "NAME")]
    example: Option<String>,

    /// Build only the specified test target
    #[structopt(long, value_name = "NAME")]
    test: Option<String>,

    /// Build only the specified bench target
    #[structopt(long, value_name = "NAME")]
    bench: Option<String>,

    /// Target triple which compiles will be for
    #[structopt(long, value_name = "TARGET")]
    target: Option<String>,

    /// Directory for all generated artifacts
    #[structopt(long, value_name = "DIRECTORY", parse(from_os_str))]
    target_dir: Option<PathBuf>,

    /// Path to Cargo.toml
    #[structopt(long, value_name = "PATH", parse(from_os_str))]
    manifest_path: Option<PathBuf>,

    /// Number of parallel jobs, defaults to # of CPUs
    #[structopt(short, long, value_name = "N")]
    jobs: Option<u64>,

    /// Coloring: auto, always, never
    #[structopt(long, value_name = "WHEN")]
    color: Option<String>,

    /// Require Cargo.lock and cache are up to date
    #[structopt(long)]
    frozen: bool,

    /// Require Cargo.lock is up to date
    #[structopt(long)]
    locked: bool,

    /// Unstable (nightly-only) flags to Cargo
    #[structopt(short = "Z", value_name = "FLAG")]
    unstable_flags: Vec<String>,
}

fn main() {
    let result = cargo_expand_or_run_nightly();
    process::exit(match result {
        Ok(code) => code,
        Err(err) => {
            let _ = writeln!(&mut io::stderr(), "{}", err);
            1
        }
    });
}

enum Error {
    Io(io::Error),
    Print(PrettyPrintError),
}

impl From<io::Error> for Error {
    fn from(error: io::Error) -> Self {
        Error::Io(error)
    }
}

impl From<PrettyPrintError> for Error {
    fn from(error: PrettyPrintError) -> Self {
        Error::Print(error)
    }
}

impl Display for Error {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Error::Io(error) => Display::fmt(error, formatter),
            Error::Print(error) => Display::fmt(error, formatter),
        }
    }
}

type Result<T> = std::result::Result<T, Error>;

fn cargo_expand_or_run_nightly() -> Result<i32> {
    const NO_RUN_NIGHTLY: &str = "CARGO_EXPAND_NO_RUN_NIGHTLY";

    let maybe_nightly = !definitely_not_nightly();
    if maybe_nightly || env::var_os(NO_RUN_NIGHTLY).is_some() {
        return cargo_expand();
    }

    let mut nightly = Command::new("cargo");
    nightly.arg("+nightly");
    nightly.arg("expand");

    let mut args = env::args_os().peekable();
    args.next().unwrap(); // cargo
    if args.peek().map_or(false, |arg| arg == "expand") {
        args.next().unwrap(); // expand
    }
    nightly.args(args);

    // Hopefully prevent infinite re-run loop.
    nightly.env(NO_RUN_NIGHTLY, "");

    let status = nightly.status()?;

    Ok(match status.code() {
        Some(code) => code,
        None => {
            if status.success() {
                0
            } else {
                1
            }
        }
    })
}

fn definitely_not_nightly() -> bool {
    let mut cmd = Command::new(cargo_binary());
    cmd.arg("--version");

    let output = match cmd.output() {
        Ok(output) => output,
        Err(_) => return false,
    };

    let version = match String::from_utf8(output.stdout) {
        Ok(version) => version,
        Err(_) => return false,
    };

    version.starts_with("cargo 1") && !version.contains("nightly")
}

fn cargo_binary() -> OsString {
    env::var_os("CARGO").unwrap_or_else(|| "cargo".to_owned().into())
}

fn cargo_expand() -> Result<i32> {
    let Opts::Expand(args) = Opts::from_args();

    let mut builder = tempfile::Builder::new();
    builder.prefix("cargo-expand");
    let outdir = builder.tempdir().expect("failed to create tmp file");
    let outfile_path = outdir.path().join("expanded");

    // Run cargo
    let mut cmd = Command::new(cargo_binary());
    apply_args(&mut cmd, &args, &outfile_path);
    let code = filter_err(&mut cmd, ignore_cargo_err)?;

    let mut outfile = File::open(&outfile_path)?;
    if outfile.metadata()?.len() == 0 {
        let _ = writeln!(
            &mut io::stderr(),
            "ERROR: rustc produced no expanded output"
        );
        return Ok(if code == 0 { 1 } else { code });
    }

    // Run rustfmt
    let which_rustfmt = which(&["rustfmt"]);
    if let Some(fmt) = which_rustfmt {
        // Discard comments, which are misplaced by the compiler
        let mut content = Vec::new();
        outfile.read_to_end(&mut content)?;
        match String::from_utf8(content) {
            Ok(content) => {
                if let Ok(syntax_tree) = syn::parse_file(&content) {
                    let content = quote!(#syntax_tree).to_string();
                    fs::write(&outfile_path, content)?;
                }
            }
            Err(_) => {
                let _ = writeln!(&mut io::stderr(), "WARNING: non-UTF8 content");
            }
        }

        // Ignore any errors.
        let _status = Command::new(fmt)
            .arg(&outfile_path)
            .stderr(Stdio::null())
            .status();
    }

    // Run pretty printer
    let allow_color = args.color.as_ref().map_or(true, |color| color != "never");
    let do_color = allow_color && atty::is(Stdout);
    if do_color {
        let mut content = fs::read_to_string(&outfile_path)?;
        if content.ends_with('\n') {
            // Pretty printer seems to print an extra trailing newline.
            content.truncate(content.len() - 1);
        }
        let printer = PrettyPrinter::default()
            .header(false)
            .grid(false)
            .line_numbers(false)
            .language("rust")
            .build()
            .unwrap();
        printer.string(content)?;
    } else {
        let mut reader = File::open(&outfile_path)?;
        io::copy(&mut reader, &mut io::stdout())?;
    }

    Ok(0)
}

// Based on https://github.com/rsolomo/cargo-check
fn apply_args(cmd: &mut Command, args: &Args, outfile: &Path) {
    cmd.arg("rustc");
    cmd.arg("--profile=check");

    if let Some(features) = &args.features {
        cmd.arg("--features");
        cmd.arg(features);
    }

    if args.all_features {
        cmd.arg("--all-features");
    }

    if args.no_default_features {
        cmd.arg("--no-default-features");
    }

    if args.lib {
        cmd.arg("--lib");
    }

    if let Some(bin) = &args.bin {
        cmd.arg("--bin");
        cmd.arg(bin);
    }

    if let Some(example) = &args.example {
        cmd.arg("--example");
        cmd.arg(example);
    }

    if let Some(test) = &args.test {
        cmd.arg("--test");
        cmd.arg(test);
    }

    if let Some(bench) = &args.bench {
        cmd.arg("--bench");
        cmd.arg(bench);
    }

    if let Some(target) = &args.target {
        cmd.arg("--target");
        cmd.arg(target);
    }

    if let Some(target_dir) = &args.target_dir {
        cmd.arg("--target-dir");
        cmd.arg(target_dir);
    }

    if let Some(manifest_path) = &args.manifest_path {
        cmd.arg("--manifest-path");
        cmd.arg(manifest_path);
    }

    if let Some(jobs) = args.jobs {
        cmd.arg("--jobs");
        cmd.arg(jobs.to_string());
    }

    cmd.arg("--color");
    if let Some(color) = &args.color {
        cmd.arg(color);
    } else {
        cmd.arg(if atty::is(Stderr) { "always" } else { "never" });
    }

    if args.frozen {
        cmd.arg("--frozen");
    }

    if args.locked {
        cmd.arg("--locked");
    }

    for unstable_flag in &args.unstable_flags {
        cmd.arg("-Z");
        cmd.arg(unstable_flag);
    }

    cmd.arg("--");
    cmd.arg("-o");
    cmd.arg(outfile);
    cmd.arg("-Zunstable-options");
    cmd.arg("--pretty=expanded");
}

fn which(cmd: &[&str]) -> Option<OsString> {
    if env::args_os().any(|arg| arg == *"--help") {
        return None;
    }

    if let Some(which) = env::var_os(&cmd[0].to_uppercase()) {
        return if which.is_empty() { None } else { Some(which) };
    }

    let spawn = Command::new(cmd[0])
        .args(&cmd[1..])
        .stdin(Stdio::null())
        .stdout(Stdio::null())
        .stderr(Stdio::null())
        .spawn();
    let mut child = match spawn {
        Ok(child) => child,
        Err(_) => {
            return None;
        }
    };

    let exit = match child.wait() {
        Ok(exit) => exit,
        Err(_) => {
            return None;
        }
    };

    if exit.success() {
        Some(cmd[0].into())
    } else {
        None
    }
}

fn filter_err(cmd: &mut Command, ignore: fn(&str) -> bool) -> io::Result<i32> {
    let mut child = cmd.stderr(Stdio::piped()).spawn()?;
    let mut stderr = io::BufReader::new(child.stderr.take().unwrap());
    let mut line = String::new();
    while let Ok(n) = stderr.read_line(&mut line) {
        if n == 0 {
            break;
        }
        if !ignore(&line) {
            let _ = write!(&mut io::stderr(), "{}", line);
        }
        line.clear();
    }
    let code = child.wait()?.code().unwrap_or(1);
    Ok(code)
}

fn ignore_cargo_err(line: &str) -> bool {
    if line.trim().is_empty() {
        return true;
    }

    let blacklist = [
        "ignoring specified output filename because multiple outputs were \
         requested",
        "ignoring specified output filename for 'link' output because multiple \
         outputs were requested",
        "ignoring --out-dir flag due to -o flag",
        "ignoring -C extra-filename flag due to -o flag",
        "due to multiple output types requested, the explicitly specified \
         output file name will be adapted for each output type",
    ];
    for s in &blacklist {
        if line.contains(s) {
            return true;
        }
    }

    false
}
<commit_msg>Disable builtin pager<commit_after>use std::env;
use std::ffi::OsString;
use std::fmt::{self, Display};
use std::fs::{self, File};
use std::io::{self, BufRead, Read, Write};
use std::path::{Path, PathBuf};
use std::process::{self, Command, Stdio};

use atty::Stream::{Stderr, Stdout};
use prettyprint::{PagingMode, PrettyPrinter, PrettyPrintError};
use quote::quote;
use structopt::clap::AppSettings;
use structopt::StructOpt;

#[derive(StructOpt)]
#[structopt(bin_name = "cargo")]
enum Opts {
    /// Show the result of macro expansion.
    #[structopt(
        name = "expand",
        raw(
            setting = "AppSettings::UnifiedHelpMessage",
            setting = "AppSettings::DeriveDisplayOrder",
            setting = "AppSettings::DontCollapseArgsInUsage"
        )
    )]
    Expand(Args),
}

#[derive(StructOpt, Debug)]
#[structopt(rename_all = "kebab-case")]
struct Args {
    /// Space-separated list of features to activate
    #[structopt(long, value_name = "FEATURES")]
    features: Option<String>,

    /// Activate all available features
    #[structopt(long)]
    all_features: bool,

    /// Do not activate the `default` feature
    #[structopt(long)]
    no_default_features: bool,

    /// Build only this package's library
    #[structopt(long)]
    lib: bool,

    /// Build only the specified binary
    #[structopt(long, value_name = "NAME")]
    bin: Option<String>,

    /// Build only the specified example
    #[structopt(long, value_name = "NAME")]
    example: Option<String>,

    /// Build only the specified test target
    #[structopt(long, value_name = "NAME")]
    test: Option<String>,

    /// Build only the specified bench target
    #[structopt(long, value_name = "NAME")]
    bench: Option<String>,

    /// Target triple which compiles will be for
    #[structopt(long, value_name = "TARGET")]
    target: Option<String>,

    /// Directory for all generated artifacts
    #[structopt(long, value_name = "DIRECTORY", parse(from_os_str))]
    target_dir: Option<PathBuf>,

    /// Path to Cargo.toml
    #[structopt(long, value_name = "PATH", parse(from_os_str))]
    manifest_path: Option<PathBuf>,

    /// Number of parallel jobs, defaults to # of CPUs
    #[structopt(short, long, value_name = "N")]
    jobs: Option<u64>,

    /// Coloring: auto, always, never
    #[structopt(long, value_name = "WHEN")]
    color: Option<String>,

    /// Require Cargo.lock and cache are up to date
    #[structopt(long)]
    frozen: bool,

    /// Require Cargo.lock is up to date
    #[structopt(long)]
    locked: bool,

    /// Unstable (nightly-only) flags to Cargo
    #[structopt(short = "Z", value_name = "FLAG")]
    unstable_flags: Vec<String>,
}

fn main() {
    let result = cargo_expand_or_run_nightly();
    process::exit(match result {
        Ok(code) => code,
        Err(err) => {
            let _ = writeln!(&mut io::stderr(), "{}", err);
            1
        }
    });
}

enum Error {
    Io(io::Error),
    Print(PrettyPrintError),
}

impl From<io::Error> for Error {
    fn from(error: io::Error) -> Self {
        Error::Io(error)
    }
}

impl From<PrettyPrintError> for Error {
    fn from(error: PrettyPrintError) -> Self {
        Error::Print(error)
    }
}

impl Display for Error {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Error::Io(error) => Display::fmt(error, formatter),
            Error::Print(error) => Display::fmt(error, formatter),
        }
    }
}

type Result<T> = std::result::Result<T, Error>;

fn cargo_expand_or_run_nightly() -> Result<i32> {
    const NO_RUN_NIGHTLY: &str = "CARGO_EXPAND_NO_RUN_NIGHTLY";

    let maybe_nightly = !definitely_not_nightly();
    if maybe_nightly || env::var_os(NO_RUN_NIGHTLY).is_some() {
        return cargo_expand();
    }

    let mut nightly = Command::new("cargo");
    nightly.arg("+nightly");
    nightly.arg("expand");

    let mut args = env::args_os().peekable();
    args.next().unwrap(); // cargo
    if args.peek().map_or(false, |arg| arg == "expand") {
        args.next().unwrap(); // expand
    }
    nightly.args(args);

    // Hopefully prevent infinite re-run loop.
    nightly.env(NO_RUN_NIGHTLY, "");

    let status = nightly.status()?;

    Ok(match status.code() {
        Some(code) => code,
        None => {
            if status.success() {
                0
            } else {
                1
            }
        }
    })
}

fn definitely_not_nightly() -> bool {
    let mut cmd = Command::new(cargo_binary());
    cmd.arg("--version");

    let output = match cmd.output() {
        Ok(output) => output,
        Err(_) => return false,
    };

    let version = match String::from_utf8(output.stdout) {
        Ok(version) => version,
        Err(_) => return false,
    };

    version.starts_with("cargo 1") && !version.contains("nightly")
}

fn cargo_binary() -> OsString {
    env::var_os("CARGO").unwrap_or_else(|| "cargo".to_owned().into())
}

fn cargo_expand() -> Result<i32> {
    let Opts::Expand(args) = Opts::from_args();

    let mut builder = tempfile::Builder::new();
    builder.prefix("cargo-expand");
    let outdir = builder.tempdir().expect("failed to create tmp file");
    let outfile_path = outdir.path().join("expanded");

    // Run cargo
    let mut cmd = Command::new(cargo_binary());
    apply_args(&mut cmd, &args, &outfile_path);
    let code = filter_err(&mut cmd, ignore_cargo_err)?;

    let mut outfile = File::open(&outfile_path)?;
    if outfile.metadata()?.len() == 0 {
        let _ = writeln!(
            &mut io::stderr(),
            "ERROR: rustc produced no expanded output"
        );
        return Ok(if code == 0 { 1 } else { code });
    }

    // Run rustfmt
    let which_rustfmt = which(&["rustfmt"]);
    if let Some(fmt) = which_rustfmt {
        // Discard comments, which are misplaced by the compiler
        let mut content = Vec::new();
        outfile.read_to_end(&mut content)?;
        match String::from_utf8(content) {
            Ok(content) => {
                if let Ok(syntax_tree) = syn::parse_file(&content) {
                    let content = quote!(#syntax_tree).to_string();
                    fs::write(&outfile_path, content)?;
                }
            }
            Err(_) => {
                let _ = writeln!(&mut io::stderr(), "WARNING: non-UTF8 content");
            }
        }

        // Ignore any errors.
        let _status = Command::new(fmt)
            .arg(&outfile_path)
            .stderr(Stdio::null())
            .status();
    }

    // Run pretty printer
    let allow_color = args.color.as_ref().map_or(true, |color| color != "never");
    let do_color = allow_color && atty::is(Stdout);
    if do_color {
        let mut content = fs::read_to_string(&outfile_path)?;
        if content.ends_with('\n') {
            // Pretty printer seems to print an extra trailing newline.
            content.truncate(content.len() - 1);
        }
        let printer = PrettyPrinter::default()
            .header(false)
            .grid(false)
            .line_numbers(false)
            .language("rust")
            .paging_mode(PagingMode::Never)
            .build()
            .unwrap();
        printer.string(content)?;
    } else {
        let mut reader = File::open(&outfile_path)?;
        io::copy(&mut reader, &mut io::stdout())?;
    }

    Ok(0)
}

// Based on https://github.com/rsolomo/cargo-check
fn apply_args(cmd: &mut Command, args: &Args, outfile: &Path) {
    cmd.arg("rustc");
    cmd.arg("--profile=check");

    if let Some(features) = &args.features {
        cmd.arg("--features");
        cmd.arg(features);
    }

    if args.all_features {
        cmd.arg("--all-features");
    }

    if args.no_default_features {
        cmd.arg("--no-default-features");
    }

    if args.lib {
        cmd.arg("--lib");
    }

    if let Some(bin) = &args.bin {
        cmd.arg("--bin");
        cmd.arg(bin);
    }

    if let Some(example) = &args.example {
        cmd.arg("--example");
        cmd.arg(example);
    }

    if let Some(test) = &args.test {
        cmd.arg("--test");
        cmd.arg(test);
    }

    if let Some(bench) = &args.bench {
        cmd.arg("--bench");
        cmd.arg(bench);
    }

    if let Some(target) = &args.target {
        cmd.arg("--target");
        cmd.arg(target);
    }

    if let Some(target_dir) = &args.target_dir {
        cmd.arg("--target-dir");
        cmd.arg(target_dir);
    }

    if let Some(manifest_path) = &args.manifest_path {
        cmd.arg("--manifest-path");
        cmd.arg(manifest_path);
    }

    if let Some(jobs) = args.jobs {
        cmd.arg("--jobs");
        cmd.arg(jobs.to_string());
    }

    cmd.arg("--color");
    if let Some(color) = &args.color {
        cmd.arg(color);
    } else {
        cmd.arg(if atty::is(Stderr) { "always" } else { "never" });
    }

    if args.frozen {
        cmd.arg("--frozen");
    }

    if args.locked {
        cmd.arg("--locked");
    }

    for unstable_flag in &args.unstable_flags {
        cmd.arg("-Z");
        cmd.arg(unstable_flag);
    }

    cmd.arg("--");
    cmd.arg("-o");
    cmd.arg(outfile);
    cmd.arg("-Zunstable-options");
    cmd.arg("--pretty=expanded");
}

fn which(cmd: &[&str]) -> Option<OsString> {
    if env::args_os().any(|arg| arg == *"--help") {
        return None;
    }

    if let Some(which) = env::var_os(&cmd[0].to_uppercase()) {
        return if which.is_empty() { None } else { Some(which) };
    }

    let spawn = Command::new(cmd[0])
        .args(&cmd[1..])
        .stdin(Stdio::null())
        .stdout(Stdio::null())
        .stderr(Stdio::null())
        .spawn();
    let mut child = match spawn {
        Ok(child) => child,
        Err(_) => {
            return None;
        }
    };

    let exit = match child.wait() {
        Ok(exit) => exit,
        Err(_) => {
            return None;
        }
    };

    if exit.success() {
        Some(cmd[0].into())
    } else {
        None
    }
}

fn filter_err(cmd: &mut Command, ignore: fn(&str) -> bool) -> io::Result<i32> {
    let mut child = cmd.stderr(Stdio::piped()).spawn()?;
    let mut stderr = io::BufReader::new(child.stderr.take().unwrap());
    let mut line = String::new();
    while let Ok(n) = stderr.read_line(&mut line) {
        if n == 0 {
            break;
        }
        if !ignore(&line) {
            let _ = write!(&mut io::stderr(), "{}", line);
        }
        line.clear();
    }
    let code = child.wait()?.code().unwrap_or(1);
    Ok(code)
}

fn ignore_cargo_err(line: &str) -> bool {
    if line.trim().is_empty() {
        return true;
    }

    let blacklist = [
        "ignoring specified output filename because multiple outputs were \
         requested",
        "ignoring specified output filename for 'link' output because multiple \
         outputs were requested",
        "ignoring --out-dir flag due to -o flag",
        "ignoring -C extra-filename flag due to -o flag",
        "due to multiple output types requested, the explicitly specified \
         output file name will be adapted for each output type",
    ];
    for s in &blacklist {
        if line.contains(s) {
            return true;
        }
    }

    false
}
<|endoftext|>


<commit_before>extern crate mio;

use mio::*;
use mio::net::{SockAddr};
use mio::net::tcp::{TcpSocket, TcpAcceptor};
use mio::buf::{ByteBuf, MutByteBuf, SliceBuf};
use mio::util::Slab;

fn main() {
    const SERVER: Token = Token(1);
    let addr = SockAddr::parse("127.0.0.1:13265").unwrap();
    let server = TcpSocket::v4().unwrap()
        .bind(&addr).unwrap()
        .listen(256).unwrap();

    let mut event_loop = EventLoop::<(), ()>::new().unwrap();
    event_loop.register(&server, SERVER).unwrap();

    struct TcpHandler {
        conn: Option<TcpSocket>,
        sock: TcpAcceptor
    };

    impl TcpHandler {
        fn accept(&mut self, event_loop: &mut EventLoop<(), ()>) {
            let conn = self.sock.accept();
            let sock = conn.unwrap().unwrap();
            let tok = Token(2);
            self.conn = Some(sock);
            match self.conn {
                Some(ref c) => { event_loop.register_opt(c, tok, Interest::readable(), PollOpt::edge() | PollOpt::oneshot()); }
                None => { }
            }
        }
    }

    impl Handler<(), ()> for TcpHandler {
        fn readable(&mut self, event_loop: &mut EventLoop<(), ()>, token: Token, _: ReadHint) {
            match token {
                SERVER => {
                    self.accept(event_loop);
                }
                tok => {
                    println!("tok: {}", tok.as_usize());
                    let mut read_buf = ByteBuf::mut_with_capacity(2048);
                    let mut interest = Interest::readable();
                    match self.conn {
                        Some(ref c) => {
                            match c.read(&mut read_buf) {
                                Ok(NonBlock::WouldBlock) => {
                                    panic!("We just got readable, but were unable to read from the socket?");
                                }
                                Ok(NonBlock::Ready(r)) => {
                                    let mut buf = read_buf.flip();
                                    let mut sl = [0; 2048];
                                    buf.read_slice(&mut sl);
                                    print!("{}", String::from_utf8(sl.to_vec()).unwrap());
                                    // self.interest.remove(Interest::readable());
                                    // self.interest.insert(Interest::writable());
                                }
                                Err(e) => {
                                    event_loop.shutdown();
                                    // println!("not implemented; client err={:?}", e);
                                    // interest = Interest::hup();
                                    // self.interest.remove(Interest::readable());
                                }
                            }
                            event_loop.reregister(c, tok, interest, PollOpt::edge() | PollOpt::oneshot());
                        },
                        None => { }
                    }
                }
            }
        }
    }
    let mut tcp_server = TcpHandler {
        conn: None,
        sock: server
    };
    let _ = event_loop.run(&mut tcp_server);
}
<commit_msg>slab no longer used, can remove `use`<commit_after>extern crate mio;

use mio::*;
use mio::net::{SockAddr};
use mio::net::tcp::{TcpSocket, TcpAcceptor};
use mio::buf::{ByteBuf, MutByteBuf, SliceBuf};

fn main() {
    const SERVER: Token = Token(1);
    let addr = SockAddr::parse("127.0.0.1:13265").unwrap();
    let server = TcpSocket::v4().unwrap()
        .bind(&addr).unwrap()
        .listen(256).unwrap();

    let mut event_loop = EventLoop::<(), ()>::new().unwrap();
    event_loop.register(&server, SERVER).unwrap();

    struct TcpHandler {
        conn: Option<TcpSocket>,
        sock: TcpAcceptor
    };

    impl TcpHandler {
        fn accept(&mut self, event_loop: &mut EventLoop<(), ()>) {
            let conn = self.sock.accept();
            let sock = conn.unwrap().unwrap();
            let tok = Token(2);
            self.conn = Some(sock);
            match self.conn {
                Some(ref c) => { event_loop.register_opt(c, tok, Interest::readable(), PollOpt::edge() | PollOpt::oneshot()); }
                None => { }
            }
        }
    }

    impl Handler<(), ()> for TcpHandler {
        fn readable(&mut self, event_loop: &mut EventLoop<(), ()>, token: Token, _: ReadHint) {
            match token {
                SERVER => {
                    self.accept(event_loop);
                }
                tok => {
                    println!("tok: {}", tok.as_usize());
                    let mut read_buf = ByteBuf::mut_with_capacity(2048);
                    let mut interest = Interest::readable();
                    match self.conn {
                        Some(ref c) => {
                            match c.read(&mut read_buf) {
                                Ok(NonBlock::WouldBlock) => {
                                    panic!("We just got readable, but were unable to read from the socket?");
                                }
                                Ok(NonBlock::Ready(r)) => {
                                    let mut buf = read_buf.flip();
                                    let mut sl = [0; 2048];
                                    buf.read_slice(&mut sl);
                                    print!("{}", String::from_utf8(sl.to_vec()).unwrap());
                                    // self.interest.remove(Interest::readable());
                                    // self.interest.insert(Interest::writable());
                                }
                                Err(e) => {
                                    event_loop.shutdown();
                                    // println!("not implemented; client err={:?}", e);
                                    // interest = Interest::hup();
                                    // self.interest.remove(Interest::readable());
                                }
                            }
                            event_loop.reregister(c, tok, interest, PollOpt::edge() | PollOpt::oneshot());
                        },
                        None => { }
                    }
                }
            }
        }
    }
    let mut tcp_server = TcpHandler {
        conn: None,
        sock: server
    };
    let _ = event_loop.run(&mut tcp_server);
}
<|endoftext|>