<commit_before>use std::{cell::RefCell, char, collections::HashMap, rc::Rc, sync::mpsc, time::Duration};

use minifb;

use super::{KeyState, MouseState, Shell, Window, WindowState};
use crate::{
    event::{ButtonState, Key, KeyEvent},
    render::RenderContext2D,
    utils::Rectangle,
    window_adapter::WindowAdapter,
    WindowRequest, WindowSettings,
};

/// The `WindowBuilder` is used to construct a window shell for the minifb backend.
pub struct WindowBuilder<'a, A: 'static>
where
    A: WindowAdapter,
{
    shell: &'a mut Shell<A>,
    adapter: A,
    title: String,
    resizeable: bool,
    always_on_top: bool,
    borderless: bool,
    fonts: HashMap<String, &'static [u8]>,
    bounds: Rectangle,
    request_receiver: Option<mpsc::Receiver<WindowRequest>>,
}

impl<'a, A> WindowBuilder<'a, A>
where
    A: WindowAdapter,
{
    /// Creates a new window builder.
    pub fn new(shell: &'a mut Shell<A>, adapter: A) -> Self {
        WindowBuilder {
            shell,
            adapter,
            title: String::default(),
            resizeable: false,
            always_on_top: false,
            borderless: false,
            fonts: HashMap::new(),
            bounds: Rectangle::new(0.0, 0.0, 100.0, 75.0),
            request_receiver: None,
        }
    }

    /// Creates the window builder from a settings object.
    pub fn from_settings(settings: WindowSettings, shell: &'a mut Shell<A>, adapter: A) -> Self {
        WindowBuilder {
            shell,
            adapter,
            title: settings.title,
            resizeable: settings.resizeable,
            always_on_top: settings.always_on_top,
            borderless: settings.borderless,
            fonts: settings.fonts,
            bounds: Rectangle::new(
                settings.position.0,
                settings.position.1,
                settings.size.0,
                settings.size.1,
            ),
            request_receiver: None,
        }
    }

    /// Sets the title.
    pub fn title(mut self, title: impl Into<String>) -> Self {
        self.title = title.into();
        self
    }

    /// Sets borderless.
    pub fn borderless(mut self, borderless: bool) -> Self {
        self.borderless = borderless;
        self
    }

    /// Sets resizeable.
    pub fn resizeable(mut self, resizeable: bool) -> Self {
        self.resizeable = resizeable;
        self
    }

    /// Sets always_on_top.
    pub fn always_on_top(mut self, always_on_top: bool) -> Self {
        self.always_on_top = always_on_top;
        self
    }

    /// Sets the bounds.
    pub fn bounds(mut self, bounds: impl Into<Rectangle>) -> Self {
        self.bounds = bounds.into();
        self
    }

    /// Registers a new font with family key.
    pub fn font(mut self, family: impl Into<String>, font_file: &'static [u8]) -> Self {
        self.fonts.insert(family.into(), font_file);
        self
    }

    /// Register a window request receiver to communicate with the window shell from outside.
    pub fn request_receiver(mut self, request_receiver: mpsc::Receiver<WindowRequest>) -> Self {
        self.request_receiver = Some(request_receiver);
        self
    }

    /// Builds the window shell and add it to the application `Shell`.
    pub fn build(self) {
        let window_options = minifb::WindowOptions {
            resize: self.resizeable,
            topmost: self.always_on_top,
            borderless: self.borderless,
            title: !self.borderless,
            scale_mode: minifb::ScaleMode::UpperLeft,
            ..Default::default()
        };

        let mut window = minifb::Window::new(
            self.title.as_str(),
            self.bounds.width as usize,
            self.bounds.height as usize,
            window_options,
        )
        .unwrap_or_else(|e| {
            panic!("{}", e);
        });

        // Limit to max ~60 fps update rate
        window.limit_update_rate(Some(Duration::from_micros(16600)));

        let key_events = Rc::new(RefCell::new(vec![]));

        window.set_input_callback(Box::new(KeyInputCallBack {
            key_events: key_events.clone(),
        }));

        window.set_position(self.bounds.x as isize, self.bounds.y as isize);

        let mut render_context = RenderContext2D::new(self.bounds.width, self.bounds.height);

        for (family, font) in self.fonts {
            render_context.register_font(&family, font);
        }

        self.shell.window_shells.push(Window::new(
            window,
            self.adapter,
            render_context,
            self.request_receiver,
            WindowState::default(),
            MouseState::default(),
            true,
            true,
            false,
            vec![
                KeyState::new(minifb::Key::Backspace, Key::Backspace),
                KeyState::new(minifb::Key::Left, Key::Left),
                KeyState::new(minifb::Key::Right, Key::Right),
                KeyState::new(minifb::Key::Up, Key::Up),
                KeyState::new(minifb::Key::Down, Key::Down),
                KeyState::new(minifb::Key::Delete, Key::Delete),
                KeyState::new(minifb::Key::Enter, Key::Enter),
                KeyState::new(minifb::Key::LeftCtrl, Key::Control),
                KeyState::new(minifb::Key::RightCtrl, Key::Control),
                KeyState::new(minifb::Key::LeftShift, Key::ShiftL),
                KeyState::new(minifb::Key::RightShift, Key::ShiftR),
                KeyState::new(minifb::Key::LeftAlt, Key::Alt),
                KeyState::new(minifb::Key::RightAlt, Key::Alt),
                KeyState::new(minifb::Key::Escape, Key::Escape),
                KeyState::new(minifb::Key::Home, Key::Home),
                KeyState::new(minifb::Key::NumPad0, Key::Numpad0),
                KeyState::new(minifb::Key::NumPad1, Key::Numpad1),
                KeyState::new(minifb::Key::NumPad2, Key::Numpad2),
                KeyState::new(minifb::Key::NumPad3, Key::Numpad3),
                KeyState::new(minifb::Key::NumPad4, Key::Numpad4),
                KeyState::new(minifb::Key::NumPad5, Key::Numpad5),
                KeyState::new(minifb::Key::NumPad6, Key::Numpad6),
                KeyState::new(minifb::Key::NumPad7, Key::Numpad7),
                KeyState::new(minifb::Key::NumPad8, Key::Numpad8),
                KeyState::new(minifb::Key::NumPad9, Key::Numpad9),
                KeyState::new(minifb::Key::NumPadSlash, Key::NumpadDivide),
                KeyState::new(minifb::Key::NumPadAsterisk, Key::NumpadMultiply),
                KeyState::new(minifb::Key::NumPadMinus, Key::NumpadSubtract),
                KeyState::new(minifb::Key::NumPadPlus, Key::NumpadAdd),
                KeyState::new(minifb::Key::NumPadEnter, Key::NumpadEnter),
                KeyState::new(minifb::Key::NumPadDot, Key::NumpadDot),
                KeyState::new(minifb::Key::A, Key::A(false)),
                KeyState::new(minifb::Key::C, Key::C(false)),
                KeyState::new(minifb::Key::V, Key::V(false)),
                KeyState::new(minifb::Key::X, Key::X(false)),
            ],
            key_events,
        ));
    }
}

// -- Helpers --

// minifb key input helper
struct KeyInputCallBack {
    key_events: Rc<RefCell<Vec<KeyEvent>>>,
}

impl KeyInputCallBack {
    fn uni_char_to_key_event(&mut self, uni_char: u32) {
        let mut text = String::new();

        let key = if let Some(character) = char::from_u32(uni_char) {
            text = character.to_string();
            Key::from(character)
        } else {
            Key::Unknown
        };
        if key == Key::Up
            || key == Key::Down
            || key == Key::Left
            || key == Key::Right
            || key == Key::Backspace
            || key == Key::Control
            || key == Key::Home
            || key == Key::Escape
            || key == Key::Delete
            || key == Key::Unknown
        {
            return;
        }

        self.key_events.borrow_mut().push(KeyEvent {
            key,
            state: ButtonState::Down,
            text,
        });
    }
}

impl minifb::InputCallback for KeyInputCallBack {
    fn add_char(&mut self, uni_char: u32) {
        self.uni_char_to_key_event(uni_char);
    }
}

// -- Helpers --
<commit_msg>Updated creation of `Rectangle` and its usage to compile<commit_after>use std::{cell::RefCell, char, collections::HashMap, rc::Rc, sync::mpsc, time::Duration};

use minifb;

use super::{KeyState, MouseState, Shell, Window, WindowState};
use crate::{
    event::{ButtonState, Key, KeyEvent},
    render::RenderContext2D,
    utils::Rectangle,
    window_adapter::WindowAdapter,
    WindowRequest, WindowSettings,
};

/// The `WindowBuilder` is used to construct a window shell for the minifb backend.
pub struct WindowBuilder<'a, A: 'static>
where
    A: WindowAdapter,
{
    shell: &'a mut Shell<A>,
    adapter: A,
    title: String,
    resizeable: bool,
    always_on_top: bool,
    borderless: bool,
    fonts: HashMap<String, &'static [u8]>,
    bounds: Rectangle,
    request_receiver: Option<mpsc::Receiver<WindowRequest>>,
}

impl<'a, A> WindowBuilder<'a, A>
where
    A: WindowAdapter,
{
    /// Creates a new window builder.
    pub fn new(shell: &'a mut Shell<A>, adapter: A) -> Self {
        WindowBuilder {
            shell,
            adapter,
            title: String::default(),
            resizeable: false,
            always_on_top: false,
            borderless: false,
            fonts: HashMap::new(),
            bounds: Rectangle::new((0.0, 0.0), 100.0, 75.0),
            request_receiver: None,
        }
    }

    /// Creates the window builder from a settings object.
    pub fn from_settings(settings: WindowSettings, shell: &'a mut Shell<A>, adapter: A) -> Self {
        WindowBuilder {
            shell,
            adapter,
            title: settings.title,
            resizeable: settings.resizeable,
            always_on_top: settings.always_on_top,
            borderless: settings.borderless,
            fonts: settings.fonts,
            bounds: Rectangle::new(
                settings.position,
                settings.size.0,
                settings.size.1,
            ),
            request_receiver: None,
        }
    }

    /// Sets the title.
    pub fn title(mut self, title: impl Into<String>) -> Self {
        self.title = title.into();
        self
    }

    /// Sets borderless.
    pub fn borderless(mut self, borderless: bool) -> Self {
        self.borderless = borderless;
        self
    }

    /// Sets resizeable.
    pub fn resizeable(mut self, resizeable: bool) -> Self {
        self.resizeable = resizeable;
        self
    }

    /// Sets always_on_top.
    pub fn always_on_top(mut self, always_on_top: bool) -> Self {
        self.always_on_top = always_on_top;
        self
    }

    /// Sets the bounds.
    pub fn bounds(mut self, bounds: impl Into<Rectangle>) -> Self {
        self.bounds = bounds.into();
        self
    }

    /// Registers a new font with family key.
    pub fn font(mut self, family: impl Into<String>, font_file: &'static [u8]) -> Self {
        self.fonts.insert(family.into(), font_file);
        self
    }

    /// Register a window request receiver to communicate with the window shell from outside.
    pub fn request_receiver(mut self, request_receiver: mpsc::Receiver<WindowRequest>) -> Self {
        self.request_receiver = Some(request_receiver);
        self
    }

    /// Builds the window shell and add it to the application `Shell`.
    pub fn build(self) {
        let window_options = minifb::WindowOptions {
            resize: self.resizeable,
            topmost: self.always_on_top,
            borderless: self.borderless,
            title: !self.borderless,
            scale_mode: minifb::ScaleMode::UpperLeft,
            ..Default::default()
        };

        let mut window = minifb::Window::new(
            self.title.as_str(),
            self.bounds.width() as usize,
            self.bounds.height() as usize,
            window_options,
        )
        .unwrap_or_else(|e| {
            panic!("{}", e);
        });

        // Limit to max ~60 fps update rate
        window.limit_update_rate(Some(Duration::from_micros(16600)));

        let key_events = Rc::new(RefCell::new(vec![]));

        window.set_input_callback(Box::new(KeyInputCallBack {
            key_events: key_events.clone(),
        }));

        window.set_position(self.bounds.x() as isize, self.bounds.y() as isize);

        let mut render_context = RenderContext2D::new(self.bounds.width(), self.bounds.height());

        for (family, font) in self.fonts {
            render_context.register_font(&family, font);
        }

        self.shell.window_shells.push(Window::new(
            window,
            self.adapter,
            render_context,
            self.request_receiver,
            WindowState::default(),
            MouseState::default(),
            true,
            true,
            false,
            vec![
                KeyState::new(minifb::Key::Backspace, Key::Backspace),
                KeyState::new(minifb::Key::Left, Key::Left),
                KeyState::new(minifb::Key::Right, Key::Right),
                KeyState::new(minifb::Key::Up, Key::Up),
                KeyState::new(minifb::Key::Down, Key::Down),
                KeyState::new(minifb::Key::Delete, Key::Delete),
                KeyState::new(minifb::Key::Enter, Key::Enter),
                KeyState::new(minifb::Key::LeftCtrl, Key::Control),
                KeyState::new(minifb::Key::RightCtrl, Key::Control),
                KeyState::new(minifb::Key::LeftShift, Key::ShiftL),
                KeyState::new(minifb::Key::RightShift, Key::ShiftR),
                KeyState::new(minifb::Key::LeftAlt, Key::Alt),
                KeyState::new(minifb::Key::RightAlt, Key::Alt),
                KeyState::new(minifb::Key::Escape, Key::Escape),
                KeyState::new(minifb::Key::Home, Key::Home),
                KeyState::new(minifb::Key::NumPad0, Key::Numpad0),
                KeyState::new(minifb::Key::NumPad1, Key::Numpad1),
                KeyState::new(minifb::Key::NumPad2, Key::Numpad2),
                KeyState::new(minifb::Key::NumPad3, Key::Numpad3),
                KeyState::new(minifb::Key::NumPad4, Key::Numpad4),
                KeyState::new(minifb::Key::NumPad5, Key::Numpad5),
                KeyState::new(minifb::Key::NumPad6, Key::Numpad6),
                KeyState::new(minifb::Key::NumPad7, Key::Numpad7),
                KeyState::new(minifb::Key::NumPad8, Key::Numpad8),
                KeyState::new(minifb::Key::NumPad9, Key::Numpad9),
                KeyState::new(minifb::Key::NumPadSlash, Key::NumpadDivide),
                KeyState::new(minifb::Key::NumPadAsterisk, Key::NumpadMultiply),
                KeyState::new(minifb::Key::NumPadMinus, Key::NumpadSubtract),
                KeyState::new(minifb::Key::NumPadPlus, Key::NumpadAdd),
                KeyState::new(minifb::Key::NumPadEnter, Key::NumpadEnter),
                KeyState::new(minifb::Key::NumPadDot, Key::NumpadDot),
                KeyState::new(minifb::Key::A, Key::A(false)),
                KeyState::new(minifb::Key::C, Key::C(false)),
                KeyState::new(minifb::Key::V, Key::V(false)),
                KeyState::new(minifb::Key::X, Key::X(false)),
            ],
            key_events,
        ));
    }
}

// -- Helpers --

// minifb key input helper
struct KeyInputCallBack {
    key_events: Rc<RefCell<Vec<KeyEvent>>>,
}

impl KeyInputCallBack {
    fn uni_char_to_key_event(&mut self, uni_char: u32) {
        let mut text = String::new();

        let key = if let Some(character) = char::from_u32(uni_char) {
            text = character.to_string();
            Key::from(character)
        } else {
            Key::Unknown
        };
        if key == Key::Up
            || key == Key::Down
            || key == Key::Left
            || key == Key::Right
            || key == Key::Backspace
            || key == Key::Control
            || key == Key::Home
            || key == Key::Escape
            || key == Key::Delete
            || key == Key::Unknown
        {
            return;
        }

        self.key_events.borrow_mut().push(KeyEvent {
            key,
            state: ButtonState::Down,
            text,
        });
    }
}

impl minifb::InputCallback for KeyInputCallBack {
    fn add_char(&mut self, uni_char: u32) {
        self.uni_char_to_key_event(uni_char);
    }
}

// -- Helpers --
<|endoftext|>


<commit_before>#[macro_use(assert_diff)]
extern crate difference;
extern crate env_logger;
#[macro_use]
extern crate pretty_assertions;

extern crate gluon_base as base;
extern crate gluon_parser as parser;

use std::fs::File;
use std::io::{Read, Write};
use std::path::Path;

use parser::format_expr;

fn test_format(name: &str) {
    let _ = env_logger::init();

    let name = Path::new(name);
    let mut contents = String::new();
    File::open(Path::new("../").join(name))
        .unwrap()
        .read_to_string(&mut contents)
        .unwrap();

    let out_str = format_expr(&contents).unwrap();

    if contents != out_str {
        let out_path = Path::new(env!("OUT_DIR")).join(name.file_name().unwrap());
        File::create(out_path)
            .unwrap()
            .write_all(out_str.as_bytes())
            .unwrap();

        assert_diff!(&contents, &out_str, " ", 0);
    }
}

#[test]
fn bool() {
    test_format("std/bool.glu");
}

#[test]
fn char() {
    test_format("std/char.glu");
}

#[test]
fn map() {
    test_format("std/map.glu");
}

#[test]
fn option() {
    test_format("std/option.glu");
}

#[test]
fn prelude() {
    test_format("std/prelude.glu");
}

#[test]
fn result() {
    test_format("std/result.glu");
}

#[test]
fn state() {
    test_format("std/state.glu");
}

#[test]
fn stream() {
    test_format("std/stream.glu");
}

#[test]
fn string() {
    test_format("std/string.glu");
}

#[test]
fn test() {
    test_format("std/test.glu");
}

#[test]
fn types() {
    test_format("std/types.glu");
}

#[test]
fn unit() {
    test_format("std/unit.glu");
}

#[test]
fn writer() {
    test_format("std/writer.glu");
}

#[test]
fn repl() {
    test_format("repl/src/repl.glu");
}

#[test]
fn dont_add_newline_for_let_literal() {
    let expr = r#"
let x = 1
x
"#;
    assert_eq!(
        &format_expr(expr).unwrap(),
        r#"
let x = 1
x
"#
    );
}

#[test]
fn dont_lose_information_in_literals() {
    let expr = r#"
3.14 "\t\n\r\""
"#;
    assert_eq!(&format_expr(expr).unwrap(), expr);
}

#[test]
fn preserve_comment_between_let_in() {
    let expr = r#"
// test1
let x = 1
// test2
type Test = Int
// test3
1
// test4
"#;
    assert_diff!(&format_expr(expr).unwrap(), expr, " ", 0);
}

#[test]
fn preserve_whitespace_in_record() {
    let expr = r#"
{
    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaax = 1,

    bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbby = 2
}
"#;
    assert_diff!(&format_expr(expr).unwrap(), expr, " ", 0);
}

#[test]
fn preserve_block_comments() {
    let expr = r#"
/* test */
let x = { field = f /* test */ 123 /* doc */ }
/* test */
x
"#;
    assert_eq!(&format_expr(expr).unwrap(), expr);
}

// TODO
#[test]
fn preserve_more_block_comments() {
    let expr = r#"
{ /* abc */ field /* abc */ = /* test */ 123 }
"#;
    assert_eq!(&format_expr(expr).unwrap(), expr);
}

#[test]
fn preserve_shebang_line() {
    let expr = r#"#!/bin/gluon
/* test */
let x = { field = f /* test */ 123 /* doc */ }
/* test */
x
"#;
    assert_eq!(&format_expr(expr).unwrap(), expr);
}

#[test]
fn nested_constructor_pattern() {
    let expr = r#"
match None with
| Some (Some x) -> x
| _ -> 123
"#;
    assert_eq!(&format_expr(expr).unwrap(), expr);
}

#[test]
fn long_pattern_match() {
    let expr = r#"
let {
    CCCCCCCCCCCCCC,
    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
    bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
} =
    test
123
"#;
    assert_eq!(&format_expr(expr).unwrap(), expr);
}

#[test]
fn preserve_comments_in_function_types() {
    let expr = r#"#!/bin/gluon
let x : /* first */ Int /* Int */ ->
    // Float
    Float /* last */ = ()
x
"#;
    assert_diff!(&format_expr(expr).unwrap(), expr, " ", 0);
}

#[test]
fn preserve_comments_app_types() {
    let expr = r#"#!/bin/gluon
let x : Test /* first */ Int
    // middle
    Float /* last */ = ()
x
"#;
    assert_diff!(&format_expr(expr).unwrap(), expr, " ", 0);
}

#[test]
fn preserve_doc_comments_in_record_types() {
    let expr = r#"#!/bin/gluon
type Test = {
    /// test
    field1 : Int,
    /**
     middle
    */
    field2 : Float
}
x
"#;
    assert_diff!(&format_expr(expr).unwrap(), expr, " ", 0);
}
<commit_msg>fix: Replace 'env!("OUT_DIR")' with 'env::var("OUT_DIR").unwrap()'<commit_after>#[macro_use(assert_diff)]
extern crate difference;
extern crate env_logger;
#[macro_use]
extern crate pretty_assertions;

extern crate gluon_base as base;
extern crate gluon_parser as parser;

use std::env;
use std::fs::File;
use std::io::{Read, Write};
use std::path::Path;

use parser::format_expr;

fn test_format(name: &str) {
    let _ = env_logger::init();

    let name = Path::new(name);
    let mut contents = String::new();
    File::open(Path::new("../").join(name))
        .unwrap()
        .read_to_string(&mut contents)
        .unwrap();

    let out_str = format_expr(&contents).unwrap();

    if contents != out_str {
        let out_path = Path::new(env::var("OUT_DIR").unwrap()).join(name.file_name().unwrap());
        File::create(out_path)
            .unwrap()
            .write_all(out_str.as_bytes())
            .unwrap();

        assert_diff!(&contents, &out_str, " ", 0);
    }
}

#[test]
fn bool() {
    test_format("std/bool.glu");
}

#[test]
fn char() {
    test_format("std/char.glu");
}

#[test]
fn map() {
    test_format("std/map.glu");
}

#[test]
fn option() {
    test_format("std/option.glu");
}

#[test]
fn prelude() {
    test_format("std/prelude.glu");
}

#[test]
fn result() {
    test_format("std/result.glu");
}

#[test]
fn state() {
    test_format("std/state.glu");
}

#[test]
fn stream() {
    test_format("std/stream.glu");
}

#[test]
fn string() {
    test_format("std/string.glu");
}

#[test]
fn test() {
    test_format("std/test.glu");
}

#[test]
fn types() {
    test_format("std/types.glu");
}

#[test]
fn unit() {
    test_format("std/unit.glu");
}

#[test]
fn writer() {
    test_format("std/writer.glu");
}

#[test]
fn repl() {
    test_format("repl/src/repl.glu");
}

#[test]
fn dont_add_newline_for_let_literal() {
    let expr = r#"
let x = 1
x
"#;
    assert_eq!(
        &format_expr(expr).unwrap(),
        r#"
let x = 1
x
"#
    );
}

#[test]
fn dont_lose_information_in_literals() {
    let expr = r#"
3.14 "\t\n\r\""
"#;
    assert_eq!(&format_expr(expr).unwrap(), expr);
}

#[test]
fn preserve_comment_between_let_in() {
    let expr = r#"
// test1
let x = 1
// test2
type Test = Int
// test3
1
// test4
"#;
    assert_diff!(&format_expr(expr).unwrap(), expr, " ", 0);
}

#[test]
fn preserve_whitespace_in_record() {
    let expr = r#"
{
    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaax = 1,

    bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbby = 2
}
"#;
    assert_diff!(&format_expr(expr).unwrap(), expr, " ", 0);
}

#[test]
fn preserve_block_comments() {
    let expr = r#"
/* test */
let x = { field = f /* test */ 123 /* doc */ }
/* test */
x
"#;
    assert_eq!(&format_expr(expr).unwrap(), expr);
}

// TODO
#[test]
fn preserve_more_block_comments() {
    let expr = r#"
{ /* abc */ field /* abc */ = /* test */ 123 }
"#;
    assert_eq!(&format_expr(expr).unwrap(), expr);
}

#[test]
fn preserve_shebang_line() {
    let expr = r#"#!/bin/gluon
/* test */
let x = { field = f /* test */ 123 /* doc */ }
/* test */
x
"#;
    assert_eq!(&format_expr(expr).unwrap(), expr);
}

#[test]
fn nested_constructor_pattern() {
    let expr = r#"
match None with
| Some (Some x) -> x
| _ -> 123
"#;
    assert_eq!(&format_expr(expr).unwrap(), expr);
}

#[test]
fn long_pattern_match() {
    let expr = r#"
let {
    CCCCCCCCCCCCCC,
    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
    bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
} =
    test
123
"#;
    assert_eq!(&format_expr(expr).unwrap(), expr);
}

#[test]
fn preserve_comments_in_function_types() {
    let expr = r#"#!/bin/gluon
let x : /* first */ Int /* Int */ ->
    // Float
    Float /* last */ = ()
x
"#;
    assert_diff!(&format_expr(expr).unwrap(), expr, " ", 0);
}

#[test]
fn preserve_comments_app_types() {
    let expr = r#"#!/bin/gluon
let x : Test /* first */ Int
    // middle
    Float /* last */ = ()
x
"#;
    assert_diff!(&format_expr(expr).unwrap(), expr, " ", 0);
}

#[test]
fn preserve_doc_comments_in_record_types() {
    let expr = r#"#!/bin/gluon
type Test = {
    /// test
    field1 : Int,
    /**
     middle
    */
    field2 : Float
}
x
"#;
    assert_diff!(&format_expr(expr).unwrap(), expr, " ", 0);
}
<|endoftext|>


<commit_before>extern crate bincode;
extern crate rustc_serialize;
extern crate rpsrtsrs;
extern crate docopt;

use std::net::TcpStream;
use std::ops::Deref;

use rpsrtsrs::state::{GameState};
use rpsrtsrs::network::{Message};

use docopt::Docopt;

use bincode::SizeLimit;
use bincode::rustc_serialize::{decode_from, encode_into};
use bincode::rustc_serialize::DecodingResult;

static USAGE: &'static str = "
Usage: cli_client [-p PORT] [-i IP] [-r ID]

Options:
    -p PORT  The port to connect to [default: 8080].
    -i IP    The ipv4 address to connect to [default: 127.0.0.1].
    -r ID    Reconnect with the given ID
";

#[derive(RustcDecodable, Debug)]
struct Args {
    flag_p: u16,
    flag_i: String,
    flag_r: Option<u32>,
}

fn main() {
    let args: Args = Docopt::new(USAGE).and_then(|d| d.decode())
                                       .unwrap_or_else(|e| e.exit());
    let host = args.flag_i;
    let port = args.flag_p;
    let reconnect = args.flag_r;

    println!("connecting to host: {:?}:{:?} reconnect? {:?}", host, port, reconnect);

    let mut stream = TcpStream::connect((host.deref(), port)).unwrap();

    match reconnect {
        Some(id) => {
            encode_into(&Message::ClientReconnect(id.into()), &mut stream, SizeLimit::Infinite).unwrap();
        },
        None => {
            encode_into(&Message::ClientHello, &mut stream, SizeLimit::Infinite).unwrap();
        }
    }
    let server_hello: DecodingResult<Message> = decode_from(&mut stream, SizeLimit::Infinite);
    println!("{:?}", server_hello);

    loop {
        let game_state: DecodingResult<GameState> = decode_from(&mut stream, SizeLimit::Infinite);
        match game_state {
            Ok(game) => println!("{:?}", game),
            Err(e) => {
                println!("{:?}", e);
                return;
            }
        }
    }
}

<commit_msg>cli_client: Add move command<commit_after>extern crate bincode;
extern crate rustc_serialize;
extern crate rpsrtsrs;
extern crate docopt;

use std::net::TcpStream;
use std::ops::Deref;

use rpsrtsrs::state::GameState;
use rpsrtsrs::network::{Command, Message};

use docopt::Docopt;

use bincode::SizeLimit;
use bincode::rustc_serialize::{decode_from, encode_into};
use bincode::rustc_serialize::DecodingResult;

static USAGE: &'static str = "
Usage: cli_client [-p PORT] [-i IP] [-r ID] (read|move <id> <x> <y>)

Options:
    -p PORT  The port to connect to [default: 8080].
    -i IP    The ipv4 address to connect to [default: 127.0.0.1].
    -r ID    Reconnect with the given ID
";

#[derive(RustcDecodable, Debug)]
struct Args {
    flag_p: u16,
    flag_i: String,
    flag_r: Option<u32>,

    cmd_read: bool,
    arg_id: Option<u32>,
    arg_x: Option<u64>,
    arg_y: Option<u64>,
}

fn main() {
    let args: Args = Docopt::new(USAGE).and_then(|d| d.decode())
                                       .unwrap_or_else(|e| e.exit());
    println!("{:?}", args);
    let host = args.flag_i;
    let port = args.flag_p;
    let reconnect = args.flag_r;
    let cmd_read = args.cmd_read;

    println!("connecting to host: {:?}:{:?} reconnect? {:?}", host, port, reconnect);

    let mut stream = TcpStream::connect((host.deref(), port)).unwrap();

    match reconnect {
        Some(id) => {
            encode_into(&Message::ClientReconnect(id.into()),
                        &mut stream,
                        SizeLimit::Infinite)
                .unwrap();
        }
        None => {
            encode_into(&Message::ClientHello, &mut stream, SizeLimit::Infinite).unwrap();
        }
    }
    let server_hello: DecodingResult<Message> = decode_from(&mut stream, SizeLimit::Infinite);
    println!("{:?}", server_hello);

    if cmd_read {
        loop {
            let game_state: DecodingResult<GameState> = decode_from(&mut stream,
                                                                    SizeLimit::Infinite);
            match game_state {
                Ok(game) => println!("{:?}", game),
                Err(e) => {
                    println!("{:?}", e);
                    return;
                }
            }
        }
    } else {
        let id = args.arg_id.expect("<id> missing");
        let x = args.arg_x.expect("<x> missing");
        let y = args.arg_y.expect("<y> missing");
        encode_into(&Message::Command(Command::Move(id.into(), [x, y])),
                    &mut stream,
                    SizeLimit::Infinite)
            .unwrap();
    }
}
<|endoftext|>


<commit_before>extern crate chrono;
extern crate clap;
extern crate fern;
#[macro_use] extern crate log;
extern crate rand;
extern crate thud;

use clap::App;
use thud::game;
use thud::mcts;
use thud::util;

use std::default::Default;

fn main() {
    // Set up arg handling.
    let matches = {
        let app = util::set_common_args(
            App::new("console_mc")
                .version("0.1.0")
                .author("Stu Black <trurl@freeshell.org>")
                .about("Play out Thud Monte Carlo iterations"),
            &[util::ITERATION_COUNT_FLAG,
              util::SIMULATION_COUNT_FLAG,
              util::EXPLORATION_BIAS_FLAG,
              util::INITIAL_BOARD_FLAG,
              util::INITIAL_PLAYER_FLAG,
              util::LOG_LEVEL_FLAG]);
        app.get_matches()
    };

    let iteration_count =
        match matches.value_of(util::ITERATION_COUNT_FLAG).unwrap().parse::<usize>() {
            Ok(x) => x,
            Err(e) => panic!("Bad iteration count: {}", e),
        };
    let simulation_count =
        match matches.value_of(util::SIMULATION_COUNT_FLAG).unwrap().parse::<usize>() {
            Ok(x) => x,
            Err(e) => panic!("Bad simulation count: {}", e),
        };
    let exploration_bias =
        match matches.value_of(util::EXPLORATION_BIAS_FLAG).unwrap().parse::<f64>() {
            Ok(x) => x,
            Err(e) => panic!("Bad exploration bias: {}", e),
        };
    let initial_cells =
        match matches.value_of(util::INITIAL_BOARD_FLAG).unwrap().parse::<util::InitialBoard>() {
            Ok(x) => x.cells(),
            Err(e) => panic!("Bad initial board configuration: {}", e),
        };
    let toggle_initial_player =
        match matches.value_of(util::INITIAL_PLAYER_FLAG).map(|x| x.parse::<game::Role>()) {
            None | Some(Ok(game::Role::Dwarf)) => false,
            Some(Ok(game::Role::Troll)) => true,
            Some(Err(x)) => panic!("{}", x),
        };
    let logging_level =
        match matches.value_of(util::LOG_LEVEL_FLAG).map(|x| x.parse::<log::LogLevelFilter>()) {
            Some(Ok(x)) => x,
            Some(Err(_)) => panic!("Bad logging level '{}'", matches.value_of(util::LOG_LEVEL_FLAG).unwrap()),
            None => log::LogLevelFilter::Info,
        };

    // Set up logging.
    let logger_config = fern::DispatchConfig {
        format: Box::new(|msg: &str, level: &log::LogLevel, _location: &log::LogLocation| {
            format!("[{}][{}] {}",
                    chrono::Local::now().format("%Y-%m-%d %T%.3f%z").to_string(), level, msg)
        }),
        output: vec![fern::OutputConfig::stdout()],
        level: logging_level,
    };
    if let Err(e) = fern::init_global_logger(logger_config, log::LogLevelFilter::Trace) {
        panic!("Failed to initialize global logger: {}", e);
    }

    let mut state = game::State::<game::board::TranspositionalEquivalence>::new(initial_cells);
    if toggle_initial_player {
        state.toggle_active_role();
    }

    let mut rng = rand::thread_rng();
    let mut turn_number = 0;
    loop {
        info!("begin turn {}; board: {}", turn_number, game::board::format_board(state.board()));
        let actions: Vec<game::Action> = state.role_actions(state.active_role()).collect();
        if actions.is_empty() {
            info!("No actions available. Exiting.");
            return
        }
        let mut action_statistics: Vec<mcts::Statistics> = {
            let mut v = Vec::with_capacity(actions.len());
            for _ in 0..actions.len() {
                v.push(Default::default());
            }
            v
        };
        let mut log_iteration = 0.0;
        for iteration in 0..iteration_count {
            if iteration % 100 == 0 {
                info!("iteration: {} / {} = {:.02}%", iteration, iteration_count,
                      ((100 * iteration) as f64) / (iteration_count as f64));
            }
            let mut selected_action_index = 0;
            let mut best_ucb = std::f64::MIN;
            for (i, stats) in action_statistics.iter().enumerate() {
                if stats.visits == 0 {
                    selected_action_index = i;
                    best_ucb = std::f64::MAX;
                    break
                } else {
                    let child_visits = stats.visits as f64;
                    let child_payoff = stats.payoff.score(state.active_role()) as f64;
                    let ucb = child_payoff / child_visits
                        + exploration_bias * f64::sqrt(log_iteration / child_visits);
                    // TODO tie-breaking.
                    if ucb > best_ucb {
                        best_ucb = ucb;
                        selected_action_index = i;
                    }
                }
            }
            trace!("UCB selected action {:?} [UCB = {}]", actions[selected_action_index], best_ucb);
            for _ in 0..simulation_count {
                let payoff = mcts::expand::simulate(&mut state.clone(), &mut rng);
                trace!("simulated payoff {:?}", payoff);
                action_statistics[selected_action_index].increment_visit(payoff);
            }
            log_iteration = f64::ln((iteration + 1) as f64);
        }

        let mut most_visited_index = std::usize::MIN;
        let mut most_visits = 0;
        for (i, stats) in action_statistics.iter().enumerate() {
            info!("Action {:?} gets statistics {:?}", actions[i], stats);
            // TODO tie-breaking.
            if stats.visits > most_visits {
                most_visited_index = i;
                most_visits = stats.visits;
            }
        }
        info!("Performing move {:?} with statistics {:?}",
              actions[most_visited_index], action_statistics[most_visited_index]);
        state.do_action(&actions[most_visited_index]);
        turn_number += 1;
    }
}
<commit_msg>Small cleanup of console_mc binary code.<commit_after>extern crate chrono;
extern crate clap;
extern crate fern;
#[macro_use] extern crate log;
extern crate rand;
extern crate thud;

use clap::App;
use thud::game;
use thud::mcts;
use thud::util;

use std::default::Default;

fn main() {
    // Set up arg handling.
    let matches = {
        let app = util::set_common_args(
            App::new("console_mc")
                .version("0.1.0")
                .author("Stu Black <trurl@freeshell.org>")
                .about("Play out Thud Monte Carlo iterations"),
            &[util::ITERATION_COUNT_FLAG,
              util::SIMULATION_COUNT_FLAG,
              util::EXPLORATION_BIAS_FLAG,
              util::INITIAL_BOARD_FLAG,
              util::INITIAL_PLAYER_FLAG,
              util::LOG_LEVEL_FLAG]);
        app.get_matches()
    };

    let iteration_count =
        match matches.value_of(util::ITERATION_COUNT_FLAG).unwrap().parse::<usize>() {
            Ok(x) => x,
            Err(e) => panic!("Bad iteration count: {}", e),
        };
    let simulation_count =
        match matches.value_of(util::SIMULATION_COUNT_FLAG).unwrap().parse::<usize>() {
            Ok(x) => x,
            Err(e) => panic!("Bad simulation count: {}", e),
        };
    let exploration_bias =
        match matches.value_of(util::EXPLORATION_BIAS_FLAG).unwrap().parse::<f64>() {
            Ok(x) => x,
            Err(e) => panic!("Bad exploration bias: {}", e),
        };
    let initial_cells =
        match matches.value_of(util::INITIAL_BOARD_FLAG).map(|x| x.parse::<util::InitialBoard>()) {
            None => game::board::Cells::default(),
            Some(Ok(x)) => x.cells(),
            Some(Err(e)) => panic!("Bad initial board configuration: {}", e),
        };
    let toggle_initial_player =
        match matches.value_of(util::INITIAL_PLAYER_FLAG).map(|x| x.parse::<game::Role>()) {
            None | Some(Ok(game::Role::Dwarf)) => false,
            Some(Ok(game::Role::Troll)) => true,
            Some(Err(x)) => panic!("{}", x),
        };
    let logging_level =
        match matches.value_of(util::LOG_LEVEL_FLAG).map(|x| x.parse::<log::LogLevelFilter>()) {
            Some(Ok(x)) => x,
            Some(Err(_)) => panic!("Bad logging level '{}'", matches.value_of(util::LOG_LEVEL_FLAG).unwrap()),
            None => log::LogLevelFilter::Info,
        };

    // Set up logging.
    let logger_config = fern::DispatchConfig {
        format: Box::new(|msg: &str, level: &log::LogLevel, _location: &log::LogLocation| {
            format!("[{}][{}] {}",
                    chrono::Local::now().format("%Y-%m-%d %T%.3f%z").to_string(), level, msg)
        }),
        output: vec![fern::OutputConfig::stdout()],
        level: logging_level,
    };
    if let Err(e) = fern::init_global_logger(logger_config, log::LogLevelFilter::Trace) {
        panic!("Failed to initialize global logger: {}", e);
    }

    let mut state = game::State::<game::board::TranspositionalEquivalence>::new(initial_cells);
    if toggle_initial_player {
        state.toggle_active_role();
    }

    let mut rng = rand::thread_rng();
    let mut turn_number = 0;
    loop {
        info!("begin turn {}; board: {}", turn_number, game::board::format_board(state.board()));
        let actions: Vec<game::Action> = state.role_actions(state.active_role()).collect();
        if actions.is_empty() {
            info!("No actions available. Exiting.");
            return
        }
        let mut action_statistics: Vec<mcts::Statistics> = {
            let mut v = Vec::with_capacity(actions.len());
            for _ in 0..actions.len() {
                v.push(Default::default());
            }
            v
        };
        let mut log_iteration = 0.0;
        for iteration in 0..iteration_count {
            if iteration % 100 == 0 {
                info!("iteration: {} / {} = {:.02}%", iteration, iteration_count,
                      ((100 * iteration) as f64) / (iteration_count as f64));
            }
            let mut selected_action_index = 0;
            let mut best_ucb = std::f64::MIN;
            for (i, stats) in action_statistics.iter().enumerate() {
                if stats.visits == 0 {
                    selected_action_index = i;
                    best_ucb = std::f64::MAX;
                    break
                } else {
                    let child_visits = stats.visits as f64;
                    let child_payoff = stats.payoff.score(state.active_role()) as f64;
                    let ucb = child_payoff / child_visits
                        + exploration_bias * f64::sqrt(log_iteration / child_visits);
                    // TODO tie-breaking.
                    if ucb > best_ucb {
                        best_ucb = ucb;
                        selected_action_index = i;
                    }
                }
            }
            trace!("UCB selected action {:?} [UCB = {}]", actions[selected_action_index], best_ucb);
            for _ in 0..simulation_count {
                let payoff = mcts::expand::simulate(&mut state.clone(), &mut rng);
                trace!("simulated payoff {:?}", payoff);
                action_statistics[selected_action_index].increment_visit(payoff);
            }
            log_iteration = f64::ln((iteration + 1) as f64);
        }

        let mut most_visited_index = std::usize::MIN;
        let mut most_visits = 0;
        for (i, stats) in action_statistics.iter().enumerate() {
            info!("Action {:?} gets {:?}", actions[i], stats);
            // TODO tie-breaking.
            if stats.visits > most_visits {
                most_visited_index = i;
                most_visits = stats.visits;
            }
        }
        info!("Performing move {:?} with {:?}",
              actions[most_visited_index], action_statistics[most_visited_index]);
        state.do_action(&actions[most_visited_index]);
        turn_number += 1;
    }
}
<|endoftext|>


<commit_before>#![allow(non_camel_case_types)]

use phf;

#[derive(Debug,PartialEq)]
pub enum TlsCipherKx {
    Null,
    Psk,
    Krb5,
    Srp,
    Rsa,
    Dh,
    Dhe,
    Ecdh,
    Ecdhe,
}

#[derive(Debug,PartialEq)]
pub enum TlsCipherAu {
    Null,
    Psk,
    Krb5,
    Srp,
    Srp_Dss,
    Srp_Rsa,
    Dss,
    Rsa,
    Dhe,
    Ecdsa,
}

#[derive(Debug,PartialEq)]
pub enum TlsCipherEnc {
    Null,
    Des,
    TripleDes,
    Rc2,
    Rc4,
    Aria,
    Idea,
    Seed,
    Aes,
    Camellia,
    Chacha20_Poly1305,
}

#[derive(Debug,PartialEq)]
pub enum TlsCipherEncMode {
    Null,
    Cbc,
    Ccm,
    Gcm,
}

#[derive(Debug,PartialEq)]
pub enum TlsCipherMac {
    Null,
    HmacMd5,
    HmacSha1,
    HmacSha256,
    HmacSha384,
    Aead,
}

#[derive(Debug)]
pub struct TlsCipherSuite {
    pub name: &'static str,
    pub id: u16,
    pub kx: TlsCipherKx,
    pub au: TlsCipherAu,
    pub enc: TlsCipherEnc,
    pub enc_mode: TlsCipherEncMode,
    pub enc_size: u16,
    pub mac: TlsCipherMac,
    pub mac_size: u16,
}

include!(concat!(env!("OUT_DIR"), "/codegen.rs"));

impl TlsCipherSuite {
    pub fn from_id(id: u16) -> Option<&'static TlsCipherSuite> {
        CIPHERS.get(&id)
    }

    pub fn from_name<'a>(name: &'a str) -> Option<&'static TlsCipherSuite> {
        for v in CIPHERS.values().filter(|&v| v.name == name) {
            return Some(v);
        }
        None
    }
}

#[cfg(test)]
mod tests {
    use tls_ciphers::{CIPHERS,TlsCipherSuite,TlsCipherKx};

#[test]
fn test_cipher_count() {
    println!("loaded: {} cipher suites", CIPHERS.len());
    assert!(CIPHERS.len() > 0);
}

#[test]
fn test_cipher_from_id() {
    match TlsCipherSuite::from_id(0xc025) {
        Some(ref cipher) => {
            println!("Found cipher: {:?}", cipher);
        },
        None => assert!(false),
    }
}

#[test]
fn test_cipher_from_name() {
    match TlsCipherSuite::from_name("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384") {
        Some(ref cipher) => {
            println!("Found cipher: {:?}", cipher);
        },
        None => assert!(false),
    }
}

#[test]
fn test_cipher_filter() {
    let ecdhe_ciphers : Vec<&TlsCipherSuite> = CIPHERS.values().filter(|c| { c.kx == TlsCipherKx::Ecdhe }).collect();
    assert!(ecdhe_ciphers.len() > 20);
}

}
<commit_msg>Add integer conversion support for all CipherSuites elements (Kx,Au,Enc,EncMode,Mac)<commit_after>#![allow(non_camel_case_types)]

use phf;

enum_from_primitive! {
#[derive(Debug,PartialEq)]
#[repr(u8)]
pub enum TlsCipherKx {
    Null = 0,
    Psk,
    Krb5,
    Srp,
    Rsa,
    Dh,
    Dhe,
    Ecdh,
    Ecdhe,
}
}

enum_from_primitive! {
#[derive(Debug,PartialEq)]
#[repr(u8)]
pub enum TlsCipherAu {
    Null = 0,
    Psk,
    Krb5,
    Srp,
    Srp_Dss,
    Srp_Rsa,
    Dss,
    Rsa,
    Dhe,
    Ecdsa,
}
}

enum_from_primitive! {
#[derive(Debug,PartialEq)]
#[repr(u8)]
pub enum TlsCipherEnc {
    Null,
    Des,
    TripleDes,
    Rc2,
    Rc4,
    Aria,
    Idea,
    Seed,
    Aes,
    Camellia,
    Chacha20_Poly1305,
}
}

enum_from_primitive! {
#[derive(Debug,PartialEq)]
#[repr(u8)]
pub enum TlsCipherEncMode {
    Null,
    Cbc,
    Ccm,
    Gcm,
}
}

enum_from_primitive! {
#[derive(Debug,PartialEq)]
#[repr(u8)]
pub enum TlsCipherMac {
    Null,
    HmacMd5,
    HmacSha1,
    HmacSha256,
    HmacSha384,
    Aead,
}
}

#[derive(Debug)]
pub struct TlsCipherSuite {
    pub name: &'static str,
    pub id: u16,
    pub kx: TlsCipherKx,
    pub au: TlsCipherAu,
    pub enc: TlsCipherEnc,
    pub enc_mode: TlsCipherEncMode,
    pub enc_size: u16,
    pub mac: TlsCipherMac,
    pub mac_size: u16,
}

include!(concat!(env!("OUT_DIR"), "/codegen.rs"));

impl TlsCipherSuite {
    pub fn from_id(id: u16) -> Option<&'static TlsCipherSuite> {
        CIPHERS.get(&id)
    }

    pub fn from_name<'a>(name: &'a str) -> Option<&'static TlsCipherSuite> {
        for v in CIPHERS.values().filter(|&v| v.name == name) {
            return Some(v);
        }
        None
    }
}

#[cfg(test)]
mod tests {
    use tls_ciphers::{CIPHERS,TlsCipherSuite,TlsCipherKx};

#[test]
fn test_cipher_count() {
    println!("loaded: {} cipher suites", CIPHERS.len());
    assert!(CIPHERS.len() > 0);
}

#[test]
fn test_cipher_from_id() {
    match TlsCipherSuite::from_id(0xc025) {
        Some(ref cipher) => {
            println!("Found cipher: {:?}", cipher);
        },
        None => assert!(false),
    }
}

#[test]
fn test_cipher_from_name() {
    match TlsCipherSuite::from_name("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384") {
        Some(ref cipher) => {
            println!("Found cipher: {:?}", cipher);
        },
        None => assert!(false),
    }
}

#[test]
fn test_cipher_filter() {
    let ecdhe_ciphers : Vec<&TlsCipherSuite> = CIPHERS.values().filter(|c| { c.kx == TlsCipherKx::Ecdhe }).collect();
    assert!(ecdhe_ciphers.len() > 20);
}

}
<|endoftext|>


<commit_before>use bins::error::*;
use bins::Bins;
use bins::configuration::BinsConfiguration;
use bins::engines;
use bins::FlexibleRange;
use bins::network;
use clap::{App, Arg, ArgGroup};
use hyper::Url;
use std::path::Path;
use std::process;

pub struct Arguments {
  pub all: bool,
  pub auth: bool,
  pub bin: Option<String>,
  pub copy: bool,
  pub files: Vec<String>,
  pub force: bool,
  pub input: Option<String>,
  pub json: bool,
  pub message: Option<String>,
  pub name: Option<String>,
  pub number_lines: bool,
  pub output: Option<String>,
  pub private: bool,
  pub range: Option<FlexibleRange>,
  pub raw_urls: bool,
  pub server: Option<Url>,
  pub urls: bool,
  pub write: bool
}

include!(concat!(env!("OUT_DIR"), "/extra_version_info.rs"));

fn get_name() -> String {
  option_env!("CARGO_PKG_NAME").unwrap_or("unknown_name").to_owned()
}

fn get_version(basic: bool) -> String {
  let version = option_env!("CARGO_PKG_VERSION").unwrap_or("unknown_version").to_owned();
  if basic {
    version
  } else {
    let extra_version_info = extra_version_info();
    let feature_info = get_feature_info().map(|x| format!("\n{}", x)).unwrap_or_else(String::new);
    format!("{}\n\n{}{}", version, extra_version_info, feature_info)
  }
}

fn get_feature_info() -> Option<String> {
  let mut features = Vec::new();
  if cfg!(feature = "clipboard_support") {
    features.push("clipboard_support");
  }
  if cfg!(feature = "file_type_checking") {
    features.push("file_type_checking");
  }
  if features.is_empty() {
    None
  } else {
    Some(format!("features: {}", features.join(", ")))
  }
}

fn get_feature_args<'a, 'b>() -> Vec<Arg<'a, 'b>> {
  let mut args = Vec::new();
  if cfg!(feature = "clipboard_support") {
    args.push(Arg::with_name("copy")
      .short("C")
      .long("copy")
      .help("copies the output of the command to the clipboard without a newline")
      .conflicts_with("no-copy"));
    args.push(Arg::with_name("no-copy")
      .short("c")
      .long("no-copy")
      .help("does not copy the output of the command to the clipboard"));
  }
  args
}

pub fn get_arguments(config: &BinsConfiguration) -> Result<Arguments> {
  let mut arguments = Arguments {
    all: false,
    auth: config.get_defaults_auth(),
    bin: config.get_defaults_bin().map(|s| s.to_owned()),
    copy: config.get_defaults_copy(),
    files: Vec::new(),
    force: false,
    input: None,
    json: false,
    message: None,
    name: None,
    number_lines: false,
    output: None,
    private: config.get_defaults_private(),
    range: None,
    raw_urls: false,
    server: None,
    urls: false,
    write: false
  };
  let name = get_name();
  let version = get_version(true);
  let mut app = App::new(name.as_ref())
    .version(version.as_ref())
    .about("A tool for pasting from the terminal")
    .arg(Arg::with_name("version")
      .short("V")
      .long("version")
      .help("prints version information"))
    .arg(Arg::with_name("files")
      .help("files to paste")
      .takes_value(true)
      .multiple(true))
    .arg(Arg::with_name("message")
      .short("m")
      .long("message")
      .help("message to paste")
      .use_delimiter(false)
      .takes_value(true)
      .value_name("string"))
    .arg(Arg::with_name("private")
      .short("p")
      .long("private")
      .help("if the paste should be private")
      .conflicts_with("public"))
    .arg(Arg::with_name("public")
      .short("P")
      .long("public")
      .help("if the paste should be public"))
    .arg(Arg::with_name("auth")
      .short("a")
      .long("auth")
      .help("if authentication (like api keys and tokens) should be used")
      .conflicts_with("anon"))
    .arg(Arg::with_name("anon")
      .short("A")
      .long("anon")
      .help("if pastes should be posted without authentication"))
    .arg(Arg::with_name("bin")
      .short("b")
      .long("bin")
      .help("bin to use when uploading")
      .takes_value(true)
      .possible_values(&*engines::get_bin_names()))
    .arg(Arg::with_name("service")
      .short("s")
      .long("service")
      .help("legacy flag included for backwards compatibility. use --bin, as this will be removed in 2.0.0")
      .takes_value(true)
      .possible_values(&*engines::get_bin_names()))
    .group(ArgGroup::with_name("bin_or_service")
      .args(&["bin", "service"])
      .required(arguments.bin.is_none()))
    .arg(Arg::with_name("list-bins")
      .short("l")
      .long("list-bins")
      .help("lists available bins and exits")
      .conflicts_with_all(&["files", "message", "private", "public", "auth", "anon", "bin_or_service", "input"]))
    .arg(Arg::with_name("list-services")
      .long("list-services")
      .help("legacy flag included for backwards compatibility. use --list-bins, as this will be removed in 2.0.0")
      .conflicts_with_all(&["files", "message", "private", "public", "auth", "anon", "bin_or_service", "input"]))
    .group(ArgGroup::with_name("list-bins_or_list-services").args(&["list-bins", "list-services"]))
    .arg(Arg::with_name("input")
      .short("i")
      .long("input")
      .help("displays raw contents of input paste")
      .takes_value(true)
      .value_name("url")
      .conflicts_with_all(&["auth", "anon", "public", "private", "message", "bin_or_service"]))
    .arg(Arg::with_name("range")
      .short("n")
      .long("range")
      .help("chooses the files to get in input mode, starting from 0 (e.g. \"0\", \"0,1\", \"0-2\", \"2-0,3\")")
      .takes_value(true)
      .value_name("range")
      .use_delimiter(false)
      .requires("input")
      .conflicts_with("files"))
    .arg(Arg::with_name("all")
      .short("L")
      .long("all")
      .help("gets all files in input mode")
      .requires("input")
      .conflicts_with_all(&["files", "range"]))
    .arg(Arg::with_name("raw-urls")
      .short("r")
      .long("raw-urls")
      .help("gets the raw urls instead of the content in input mode")
      .requires("input"))
    .arg(Arg::with_name("urls")
      .short("u")
      .long("urls")
      .help("gets the urls instead of the content in input mode")
      .requires("input")
      .conflicts_with("raw-urls"))
    .arg(Arg::with_name("server")
      .short("S")
      .long("server")
      .help("specifies the server to use for the service (only support on hastebin)")
      .takes_value(true)
      .value_name("server_url"))
    .arg(Arg::with_name("name")
      .short("N")
      .long("name")
      .help("specifies a file name for --message or stdin")
      .takes_value(true)
      .value_name("name")
      .conflicts_with("files"))
    .arg(Arg::with_name("force")
      .short("f")
      .long("force")
      .help("overrides warnings about file type or size when uploading")
      .conflicts_with("input"))
    .arg(Arg::with_name("number_lines")
      .short("e")
      .long("number-lines")
      .help("display line numbers for each file in input mode")
      .requires("input"))
    .arg(Arg::with_name("write")
      .short("w")
      .long("write")
      .help("writes pastes to files in input mode")
      .requires("input"))
    .arg(Arg::with_name("output")
      .short("o")
      .long("output")
      .help("specifies where to save files in write mode")
      .takes_value(true)
      .value_name("dir")
      .requires("write"))
    .arg(Arg::with_name("json")
      .short("j")
      .long("json")
      .help("output a json object instead of normal values")
      .conflicts_with_all(&["write", "urls", "raw-urls"]));
  for arg in get_feature_args() {
    app = app.arg(arg);
  }
  let res = app.get_matches();
  if res.is_present("version") {
    let version = get_version(false);
    println!("{} {}", name, version);
    process::exit(0);
  }
  if res.is_present("list-bins_or_list-services") {
    println!("{}", engines::get_bin_names().join("\n"));
    process::exit(0);
  }
  if let Some(files) = res.values_of("files") {
    arguments.files = files.map(|s| s.to_owned()).collect();
  }
  if let Some(message) = res.value_of("message") {
    arguments.message = Some(message.to_owned());
  }
  if let Some(bin) = res.value_of("bin_or_service") {
    arguments.bin = Some(bin.to_owned());
  }
  if let Some(input) = res.value_of("input") {
    arguments.input = Some(input.to_owned());
  }
  if let Some(range) = res.value_of("range") {
    arguments.range = Some(try!(FlexibleRange::parse(range)));
  }
  if let Some(server) = res.value_of("server") {
    if let Some(ref bin) = arguments.bin {
      if bin.to_lowercase() != "hastebin" {
        return Err("--server may only be used if --service is hastebin".into());
      }
    }
    arguments.server = Some(try!(network::parse_url(server).chain_err(|| "invalid --server")));
  }
  if let Some(name) = res.value_of("name") {
    let name = try!(Bins::sanitize_path(Path::new(name)));
    arguments.name = Some(name.to_owned());
  }
  if let Some(output) = res.value_of("output") {
    arguments.output = Some(output.to_owned());
  }
  arguments.all = res.is_present("all");
  arguments.force = res.is_present("force");
  arguments.json = res.is_present("json");
  arguments.number_lines = res.is_present("number_lines");
  arguments.raw_urls = res.is_present("raw-urls");
  arguments.urls = res.is_present("urls");
  arguments.write = res.is_present("write");
  if res.is_present("private") {
    arguments.private = true;
  } else if res.is_present("public") {
    arguments.private = false;
  }
  if res.is_present("anon") {
    arguments.auth = false;
  } else if res.is_present("auth") {
    arguments.auth = true;
  }
  if res.is_present("copy") {
    arguments.copy = true;
  } else if res.is_present("no-copy") {
    arguments.copy = false;
  }
  Ok(arguments)
}
<commit_msg>refactor: sort --help<commit_after>use bins::error::*;
use bins::Bins;
use bins::configuration::BinsConfiguration;
use bins::engines;
use bins::FlexibleRange;
use bins::network;
use clap::{App, AppSettings, Arg, ArgGroup};
use hyper::Url;
use std::path::Path;
use std::process;

pub struct Arguments {
  pub all: bool,
  pub auth: bool,
  pub bin: Option<String>,
  pub copy: bool,
  pub files: Vec<String>,
  pub force: bool,
  pub input: Option<String>,
  pub json: bool,
  pub message: Option<String>,
  pub name: Option<String>,
  pub number_lines: bool,
  pub output: Option<String>,
  pub private: bool,
  pub range: Option<FlexibleRange>,
  pub raw_urls: bool,
  pub server: Option<Url>,
  pub urls: bool,
  pub write: bool
}

include!(concat!(env!("OUT_DIR"), "/extra_version_info.rs"));

fn get_name() -> String {
  option_env!("CARGO_PKG_NAME").unwrap_or("unknown_name").to_owned()
}

fn get_version(basic: bool) -> String {
  let version = option_env!("CARGO_PKG_VERSION").unwrap_or("unknown_version").to_owned();
  if basic {
    version
  } else {
    let extra_version_info = extra_version_info();
    let feature_info = get_feature_info().map(|x| format!("\n{}", x)).unwrap_or_else(String::new);
    format!("{}\n\n{}{}", version, extra_version_info, feature_info)
  }
}

fn get_feature_info() -> Option<String> {
  let mut features = Vec::new();
  if cfg!(feature = "clipboard_support") {
    features.push("clipboard_support");
  }
  if cfg!(feature = "file_type_checking") {
    features.push("file_type_checking");
  }
  if features.is_empty() {
    None
  } else {
    Some(format!("features: {}", features.join(", ")))
  }
}

fn get_feature_args<'a, 'b>() -> Vec<Arg<'a, 'b>> {
  let mut args = Vec::new();
  if cfg!(feature = "clipboard_support") {
    args.push(Arg::with_name("copy")
      .short("C")
      .long("copy")
      .help("copies the output of the command to the clipboard without a newline")
      .conflicts_with("no-copy"));
    args.push(Arg::with_name("no-copy")
      .short("c")
      .long("no-copy")
      .help("does not copy the output of the command to the clipboard"));
  }
  args
}

pub fn get_arguments(config: &BinsConfiguration) -> Result<Arguments> {
  let mut arguments = Arguments {
    all: false,
    auth: config.get_defaults_auth(),
    bin: config.get_defaults_bin().map(|s| s.to_owned()),
    copy: config.get_defaults_copy(),
    files: Vec::new(),
    force: false,
    input: None,
    json: false,
    message: None,
    name: None,
    number_lines: false,
    output: None,
    private: config.get_defaults_private(),
    range: None,
    raw_urls: false,
    server: None,
    urls: false,
    write: false
  };
  let name = get_name();
  let version = get_version(true);
  let mut app = App::new(name.as_ref())
    .version(version.as_ref())
    .about("A tool for pasting from the terminal")
    .setting(AppSettings::DeriveDisplayOrder)
    .setting(AppSettings::UnifiedHelpMessage)
    .arg(Arg::with_name("bin")
      .short("b")
      .long("bin")
      .help("bin to use when uploading")
      .takes_value(true)
      .possible_values(&*engines::get_bin_names()))
    .arg(Arg::with_name("service")
      .short("s")
      .long("service")
      .help("legacy flag included for backwards compatibility. use --bin, as this will be removed in 2.0.0")
      .takes_value(true)
      .possible_values(&*engines::get_bin_names()))
    .group(ArgGroup::with_name("bin_or_service")
      .args(&["bin", "service"])
      .required(arguments.bin.is_none()))
    .arg(Arg::with_name("files")
      .help("files to paste")
      .takes_value(true)
      .multiple(true))
    .arg(Arg::with_name("message")
      .short("m")
      .long("message")
      .help("message to paste")
      .use_delimiter(false)
      .takes_value(true)
      .value_name("string"))
    .arg(Arg::with_name("private")
      .short("p")
      .long("private")
      .help("if the paste should be private")
      .conflicts_with("public"))
    .arg(Arg::with_name("public")
      .short("P")
      .long("public")
      .help("if the paste should be public"))
    .arg(Arg::with_name("auth")
      .short("a")
      .long("auth")
      .help("if authentication (like api keys and tokens) should be used")
      .conflicts_with("anon"))
    .arg(Arg::with_name("anon")
      .short("A")
      .long("anon")
      .help("if pastes should be posted without authentication"))
    .arg(Arg::with_name("name")
      .short("N")
      .long("name")
      .help("specifies a file name for --message or stdin")
      .takes_value(true)
      .value_name("name")
      .conflicts_with("files"))
    .arg(Arg::with_name("force")
      .short("f")
      .long("force")
      .help("overrides warnings about file type or size when uploading")
      .conflicts_with("input"))
    .arg(Arg::with_name("list-bins")
      .short("l")
      .long("list-bins")
      .help("lists available bins and exits")
      .conflicts_with_all(&["files", "message", "private", "public", "auth", "anon", "bin_or_service", "input"]))
    .arg(Arg::with_name("list-services")
      .long("list-services")
      .help("legacy flag included for backwards compatibility. use --list-bins, as this will be removed in 2.0.0")
      .conflicts_with_all(&["files", "message", "private", "public", "auth", "anon", "bin_or_service", "input"]))
    .group(ArgGroup::with_name("list-bins_or_list-services").args(&["list-bins", "list-services"]))
    .arg(Arg::with_name("server")
      .short("S")
      .long("server")
      .help("specifies the server to use for the service (only supported on hastebin)")
      .takes_value(true)
      .value_name("server_url"))
    .arg(Arg::with_name("input")
      .short("i")
      .long("input")
      .help("displays raw contents of input paste")
      .takes_value(true)
      .value_name("url")
      .conflicts_with_all(&["auth", "anon", "public", "private", "message", "bin_or_service"]))
    .arg(Arg::with_name("write")
      .short("w")
      .long("write")
      .help("writes pastes to files in input mode")
      .requires("input"))
    .arg(Arg::with_name("output")
      .short("o")
      .long("output")
      .help("specifies where to save files in write mode")
      .takes_value(true)
      .value_name("dir")
      .requires("write"))
    .arg(Arg::with_name("all")
      .short("L")
      .long("all")
      .help("gets all files in input mode")
      .requires("input")
      .conflicts_with_all(&["files", "range"]))
    .arg(Arg::with_name("range")
      .short("n")
      .long("range")
      .help("chooses the files to get in input mode, starting from 0 (e.g. \"0\", \"0,1\", \"0-2\", \"2-0,3\")")
      .takes_value(true)
      .value_name("range")
      .use_delimiter(false)
      .requires("input")
      .conflicts_with("files"))
    .arg(Arg::with_name("raw-urls")
      .short("r")
      .long("raw-urls")
      .help("gets the raw urls instead of the content in input mode")
      .requires("input"))
    .arg(Arg::with_name("urls")
      .short("u")
      .long("urls")
      .help("gets the urls instead of the content in input mode")
      .requires("input")
      .conflicts_with("raw-urls"))
    .arg(Arg::with_name("number_lines")
      .short("e")
      .long("number-lines")
      .help("display line numbers for each file in input mode")
      .requires("input"))
    .arg(Arg::with_name("json")
      .short("j")
      .long("json")
      .help("output a json object instead of normal values")
      .conflicts_with_all(&["write", "urls", "raw-urls"]));
  for arg in get_feature_args() {
    app = app.arg(arg);
  }
  app = app.arg(Arg::with_name("version")
    .short("V")
    .long("version")
    .help("prints version information")
    .overrides_with("bin_or_service"));
  let res = app.get_matches();
  if res.is_present("version") {
    let version = get_version(false);
    println!("{} {}", name, version);
    process::exit(0);
  }
  if res.is_present("list-bins_or_list-services") {
    println!("{}", engines::get_bin_names().join("\n"));
    process::exit(0);
  }
  if let Some(files) = res.values_of("files") {
    arguments.files = files.map(|s| s.to_owned()).collect();
  }
  if let Some(message) = res.value_of("message") {
    arguments.message = Some(message.to_owned());
  }
  if let Some(bin) = res.value_of("bin_or_service") {
    arguments.bin = Some(bin.to_owned());
  }
  if let Some(input) = res.value_of("input") {
    arguments.input = Some(input.to_owned());
  }
  if let Some(range) = res.value_of("range") {
    arguments.range = Some(try!(FlexibleRange::parse(range)));
  }
  if let Some(server) = res.value_of("server") {
    if let Some(ref bin) = arguments.bin {
      if bin.to_lowercase() != "hastebin" {
        return Err("--server may only be used if --service is hastebin".into());
      }
    }
    arguments.server = Some(try!(network::parse_url(server).chain_err(|| "invalid --server")));
  }
  if let Some(name) = res.value_of("name") {
    let name = try!(Bins::sanitize_path(Path::new(name)));
    arguments.name = Some(name.to_owned());
  }
  if let Some(output) = res.value_of("output") {
    arguments.output = Some(output.to_owned());
  }
  arguments.all = res.is_present("all");
  arguments.force = res.is_present("force");
  arguments.json = res.is_present("json");
  arguments.number_lines = res.is_present("number_lines");
  arguments.raw_urls = res.is_present("raw-urls");
  arguments.urls = res.is_present("urls");
  arguments.write = res.is_present("write");
  if res.is_present("private") {
    arguments.private = true;
  } else if res.is_present("public") {
    arguments.private = false;
  }
  if res.is_present("anon") {
    arguments.auth = false;
  } else if res.is_present("auth") {
    arguments.auth = true;
  }
  if res.is_present("copy") {
    arguments.copy = true;
  } else if res.is_present("no-copy") {
    arguments.copy = false;
  }
  Ok(arguments)
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
use net_traits::storage_thread::{StorageThreadMsg, StorageType};
use resource_thread;
use std::borrow::ToOwned;
use std::collections::BTreeMap;
use std::collections::HashMap;
use url::Url;
use util::opts;
use util::thread::spawn_named;

const QUOTA_SIZE_LIMIT: usize = 5 * 1024 * 1024;

pub trait StorageThreadFactory {
    fn new() -> Self;
}

impl StorageThreadFactory for IpcSender<StorageThreadMsg> {
    /// Create a storage thread
    fn new() -> IpcSender<StorageThreadMsg> {
        let (chan, port) = ipc::channel().unwrap();
        spawn_named("StorageManager".to_owned(), move || {
            StorageManager::new(port).start();
        });
        chan
    }
}

struct StorageManager {
    port: IpcReceiver<StorageThreadMsg>,
    session_data: HashMap<String, (usize, BTreeMap<String, String>)>,
    local_data: HashMap<String, (usize, BTreeMap<String, String>)>,
}

impl StorageManager {
    fn new(port: IpcReceiver<StorageThreadMsg>) -> StorageManager {
        let mut local_data = HashMap::new();
        if let Some(ref config_dir) = opts::get().config_dir {
            resource_thread::read_json_from_file(&mut local_data, config_dir, "local_data.json");
        }
        StorageManager {
            port: port,
            session_data: HashMap::new(),
            local_data: local_data,
        }
    }
}

impl StorageManager {
    fn start(&mut self) {
        loop {
            match self.port.recv().unwrap() {
                StorageThreadMsg::Length(sender, url, storage_type) => {
                    self.length(sender, url, storage_type)
                }
                StorageThreadMsg::Key(sender, url, storage_type, index) => {
                    self.key(sender, url, storage_type, index)
                }
                StorageThreadMsg::Keys(sender, url, storage_type) => {
                    self.keys(sender, url, storage_type)
                }
                StorageThreadMsg::SetItem(sender, url, storage_type, name, value) => {
                    self.set_item(sender, url, storage_type, name, value)
                }
                StorageThreadMsg::GetItem(sender, url, storage_type, name) => {
                    self.request_item(sender, url, storage_type, name)
                }
                StorageThreadMsg::RemoveItem(sender, url, storage_type, name) => {
                    self.remove_item(sender, url, storage_type, name)
                }
                StorageThreadMsg::Clear(sender, url, storage_type) => {
                    self.clear(sender, url, storage_type)
                }
                StorageThreadMsg::Exit(sender) => {
                    if let Some(ref config_dir) = opts::get().config_dir {
                        resource_thread::write_json_to_file(&self.local_data, config_dir, "local_data.json");
                    }
                    let _ = sender.send(());
                    break
                }
            }
        }
    }

    fn select_data(&self, storage_type: StorageType)
                   -> &HashMap<String, (usize, BTreeMap<String, String>)> {
        match storage_type {
            StorageType::Session => &self.session_data,
            StorageType::Local => &self.local_data
        }
    }

    fn select_data_mut(&mut self, storage_type: StorageType)
                       -> &mut HashMap<String, (usize, BTreeMap<String, String>)> {
        match storage_type {
            StorageType::Session => &mut self.session_data,
            StorageType::Local => &mut self.local_data
        }
    }

    fn length(&self, sender: IpcSender<usize>, url: Url, storage_type: StorageType) {
        let origin = self.origin_as_string(url);
        let data = self.select_data(storage_type);
        sender.send(data.get(&origin).map_or(0, |&(_, ref entry)| entry.len())).unwrap();
    }

    fn key(&self,
           sender: IpcSender<Option<String>>,
           url: Url,
           storage_type: StorageType,
           index: u32) {
        let origin = self.origin_as_string(url);
        let data = self.select_data(storage_type);
        let key = data.get(&origin)
                      .and_then(|&(_, ref entry)| entry.keys().nth(index as usize))
                      .cloned();
        sender.send(key).unwrap();
    }

    fn keys(&self,
            sender: IpcSender<Vec<String>>,
            url: Url,
            storage_type: StorageType) {
        let origin = self.origin_as_string(url);
        let data = self.select_data(storage_type);
        let keys = data.get(&origin)
                       .map_or(vec![], |&(_, ref entry)| entry.keys().cloned().collect());

        sender.send(keys).unwrap();
    }

    /// Sends Ok(changed, Some(old_value)) in case there was a previous
    /// value with the same key name but with different value name
    /// otherwise sends Err(()) to indicate that the operation would result in
    /// exceeding the quota limit
    fn set_item(&mut self,
                sender: IpcSender<Result<(bool, Option<String>), ()>>,
                url: Url,
                storage_type: StorageType,
                name: String,
                value: String) {
        let origin = self.origin_as_string(url);

        let current_total_size = {
            let local_data = self.select_data(StorageType::Local);
            let session_data = self.select_data(StorageType::Session);
            let local_data_size = local_data.get(&origin).map_or(0, |&(total, _)| total);
            let session_data_size = session_data.get(&origin).map_or(0, |&(total, _)| total);
            local_data_size + session_data_size
        };

        let data = self.select_data_mut(storage_type);
        if !data.contains_key(&origin) {
            data.insert(origin.clone(), (0, BTreeMap::new()));
        }

        let message = data.get_mut(&origin).map(|&mut (ref mut total, ref mut entry)| {
            let mut new_total_size = current_total_size + value.as_bytes().len();
            if let Some(old_value) = entry.get(&name) {
                new_total_size -= old_value.as_bytes().len();
            } else {
                new_total_size += name.as_bytes().len();
            }

            if new_total_size > QUOTA_SIZE_LIMIT {
                return Err(());
            }

            let message = entry.insert(name.clone(), value.clone()).map_or(
                Ok((true, None)),
                |old| if old == value {
                    Ok((false, None))
                } else {
                    Ok((true, Some(old)))
                });
            *total = new_total_size;
            message
        }).unwrap();
        sender.send(message).unwrap();
    }

    fn request_item(&self,
                sender: IpcSender<Option<String>>,
                url: Url,
                storage_type: StorageType,
                name: String) {
        let origin = self.origin_as_string(url);
        let data = self.select_data(storage_type);
        sender.send(data.get(&origin)
                    .and_then(|&(_, ref entry)| entry.get(&name))
                    .map(String::clone)).unwrap();
    }

    /// Sends Some(old_value) in case there was a previous value with the key name, otherwise sends None
    fn remove_item(&mut self,
                   sender: IpcSender<Option<String>>,
                   url: Url,
                   storage_type: StorageType,
                   name: String) {
        let origin = self.origin_as_string(url);
        let data = self.select_data_mut(storage_type);
        let old_value = data.get_mut(&origin).and_then(|&mut (ref mut total, ref mut entry)| {
            entry.remove(&name).and_then(|old| {
                *total -= name.as_bytes().len() + old.as_bytes().len();
                Some(old)
            })
        });
        sender.send(old_value).unwrap();
    }

    fn clear(&mut self, sender: IpcSender<bool>, url: Url, storage_type: StorageType) {
        let origin = self.origin_as_string(url);
        let data = self.select_data_mut(storage_type);
        sender.send(data.get_mut(&origin)
                    .map_or(false, |&mut (ref mut total, ref mut entry)| {
                        if !entry.is_empty() {
                            entry.clear();
                            *total = 0;
                            true
                        } else {
                            false
                        }})).unwrap();
    }

    fn origin_as_string(&self, url: Url) -> String {
        url.origin().ascii_serialization()
    }
}
<commit_msg>Auto merge of #12289 - nox:slate, r=Ms2ger<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
use net_traits::storage_thread::{StorageThreadMsg, StorageType};
use resource_thread;
use std::borrow::ToOwned;
use std::collections::BTreeMap;
use std::collections::HashMap;
use url::Url;
use util::opts;
use util::thread::spawn_named;

const QUOTA_SIZE_LIMIT: usize = 5 * 1024 * 1024;

pub trait StorageThreadFactory {
    fn new() -> Self;
}

impl StorageThreadFactory for IpcSender<StorageThreadMsg> {
    /// Create a storage thread
    fn new() -> IpcSender<StorageThreadMsg> {
        let (chan, port) = ipc::channel().unwrap();
        spawn_named("StorageManager".to_owned(), move || {
            StorageManager::new(port).start();
        });
        chan
    }
}

struct StorageManager {
    port: IpcReceiver<StorageThreadMsg>,
    session_data: HashMap<String, (usize, BTreeMap<String, String>)>,
    local_data: HashMap<String, (usize, BTreeMap<String, String>)>,
}

impl StorageManager {
    fn new(port: IpcReceiver<StorageThreadMsg>) -> StorageManager {
        let mut local_data = HashMap::new();
        if let Some(ref config_dir) = opts::get().config_dir {
            resource_thread::read_json_from_file(&mut local_data, config_dir, "local_data.json");
        }
        StorageManager {
            port: port,
            session_data: HashMap::new(),
            local_data: local_data,
        }
    }
}

impl StorageManager {
    fn start(&mut self) {
        loop {
            match self.port.recv().unwrap() {
                StorageThreadMsg::Length(sender, url, storage_type) => {
                    self.length(sender, url, storage_type)
                }
                StorageThreadMsg::Key(sender, url, storage_type, index) => {
                    self.key(sender, url, storage_type, index)
                }
                StorageThreadMsg::Keys(sender, url, storage_type) => {
                    self.keys(sender, url, storage_type)
                }
                StorageThreadMsg::SetItem(sender, url, storage_type, name, value) => {
                    self.set_item(sender, url, storage_type, name, value)
                }
                StorageThreadMsg::GetItem(sender, url, storage_type, name) => {
                    self.request_item(sender, url, storage_type, name)
                }
                StorageThreadMsg::RemoveItem(sender, url, storage_type, name) => {
                    self.remove_item(sender, url, storage_type, name)
                }
                StorageThreadMsg::Clear(sender, url, storage_type) => {
                    self.clear(sender, url, storage_type)
                }
                StorageThreadMsg::Exit(sender) => {
                    if let Some(ref config_dir) = opts::get().config_dir {
                        resource_thread::write_json_to_file(&self.local_data, config_dir, "local_data.json");
                    }
                    let _ = sender.send(());
                    break
                }
            }
        }
    }

    fn select_data(&self, storage_type: StorageType)
                   -> &HashMap<String, (usize, BTreeMap<String, String>)> {
        match storage_type {
            StorageType::Session => &self.session_data,
            StorageType::Local => &self.local_data
        }
    }

    fn select_data_mut(&mut self, storage_type: StorageType)
                       -> &mut HashMap<String, (usize, BTreeMap<String, String>)> {
        match storage_type {
            StorageType::Session => &mut self.session_data,
            StorageType::Local => &mut self.local_data
        }
    }

    fn length(&self, sender: IpcSender<usize>, url: Url, storage_type: StorageType) {
        let origin = self.origin_as_string(url);
        let data = self.select_data(storage_type);
        sender.send(data.get(&origin).map_or(0, |&(_, ref entry)| entry.len())).unwrap();
    }

    fn key(&self,
           sender: IpcSender<Option<String>>,
           url: Url,
           storage_type: StorageType,
           index: u32) {
        let origin = self.origin_as_string(url);
        let data = self.select_data(storage_type);
        let key = data.get(&origin)
                      .and_then(|&(_, ref entry)| entry.keys().nth(index as usize))
                      .cloned();
        sender.send(key).unwrap();
    }

    fn keys(&self,
            sender: IpcSender<Vec<String>>,
            url: Url,
            storage_type: StorageType) {
        let origin = self.origin_as_string(url);
        let data = self.select_data(storage_type);
        let keys = data.get(&origin)
                       .map_or(vec![], |&(_, ref entry)| entry.keys().cloned().collect());

        sender.send(keys).unwrap();
    }

    /// Sends Ok(changed, Some(old_value)) in case there was a previous
    /// value with the same key name but with different value name
    /// otherwise sends Err(()) to indicate that the operation would result in
    /// exceeding the quota limit
    fn set_item(&mut self,
                sender: IpcSender<Result<(bool, Option<String>), ()>>,
                url: Url,
                storage_type: StorageType,
                name: String,
                value: String) {
        let origin = self.origin_as_string(url);

        let (this_storage_size, other_storage_size) = {
            let local_data = self.select_data(StorageType::Local);
            let session_data = self.select_data(StorageType::Session);
            let local_data_size = local_data.get(&origin).map_or(0, |&(total, _)| total);
            let session_data_size = session_data.get(&origin).map_or(0, |&(total, _)| total);
            match storage_type {
                StorageType::Local => (local_data_size, session_data_size),
                StorageType::Session => (session_data_size, local_data_size),
            }
        };

        let data = self.select_data_mut(storage_type);
        if !data.contains_key(&origin) {
            data.insert(origin.clone(), (0, BTreeMap::new()));
        }

        let message = data.get_mut(&origin).map(|&mut (ref mut total, ref mut entry)| {
            let mut new_total_size = this_storage_size + value.as_bytes().len();
            if let Some(old_value) = entry.get(&name) {
                new_total_size -= old_value.as_bytes().len();
            } else {
                new_total_size += name.as_bytes().len();
            }

            if (new_total_size + other_storage_size) > QUOTA_SIZE_LIMIT {
                return Err(());
            }

            let message = entry.insert(name.clone(), value.clone()).map_or(
                Ok((true, None)),
                |old| if old == value {
                    Ok((false, None))
                } else {
                    Ok((true, Some(old)))
                });
            *total = new_total_size;
            message
        }).unwrap();
        sender.send(message).unwrap();
    }

    fn request_item(&self,
                sender: IpcSender<Option<String>>,
                url: Url,
                storage_type: StorageType,
                name: String) {
        let origin = self.origin_as_string(url);
        let data = self.select_data(storage_type);
        sender.send(data.get(&origin)
                    .and_then(|&(_, ref entry)| entry.get(&name))
                    .map(String::clone)).unwrap();
    }

    /// Sends Some(old_value) in case there was a previous value with the key name, otherwise sends None
    fn remove_item(&mut self,
                   sender: IpcSender<Option<String>>,
                   url: Url,
                   storage_type: StorageType,
                   name: String) {
        let origin = self.origin_as_string(url);
        let data = self.select_data_mut(storage_type);
        let old_value = data.get_mut(&origin).and_then(|&mut (ref mut total, ref mut entry)| {
            entry.remove(&name).and_then(|old| {
                *total -= name.as_bytes().len() + old.as_bytes().len();
                Some(old)
            })
        });
        sender.send(old_value).unwrap();
    }

    fn clear(&mut self, sender: IpcSender<bool>, url: Url, storage_type: StorageType) {
        let origin = self.origin_as_string(url);
        let data = self.select_data_mut(storage_type);
        sender.send(data.get_mut(&origin)
                    .map_or(false, |&mut (ref mut total, ref mut entry)| {
                        if !entry.is_empty() {
                            entry.clear();
                            *total = 0;
                            true
                        } else {
                            false
                        }})).unwrap();
    }

    fn origin_as_string(&self, url: Url) -> String {
        url.origin().ascii_serialization()
    }
}
<|endoftext|>


<commit_before>use pg_db::PgDB;
use capnp_rpc::ez_rpc::EzRpcServer;
use holmes_capnp::holmes;
use server::HolmesImpl;
use std::error::Error;
use postgres::{SslMode, Connection, IntoConnectParams};
use std::fmt::{Formatter, Display};
use std::thread::{Thread, JoinGuard};

pub fn unwrap<T, E : Display>(r : &Result<T,E>) -> &T {
  match r {
    &Ok(ref v) => {v}
    &Err(ref e) => {panic!(format!("unwrap failed: {}", e))}
  }
}

pub enum DB {
  Postgres(&'static str)
}

pub enum DBError {
  NoDB
}

use server_control::DBError::*;

impl Display for DBError {
  fn fmt(&self, fmt : &mut Formatter) -> Result<(),::std::fmt::Error> {
    match self {
      &NoDB => {fmt.write_str("No database specified"); Ok(())}
    }
  }
}

impl Error for DBError {
  fn description(&self) -> &str {
    match self {
      &NoDB => {"No database specified"}
    }
  }
}

impl DB {
  fn destroy(&self) -> Result<(), Box<Error>> {
    match self {
      &DB::Postgres(str) => {
        let mut params = try!(str.into_connect_params());
        let old_db = try!(params.database.ok_or(NoDB));
        params.database = Some("postgresql".to_string());
        let conn = try!(Connection::connect(params, &SslMode::None));
        try!(conn.execute("DROP DATABASE $1", &[&old_db]));
      }
    }
    Ok(())
  }
}

pub struct Server<'a> {
  addr : &'a str,
  db : DB,
  thread : Option<JoinGuard<'a, ()>>
}

impl<'a> Server<'a> {
  pub fn new(addr : &str, db : DB) -> Server {
    Server {
      addr   : addr,
      db     : db,
      thread : None
    }
  }
  pub fn boot(&mut self) -> Result<(), Box<Error+'a>> {
    let rpc_server = try!(EzRpcServer::new(self.addr));
    let db = match self.db {
      DB::Postgres(s) => {try!(PgDB::new(s))}
    };
    let holmes = Box::new(holmes::ServerDispatch {
      server : Box::new(HolmesImpl::new(Box::new(db)))
      });
    rpc_server.export_cap("holmes", holmes);
    let thread = Thread::scoped(move || {
      let _ = rpc_server.serve().join();
    });
    self.thread = Some(thread);
    Ok(())
  }
  pub fn join(&mut self) -> ::std::thread::Result<()> {
    let thread = self.thread.take();
    thread.expect("Tried to join non-running server").join()
  }
  pub fn shutdown(&mut self) -> ::std::thread::Result<()> {
    //TODO: Send message to thread that destroys it
    self.join()
  }
  pub fn destroy(&mut self) -> ::std::thread::Result<()> {
    let res = self.shutdown();
    self.db.destroy();
    res
  }
  pub fn reboot(&mut self) -> Result<(), Box<Error+'a>> {
    self.shutdown();
    try!(self.boot());
    Ok(())
  }
}
<commit_msg>Make server create the database if necessary<commit_after>use pg_db::PgDB;
use capnp_rpc::ez_rpc::EzRpcServer;
use holmes_capnp::holmes;
use server::HolmesImpl;
use std::error::Error;
use postgres::{SslMode, Connection, IntoConnectParams};
use std::fmt::{Formatter, Display};
use std::thread::{Thread, JoinGuard};

pub fn unwrap<T, E : Display>(r : &Result<T,E>) -> &T {
  match r {
    &Ok(ref v) => {v}
    &Err(ref e) => {panic!(format!("unwrap failed: {}", e))}
  }
}

pub enum DB {
  Postgres(&'static str)
}

pub enum DBError {
  NoDB
}

use server_control::DBError::*;

impl Display for DBError {
  fn fmt(&self, fmt : &mut Formatter) -> Result<(),::std::fmt::Error> {
    match self {
      &NoDB => {fmt.write_str("No database specified"); Ok(())}
    }
  }
}

impl Error for DBError {
  fn description(&self) -> &str {
    match self {
      &NoDB => {"No database specified"}
    }
  }
}

impl DB {
  fn destroy(&self) -> Result<(), Box<Error>> {
    match self {
      &DB::Postgres(str) => {
        let mut params = try!(str.into_connect_params());
        let old_db = try!(params.database.ok_or(NoDB));
        params.database = Some("postgres".to_string());
        let conn = try!(Connection::connect(params, &SslMode::None));
        let drop_query = format!("DROP DATABASE {}", &old_db);
        try!(conn.execute(drop_query.as_slice(), &[]));
      }
    }
    Ok(())
  }
  fn create(&self) -> Result<(), Box<Error>> {
    match self {
      &DB::Postgres(str) => {
        let mut params = try!(str.into_connect_params());
        let old_db = try!(params.database.ok_or(NoDB));
        params.database = Some("postgres".to_string());
        let conn = try!(Connection::connect(params, &SslMode::None));
        let create_query = format!("CREATE DATABASE {}", &old_db);
        try!(conn.execute(create_query.as_slice(), &[]));
      }
    }
    Ok(())
  }
}

pub struct Server<'a> {
  addr : &'a str,
  db : DB,
  thread : Option<JoinGuard<'a, ()>>
}

impl<'a> Server<'a> {
  pub fn new(addr : &str, db : DB) -> Server {
    Server {
      addr   : addr,
      db     : db,
      thread : None
    }
  }
  pub fn boot(&mut self) -> Result<(), Box<Error+'a>> {
    self.db.create();
    let rpc_server = try!(EzRpcServer::new(self.addr));
    let db = match self.db {
      DB::Postgres(s) => {try!(PgDB::new(s))}
    };
    let holmes = Box::new(holmes::ServerDispatch {
      server : Box::new(HolmesImpl::new(Box::new(db)))
      });
    rpc_server.export_cap("holmes", holmes);
    let thread = Thread::scoped(move || {
      let _ = rpc_server.serve().join();
    });
    self.thread = Some(thread);
    Ok(())
  }
  pub fn join(&mut self) -> ::std::thread::Result<()> {
    let thread = self.thread.take();
    thread.expect("Tried to join non-running server").join()
  }
  pub fn shutdown(&mut self) -> ::std::thread::Result<()> {
    //TODO: Send message to thread that destroys it
    self.join()
  }
  pub fn destroy(&mut self) -> ::std::thread::Result<()> {
    let res = self.shutdown();
    self.db.destroy();
    res
  }
  pub fn reboot(&mut self) -> Result<(), Box<Error+'a>> {
    self.shutdown();
    try!(self.boot());
    Ok(())
  }
}
<|endoftext|>