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

use rgtk::*;
use std::collections::HashSet;
use std::old_io::fs;
use std::old_io::fs::PathExtensions;

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

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

    // constants

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

    // create the window

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

    window.connect(gtk::signals::DeleteEvent::new(&mut |_| {
        unsafe {
            ffi::close(read_fd);
            ffi::close(write_fd);
            ffi::kill(pid, 15);
        }
        quit_app = true;
        true
    }));

    // create the panes

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

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

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

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

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

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

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

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

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

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

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

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

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

    // populate the project tree

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

    ::utils::read_prefs(&mut state);
    ::ui::update_project_tree(&mut state, &mut project_tree);
    ::projects::set_selection(&mut state, 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);
    }));
    remove_button.connect(gtk::signals::Clicked::new(&mut || {
        ::projects::remove_item(&mut state);
    }));
    selection.connect(gtk::signals::Changed::new(&mut || {
        ::projects::set_selection(&mut state, 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);
    }));

    // show the window

    window.show_all();

    // start communicating with nvim

    ffi::nvim_execute(write_fd, "au BufEnter * call rpcnotify(1, \"bufenter\", bufname(\"\"))");

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

    unsafe { ffi::fcntl(read_fd, ffi::F_SETFL, ffi::O_NONBLOCK) };

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

    loop {
        gtk::main_iteration_do(false);

        if let Some(recv_arr) = ffi::receive_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);
                                ::ui::update_project_tree(&mut state, &mut project_tree);
                            }
                        }
                    },
                    _ => (),
                }
            }
        }

        if quit_app {
            break;
        }
    }
}

fn main() {
    // create data dir
    let home_dir = ::utils::get_home_dir();
    let data_dir = home_dir.join(::utils::DATA_DIR);
    if !data_dir.exists() {
        match fs::mkdir(&data_dir, ::std::old_io::USER_DIR) {
            Ok(_) => {
                for res in ::utils::DATA_CONTENT.iter() {
                    let res_path = data_dir.join_many(res.path);
                    ::std::old_io::fs::mkdir_recursive(&res_path.dir_path(), ::std::old_io::USER_DIR).ok();
                    ::std::old_io::File::create(&res_path).write_all(res.data.as_bytes()).ok();
                }
                println!("Created data dir at {}", data_dir.as_str().unwrap());
            },
            Err(e) => { println!("Error creating data dir: {}", e) }
        }
    }

    // set $VIM to the data dir if it isn't already set
    if ::std::env::var("VIM").is_err() {
        ::std::env::set_var("VIM", data_dir.as_str().unwrap());
    }

    // create config file
    let config_file = home_dir.join(::utils::CONFIG_FILE);
    if !config_file.exists() {
        match ::std::old_io::File::create(&config_file).write_all(::utils::CONFIG_CONTENT.as_bytes()) {
            Ok(_) => { println!("Created config file at {}", config_file.as_str().unwrap()) },
            Err(e) => { println!("Error creating config file: {}", e) }
        }
    }

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

    // two pairs of anonymous pipes for msgpack-rpc between the gui and nvim
    let mut nvim_gui : [ffi::c_int; 2] = [0; 2]; // to nvim from gui
    let mut gui_nvim : [ffi::c_int; 2] = [0; 2]; // to gui from nvim
    unsafe {
        ffi::pipe(nvim_gui.as_mut_ptr());
        ffi::pipe(gui_nvim.as_mut_ptr());
    };

    // split into two processes
    let pid = unsafe { ffi::fork() };

    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 mut args = Vec::new();
        for arg in ::std::env::args() {
            args.push(arg);
        }
        args.push_all(&["-u".to_string(), config_file.as_str().unwrap().to_string()]);
        neovim::main_setup(&args);
        neovim::channel_from_fds(nvim_gui[0], gui_nvim[1]);
        neovim::main_loop();
    }
}
<commit_msg>Add sleep to prevent max CPU<commit_after>extern crate libc;
extern crate neovim;
extern crate rgtk;
extern crate "rustc-serialize" as rustc_serialize;

use rgtk::*;
use std::collections::HashSet;
use std::old_io::fs;
use std::old_io::fs::PathExtensions;
use std::old_io::timer;
use std::time::duration::Duration;

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

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

    // constants

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

    // create the window

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

    window.connect(gtk::signals::DeleteEvent::new(&mut |_| {
        unsafe {
            ffi::close(read_fd);
            ffi::close(write_fd);
            ffi::kill(pid, 15);
        }
        quit_app = true;
        true
    }));

    // create the panes

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

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

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

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

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

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

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

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

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

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

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

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

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

    // populate the project tree

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

    ::utils::read_prefs(&mut state);
    ::ui::update_project_tree(&mut state, &mut project_tree);
    ::projects::set_selection(&mut state, 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);
    }));
    remove_button.connect(gtk::signals::Clicked::new(&mut || {
        ::projects::remove_item(&mut state);
    }));
    selection.connect(gtk::signals::Changed::new(&mut || {
        ::projects::set_selection(&mut state, 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);
    }));

    // show the window

    window.show_all();

    // start communicating with nvim

    ffi::nvim_execute(write_fd, "au BufEnter * call rpcnotify(1, \"bufenter\", bufname(\"\"))");

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

    unsafe { ffi::fcntl(read_fd, ffi::F_SETFL, ffi::O_NONBLOCK) };

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

    loop {
        gtk::main_iteration_do(false);

        if let Some(recv_arr) = ffi::receive_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);
                                ::ui::update_project_tree(&mut state, &mut project_tree);
                            }
                        }
                    },
                    _ => (),
                }
            }
        }

        if quit_app {
            break;
        }

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

fn main() {
    // create data dir
    let home_dir = ::utils::get_home_dir();
    let data_dir = home_dir.join(::utils::DATA_DIR);
    if !data_dir.exists() {
        match fs::mkdir(&data_dir, ::std::old_io::USER_DIR) {
            Ok(_) => {
                for res in ::utils::DATA_CONTENT.iter() {
                    let res_path = data_dir.join_many(res.path);
                    ::std::old_io::fs::mkdir_recursive(&res_path.dir_path(), ::std::old_io::USER_DIR).ok();
                    ::std::old_io::File::create(&res_path).write_all(res.data.as_bytes()).ok();
                }
                println!("Created data dir at {}", data_dir.as_str().unwrap());
            },
            Err(e) => { println!("Error creating data dir: {}", e) }
        }
    }

    // set $VIM to the data dir if it isn't already set
    if ::std::env::var("VIM").is_err() {
        ::std::env::set_var("VIM", data_dir.as_str().unwrap());
    }

    // create config file
    let config_file = home_dir.join(::utils::CONFIG_FILE);
    if !config_file.exists() {
        match ::std::old_io::File::create(&config_file).write_all(::utils::CONFIG_CONTENT.as_bytes()) {
            Ok(_) => { println!("Created config file at {}", config_file.as_str().unwrap()) },
            Err(e) => { println!("Error creating config file: {}", e) }
        }
    }

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

    // two pairs of anonymous pipes for msgpack-rpc between the gui and nvim
    let mut nvim_gui : [ffi::c_int; 2] = [0; 2]; // to nvim from gui
    let mut gui_nvim : [ffi::c_int; 2] = [0; 2]; // to gui from nvim
    unsafe {
        ffi::pipe(nvim_gui.as_mut_ptr());
        ffi::pipe(gui_nvim.as_mut_ptr());
    };

    // split into two processes
    let pid = unsafe { ffi::fork() };

    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 mut args = Vec::new();
        for arg in ::std::env::args() {
            args.push(arg);
        }
        args.push_all(&["-u".to_string(), config_file.as_str().unwrap().to_string()]);
        neovim::main_setup(&args);
        neovim::channel_from_fds(nvim_gui[0], gui_nvim[1]);
        neovim::main_loop();
    }
}
<|endoftext|>


<commit_before>// Copyright 2015 Jerome Rasky <jerome@rasky.co>
//
// 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 expressed or implied. See the
// License for the specific language concerning governing permissions and
// limitations under the License.
#![feature(cstr_to_str)]
#![feature(result_expect)]
#![feature(convert)]
#![feature(iter_arith)]
#![feature(io)]
#![feature(binary_heap_extras)]
#![feature(raw)]
#![feature(arc_unique)]
extern crate unicode_width;
extern crate term;
extern crate libc;
#[macro_use]
extern crate log;
extern crate env_logger;
extern crate threadpool;

use std::mem;

use event_loop::EventLoop;

mod constants;
#[macro_use]
mod error;
mod search;
mod types;
mod bis_c;
mod terminal;
mod ui;
mod threads;
mod event_loop;

fn main() {
    // init logging
    match env_logger::init() {
        Ok(_) => {
            trace!("Successfully initialized logging");
        },
        Err(e) => {
            panic!("Failed to initialize logging: {}", e);
        }
    }

    // create the event loop
    let mut ev_loop = match EventLoop::create() {
        Ok(ev) => ev,
        Err(e) => panic!("Failed to create event loop: {}", e)
    };

    // run the event loop
    match ev_loop.run() {
        Ok(_) => {
            debug!("Event loop exited successfully");
        },
        Err(e) => {
            panic!("Event loop failed: {}", e);
        }
    }

    // destroy the event loop
    mem::drop(ev_loop);

    // done
}
<commit_msg>Updated dependencies<commit_after>// Copyright 2015 Jerome Rasky <jerome@rasky.co>
//
// 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 expressed or implied. See the
// License for the specific language concerning governing permissions and
// limitations under the License.
#![feature(convert)]
#![feature(iter_arith)]
#![feature(io)]
#![feature(binary_heap_extras)]
#![feature(raw)]
extern crate unicode_width;
extern crate term;
extern crate libc;
#[macro_use]
extern crate log;
extern crate env_logger;
extern crate threadpool;

use std::mem;

use event_loop::EventLoop;

mod constants;
#[macro_use]
mod error;
mod search;
mod types;
mod bis_c;
mod terminal;
mod ui;
mod threads;
mod event_loop;

fn main() {
    // init logging
    match env_logger::init() {
        Ok(_) => {
            trace!("Successfully initialized logging");
        },
        Err(e) => {
            panic!("Failed to initialize logging: {}", e);
        }
    }

    // create the event loop
    let mut ev_loop = match EventLoop::create() {
        Ok(ev) => ev,
        Err(e) => panic!("Failed to create event loop: {}", e)
    };

    // run the event loop
    match ev_loop.run() {
        Ok(_) => {
            debug!("Event loop exited successfully");
        },
        Err(e) => {
            panic!("Event loop failed: {}", e);
        }
    }

    // destroy the event loop
    mem::drop(ev_loop);

    // done
}
<|endoftext|>


<commit_before>/*
== run-info == (Matthias Drescher, 2016)

This program shows the current cpu and memory load of the machine within a terminal UI.
Since it depends on linux-specific system files, it will only run on a linux machine.
*/

extern crate term;
use term::color;
use term::Attr;

extern crate clap;
use clap::{Arg, App};

extern crate time;

use std::io::prelude::*;
use std::mem;
use std::thread;

mod reader;
use reader::MemInfo;
use reader::{CPUInfo, CPULoad};

fn main()
{
	let matches = App::new("run-info")
						.version("0.1")
						.about("Shows current CPU and memory load")
						.arg(Arg::with_name("delay")
							.short("d")
							.long("delay")
							.help("Sets the delay between updating the information (in ms)")
							.takes_value(true))
						.arg(Arg::with_name("no-color")
							.short("c")
							.long("no-color")
							.help("Switches to monochrome mode"))
						.arg(Arg::with_name("simple")
							.short("s")
							.long("simple")
							.help("One-line display for logging"))
						.get_matches();
	let delay_str = matches.value_of("delay").unwrap_or("1500").to_owned();
	let color = matches.occurrences_of("no-color") == 0;
	let simple = matches.occurrences_of("simple") != 0;
	let mut valid_delay = true;
	let delay = match delay_str.parse::<usize>()
	{
		Ok(v) => v,
		Err(_) => {println!("Error: delay argument is not a number"); valid_delay = false; 0}
	};
	if valid_delay
	{
		main_loop(delay, color, simple);
	}
}

#[allow(unused_assignments)]
fn main_loop(delay: usize, color: bool, simple: bool)
{
	println!("");
	let mut term = term::stdout().expect("term is not available");
	let mut meminfo = MemInfo::new();
	let mut cpuinfo_old = CPUInfo::new();
	let mut cpuinfo_new = CPUInfo::new();
	let mut cpuinfo_delta = CPUInfo::new(); //The delta between the two time frames
	let _ = cpuinfo_new.update();

	loop
	{
		match meminfo.update()  //we can just update the meminfo
		{
			Ok(_) => {},
			Err(_) => {
				println!("Error: Memory information is not available.");
				println!("Maybe you are not running this program on a Linux OS?");
				break;}
		};

		mem::swap(&mut cpuinfo_new, &mut cpuinfo_old); //the new info is becoming the old info, and a new info is requested
		cpuinfo_new = CPUInfo::new();
		match cpuinfo_new.update()
		{
			Ok(_) => {},
			Err(_) => {
				println!("Error: CPU information is not available.");
				println!("Maybe you are not running this program on a Linux OS?");
				break;
			}
		};
		cpuinfo_delta = CPUInfo::new(); //reset delta
		CPUInfo::calculate_delta(&mut cpuinfo_delta, &cpuinfo_old, &cpuinfo_new); //calculate the difference

		if simple
		{
			print_simple(&mut term, color, &cpuinfo_delta, &meminfo);
		}
		else
		{
			print(&mut term, color, &cpuinfo_delta, &meminfo);
		}

		thread::sleep(std::time::Duration::from_millis(delay as u64)); //wait until next update
	}
}

//Printing

fn print_simple(mut term: &mut Box<term::Terminal<Output=std::io::Stdout> + Send>, color: bool, cpu: &CPUInfo, mem: &MemInfo)
{
	let time = time::now();
	let timestamp = format!("{}m/{}d/{}y-{}h:{}m:{}s", time.tm_mon+1, time.tm_mday, time.tm_year, time.tm_hour, time.tm_min, time.tm_sec);
	let cpuload_string = format_float(calc_cpu_load_percentage(&cpu.total_load));
	let mem_string = format_gib(mem.total - mem.free - mem.cached);
	let swap_string = format_gib(mem.swap_used);

	let _ = write!(term, "{}\t\tCPU:", timestamp);
	print_highlighted(term, color, format!("{} %", cpuload_string));
	let _ = write!(term, "\t\tRAM: ");
	print_highlighted(term, color, format!("{} Gib", mem_string));
	if mem.swap_used != 0
	{
		let _ = write!(term, "\t\tSWAP: ");
		print_highlighted(term, color, format!("{} Gib", swap_string));
	}

	let _ = writeln!(term, "");
}

fn print(mut term: &mut Box<term::Terminal<Output=std::io::Stdout> + Send>, color: bool, cpu: &CPUInfo, mem: &MemInfo)
{
	let mut lines_printed = 13;

	//CPU

	//"x processes on x cores"
	print_header(term, color, 57, String::from("CPU"));
	print_highlighted(term, color, format!("{}", cpu.processes));
	if cpu.processes > 1
	{
		let _ = write!(term, " processes on ");
	}else
	{
	   	let _ = write!(term, " process on ");
	}
	print_highlighted(term, color, format!("{}", cpu.cores));
	let _ = writeln!(term, " cores      ");
	let _ = writeln!(term, "");

	//print bars
	print_highlighted(term, color, String::from("TOTAL: "));
	let total_percentage = calc_cpu_load_percentage(&cpu.total_load);
	print_progress_bar(term, color, total_percentage, 40, color::RED);
	print_highlighted(term, color, format!(" {} %   ", format_float(total_percentage)));
	let _ = writeln!(term, "");

	let mut core_counter = 1;
	for core_load in &cpu.cores_load
	{
		let _ = write!(term, "CPU {}: ", core_counter);
		let core_percentage = calc_cpu_load_percentage(&core_load);
		print_progress_bar(term, color, core_percentage, 40, color::GREEN);
		let _ = write!(term, " {} %   ", format_float(core_percentage));
		let _ = writeln!(term, "");
		lines_printed += 1;
		core_counter += 1;
	}
	let _ = writeln!(term, "");

	//MEMORY

	print_header(term, color, 57, String::from("MEMORY"));
	let _ = writeln!(term, "");

	let memory_use: f64 = (mem.total - mem.free - mem.cached) as f64 / mem.total as f64;
	let swap_use: f64 = (mem.swap_total - mem.swap_free) as f64 / mem.swap_total as f64;

	let _ = write!(term, "  RAM: "); //RAM BAR
	print_progress_bar(term, color, memory_use, 40, color::GREEN);
	let _ = writeln!(term, "");
	print_highlighted(term, color, format!("             {}", format_gib(mem.total - mem.free - mem.cached)));
	let _ = write!(term, " GiB / ");
	print_highlighted(term, color, format!("{}", format_gib(mem.total)));
	let _ = write!(term, " GiB (");
	print_highlighted(term, color, format!(" {}% ", format_float(memory_use)));
	let _ = write!(term, ")");
	let _ = writeln!(term, "\n");

	let _ = write!(term, " SWAP: "); //SWAP BAR
	print_progress_bar(term, color, swap_use, 40, color::GREEN);
	let _ = writeln!(term, "");
	print_highlighted(term, color, format!("               {}", format_gib(mem.swap_used)));
	let _ = write!(term, " GiB / ");
	print_highlighted(term, color, format!("{}", format_gib(mem.swap_total)));
	let _ = write!(term, " GiB (");
	print_highlighted(term, color, format!(" {}% ", format_float(swap_use)));
	let _ = write!(term, ")");
	let _ = writeln!(term, "\n");

	for _ in 0..lines_printed
	{
		let _ = term.cursor_up();
	}
}

//UI Objects

fn print_progress_bar(term: &mut Box<term::Terminal<Output=std::io::Stdout> + Send>, enabled: bool, value: f64, size: usize, color: u16)
{
	let barsize = ((value * size as f64)) as usize;
	let _ = write!(term, "[");
	colorize(term, enabled, color);
	attribute(term, enabled, Attr::Bold);
	for i in 0..size
	{
		if i < barsize
		{
			let _ = write!(term, "=");
		}
		else
		{
		 	let _ = write!(term, " ");
		}
	}
	reset(term, enabled);
	let _ = write!(term, "]");
}

fn print_highlighted(term: &mut Box<term::Terminal<Output=std::io::Stdout> + Send>, color: bool, content: String)
{
	colorize(term, color, color::CYAN);
	attribute(term, color, Attr::Bold);
	let _ = write!(term, "{}", content);
	reset(term, color);
}

fn print_header(term: &mut Box<term::Terminal<Output=std::io::Stdout> + Send>, color: bool, size: usize, name: String)
{
	let halfsize = (size - name.len() - 2) / 2;
	let extend_first = halfsize * 2 + name.len() + 2 != size; //catch rounding errors
	for _ in 0..halfsize
	{
		let _ = write!(term, "=");
	}
	if extend_first
	{
		let _ = write!(term, "=");
	}
	colorize(term, color, color::YELLOW);
	attribute(term, color, Attr::Bold);
	let _ = write!(term, " {} ", name);
	reset(term, color);
	for _ in 0..halfsize
	{
		let _ = write!(term, "=");
	}
	let _ = writeln!(term, "");
}

//HELPER FUNCTIONS

fn attribute(term: &mut Box<term::Terminal<Output=std::io::Stdout> + Send>, color: bool, attrib: Attr)
{
	if color
	{
		let _ = term.attr(attrib);
	}
}

fn reset(term: &mut Box<term::Terminal<Output=std::io::Stdout> + Send>, color: bool)
{
	if color
	{
		let _ = term.reset();
	}
}

fn colorize(term: &mut Box<term::Terminal<Output=std::io::Stdout> + Send>, color: bool, color_code: u16)
{
	if color
	{
		let _ = term.fg(color_code);
	}
}

fn format_float(float: f64) -> String
{
	let mut string = format!("{}", float*100.0);
	if string.contains(".")
	{
		string = string.split_at(string.find(".").unwrap() + 2).0.to_owned();
	}
	string
}

//takes kilobytes, transforms to gibibytes and crops the result according to format_float()
fn format_gib(kib: u64) -> String
{
	let gib = ((kib as f64 / 1024.0) / 1024.0) / 1024.0;

	let mut string = format!("{}", gib);
	if string.contains(".")
	{
		string = string.split_at(string.find(".").unwrap() + 2).0.to_owned();
	}
	string
}

fn calc_cpu_load_percentage(load: &CPULoad) -> f64
{
	let mut load_percentage: f64 = 0.0;
	if load.busy != 0
	{
		load_percentage = load.busy as f64 / (load.idle + load.busy) as f64;
	}
	load_percentage
}<commit_msg>display correct year<commit_after>/*
== run-info == (Matthias Drescher, 2016)

This program shows the current cpu and memory load of the machine within a terminal UI.
Since it depends on linux-specific system files, it will only run on a linux machine.
*/

extern crate term;
use term::color;
use term::Attr;

extern crate clap;
use clap::{Arg, App};

extern crate time;

use std::io::prelude::*;
use std::mem;
use std::thread;

mod reader;
use reader::MemInfo;
use reader::{CPUInfo, CPULoad};

fn main()
{
	let matches = App::new("run-info")
						.version("0.1")
						.about("Shows current CPU and memory load")
						.arg(Arg::with_name("delay")
							.short("d")
							.long("delay")
							.help("Sets the delay between updating the information (in ms)")
							.takes_value(true))
						.arg(Arg::with_name("no-color")
							.short("c")
							.long("no-color")
							.help("Switches to monochrome mode"))
						.arg(Arg::with_name("simple")
							.short("s")
							.long("simple")
							.help("One-line display for logging"))
						.get_matches();
	let delay_str = matches.value_of("delay").unwrap_or("1500").to_owned();
	let color = matches.occurrences_of("no-color") == 0;
	let simple = matches.occurrences_of("simple") != 0;
	let mut valid_delay = true;
	let delay = match delay_str.parse::<usize>()
	{
		Ok(v) => v,
		Err(_) => {println!("Error: delay argument is not a number"); valid_delay = false; 0}
	};
	if valid_delay
	{
		main_loop(delay, color, simple);
	}
}

#[allow(unused_assignments)]
fn main_loop(delay: usize, color: bool, simple: bool)
{
	println!("");
	let mut term = term::stdout().expect("term is not available");
	let mut meminfo = MemInfo::new();
	let mut cpuinfo_old = CPUInfo::new();
	let mut cpuinfo_new = CPUInfo::new();
	let mut cpuinfo_delta = CPUInfo::new(); //The delta between the two time frames
	let _ = cpuinfo_new.update();

	loop
	{
		match meminfo.update()  //we can just update the meminfo
		{
			Ok(_) => {},
			Err(_) => {
				println!("Error: Memory information is not available.");
				println!("Maybe you are not running this program on a Linux OS?");
				break;}
		};

		mem::swap(&mut cpuinfo_new, &mut cpuinfo_old); //the new info is becoming the old info, and a new info is requested
		cpuinfo_new = CPUInfo::new();
		match cpuinfo_new.update()
		{
			Ok(_) => {},
			Err(_) => {
				println!("Error: CPU information is not available.");
				println!("Maybe you are not running this program on a Linux OS?");
				break;
			}
		};
		cpuinfo_delta = CPUInfo::new(); //reset delta
		CPUInfo::calculate_delta(&mut cpuinfo_delta, &cpuinfo_old, &cpuinfo_new); //calculate the difference

		if simple
		{
			print_simple(&mut term, color, &cpuinfo_delta, &meminfo);
		}
		else
		{
			print(&mut term, color, &cpuinfo_delta, &meminfo);
		}

		thread::sleep(std::time::Duration::from_millis(delay as u64)); //wait until next update
	}
}

//Printing

fn print_simple(mut term: &mut Box<term::Terminal<Output=std::io::Stdout> + Send>, color: bool, cpu: &CPUInfo, mem: &MemInfo)
{
	let time = time::now();
	let timestamp = format!("{}m/{}d/{}y-{}h:{}m:{}s", time.tm_mon+1, time.tm_mday, time.tm_year+1900, time.tm_hour, time.tm_min, time.tm_sec);
	let cpuload_string = format_float(calc_cpu_load_percentage(&cpu.total_load));
	let mem_string = format_gib(mem.total - mem.free - mem.cached);
	let swap_string = format_gib(mem.swap_used);

	let _ = write!(term, "{}\t\tCPU:", timestamp);
	print_highlighted(term, color, format!("{} %", cpuload_string));
	let _ = write!(term, "\t\tRAM: ");
	print_highlighted(term, color, format!("{} Gib", mem_string));
	if mem.swap_used != 0
	{
		let _ = write!(term, "\t\tSWAP: ");
		print_highlighted(term, color, format!("{} Gib", swap_string));
	}

	let _ = writeln!(term, "");
}

fn print(mut term: &mut Box<term::Terminal<Output=std::io::Stdout> + Send>, color: bool, cpu: &CPUInfo, mem: &MemInfo)
{
	let mut lines_printed = 13;

	//CPU

	//"x processes on x cores"
	print_header(term, color, 57, String::from("CPU"));
	print_highlighted(term, color, format!("{}", cpu.processes));
	if cpu.processes > 1
	{
		let _ = write!(term, " processes on ");
	}else
	{
	   	let _ = write!(term, " process on ");
	}
	print_highlighted(term, color, format!("{}", cpu.cores));
	let _ = writeln!(term, " cores      ");
	let _ = writeln!(term, "");

	//print bars
	print_highlighted(term, color, String::from("TOTAL: "));
	let total_percentage = calc_cpu_load_percentage(&cpu.total_load);
	print_progress_bar(term, color, total_percentage, 40, color::RED);
	print_highlighted(term, color, format!(" {} %   ", format_float(total_percentage)));
	let _ = writeln!(term, "");

	let mut core_counter = 1;
	for core_load in &cpu.cores_load
	{
		let _ = write!(term, "CPU {}: ", core_counter);
		let core_percentage = calc_cpu_load_percentage(&core_load);
		print_progress_bar(term, color, core_percentage, 40, color::GREEN);
		let _ = write!(term, " {} %   ", format_float(core_percentage));
		let _ = writeln!(term, "");
		lines_printed += 1;
		core_counter += 1;
	}
	let _ = writeln!(term, "");

	//MEMORY

	print_header(term, color, 57, String::from("MEMORY"));
	let _ = writeln!(term, "");

	let memory_use: f64 = (mem.total - mem.free - mem.cached) as f64 / mem.total as f64;
	let swap_use: f64 = (mem.swap_total - mem.swap_free) as f64 / mem.swap_total as f64;

	let _ = write!(term, "  RAM: "); //RAM BAR
	print_progress_bar(term, color, memory_use, 40, color::GREEN);
	let _ = writeln!(term, "");
	print_highlighted(term, color, format!("             {}", format_gib(mem.total - mem.free - mem.cached)));
	let _ = write!(term, " GiB / ");
	print_highlighted(term, color, format!("{}", format_gib(mem.total)));
	let _ = write!(term, " GiB (");
	print_highlighted(term, color, format!(" {}% ", format_float(memory_use)));
	let _ = write!(term, ")");
	let _ = writeln!(term, "\n");

	let _ = write!(term, " SWAP: "); //SWAP BAR
	print_progress_bar(term, color, swap_use, 40, color::GREEN);
	let _ = writeln!(term, "");
	print_highlighted(term, color, format!("               {}", format_gib(mem.swap_used)));
	let _ = write!(term, " GiB / ");
	print_highlighted(term, color, format!("{}", format_gib(mem.swap_total)));
	let _ = write!(term, " GiB (");
	print_highlighted(term, color, format!(" {}% ", format_float(swap_use)));
	let _ = write!(term, ")");
	let _ = writeln!(term, "\n");

	for _ in 0..lines_printed
	{
		let _ = term.cursor_up();
	}
}

//UI Objects

fn print_progress_bar(term: &mut Box<term::Terminal<Output=std::io::Stdout> + Send>, enabled: bool, value: f64, size: usize, color: u16)
{
	let barsize = ((value * size as f64)) as usize;
	let _ = write!(term, "[");
	colorize(term, enabled, color);
	attribute(term, enabled, Attr::Bold);
	for i in 0..size
	{
		if i < barsize
		{
			let _ = write!(term, "=");
		}
		else
		{
		 	let _ = write!(term, " ");
		}
	}
	reset(term, enabled);
	let _ = write!(term, "]");
}

fn print_highlighted(term: &mut Box<term::Terminal<Output=std::io::Stdout> + Send>, color: bool, content: String)
{
	colorize(term, color, color::CYAN);
	attribute(term, color, Attr::Bold);
	let _ = write!(term, "{}", content);
	reset(term, color);
}

fn print_header(term: &mut Box<term::Terminal<Output=std::io::Stdout> + Send>, color: bool, size: usize, name: String)
{
	let halfsize = (size - name.len() - 2) / 2;
	let extend_first = halfsize * 2 + name.len() + 2 != size; //catch rounding errors
	for _ in 0..halfsize
	{
		let _ = write!(term, "=");
	}
	if extend_first
	{
		let _ = write!(term, "=");
	}
	colorize(term, color, color::YELLOW);
	attribute(term, color, Attr::Bold);
	let _ = write!(term, " {} ", name);
	reset(term, color);
	for _ in 0..halfsize
	{
		let _ = write!(term, "=");
	}
	let _ = writeln!(term, "");
}

//HELPER FUNCTIONS

fn attribute(term: &mut Box<term::Terminal<Output=std::io::Stdout> + Send>, color: bool, attrib: Attr)
{
	if color
	{
		let _ = term.attr(attrib);
	}
}

fn reset(term: &mut Box<term::Terminal<Output=std::io::Stdout> + Send>, color: bool)
{
	if color
	{
		let _ = term.reset();
	}
}

fn colorize(term: &mut Box<term::Terminal<Output=std::io::Stdout> + Send>, color: bool, color_code: u16)
{
	if color
	{
		let _ = term.fg(color_code);
	}
}

fn format_float(float: f64) -> String
{
	let mut string = format!("{}", float*100.0);
	if string.contains(".")
	{
		string = string.split_at(string.find(".").unwrap() + 2).0.to_owned();
	}
	string
}

//takes kilobytes, transforms to gibibytes and crops the result according to format_float()
fn format_gib(kib: u64) -> String
{
	let gib = ((kib as f64 / 1024.0) / 1024.0) / 1024.0;

	let mut string = format!("{}", gib);
	if string.contains(".")
	{
		string = string.split_at(string.find(".").unwrap() + 2).0.to_owned();
	}
	string
}

fn calc_cpu_load_percentage(load: &CPULoad) -> f64
{
	let mut load_percentage: f64 = 0.0;
	if load.busy != 0
	{
		load_percentage = load.busy as f64 / (load.idle + load.busy) as f64;
	}
	load_percentage
}<|endoftext|>


<commit_before>//! An implementation of [tldr](https://github.com/tldr-pages/tldr) in Rust.
//
// Copyright (c) 2015-2020 tealdeer developers
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. All files in the project carrying such notice may not be
// copied, modified, or distributed except according to those terms.

#![deny(clippy::all)]
#![warn(clippy::pedantic)]
#![allow(clippy::similar_names)]
#![allow(clippy::module_name_repetitions)]

#[cfg(feature = "logging")]
extern crate env_logger;

use std::env;
use std::fs::File;
use std::io::BufRead;
use std::io::BufReader;
use std::path::{Path, PathBuf};
use std::process;

use ansi_term::{Color, Style};
use app_dirs::AppInfo;
use atty::Stream;
use docopt::Docopt;
#[cfg(not(target_os = "windows"))]
use pager::Pager;
use serde_derive::Deserialize;

mod cache;
mod config;
mod error;
mod formatter;
mod tokenizer;
mod types;

use crate::cache::Cache;
use crate::config::{get_config_path, make_default_config, Config, MAX_CACHE_AGE};
use crate::error::TealdeerError::{CacheError, ConfigError, UpdateError};
use crate::formatter::print_lines;
use crate::tokenizer::Tokenizer;
use crate::types::{ColorOptions, OsType};

const NAME: &str = "tealdeer";
const APP_INFO: AppInfo = AppInfo {
    name: NAME,
    author: NAME,
};
const VERSION: &str = env!("CARGO_PKG_VERSION");
const USAGE: &str = "
Usage:

    tldr [options] <command>...
    tldr [options]

Options:

    -h --help           Show this screen
    -v --version        Show version information
    -l --list           List all commands in the cache
    -f --render <file>  Render a specific markdown file
    -o --os <type>      Override the operating system [linux, osx, sunos, windows]
    -u --update         Update the local cache
    -c --clear-cache    Clear the local cache
    -p --pager          Use a pager to page output
    -m --markdown       Display the raw markdown instead of rendering it
    -q --quiet          Suppress informational messages
    --config-path       Show config file path
    --seed-config       Create a basic config
    --color <when>      Control when to use color [always, auto, never] [default: auto]

Examples:

    $ tldr tar
    $ tldr --list

To control the cache:

    $ tldr --update
    $ tldr --clear-cache

To render a local file (for testing):

    $ tldr --render /path/to/file.md
";
const ARCHIVE_URL: &str = "https://github.com/tldr-pages/tldr/archive/master.tar.gz";
#[cfg(not(target_os = "windows"))]
const PAGER_COMMAND: &str = "less -R";

#[derive(Debug, Deserialize)]
#[allow(clippy::struct_excessive_bools)]
struct Args {
    arg_command: Option<Vec<String>>,
    flag_help: bool,
    flag_version: bool,
    flag_list: bool,
    flag_render: Option<String>,
    flag_os: Option<OsType>,
    flag_update: bool,
    flag_clear_cache: bool,
    flag_pager: bool,
    flag_quiet: bool,
    flag_config_path: bool,
    flag_seed_config: bool,
    flag_markdown: bool,
    flag_color: ColorOptions,
}

/// Print page by path
fn print_page(path: &Path, enable_markdown: bool, config: &Config) -> Result<(), String> {
    // Open file
    let file = File::open(path).map_err(|msg| format!("Could not open file: {}", msg))?;
    let reader = BufReader::new(file);

    if enable_markdown {
        // Print the raw markdown of the file.
        for line in reader.lines() {
            println!("{}", line.unwrap());
        }
    } else {
        // Create tokenizer and print output
        let mut tokenizer = Tokenizer::new(reader);
        print_lines(&mut tokenizer, &config);
    };

    Ok(())
}

/// Set up display pager
#[cfg(not(target_os = "windows"))]
fn configure_pager() {
    Pager::with_default_pager(PAGER_COMMAND).setup();
}

#[cfg(target_os = "windows")]
fn configure_pager() {
    eprintln!("Warning: -p / --pager flag not available on Windows!");
}

fn should_update_cache(args: &Args, config: &Config) -> bool {
    args.flag_update
        || (config.updates.auto_update
            && Cache::last_update().map_or(true, |ago| ago >= config.updates.auto_update_interval))
}

/// Check the cache for freshness
fn check_cache(args: &Args, enable_styles: bool) {
    match Cache::last_update() {
        Some(ago) if ago > MAX_CACHE_AGE => {
            if args.flag_quiet {
                return;
            }

            // Only use color if enabled
            let warning_style = if enable_styles {
                Style::new().fg(Color::Yellow)
            } else {
                Style::default()
            };

            eprintln!(
                "{}",
                warning_style.paint(format!(
                    "The cache hasn't been updated for more than {} days.\n\
                         You should probably run `tldr --update` soon.",
                    MAX_CACHE_AGE.as_secs() / 24 / 3600
                ))
            );
        }
        Some(_) => {}
        None => {
            eprintln!("Cache not found. Please run `tldr --update`.");
            process::exit(1);
        }
    };
}

/// Clear the cache
fn clear_cache(quietly: bool) {
    Cache::clear().unwrap_or_else(|e| {
        match e {
            CacheError(msg) | ConfigError(msg) | UpdateError(msg) => {
                eprintln!("Could not delete cache: {}", msg)
            }
        };
        process::exit(1);
    });
    if !quietly {
        println!("Successfully deleted cache.");
    }
}

/// Update the cache
fn update_cache(cache: &Cache, quietly: bool) {
    cache.update().unwrap_or_else(|e| {
        match e {
            CacheError(msg) | ConfigError(msg) | UpdateError(msg) => {
                eprintln!("Could not update cache: {}", msg)
            }
        };
        process::exit(1);
    });
    if !quietly {
        println!("Successfully updated cache.");
    }
}

/// Show the config path
fn show_config_path() {
    match get_config_path() {
        Ok(config_file_path) => {
            println!("Config path is: {}", config_file_path.to_str().unwrap());
        }
        Err(ConfigError(msg)) => {
            eprintln!("Could not look up config_path: {}", msg);
            process::exit(1);
        }
        Err(_) => {
            eprintln!("Unknown error");
            process::exit(1);
        }
    }
}

/// Create seed config file and exit
fn create_config_and_exit() {
    match make_default_config() {
        Ok(config_file_path) => {
            println!(
                "Successfully created seed config file here: {}",
                config_file_path.to_str().unwrap()
            );
            process::exit(0);
        }
        Err(ConfigError(msg)) => {
            eprintln!("Could not create seed config: {}", msg);
            process::exit(1);
        }
        Err(_) => {
            eprintln!("Unknown error");
            process::exit(1);
        }
    }
}

#[cfg(feature = "logging")]
fn init_log() {
    env_logger::init();
}

#[cfg(not(feature = "logging"))]
fn init_log() {}

#[cfg(target_os = "linux")]
fn get_os() -> OsType {
    OsType::Linux
}

#[cfg(any(
    target_os = "macos",
    target_os = "freebsd",
    target_os = "netbsd",
    target_os = "openbsd",
    target_os = "dragonfly"
))]
fn get_os() -> OsType {
    OsType::OsX
}

#[cfg(target_os = "windows")]
fn get_os() -> OsType {
    OsType::Windows
}

#[cfg(not(any(
    target_os = "linux",
    target_os = "macos",
    target_os = "freebsd",
    target_os = "netbsd",
    target_os = "openbsd",
    target_os = "dragonfly",
    target_os = "windows"
)))]
fn get_os() -> OsType {
    OsType::Other
}

fn main() {
    // Initialize logger
    init_log();

    // Parse arguments
    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.deserialize())
        .unwrap_or_else(|e| e.exit());

    // Show version and exit
    if args.flag_version {
        let os = get_os();
        println!("{} v{} ({})", NAME, VERSION, os);
        process::exit(0);
    }

    // Show config file and path, pass through
    if args.flag_config_path {
        show_config_path();
    }

    // Create a basic config and exit
    if args.flag_seed_config {
        create_config_and_exit();
    }

    // Determine the usage of styles
    #[cfg(target_os = "windows")]
    let ansi_support = ansi_term::enable_ansi_support().is_ok();
    #[cfg(not(target_os = "windows"))]
    let ansi_support = true;

    let enable_styles = match args.flag_color {
        // Attempt to use styling if instructed
        ColorOptions::Always => true,
        // Enable styling if:
        // * There is `ansi_support`
        // * NO_COLOR env var isn't set: https://no-color.org/
        // * The output stream is stdout (not being piped)
        ColorOptions::Auto => {
            ansi_support && env::var_os("NO_COLOR").is_none() && atty::is(Stream::Stdout)
        }
        // Disable styling
        ColorOptions::Never => false,
    };

    // Look up config file, if none is found fall back to default config.
    let config = match Config::load(enable_styles) {
        Ok(config) => config,
        Err(ConfigError(msg)) => {
            eprintln!("Could not load config: {}", msg);
            process::exit(1);
        }
        Err(e) => {
            eprintln!("Could not load config: {}", e);
            process::exit(1);
        }
    };

    if args.flag_pager || config.display.use_pager {
        configure_pager();
    }

    // Specify target OS
    let os: OsType = match args.flag_os {
        Some(os) => os,
        None => get_os(),
    };

    // Initialize cache
    let cache = Cache::new(ARCHIVE_URL, os);

    // Clear cache, pass through
    if args.flag_clear_cache {
        clear_cache(args.flag_quiet);
    }

    // Update cache, pass through
    let cache_updated = if should_update_cache(&args, &config) {
        update_cache(&cache, args.flag_quiet);
        true
    } else {
        false
    };

    // Render local file and exit
    if let Some(ref file) = args.flag_render {
        let path = PathBuf::from(file);
        if let Err(msg) = print_page(&path, args.flag_markdown, &config) {
            eprintln!("{}", msg);
            process::exit(1);
        } else {
            process::exit(0);
        };
    }

    // List cached commands and exit
    if args.flag_list {
        if !cache_updated {
            // Check cache for freshness
            check_cache(&args, enable_styles);
        }

        // Get list of pages
        let pages = cache.list_pages().unwrap_or_else(|e| {
            match e {
                CacheError(msg) | ConfigError(msg) | UpdateError(msg) => {
                    eprintln!("Could not get list of pages: {}", msg)
                }
            }
            process::exit(1);
        });

        // Print pages
        println!("{}", pages.join("\n"));
        process::exit(0);
    }

    // Show command from cache
    if let Some(ref command) = args.arg_command {
        let command = command.join("-");

        if !cache_updated {
            // Check cache for freshness
            check_cache(&args, enable_styles);
        }

        // Search for command in cache
        if let Some(path) = cache.find_page(&command) {
            if let Err(msg) = print_page(&path, args.flag_markdown, &config) {
                eprintln!("{}", msg);
                process::exit(1);
            } else {
                process::exit(0);
            }
        } else {
            if !args.flag_quiet {
                println!("Page {} not found in cache", &command);
                println!("Try updating with `tldr --update`, or submit a pull request to:");
                println!("https://github.com/tldr-pages/tldr");
            }
            process::exit(1);
        }
    }

    // Some flags can be run without a command.
    if !(args.flag_update || args.flag_clear_cache || args.flag_config_path) {
        eprintln!("{}", USAGE);
        process::exit(1);
    }
}

#[cfg(test)]
mod test {
    use crate::{Args, OsType, USAGE};
    use docopt::{Docopt, Error};

    fn test_helper(argv: &[&str]) -> Result<Args, Error> {
        Docopt::new(USAGE).and_then(|d| d.argv(argv.iter()).deserialize())
    }

    #[test]
    fn test_docopt_os_case_insensitive() {
        let argv = vec!["cp", "--os", "LiNuX"];
        let os = test_helper(&argv).unwrap().flag_os.unwrap();
        assert_eq!(OsType::Linux, os);
    }

    #[test]
    fn test_docopt_expect_error() {
        let argv = vec!["cp", "--os", "lindows"];
        assert!(!test_helper(&argv).is_ok());
    }
}
<commit_msg>Disable clippy::too_many_lines warning<commit_after>//! An implementation of [tldr](https://github.com/tldr-pages/tldr) in Rust.
//
// Copyright (c) 2015-2020 tealdeer developers
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. All files in the project carrying such notice may not be
// copied, modified, or distributed except according to those terms.

#![deny(clippy::all)]
#![warn(clippy::pedantic)]
#![allow(clippy::similar_names)]
#![allow(clippy::module_name_repetitions)]
#![allow(clippy::too_many_lines)]

#[cfg(feature = "logging")]
extern crate env_logger;

use std::env;
use std::fs::File;
use std::io::BufRead;
use std::io::BufReader;
use std::path::{Path, PathBuf};
use std::process;

use ansi_term::{Color, Style};
use app_dirs::AppInfo;
use atty::Stream;
use docopt::Docopt;
#[cfg(not(target_os = "windows"))]
use pager::Pager;
use serde_derive::Deserialize;

mod cache;
mod config;
mod error;
mod formatter;
mod tokenizer;
mod types;

use crate::cache::Cache;
use crate::config::{get_config_path, make_default_config, Config, MAX_CACHE_AGE};
use crate::error::TealdeerError::{CacheError, ConfigError, UpdateError};
use crate::formatter::print_lines;
use crate::tokenizer::Tokenizer;
use crate::types::{ColorOptions, OsType};

const NAME: &str = "tealdeer";
const APP_INFO: AppInfo = AppInfo {
    name: NAME,
    author: NAME,
};
const VERSION: &str = env!("CARGO_PKG_VERSION");
const USAGE: &str = "
Usage:

    tldr [options] <command>...
    tldr [options]

Options:

    -h --help           Show this screen
    -v --version        Show version information
    -l --list           List all commands in the cache
    -f --render <file>  Render a specific markdown file
    -o --os <type>      Override the operating system [linux, osx, sunos, windows]
    -u --update         Update the local cache
    -c --clear-cache    Clear the local cache
    -p --pager          Use a pager to page output
    -m --markdown       Display the raw markdown instead of rendering it
    -q --quiet          Suppress informational messages
    --config-path       Show config file path
    --seed-config       Create a basic config
    --color <when>      Control when to use color [always, auto, never] [default: auto]

Examples:

    $ tldr tar
    $ tldr --list

To control the cache:

    $ tldr --update
    $ tldr --clear-cache

To render a local file (for testing):

    $ tldr --render /path/to/file.md
";
const ARCHIVE_URL: &str = "https://github.com/tldr-pages/tldr/archive/master.tar.gz";
#[cfg(not(target_os = "windows"))]
const PAGER_COMMAND: &str = "less -R";

#[derive(Debug, Deserialize)]
#[allow(clippy::struct_excessive_bools)]
struct Args {
    arg_command: Option<Vec<String>>,
    flag_help: bool,
    flag_version: bool,
    flag_list: bool,
    flag_render: Option<String>,
    flag_os: Option<OsType>,
    flag_update: bool,
    flag_clear_cache: bool,
    flag_pager: bool,
    flag_quiet: bool,
    flag_config_path: bool,
    flag_seed_config: bool,
    flag_markdown: bool,
    flag_color: ColorOptions,
}

/// Print page by path
fn print_page(path: &Path, enable_markdown: bool, config: &Config) -> Result<(), String> {
    // Open file
    let file = File::open(path).map_err(|msg| format!("Could not open file: {}", msg))?;
    let reader = BufReader::new(file);

    if enable_markdown {
        // Print the raw markdown of the file.
        for line in reader.lines() {
            println!("{}", line.unwrap());
        }
    } else {
        // Create tokenizer and print output
        let mut tokenizer = Tokenizer::new(reader);
        print_lines(&mut tokenizer, &config);
    };

    Ok(())
}

/// Set up display pager
#[cfg(not(target_os = "windows"))]
fn configure_pager() {
    Pager::with_default_pager(PAGER_COMMAND).setup();
}

#[cfg(target_os = "windows")]
fn configure_pager() {
    eprintln!("Warning: -p / --pager flag not available on Windows!");
}

fn should_update_cache(args: &Args, config: &Config) -> bool {
    args.flag_update
        || (config.updates.auto_update
            && Cache::last_update().map_or(true, |ago| ago >= config.updates.auto_update_interval))
}

/// Check the cache for freshness
fn check_cache(args: &Args, enable_styles: bool) {
    match Cache::last_update() {
        Some(ago) if ago > MAX_CACHE_AGE => {
            if args.flag_quiet {
                return;
            }

            // Only use color if enabled
            let warning_style = if enable_styles {
                Style::new().fg(Color::Yellow)
            } else {
                Style::default()
            };

            eprintln!(
                "{}",
                warning_style.paint(format!(
                    "The cache hasn't been updated for more than {} days.\n\
                         You should probably run `tldr --update` soon.",
                    MAX_CACHE_AGE.as_secs() / 24 / 3600
                ))
            );
        }
        Some(_) => {}
        None => {
            eprintln!("Cache not found. Please run `tldr --update`.");
            process::exit(1);
        }
    };
}

/// Clear the cache
fn clear_cache(quietly: bool) {
    Cache::clear().unwrap_or_else(|e| {
        match e {
            CacheError(msg) | ConfigError(msg) | UpdateError(msg) => {
                eprintln!("Could not delete cache: {}", msg)
            }
        };
        process::exit(1);
    });
    if !quietly {
        println!("Successfully deleted cache.");
    }
}

/// Update the cache
fn update_cache(cache: &Cache, quietly: bool) {
    cache.update().unwrap_or_else(|e| {
        match e {
            CacheError(msg) | ConfigError(msg) | UpdateError(msg) => {
                eprintln!("Could not update cache: {}", msg)
            }
        };
        process::exit(1);
    });
    if !quietly {
        println!("Successfully updated cache.");
    }
}

/// Show the config path
fn show_config_path() {
    match get_config_path() {
        Ok(config_file_path) => {
            println!("Config path is: {}", config_file_path.to_str().unwrap());
        }
        Err(ConfigError(msg)) => {
            eprintln!("Could not look up config_path: {}", msg);
            process::exit(1);
        }
        Err(_) => {
            eprintln!("Unknown error");
            process::exit(1);
        }
    }
}

/// Create seed config file and exit
fn create_config_and_exit() {
    match make_default_config() {
        Ok(config_file_path) => {
            println!(
                "Successfully created seed config file here: {}",
                config_file_path.to_str().unwrap()
            );
            process::exit(0);
        }
        Err(ConfigError(msg)) => {
            eprintln!("Could not create seed config: {}", msg);
            process::exit(1);
        }
        Err(_) => {
            eprintln!("Unknown error");
            process::exit(1);
        }
    }
}

#[cfg(feature = "logging")]
fn init_log() {
    env_logger::init();
}

#[cfg(not(feature = "logging"))]
fn init_log() {}

#[cfg(target_os = "linux")]
fn get_os() -> OsType {
    OsType::Linux
}

#[cfg(any(
    target_os = "macos",
    target_os = "freebsd",
    target_os = "netbsd",
    target_os = "openbsd",
    target_os = "dragonfly"
))]
fn get_os() -> OsType {
    OsType::OsX
}

#[cfg(target_os = "windows")]
fn get_os() -> OsType {
    OsType::Windows
}

#[cfg(not(any(
    target_os = "linux",
    target_os = "macos",
    target_os = "freebsd",
    target_os = "netbsd",
    target_os = "openbsd",
    target_os = "dragonfly",
    target_os = "windows"
)))]
fn get_os() -> OsType {
    OsType::Other
}

fn main() {
    // Initialize logger
    init_log();

    // Parse arguments
    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.deserialize())
        .unwrap_or_else(|e| e.exit());

    // Show version and exit
    if args.flag_version {
        let os = get_os();
        println!("{} v{} ({})", NAME, VERSION, os);
        process::exit(0);
    }

    // Show config file and path, pass through
    if args.flag_config_path {
        show_config_path();
    }

    // Create a basic config and exit
    if args.flag_seed_config {
        create_config_and_exit();
    }

    // Determine the usage of styles
    #[cfg(target_os = "windows")]
    let ansi_support = ansi_term::enable_ansi_support().is_ok();
    #[cfg(not(target_os = "windows"))]
    let ansi_support = true;

    let enable_styles = match args.flag_color {
        // Attempt to use styling if instructed
        ColorOptions::Always => true,
        // Enable styling if:
        // * There is `ansi_support`
        // * NO_COLOR env var isn't set: https://no-color.org/
        // * The output stream is stdout (not being piped)
        ColorOptions::Auto => {
            ansi_support && env::var_os("NO_COLOR").is_none() && atty::is(Stream::Stdout)
        }
        // Disable styling
        ColorOptions::Never => false,
    };

    // Look up config file, if none is found fall back to default config.
    let config = match Config::load(enable_styles) {
        Ok(config) => config,
        Err(ConfigError(msg)) => {
            eprintln!("Could not load config: {}", msg);
            process::exit(1);
        }
        Err(e) => {
            eprintln!("Could not load config: {}", e);
            process::exit(1);
        }
    };

    if args.flag_pager || config.display.use_pager {
        configure_pager();
    }

    // Specify target OS
    let os: OsType = match args.flag_os {
        Some(os) => os,
        None => get_os(),
    };

    // Initialize cache
    let cache = Cache::new(ARCHIVE_URL, os);

    // Clear cache, pass through
    if args.flag_clear_cache {
        clear_cache(args.flag_quiet);
    }

    // Update cache, pass through
    let cache_updated = if should_update_cache(&args, &config) {
        update_cache(&cache, args.flag_quiet);
        true
    } else {
        false
    };

    // Render local file and exit
    if let Some(ref file) = args.flag_render {
        let path = PathBuf::from(file);
        if let Err(msg) = print_page(&path, args.flag_markdown, &config) {
            eprintln!("{}", msg);
            process::exit(1);
        } else {
            process::exit(0);
        };
    }

    // List cached commands and exit
    if args.flag_list {
        if !cache_updated {
            // Check cache for freshness
            check_cache(&args, enable_styles);
        }

        // Get list of pages
        let pages = cache.list_pages().unwrap_or_else(|e| {
            match e {
                CacheError(msg) | ConfigError(msg) | UpdateError(msg) => {
                    eprintln!("Could not get list of pages: {}", msg)
                }
            }
            process::exit(1);
        });

        // Print pages
        println!("{}", pages.join("\n"));
        process::exit(0);
    }

    // Show command from cache
    if let Some(ref command) = args.arg_command {
        let command = command.join("-");

        if !cache_updated {
            // Check cache for freshness
            check_cache(&args, enable_styles);
        }

        // Search for command in cache
        if let Some(path) = cache.find_page(&command) {
            if let Err(msg) = print_page(&path, args.flag_markdown, &config) {
                eprintln!("{}", msg);
                process::exit(1);
            } else {
                process::exit(0);
            }
        } else {
            if !args.flag_quiet {
                println!("Page {} not found in cache", &command);
                println!("Try updating with `tldr --update`, or submit a pull request to:");
                println!("https://github.com/tldr-pages/tldr");
            }
            process::exit(1);
        }
    }

    // Some flags can be run without a command.
    if !(args.flag_update || args.flag_clear_cache || args.flag_config_path) {
        eprintln!("{}", USAGE);
        process::exit(1);
    }
}

#[cfg(test)]
mod test {
    use crate::{Args, OsType, USAGE};
    use docopt::{Docopt, Error};

    fn test_helper(argv: &[&str]) -> Result<Args, Error> {
        Docopt::new(USAGE).and_then(|d| d.argv(argv.iter()).deserialize())
    }

    #[test]
    fn test_docopt_os_case_insensitive() {
        let argv = vec!["cp", "--os", "LiNuX"];
        let os = test_helper(&argv).unwrap().flag_os.unwrap();
        assert_eq!(OsType::Linux, os);
    }

    #[test]
    fn test_docopt_expect_error() {
        let argv = vec!["cp", "--os", "lindows"];
        assert!(!test_helper(&argv).is_ok());
    }
}
<|endoftext|>


<commit_before>#![feature(proc_macro)]

extern crate hyper;
extern crate iron;
extern crate regex;
extern crate router;
extern crate rustc_serialize;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
extern crate scoped_threadpool;
extern crate tera;

use std::collections::{BTreeMap, HashMap};
use std::env;
use std::sync::{Arc, RwLock};
use std::thread::sleep;
use std::time::{Instant, Duration, SystemTime, UNIX_EPOCH};

use iron::headers::ContentType;
use iron::prelude::*;
use iron::status;
use router::Router;
use scoped_threadpool::Pool;
use tera::{Context, Tera};

pub mod battle_net_api_client;
pub mod thread_throttler;

use battle_net_api_client::{AuctionListing, BattleNetApiClient};

/// Represents a single option available for sale from the blood vendor.
#[derive(Debug, Deserialize)]
struct BloodVendorItem {
    name: String,
    quantity: u64,
    id: u64,
}

/// Value of an item on a realm.
#[derive(Debug)]
struct ItemValue {
    id: u64,
    value: u64,
}

/// The calculated values for items on a particular realm.
#[derive(Debug)]
struct CurrentRealmValues {
    last_update: u64,  // The last time we got this info, as reported by the Blizzard API.
    auction_values: Arc<Vec<ItemValue>>,  // Should be sorted by value.
}

/// All the data in a single row in our price list for a realm.
#[derive(Debug, Serialize)]
struct PriceRow {
    name: String,
    quantity: u64,
    icon: String,
    value_ratio: u64,
    gold: u64,
    silver: u64,
    copper: u64,
}

/// Number of threads to use when fetching auction house results.
const NUM_AUCTION_DATA_THREADS: u32 = 5;

/// Number of seconds to wait between fetching new auction results.
const RESULT_FETCH_PERIOD: u64 = 60 * 30;

/// Given a vec of auction listings for a realm and a map of the items we care about,
/// returns a vec of (item_id, value) sorted by decreasing value, where value is
/// based on the 5th percentile buyout price.
fn calculate_auction_values(listings: &Vec<AuctionListing>, items: &HashMap<u64, BloodVendorItem>) -> Vec<ItemValue> {
    // Calculate 5th percentiles for the items we care about.
    let mut price_points: BTreeMap<u64, Vec<(u64, u64)>> = BTreeMap::new();
    for listing in listings {
        if items.contains_key(&listing.item) && listing.buyout > 0 {
            price_points.entry(listing.item).or_insert(Vec::new()).push((listing.quantity, listing.buyout / listing.quantity));
        }
    }
    for quantities_and_buyouts in price_points.values_mut() {
        quantities_and_buyouts.sort_by_key(|a| a.1);
    }
    let total_item_quantities: BTreeMap<u64, u64> =
        price_points.iter().map(|(k, v)| {
            (*k, v.iter().fold(0, |sum, quantity_and_buyout| sum + quantity_and_buyout.0))
        }).collect();
    let fifth_percentile_price_points: BTreeMap<u64, u64> =
        price_points.iter().map(|(item_id, ref item_listings)| {
            let fifth_percentile_quantity = total_item_quantities.get(item_id).unwrap() / 20;
            let mut running_sum: u64 = 0;
            let fifth_percentile_listing = item_listings.iter().find(|&&(quantity, _)| {
                running_sum += quantity;
                running_sum >= fifth_percentile_quantity
            }).unwrap();
            (*item_id, fifth_percentile_listing.1)
        }).collect();
    let mut item_values: Vec<ItemValue> = items.values().map(|item|
        ItemValue {
            id: item.id,
            value: fifth_percentile_price_points.get(&item.id).unwrap_or(&0u64) * item.quantity,
        }
    ).collect();
    item_values.sort_by_key(|item_value| !item_value.value);
    item_values
}

fn main() {
    let token = match env::args().nth(1) {
        Some(token) => token,
        None => {
            println!("Usage: bloodmoney <api token>");
            return;
        }
    };
    let client = Arc::new(BattleNetApiClient::new(&token));
    // Process our item options and grab their icon names.
    let items: Vec<BloodVendorItem> = serde_json::from_str(include_str!("../catalog/items.json"))
        .expect("Error reading items.");
    let item_id_map: Arc<HashMap<u64, BloodVendorItem>> = Arc::new(items.into_iter().map(|x| (x.id, x)).collect());
    let item_icons: Arc<HashMap<u64, String>> = Arc::new(item_id_map.keys().map(|&id| (id, client.get_item_info(id).icon)).collect());

    // Get the list of realms and create an empty price map so we can render pages while
    // waiting for the auction results to be retrieved.
    let realms = Arc::new(client.get_realms());
    let connected_realms = BattleNetApiClient::process_connected_realms(&realms);
    let price_map: Arc<BTreeMap<String, RwLock<CurrentRealmValues>>> =
        Arc::new(realms.iter().map(|realm| (realm.slug.clone(), RwLock::new(CurrentRealmValues {
            last_update: 0,
            auction_values: Arc::new(item_id_map.keys().map(|id| ItemValue{id: *id, value: 0u64}).collect()),
        }))).collect());

    // Set up our web-app.
    let tera = Arc::new(Tera::new("templates/**/*"));
    let mut router = Router::new();
    {
        let realms = realms.clone();
        let tera = tera.clone();
        router.get("/blood-money", move |_: &mut Request| {
            let mut context = Context::new();
            context.add("realms", &realms);
            Ok(Response::with((ContentType::html().0, status::Ok, tera.render("index.html", context).unwrap())))
        }, "index");
    }
    {
        let price_map = price_map.clone();
        let item_id_map = item_id_map.clone();
        let realms = realms.clone();
        let tera = tera.clone();
        router.get("/blood-money/:realm", move |req : &mut Request| {
            let realm = req.extensions.get::<Router>().unwrap().find("realm").unwrap();
            if let Some(realm_prices_lock) = price_map.get(realm) {
                let mut context = Context::new();
                let realm_prices = realm_prices_lock.read().unwrap();
                // Build up a list of entries.
                let highest_value = realm_prices.auction_values.get(0).unwrap().value;
                let price_rows: Vec<PriceRow> = realm_prices.auction_values.iter().map(|&ItemValue{id, value}| {
                    let item_info = item_id_map.get(&id).unwrap();
                    let gold = value / (10_000);
                    let silver = (value - gold * 10_000) / 100;
                    let copper = value - gold * 10_000 - silver * 100;
                    let value_ratio = match highest_value {
                        0u64 => 0u64,
                        _ => value*100/highest_value,  // Percentile!
                    };
                    PriceRow {
                        name: item_info.name.clone(),
                        quantity: item_info.quantity,
                        icon: item_icons.get(&id).unwrap().clone(),
                        value_ratio: value_ratio,
                        gold: gold,
                        silver: silver,
                        copper: copper,
                    }
                }).collect();
                context.add("realm_name", &realms.iter().find(|&realm_info| &realm_info.slug == realm).unwrap().name);
                context.add("price_rows", &price_rows);
                // TODO: Change this to something more human readable.
                if realm_prices.last_update == 0 {
                    context.add("update_age", &-1);
                } else {
                    context.add("update_age",
                        &(SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() - realm_prices.last_update / 1000));
                }
                Ok(Response::with((ContentType::html().0, status::Ok, tera.render("prices.html", context).unwrap())))
            } else {
                return Ok(Response::with(status::NotFound));
            }
        }, "realm-prices");
    }
    let http_result = Iron::new(router).http("localhost:3000");
    println!("Ready for web traffic.");

    // Now that the webserver is up, periodically fetch
    // new auction house data.
    let mut pool = Pool::new(NUM_AUCTION_DATA_THREADS);
    loop {
        let download_start = Instant::now();
        let next_download_time = download_start + Duration::from_secs(RESULT_FETCH_PERIOD);
        println!("Starting download of auction data.");
        pool.scoped(|scope| {
            for realm_list in &connected_realms {
                // We have to move realm_list into the closure.
                // Clone other values.
                let client = client.clone();
                let price_map = price_map.clone();
                let item_id_map = item_id_map.clone();
                scope.execute(move || {
                    let lead_realm = realm_list.get(0).unwrap();
                    let update_time: u64;
                    let auction_listings: Vec<AuctionListing>;
                    println!("Downloading {}", lead_realm);
                    {
                        let current_realm_values =
                            price_map.get(lead_realm).unwrap().read().unwrap();
                        match client.get_auction_listings(lead_realm, current_realm_values.last_update) {
                            Some((ts, al)) => {
                                update_time = ts;
                                auction_listings = al;
                            },
                            None => return,
                        }
                    }
                    let auction_values = Arc::new(calculate_auction_values(&auction_listings, &item_id_map));
                    for realm in realm_list {
                        println!("Updating {}", realm);
                        let mut current_realm_values =
                            price_map.get(realm).unwrap().write().unwrap();
                        current_realm_values.auction_values = auction_values.clone();
                        current_realm_values.last_update = update_time;
                    }
                })
            }
            scope.join_all();
        });
        let download_end_time = Instant::now();
        println!("Downloading all realms took {} seconds.", download_end_time.duration_since(download_start).as_secs());
        if download_end_time < next_download_time {
            println!("Sleeping for {}", next_download_time.duration_since(download_end_time).as_secs());
            sleep(next_download_time.duration_since(download_end_time));
        }
    }
}
<commit_msg>Changed binding to *:80<commit_after>#![feature(proc_macro)]

extern crate hyper;
extern crate iron;
extern crate regex;
extern crate router;
extern crate rustc_serialize;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
extern crate scoped_threadpool;
extern crate tera;

use std::collections::{BTreeMap, HashMap};
use std::env;
use std::sync::{Arc, RwLock};
use std::thread::sleep;
use std::time::{Instant, Duration, SystemTime, UNIX_EPOCH};

use iron::headers::ContentType;
use iron::prelude::*;
use iron::status;
use router::Router;
use scoped_threadpool::Pool;
use tera::{Context, Tera};

pub mod battle_net_api_client;
pub mod thread_throttler;

use battle_net_api_client::{AuctionListing, BattleNetApiClient};

/// Represents a single option available for sale from the blood vendor.
#[derive(Debug, Deserialize)]
struct BloodVendorItem {
    name: String,
    quantity: u64,
    id: u64,
}

/// Value of an item on a realm.
#[derive(Debug)]
struct ItemValue {
    id: u64,
    value: u64,
}

/// The calculated values for items on a particular realm.
#[derive(Debug)]
struct CurrentRealmValues {
    last_update: u64,  // The last time we got this info, as reported by the Blizzard API.
    auction_values: Arc<Vec<ItemValue>>,  // Should be sorted by value.
}

/// All the data in a single row in our price list for a realm.
#[derive(Debug, Serialize)]
struct PriceRow {
    name: String,
    quantity: u64,
    icon: String,
    value_ratio: u64,
    gold: u64,
    silver: u64,
    copper: u64,
}

/// Number of threads to use when fetching auction house results.
const NUM_AUCTION_DATA_THREADS: u32 = 5;

/// Number of seconds to wait between fetching new auction results.
const RESULT_FETCH_PERIOD: u64 = 60 * 30;

/// Given a vec of auction listings for a realm and a map of the items we care about,
/// returns a vec of (item_id, value) sorted by decreasing value, where value is
/// based on the 5th percentile buyout price.
fn calculate_auction_values(listings: &Vec<AuctionListing>, items: &HashMap<u64, BloodVendorItem>) -> Vec<ItemValue> {
    // Calculate 5th percentiles for the items we care about.
    let mut price_points: BTreeMap<u64, Vec<(u64, u64)>> = BTreeMap::new();
    for listing in listings {
        if items.contains_key(&listing.item) && listing.buyout > 0 {
            price_points.entry(listing.item).or_insert(Vec::new()).push((listing.quantity, listing.buyout / listing.quantity));
        }
    }
    for quantities_and_buyouts in price_points.values_mut() {
        quantities_and_buyouts.sort_by_key(|a| a.1);
    }
    let total_item_quantities: BTreeMap<u64, u64> =
        price_points.iter().map(|(k, v)| {
            (*k, v.iter().fold(0, |sum, quantity_and_buyout| sum + quantity_and_buyout.0))
        }).collect();
    let fifth_percentile_price_points: BTreeMap<u64, u64> =
        price_points.iter().map(|(item_id, ref item_listings)| {
            let fifth_percentile_quantity = total_item_quantities.get(item_id).unwrap() / 20;
            let mut running_sum: u64 = 0;
            let fifth_percentile_listing = item_listings.iter().find(|&&(quantity, _)| {
                running_sum += quantity;
                running_sum >= fifth_percentile_quantity
            }).unwrap();
            (*item_id, fifth_percentile_listing.1)
        }).collect();
    let mut item_values: Vec<ItemValue> = items.values().map(|item|
        ItemValue {
            id: item.id,
            value: fifth_percentile_price_points.get(&item.id).unwrap_or(&0u64) * item.quantity,
        }
    ).collect();
    item_values.sort_by_key(|item_value| !item_value.value);
    item_values
}

fn main() {
    let token = match env::args().nth(1) {
        Some(token) => token,
        None => {
            println!("Usage: bloodmoney <api token>");
            return;
        }
    };
    let client = Arc::new(BattleNetApiClient::new(&token));
    // Process our item options and grab their icon names.
    let items: Vec<BloodVendorItem> = serde_json::from_str(include_str!("../catalog/items.json"))
        .expect("Error reading items.");
    let item_id_map: Arc<HashMap<u64, BloodVendorItem>> = Arc::new(items.into_iter().map(|x| (x.id, x)).collect());
    let item_icons: Arc<HashMap<u64, String>> = Arc::new(item_id_map.keys().map(|&id| (id, client.get_item_info(id).icon)).collect());

    // Get the list of realms and create an empty price map so we can render pages while
    // waiting for the auction results to be retrieved.
    let realms = Arc::new(client.get_realms());
    let connected_realms = BattleNetApiClient::process_connected_realms(&realms);
    let price_map: Arc<BTreeMap<String, RwLock<CurrentRealmValues>>> =
        Arc::new(realms.iter().map(|realm| (realm.slug.clone(), RwLock::new(CurrentRealmValues {
            last_update: 0,
            auction_values: Arc::new(item_id_map.keys().map(|id| ItemValue{id: *id, value: 0u64}).collect()),
        }))).collect());

    // Set up our web-app.
    let tera = Arc::new(Tera::new("templates/**/*"));
    let mut router = Router::new();
    {
        let realms = realms.clone();
        let tera = tera.clone();
        router.get("/blood-money", move |_: &mut Request| {
            let mut context = Context::new();
            context.add("realms", &realms);
            Ok(Response::with((ContentType::html().0, status::Ok, tera.render("index.html", context).unwrap())))
        }, "index");
    }
    {
        let price_map = price_map.clone();
        let item_id_map = item_id_map.clone();
        let realms = realms.clone();
        let tera = tera.clone();
        router.get("/blood-money/:realm", move |req : &mut Request| {
            let realm = req.extensions.get::<Router>().unwrap().find("realm").unwrap();
            if let Some(realm_prices_lock) = price_map.get(realm) {
                let mut context = Context::new();
                let realm_prices = realm_prices_lock.read().unwrap();
                // Build up a list of entries.
                let highest_value = realm_prices.auction_values.get(0).unwrap().value;
                let price_rows: Vec<PriceRow> = realm_prices.auction_values.iter().map(|&ItemValue{id, value}| {
                    let item_info = item_id_map.get(&id).unwrap();
                    let gold = value / (10_000);
                    let silver = (value - gold * 10_000) / 100;
                    let copper = value - gold * 10_000 - silver * 100;
                    let value_ratio = match highest_value {
                        0u64 => 0u64,
                        _ => value*100/highest_value,  // Percentile!
                    };
                    PriceRow {
                        name: item_info.name.clone(),
                        quantity: item_info.quantity,
                        icon: item_icons.get(&id).unwrap().clone(),
                        value_ratio: value_ratio,
                        gold: gold,
                        silver: silver,
                        copper: copper,
                    }
                }).collect();
                context.add("realm_name", &realms.iter().find(|&realm_info| &realm_info.slug == realm).unwrap().name);
                context.add("price_rows", &price_rows);
                // TODO: Change this to something more human readable.
                if realm_prices.last_update == 0 {
                    context.add("update_age", &-1);
                } else {
                    context.add("update_age",
                        &(SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() - realm_prices.last_update / 1000));
                }
                Ok(Response::with((ContentType::html().0, status::Ok, tera.render("prices.html", context).unwrap())))
            } else {
                return Ok(Response::with(status::NotFound));
            }
        }, "realm-prices");
    }
    let http_result = Iron::new(router).http("0.0.0.0:80");
    println!("Ready for web traffic.");

    // Now that the webserver is up, periodically fetch
    // new auction house data.
    let mut pool = Pool::new(NUM_AUCTION_DATA_THREADS);
    loop {
        let download_start = Instant::now();
        let next_download_time = download_start + Duration::from_secs(RESULT_FETCH_PERIOD);
        println!("Starting download of auction data.");
        pool.scoped(|scope| {
            for realm_list in &connected_realms {
                // We have to move realm_list into the closure.
                // Clone other values.
                let client = client.clone();
                let price_map = price_map.clone();
                let item_id_map = item_id_map.clone();
                scope.execute(move || {
                    let lead_realm = realm_list.get(0).unwrap();
                    let update_time: u64;
                    let auction_listings: Vec<AuctionListing>;
                    println!("Downloading {}", lead_realm);
                    {
                        let current_realm_values =
                            price_map.get(lead_realm).unwrap().read().unwrap();
                        match client.get_auction_listings(lead_realm, current_realm_values.last_update) {
                            Some((ts, al)) => {
                                update_time = ts;
                                auction_listings = al;
                            },
                            None => return,
                        }
                    }
                    let auction_values = Arc::new(calculate_auction_values(&auction_listings, &item_id_map));
                    for realm in realm_list {
                        println!("Updating {}", realm);
                        let mut current_realm_values =
                            price_map.get(realm).unwrap().write().unwrap();
                        current_realm_values.auction_values = auction_values.clone();
                        current_realm_values.last_update = update_time;
                    }
                })
            }
            scope.join_all();
        });
        let download_end_time = Instant::now();
        println!("Downloading all realms took {} seconds.", download_end_time.duration_since(download_start).as_secs());
        if download_end_time < next_download_time {
            println!("Sleeping for {}", next_download_time.duration_since(download_end_time).as_secs());
            sleep(next_download_time.duration_since(download_end_time));
        }
    }
}
<|endoftext|>


<commit_before>#![feature(collections)]
#![feature(convert)]
#![feature(core)]

extern crate toml;
extern crate iron;
extern crate router;
extern crate core;

use std::fs::File;
use std::io::Read;
use core::convert::From as ConvertFrom;

//use iron::Iron;
use iron::prelude::*;
use iron::headers::*;
use iron::{status, headers};

//use core::str::Str;

use router::{Router};

use liquidwrapper::LiquidTemplate;
use templatewrapper::TemplateWrapper;

mod templatewrapper;
mod liquidwrapper;

#[derive(Clone)]
struct SbWikiServer {
    listenaddr: String,
    wikipath: String,
    wikifrontpage: String,
}

impl SbWikiServer {
    //TODO: pass config object instead of bunch of argument
    pub fn new(cfgfile: &'static str) -> SbWikiServer {
        let mut listenaddr    = String::new();
        let wikipath      = String::from("/wiki");
        let wikifrontpage = String::from("FrontPage");

        //loading toml configure file
        let mut confs = String::new();
        File::open(cfgfile).unwrap().read_to_string(&mut confs);
        let mut parser = toml::Parser::new(confs.as_str());

        //add portnum to address
        let root = parser.parse().unwrap();

        let port = root["server"].lookup("port").unwrap();
        let host = root["server"].lookup("host").unwrap();

        let portstr = format!("{}:{}",
                              host.as_str().unwrap(),
                              port.as_integer().unwrap());

        listenaddr.push_str(portstr.as_str());

        println!("{}", listenaddr);

        SbWikiServer {
            listenaddr: listenaddr,
            wikipath: wikipath,
            wikifrontpage: wikifrontpage,
        }
    }

    pub fn open(self, isTLS: bool) {
        let mut wikidocument = self.wikipath.clone();
        wikidocument.push_str("/*");

        let mut router = Router::new();
        //TODO: fetch it from toml config.
        let wpath = self.wikipath.clone();
        let listenaddr = self.listenaddr.clone();

        let cloned = self.clone();
        router.get(wpath.as_str(),
                   move |req: &mut Request| -> IronResult<Response>
                   {cloned.wikiredirect(req)});

        let cloned = self.clone();
        router.get(wikidocument.as_str(),
                   move |req: &mut Request| -> IronResult<Response>
                   {cloned.wikihandler(req)});

        let cloned = self.clone();
        router.get("/",
                   move |req: &mut Request| -> IronResult<Response>
                   {cloned.roothandler(req)});

        let cloned = self.clone();
        router.get("/:query",
                   move |req: &mut Request| -> IronResult<Response>
                   {cloned.roothandler(req)});

        Iron::new(router).http(
            listenaddr.as_str()).unwrap();
    }

    fn roothandler(&self, req: &mut Request)
                   -> IronResult<Response> {
        let ref query = req.extensions.get::<Router>()
            .unwrap().find("query").unwrap_or("/");

        Ok(Response::with((status::Ok, *query)))
    }

    fn wikiredirect(&self, req: &mut Request)
                    -> IronResult<Response> {
        let mut url = req.url.path.clone();
        let mut header = Headers::new();
        let mut newpath = String::new();

        //TODO: customizable root page.
        url.push(self.wikifrontpage.clone());

        for i in url.iter() {
            newpath.push_str("/");
            newpath.push_str(i);
        }

        println!("{}", newpath);
        header.set(Location(newpath));

        let mut resp: Response = Response::with((status::MovedPermanently));
        resp.headers = header;

        Ok(resp)
    }

    fn wikihandler(&self, req: &mut Request)
                   -> IronResult<Response> {
        let ref requrl = req.url.clone();
        let mut urlpath = requrl.path.clone();

        for i in urlpath.iter() {
            println!("{}", i);
        }

        Ok(Response::with((status::Ok, "Watch console.")))
    }
}

fn main() {
    //TODO: make it can fetch config file name from command line argument.
    let sbwiki: SbWikiServer = SbWikiServer::new("config.toml");

    sbwiki.open(false);

    let template: LiquidTemplate = LiquidTemplate::new(String::from("hello.liquid"));
}
<commit_msg>Use render function (in LiquidTemplate) in Main function<commit_after>#![feature(collections)]
#![feature(convert)]
#![feature(core)]

extern crate toml;
extern crate iron;
extern crate router;
extern crate core;
extern crate liquid;

use std::fs::File;
use std::io::Read;
use core::convert::From as ConvertFrom;

//use iron::Iron;
use iron::prelude::*;
use iron::headers::*;
use iron::{status, headers};

//use core::str::Str;

use router::{Router};

use liquid::Context;
use liquid::Renderable;
use liquid::Value;

use liquidwrapper::LiquidTemplate;
use templatewrapper::TemplateWrapper;

mod templatewrapper;
mod liquidwrapper;

#[derive(Clone)]
struct SbWikiServer {
    listenaddr: String,
    wikipath: String,
    wikifrontpage: String,
}

impl SbWikiServer {
    //TODO: pass config object instead of bunch of argument
    pub fn new(cfgfile: &'static str) -> SbWikiServer {
        let mut listenaddr    = String::new();
        let wikipath      = String::from("/wiki");
        let wikifrontpage = String::from("FrontPage");

        //loading toml configure file
        let mut confs = String::new();
        File::open(cfgfile).unwrap().read_to_string(&mut confs);
        let mut parser = toml::Parser::new(confs.as_str());

        //add portnum to address
        let root = parser.parse().unwrap();

        let port = root["server"].lookup("port").unwrap();
        let host = root["server"].lookup("host").unwrap();

        let portstr = format!("{}:{}",
                              host.as_str().unwrap(),
                              port.as_integer().unwrap());

        listenaddr.push_str(portstr.as_str());

        println!("{}", listenaddr);

        SbWikiServer {
            listenaddr: listenaddr,
            wikipath: wikipath,
            wikifrontpage: wikifrontpage,
        }
    }

    pub fn open(self, isTLS: bool) {
        let mut wikidocument = self.wikipath.clone();
        wikidocument.push_str("/*");

        let mut router = Router::new();
        //TODO: fetch it from toml config.
        let wpath = self.wikipath.clone();
        let listenaddr = self.listenaddr.clone();

        let cloned = self.clone();
        router.get(wpath.as_str(),
                   move |req: &mut Request| -> IronResult<Response>
                   {cloned.wikiredirect(req)});

        let cloned = self.clone();
        router.get(wikidocument.as_str(),
                   move |req: &mut Request| -> IronResult<Response>
                   {cloned.wikihandler(req)});

        let cloned = self.clone();
        router.get("/",
                   move |req: &mut Request| -> IronResult<Response>
                   {cloned.roothandler(req)});

        let cloned = self.clone();
        router.get("/:query",
                   move |req: &mut Request| -> IronResult<Response>
                   {cloned.roothandler(req)});

        Iron::new(router).http(
            listenaddr.as_str()).unwrap();
    }

    fn roothandler(&self, req: &mut Request)
                   -> IronResult<Response> {
        let ref query = req.extensions.get::<Router>()
            .unwrap().find("query").unwrap_or("/");

        Ok(Response::with((status::Ok, *query)))
    }

    fn wikiredirect(&self, req: &mut Request)
                    -> IronResult<Response> {
        let mut url = req.url.path.clone();
        let mut header = Headers::new();
        let mut newpath = String::new();

        //TODO: customizable root page.
        url.push(self.wikifrontpage.clone());

        for i in url.iter() {
            newpath.push_str("/");
            newpath.push_str(i);
        }

        println!("{}", newpath);
        header.set(Location(newpath));

        let mut resp: Response = Response::with((status::MovedPermanently));
        resp.headers = header;

        Ok(resp)
    }

    fn wikihandler(&self, req: &mut Request)
                   -> IronResult<Response> {
        let ref requrl = req.url.clone();
        let mut urlpath = requrl.path.clone();

        for i in urlpath.iter() {
            println!("{}", i);
        }

        Ok(Response::with((status::Ok, "Watch console.")))
    }
}

fn main() {

    //liquid template test
    let template: LiquidTemplate = LiquidTemplate::new(String::from("hello.liquid"));
    let mut con = Context::new();
    con.set_val("message", Value::Str(String::from("This is Message")));
    println!("Now liquid!");
    println!("{}", template.render(&mut con).unwrap());

    //TODO: make it can fetch config file name from command line argument.
    let sbwiki: SbWikiServer = SbWikiServer::new("config.toml");

    sbwiki.open(false);
}
<|endoftext|>