<commit_before>use std::mem;
use std::ptr;
use std::marker::PhantomData;
use std::borrow::{Borrow, BorrowMut};
use gl;
use gl::types::*;
use context::ContextOf;
use framebuffer::FramebufferBinding;
use types::{DrawingMode, GLError};
use index_data::{IndexData, IndexDatum, IndexDatumType};

pub struct Buffer {
    gl_id: GLuint
}

impl Buffer {
    pub fn gl_id(&self) -> GLuint {
        self.gl_id
    }
}

impl Drop for Buffer {
    fn drop(&mut self) {
        unsafe {
            gl::DeleteBuffers(1, &self.gl_id as *const GLuint);
        }
    }
}

impl<AB, EAB, P, FB, RB, TU> ContextOf<AB, EAB, P, FB, RB, TU> {
    pub fn gen_buffer(&self) -> Buffer {
        unsafe {
            let mut id : GLuint = 0;

            gl::GenBuffers(1, &mut id as *mut GLuint);
            dbg_gl_sanity_check! {
                GLError::InvalidValue => "`n` is negative",
                _ => "Unknown error"
            }

            Buffer { gl_id: id }
        }
    }

    pub fn bind_array_buffer<'a>(self, buffer: &'a mut Buffer)
        -> (
            ArrayBufferBinding<'a>,
            ContextOf<(), EAB, P, FB, RB, TU>
        )
        where AB: BorrowMut<ArrayBufferBinder>
    {
        let (mut array_buffer, gl) = self.split_array_buffer();
        (array_buffer.borrow_mut().bind(buffer), gl)
    }

    pub fn bind_element_array_buffer<'a>(self, buffer: &'a mut Buffer)
        -> (
            ElementArrayBufferBinding<'a>,
            ContextOf<AB, (), P, FB, RB, TU>
        )
        where EAB: BorrowMut<ElementArrayBufferBinder>
    {
        let (mut element_array_buffer, gl) = self.split_element_array_buffer();
        (element_array_buffer.borrow_mut().bind(buffer), gl)
    }
}

gl_enum! {
    pub gl_enum BufferDataUsage {
        StreamDraw as STREAM_DRAW = gl::STREAM_DRAW,
        StaticDraw as STATIC_DRAW = gl::STATIC_DRAW,
        DynamicDraw as DYNAMIC_DRAW = gl::DYNAMIC_DRAW
    }
}

gl_enum! {
    pub gl_enum BufferBindingTarget {
        ArrayBuffer as ARRAY_BUFFER = gl::ARRAY_BUFFER,
        ElementArrayBuffer as ELEMENT_ARRAY_BUFFER = gl::ELEMENT_ARRAY_BUFFER
    }
}

pub trait BufferBinding {
    fn target(&self) -> BufferBindingTarget;

    fn buffer_bytes(&mut self, bytes: &[u8], usage: BufferDataUsage) {
        let ptr = bytes.as_ptr();
        let size = bytes.len() * mem::size_of::<u8>();
        unsafe {
            gl::BufferData(self.target().gl_enum(),
                           size as GLsizeiptr,
                           ptr as *const GLvoid,
                           usage.gl_enum());
            dbg_gl_error! {
                GLError::InvalidEnum => "Invalid `target` or `usage`",
                GLError::InvalidValue => "`size` is negative",
                GLError::InvalidOperation => "Object 0 is bound to buffer target",
                GLError::OutOfMemory => "Unable to create a large enough buffer",
                _ => "Unknown error"
            }
        }
    }
}

pub struct ArrayBufferBinding<'a> {
    phantom: PhantomData<&'a mut Buffer>
}

impl<'a> BufferBinding for ArrayBufferBinding<'a> {
    fn target(&self) -> BufferBindingTarget {
        ARRAY_BUFFER
    }
}

unsafe fn _draw_elements(mode: DrawingMode,
                         count: usize,
                         index_type: IndexDatumType,
                         indices: *const GLvoid)
{
    let gl_index_type: GLenum = match index_type {
        IndexDatumType::UnsignedByte => gl::UNSIGNED_BYTE,
        IndexDatumType::UnsignedShort => gl::UNSIGNED_SHORT
    };
    gl::DrawElements(mode.gl_enum(),
                     count as GLsizei,
                     gl_index_type,
                     indices);
    dbg_gl_error! {
        GLError::InvalidEnum => "`mode` or `type` is not an accepted value",
        GLError::InvalidValue => "`count` is negative",
        GLError::InvalidFramebufferOperation => "The current framebuffer is not framebuffer-complete",
        _ => "Unknown error"
    }
}

impl<'a> ArrayBufferBinding<'a> {
    pub unsafe fn vertex_attrib_pointer(&self,
                                        attrib: super::ProgramAttrib,
                                        components: i8,
                                        gl_type: super::DataType,
                                        normalized: bool,
                                        stride: usize,
                                        offset: usize)
    {
        debug_assert!(1 <= components && components <= 4);

        let gl_normalized = if normalized { gl::TRUE } else { gl::FALSE };
        gl::VertexAttribPointer(attrib.gl_index,
                                components as GLint,
                                gl_type.gl_enum(),
                                gl_normalized,
                                stride as GLsizei,
                                offset as *const GLvoid);
        dbg_gl_error! {
            GLError::InvalidEnum => "Illegal vertex attribute type",
            GLError::InvalidValue => "`stride` is negative, `size` is not in range, or `index` is >= GL_MAX_VERTEX_ATTRIBS",
            GLError::InvalidFramebufferOperation => "Currently bound framebuffer is not framebuffer complete",
            _ => "Unknown error"
        }
    }
}

impl<'a> FramebufferBinding<'a> {
    pub unsafe fn draw_arrays_range(&mut self,
                                    _ab: &ArrayBufferBinding,
                                    mode: DrawingMode,
                                    first: u32,
                                    count: usize)
    {
        gl::DrawArrays(mode.gl_enum(), first as GLint, count as GLsizei);
        dbg_gl_sanity_check! {
            GLError::InvalidEnum => "`mode` is not an accepted value",
            GLError::InvalidValue => "`count` is negative",
            _ => "Unknown error"
        }
    }

    pub unsafe fn draw_n_elements_buffered(&mut self,
                                           _ab: &ArrayBufferBinding,
                                           _eab: &ElementArrayBufferBinding,
                                           mode: DrawingMode,
                                           count: usize,
                                           index_type: IndexDatumType)
    {
        _draw_elements(mode, count, index_type, ptr::null());
    }

    pub unsafe fn draw_n_elements<I>(&mut self,
                                     _ab: &ArrayBufferBinding,
                                     mode: DrawingMode,
                                     count: usize,
                                     indices: &[I])
        where I: IndexDatum, [I]: IndexData
    {
        debug_assert!(count <= indices.len());

        let ptr = indices.index_bytes().as_ptr();
        let index_type = I::index_datum_type();
        _draw_elements(mode, count, index_type, mem::transmute(ptr));
    }

    pub unsafe fn draw_elements<I>(&mut self,
                                   _ab: &ArrayBufferBinding,
                                   mode: DrawingMode,
                                   indices: &[I])
        where I: IndexDatum, [I]: IndexData
    {
        self.draw_n_elements(_ab, mode, indices.len(), indices);
    }
}

pub struct ElementArrayBufferBinding<'a> {
    phantom: PhantomData<&'a mut Buffer>
}

impl<'a> BufferBinding for ElementArrayBufferBinding<'a> {
    fn target(&self) -> BufferBindingTarget {
        ELEMENT_ARRAY_BUFFER
    }
}

fn _bind_buffer(target: BufferBindingTarget, buffer: &mut Buffer) {
    unsafe {
        gl::BindBuffer(target as GLuint, buffer.gl_id());
        dbg_gl_sanity_check! {
            GLError::InvalidEnum => "`target` is not an allowed value",
            _ => "Unknown error"
        }
    }
}

pub struct BufferBinderOf<A, E> {
    array: A,
    element_array: E
}

pub type BufferBinder = BufferBinderOf<ArrayBufferBinder,
                                       ElementArrayBufferBinder>;

pub type BufferBinderRef<'a> = BufferBinderOf<&'a ArrayBufferBinder,
                                              &'a ElementArrayBufferBinder>;

pub type BufferBinderMut<'a> = BufferBinderOf<&'a mut ArrayBufferBinder,
                                              &'a mut ElementArrayBufferBinder>;

impl<A, E> BufferBinderOf<A, E> {
    pub unsafe fn current() -> BufferBinder {
        BufferBinderOf {
            array: ArrayBufferBinder,
            element_array: ElementArrayBufferBinder
        }
    }

    pub fn borrowed<'a, BA = A, BE = E>(&'a self)
        -> BufferBinderOf<&'a BA, &'a BE>
        where A: Borrow<BA>,
              E: Borrow<BE>
    {
        BufferBinderOf {
            array: self.array.borrow(),
            element_array: self.element_array.borrow()
        }
    }

    pub fn borrowed_mut<'a, BA = A, BE = E>(&'a mut self)
        -> BufferBinderOf<&'a mut BA, &'a mut BE>
        where A: BorrowMut<BA>,
              E: BorrowMut<BE>
    {
        BufferBinderOf {
            array: self.array.borrow_mut(),
            element_array: self.element_array.borrow_mut()
        }
    }
}

pub struct ArrayBufferBinder;
impl ArrayBufferBinder {
    pub fn bind<'a>(&mut self, buffer: &'a mut Buffer) -> ArrayBufferBinding<'a>
    {
        let binding = ArrayBufferBinding { phantom: PhantomData };
        _bind_buffer(binding.target(), buffer);
        binding
    }
}

pub struct ElementArrayBufferBinder;
impl ElementArrayBufferBinder {
    pub fn bind<'a>(&mut self, buffer: &'a mut Buffer)
        -> ElementArrayBufferBinding<'a>
    {
        let binding = ElementArrayBufferBinding { phantom: PhantomData };
        _bind_buffer(binding.target(), buffer);
        binding
    }
}
<commit_msg>Add `BufferBinder.split*()` methods<commit_after>use std::mem;
use std::ptr;
use std::marker::PhantomData;
use std::borrow::{Borrow, BorrowMut};
use gl;
use gl::types::*;
use context::ContextOf;
use framebuffer::FramebufferBinding;
use types::{DrawingMode, GLError};
use index_data::{IndexData, IndexDatum, IndexDatumType};

pub struct Buffer {
    gl_id: GLuint
}

impl Buffer {
    pub fn gl_id(&self) -> GLuint {
        self.gl_id
    }
}

impl Drop for Buffer {
    fn drop(&mut self) {
        unsafe {
            gl::DeleteBuffers(1, &self.gl_id as *const GLuint);
        }
    }
}

impl<AB, EAB, P, FB, RB, TU> ContextOf<AB, EAB, P, FB, RB, TU> {
    pub fn gen_buffer(&self) -> Buffer {
        unsafe {
            let mut id : GLuint = 0;

            gl::GenBuffers(1, &mut id as *mut GLuint);
            dbg_gl_sanity_check! {
                GLError::InvalidValue => "`n` is negative",
                _ => "Unknown error"
            }

            Buffer { gl_id: id }
        }
    }

    pub fn bind_array_buffer<'a>(self, buffer: &'a mut Buffer)
        -> (
            ArrayBufferBinding<'a>,
            ContextOf<(), EAB, P, FB, RB, TU>
        )
        where AB: BorrowMut<ArrayBufferBinder>
    {
        let (mut array_buffer, gl) = self.split_array_buffer();
        (array_buffer.borrow_mut().bind(buffer), gl)
    }

    pub fn bind_element_array_buffer<'a>(self, buffer: &'a mut Buffer)
        -> (
            ElementArrayBufferBinding<'a>,
            ContextOf<AB, (), P, FB, RB, TU>
        )
        where EAB: BorrowMut<ElementArrayBufferBinder>
    {
        let (mut element_array_buffer, gl) = self.split_element_array_buffer();
        (element_array_buffer.borrow_mut().bind(buffer), gl)
    }
}

gl_enum! {
    pub gl_enum BufferDataUsage {
        StreamDraw as STREAM_DRAW = gl::STREAM_DRAW,
        StaticDraw as STATIC_DRAW = gl::STATIC_DRAW,
        DynamicDraw as DYNAMIC_DRAW = gl::DYNAMIC_DRAW
    }
}

gl_enum! {
    pub gl_enum BufferBindingTarget {
        ArrayBuffer as ARRAY_BUFFER = gl::ARRAY_BUFFER,
        ElementArrayBuffer as ELEMENT_ARRAY_BUFFER = gl::ELEMENT_ARRAY_BUFFER
    }
}

pub trait BufferBinding {
    fn target(&self) -> BufferBindingTarget;

    fn buffer_bytes(&mut self, bytes: &[u8], usage: BufferDataUsage) {
        let ptr = bytes.as_ptr();
        let size = bytes.len() * mem::size_of::<u8>();
        unsafe {
            gl::BufferData(self.target().gl_enum(),
                           size as GLsizeiptr,
                           ptr as *const GLvoid,
                           usage.gl_enum());
            dbg_gl_error! {
                GLError::InvalidEnum => "Invalid `target` or `usage`",
                GLError::InvalidValue => "`size` is negative",
                GLError::InvalidOperation => "Object 0 is bound to buffer target",
                GLError::OutOfMemory => "Unable to create a large enough buffer",
                _ => "Unknown error"
            }
        }
    }
}

pub struct ArrayBufferBinding<'a> {
    phantom: PhantomData<&'a mut Buffer>
}

impl<'a> BufferBinding for ArrayBufferBinding<'a> {
    fn target(&self) -> BufferBindingTarget {
        ARRAY_BUFFER
    }
}

unsafe fn _draw_elements(mode: DrawingMode,
                         count: usize,
                         index_type: IndexDatumType,
                         indices: *const GLvoid)
{
    let gl_index_type: GLenum = match index_type {
        IndexDatumType::UnsignedByte => gl::UNSIGNED_BYTE,
        IndexDatumType::UnsignedShort => gl::UNSIGNED_SHORT
    };
    gl::DrawElements(mode.gl_enum(),
                     count as GLsizei,
                     gl_index_type,
                     indices);
    dbg_gl_error! {
        GLError::InvalidEnum => "`mode` or `type` is not an accepted value",
        GLError::InvalidValue => "`count` is negative",
        GLError::InvalidFramebufferOperation => "The current framebuffer is not framebuffer-complete",
        _ => "Unknown error"
    }
}

impl<'a> ArrayBufferBinding<'a> {
    pub unsafe fn vertex_attrib_pointer(&self,
                                        attrib: super::ProgramAttrib,
                                        components: i8,
                                        gl_type: super::DataType,
                                        normalized: bool,
                                        stride: usize,
                                        offset: usize)
    {
        debug_assert!(1 <= components && components <= 4);

        let gl_normalized = if normalized { gl::TRUE } else { gl::FALSE };
        gl::VertexAttribPointer(attrib.gl_index,
                                components as GLint,
                                gl_type.gl_enum(),
                                gl_normalized,
                                stride as GLsizei,
                                offset as *const GLvoid);
        dbg_gl_error! {
            GLError::InvalidEnum => "Illegal vertex attribute type",
            GLError::InvalidValue => "`stride` is negative, `size` is not in range, or `index` is >= GL_MAX_VERTEX_ATTRIBS",
            GLError::InvalidFramebufferOperation => "Currently bound framebuffer is not framebuffer complete",
            _ => "Unknown error"
        }
    }
}

impl<'a> FramebufferBinding<'a> {
    pub unsafe fn draw_arrays_range(&mut self,
                                    _ab: &ArrayBufferBinding,
                                    mode: DrawingMode,
                                    first: u32,
                                    count: usize)
    {
        gl::DrawArrays(mode.gl_enum(), first as GLint, count as GLsizei);
        dbg_gl_sanity_check! {
            GLError::InvalidEnum => "`mode` is not an accepted value",
            GLError::InvalidValue => "`count` is negative",
            _ => "Unknown error"
        }
    }

    pub unsafe fn draw_n_elements_buffered(&mut self,
                                           _ab: &ArrayBufferBinding,
                                           _eab: &ElementArrayBufferBinding,
                                           mode: DrawingMode,
                                           count: usize,
                                           index_type: IndexDatumType)
    {
        _draw_elements(mode, count, index_type, ptr::null());
    }

    pub unsafe fn draw_n_elements<I>(&mut self,
                                     _ab: &ArrayBufferBinding,
                                     mode: DrawingMode,
                                     count: usize,
                                     indices: &[I])
        where I: IndexDatum, [I]: IndexData
    {
        debug_assert!(count <= indices.len());

        let ptr = indices.index_bytes().as_ptr();
        let index_type = I::index_datum_type();
        _draw_elements(mode, count, index_type, mem::transmute(ptr));
    }

    pub unsafe fn draw_elements<I>(&mut self,
                                   _ab: &ArrayBufferBinding,
                                   mode: DrawingMode,
                                   indices: &[I])
        where I: IndexDatum, [I]: IndexData
    {
        self.draw_n_elements(_ab, mode, indices.len(), indices);
    }
}

pub struct ElementArrayBufferBinding<'a> {
    phantom: PhantomData<&'a mut Buffer>
}

impl<'a> BufferBinding for ElementArrayBufferBinding<'a> {
    fn target(&self) -> BufferBindingTarget {
        ELEMENT_ARRAY_BUFFER
    }
}

fn _bind_buffer(target: BufferBindingTarget, buffer: &mut Buffer) {
    unsafe {
        gl::BindBuffer(target as GLuint, buffer.gl_id());
        dbg_gl_sanity_check! {
            GLError::InvalidEnum => "`target` is not an allowed value",
            _ => "Unknown error"
        }
    }
}

pub struct BufferBinderOf<A, E> {
    array: A,
    element_array: E
}

pub type BufferBinder = BufferBinderOf<ArrayBufferBinder,
                                       ElementArrayBufferBinder>;

pub type BufferBinderRef<'a> = BufferBinderOf<&'a ArrayBufferBinder,
                                              &'a ElementArrayBufferBinder>;

pub type BufferBinderMut<'a> = BufferBinderOf<&'a mut ArrayBufferBinder,
                                              &'a mut ElementArrayBufferBinder>;

impl<A, E> BufferBinderOf<A, E> {
    pub unsafe fn current() -> BufferBinder {
        BufferBinderOf {
            array: ArrayBufferBinder,
            element_array: ElementArrayBufferBinder
        }
    }

    pub fn borrowed<'a, BA = A, BE = E>(&'a self)
        -> BufferBinderOf<&'a BA, &'a BE>
        where A: Borrow<BA>,
              E: Borrow<BE>
    {
        BufferBinderOf {
            array: self.array.borrow(),
            element_array: self.element_array.borrow()
        }
    }

    pub fn borrowed_mut<'a, BA = A, BE = E>(&'a mut self)
        -> BufferBinderOf<&'a mut BA, &'a mut BE>
        where A: BorrowMut<BA>,
              E: BorrowMut<BE>
    {
        BufferBinderOf {
            array: self.array.borrow_mut(),
            element_array: self.element_array.borrow_mut()
        }
    }

    pub fn split_array(self) -> (A, BufferBinderOf<(), E>) {
        (
            self.array,
            BufferBinderOf {
                array: (),
                element_array: self.element_array
            }
        )
    }

    pub fn split_element_array(self) -> (E, BufferBinderOf<A, ()>) {
        (
            self.element_array,
            BufferBinderOf {
                array: self.array,
                element_array: ()
            }
        )
    }
}

pub struct ArrayBufferBinder;
impl ArrayBufferBinder {
    pub fn bind<'a>(&mut self, buffer: &'a mut Buffer) -> ArrayBufferBinding<'a>
    {
        let binding = ArrayBufferBinding { phantom: PhantomData };
        _bind_buffer(binding.target(), buffer);
        binding
    }
}

pub struct ElementArrayBufferBinder;
impl ElementArrayBufferBinder {
    pub fn bind<'a>(&mut self, buffer: &'a mut Buffer)
        -> ElementArrayBufferBinding<'a>
    {
        let binding = ElementArrayBufferBinding { phantom: PhantomData };
        _bind_buffer(binding.target(), buffer);
        binding
    }
}
<|endoftext|>


<commit_before>//! A Web client to Fetcher Web UI
//!
//!

use url::{Url, ParseError};

use hyper;
use hyper::client::response::Response;
use hyper::header::ContentType;
use hyper::mime::Mime;
use hyper::header::Connection;
use hyper::header::ConnectionOption;
use hyper::error::HttpResult;

///
///
///
pub struct Client {
    url: Url,
    client: hyper::Client,
}

///
///
///
impl Client {

    ///
    /// #Example
    ///
    /// ```
    /// use fetcher::client::Client;
    ///
    /// let client = Client::new("http://127.0.0.1/").unwrap();
    /// ```
    pub fn new(url: &str) -> Result<Client, ParseError> {
        let url_parsed = match Url::parse(url) {
            Ok(u) => u,
            Err(e) => return Err(e),
        };

        Ok(Client {
            url: url_parsed,
            client: hyper::Client::new(),
        })
    }

    ///
    ///
    ///
    fn build_url(&self, to_add: &str) -> Result<Url, ParseError> {
        let base_url = &self.url.to_string();
        let new_url: String;
        if base_url.ends_with('/') ^ to_add.starts_with('/') {
            // One trailing or starting '/'
            new_url = format!("{}{}", base_url, to_add).to_string();
        } else if base_url.ends_with('/') && to_add.starts_with('/') {
            // Pair of trailing and starting '/'
            new_url = format!("{}{}", base_url.trim_right_matches('/'), to_add).to_string();
        } else {
            // No trailing or starting '/'
            new_url = format!("{}/{}", base_url, to_add).to_string();
        }

        Url::parse(&new_url)
    }

    /// Raw access to using the HTTP `GET` method at a given `path`
    /// from the base url given at construction.
    ///
    /// #Example
    ///
    /// ```
    /// use fetcher::client::Client;
    ///
    /// let mut client = Client::new("http://127.0.0.1/").unwrap();
    ///
    /// client.get("/api/");
    /// ```
    pub fn get(&mut self, path: &str) -> HttpResult<Response> {
        // FIXME: build_url cannot be asserted to be valid
        let full_url = self.build_url(path).unwrap();
        self.client.get(full_url)
            .header(Connection(vec![ConnectionOption::Close]))
            .header(ContentType("application/json".parse::<Mime>().unwrap()))
            .send()
    }

    ///
    ///
    ///
    pub fn get_series(&mut self) -> Result<(), ()> {
        unimplemented!();
    }

    ///
    ///
    ///
    pub fn get_providers(&mut self) -> Result<(), ()> {
        unimplemented!();
    }

    ///
    ///
    ///
    pub fn get_base_providers(&mut self) -> Result<(), ()> {
        unimplemented!();
    }

}

#[cfg(test)]
mod test{
    extern crate url;
    use url::Url;
    use super::*;

    #[test]
    fn test_url_build() {
        for (url, path, built) in vec![
                ("http://e.co", "/p/t/f.html", "http://e.co/p/t/f.html"),
                ("http://e.co/", "/p/t/f.html", "http://e.co/p/t/f.html"),
                ("http://e.co", "p/t/f.html", "http://e.co/p/t/f.html"),
                ("http://e.co", "p/t/f.html?var=1", "http://e.co/p/t/f.html?var=1"),
            ] {
            let c = Client::new(url).unwrap();
            let built_url = c.build_url(path);
            assert!(Url::parse(built).unwrap() == built_url.unwrap(),
                    "assertion failed: url:{} path:{} built:{} != expected:{}", url, path, built, Url::parse(built).unwrap());
        }
    }
}
<commit_msg>added test for build_url<commit_after>//! A Web client to Fetcher Web UI
//!
//!

use url::{Url, ParseError};

use hyper;
use hyper::client::response::Response;
use hyper::header::ContentType;
use hyper::mime::Mime;
use hyper::header::Connection;
use hyper::header::ConnectionOption;
use hyper::error::HttpResult;

///
///
///
pub struct Client {
    url: Url,
    client: hyper::Client,
}

///
///
///
impl Client {

    ///
    /// #Example
    ///
    /// ```
    /// use fetcher::client::Client;
    ///
    /// let client = Client::new("http://127.0.0.1/").unwrap();
    /// ```
    pub fn new(url: &str) -> Result<Client, ParseError> {
        let url_parsed = match Url::parse(url) {
            Ok(u) => u,
            Err(e) => return Err(e),
        };

        Ok(Client {
            url: url_parsed,
            client: hyper::Client::new(),
        })
    }

    ///
    ///
    ///
    fn build_url(&self, to_add: &str) -> Result<Url, ParseError> {
        let base_url = &self.url.to_string();
        let new_url: String;
        if base_url.ends_with('/') ^ to_add.starts_with('/') {
            // One trailing or starting '/'
            new_url = format!("{}{}", base_url, to_add).to_string();
        } else if base_url.ends_with('/') && to_add.starts_with('/') {
            // Pair of trailing and starting '/'
            new_url = format!("{}{}", base_url.trim_right_matches('/'), to_add).to_string();
        } else {
            // No trailing or starting '/'
            new_url = format!("{}/{}", base_url, to_add).to_string();
        }

        Url::parse(&new_url)
    }

    /// Raw access to using the HTTP `GET` method at a given `path`
    /// from the base url given at construction.
    ///
    /// #Example
    ///
    /// ```
    /// use fetcher::client::Client;
    ///
    /// let mut client = Client::new("http://127.0.0.1/").unwrap();
    ///
    /// client.get("/api/");
    /// ```
    pub fn get(&mut self, path: &str) -> HttpResult<Response> {
        // FIXME: build_url cannot be asserted to be valid
        let full_url = self.build_url(path).unwrap();
        self.client.get(full_url)
            .header(Connection(vec![ConnectionOption::Close]))
            .header(ContentType("application/json".parse::<Mime>().unwrap()))
            .send()
    }

    ///
    ///
    ///
    pub fn get_series(&mut self) -> Result<(), ()> {
        unimplemented!();
    }

    ///
    ///
    ///
    pub fn get_providers(&mut self) -> Result<(), ()> {
        unimplemented!();
    }

    ///
    ///
    ///
    pub fn get_base_providers(&mut self) -> Result<(), ()> {
        unimplemented!();
    }

}

#[cfg(test)]
mod test{
    extern crate url;
    use url::Url;
    use super::*;

    #[test]
    fn test_url_build() {
        for (url, path, built) in vec![
                ("http://e.co", "/p/t/f.html", "http://e.co/p/t/f.html"),
                ("http://e.co/", "/p/t/f.html", "http://e.co/p/t/f.html"),
                ("http://e.co", "p/t/f.html", "http://e.co/p/t/f.html"),
                ("http://e.co:8080", "p/t/f.html", "http://e.co:8080/p/t/f.html"),
                ("http://e.co", "p/t/f.html?var=1", "http://e.co/p/t/f.html?var=1"),
            ] {
            let c = Client::new(url).unwrap();
            let built_url = c.build_url(path);
            assert!(Url::parse(built).unwrap() == built_url.unwrap(),
                    "assertion failed: url:{} path:{} built:{} != expected:{}", url, path, built, Url::parse(built).unwrap());
        }
    }
}
<|endoftext|>


<commit_before>use std::ascii::AsciiExt;
use std::sync;
use std::collections;

use chrono;
use fern;
use fern_macros;

use errors::InitializationError;
use plugins;
use interface;
use config;
use irc;

pub struct PluginRegister {
    commands: collections::HashMap<String, Vec<Box<Fn(&interface::CommandEvent) + Send + Sync>>>,
    ctcp_listeners: collections::HashMap<String, Vec<Box<Fn(&interface::CtcpEvent) + Send + Sync>>>,
    raw_listeners: collections::HashMap<String, Vec<Box<Fn(&interface::IrcMessageEvent) + Send + Sync>>>,
    catch_all: Vec<Box<Fn(&interface::IrcMessageEvent) + Send + Sync>>,
}

impl PluginRegister {
    pub fn new() -> PluginRegister {
        return PluginRegister {
            commands: collections::HashMap::new(),
            raw_listeners: collections::HashMap::new(),
            ctcp_listeners: collections::HashMap::new(),
            catch_all: Vec::new(),
        }
    }

    pub fn register_irc<T: Fn(&interface::IrcMessageEvent) + Send + Sync>(&mut self, irc_command: &str, f: T) {
        let boxed = box f as Box<Fn(&interface::IrcMessageEvent) + Send + Sync>;
        let command_string = irc_command.into_string().to_ascii_lower();

        // I can't use a match here because then I would be borrowing listener_map mutably twice:
        // Once for the match statement, and a second time inside the None branch
        if self.raw_listeners.contains_key(&command_string) {
            self.raw_listeners.get_mut(&command_string).expect("Honestly, this won't happen.").push(boxed);
        } else {
            self.raw_listeners.insert(command_string, vec!(boxed));
        }
    }

    pub fn register_ctcp<T: Fn(&interface::CtcpEvent) + Send + Sync>(&mut self, ctcp_command: &str, f: T) {
        let boxed = box f as Box<Fn(&interface::CtcpEvent) + Send + Sync>;
        let command_string = ctcp_command.into_string().to_ascii_lower();

        // I can't use a match here because then I would be borrowing listener_map mutably twice:
        // Once for the match statement, and a second time inside the None branch
        if self.ctcp_listeners.contains_key(&command_string) {
            self.ctcp_listeners.get_mut(&command_string).expect("Honestly, this won't happen.").push(boxed);
        } else {
            self.ctcp_listeners.insert(command_string, vec!(boxed));
        }
    }

    pub fn register_catch_all<T: Fn(&interface::IrcMessageEvent) + Send + Sync>(&mut self, f: T) {
        self.catch_all.push(box f as Box<Fn(&interface::IrcMessageEvent) + Send + Sync>);
    }

    pub fn register_command<T: Fn(&interface::CommandEvent) + Send + Sync>(&mut self, command: &str, f: T) {
        let boxed = box f as Box<Fn(&interface::CommandEvent) + Send + Sync>;
        let command_lower = command.into_string().to_ascii_lower();

        // I can't use a match here because then I would be borrowing the command_map mutably twice:
        // Once for the match statement, and a second time inside the None branch
        if self.commands.contains_key(&command_lower) {
            self.commands.get_mut(&command_lower).expect("Honestly, this won't happen.").push(boxed);
        } else {
            self.commands.insert(command_lower, vec!(boxed));
        }
    }
}

pub struct ClientState {
    pub nick: String,
    pub channels: Vec<String>,
}

impl ClientState {
    pub fn new(nick: String) -> ClientState {
        return ClientState {
            nick: nick,
            channels: Vec::new(),
        };
    }
}

pub struct Client {
    pub plugins: sync::RWLock<PluginRegister>,
    pub config: config::ClientConfiguration,
    pub state: sync::RWLock<ClientState>,
}

impl Client {
    pub fn new(plugins: PluginRegister, config: config::ClientConfiguration) -> Client {
        let state = sync::RWLock::new(ClientState::new(config.nick.clone()));
        return Client {
            plugins: sync::RWLock::new(plugins),
            config: config,
            state: state,
        }
    }
}

/// This allows access to configuration fields directly on Client
impl Deref<config::ClientConfiguration> for Client {
    fn deref<'a>(&'a self) -> &'a config::ClientConfiguration {
        return &self.config;
    }
}

pub struct Dispatch {
    interface: interface::IrcInterface,
    state: sync::Arc<Client>,
    data_in: Receiver<Option<irc::IrcMessage>>,
}

impl Dispatch {
    fn new(interface: interface::IrcInterface, state: sync::Arc<Client>, data_in: Receiver<Option<irc::IrcMessage>>) -> Dispatch {
        return Dispatch {
            interface: interface,
            state: state,
            data_in: data_in,
        };
    }

    fn start_dispatch_loop(self) {
        loop {
            let message = match self.data_in.recv() {
                Some(v) => v,
                None => break,
            };
            self.process_message(&message);
        }
    }

    // Noting: This has to be a separate method from spawn_dispatch_thread, so that we can name an 'a lifetime.
    // This allows us to give the new &str slices a specific lifetime, which I don't know a way to do without making a new function.
    fn process_message<'a>(&self, message: &'a irc::IrcMessage) {
        let plugins = self.state.plugins.read();

        let shared_mask = &interface::IrcMask::from_internal(&message.mask);
        let shared_args = message.args.iter().map(|s| s.as_slice()).collect::<Vec<&'a str>>();
        let shared_ctcp = message.ctcp.as_ref().map(|t| (t.0.as_slice(), t.1.as_slice()));
        let shared_channel = message.channel.as_ref().map(|s| s.as_slice());

        // PING
        if message.command.as_slice().eq_ignore_ascii_case("PING") {
            self.interface.send_command("PONG".into_string(), shared_args.as_slice());
        }

        let message_event = &mut interface::IrcMessageEvent::new(&self.interface, message.command.as_slice(), shared_args.as_slice(), shared_mask, shared_ctcp, shared_channel);

        // Catch all listeners
        for listener in plugins.catch_all.iter() {
            listener.call((message_event,));
        }

        // Raw listeners
        match plugins.raw_listeners.get(&message.command.to_ascii_lower()) {
            Some(list) => {
                for listener in list.iter() {
                    listener.call((message_event,));
                }
            },
            None => (),
        }

        if message.command.as_slice().eq_ignore_ascii_case("PRIVMSG") {
            let channel = shared_channel.unwrap(); // Always exists for PRIVMSG

            // CTCP
            match message.ctcp {
                Some(ref t) => {
                    let ctcp_event = interface::CtcpEvent::new(&self.interface, message.args[0].as_slice(), t.0.as_slice(), t.1.as_slice(), shared_mask);

                    match plugins.ctcp_listeners.get(&ctcp_event.command.to_ascii_lower()) {
                        Some(list) => {
                            for ctcp_listener in list.iter() {
                                ctcp_listener.call((&ctcp_event,));
                            }
                        },
                        None => (),
                    }
                },
                None => (),
            }

            // Commands
            let command_prefix = format!(":{}", self.state.command_prefix.as_slice());

            // This checks for the command prefix, commands typed like '.command_name args'
            if shared_args[1].starts_with(command_prefix.as_slice()) {
                let command = shared_args[1].slice_from(command_prefix.len());
                let args = shared_args.slice_from(2);
                self.dispatch_command(&plugins, command, channel, args, shared_mask);
            } else {
                // This checks for someone typing commands like 'BotName, command_name args'
                // We store whether or not a command was matched in a variable so that we can use it below.
                // We can't just put the below in one of the None => branches, because there are multiple instances of them.
                let command_matched = match regex!(r"^:([^\s]+?)[:;,]?\s+(.+)$").captures(shared_args.slice_from(1).connect(" ").as_slice()) {
                    Some(captures) => {
                        if captures.at(1) == Some(self.state.state.read().nick.as_slice()) {
                            match captures.at(2) {
                                Some(args_str) => {
                                    let split = args_str.split(' ').collect::<Vec<&str>>();
                                    let command = split[0];
                                    let args = split.slice_from(1);
                                    self.dispatch_command(&plugins, command, channel, args, shared_mask);
                                    true
                                },
                                None => false,
                            }
                        } else {
                            false
                        }
                    },
                    None => false,
                };

                // This checks for commands in a private message, where a prefix isn't required
                // People can just say 'command args' in a private message.
                // If the channel is the sender's nick, the message is being sent in a private message.
                if !command_matched && shared_mask.nick() == Some(channel) {
                    let command = shared_args[1].slice_from(1); // slice_from(1) to remove the `:` at the beginning of privmsg content.
                    let args = shared_args.slice_from(2);
                    self.dispatch_command(&plugins, command, channel, args, shared_mask);
                }
            }
        }
    }

    fn dispatch_command(&self, plugins: &sync::RWLockReadGuard<PluginRegister>, command: &str, channel: &str, args: &[&str], mask: &interface::IrcMask) {
        match plugins.commands.get(&command.to_ascii_lower()) {
            Some(list) => {
                let command_event = &mut interface::CommandEvent::new(&self.interface, channel, args, mask);
                for closure in list.iter() {
                    closure.call((command_event,));
                }
            },
            None => (),
        }
    }
}

pub fn run(config: config::ClientConfiguration) -> Result<(), InitializationError> {
    run_with_plugins(config, PluginRegister::new())
}

pub fn run_with_plugins(config: config::ClientConfiguration, mut plugins: PluginRegister) -> Result<(), InitializationError> {
        // Register built-in plugins
        plugins::register_plugins(&mut plugins);

        let logger = sync::Arc::new(try!(fern::LoggerConfig {
            format: box |msg: &str, level: &fern::Level| {
                return format!("[{}][{}] {}", chrono::Local::now().format("%Y-%m-%d][%H:%M:%S"), level, msg);
            },
            output: vec![fern::OutputConfig::Stdout, fern::OutputConfig::File(Path::new("zaldinar.log"))],
            level: fern::Level::Debug,
        }.into_logger()));

        fern_macros::init_thread_logger(logger.clone());

        let client = sync::Arc::new(Client::new(plugins, config));

        let (data_out, connection_data_in) = channel();
        let (connection_data_out, data_in) = channel();

        let interface = try!(interface::IrcInterface::new(data_out, client.clone()));

        // Send NICK and USER, the initial IRC commands. Because an IrcConnection hasn't been created to receive these yet,
        //  they will just go on hold and get sent as soon as the IrcConnection connects.
        interface.send_command("NICK".into_string(), &[client.nick.as_slice()]);
        interface.send_command("USER".into_string(), &[client.user.as_slice(), "0", "*", format!(":{}", client.real_name).as_slice()]);

        try!(irc::IrcConnection::create(client.address.as_slice(), connection_data_out, connection_data_in, logger.clone(), client.clone()));

        let dispatch = Dispatch::new(interface, client, data_in);

        // This statement will run until the bot exists
        dispatch.start_dispatch_loop();

        return Ok(());
}
<commit_msg>I'm not entirely sure why that was double-indented<commit_after>use std::ascii::AsciiExt;
use std::sync;
use std::collections;

use chrono;
use fern;
use fern_macros;

use errors::InitializationError;
use plugins;
use interface;
use config;
use irc;

pub struct PluginRegister {
    commands: collections::HashMap<String, Vec<Box<Fn(&interface::CommandEvent) + Send + Sync>>>,
    ctcp_listeners: collections::HashMap<String, Vec<Box<Fn(&interface::CtcpEvent) + Send + Sync>>>,
    raw_listeners: collections::HashMap<String, Vec<Box<Fn(&interface::IrcMessageEvent) + Send + Sync>>>,
    catch_all: Vec<Box<Fn(&interface::IrcMessageEvent) + Send + Sync>>,
}

impl PluginRegister {
    pub fn new() -> PluginRegister {
        return PluginRegister {
            commands: collections::HashMap::new(),
            raw_listeners: collections::HashMap::new(),
            ctcp_listeners: collections::HashMap::new(),
            catch_all: Vec::new(),
        }
    }

    pub fn register_irc<T: Fn(&interface::IrcMessageEvent) + Send + Sync>(&mut self, irc_command: &str, f: T) {
        let boxed = box f as Box<Fn(&interface::IrcMessageEvent) + Send + Sync>;
        let command_string = irc_command.into_string().to_ascii_lower();

        // I can't use a match here because then I would be borrowing listener_map mutably twice:
        // Once for the match statement, and a second time inside the None branch
        if self.raw_listeners.contains_key(&command_string) {
            self.raw_listeners.get_mut(&command_string).expect("Honestly, this won't happen.").push(boxed);
        } else {
            self.raw_listeners.insert(command_string, vec!(boxed));
        }
    }

    pub fn register_ctcp<T: Fn(&interface::CtcpEvent) + Send + Sync>(&mut self, ctcp_command: &str, f: T) {
        let boxed = box f as Box<Fn(&interface::CtcpEvent) + Send + Sync>;
        let command_string = ctcp_command.into_string().to_ascii_lower();

        // I can't use a match here because then I would be borrowing listener_map mutably twice:
        // Once for the match statement, and a second time inside the None branch
        if self.ctcp_listeners.contains_key(&command_string) {
            self.ctcp_listeners.get_mut(&command_string).expect("Honestly, this won't happen.").push(boxed);
        } else {
            self.ctcp_listeners.insert(command_string, vec!(boxed));
        }
    }

    pub fn register_catch_all<T: Fn(&interface::IrcMessageEvent) + Send + Sync>(&mut self, f: T) {
        self.catch_all.push(box f as Box<Fn(&interface::IrcMessageEvent) + Send + Sync>);
    }

    pub fn register_command<T: Fn(&interface::CommandEvent) + Send + Sync>(&mut self, command: &str, f: T) {
        let boxed = box f as Box<Fn(&interface::CommandEvent) + Send + Sync>;
        let command_lower = command.into_string().to_ascii_lower();

        // I can't use a match here because then I would be borrowing the command_map mutably twice:
        // Once for the match statement, and a second time inside the None branch
        if self.commands.contains_key(&command_lower) {
            self.commands.get_mut(&command_lower).expect("Honestly, this won't happen.").push(boxed);
        } else {
            self.commands.insert(command_lower, vec!(boxed));
        }
    }
}

pub struct ClientState {
    pub nick: String,
    pub channels: Vec<String>,
}

impl ClientState {
    pub fn new(nick: String) -> ClientState {
        return ClientState {
            nick: nick,
            channels: Vec::new(),
        };
    }
}

pub struct Client {
    pub plugins: sync::RWLock<PluginRegister>,
    pub config: config::ClientConfiguration,
    pub state: sync::RWLock<ClientState>,
}

impl Client {
    pub fn new(plugins: PluginRegister, config: config::ClientConfiguration) -> Client {
        let state = sync::RWLock::new(ClientState::new(config.nick.clone()));
        return Client {
            plugins: sync::RWLock::new(plugins),
            config: config,
            state: state,
        }
    }
}

/// This allows access to configuration fields directly on Client
impl Deref<config::ClientConfiguration> for Client {
    fn deref<'a>(&'a self) -> &'a config::ClientConfiguration {
        return &self.config;
    }
}

pub struct Dispatch {
    interface: interface::IrcInterface,
    state: sync::Arc<Client>,
    data_in: Receiver<Option<irc::IrcMessage>>,
}

impl Dispatch {
    fn new(interface: interface::IrcInterface, state: sync::Arc<Client>, data_in: Receiver<Option<irc::IrcMessage>>) -> Dispatch {
        return Dispatch {
            interface: interface,
            state: state,
            data_in: data_in,
        };
    }

    fn start_dispatch_loop(self) {
        loop {
            let message = match self.data_in.recv() {
                Some(v) => v,
                None => break,
            };
            self.process_message(&message);
        }
    }

    // Noting: This has to be a separate method from spawn_dispatch_thread, so that we can name an 'a lifetime.
    // This allows us to give the new &str slices a specific lifetime, which I don't know a way to do without making a new function.
    fn process_message<'a>(&self, message: &'a irc::IrcMessage) {
        let plugins = self.state.plugins.read();

        let shared_mask = &interface::IrcMask::from_internal(&message.mask);
        let shared_args = message.args.iter().map(|s| s.as_slice()).collect::<Vec<&'a str>>();
        let shared_ctcp = message.ctcp.as_ref().map(|t| (t.0.as_slice(), t.1.as_slice()));
        let shared_channel = message.channel.as_ref().map(|s| s.as_slice());

        // PING
        if message.command.as_slice().eq_ignore_ascii_case("PING") {
            self.interface.send_command("PONG".into_string(), shared_args.as_slice());
        }

        let message_event = &mut interface::IrcMessageEvent::new(&self.interface, message.command.as_slice(), shared_args.as_slice(), shared_mask, shared_ctcp, shared_channel);

        // Catch all listeners
        for listener in plugins.catch_all.iter() {
            listener.call((message_event,));
        }

        // Raw listeners
        match plugins.raw_listeners.get(&message.command.to_ascii_lower()) {
            Some(list) => {
                for listener in list.iter() {
                    listener.call((message_event,));
                }
            },
            None => (),
        }

        if message.command.as_slice().eq_ignore_ascii_case("PRIVMSG") {
            let channel = shared_channel.unwrap(); // Always exists for PRIVMSG

            // CTCP
            match message.ctcp {
                Some(ref t) => {
                    let ctcp_event = interface::CtcpEvent::new(&self.interface, message.args[0].as_slice(), t.0.as_slice(), t.1.as_slice(), shared_mask);

                    match plugins.ctcp_listeners.get(&ctcp_event.command.to_ascii_lower()) {
                        Some(list) => {
                            for ctcp_listener in list.iter() {
                                ctcp_listener.call((&ctcp_event,));
                            }
                        },
                        None => (),
                    }
                },
                None => (),
            }

            // Commands
            let command_prefix = format!(":{}", self.state.command_prefix.as_slice());

            // This checks for the command prefix, commands typed like '.command_name args'
            if shared_args[1].starts_with(command_prefix.as_slice()) {
                let command = shared_args[1].slice_from(command_prefix.len());
                let args = shared_args.slice_from(2);
                self.dispatch_command(&plugins, command, channel, args, shared_mask);
            } else {
                // This checks for someone typing commands like 'BotName, command_name args'
                // We store whether or not a command was matched in a variable so that we can use it below.
                // We can't just put the below in one of the None => branches, because there are multiple instances of them.
                let command_matched = match regex!(r"^:([^\s]+?)[:;,]?\s+(.+)$").captures(shared_args.slice_from(1).connect(" ").as_slice()) {
                    Some(captures) => {
                        if captures.at(1) == Some(self.state.state.read().nick.as_slice()) {
                            match captures.at(2) {
                                Some(args_str) => {
                                    let split = args_str.split(' ').collect::<Vec<&str>>();
                                    let command = split[0];
                                    let args = split.slice_from(1);
                                    self.dispatch_command(&plugins, command, channel, args, shared_mask);
                                    true
                                },
                                None => false,
                            }
                        } else {
                            false
                        }
                    },
                    None => false,
                };

                // This checks for commands in a private message, where a prefix isn't required
                // People can just say 'command args' in a private message.
                // If the channel is the sender's nick, the message is being sent in a private message.
                if !command_matched && shared_mask.nick() == Some(channel) {
                    let command = shared_args[1].slice_from(1); // slice_from(1) to remove the `:` at the beginning of privmsg content.
                    let args = shared_args.slice_from(2);
                    self.dispatch_command(&plugins, command, channel, args, shared_mask);
                }
            }
        }
    }

    fn dispatch_command(&self, plugins: &sync::RWLockReadGuard<PluginRegister>, command: &str, channel: &str, args: &[&str], mask: &interface::IrcMask) {
        match plugins.commands.get(&command.to_ascii_lower()) {
            Some(list) => {
                let command_event = &mut interface::CommandEvent::new(&self.interface, channel, args, mask);
                for closure in list.iter() {
                    closure.call((command_event,));
                }
            },
            None => (),
        }
    }
}

pub fn run(config: config::ClientConfiguration) -> Result<(), InitializationError> {
    run_with_plugins(config, PluginRegister::new())
}

pub fn run_with_plugins(config: config::ClientConfiguration, mut plugins: PluginRegister) -> Result<(), InitializationError> {
    // Register built-in plugins
    plugins::register_plugins(&mut plugins);

    let logger = sync::Arc::new(try!(fern::LoggerConfig {
        format: box |msg: &str, level: &fern::Level| {
            return format!("[{}][{}] {}", chrono::Local::now().format("%Y-%m-%d][%H:%M:%S"), level, msg);
        },
        output: vec![fern::OutputConfig::Stdout, fern::OutputConfig::File(Path::new("zaldinar.log"))],
        level: fern::Level::Debug,
    }.into_logger()));

    fern_macros::init_thread_logger(logger.clone());

    let client = sync::Arc::new(Client::new(plugins, config));

    let (data_out, connection_data_in) = channel();
    let (connection_data_out, data_in) = channel();

    let interface = try!(interface::IrcInterface::new(data_out, client.clone()));

    // Send NICK and USER, the initial IRC commands. Because an IrcConnection hasn't been created to receive these yet,
    //  they will just go on hold and get sent as soon as the IrcConnection connects.
    interface.send_command("NICK".into_string(), &[client.nick.as_slice()]);
    interface.send_command("USER".into_string(), &[client.user.as_slice(), "0", "*", format!(":{}", client.real_name).as_slice()]);

    try!(irc::IrcConnection::create(client.address.as_slice(), connection_data_out, connection_data_in, logger.clone(), client.clone()));

    let dispatch = Dispatch::new(interface, client, data_in);

    // This statement will run until the bot exists
    dispatch.start_dispatch_loop();

    return Ok(());
}
<|endoftext|>


<commit_before>use monsters::MonsterId;
use map::RoomContents;
use game_state::{GameState, GameMode};
use command::CommandInfo;
use items::Item::*;
use direction::Direction;
use sized_enum::SizedEnum;
use platform;

use self::FleeCommand::*;
use self::CombatPhase::*;

const CHANCE_TO_RUN: f32 = 0.3;

#[derive(Debug, PartialEq, Copy, Clone)]
pub enum CombatPhase {
  Preparation,
  Battle,
  Aftermath,
}

#[derive(Debug, PartialEq, Copy, Clone)]
pub struct CombatState {
  phase: CombatPhase,
  enemy: MonsterId,
  ff: i32,
}

#[derive(Debug, Copy, Clone)]
pub enum FleeCommand {
  Flee(Direction),
}

command_processor!(FleeCommand, {
  Direction::iter().map(|dir| {
    CommandInfo::new(dir.character(), format!("flee {}", dir), Flee(dir))
  }).collect()
});

impl GameState {
  pub fn maybe_start_combat(&mut self) -> bool {
    if let Some(RoomContents::Terror(monster_id)) =
        self.map.room(self.curr_room).contents {
      wrapln!("\nDanger... There is a monster here....");
      Self::pause();
      wrapln!("\nIt is a {}!", monster_id);
      let ff = self.get_modified_ff(monster_id.ferocity_factor());
      wrapln!("\nThe danger level is {}!!\n", ff);
      Self::pause();
      self.set_mode(GameMode::Combat(CombatState {
        phase: Preparation,
        enemy: monster_id,
        ff: ff,
      }));
      return true;
    }
    false
  }

  fn get_modified_ff(&self, base_ff: i32) -> i32 {
    let mut ff = base_ff;
    let axe = self.items.owns(Axe);
    let sword = self.items.owns(Sword);

    if self.items.owns(Armor) { ff = 3 * (ff / 4); }
    if !axe && !sword {
      ff + (ff / 5)
    } else if axe && !sword {
      4 * (ff / 5)
    } else if sword && !axe {
      3 * (ff / 4)
    } else {
      3 * (ff / 5)
    }
  }

  fn prepare(&self) {
    if self.items.owns(Armor) {
      wrapln!("Your armor increases your chance of success.");
    }

    let axe = self.items.owns(Axe);
    let sword = self.items.owns(Sword);

    if !axe && !sword {
      wrapln!("You have no weapons.");
      wrapln!("You must fight with bare hands.");
    } else if axe && !sword {
      wrapln!("You have only an axe to fight with.");
    } else if sword && !axe {
      wrapln!("You must fight with your sword.");
    } else {
      wrapln!("You are dual-wielding a sword and axe like a boss.");
    }
    Self::pause();
  }

  fn press_enter_to_fight(&mut self, state: CombatState) {
    self.ask("Press enter to fight! ", move |game_state, _| {
      game_state.prepare();
      wrapln!("\n");
      game_state.curr_mode = GameMode::Combat(CombatState {
        phase: Battle,
        .. state
      });
    });
  }

  fn ask_direction_to_run(&mut self) {
    self.ask("What direction will you run? ", move |game_state, input| {
      if let Some(Flee(dir)) = FleeCommand::get_from_input(input) {
        if game_state.try_to_move(dir) {
          return;
        }
      }
      game_state.ask_again();
    });
  }

  fn tick_preparation_phase(&mut self, state: &CombatState) {
    let state = *state;
    self.ask("Will you run away like a coward? ", move |game_state, input| {
      if input.to_lowercase().starts_with('y') {
        if platform::random() <= CHANCE_TO_RUN {
          game_state.set_mode(GameMode::Primary);
          game_state.ask_direction_to_run();
          return;
        }
        wrapln!("Your craven attempt to escape has failed.");
      }
      game_state.press_enter_to_fight(state);
    });
  }

  fn tick_battle_phase(&mut self, state: &CombatState) {
    let mut ff = state.ff;
    let mut phase = state.phase;

    if platform::random() > 0.5 {
      wrapln!("The {} attacks!", state.enemy);
    } else {
      wrapln!("You attack!");
    }
    Self::pause();
    if platform::random() > 0.5 {
      wrapln!("\nYou manage to wound it!");
      ff = 5 * ff / 6;
      Self::pause();
    }
    if platform::random() > 0.5 {
      self.strength -= 5;
      if self.strength > 0 {
        wrapln!("\nThe monster wounds you!");
      } else {
        wrapln!("\nThe monster lands a killing blow!");
      }
      Self::pause();
    }
    if platform::random() <= 0.35 {
      phase = Aftermath;
    }
    self.curr_mode = GameMode::Combat(CombatState {
      phase: phase,
      ff: ff,
      .. *state
    });
  }

  fn tick_aftermath_phase(&mut self, state: &CombatState) {
    if platform::random_i32(0, 16) > state.ff {
      wrapln!("\nWounded and ashamed, the {} scurries off.",
              state.enemy);
      wrapln!("\nYou are victorious!");
      self.monsters_killed += 1;
    } else {
      wrapln!("\nThe {} knocks you down!", state.enemy);
      wrapln!("\nYou are at its mercy!");
      Self::pause();
      wrapln!("\nIt appears to be in a generous mood and wanders off.");
      self.strength /= 2;
    }
    wrapln!("\n");
    Self::pause();
    self.map.mut_room(self.curr_room).contents = None;
    self.set_mode(GameMode::Primary);
  }

  pub fn tick_combat_mode(&mut self, state: &CombatState) {
    match state.phase {
      Preparation => { self.tick_preparation_phase(state) },
      Battle => { self.tick_battle_phase(state) },
      Aftermath => { self.tick_aftermath_phase(state) },
    }
  }
}
<commit_msg>Better flee prompting.<commit_after>use monsters::MonsterId;
use map::RoomContents;
use game_state::{GameState, GameMode};
use command::CommandInfo;
use items::Item::*;
use direction::Direction;
use sized_enum::SizedEnum;
use platform;

use self::FleeCommand::*;
use self::CombatPhase::*;

const CHANCE_TO_RUN: f32 = 0.3;

#[derive(Debug, PartialEq, Copy, Clone)]
pub enum CombatPhase {
  Preparation,
  Battle,
  Aftermath,
}

#[derive(Debug, PartialEq, Copy, Clone)]
pub struct CombatState {
  phase: CombatPhase,
  enemy: MonsterId,
  ff: i32,
}

#[derive(Debug, Copy, Clone)]
pub enum FleeCommand {
  Flee(Direction),
}

command_processor!(FleeCommand, {
  Direction::iter().map(|dir| {
    CommandInfo::new(dir.character(), format!("flee {}", dir), Flee(dir))
  }).collect()
});

impl GameState {
  pub fn maybe_start_combat(&mut self) -> bool {
    if let Some(RoomContents::Terror(monster_id)) =
        self.map.room(self.curr_room).contents {
      wrapln!("\nDanger... There is a monster here....");
      Self::pause();
      wrapln!("\nIt is a {}!", monster_id);
      let ff = self.get_modified_ff(monster_id.ferocity_factor());
      wrapln!("\nThe danger level is {}!!\n", ff);
      Self::pause();
      self.set_mode(GameMode::Combat(CombatState {
        phase: Preparation,
        enemy: monster_id,
        ff: ff,
      }));
      return true;
    }
    false
  }

  fn get_modified_ff(&self, base_ff: i32) -> i32 {
    let mut ff = base_ff;
    let axe = self.items.owns(Axe);
    let sword = self.items.owns(Sword);

    if self.items.owns(Armor) { ff = 3 * (ff / 4); }
    if !axe && !sword {
      ff + (ff / 5)
    } else if axe && !sword {
      4 * (ff / 5)
    } else if sword && !axe {
      3 * (ff / 4)
    } else {
      3 * (ff / 5)
    }
  }

  fn prepare(&self) {
    if self.items.owns(Armor) {
      wrapln!("Your armor increases your chance of success.");
    }

    let axe = self.items.owns(Axe);
    let sword = self.items.owns(Sword);

    if !axe && !sword {
      wrapln!("You have no weapons.");
      wrapln!("You must fight with bare hands.");
    } else if axe && !sword {
      wrapln!("You have only an axe to fight with.");
    } else if sword && !axe {
      wrapln!("You must fight with your sword.");
    } else {
      wrapln!("You are dual-wielding a sword and axe like a boss.");
    }
    Self::pause();
  }

  fn press_enter_to_fight(&mut self, state: CombatState) {
    self.ask("Press enter to fight! ", move |game_state, _| {
      game_state.prepare();
      wrapln!("\n");
      game_state.curr_mode = GameMode::Combat(CombatState {
        phase: Battle,
        .. state
      });
    });
  }

  fn ask_direction_to_run(&mut self) {
    self.ask("What direction will you run? ", move |game_state, input| {
      if let Some(Flee(dir)) = FleeCommand::get_from_input(input) {
        if game_state.try_to_move(dir) {
          return;
        }
      }
      game_state.ask_again();
    });
  }

  fn tick_preparation_phase(&mut self, state: &CombatState) {
    let state = *state;
    self.ask("Will you run away like a coward? ", move |game_state, input| {
      let lower_input = input.to_lowercase();
      if lower_input.starts_with('y') {
        if platform::random() <= CHANCE_TO_RUN {
          game_state.set_mode(GameMode::Primary);
          game_state.ask_direction_to_run();
          return;
        }
        wrapln!("Your craven attempt to escape has failed.");
      } else if lower_input.starts_with('n') {
        wrapln!("Awesome.");
      } else {
        wrapln!("Please answer 'yes' or 'no'.");
        game_state.ask_again();
        return;
      }
      game_state.press_enter_to_fight(state);
    });
  }

  fn tick_battle_phase(&mut self, state: &CombatState) {
    let mut ff = state.ff;
    let mut phase = state.phase;

    if platform::random() > 0.5 {
      wrapln!("The {} attacks!", state.enemy);
    } else {
      wrapln!("You attack!");
    }
    Self::pause();
    if platform::random() > 0.5 {
      wrapln!("\nYou manage to wound it!");
      ff = 5 * ff / 6;
      Self::pause();
    }
    if platform::random() > 0.5 {
      self.strength -= 5;
      if self.strength > 0 {
        wrapln!("\nThe monster wounds you!");
      } else {
        wrapln!("\nThe monster lands a killing blow!");
      }
      Self::pause();
    }
    if platform::random() <= 0.35 {
      phase = Aftermath;
    }
    self.curr_mode = GameMode::Combat(CombatState {
      phase: phase,
      ff: ff,
      .. *state
    });
  }

  fn tick_aftermath_phase(&mut self, state: &CombatState) {
    if platform::random_i32(0, 16) > state.ff {
      wrapln!("\nWounded and ashamed, the {} scurries off.",
              state.enemy);
      wrapln!("\nYou are victorious!");
      self.monsters_killed += 1;
    } else {
      wrapln!("\nThe {} knocks you down!", state.enemy);
      wrapln!("\nYou are at its mercy!");
      Self::pause();
      wrapln!("\nIt appears to be in a generous mood and wanders off.");
      self.strength /= 2;
    }
    wrapln!("\n");
    Self::pause();
    self.map.mut_room(self.curr_room).contents = None;
    self.set_mode(GameMode::Primary);
  }

  pub fn tick_combat_mode(&mut self, state: &CombatState) {
    match state.phase {
      Preparation => { self.tick_preparation_phase(state) },
      Battle => { self.tick_battle_phase(state) },
      Aftermath => { self.tick_aftermath_phase(state) },
    }
  }
}
<|endoftext|>


<commit_before>//! To see how color numbers map to actual colors in your terminal run
//! `cargo run --example colors`. Use tab to swap fg/bg colors.

use std::env::home_dir;
use std::path::PathBuf;

#[derive(Debug, Clone, Deserialize)]
pub struct Server {
    /// Address of the server
    pub addr: String,

    /// Port of the server
    pub port: u16,

    /// Hostname to be used in connection registration
    pub hostname: String,

    /// Real name to be used in connection registration
    pub realname: String,

    /// Nicks to try when connecting to this server. tiny tries these sequentially, and starts
    /// adding trailing underscores to the last one if none of the nicks are available.
    pub nicks: Vec<String>,

    /// Commands to automatically run after joining to the server. Useful for e.g. registering the
    /// nick via nickserv or joining channels. Uses tiny command syntax.
    pub auto_cmds: Vec<String>,
}

/// Similar to `Server`, but used when connecting via the `/connect` command.
#[derive(Debug, Clone, Deserialize)]
pub struct Defaults {
    pub nicks: Vec<String>,
    pub hostname: String,
    pub realname: String,
    pub auto_cmds: Vec<String>
}

#[derive(Debug, Deserialize)]
pub struct Config {
    pub servers: Vec<Server>,
    pub defaults: Defaults,
    pub theme: Theme,
    pub log_dir: String,
}

pub fn get_config_path() -> PathBuf {
    let mut config_path = home_dir().unwrap();
    config_path.push(".tinyrc.yml");
    config_path
}

pub fn get_default_config_yaml() -> String {
    let mut log_dir = home_dir().unwrap();
    log_dir.push("tiny_logs");
    format!("\
# Servers to auto-connect
servers:
    - addr: irc.mozilla.org
      port: 6667
      hostname: yourhost
      realname: yourname
      nicks: [tiny_user]
      auto_cmds:
          - 'msg NickServ identify hunter2'
          - 'join #tiny'

# Defaults used when connecting to servers via the /connect command
defaults:
    nicks: [tiny_user]
    hostname: yourhost
    realname: yourname
    auto_cmds: []

# Where to put log files
log_dir: '{}'

# Color theme based on 256 colors (if supported), colors can be defined as color
# index (0-255) or # with its name
#
# Accepted color names are:
# default, black, darkred, darkgreen, darkyellow, darkblue, darkmagenta,
# darkcyan, lightgray, darkgray, red, green, yellow, blue, magenta, cyan, white
#
# Attributes can be combined (e.g [bold, underline]), and valid values are bold
# and underline
theme:
    nick_colors: [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 ]

    # Used for whitespace
    clear:
        fg: default
        bg: default

    user_msg:
        fg: black
        bg: default

    err_msg:
        fg: black
        bg: darkred
        attrs: [underline]

    topic:
        fg: lightgray
        bg: default
        attrs: [bold]

    cursor:
        fg: black
        bg: default

    join:
        fg: green
        bg: default
        attrs: [bold]

    part:
        fg: darkred
        bg: default
        attrs: [bold]

    nick:
        fg: green
        bg: default
        attrs: [bold]

    faded:
        fg: 242
        bg: default

    exit_dialogue:
        fg: default
        bg: darkblue

    highlight:
        fg: red
        bg: default
        attrs: [bold]

    completion:
        fg: 84
        bg: default

    timestamp:
        fg: 242
        bg: default

    tab_active:
        fg: darkred
        bg: black
        attrs: [bold]

    tab_normal:
        fg: darkgray
        bg: black

    tab_new_msg:
        fg: darkmagenta
        bg: black

    tab_highlight:
        fg: red
        bg: black
        attrs: [bold]\n", log_dir.as_path().to_str().unwrap())
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// Theme
////////////////////////////////////////////////////////////////////////////////////////////////////
use serde::de::{self, Deserialize, Deserializer, Visitor, MapAccess};
pub use termbox_simple::*;

#[derive(Debug, Clone, Copy)]
pub struct Style {
    /// Termbox fg.
    pub fg: u16,

    /// Termbox bg.
    pub bg: u16,
}

// Color names are taken from https://en.wikipedia.org/wiki/List_of_software_palettes
const COLORS: [(&'static str, u16); 17] =
[
    // Default fg/bg color of the terminal
    ("default",     TB_DEFAULT),

    // Dark variants
    ("black",       0),
    ("darkred",     1),
    ("darkgreen",   2),
    ("darkyellow",  3),
    ("darkblue",    4),
    ("darkmagenta", 5),
    ("darkcyan",    6),
    ("lightgray",   7),

    // Bright variants
    ("darkgray",    8),
    ("red",         9),
    ("green",       10),
    ("yellow",      11),
    ("blue",        12),
    ("magenta",     13),
    ("cyan",        14),
    ("white",       15)
];

const ATTRS: [(&'static str, u16); 2] =
[
    ("bold",      TB_BOLD),
    ("underline", TB_UNDERLINE)
];

fn parse_color(val: String) -> Option<u16> {
    for &(name, color) in &COLORS {
        if val == name {
            return Some(color);
        }
    }

    // If color name doesn't match try get a number
    val.parse().ok()
}

fn parse_attr(val: String) -> u16 {
    for &(name, attr) in &ATTRS {
        if name == val {
            return attr;
        }
    }

    return 0;
}

impl<'de> Deserialize<'de> for Style {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
        where D: Deserializer<'de>
    {
        #[derive(Deserialize)]
        #[serde(field_identifier, rename_all = "lowercase")]
        enum Field { Fg, Bg, Attrs }

        use std::fmt;

        struct StyleVisitor;
        impl<'de> Visitor<'de> for StyleVisitor {
            type Value = Style;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                let colors = COLORS.iter().map(|&(name, _)| name).collect::<Vec<&str>>().join(", ");
                let attrs = ATTRS.iter().map(|&(name, _)| name).collect::<Vec<&str>>().join(", ");

                let expected_format = format!(
                    "Expected format:\n\
                    fg: 0-255 or color name\n\
                    bg: 0-255 or color name\n\
                    attrs: [{}]\n\n\
                    color names: {}", attrs, colors);
                let msg = format!("color style\n{}", expected_format);

                formatter.write_str(msg.as_str())
            }

            fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
                where M: MapAccess<'de>
            {
                let mut fg: Option<u16> = None;
                let mut bg: Option<u16> = None;
                let mut attr: u16 = 0;

                while let Some(key) = map.next_key()? {
                    match key {
                        Field::Fg => {
                             let color = parse_color(map.next_value()?)
                                 .ok_or_else(|| de::Error::invalid_value(de::Unexpected::UnitVariant, &self))?;

                             fg = Some(color);
                        },

                        Field::Bg => {
                            let color = parse_color(map.next_value()?)
                                .ok_or_else(|| de::Error::invalid_value(de::Unexpected::UnitVariant, &self))?;

                            bg = Some(color);
                        },

                        Field::Attrs => {
                            let attrs: Vec<String> = map.next_value()?;
                            attr = attrs.into_iter()
                                .map(parse_attr)
                                .fold(0, |style, a| style | a);
                        }
                    }
                }

                let fg = fg.ok_or_else(|| de::Error::missing_field("fg"))?;
                let bg = bg.ok_or_else(|| de::Error::missing_field("bg"))?;

                Ok(Style { fg: fg | attr, bg })
            }
        }

        deserializer.deserialize_map(StyleVisitor)
    }
}

#[derive(Debug, Deserialize)]
#[serde(default)]
pub struct Theme {
    /// Colors used to highlight nicks
    pub nick_colors: [u8; 14],
    pub clear: Style,
    pub user_msg: Style,
    pub err_msg: Style,
    pub topic: Style,
    pub cursor: Style,
    pub join: Style,
    pub part: Style,
    pub nick: Style,
    pub faded: Style,
    pub exit_dialogue: Style,
    pub highlight: Style,
    pub completion: Style,
    pub timestamp: Style,
    pub tab_active: Style,
    pub tab_normal: Style,
    pub tab_new_msg: Style,
    pub tab_highlight: Style
}

const fn default_theme() -> Theme {
    Theme {
        nick_colors: [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 ],
        clear: Style { fg: TB_DEFAULT, bg: TB_DEFAULT },
        user_msg: Style { fg: 0, bg: TB_DEFAULT },
        err_msg: Style { fg: 0 | TB_BOLD, bg: 1 },
        topic: Style { fg: 14 | TB_BOLD, bg: TB_DEFAULT },
        cursor: Style { fg: 0, bg: TB_DEFAULT },
        join: Style { fg: 10 | TB_BOLD, bg: TB_DEFAULT },
        part: Style { fg: 1 | TB_BOLD, bg: TB_DEFAULT },
        nick: Style { fg: 10 | TB_BOLD, bg: TB_DEFAULT },
        faded: Style { fg: 242, bg: TB_DEFAULT },
        exit_dialogue: Style { fg: TB_DEFAULT, bg: 4 },
        highlight: Style { fg: 9 | TB_BOLD, bg: TB_DEFAULT },
        completion: Style { fg: 84, bg: TB_DEFAULT },
        timestamp: Style { fg: 242, bg: TB_DEFAULT },
        tab_active: Style { fg: 1 | TB_BOLD, bg: 0 },
        tab_normal: Style { fg: 8, bg: 0 },
        tab_new_msg: Style { fg: 5, bg: 0 },
        tab_highlight: Style { fg: 9 | TB_BOLD, bg: 0 },
    }
}

impl Default for Theme {
    fn default() -> Self {
        default_theme()
    }
}

static mut THEME: Theme = default_theme();

pub fn get_theme() -> &'static Theme {
    unsafe { &THEME }
}
pub fn set_theme(theme: Theme) {
    unsafe { THEME = theme; }
}

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

    #[test]
    fn parse_default_config() {
        match serde_yaml::from_str(&get_default_config_yaml()) {
            Err(yaml_err) => {
                println!("{}", yaml_err);
                assert!(false);
            }
            Ok(Config { .. }) => {}
        }
    }

    #[test]
    fn parse_pre_color_config() {
        // Parse config file without a theme field. Important to be able to
        // parse old config files.
        let config = "\
# Servers to auto-connect
servers:
    - addr: irc.mozilla.org
      port: 6667
      hostname: yourhost
      realname: yourname
      nicks: [tiny_user]
      auto_cmds:
          - 'msg NickServ identify hunter2'
          - 'join #tiny'

# Defaults used when connecting to servers via the /connect command
defaults:
    nicks: [tiny_user]
    hostname: yourhost
    realname: yourname
    auto_cmds: []

# Where to put log files
log_dir: path";
        match serde_yaml::from_str(config) {
            Err(yaml_err) => {
                println!("{}", yaml_err);
                assert!(false);
            }
            Ok(Config { .. }) => {}
        }
    }
}
<commit_msg>Fix config parser to make it backward compatible<commit_after>//! To see how color numbers map to actual colors in your terminal run
//! `cargo run --example colors`. Use tab to swap fg/bg colors.

use std::env::home_dir;
use std::path::PathBuf;

#[derive(Debug, Clone, Deserialize)]
pub struct Server {
    /// Address of the server
    pub addr: String,

    /// Port of the server
    pub port: u16,

    /// Hostname to be used in connection registration
    pub hostname: String,

    /// Real name to be used in connection registration
    pub realname: String,

    /// Nicks to try when connecting to this server. tiny tries these sequentially, and starts
    /// adding trailing underscores to the last one if none of the nicks are available.
    pub nicks: Vec<String>,

    /// Commands to automatically run after joining to the server. Useful for e.g. registering the
    /// nick via nickserv or joining channels. Uses tiny command syntax.
    pub auto_cmds: Vec<String>,
}

/// Similar to `Server`, but used when connecting via the `/connect` command.
#[derive(Debug, Clone, Deserialize)]
pub struct Defaults {
    pub nicks: Vec<String>,
    pub hostname: String,
    pub realname: String,
    pub auto_cmds: Vec<String>
}

#[derive(Debug, Deserialize)]
pub struct Config {
    pub servers: Vec<Server>,
    pub defaults: Defaults,
    #[serde(default)]
    pub theme: Theme,
    pub log_dir: String,
}

pub fn get_config_path() -> PathBuf {
    let mut config_path = home_dir().unwrap();
    config_path.push(".tinyrc.yml");
    config_path
}

pub fn get_default_config_yaml() -> String {
    let mut log_dir = home_dir().unwrap();
    log_dir.push("tiny_logs");
    format!("\
# Servers to auto-connect
servers:
    - addr: irc.mozilla.org
      port: 6667
      hostname: yourhost
      realname: yourname
      nicks: [tiny_user]
      auto_cmds:
          - 'msg NickServ identify hunter2'
          - 'join #tiny'

# Defaults used when connecting to servers via the /connect command
defaults:
    nicks: [tiny_user]
    hostname: yourhost
    realname: yourname
    auto_cmds: []

# Where to put log files
log_dir: '{}'

# Color theme based on 256 colors (if supported), colors can be defined as color
# index (0-255) or # with its name
#
# Accepted color names are:
# default, black, darkred, darkgreen, darkyellow, darkblue, darkmagenta,
# darkcyan, lightgray, darkgray, red, green, yellow, blue, magenta, cyan, white
#
# Attributes can be combined (e.g [bold, underline]), and valid values are bold
# and underline
theme:
    nick_colors: [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 ]

    # Used for whitespace
    clear:
        fg: default
        bg: default

    user_msg:
        fg: black
        bg: default

    err_msg:
        fg: black
        bg: darkred
        attrs: [underline]

    topic:
        fg: lightgray
        bg: default
        attrs: [bold]

    cursor:
        fg: black
        bg: default

    join:
        fg: green
        bg: default
        attrs: [bold]

    part:
        fg: darkred
        bg: default
        attrs: [bold]

    nick:
        fg: green
        bg: default
        attrs: [bold]

    faded:
        fg: 242
        bg: default

    exit_dialogue:
        fg: default
        bg: darkblue

    highlight:
        fg: red
        bg: default
        attrs: [bold]

    completion:
        fg: 84
        bg: default

    timestamp:
        fg: 242
        bg: default

    tab_active:
        fg: darkred
        bg: black
        attrs: [bold]

    tab_normal:
        fg: darkgray
        bg: black

    tab_new_msg:
        fg: darkmagenta
        bg: black

    tab_highlight:
        fg: red
        bg: black
        attrs: [bold]\n", log_dir.as_path().to_str().unwrap())
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// Theme
////////////////////////////////////////////////////////////////////////////////////////////////////
use serde::de::{self, Deserialize, Deserializer, Visitor, MapAccess};
pub use termbox_simple::*;

#[derive(Debug, Clone, Copy)]
pub struct Style {
    /// Termbox fg.
    pub fg: u16,

    /// Termbox bg.
    pub bg: u16,
}

// Color names are taken from https://en.wikipedia.org/wiki/List_of_software_palettes
const COLORS: [(&'static str, u16); 17] =
[
    // Default fg/bg color of the terminal
    ("default",     TB_DEFAULT),

    // Dark variants
    ("black",       0),
    ("darkred",     1),
    ("darkgreen",   2),
    ("darkyellow",  3),
    ("darkblue",    4),
    ("darkmagenta", 5),
    ("darkcyan",    6),
    ("lightgray",   7),

    // Bright variants
    ("darkgray",    8),
    ("red",         9),
    ("green",       10),
    ("yellow",      11),
    ("blue",        12),
    ("magenta",     13),
    ("cyan",        14),
    ("white",       15)
];

const ATTRS: [(&'static str, u16); 2] =
[
    ("bold",      TB_BOLD),
    ("underline", TB_UNDERLINE)
];

fn parse_color(val: String) -> Option<u16> {
    for &(name, color) in &COLORS {
        if val == name {
            return Some(color);
        }
    }

    // If color name doesn't match try get a number
    val.parse().ok()
}

fn parse_attr(val: String) -> u16 {
    for &(name, attr) in &ATTRS {
        if name == val {
            return attr;
        }
    }

    return 0;
}

impl<'de> Deserialize<'de> for Style {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
        where D: Deserializer<'de>
    {
        #[derive(Deserialize)]
        #[serde(field_identifier, rename_all = "lowercase")]
        enum Field { Fg, Bg, Attrs }

        use std::fmt;

        struct StyleVisitor;
        impl<'de> Visitor<'de> for StyleVisitor {
            type Value = Style;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                let colors = COLORS.iter().map(|&(name, _)| name).collect::<Vec<&str>>().join(", ");
                let attrs = ATTRS.iter().map(|&(name, _)| name).collect::<Vec<&str>>().join(", ");

                let expected_format = format!(
                    "Expected format:\n\
                    fg: 0-255 or color name\n\
                    bg: 0-255 or color name\n\
                    attrs: [{}]\n\n\
                    color names: {}", attrs, colors);
                let msg = format!("color style\n{}", expected_format);

                formatter.write_str(msg.as_str())
            }

            fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
                where M: MapAccess<'de>
            {
                let mut fg: Option<u16> = None;
                let mut bg: Option<u16> = None;
                let mut attr: u16 = 0;

                while let Some(key) = map.next_key()? {
                    match key {
                        Field::Fg => {
                             let color = parse_color(map.next_value()?)
                                 .ok_or_else(|| de::Error::invalid_value(de::Unexpected::UnitVariant, &self))?;

                             fg = Some(color);
                        },

                        Field::Bg => {
                            let color = parse_color(map.next_value()?)
                                .ok_or_else(|| de::Error::invalid_value(de::Unexpected::UnitVariant, &self))?;

                            bg = Some(color);
                        },

                        Field::Attrs => {
                            let attrs: Vec<String> = map.next_value()?;
                            attr = attrs.into_iter()
                                .map(parse_attr)
                                .fold(0, |style, a| style | a);
                        }
                    }
                }

                let fg = fg.ok_or_else(|| de::Error::missing_field("fg"))?;
                let bg = bg.ok_or_else(|| de::Error::missing_field("bg"))?;

                Ok(Style { fg: fg | attr, bg })
            }
        }

        deserializer.deserialize_map(StyleVisitor)
    }
}

#[derive(Debug, Deserialize)]
#[serde(default)]
pub struct Theme {
    /// Colors used to highlight nicks
    pub nick_colors: [u8; 14],
    pub clear: Style,
    pub user_msg: Style,
    pub err_msg: Style,
    pub topic: Style,
    pub cursor: Style,
    pub join: Style,
    pub part: Style,
    pub nick: Style,
    pub faded: Style,
    pub exit_dialogue: Style,
    pub highlight: Style,
    pub completion: Style,
    pub timestamp: Style,
    pub tab_active: Style,
    pub tab_normal: Style,
    pub tab_new_msg: Style,
    pub tab_highlight: Style
}

const fn default_theme() -> Theme {
    Theme {
        nick_colors: [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 ],
        clear: Style { fg: TB_DEFAULT, bg: TB_DEFAULT },
        user_msg: Style { fg: 0, bg: TB_DEFAULT },
        err_msg: Style { fg: 0 | TB_BOLD, bg: 1 },
        topic: Style { fg: 14 | TB_BOLD, bg: TB_DEFAULT },
        cursor: Style { fg: 0, bg: TB_DEFAULT },
        join: Style { fg: 10 | TB_BOLD, bg: TB_DEFAULT },
        part: Style { fg: 1 | TB_BOLD, bg: TB_DEFAULT },
        nick: Style { fg: 10 | TB_BOLD, bg: TB_DEFAULT },
        faded: Style { fg: 242, bg: TB_DEFAULT },
        exit_dialogue: Style { fg: TB_DEFAULT, bg: 4 },
        highlight: Style { fg: 9 | TB_BOLD, bg: TB_DEFAULT },
        completion: Style { fg: 84, bg: TB_DEFAULT },
        timestamp: Style { fg: 242, bg: TB_DEFAULT },
        tab_active: Style { fg: 1 | TB_BOLD, bg: 0 },
        tab_normal: Style { fg: 8, bg: 0 },
        tab_new_msg: Style { fg: 5, bg: 0 },
        tab_highlight: Style { fg: 9 | TB_BOLD, bg: 0 },
    }
}

impl Default for Theme {
    fn default() -> Self {
        default_theme()
    }
}

static mut THEME: Theme = default_theme();

pub fn get_theme() -> &'static Theme {
    unsafe { &THEME }
}
pub fn set_theme(theme: Theme) {
    unsafe { THEME = theme; }
}

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

    #[test]
    fn parse_default_config() {
        match serde_yaml::from_str(&get_default_config_yaml()) {
            Err(yaml_err) => {
                println!("{}", yaml_err);
                assert!(false);
            }
            Ok(Config { .. }) => {}
        }
    }

    #[test]
    fn parse_pre_color_config() {
        // Parse config file without a theme field. Important to be able to
        // parse old config files.
        let config = "\
# Servers to auto-connect
servers:
    - addr: irc.mozilla.org
      port: 6667
      hostname: yourhost
      realname: yourname
      nicks: [tiny_user]
      auto_cmds:
          - 'msg NickServ identify hunter2'
          - 'join #tiny'

# Defaults used when connecting to servers via the /connect command
defaults:
    nicks: [tiny_user]
    hostname: yourhost
    realname: yourname
    auto_cmds: []

# Where to put log files
log_dir: path";
        match serde_yaml::from_str(config) {
            Err(yaml_err) => {
                println!("{}", yaml_err);
                assert!(false);
            }
            Ok(Config { .. }) => {}
        }
    }
}
<|endoftext|>