<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::{HashMap, HashSet};
use std::env;
use std::fs;
use std::fs::PathExt;
use std::io::Write;
use std::old_io::timer;
use std::ops::Deref;
use std::time::duration::Duration;

mod builders;
mod native;
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();

    // 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 quit_app = false;
    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(utils::WINDOW_WIDTH, utils::WINDOW_HEIGHT);
    window.connect(gtk::signals::DeleteEvent::new(&mut |_| {
        native::send_message(write_fd, "qall!");
        native::close_fd(read_fd);
        native::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 = [ffi::glib::g_type_string, ffi::glib::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_pty(pty);
    editor_pane.watch_child(pid);
    editor_pane.set_size_request(-1, (utils::EDITOR_HEIGHT_PCT * (utils::WINDOW_HEIGHT as f32)) as i32);

    let mut build_pane = gtk::Stack::new().unwrap();

    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(),
        builders: HashMap::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

    let cmd = "au BufEnter * call rpcnotify(1, 'bufenter', fnamemodify(bufname(''), ':p'))";
    native::send_message(write_fd, cmd);

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

    native::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) = native::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);
            ::builders::show_builder(&mut state, &mut build_pane);
        }

        if quit_app {
            break;
        }

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

fn main() {
    // create data dir
    let home_dir = ::utils::get_home_dir();
    let data_dir = home_dir.deref().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.deref().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 = native::new_pipe(); // to nvim from gui
    let gui_nvim = native::new_pipe(); // to gui from nvim

    // split into two processes
    let pid = native::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>Update for latest version of neovim-rs<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::{HashMap, HashSet};
use std::env;
use std::fs;
use std::fs::PathExt;
use std::io::Write;
use std::old_io::timer;
use std::ops::Deref;
use std::time::duration::Duration;

mod builders;
mod native;
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();

    // 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 quit_app = false;
    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(utils::WINDOW_WIDTH, utils::WINDOW_HEIGHT);
    window.connect(gtk::signals::DeleteEvent::new(&mut |_| {
        native::send_message(write_fd, "qall!");
        native::close_fd(read_fd);
        native::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 = [ffi::glib::g_type_string, ffi::glib::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_pty(pty);
    editor_pane.watch_child(pid);
    editor_pane.set_size_request(-1, (utils::EDITOR_HEIGHT_PCT * (utils::WINDOW_HEIGHT as f32)) as i32);

    let mut build_pane = gtk::Stack::new().unwrap();

    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(),
        builders: HashMap::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

    let cmd = "au BufEnter * call rpcnotify(1, 'bufenter', fnamemodify(bufname(''), ':p'))";
    native::send_message(write_fd, cmd);

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

    native::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) = native::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);
            ::builders::show_builder(&mut state, &mut build_pane);
        }

        if quit_app {
            break;
        }

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

fn main() {
    // create data dir
    let home_dir = ::utils::get_home_dir();
    let data_dir = home_dir.deref().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.deref().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_str : Vec<String> = env::args().collect();
    if let Some(path_str) = config_file.to_str() {
        args_vec_str.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_str.retain(|arg| arg.as_slice() != ::utils::NO_WINDOW_FLAG);
        let args_vec_slice : Vec<&str> = args_vec_str.iter().map(|s| s.as_slice()).collect();
        neovim::main_setup(args_vec_slice.as_slice());
        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 = native::new_pipe(); // to nvim from gui
    let gui_nvim = native::new_pipe(); // to gui from nvim

    // split into two processes
    let pid = native::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
        let args_vec_slice : Vec<&str> = args_vec_str.iter().map(|s| s.as_slice()).collect();
        neovim::main_setup(args_vec_slice.as_slice());
        neovim::channel_from_fds(nvim_gui[0], gui_nvim[1]);
        neovim::main_loop();
    }
}
<|endoftext|>


<commit_before>#![allow(dead_code)]

#[macro_use]
extern crate quick_error;
extern crate regex;
extern crate toml;
extern crate semver;
extern crate clap;

use std::process::exit;

use clap::{App, ArgMatches, SubCommand};
use semver::Identifier;

mod config;
mod error;
mod cmd;
mod git;
mod cargo;

fn execute(args: &ArgMatches) -> Result<i32, error::FatalError> {
    let cargo_file = try!(config::parse_cargo_config());

    // step -1
    if let Some(invalid_keys) = config::verify_release_config(&cargo_file) {
        for i in invalid_keys {
            println!("Unknown config key \"{}\" found for [package.metadata.release]",
                     i);
        }
        return Ok(109);
    }

    let dry_run = args.occurrences_of("dry-run") > 0;
    let sign = args.occurrences_of("sign") > 0 ||
               config::get_release_config(&cargo_file, config::SIGN_COMMIT)
                   .and_then(|f| f.as_bool())
                   .unwrap_or(false);
    let upload_doc = args.occurrences_of("upload-doc") > 0 ||
                     config::get_release_config(&cargo_file, config::UPLOAD_DOC)
                         .and_then(|f| f.as_bool())
                         .unwrap_or(false);
    let git_remote = args.value_of("push-remote")
                         .or_else(|| {
                             config::get_release_config(&cargo_file, config::PUSH_REMOTE)
                                 .and_then(|f| f.as_str())
                         })
                         .unwrap_or("origin");
    let doc_branch = args.value_of("doc-branch")
                         .or_else(|| {
                             config::get_release_config(&cargo_file, config::DOC_BRANCH)
                                 .and_then(|f| f.as_str())
                         })
                         .unwrap_or("gh-pages");
    let skip_push = args.occurrences_of("skip-push") > 0 ||
                    config::get_release_config(&cargo_file, config::DISABLE_PUSH)
                        .and_then(|f| f.as_bool())
                        .unwrap_or(false);

    let pre_release_commit_msg = config::get_release_config(&cargo_file,
                                                            config::PRE_RELEASE_COMMIT_MESSAGE)
                                     .and_then(|f| f.as_str())
                                     .unwrap_or("(cargo-release) version {}");
    let pro_release_commit_msg =
        config::get_release_config(&cargo_file, config::PRO_RELEASE_COMMIT_MESSAGE)
            .and_then(|f| f.as_str())
            .unwrap_or("(cargo-release) start next development iteration {}");
    let tag_msg = config::get_release_config(&cargo_file, config::TAG_MESSAGE)
                      .and_then(|f| f.as_str())
                      .unwrap_or("(cargo-release) {} version {}");
    let doc_commit_msg = config::get_release_config(&cargo_file, config::DOC_COMMIT_MESSAGE)
                             .and_then(|f| f.as_str())
                             .unwrap_or("(cargo-release) generate docs");

    // STEP 0: Check if working directory is clean
    if !try!(git::status()) {
        println!("Uncommitted changes detected, please commit before release");
        if !dry_run {
            return Ok(101);
        }
    }

    // STEP 1: Read version from Cargo.toml and remove
    let mut version = cargo_file.get("package")
                                .and_then(|f| f.as_table())
                                .and_then(|f| f.get("version"))
                                .and_then(|f| f.as_str())
                                .and_then(|f| config::parse_version(f).ok())
                                .unwrap();

    // STEP 2: update current version, save and commit
    let mut need_commit = false;
    match args.value_of("level") {
        Some(level) => {
            match level {
                "major" => {
                    version.increment_major();
                    need_commit = true;
                }
                "minor" => {
                    version.increment_minor();
                    need_commit = true
                }
                "patch" => {
                    if !version.is_prerelease() {
                        version.increment_patch();
                    } else {
                        version.pre.clear();
                    }
                    need_commit = true
                }
                _ => {
                    panic!("Invalid level: {}", level);
                }
            }
        }
        None => {
            if version.is_prerelease() {
                version.pre.clear();
                need_commit = true;
            }
        }
    }

    if need_commit {
        let new_version_string = version.to_string();
        if !dry_run {
            try!(config::rewrite_cargo_version(&new_version_string));
        }

        let commit_msg = String::from(pre_release_commit_msg)
                             .replace("{{version}}", &new_version_string);
        if !try!(git::commit_all(".", &commit_msg, sign, dry_run)) {
            // commit failed, abort release
            return Ok(102);
        }
    }

    // STEP 3: cargo publish
    if !try!(cargo::publish(dry_run)) {
        return Ok(103);
    }

    // STEP 4: upload doc
    if upload_doc {
        println!("Building and exporting docs.");
        try!(cargo::doc(dry_run));

        let doc_path = "target/doc/";

        try!(git::init(doc_path, dry_run));
        try!(git::add_all(doc_path, dry_run));
        try!(git::commit_all(doc_path, doc_commit_msg, sign, dry_run));
        let default_remote = try!(git::origin_url());

        let mut refspec = String::from("master:");
        refspec.push_str(doc_branch);

        try!(git::force_push(doc_path, default_remote.trim(), &refspec, dry_run));
    }

    // STEP 5: Tag
    let root = try!(git::top_level());
    let rel_path = try!(cmd::relative_path_for(&root));
    let tag_prefix = args.value_of("tag-prefix")
                         .map(|t| t.to_owned())
                         .or(rel_path.as_ref().map(|t| format!("{}-", t)));

    let current_version = version.to_string();
    let tag_name = tag_prefix.as_ref().map_or_else(|| current_version.clone(),
                                                   |x| format!("{}{}", x, current_version));

    let tag_message = String::from(tag_msg)
                          .replace("{{module}}", &rel_path.unwrap_or("".to_owned()))
                          .replace("{{version}}", &current_version);

    if !try!(git::tag(&tag_name, &tag_message, sign, dry_run)) {
        // tag failed, abort release
        return Ok(104);
    }

    // STEP 6: bump version
    version.increment_patch();
    version.pre.push(Identifier::AlphaNumeric("pre".to_owned()));
    println!("Starting next development iteration {}", version);
    let updated_version_string = version.to_string();
    if !dry_run {
        try!(config::rewrite_cargo_version(&updated_version_string));
    }
    let commit_msg = String::from(pro_release_commit_msg)
                         .replace("{{version}}", &updated_version_string);

    if !try!(git::commit_all(".", &commit_msg, sign, dry_run)) {
        return Ok(105);
    }

    // STEP 7: git push
    if !skip_push {
        if !try!(git::push(git_remote, dry_run)) {
            return Ok(106);
        }
    }

    Ok(0)
}

static USAGE: &'static str = "-l, --level=[level] 'Release level: bumpping major|minor|patch version on release or removing prerelease extensions by default'
                             [sign]... --sign 'Sign git commit and tag'
                             [dry-run]... --dry-run 'Do not actually change anything.'
                             [upload-doc]... --upload-doc 'Upload rust document to gh-pages branch'
                             --push-remote=[push-remote] 'Git remote to push'
                             [skip-push]... --skip-push 'Do not run git push in the last step'
                             --doc-branch=[doc-branch] 'Git branch to push documentation on'
                             --tag-prefix=[tag-prefix] 'Prefix of git tag, note that this will override default prefix based on sub-directory ";

fn main() {
    let matches =
        App::new("cargo")
            .subcommand(SubCommand::with_name("release")
                            .version(env!("CARGO_PKG_VERSION"))
                            .author("Ning Sun <sunng@about.me>")
                            .about("Cargo subcommand for you to smooth your release process.")
                            .args_from_usage(USAGE))
            .get_matches();

    if let Some(ref release_matches) = matches.subcommand_matches("release") {
        match execute(release_matches) {
            Ok(code) => exit(code),
            Err(e) => {
                println!("Fatal: {}", e);
                exit(128);
            }
        }
    }
}
<commit_msg>(feat) update tag message template<commit_after>#![allow(dead_code)]

#[macro_use]
extern crate quick_error;
extern crate regex;
extern crate toml;
extern crate semver;
extern crate clap;

use std::process::exit;

use clap::{App, ArgMatches, SubCommand};
use semver::Identifier;

mod config;
mod error;
mod cmd;
mod git;
mod cargo;

fn execute(args: &ArgMatches) -> Result<i32, error::FatalError> {
    let cargo_file = try!(config::parse_cargo_config());

    // step -1
    if let Some(invalid_keys) = config::verify_release_config(&cargo_file) {
        for i in invalid_keys {
            println!("Unknown config key \"{}\" found for [package.metadata.release]",
                     i);
        }
        return Ok(109);
    }

    let dry_run = args.occurrences_of("dry-run") > 0;
    let sign = args.occurrences_of("sign") > 0 ||
               config::get_release_config(&cargo_file, config::SIGN_COMMIT)
                   .and_then(|f| f.as_bool())
                   .unwrap_or(false);
    let upload_doc = args.occurrences_of("upload-doc") > 0 ||
                     config::get_release_config(&cargo_file, config::UPLOAD_DOC)
                         .and_then(|f| f.as_bool())
                         .unwrap_or(false);
    let git_remote = args.value_of("push-remote")
                         .or_else(|| {
                             config::get_release_config(&cargo_file, config::PUSH_REMOTE)
                                 .and_then(|f| f.as_str())
                         })
                         .unwrap_or("origin");
    let doc_branch = args.value_of("doc-branch")
                         .or_else(|| {
                             config::get_release_config(&cargo_file, config::DOC_BRANCH)
                                 .and_then(|f| f.as_str())
                         })
                         .unwrap_or("gh-pages");
    let skip_push = args.occurrences_of("skip-push") > 0 ||
                    config::get_release_config(&cargo_file, config::DISABLE_PUSH)
                        .and_then(|f| f.as_bool())
                        .unwrap_or(false);

    let pre_release_commit_msg = config::get_release_config(&cargo_file,
                                                            config::PRE_RELEASE_COMMIT_MESSAGE)
                                     .and_then(|f| f.as_str())
                                     .unwrap_or("(cargo-release) version {}");
    let pro_release_commit_msg =
        config::get_release_config(&cargo_file, config::PRO_RELEASE_COMMIT_MESSAGE)
            .and_then(|f| f.as_str())
            .unwrap_or("(cargo-release) start next development iteration {}");
    let tag_msg = config::get_release_config(&cargo_file, config::TAG_MESSAGE)
                      .and_then(|f| f.as_str())
                      .unwrap_or("(cargo-release) {} version {}");
    let doc_commit_msg = config::get_release_config(&cargo_file, config::DOC_COMMIT_MESSAGE)
                             .and_then(|f| f.as_str())
                             .unwrap_or("(cargo-release) generate docs");

    // STEP 0: Check if working directory is clean
    if !try!(git::status()) {
        println!("Uncommitted changes detected, please commit before release");
        if !dry_run {
            return Ok(101);
        }
    }

    // STEP 1: Read version from Cargo.toml and remove
    let mut version = cargo_file.get("package")
                                .and_then(|f| f.as_table())
                                .and_then(|f| f.get("version"))
                                .and_then(|f| f.as_str())
                                .and_then(|f| config::parse_version(f).ok())
                                .unwrap();

    // STEP 2: update current version, save and commit
    let mut need_commit = false;
    match args.value_of("level") {
        Some(level) => {
            match level {
                "major" => {
                    version.increment_major();
                    need_commit = true;
                }
                "minor" => {
                    version.increment_minor();
                    need_commit = true
                }
                "patch" => {
                    if !version.is_prerelease() {
                        version.increment_patch();
                    } else {
                        version.pre.clear();
                    }
                    need_commit = true
                }
                _ => {
                    panic!("Invalid level: {}", level);
                }
            }
        }
        None => {
            if version.is_prerelease() {
                version.pre.clear();
                need_commit = true;
            }
        }
    }

    if need_commit {
        let new_version_string = version.to_string();
        if !dry_run {
            try!(config::rewrite_cargo_version(&new_version_string));
        }

        let commit_msg = String::from(pre_release_commit_msg)
                             .replace("{{version}}", &new_version_string);
        if !try!(git::commit_all(".", &commit_msg, sign, dry_run)) {
            // commit failed, abort release
            return Ok(102);
        }
    }

    // STEP 3: cargo publish
    if !try!(cargo::publish(dry_run)) {
        return Ok(103);
    }

    // STEP 4: upload doc
    if upload_doc {
        println!("Building and exporting docs.");
        try!(cargo::doc(dry_run));

        let doc_path = "target/doc/";

        try!(git::init(doc_path, dry_run));
        try!(git::add_all(doc_path, dry_run));
        try!(git::commit_all(doc_path, doc_commit_msg, sign, dry_run));
        let default_remote = try!(git::origin_url());

        let mut refspec = String::from("master:");
        refspec.push_str(doc_branch);

        try!(git::force_push(doc_path, default_remote.trim(), &refspec, dry_run));
    }

    // STEP 5: Tag
    let root = try!(git::top_level());
    let rel_path = try!(cmd::relative_path_for(&root));
    let tag_prefix = args.value_of("tag-prefix")
                         .map(|t| t.to_owned())
                         .or(rel_path.as_ref().map(|t| format!("{}-", t)));

    let current_version = version.to_string();
    let tag_name = tag_prefix.as_ref().map_or_else(|| current_version.clone(),
                                                   |x| format!("{}{}", x, current_version));

    let tag_message = String::from(tag_msg)
                          .replace("{{prefix}}", tag_prefix.as_ref().unwrap_or(&"".to_owned()))
                          .replace("{{version}}", &current_version);

    if !try!(git::tag(&tag_name, &tag_message, sign, dry_run)) {
        // tag failed, abort release
        return Ok(104);
    }

    // STEP 6: bump version
    version.increment_patch();
    version.pre.push(Identifier::AlphaNumeric("pre".to_owned()));
    println!("Starting next development iteration {}", version);
    let updated_version_string = version.to_string();
    if !dry_run {
        try!(config::rewrite_cargo_version(&updated_version_string));
    }
    let commit_msg = String::from(pro_release_commit_msg)
                         .replace("{{version}}", &updated_version_string);

    if !try!(git::commit_all(".", &commit_msg, sign, dry_run)) {
        return Ok(105);
    }

    // STEP 7: git push
    if !skip_push {
        if !try!(git::push(git_remote, dry_run)) {
            return Ok(106);
        }
    }

    Ok(0)
}

static USAGE: &'static str = "-l, --level=[level] 'Release level: bumpping major|minor|patch version on release or removing prerelease extensions by default'
                             [sign]... --sign 'Sign git commit and tag'
                             [dry-run]... --dry-run 'Do not actually change anything.'
                             [upload-doc]... --upload-doc 'Upload rust document to gh-pages branch'
                             --push-remote=[push-remote] 'Git remote to push'
                             [skip-push]... --skip-push 'Do not run git push in the last step'
                             --doc-branch=[doc-branch] 'Git branch to push documentation on'
                             --tag-prefix=[tag-prefix] 'Prefix of git tag, note that this will override default prefix based on sub-directory ";

fn main() {
    let matches =
        App::new("cargo")
            .subcommand(SubCommand::with_name("release")
                            .version(env!("CARGO_PKG_VERSION"))
                            .author("Ning Sun <sunng@about.me>")
                            .about("Cargo subcommand for you to smooth your release process.")
                            .args_from_usage(USAGE))
            .get_matches();

    if let Some(ref release_matches) = matches.subcommand_matches("release") {
        match execute(release_matches) {
            Ok(code) => exit(code),
            Err(e) => {
                println!("Fatal: {}", e);
                exit(128);
            }
        }
    }
}
<|endoftext|>


<commit_before>#[crate_id = "rboy"];

extern crate native;
extern crate green;
extern crate extra;
extern crate getopts;
extern crate sync;
extern crate time;
extern crate sdl;

use cpu::CPU;
use sync::DuplexStream;
use sync::RWArc;

mod register;
mod mbc;
mod mmu;
mod cpu;
mod serial;
mod timer;
mod keypad;
mod gpu;
mod sound;
mod gbmode;

static SCALE: int = 3;

#[start]
fn start(argc: int, argv: **u8) -> int { green::start(argc, argv, main) }

fn main() {
	let args: ~[~str] = std::os::args();
	let program = args[0].clone() + " <filename>";

	let opts = ~[ getopts::optflag("s", "serial", "Output serial to stdout"), getopts::optflag("c", "classic", "Force Classic mode") ];
	let matches = match getopts::getopts(args.tail(), opts) {
		Ok(m) => { m }
		Err(f) => { println!("{}", f.to_err_msg()); return }
	};

	let filename: ~str = if !matches.free.is_empty() {
		matches.free[0].clone()
	} else {
		println!("{}", getopts::usage(program, opts));
		return;
	};

	sdl::init([sdl::InitVideo]);
	sdl::wm::set_caption("RBoy - A gameboy in Rust", "rboy");
	let screen = match sdl::video::set_video_mode(160*SCALE, 144*SCALE, 32, [sdl::video::HWSurface], [sdl::video::DoubleBuf]) {
		Ok(screen) => screen,
		Err(err) => fail!("failed to open screen: {}", err),
	};

	let (sdlstream, cpustream) = DuplexStream::new();
	let rawscreen = ~[0x00u8,.. 160*144*3];
	let arc = RWArc::new(rawscreen);
	let arc2 = arc.clone();
	native::task::spawn(proc() cpuloop(&cpustream, arc2, filename, &matches));

	let mut timer = std::io::timer::Timer::new().unwrap();
	let periodic = timer.periodic(8);

	'main : loop {
		periodic.recv();
		match sdlstream.try_recv() {
			std::comm::Disconnected => { break 'main },
			std::comm::Data(_) => recalculate_screen(screen, &arc),
			std::comm::Empty => {},
		}
		'event : loop {
			match sdl::event::poll_event() {
				sdl::event::QuitEvent => break 'main,
				sdl::event::NoEvent => break 'event,
				sdl::event::KeyEvent(sdl::event::EscapeKey, _, _, _)
					=> break 'main,
				sdl::event::KeyEvent(sdl::event::LShiftKey, true, _, _)
					=> sdlstream.send(SpeedUp),
				sdl::event::KeyEvent(sdl::event::LShiftKey, false, _, _)
					=> sdlstream.send(SlowDown),
				sdl::event::KeyEvent(sdlkey, true, _, _) => {
					match sdl_to_keypad(sdlkey) {
						Some(key) => sdlstream.send(KeyDown(key)),
						None => {},
					}
				},
				sdl::event::KeyEvent(sdlkey, false, _, _) => {
					match sdl_to_keypad(sdlkey) {
						Some(key) => sdlstream.send(KeyUp(key)),
						None => {},
					}
				},
				_ => {}
			}
		}
	}
}

fn sdl_to_keypad(key: sdl::event::Key) -> Option<keypad::KeypadKey> {
	match key {
		sdl::event::ZKey => Some(keypad::A),
		sdl::event::XKey => Some(keypad::B),
		sdl::event::UpKey => Some(keypad::Up),
		sdl::event::DownKey => Some(keypad::Down),
		sdl::event::LeftKey => Some(keypad::Left),
		sdl::event::RightKey => Some(keypad::Right),
		sdl::event::SpaceKey => Some(keypad::Select),
		sdl::event::ReturnKey => Some(keypad::Start),
		_ => None,
	}
}

fn recalculate_screen(screen: &sdl::video::Surface, arc: &RWArc<~[u8]>) {
	arc.read(|data|
		for y in range(0, 144) {
			for x in range(0, 160) {
				screen.fill_rect(
					Some(sdl::Rect { x: (x*SCALE) as i16, y: (y*SCALE) as i16, w: SCALE as u16, h: SCALE as u16 }),
					sdl::video::RGB(data[y*160*3 + x*3 + 0],
					                data[y*160*3 + x*3 + 1],
					                data[y*160*3 + x*3 + 2])
				);
			}
		}
	);
	screen.flip();
}

enum GBEvent {
	KeyUp(keypad::KeypadKey),
	KeyDown(keypad::KeypadKey),
	SpeedUp,
	SlowDown,
}

fn cpuloop(channel: &DuplexStream<uint, GBEvent>, arc: RWArc<~[u8]>, filename: ~str, matches: &getopts::Matches) {
	let mut c = match matches.opt_present("classic") {
		true => CPU::new(filename),
		false => CPU::new_cgb(filename),
	};
	c.mmu.serial.tostdout = matches.opt_present("serial");

	let mut timer = std::io::timer::Timer::new().unwrap();
	let mut periodic = timer.periodic(8);

	let waitticks = (4194.304 * 4.0) as uint;

	let mut ticks = 0;
	loop {
		while ticks < waitticks {
			ticks += c.cycle();
			if c.mmu.gpu.updated {
				c.mmu.gpu.updated = false;
				arc.write(|data|
					for i in range(0, data.len()) { data[i] = c.mmu.gpu.data[i]; }
				);
				channel.try_send(0);
			}
		}
		ticks -= waitticks;
		periodic.recv();

		match channel.try_recv() {
			std::comm::Data(event) => match event {
				KeyUp(key) => c.mmu.keypad.keyup(key),
				KeyDown(key) => c.mmu.keypad.keydown(key),
				SpeedUp => periodic = timer.periodic(1),
				SlowDown => periodic = timer.periodic(8),
			},
			std::comm::Empty => {},
			std::comm::Disconnected => { break },
		};
	}
}
<commit_msg>Fixed change in DuplexStream<commit_after>#[crate_id = "rboy"];

extern crate native;
extern crate green;
extern crate extra;
extern crate getopts;
extern crate sync;
extern crate time;
extern crate sdl;

use cpu::CPU;
use sync::DuplexStream;
use sync::RWArc;

mod register;
mod mbc;
mod mmu;
mod cpu;
mod serial;
mod timer;
mod keypad;
mod gpu;
mod sound;
mod gbmode;

static SCALE: int = 3;

#[start]
fn start(argc: int, argv: **u8) -> int { green::start(argc, argv, main) }

fn main() {
	let args: ~[~str] = std::os::args();
	let program = args[0].clone() + " <filename>";

	let opts = ~[ getopts::optflag("s", "serial", "Output serial to stdout"), getopts::optflag("c", "classic", "Force Classic mode") ];
	let matches = match getopts::getopts(args.tail(), opts) {
		Ok(m) => { m }
		Err(f) => { println!("{}", f.to_err_msg()); return }
	};

	let filename: ~str = if !matches.free.is_empty() {
		matches.free[0].clone()
	} else {
		println!("{}", getopts::usage(program, opts));
		return;
	};

	sdl::init([sdl::InitVideo]);
	sdl::wm::set_caption("RBoy - A gameboy in Rust", "rboy");
	let screen = match sdl::video::set_video_mode(160*SCALE, 144*SCALE, 32, [sdl::video::HWSurface], [sdl::video::DoubleBuf]) {
		Ok(screen) => screen,
		Err(err) => fail!("failed to open screen: {}", err),
	};

	let (sdlstream, cpustream) = sync::duplex();
	let rawscreen = ~[0x00u8,.. 160*144*3];
	let arc = RWArc::new(rawscreen);
	let arc2 = arc.clone();
	native::task::spawn(proc() cpuloop(&cpustream, arc2, filename, &matches));

	let mut timer = std::io::timer::Timer::new().unwrap();
	let periodic = timer.periodic(8);

	'main : loop {
		periodic.recv();
		match sdlstream.try_recv() {
			std::comm::Disconnected => { break 'main },
			std::comm::Data(_) => recalculate_screen(screen, &arc),
			std::comm::Empty => {},
		}
		'event : loop {
			match sdl::event::poll_event() {
				sdl::event::QuitEvent => break 'main,
				sdl::event::NoEvent => break 'event,
				sdl::event::KeyEvent(sdl::event::EscapeKey, _, _, _)
					=> break 'main,
				sdl::event::KeyEvent(sdl::event::LShiftKey, true, _, _)
					=> sdlstream.send(SpeedUp),
				sdl::event::KeyEvent(sdl::event::LShiftKey, false, _, _)
					=> sdlstream.send(SlowDown),
				sdl::event::KeyEvent(sdlkey, true, _, _) => {
					match sdl_to_keypad(sdlkey) {
						Some(key) => sdlstream.send(KeyDown(key)),
						None => {},
					}
				},
				sdl::event::KeyEvent(sdlkey, false, _, _) => {
					match sdl_to_keypad(sdlkey) {
						Some(key) => sdlstream.send(KeyUp(key)),
						None => {},
					}
				},
				_ => {}
			}
		}
	}
}

fn sdl_to_keypad(key: sdl::event::Key) -> Option<keypad::KeypadKey> {
	match key {
		sdl::event::ZKey => Some(keypad::A),
		sdl::event::XKey => Some(keypad::B),
		sdl::event::UpKey => Some(keypad::Up),
		sdl::event::DownKey => Some(keypad::Down),
		sdl::event::LeftKey => Some(keypad::Left),
		sdl::event::RightKey => Some(keypad::Right),
		sdl::event::SpaceKey => Some(keypad::Select),
		sdl::event::ReturnKey => Some(keypad::Start),
		_ => None,
	}
}

fn recalculate_screen(screen: &sdl::video::Surface, arc: &RWArc<~[u8]>) {
	arc.read(|data|
		for y in range(0, 144) {
			for x in range(0, 160) {
				screen.fill_rect(
					Some(sdl::Rect { x: (x*SCALE) as i16, y: (y*SCALE) as i16, w: SCALE as u16, h: SCALE as u16 }),
					sdl::video::RGB(data[y*160*3 + x*3 + 0],
					                data[y*160*3 + x*3 + 1],
					                data[y*160*3 + x*3 + 2])
				);
			}
		}
	);
	screen.flip();
}

enum GBEvent {
	KeyUp(keypad::KeypadKey),
	KeyDown(keypad::KeypadKey),
	SpeedUp,
	SlowDown,
}

fn cpuloop(channel: &DuplexStream<uint, GBEvent>, arc: RWArc<~[u8]>, filename: ~str, matches: &getopts::Matches) {
	let mut c = match matches.opt_present("classic") {
		true => CPU::new(filename),
		false => CPU::new_cgb(filename),
	};
	c.mmu.serial.tostdout = matches.opt_present("serial");

	let mut timer = std::io::timer::Timer::new().unwrap();
	let mut periodic = timer.periodic(8);

	let waitticks = (4194.304 * 4.0) as uint;

	let mut ticks = 0;
	loop {
		while ticks < waitticks {
			ticks += c.cycle();
			if c.mmu.gpu.updated {
				c.mmu.gpu.updated = false;
				arc.write(|data|
					for i in range(0, data.len()) { data[i] = c.mmu.gpu.data[i]; }
				);
				channel.try_send(0);
			}
		}
		ticks -= waitticks;
		periodic.recv();

		match channel.try_recv() {
			std::comm::Data(event) => match event {
				KeyUp(key) => c.mmu.keypad.keyup(key),
				KeyDown(key) => c.mmu.keypad.keydown(key),
				SpeedUp => periodic = timer.periodic(1),
				SlowDown => periodic = timer.periodic(8),
			},
			std::comm::Empty => {},
			std::comm::Disconnected => { break },
		};
	}
}
<|endoftext|>


<commit_before>extern crate html5ever;
extern crate html5ever_dom_sink;
extern crate hyper;
extern crate rustc_serialize;
extern crate tempfile;
extern crate tendril;
extern crate url;

mod json;

use std::fs::OpenOptions;
use std::io::Read;
use std::io::Write;
use std::process::Command;
use std::process::Stdio;
use std::str::FromStr;

use html5ever::Attribute;
use html5ever::tree_builder::interface::TreeSink;
use html5ever_dom_sink::common::NodeEnum;
use html5ever_dom_sink::rcdom::Handle;
use html5ever_dom_sink::rcdom::RcDom;
use hyper::Client;
use hyper::header::Connection;
use rustc_serialize::json::Json;
use tempfile::NamedTempFile;
use url::percent_encoding;

use json::JsonPathElement::{Key, Only};

// TODO: there are some places where I've handrolled try!() equivalents. Fix those.
// TODO: make sure I'm returning Results everywhere, and propagating errors correctly.
// TODO: randomize placeholder per-request

// TODO: Can I get rid of the repeated "Zachary_Taylor"s everywhere? Surely the MediaWiki API doesn't actually need that - I can't imagine revision IDs aren't unique across all pages.

fn get_revisions(page: &str, limit: i32, properties: Vec<&str>, start_id: Option<u64>) -> String {
    let client = Client::new();
    let mut res = client.get(
        &format!(
            "https://en.wikipedia.org/w/api.php?action=query&prop=revisions&titles={}&rvprop={}&rvlimit={}{}&format=json",
            page, properties.join("|"), limit,
            match start_id { Some(id) => format!("&rvstartid={}", id), None => "".to_string()}))
        .header(Connection::close())
        .send().unwrap();

    let mut body = String::new();
    res.read_to_string(&mut body).unwrap();

    body
}

fn parse_wikitext(wikitext: &str) -> String {
    let post_body = format!(
        "text={}", percent_encoding::percent_encode(
            wikitext.as_bytes(), percent_encoding::QUERY_ENCODE_SET));

    let client = Client::new();
    let mut res = client.post(
        "https://en.wikipedia.org/w/api.php?action=parse&format=json&prop=text&disablepp=&contentmodel=wikitext")
        .body(&post_body)
        .header(Connection::close())
        .send().unwrap();

    let mut body = String::new();
    res.read_to_string(&mut body).unwrap();
    body
}

// TODO: this name is terrible.
// Returns pairs of (revid, parentid)
fn get_revert_revision_ids(page: &str) -> Result<Vec<(u64, u64)>, String> {
    let json = Json::from_str(&get_revisions(page, 60, vec!["comment", "ids"], None)).unwrap();
    let revisions = try!(
        json::get_json_array(&json, &[Key("query"), Key("pages"), Only, Key("revisions")]));

    // Filter for revisions that mention vandalism, and that have revid and parentid (which they all
    // should, but merits checking).
    Ok(revisions.into_iter().filter(|revision| {
        match (json::get_json_string(revision, &[Key("comment")]),
               json::get_json_number(revision, &[Key("revid")]),
               json::get_json_number(revision, &[Key("parentid")])) {
            (Ok(ref comment), Ok(_), Ok(_)) => comment.contains("vandal"),
            _ => false,
        }})
       .map(|revision| {
           (json::get_json_number(revision, &[Key("revid")]).unwrap(),
            json::get_json_number(revision, &[Key("parentid")]).unwrap())
       }).collect())
}

fn get_latest_revision_id(page: &str) -> Result<u64, String> {
    let json = Json::from_str(&get_revisions(page, 1, vec!["ids"], None)).unwrap();
    json::get_json_number(
        &json, &[Key("query"), Key("pages"), Only, Key("revisions"), Only, Key("revid")])
}

fn get_revision_content(page: &str, id: u64) -> Result<String, String> {
    let json = Json::from_str(&get_revisions(page, 1, vec!["content"], Some(id))).unwrap();
    match json::get_json_string(
        &json, &[Key("query"), Key("pages"), Only, Key("revisions"), Only, Key("*")]) {
        Ok(content) => Ok(content.to_string()),
        Err(msg) => Err(msg),
    }
}

fn write_to_temp_file(contents: &str) -> NamedTempFile {
    let tempfile = NamedTempFile::new().unwrap();
    let mut file = OpenOptions::new().write(true).open(tempfile.path()).unwrap();
    file.write_all(contents.as_bytes()).unwrap();
    file.flush().unwrap();
    tempfile
}

// TODO: I'm not so sure these parameter names aren't terrible.
fn merge(old: &str, new1: &str, new2: &str) -> Option<String> {
    let old_tempfile = write_to_temp_file(old);
    let new1_tempfile = write_to_temp_file(new1);
    let new2_tempfile = write_to_temp_file(new2);

    let mut process = Command::new("diff3");
    process.arg("-m").args(&[new1_tempfile.path(), old_tempfile.path(), new2_tempfile.path()])
        .stdout(Stdio::piped()).stderr(Stdio::null());
    let output = process.output().unwrap();
    if output.status.success() {
        Some(String::from_utf8(output.stdout).unwrap())
    } else {
        None
    }
}

fn render(wikitext: &str) -> Result<String, String> {
    let html = parse_wikitext(wikitext);
    // TODO: check return value
    let json = Json::from_str(&html).unwrap();
    match json::get_json_string(&json, &[Key("parse"), Key("text"), Key("*")]) {
        Ok(contents) => Ok(contents.to_string()),
        Err(message) => Err(message),
    }
}

fn has_matching_id(attributes: &Vec<Attribute>, id: &str) -> bool {
    // TODO: could also do this with filter() and is_empty(). Not sure if that would be better.
    for attribute in attributes {
        // TODO: do I seriously have to construct a StrTendril here?
        // There has to be a better way.
        if attribute.name.local.as_slice() == "id" &&
            attribute.value == tendril::StrTendril::from_str(id).unwrap() {
                return true;
            }
    }
    return false;
}

fn find_node_by_id(handle: &Handle, id: &str) -> Result<Handle, String> {
    let node = handle.borrow();
    match node.node {
        NodeEnum::Element(_, ref attributes) if has_matching_id(attributes, id) => Ok(handle.clone()),
        _ => {
            for child in &node.children {
                match find_node_by_id(child, id) {
                    // TODO: this looks weird. Is this an abnormal way to do things?
                    Ok(node) => return Ok(node),
                    _ => continue,
                }
            }
            Err(format!("No node with ID {} found", id))
        },
    }
}

fn replace_node_with_placeholder(original_html: &str, div_id: &str, placeholder: &str) -> Result<String, String> {
    // TODO: check errors
    let html = tendril::StrTendril::from_str(original_html).unwrap();
    let mut dom: RcDom = html5ever::parse(html5ever::one_input(html), Default::default());

    let handle = try!(find_node_by_id(&dom.get_document(), div_id));
    let child_handles =
        (&handle.borrow().children).into_iter().map(|child| child.clone()).collect::<Vec<_>>();
    for child_handle in child_handles {
        dom.remove_from_parent(child_handle);
    }
    dom.append(handle,
               html5ever::tree_builder::interface::NodeOrText::AppendText(
                   tendril::StrTendril::from_str(placeholder).unwrap()));
    let mut serialized: Vec<u8> = vec![];
    html5ever::serialize::serialize(&mut serialized, &dom.document, Default::default());
    // TODO: error handling
    Ok(String::from_utf8(serialized).unwrap())
}

fn get_current_page(page: &str) -> String {
    let client = Client::new();
    let mut res = client.get(
        &format!("https://en.wikipedia.org/wiki/{}", page))
        .header(Connection::close())
        .send().unwrap();

    let mut body = String::new();
    res.read_to_string(&mut body).unwrap();

    body
}

fn main() {
    let latest_revid = get_latest_revision_id("Zachary_Taylor").unwrap();
    let mut accumulated_contents = get_revision_content("Zachary_Taylor", latest_revid).unwrap();
    let mut revisions = vec![];
    for (revert_revid, vandalism_revid) in get_revert_revision_ids("Zachary_Taylor").ok().unwrap() {
        let reverted_contents = get_revision_content("Zachary_Taylor", revert_revid).unwrap();
        let vandalized_contents = get_revision_content("Zachary_Taylor", vandalism_revid).unwrap();
        match merge(&reverted_contents, &vandalized_contents, &accumulated_contents) {
            Some(merged_contents) => {
                accumulated_contents = merged_contents;
                revisions.push(revert_revid);
            }
            None => (),
        }
    }

    let rendered_body = render(&accumulated_contents).unwrap();
    let current_page_contents = get_current_page("Zachary_Taylor");
    let page_contents_with_placeholder =
        replace_node_with_placeholder(
            &current_page_contents, "mw-content-text", "WMW_PLACEHOLDER_TEXT").unwrap();
    let final_page_contents = page_contents_with_placeholder.replace("WMW_PLACEHOLDER_TEXT", &rendered_body);

    println!("Restored vandalisms reverted in: {:?}", revisions);
    println!("{}", final_page_contents);
}

#[cfg(test)]
mod tests {
    use super::{merge, replace_node_with_placeholder};

    #[test]
    fn test_merge_clean() {
        let old = "First line.\n\nSecond line.\n";
        let new1 = "First line.\n\nSecond line changed.\n";
        let new2 = "First line changed.\n\nSecond line.\n";
        assert_eq!("First line changed.\n\nSecond line changed.\n", merge(old, new1, new2).unwrap());
    }

    #[test]
    fn test_merge_conflicting() {
        let old = "First line.\n\nSecond line.\n";
        let new1 = "First line.\n\nSecond line changed one way.\n";
        let new2 = "First line changed.\n\nSecond line changed a different way.\n";
        assert_eq!(None, merge(old, new1, new2));
    }

    #[test]
    fn test_merge_special_characters() {
        let old = "First line.\n\nSecond line.\n";
        let new1 = "First line.\n\nSecond line 𐅃.\n";
        let new2 = "First line さようなら.\n\nSecond line.\n";
        assert_eq!("First line さようなら.\n\nSecond line 𐅃.\n", merge(old, new1, new2).unwrap());
    }

    #[test]
    fn test_replace_html_content() {
        let original_html = "

original text

Other text

";
        let expected_html = "

replaced text

Other text

";
        let processed_html = replace_node_with_placeholder(original_html, "mw-content-text", "replaced text").unwrap();
        assert_eq!(expected_html, processed_html);
    }
}
<commit_msg>Extract a parse_wikitext() function from parse().<commit_after>extern crate html5ever;
extern crate html5ever_dom_sink;
extern crate hyper;
extern crate rustc_serialize;
extern crate tempfile;
extern crate tendril;
extern crate url;

mod json;

use std::fs::OpenOptions;
use std::io::Read;
use std::io::Write;
use std::process::Command;
use std::process::Stdio;
use std::str::FromStr;

use html5ever::Attribute;
use html5ever::tree_builder::interface::TreeSink;
use html5ever_dom_sink::common::NodeEnum;
use html5ever_dom_sink::rcdom::Handle;
use html5ever_dom_sink::rcdom::RcDom;
use hyper::Client;
use hyper::header::Connection;
use rustc_serialize::json::Json;
use tempfile::NamedTempFile;
use url::percent_encoding;

use json::JsonPathElement::{Key, Only};

// TODO: there are some places where I've handrolled try!() equivalents. Fix those.
// TODO: make sure I'm returning Results everywhere, and propagating errors correctly.
// TODO: randomize placeholder per-request

// TODO: Can I get rid of the repeated "Zachary_Taylor"s everywhere? Surely the MediaWiki API doesn't actually need that - I can't imagine revision IDs aren't unique across all pages.

// TODO: return a Result
//fn call_wikimedia_api(action: &str, prop: &str, Vec<(&str, &str)> parameters) -> String {
//    let client = Client::new();
//    let mut res = client.get(
//        &format!(
//            "https://en.wikipedia.org/w/api.php?action=query&prop=revisions&titles={}&rvprop={}&rvlimit={}{}&format=json",
//            page, properties.join("|"), limit,
//            match start_id { Some(id) => format!("&rvstartid={}", id), None => "".to_string()}))
//        .header(Connection::close())
//        .send().unwrap();
//
//    let mut body = String::new();
//    res.read_to_string(&mut body).unwrap();
//
//    body
//}

fn get_revisions(page: &str, limit: i32, properties: Vec<&str>, start_id: Option<u64>) -> String {
    let client = Client::new();
    let mut res = client.post(
        &format!(
            "https://en.wikipedia.org/w/api.php?action=query&prop=revisions&titles={}&rvprop={}&rvlimit={}{}&format=json",
            page, properties.join("|"), limit,
            match start_id { Some(id) => format!("&rvstartid={}", id), None => "".to_string()}))
        .header(Connection::close())
        .send().unwrap();

    let mut body = String::new();
    res.read_to_string(&mut body).unwrap();

    body
}

fn parse_wikitext(wikitext: &str) -> String {
    let post_body = format!(
        "text={}", percent_encoding::percent_encode(
            wikitext.as_bytes(), percent_encoding::QUERY_ENCODE_SET));

    let client = Client::new();
    let mut res = client.post(
        "https://en.wikipedia.org/w/api.php?action=parse&format=json&prop=text&disablepp=&contentmodel=wikitext")
        .body(&post_body)
        .header(Connection::close())
        .send().unwrap();

    let mut body = String::new();
    res.read_to_string(&mut body).unwrap();
    body
}

// TODO: this name is terrible.
// Returns pairs of (revid, parentid)
fn get_revert_revision_ids(page: &str) -> Result<Vec<(u64, u64)>, String> {
    let json = Json::from_str(&get_revisions(page, 60, vec!["comment", "ids"], None)).unwrap();
    let revisions = try!(
        json::get_json_array(&json, &[Key("query"), Key("pages"), Only, Key("revisions")]));

    // Filter for revisions that mention vandalism, and that have revid and parentid (which they all
    // should, but merits checking).
    Ok(revisions.into_iter().filter(|revision| {
        match (json::get_json_string(revision, &[Key("comment")]),
               json::get_json_number(revision, &[Key("revid")]),
               json::get_json_number(revision, &[Key("parentid")])) {
            (Ok(ref comment), Ok(_), Ok(_)) => comment.contains("vandal"),
            _ => false,
        }})
       .map(|revision| {
           (json::get_json_number(revision, &[Key("revid")]).unwrap(),
            json::get_json_number(revision, &[Key("parentid")]).unwrap())
       }).collect())
}

fn get_latest_revision_id(page: &str) -> Result<u64, String> {
    let json = Json::from_str(&get_revisions(page, 1, vec!["ids"], None)).unwrap();
    json::get_json_number(
        &json, &[Key("query"), Key("pages"), Only, Key("revisions"), Only, Key("revid")])
}

fn get_revision_content(page: &str, id: u64) -> Result<String, String> {
    let json = Json::from_str(&get_revisions(page, 1, vec!["content"], Some(id))).unwrap();
    match json::get_json_string(
        &json, &[Key("query"), Key("pages"), Only, Key("revisions"), Only, Key("*")]) {
        Ok(content) => Ok(content.to_string()),
        Err(msg) => Err(msg),
    }
}

fn write_to_temp_file(contents: &str) -> NamedTempFile {
    let tempfile = NamedTempFile::new().unwrap();
    let mut file = OpenOptions::new().write(true).open(tempfile.path()).unwrap();
    file.write_all(contents.as_bytes()).unwrap();
    file.flush().unwrap();
    tempfile
}

// TODO: I'm not so sure these parameter names aren't terrible.
fn merge(old: &str, new1: &str, new2: &str) -> Option<String> {
    let old_tempfile = write_to_temp_file(old);
    let new1_tempfile = write_to_temp_file(new1);
    let new2_tempfile = write_to_temp_file(new2);

    let mut process = Command::new("diff3");
    process.arg("-m").args(&[new1_tempfile.path(), old_tempfile.path(), new2_tempfile.path()])
        .stdout(Stdio::piped()).stderr(Stdio::null());
    let output = process.output().unwrap();
    if output.status.success() {
        Some(String::from_utf8(output.stdout).unwrap())
    } else {
        None
    }
}

fn render(wikitext: &str) -> Result<String, String> {
    let html = parse_wikitext(wikitext);
    // TODO: check return value
    let json = Json::from_str(&html).unwrap();
    match json::get_json_string(&json, &[Key("parse"), Key("text"), Key("*")]) {
        Ok(contents) => Ok(contents.to_string()),
        Err(message) => Err(message),
    }
}

fn has_matching_id(attributes: &Vec<Attribute>, id: &str) -> bool {
    // TODO: could also do this with filter() and is_empty(). Not sure if that would be better.
    for attribute in attributes {
        // TODO: do I seriously have to construct a StrTendril here?
        // There has to be a better way.
        if attribute.name.local.as_slice() == "id" &&
            attribute.value == tendril::StrTendril::from_str(id).unwrap() {
                return true;
            }
    }
    return false;
}

fn find_node_by_id(handle: &Handle, id: &str) -> Result<Handle, String> {
    let node = handle.borrow();
    match node.node {
        NodeEnum::Element(_, ref attributes) if has_matching_id(attributes, id) => Ok(handle.clone()),
        _ => {
            for child in &node.children {
                match find_node_by_id(child, id) {
                    // TODO: this looks weird. Is this an abnormal way to do things?
                    Ok(node) => return Ok(node),
                    _ => continue,
                }
            }
            Err(format!("No node with ID {} found", id))
        },
    }
}

fn replace_node_with_placeholder(original_html: &str, div_id: &str, placeholder: &str) -> Result<String, String> {
    // TODO: check errors
    let html = tendril::StrTendril::from_str(original_html).unwrap();
    let mut dom: RcDom = html5ever::parse(html5ever::one_input(html), Default::default());

    let handle = try!(find_node_by_id(&dom.get_document(), div_id));
    let child_handles =
        (&handle.borrow().children).into_iter().map(|child| child.clone()).collect::<Vec<_>>();
    for child_handle in child_handles {
        dom.remove_from_parent(child_handle);
    }
    dom.append(handle,
               html5ever::tree_builder::interface::NodeOrText::AppendText(
                   tendril::StrTendril::from_str(placeholder).unwrap()));
    let mut serialized: Vec<u8> = vec![];
    html5ever::serialize::serialize(&mut serialized, &dom.document, Default::default());
    // TODO: error handling
    Ok(String::from_utf8(serialized).unwrap())
}

fn get_current_page(page: &str) -> String {
    let client = Client::new();
    let mut res = client.get(
        &format!("https://en.wikipedia.org/wiki/{}", page))
        .header(Connection::close())
        .send().unwrap();

    let mut body = String::new();
    res.read_to_string(&mut body).unwrap();

    body
}

fn main() {
    let latest_revid = get_latest_revision_id("Zachary_Taylor").unwrap();
    let mut accumulated_contents = get_revision_content("Zachary_Taylor", latest_revid).unwrap();
    let mut revisions = vec![];
    for (revert_revid, vandalism_revid) in get_revert_revision_ids("Zachary_Taylor").ok().unwrap() {
        let reverted_contents = get_revision_content("Zachary_Taylor", revert_revid).unwrap();
        let vandalized_contents = get_revision_content("Zachary_Taylor", vandalism_revid).unwrap();
        match merge(&reverted_contents, &vandalized_contents, &accumulated_contents) {
            Some(merged_contents) => {
                accumulated_contents = merged_contents;
                revisions.push(revert_revid);
            }
            None => (),
        }
    }

    let rendered_body = render(&accumulated_contents).unwrap();
    let current_page_contents = get_current_page("Zachary_Taylor");
    let page_contents_with_placeholder =
        replace_node_with_placeholder(
            &current_page_contents, "mw-content-text", "WMW_PLACEHOLDER_TEXT").unwrap();
    let final_page_contents = page_contents_with_placeholder.replace("WMW_PLACEHOLDER_TEXT", &rendered_body);

    println!("Restored vandalisms reverted in: {:?}", revisions);
    println!("{}", final_page_contents);
}

#[cfg(test)]
mod tests {
    use super::{merge, replace_node_with_placeholder};

    #[test]
    fn test_merge_clean() {
        let old = "First line.\n\nSecond line.\n";
        let new1 = "First line.\n\nSecond line changed.\n";
        let new2 = "First line changed.\n\nSecond line.\n";
        assert_eq!("First line changed.\n\nSecond line changed.\n", merge(old, new1, new2).unwrap());
    }

    #[test]
    fn test_merge_conflicting() {
        let old = "First line.\n\nSecond line.\n";
        let new1 = "First line.\n\nSecond line changed one way.\n";
        let new2 = "First line changed.\n\nSecond line changed a different way.\n";
        assert_eq!(None, merge(old, new1, new2));
    }

    #[test]
    fn test_merge_special_characters() {
        let old = "First line.\n\nSecond line.\n";
        let new1 = "First line.\n\nSecond line 𐅃.\n";
        let new2 = "First line さようなら.\n\nSecond line.\n";
        assert_eq!("First line さようなら.\n\nSecond line 𐅃.\n", merge(old, new1, new2).unwrap());
    }

    #[test]
    fn test_replace_html_content() {
        let original_html = "

original text

Other text

";
        let expected_html = "

replaced text

Other text

";
        let processed_html = replace_node_with_placeholder(original_html, "mw-content-text", "replaced text").unwrap();
        assert_eq!(expected_html, processed_html);
    }
}
<|endoftext|>


<commit_before>trait StringChars<T> {
    fn all_chars(self) -> Vec<T>;
}

impl StringChars<String> for String {
    fn all_chars(self) -> Vec<String> {
        let char_vec: Vec<String> =
            self
            .split(",")
            .map(|cur_char| { cur_char.to_string() })
            .collect();

        char_vec
    }
}

trait VecExtensions<T> {
    fn distance(self, from: usize, to: usize) -> Vec<T>;
    fn last_pos(self) -> usize;
}

impl VecExtensions<String> for Vec<String> {
    fn distance(self, start: usize, end: usize) -> Vec<String> {
        let mut temp_vec: Vec<String> = Vec::new();
        // for i in start..end {
        //     if let Some(cur_char) = self[i] {
        //         temp_vec.push(cur_char.unwrap())
        //     }
        // }
        return temp_vec
    }

    fn last_pos(self) -> usize {
        self.len() - 1
    }
}

fn shift_seq(offset: i32, base_seq: String) -> String {
    let mut new_seq = String::new();
    let seq_vec: Vec<String> = base_seq.all_chars();

    if offset >= seq_vec.len() as i32 {
        // new_seq =
        //     seq_vec
        //     .distance_vec(offset % seq_vec.len(), )
    }

    new_seq
}

fn main() {
}
<commit_msg>Implement sequence shift<commit_after>trait StringChars<T> {
    fn all_chars(self) -> Vec<T>;
}

impl StringChars<String> for String {
    fn all_chars(self) -> Vec<String> {
        let char_vec: Vec<String> = self
                                    .split("")
                                    .map(|cur_char| { cur_char.to_string() })
                                    .collect();
        char_vec
    }
}

trait VecExtensions<T> {
    fn last_pos(self) -> usize;
}

impl VecExtensions<String> for Vec<String> {
    fn last_pos(self) -> usize {
        self.len() - 1
    }
}

fn vec_shift(input: &Vec<String>, start: usize) -> String {
    input
    .iter()
    .enumerate()
    .filter(|&(i, _)| { i >= start })
    .map(|(i, x)| { x.to_string() })
    .collect::<Vec<String>>()
    .join("")
}

fn shift_seq(offset: i32, base_seq: String) -> String {
    let mut new_seq = String::new();
    let seq_vec: Vec<String> = base_seq.all_chars();

    if offset >= seq_vec.len() as i32 {
        new_seq = vec_shift(&seq_vec, (offset as usize) % seq_vec.len());
        if offset % seq_vec.len() as i32 != 0 {
            new_seq = format!("{}{}", new_seq, vec_shift(&seq_vec, 0))
        }
    } else {
        new_seq = vec_shift(&seq_vec, offset as usize)
        // if offset != 0 {
        //     new_seq = format!("{}{}", new_seq, )
        // }
    }
    new_seq
}

fn main() {
    // PROBLEMS:
    // - Doesn't add Everything before starting point to the end
    // - Doesn't start shifting at 1, but at 2
    // - In Else part, fix the offset != 0 problem

    let shift = shift_seq(3, "ABCDEFGHIJKLMNOPQRSTUVWXYZ".to_string());
    println!("{}", shift);
}
<|endoftext|>


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

mod playlist;
mod default_ui;

use argparse::{ArgumentParser, Store, StoreTrue, List};
use default_ui::*;
use regex::Regex;
use gst::ElementT;
use std::fs;
use std::io;

// Takes a list of songs and directories the user wants to play and recurses
// through the directories, replacing them in the list with the songs inside
// them.
//
// If recurse is true, recurse_songs will follow nested directories to the
// bottom.
//
// is_first allows rfsmp to recurse into a directory one level if the user
// passes one directory as the only input.
fn recurse_songs(songs: &mut Vec<String>, recurse: bool, is_first: bool) ->
                    Result<(), io::Error>{
    let mut new : Vec<String> = vec![];

    for song in songs.iter() {
        if try!(fs::metadata(&song)).is_dir() {
            if recurse || is_first {
                let mut contents: Vec<String> = vec![];
                for entry in try!(fs::read_dir(song)) {
                    let memes = String::from(match try!(entry).path().to_str() {
                        Some(a) => a,
                        None => return Err(io::Error::new(
                                io::ErrorKind::InvalidInput, "Failed to parse song file names.")),
                    });
                    contents.push(memes);
                }
                try!(recurse_songs(&mut contents, recurse, false));
                new.append(&mut contents);
            }
            else {
                println!("WARNING: Ignoring directory {}", song);
                println!("Use -r to make recursive");
                println!("Press Enter to continue");
                let mut temp = String::new();
                match io::stdin().read_line(&mut temp) {
                    Ok(_) => {},
                    Err(err) => return Err(err),
                }
            }
        }
        else {
            new.push(song.clone());
        }
    }
    songs.clear();
    songs.append(&mut new);
    Ok(())
}

// Results that loop_main can return
enum LoopResult {
    Error(String),
    Clean,
}

// The main loop. Manages UI communications with gstreamer.
fn loop_main (bus_receiver: gst::bus::Receiver,
              main_loop: &mut gst::mainloop::MainLoop,
              playbin: &mut gst::PlayBin,
              playlist: &mut playlist::Playlist) -> LoopResult {
    let mut ui = UI::new();

    main_loop.spawn();
    playbin.play();

    let mut song_buffered = false;

    'outer: loop {
        loop {
            match bus_receiver.try_recv() {
                Ok(message) => {
                    match message.parse(){
                        gst::Message::ErrorParsed{ref error, ..} => {
                            main_loop.quit();
                            return LoopResult::Error(
                                    format!("error msg from element `{}`: {}, quit",
                                        message.src_name(), error.message()));
                        }
                        gst::Message::Eos(ref _msg) => {
                            main_loop.quit();
                            return LoopResult::Clean;
                        }
                        gst::Message::StreamStart(ref _msg) => {
                            song_buffered = false;
                            playlist.go_to_next();
                        }
                        _ => {
                            //println!("msg of type `{}` from element `{}`",
                            //         message.type_name(), message.src_name());
                        }
                    }
                }
                Err(_) => {
                    break;
                }
            }
        }

        let stream_dir = playbin.duration_s();
        let stream_pos = playbin.position_s();

        if stream_dir.is_some() && stream_pos.is_some() && !song_buffered {
            if stream_dir.unwrap() - stream_pos.unwrap() < 3.0 {
                match playlist.get_next_song() {
                    Some(a) => {
                        let song = match gst::filename_to_uri(a) {
                            Ok(a) => a,
                            Err(e) => {
                                main_loop.quit();
                                return LoopResult::Error(format!("URI Error {}",
                                                &e.message()));
                            }
                        };
                        playbin.set_uri(&song);
                        song_buffered = true;
                    }
                    None => {
                        //println!("All songs played");
                    }
                };
            }
        }

        let stream_dir = match stream_dir {
            Some(a) => a as i32,
            None => 0,
        };
        let stream_pos = match stream_pos {
            Some(a) => a as i32,
            None => 0,
        };

        match ui.manage_ui(&playlist, stream_pos, stream_dir) {
            UIResult::PlayPause => {
                if playbin.is_paused() {
                    playbin.play();
                }
                else {
                    playbin.pause();
                }
            }
            UIResult::Previous => {
                playbin.set_state(gst::ffi::GstState::GST_STATE_NULL);
                playlist.go_to_prev();
                playlist.go_to_prev();
                match playlist.get_next_song() {
                    Some(a) => {
                        let song = match gst::filename_to_uri(a) {
                            Ok(a) => a,
                            Err(e) => {
                                main_loop.quit();
                                return LoopResult::Error(format!("URI Error {}",
                                                        &e.message()));
                            }
                        };
                        playbin.set_uri(&song);
                        song_buffered = true;
                    }
                    None => {
                        main_loop.quit();
                        return LoopResult::Clean;
                    }
                };
                playbin.play();

            }
            UIResult::Next => {
                playbin.set_state(gst::ffi::GstState::GST_STATE_NULL);
                match playlist.get_next_song() {
                    Some(a) => {
                        let song = match gst::filename_to_uri(a) {
                            Ok(a) => a,
                            Err(e) => {
                                main_loop.quit();
                                return LoopResult::Error(format!("URI Error {}",
                                                        &e.message()));
                            }
                        };
                        playbin.set_uri(&song);
                        song_buffered = true;
                    }
                    None => {
                        main_loop.quit();
                        return LoopResult::Clean;
                    }
                };
                playbin.play();
            }
            UIResult::Exit => {
                main_loop.quit();
                return LoopResult::Clean;
            }
            UIResult::Error(a) => {
                main_loop.quit();
                return LoopResult::Error(a);
            }
            UIResult::NA => {}
        }
        std::thread::sleep(std::time::Duration::new(0, 20000000));
    }
}

fn main() {
    // Get and parse user arguments.
    let mut regex = String::new();
    let mut songs : Vec<String> = vec![]; // TODO: add with capacity!
    let mut recurse = false;
    {
        let mut ap = ArgumentParser::new();
        ap.set_description("Rust Fucking Simple Music Player");
        ap.refer(&mut recurse)
            .add_option(&["-r", "--recursive"], StoreTrue, "Recurse through directories if passed them");
        ap.refer(&mut regex)
            .add_option(&["-s", "--search"], Store, "Search songs using regular expressions");
        ap.refer(&mut songs)
            .add_argument("arguments", List, "Songs to play");
        ap.parse_args_or_exit();
    }

    let mut recur_one = false;
    if songs.len() == 1 {
        recur_one = true;
    }

    match recurse_songs(&mut songs, recurse, recur_one) {
        Ok(_) => {},
        Err(e) => {
            println!("{}", e.to_string());
            return;
        }
    }

    if songs.len() == 0 {
        println!("Usage:");
        println!("    rfsmp [OPTIONS] [SONGS ...]");
        return;
    }

    if regex != "" {
        let re = match Regex::new(&regex) {
            Ok(o) => o,
            Err(e) => {
                println!("Regex invalid: {}", e.to_string());
                return;
            }
        };
        songs.retain(|i| re.is_match(i));
        if songs.len() == 0 {
            println!("Failed to find songs matching regex patern.");
            return;
        }
    }

    // Initialize everything
    let mut playlist = playlist::Playlist::new(songs);

    gst::init();
    let mut playbin = match gst::PlayBin::new("audio_player") {
        Some(a) => a,
        None => {
            println!("Couldn't create PlayBin.");
            return;
        }
    };
    let mut main_loop = gst::MainLoop::new();

    let mut bus;
    let bus_receiver;

    // Send gstreamer the first song to get it started
    let song = match playlist.get_next_song() {
        Some(a) => gst::filename_to_uri(a).expect("URI Error"),
        None => {
            println!("Can't get song.");
            return;
        }
    };

    playbin.set_uri(&song);
    bus = playbin.bus().expect("Couldn't get pipeline bus");
    bus_receiver = bus.receiver();

    // run main loop and handle errors
    match loop_main(bus_receiver, &mut main_loop, &mut playbin, &mut playlist) {
        LoopResult::Error(e) => println!("{}", e),
        LoopResult::Clean => {}
    }
}
<commit_msg>Made songs play in alphabetical order.<commit_after>//  Copyright 2015 Nathanael Merrill
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
extern crate rustty;
extern crate gst;
extern crate argparse;
extern crate regex;

mod playlist;
mod default_ui;

use argparse::{ArgumentParser, Store, StoreTrue, List};
use default_ui::*;
use regex::Regex;
use gst::ElementT;
use std::fs;
use std::io;

// Takes a list of songs and directories the user wants to play and recurses
// through the directories, replacing them in the list with the songs inside
// them.
//
// If recurse is true, recurse_songs will follow nested directories to the
// bottom.
//
// is_first allows rfsmp to recurse into a directory one level if the user
// passes one directory as the only input.
fn recurse_songs(songs: &mut Vec<String>, recurse: bool, is_first: bool) ->
                    Result<(), io::Error>{
    let mut new : Vec<String> = vec![];

    for song in songs.iter() {
        if try!(fs::metadata(&song)).is_dir() {
            if recurse || is_first {
                let mut contents: Vec<String> = vec![];
                for entry in try!(fs::read_dir(song)) {
                    let memes = String::from(match try!(entry).path().to_str() {
                        Some(a) => a,
                        None => return Err(io::Error::new(
                                io::ErrorKind::InvalidInput, "Failed to parse song file names.")),
                    });
                    contents.push(memes);
                }
                try!(recurse_songs(&mut contents, recurse, false));
                new.append(&mut contents);
            }
            else {
                println!("WARNING: Ignoring directory {}", song);
                println!("Use -r to make recursive");
                println!("Press Enter to continue");
                let mut temp = String::new();
                match io::stdin().read_line(&mut temp) {
                    Ok(_) => {},
                    Err(err) => return Err(err),
                }
            }
        }
        else {
            new.push(song.clone());
        }
    }
    songs.clear();
    songs.append(&mut new);
    songs.sort();
    Ok(())
}

// Results that loop_main can return
enum LoopResult {
    Error(String),
    Clean,
}

// The main loop. Manages UI communications with gstreamer.
fn loop_main (bus_receiver: gst::bus::Receiver,
              main_loop: &mut gst::mainloop::MainLoop,
              playbin: &mut gst::PlayBin,
              playlist: &mut playlist::Playlist) -> LoopResult {
    let mut ui = UI::new();

    main_loop.spawn();
    playbin.play();

    let mut song_buffered = false;

    'outer: loop {
        loop {
            match bus_receiver.try_recv() {
                Ok(message) => {
                    match message.parse(){
                        gst::Message::ErrorParsed{ref error, ..} => {
                            main_loop.quit();
                            return LoopResult::Error(
                                    format!("error msg from element `{}`: {}, quit",
                                        message.src_name(), error.message()));
                        }
                        gst::Message::Eos(ref _msg) => {
                            main_loop.quit();
                            return LoopResult::Clean;
                        }
                        gst::Message::StreamStart(ref _msg) => {
                            song_buffered = false;
                            playlist.go_to_next();
                        }
                        _ => {
                            //println!("msg of type `{}` from element `{}`",
                            //         message.type_name(), message.src_name());
                        }
                    }
                }
                Err(_) => {
                    break;
                }
            }
        }

        let stream_dir = playbin.duration_s();
        let stream_pos = playbin.position_s();

        if stream_dir.is_some() && stream_pos.is_some() && !song_buffered {
            if stream_dir.unwrap() - stream_pos.unwrap() < 3.0 {
                match playlist.get_next_song() {
                    Some(a) => {
                        let song = match gst::filename_to_uri(a) {
                            Ok(a) => a,
                            Err(e) => {
                                main_loop.quit();
                                return LoopResult::Error(format!("URI Error {}",
                                                &e.message()));
                            }
                        };
                        playbin.set_uri(&song);
                        song_buffered = true;
                    }
                    None => {
                        //println!("All songs played");
                    }
                };
            }
        }

        let stream_dir = match stream_dir {
            Some(a) => a as i32,
            None => 0,
        };
        let stream_pos = match stream_pos {
            Some(a) => a as i32,
            None => 0,
        };

        match ui.manage_ui(&playlist, stream_pos, stream_dir) {
            UIResult::PlayPause => {
                if playbin.is_paused() {
                    playbin.play();
                }
                else {
                    playbin.pause();
                }
            }
            UIResult::Previous => {
                playbin.set_state(gst::ffi::GstState::GST_STATE_NULL);
                playlist.go_to_prev();
                playlist.go_to_prev();
                match playlist.get_next_song() {
                    Some(a) => {
                        let song = match gst::filename_to_uri(a) {
                            Ok(a) => a,
                            Err(e) => {
                                main_loop.quit();
                                return LoopResult::Error(format!("URI Error {}",
                                                        &e.message()));
                            }
                        };
                        playbin.set_uri(&song);
                        song_buffered = true;
                    }
                    None => {
                        main_loop.quit();
                        return LoopResult::Clean;
                    }
                };
                playbin.play();

            }
            UIResult::Next => {
                playbin.set_state(gst::ffi::GstState::GST_STATE_NULL);
                match playlist.get_next_song() {
                    Some(a) => {
                        let song = match gst::filename_to_uri(a) {
                            Ok(a) => a,
                            Err(e) => {
                                main_loop.quit();
                                return LoopResult::Error(format!("URI Error {}",
                                                        &e.message()));
                            }
                        };
                        playbin.set_uri(&song);
                        song_buffered = true;
                    }
                    None => {
                        main_loop.quit();
                        return LoopResult::Clean;
                    }
                };
                playbin.play();
            }
            UIResult::Exit => {
                main_loop.quit();
                return LoopResult::Clean;
            }
            UIResult::Error(a) => {
                main_loop.quit();
                return LoopResult::Error(a);
            }
            UIResult::NA => {}
        }
        std::thread::sleep(std::time::Duration::new(0, 20000000));
    }
}

fn main() {
    // Get and parse user arguments.
    let mut regex = String::new();
    let mut songs : Vec<String> = vec![]; // TODO: add with capacity!
    let mut recurse = false;
    {
        let mut ap = ArgumentParser::new();
        ap.set_description("Rust Fucking Simple Music Player");
        ap.refer(&mut recurse)
            .add_option(&["-r", "--recursive"], StoreTrue, "Recurse through directories if passed them");
        ap.refer(&mut regex)
            .add_option(&["-s", "--search"], Store, "Search songs using regular expressions");
        ap.refer(&mut songs)
            .add_argument("arguments", List, "Songs to play");
        ap.parse_args_or_exit();
    }

    let mut recur_one = false;
    if songs.len() == 1 {
        recur_one = true;
    }

    match recurse_songs(&mut songs, recurse, recur_one) {
        Ok(_) => {},
        Err(e) => {
            println!("{}", e.to_string());
            return;
        }
    }

    if songs.len() == 0 {
        println!("Usage:");
        println!("    rfsmp [OPTIONS] [SONGS ...]");
        return;
    }

    if regex != "" {
        let re = match Regex::new(&regex) {
            Ok(o) => o,
            Err(e) => {
                println!("Regex invalid: {}", e.to_string());
                return;
            }
        };
        songs.retain(|i| re.is_match(i));
        if songs.len() == 0 {
            println!("Failed to find songs matching regex patern.");
            return;
        }
    }

    // Initialize everything
    let mut playlist = playlist::Playlist::new(songs);

    gst::init();
    let mut playbin = match gst::PlayBin::new("audio_player") {
        Some(a) => a,
        None => {
            println!("Couldn't create PlayBin.");
            return;
        }
    };
    let mut main_loop = gst::MainLoop::new();

    let mut bus;
    let bus_receiver;

    // Send gstreamer the first song to get it started
    let song = match playlist.get_next_song() {
        Some(a) => gst::filename_to_uri(a).expect("URI Error"),
        None => {
            println!("Can't get song.");
            return;
        }
    };

    playbin.set_uri(&song);
    bus = playbin.bus().expect("Couldn't get pipeline bus");
    bus_receiver = bus.receiver();

    // run main loop and handle errors
    match loop_main(bus_receiver, &mut main_loop, &mut playbin, &mut playlist) {
        LoopResult::Error(e) => println!("{}", e),
        LoopResult::Clean => {}
    }
}
<|endoftext|>