<commit_before>// Copyright (c) 2017 Anatoly Ikorsky
//
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. All files in the project carrying such notice may not be copied,
// modified, or distributed except according to those terms.

use packets::Column;
use smallvec::SmallVec;
use std::fmt;
use std::ops::Index;
use std::sync::Arc;
use value::Value;
use value::convert::{FromValue, FromValueError, from_value, from_value_opt};

pub mod convert;

/// Client side representation of a MySql row.
///
/// It allows you to move column values out of a row with `Row::take` method but note that it
/// makes row incomplete. Calls to `from_row_opt` on incomplete row will return
/// `Error::FromRowError` and also numerical indexing on taken columns will panic.
#[derive(Clone, PartialEq)]
pub struct Row {
    values: SmallVec<[Option<Value>; 12]>,
    columns: Arc<Vec<Column>>,
}

impl fmt::Debug for Row {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let mut debug = f.debug_struct("Row");
        for (val, column) in self.values.iter().zip(self.columns.iter()) {
            match *val {
                Some(ref val) => {
                    debug.field(column.name_str().as_ref(), val);
                }
                None => {
                    debug.field(column.name_str().as_ref(), &"<taken>");
                }
            }
        }
        debug.finish()
    }
}

/// Creates `Row` from values and columns.
pub fn new_row(values: SmallVec<[Value; 12]>, columns: Arc<Vec<Column>>) -> Row {
    assert!(values.len() == columns.len());
    Row {
        values: values.into_iter().map(|value| Some(value)).collect(),
        columns,
    }
}

impl Row {
    /// Returns length of a row.
    pub fn len(&self) -> usize {
        self.values.len()
    }

    /// Returns reference to the value of a column with index `index` if it exists and wasn't taken
    /// by `Row::take` method.
    ///
    /// Non panicking version of `row[usize]`.
    pub fn as_ref(&self, index: usize) -> Option<&Value> {
        self.values.get(index).and_then(|x| x.as_ref())
    }

    /// Will copy value at index `index` if it was not taken by `Row::take` earlier,
    /// then will convert it to `T`.
    pub fn get<T, I>(&mut self, index: I) -> Option<T>
    where
        T: FromValue,
        I: ColumnIndex,
    {
        index.idx(&*self.columns).and_then(|idx| {
            self.values.get(idx).and_then(|x| x.as_ref()).map(|x| {
                from_value::<T>(x.clone())
            })
        })
    }

    /// Will copy value at index `index` if it was not taken by `Row::take` or `Row::take_opt`
    /// earlier, then will attempt convert it to `T`. Unlike `Row::get`, `Row::get_opt` will
    /// allow you to directly handle errors if the value could not be converted to `T`.
    pub fn get_opt<T, I>(&mut self, index: I) -> Option<Result<T, FromValueError>>
    where
        T: FromValue,
        I: ColumnIndex,
    {
        index
            .idx(&*self.columns)
            .and_then(|idx| self.values.get(idx))
            .and_then(|x| x.as_ref())
            .and_then(|x| Some(from_value_opt::<T>(x.clone())))
    }

    /// Will take value of a column with index `index` if it exists and wasn't taken earlier then
    /// will converts it to `T`.
    pub fn take<T, I>(&mut self, index: I) -> Option<T>
    where
        T: FromValue,
        I: ColumnIndex,
    {
        index.idx(&*self.columns).and_then(|idx| {
            self.values.get_mut(idx).and_then(|x| x.take()).map(
                from_value::<T>,
            )
        })
    }

    /// Will take value of a column with index `index` if it exists and wasn't taken earlier then
    /// will attempt to convert it to `T`. Unlike `Row::take`, `Row::take_opt` will allow you to
    /// directly handle errors if the value could not be converted to `T`.
    pub fn take_opt<T, I>(&mut self, index: I) -> Option<Result<T, FromValueError>>
    where
        T: FromValue,
        I: ColumnIndex,
    {
        index
            .idx(&*self.columns)
            .and_then(|idx| self.values.get_mut(idx))
            .and_then(|x| x.take())
            .and_then(|x| Some(from_value_opt::<T>(x)))
    }

    /// Unwraps values of a row.
    ///
    /// # Panics
    ///
    /// Panics if any of columns was taken by `take` method.
    pub fn unwrap(self) -> Vec<Value> {
        self.values
            .into_iter()
            .map(|x| {
                x.expect("Can't unwrap row if some of columns was taken")
            })
            .collect()
    }

    #[doc(hidden)]
    pub fn place(&mut self, index: usize, value: Value) {
        self.values[index] = Some(value);
    }
}

impl Index<usize> for Row {
    type Output = Value;

    fn index<'a>(&'a self, index: usize) -> &'a Value {
        self.values[index].as_ref().unwrap()
    }
}

impl<'a> Index<&'a str> for Row {
    type Output = Value;

    fn index<'r>(&'r self, index: &'a str) -> &'r Value {
        for (i, column) in self.columns.iter().enumerate() {
            if column.name_ref() == index.as_bytes() {
                return self.values[i].as_ref().unwrap();
            }
        }
        panic!("No such column: `{}` in row {:?}", index, self);
    }
}

/// Things that may be used as an index of a row column.
pub trait ColumnIndex {
    fn idx(&self, columns: &Vec<Column>) -> Option<usize>;
}

impl ColumnIndex for usize {
    fn idx(&self, columns: &Vec<Column>) -> Option<usize> {
        if *self >= columns.len() {
            None
        } else {
            Some(*self)
        }
    }
}

impl<'a> ColumnIndex for &'a str {
    fn idx(&self, columns: &Vec<Column>) -> Option<usize> {
        for (i, c) in columns.iter().enumerate() {
            if c.name_ref() == self.as_bytes() {
                return Some(i);
            }
        }
        None
    }
}
<commit_msg>Remove unnecessary mut<commit_after>// Copyright (c) 2017 Anatoly Ikorsky
//
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. All files in the project carrying such notice may not be copied,
// modified, or distributed except according to those terms.

use packets::Column;
use smallvec::SmallVec;
use std::fmt;
use std::ops::Index;
use std::sync::Arc;
use value::Value;
use value::convert::{FromValue, FromValueError, from_value, from_value_opt};

pub mod convert;

/// Client side representation of a MySql row.
///
/// It allows you to move column values out of a row with `Row::take` method but note that it
/// makes row incomplete. Calls to `from_row_opt` on incomplete row will return
/// `Error::FromRowError` and also numerical indexing on taken columns will panic.
#[derive(Clone, PartialEq)]
pub struct Row {
    values: SmallVec<[Option<Value>; 12]>,
    columns: Arc<Vec<Column>>,
}

impl fmt::Debug for Row {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let mut debug = f.debug_struct("Row");
        for (val, column) in self.values.iter().zip(self.columns.iter()) {
            match *val {
                Some(ref val) => {
                    debug.field(column.name_str().as_ref(), val);
                }
                None => {
                    debug.field(column.name_str().as_ref(), &"<taken>");
                }
            }
        }
        debug.finish()
    }
}

/// Creates `Row` from values and columns.
pub fn new_row(values: SmallVec<[Value; 12]>, columns: Arc<Vec<Column>>) -> Row {
    assert!(values.len() == columns.len());
    Row {
        values: values.into_iter().map(|value| Some(value)).collect(),
        columns,
    }
}

impl Row {
    /// Returns length of a row.
    pub fn len(&self) -> usize {
        self.values.len()
    }

    /// Returns reference to the value of a column with index `index` if it exists and wasn't taken
    /// by `Row::take` method.
    ///
    /// Non panicking version of `row[usize]`.
    pub fn as_ref(&self, index: usize) -> Option<&Value> {
        self.values.get(index).and_then(|x| x.as_ref())
    }

    /// Will copy value at index `index` if it was not taken by `Row::take` earlier,
    /// then will convert it to `T`.
    pub fn get<T, I>(&self, index: I) -> Option<T>
    where
        T: FromValue,
        I: ColumnIndex,
    {
        index.idx(&*self.columns).and_then(|idx| {
            self.values.get(idx).and_then(|x| x.as_ref()).map(|x| {
                from_value::<T>(x.clone())
            })
        })
    }

    /// Will copy value at index `index` if it was not taken by `Row::take` or `Row::take_opt`
    /// earlier, then will attempt convert it to `T`. Unlike `Row::get`, `Row::get_opt` will
    /// allow you to directly handle errors if the value could not be converted to `T`.
    pub fn get_opt<T, I>(&self, index: I) -> Option<Result<T, FromValueError>>
    where
        T: FromValue,
        I: ColumnIndex,
    {
        index
            .idx(&*self.columns)
            .and_then(|idx| self.values.get(idx))
            .and_then(|x| x.as_ref())
            .and_then(|x| Some(from_value_opt::<T>(x.clone())))
    }

    /// Will take value of a column with index `index` if it exists and wasn't taken earlier then
    /// will converts it to `T`.
    pub fn take<T, I>(&mut self, index: I) -> Option<T>
    where
        T: FromValue,
        I: ColumnIndex,
    {
        index.idx(&*self.columns).and_then(|idx| {
            self.values.get_mut(idx).and_then(|x| x.take()).map(
                from_value::<T>,
            )
        })
    }

    /// Will take value of a column with index `index` if it exists and wasn't taken earlier then
    /// will attempt to convert it to `T`. Unlike `Row::take`, `Row::take_opt` will allow you to
    /// directly handle errors if the value could not be converted to `T`.
    pub fn take_opt<T, I>(&mut self, index: I) -> Option<Result<T, FromValueError>>
    where
        T: FromValue,
        I: ColumnIndex,
    {
        index
            .idx(&*self.columns)
            .and_then(|idx| self.values.get_mut(idx))
            .and_then(|x| x.take())
            .and_then(|x| Some(from_value_opt::<T>(x)))
    }

    /// Unwraps values of a row.
    ///
    /// # Panics
    ///
    /// Panics if any of columns was taken by `take` method.
    pub fn unwrap(self) -> Vec<Value> {
        self.values
            .into_iter()
            .map(|x| {
                x.expect("Can't unwrap row if some of columns was taken")
            })
            .collect()
    }

    #[doc(hidden)]
    pub fn place(&mut self, index: usize, value: Value) {
        self.values[index] = Some(value);
    }
}

impl Index<usize> for Row {
    type Output = Value;

    fn index<'a>(&'a self, index: usize) -> &'a Value {
        self.values[index].as_ref().unwrap()
    }
}

impl<'a> Index<&'a str> for Row {
    type Output = Value;

    fn index<'r>(&'r self, index: &'a str) -> &'r Value {
        for (i, column) in self.columns.iter().enumerate() {
            if column.name_ref() == index.as_bytes() {
                return self.values[i].as_ref().unwrap();
            }
        }
        panic!("No such column: `{}` in row {:?}", index, self);
    }
}

/// Things that may be used as an index of a row column.
pub trait ColumnIndex {
    fn idx(&self, columns: &Vec<Column>) -> Option<usize>;
}

impl ColumnIndex for usize {
    fn idx(&self, columns: &Vec<Column>) -> Option<usize> {
        if *self >= columns.len() {
            None
        } else {
            Some(*self)
        }
    }
}

impl<'a> ColumnIndex for &'a str {
    fn idx(&self, columns: &Vec<Column>) -> Option<usize> {
        for (i, c) in columns.iter().enumerate() {
            if c.name_ref() == self.as_bytes() {
                return Some(i);
            }
        }
        None
    }
}
<|endoftext|>


<commit_before>extern crate winapi;

use std::fs::File;
use std::io;
use std::io::{Write, Seek, SeekFrom};
use std::mem;
use std::ptr;
use std::cmp::min;
use std::os::windows::fs::FileExt;
use std::os::windows::io::AsRawHandle;

use winapi::shared::basetsd::SIZE_T;
use winapi::shared::minwindef::{BOOL, DWORD};
use winapi::um::handleapi::CloseHandle;
use winapi::um::sysinfoapi::GetSystemInfo;
use winapi::um::winnt::{HANDLE, PAGE_READONLY};
use winapi::um::memoryapi::{CreateFileMappingW, MapViewOfFile, UnmapViewOfFile, FILE_MAP_READ};

use super::{ReadAt, WriteAt};

fn result(e: BOOL) -> io::Result<()> {
    if e == 0 {
        Err(io::Error::last_os_error())
    } else {
        Ok(())
    }
}

fn allocation_granularity() -> usize {
    unsafe {
        let mut info = mem::zeroed();
        GetSystemInfo(&mut info);
        info.dwAllocationGranularity as usize
    }
}

impl ReadAt for File {
    fn read_at(&self, pos: u64, buf: &mut [u8]) -> io::Result<usize> {
        let file_len = self.metadata()?.len();

        if buf.is_empty() || pos >= file_len {
            return Ok(0);
        }

        let len = min(file_len - pos, buf.len() as u64) as usize;

        unsafe {
            let alignment = pos % allocation_granularity() as u64;
            let aligned_pos = pos - alignment;
            let aligned_len = len + alignment as usize;

            let mapping = CreateFileMappingW(
                self.as_raw_handle() as HANDLE,
                ptr::null_mut(),
                PAGE_READONLY,
                0,
                0,
                ptr::null(),
            );

            if mapping.is_null() {
                return Err(io::Error::last_os_error());
            }

            let aligned_ptr = MapViewOfFile(
                mapping,
                FILE_MAP_READ,
                (aligned_pos >> 32) as DWORD,
                (aligned_pos & 0xffff_ffff) as DWORD,
                aligned_len as SIZE_T,
            );

            CloseHandle(mapping);

            if aligned_ptr.is_null() {
                return Err(io::Error::last_os_error());
            }

            let ptr = (aligned_ptr as *const u8).offset(alignment as isize);
            ptr::copy_nonoverlapping(ptr, buf.as_mut_ptr(), len);

            result(UnmapViewOfFile(aligned_ptr))?;
        }

        Ok(len)
    }
}

impl WriteAt for File {
    fn write_at(&mut self, pos: u64, buf: &[u8]) -> io::Result<usize> {
        let cursor = self.seek(SeekFrom::Current(0))?;
        let result = self.seek_write(buf, pos)?;
        self.seek(SeekFrom::Start(cursor))?;
        Ok(result)
    }

    fn flush(&mut self) -> io::Result<()> {
        Write::flush(self)
    }
}
<commit_msg>fix winapi imports<commit_after>extern crate winapi;

use std::fs::File;
use std::io;
use std::io::{Write, Seek, SeekFrom};
use std::mem;
use std::ptr;
use std::cmp::min;
use std::os::windows::fs::FileExt;
use std::os::windows::io::AsRawHandle;

use self::winapi::shared::basetsd::SIZE_T;
use self::winapi::shared::minwindef::{BOOL, DWORD};
use self::winapi::um::handleapi::CloseHandle;
use self::winapi::um::sysinfoapi::GetSystemInfo;
use self::winapi::um::winnt::{HANDLE, PAGE_READONLY};
use self::winapi::um::memoryapi::{CreateFileMappingW, MapViewOfFile, UnmapViewOfFile, FILE_MAP_READ};

use super::{ReadAt, WriteAt};

fn result(e: BOOL) -> io::Result<()> {
    if e == 0 {
        Err(io::Error::last_os_error())
    } else {
        Ok(())
    }
}

fn allocation_granularity() -> usize {
    unsafe {
        let mut info = mem::zeroed();
        GetSystemInfo(&mut info);
        info.dwAllocationGranularity as usize
    }
}

impl ReadAt for File {
    fn read_at(&self, pos: u64, buf: &mut [u8]) -> io::Result<usize> {
        let file_len = self.metadata()?.len();

        if buf.is_empty() || pos >= file_len {
            return Ok(0);
        }

        let len = min(file_len - pos, buf.len() as u64) as usize;

        unsafe {
            let alignment = pos % allocation_granularity() as u64;
            let aligned_pos = pos - alignment;
            let aligned_len = len + alignment as usize;

            let mapping = CreateFileMappingW(
                self.as_raw_handle() as HANDLE,
                ptr::null_mut(),
                PAGE_READONLY,
                0,
                0,
                ptr::null(),
            );

            if mapping.is_null() {
                return Err(io::Error::last_os_error());
            }

            let aligned_ptr = MapViewOfFile(
                mapping,
                FILE_MAP_READ,
                (aligned_pos >> 32) as DWORD,
                (aligned_pos & 0xffff_ffff) as DWORD,
                aligned_len as SIZE_T,
            );

            CloseHandle(mapping);

            if aligned_ptr.is_null() {
                return Err(io::Error::last_os_error());
            }

            let ptr = (aligned_ptr as *const u8).offset(alignment as isize);
            ptr::copy_nonoverlapping(ptr, buf.as_mut_ptr(), len);

            result(UnmapViewOfFile(aligned_ptr))?;
        }

        Ok(len)
    }
}

impl WriteAt for File {
    fn write_at(&mut self, pos: u64, buf: &[u8]) -> io::Result<usize> {
        let cursor = self.seek(SeekFrom::Current(0))?;
        let result = self.seek_write(buf, pos)?;
        self.seek(SeekFrom::Start(cursor))?;
        Ok(result)
    }

    fn flush(&mut self) -> io::Result<()> {
        Write::flush(self)
    }
}
<|endoftext|>


<commit_before>extern crate libc;
extern crate "termbox-sys" as termbox;

use termbox::RawEvent;
use libc::{c_int, c_uint};

#[deriving(Copy)]
pub enum Event {
    KeyEvent(u8, u16, u32),
    ResizeEvent(i32, i32),
    NoEvent
}

#[deriving(Copy)]
pub enum Color {
    Default = 0,
    Black   = 1,
    Red     = 2,
    Green   = 3,
    Yellow  = 4,
    Blue    = 5,
    Magenta = 6,
    Cyan    = 7,
    White   = 8
}

#[deriving(Copy)]
pub enum Style {
    Normal        = 0x0000,
    Bold          = 0x0100,
    Underline     = 0x0200,
    BoldUnderline = 0x0300,
    Reverse       = 0x0400
}

fn nil_raw_event() -> RawEvent {
    RawEvent{etype: 0, emod: 0, key: 0, ch: 0, w: 0, h: 0}
}

fn unpack_event(ev_type: c_int, ev: &RawEvent) -> Event {
    match ev_type {
        0 => Event::NoEvent,
        1 => Event::KeyEvent(ev.emod, ev.key, ev.ch),
        2 => Event::ResizeEvent(ev.w, ev.h),
        _ => panic!("Unknown event")
    }
}

pub fn convert_color(c: Color) -> u16 {
    c as u16
}

pub fn convert_style(sty: Style) -> u16 {
    sty as u16
}

pub fn init() -> int {
    unsafe {
        termbox::tb_init() as int
    }
}

pub fn shutdown() {
    unsafe { termbox::tb_shutdown() }
}

pub fn width() -> uint {
    unsafe { termbox::tb_width() as uint }
}

pub fn height() -> uint {
    unsafe { termbox::tb_height() as uint }
}

pub fn clear() {
    unsafe { termbox::tb_clear() }
}

pub fn present() {
    unsafe { termbox::tb_present() }
}

pub fn set_cursor(x: int, y: int) {
    unsafe { termbox::tb_set_cursor(x as c_int, y as c_int) }
}

pub fn change_cell(x: uint, y: uint, ch: u32, fg: u16, bg: u16) {
    unsafe {
        termbox::tb_change_cell(x as c_uint, y as c_uint, ch, fg, bg)
    }
}

pub fn print(x: uint, y: uint, sty: Style, fg: Color, bg: Color, s: &str) {
    let fg: u16 = convert_color(fg) | convert_style(sty);
    let bg: u16 = convert_color(bg);
    for (i, ch) in s.chars().enumerate() {
        change_cell(x+i, y, ch as u32, fg, bg);
    }
}

pub fn print_char(x: uint, y: uint, sty: Style, fg: Color, bg: Color, ch: char) {
    let fg: u16 = convert_color(fg) | convert_style(sty);
    let bg: u16 = convert_color(bg);
    change_cell(x, y, ch as u32, fg, bg);
}

pub fn poll_event() -> Event {
    let ev = nil_raw_event();
    let rc = unsafe {
        termbox::tb_poll_event(&ev as *const RawEvent)
    };
    unpack_event(rc, &ev)
}

pub fn peek_event(timeout: uint) -> Event {
    let ev = nil_raw_event();
    let rc = unsafe {
        termbox::tb_peek_event(&ev as *const RawEvent, timeout as c_uint)
    };
    unpack_event(rc, &ev)
}
<commit_msg>Added missing lifetime param<commit_after>extern crate libc;
extern crate "termbox-sys" as termbox;

use termbox::RawEvent;
use libc::{c_int, c_uint};

#[deriving(Copy)]
pub enum Event {
    KeyEvent(u8, u16, u32),
    ResizeEvent(i32, i32),
    NoEvent
}

#[deriving(Copy)]
pub enum Color {
    Default = 0,
    Black   = 1,
    Red     = 2,
    Green   = 3,
    Yellow  = 4,
    Blue    = 5,
    Magenta = 6,
    Cyan    = 7,
    White   = 8
}

#[deriving(Copy)]
pub enum Style {
    Normal        = 0x0000,
    Bold          = 0x0100,
    Underline     = 0x0200,
    BoldUnderline = 0x0300,
    Reverse       = 0x0400
}

fn nil_raw_event() -> RawEvent {
    RawEvent{etype: 0, emod: 0, key: 0, ch: 0, w: 0, h: 0}
}

fn unpack_event(ev_type: c_int, ev: &RawEvent) -> Event {
    match ev_type {
        0 => Event::NoEvent,
        1 => Event::KeyEvent(ev.emod, ev.key, ev.ch),
        2 => Event::ResizeEvent(ev.w, ev.h),
        _ => panic!("Unknown event")
    }
}

pub fn convert_color(c: Color) -> u16 {
    c as u16
}

pub fn convert_style(sty: Style) -> u16 {
    sty as u16
}

pub fn init() -> int {
    unsafe {
        termbox::tb_init() as int
    }
}

pub fn shutdown() {
    unsafe { termbox::tb_shutdown() }
}

pub fn width() -> uint {
    unsafe { termbox::tb_width() as uint }
}

pub fn height() -> uint {
    unsafe { termbox::tb_height() as uint }
}

pub fn clear() {
    unsafe { termbox::tb_clear() }
}

pub fn present() {
    unsafe { termbox::tb_present() }
}

pub fn set_cursor(x: int, y: int) {
    unsafe { termbox::tb_set_cursor(x as c_int, y as c_int) }
}

pub fn change_cell(x: uint, y: uint, ch: u32, fg: u16, bg: u16) {
    unsafe {
        termbox::tb_change_cell(x as c_uint, y as c_uint, ch, fg, bg)
    }
}

pub fn print(x: uint, y: uint, sty: Style, fg: Color, bg: Color, s: &'static str) {
    let fg: u16 = convert_color(fg) | convert_style(sty);
    let bg: u16 = convert_color(bg);
    for (i, ch) in s.chars().enumerate() {
        change_cell(x+i, y, ch as u32, fg, bg);
    }
}

pub fn print_char(x: uint, y: uint, sty: Style, fg: Color, bg: Color, ch: char) {
    let fg: u16 = convert_color(fg) | convert_style(sty);
    let bg: u16 = convert_color(bg);
    change_cell(x, y, ch as u32, fg, bg);
}

pub fn poll_event() -> Event {
    let ev = nil_raw_event();
    let rc = unsafe {
        termbox::tb_poll_event(&ev as *const RawEvent)
    };
    unpack_event(rc, &ev)
}

pub fn peek_event(timeout: uint) -> Event {
    let ev = nil_raw_event();
    let rc = unsafe {
        termbox::tb_peek_event(&ev as *const RawEvent, timeout as c_uint)
    };
    unpack_event(rc, &ev)
}
<|endoftext|>


<commit_before>use std::iter::{Iterator, Peekable};
use std::result;
use std::str::Chars;

#[derive(Clone,Debug,PartialEq)]
pub enum Token {
    OpenParen,
    CloseParen,
    Eq,
    DoubleEq,
    Lt,
    LtEq,
    Gt,
    GtEq,
    Plus,
    Minus,
    Times,
    Divide,
}

#[derive(Clone,Debug,PartialEq)]
pub enum TokenError {
    UnexpectedChar(char),
}

pub type Result<T> = result::Result<T, TokenError>;

pub struct Scanner<'a> {
    input: Peekable<Chars<'a>>,
}

impl<'a> Scanner<'a> {
    pub fn new(input: &'a str) -> Self {
        Scanner{input: input.chars().peekable()}
    }

    fn is_space(c: char) -> bool {
        c == ' ' || c == '\t' || c == '\n' || c == '\r'
    }
}

impl<'a> Iterator for Scanner<'a> {
    type Item = Result<Token>;

    fn next(&mut self) -> Option<Self::Item> {
        while let Some(&c) = self.input.peek() {
            if Self::is_space(c) {
                self.input.next();
            } else {
                break;
            }
        }

        match self.input.peek() {
            None => None,
            Some(&'(') => {
                self.input.next();
                Some(Ok(Token::OpenParen))
            },
            Some(&')') => {
                self.input.next();
                Some(Ok(Token::CloseParen))
            },
            Some(&'=') => {
                self.input.next();
                if let Some(&'=') = self.input.peek() {
                    self.input.next();
                    Some(Ok(Token::DoubleEq))
                } else {
                    Some(Ok(Token::Eq))
                }
            },
            Some(&'<') => {
                self.input.next();
                if let Some(&'=') = self.input.peek() {
                    self.input.next();
                    Some(Ok(Token::LtEq))
                } else {
                    Some(Ok(Token::Lt))
                }
            },
            Some(&'>') => {
                self.input.next();
                if let Some(&'=') = self.input.peek() {
                    self.input.next();
                    Some(Ok(Token::GtEq))
                } else {
                    Some(Ok(Token::Gt))
                }
            },
            Some(&'+') => {
                self.input.next();
                Some(Ok(Token::Plus))
            },
            Some(&'-') => {
                self.input.next();
                Some(Ok(Token::Minus))
            },
            Some(&'*') => {
                self.input.next();
                Some(Ok(Token::Times))
            },
            Some(&'/') => {
                self.input.next();
                Some(Ok(Token::Divide))
            },
            Some(&c) => {
                self.input.next();
                Some(Err(TokenError::UnexpectedChar(c)))
            },
        }
    }
}

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

    #[test]
    fn test_punctuation() {
        let mut s = Scanner::new("() = == < <= > >= +-*/");
        assert_eq!(s.next(), Some(Ok(OpenParen)));
        assert_eq!(s.next(), Some(Ok(CloseParen)));
        assert_eq!(s.next(), Some(Ok(Eq)));
        assert_eq!(s.next(), Some(Ok(DoubleEq)));
        assert_eq!(s.next(), Some(Ok(Lt)));
        assert_eq!(s.next(), Some(Ok(LtEq)));
        assert_eq!(s.next(), Some(Ok(Gt)));
        assert_eq!(s.next(), Some(Ok(GtEq)));
        assert_eq!(s.next(), Some(Ok(Plus)));
        assert_eq!(s.next(), Some(Ok(Minus)));
        assert_eq!(s.next(), Some(Ok(Times)));
        assert_eq!(s.next(), Some(Ok(Divide)));
        assert_eq!(s.next(), None);
    }

    #[test]
    fn test_unexpected_char() {
        let mut s = Scanner::new("($)");
        assert_eq!(s.next(), Some(Ok(OpenParen)));
        assert_eq!(s.next(), Some(Err(TokenError::UnexpectedChar('$'))));
    }
}
<commit_msg>Add identifiers to scanner<commit_after>use std::iter::{Iterator, Peekable};
use std::result;
use std::str::Chars;

#[derive(Clone,Debug,PartialEq)]
pub enum Token {
    OpenParen,
    CloseParen,
    Eq,
    DoubleEq,
    Lt,
    LtEq,
    Gt,
    GtEq,
    Plus,
    Minus,
    Times,
    Divide,
    Nil,
    Boolean(bool),
    Identifier(String),
}

#[derive(Clone,Debug,PartialEq)]
pub enum TokenError {
    UnexpectedChar(char),
}

pub type Result<T> = result::Result<T, TokenError>;

pub struct Scanner<'a> {
    input: Peekable<Chars<'a>>,
}

impl<'a> Scanner<'a> {
    pub fn new(input: &'a str) -> Self {
        Scanner{input: input.chars().peekable()}
    }

    fn read_word(&mut self) -> Token {
        let mut word = String::new();
        while let Some(&c) = self.input.peek() {
            if !Self::is_digit(c) && !Self::is_alpha(c) {
                break;
            }

            self.input.next();
            word.push(c);
        }

        match word.as_ref() {
            "nil" => Token::Nil,
            "true" => Token::Boolean(true),
            "false" => Token::Boolean(false),
            _ => Token::Identifier(word),
        }
    }

    fn is_space(c: char) -> bool {
        c == ' ' || c == '\t' || c == '\n' || c == '\r'
    }

    fn is_alpha(c: char) -> bool {
        c == '_' || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z')
    }

    fn is_digit(c: char) -> bool {
        ('0' <= c && c <= '9')
    }
}

impl<'a> Iterator for Scanner<'a> {
    type Item = Result<Token>;

    fn next(&mut self) -> Option<Self::Item> {
        while let Some(&c) = self.input.peek() {
            if Self::is_space(c) {
                self.input.next();
            } else {
                break;
            }
        }

        match self.input.peek() {
            None => None,
            Some(&'(') => {
                self.input.next();
                Some(Ok(Token::OpenParen))
            },
            Some(&')') => {
                self.input.next();
                Some(Ok(Token::CloseParen))
            },
            Some(&'=') => {
                self.input.next();
                if let Some(&'=') = self.input.peek() {
                    self.input.next();
                    Some(Ok(Token::DoubleEq))
                } else {
                    Some(Ok(Token::Eq))
                }
            },
            Some(&'<') => {
                self.input.next();
                if let Some(&'=') = self.input.peek() {
                    self.input.next();
                    Some(Ok(Token::LtEq))
                } else {
                    Some(Ok(Token::Lt))
                }
            },
            Some(&'>') => {
                self.input.next();
                if let Some(&'=') = self.input.peek() {
                    self.input.next();
                    Some(Ok(Token::GtEq))
                } else {
                    Some(Ok(Token::Gt))
                }
            },
            Some(&'+') => {
                self.input.next();
                Some(Ok(Token::Plus))
            },
            Some(&'-') => {
                self.input.next();
                Some(Ok(Token::Minus))
            },
            Some(&'*') => {
                self.input.next();
                Some(Ok(Token::Times))
            },
            Some(&'/') => {
                self.input.next();
                Some(Ok(Token::Divide))
            },
            Some(&c) if Self::is_alpha(c) => Some(Ok(self.read_word())),
            Some(&c) => {
                self.input.next();
                Some(Err(TokenError::UnexpectedChar(c)))
            },
        }
    }
}

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

    #[test]
    fn test_punctuation() {
        let mut s = Scanner::new("() = == < <= > >= +-*/");
        assert_eq!(s.next(), Some(Ok(OpenParen)));
        assert_eq!(s.next(), Some(Ok(CloseParen)));
        assert_eq!(s.next(), Some(Ok(Eq)));
        assert_eq!(s.next(), Some(Ok(DoubleEq)));
        assert_eq!(s.next(), Some(Ok(Lt)));
        assert_eq!(s.next(), Some(Ok(LtEq)));
        assert_eq!(s.next(), Some(Ok(Gt)));
        assert_eq!(s.next(), Some(Ok(GtEq)));
        assert_eq!(s.next(), Some(Ok(Plus)));
        assert_eq!(s.next(), Some(Ok(Minus)));
        assert_eq!(s.next(), Some(Ok(Times)));
        assert_eq!(s.next(), Some(Ok(Divide)));
        assert_eq!(s.next(), None);
    }

    #[test]
    fn test_unexpected_char() {
        let mut s = Scanner::new("($)");
        assert_eq!(s.next(), Some(Ok(OpenParen)));
        assert_eq!(s.next(), Some(Err(TokenError::UnexpectedChar('$'))));
    }

    #[test]
    fn test_words() {
        let mut s = Scanner::new("foo FOO _123_ Nil nil false true");
        assert_eq!(s.next(), Some(Ok(Identifier("foo".to_owned()))));
        assert_eq!(s.next(), Some(Ok(Identifier("FOO".to_owned()))));
        assert_eq!(s.next(), Some(Ok(Identifier("_123_".to_owned()))));
        assert_eq!(s.next(), Some(Ok(Identifier("Nil".to_owned()))));
        assert_eq!(s.next(), Some(Ok(Nil)));
        assert_eq!(s.next(), Some(Ok(Boolean(false))));
        assert_eq!(s.next(), Some(Ok(Boolean(true))));
        assert_eq!(s.next(), None);
    }
}
<|endoftext|>


<commit_before>extern crate html5ever;
extern crate tendril;
extern crate regex;
extern crate hyper;
extern crate string_cache;
extern crate url;

use html5ever::rcdom::{Document, Doctype, Text, Comment, Element};
use html5ever::rcdom::{RcDom, Handle};
use html5ever::{parse_document, Attribute};
use tendril::stream::TendrilSink;
use std::default::Default;

use regex::Regex;
use hyper::Client;
use hyper::header::Connection;
use hyper::header::ConnectionOption;

use Provider;
use Track;
use open_graph;
use soundcloud;
use youtube;
use error::Error;

use url::percent_encoding::{percent_decode};

static YOUTUBE_EMBED:       &'static str = r"www.youtube.com/embed/([a-zA-Z0-9_-].+)";
static YOUTUBE_LIST:        &'static str = r"www.youtube.com/embed/videoseries\?list=([a-zA-Z0-9_-]+)";
static YOUTUBE_WATCH:       &'static str = r"www.youtube.com/watch\?v=([a-zA-Z0-9_-]+)";
static SOUNDCLOUD_TRACK:    &'static str = r"api.soundcloud.com/tracks/([a-zA-Z0-9_-]+)";
static SOUNDCLOUD_PLAYLIST: &'static str = r"api.soundcloud.com/playlists/([a-zA-Z0-9_-]+)";
static SOUNDCLOUD_USER:     &'static str = r"api.soundcloud.com/users/([a-zA-Z0-9_-]+)";

#[derive(Debug)]
pub struct ScraperProduct {
    pub tracks: Vec<Track>,
    pub og_obj: Option<open_graph::Object>,
}

pub fn extract(url: &str) -> Result<ScraperProduct, Error> {
    let client = Client::new();
    let mut res = client.get(url)
        .header(Connection(vec![ConnectionOption::Close]))
        .send()
        .unwrap();
    if res.status.is_success() {
        let dom = parse_document(RcDom::default(), Default::default())
            .from_utf8()
            .read_from(&mut res)
            .unwrap();
        let mut tracks   = Vec::new();
        let mut og_props = Vec::new();
        walk(0, dom.document, &mut tracks, &mut og_props);
        let og_obj = if og_props.len() > 0 {
            Some(open_graph::Object::new(&og_props))
        } else {
            None
        };
        Ok(ScraperProduct {
            tracks: tracks,
            og_obj: og_obj
        })
    } else {
        println!("Failed to get entry html {}: {}", res.status, url);
        Err(Error::NotFound)
    }
}

// This is not proper HTML serialization, of course.
fn walk(indent: usize,
        handle: Handle,
        tracks: &mut Vec<Track>,
        og_props: &mut Vec<(String, String)>) {
    let node = handle.borrow();
    match node.node {
        Document         => (),
        Doctype(_, _, _) => (),
        Text(_)          => (),
        Comment(_)       => (),
        Element(ref name, _, ref attrs) => {
            let tag_name = name.local.as_ref();
            let mut ps = extract_open_graph_metadata_from_tag(tag_name, attrs);
            og_props.append(&mut ps);
            let ts = extract_tracks_from_tag(tag_name, attrs);
            for track in ts.iter().cloned() {
                if !(tracks).iter().any(|t| track == *t) {
                    (*tracks).push(track)
                }
            }
        }
    }
    for child in node.children.iter() {
        walk(indent+4, child.clone(), tracks, og_props);
    }
}

fn attr(attr_name: &str, attrs: &Vec<Attribute>) -> Option<String> {
    for attr in attrs.iter() {
        if attr.name.local.as_ref() == attr_name {
            return Some(attr.value.to_string())
        }
    }
    None
}

pub fn extract_tracks_from_tag(tag_name: &str,
                               attrs: &Vec<Attribute>) -> Vec<Track> {
    if tag_name == "iframe" {
        match attr("src", attrs) {
            Some(ref src) => extract_tracks_from_url(src.to_string()),
            None => vec![]
        }
    } else if tag_name == "a" || tag_name == "link" {
        match attr("href", attrs) {
            Some(ref href) => extract_tracks_from_url(href.to_string()),
            None => vec![]
        }
    } else {
        vec![]
    }
}

pub fn extract_open_graph_metadata_from_tag(tag_name: &str,
                                            attrs: &Vec<Attribute>) -> Vec<(String, String)> {

    let mut og_props = vec!();
    if tag_name == "meta" {
        match extract_open_graph_prop("property", attrs) {
            Some((key, content)) => og_props.push((key, content)),
            None                 => (),
        }
        match extract_open_graph_prop("name", attrs) {
            Some((key, content)) => og_props.push((key, content)),
            None                 => (),
        }
    }
    og_props
}

fn extract_open_graph_prop<'a>(attr_name: &str, attrs: &Vec<Attribute>) -> Option<(String, String)> {
    attr(attr_name, attrs)
        .and_then(|property|
                  if property.starts_with("og:") {
                      let end = property.chars().count();
                      let key = unsafe {
                          property.slice_unchecked(3, end)
                      }.to_string();
                      attr("content", attrs).map(|content| (key, content))
                  } else {
                      None
                  })
}

fn extract_identifier(value: &str, regex_str: &str) -> Option<String> {
    match Regex::new(regex_str) {
        Ok(re) => match re.captures(value) {
            Some(cap) => {
                let strs: Vec<&str> = cap[1].split('?').collect();
                return Some(strs[0].to_string())
            },
            None => None
        },
        Err(_) => None
    }
}

fn extract_tracks_from_url(url: String) -> Vec<Track> {
    let decoded = percent_decode(url.as_bytes()).decode_utf8_lossy().into_owned();
    match extract_identifier(&decoded, YOUTUBE_WATCH) {
        Some(identifier) => {
            return vec![Track::new(Provider::YouTube, identifier)]
        },
        None => ()
    }
    match extract_identifier(&decoded, YOUTUBE_LIST) {
        Some(identifier) => {
            return match youtube::fetch_playlist(&identifier) {
                Ok(res) => res.items.iter()
                                    .map(|ref i| Track::from_yt_playlist_item(i))
                                    .collect::<Vec<_>>(),
                Err(_)       => vec![]
            }
        },
        None => ()
    }
    match extract_identifier(&decoded, YOUTUBE_EMBED) {
        Some(identifier) => {
            return vec![Track::new(Provider::YouTube, identifier)]
        },
        None => ()
    }
    match extract_identifier(&decoded, SOUNDCLOUD_TRACK) {
        Some(identifier) => {
            return vec![Track::new(Provider::SoundCloud, identifier)]
        },
        None => ()
    }
    match extract_identifier(&decoded, SOUNDCLOUD_PLAYLIST) {
        Some(identifier) => {
            return match soundcloud::fetch_playlist(&identifier) {
                Ok(playlist) => playlist.tracks
                                        .iter()
                                        .map(|ref t| Track::from_sc_track(t))
                                        .collect::<Vec<_>>(),
                Err(_)       => vec![]
            }
        },
        None => ()
    }
    match extract_identifier(&decoded, SOUNDCLOUD_USER) {
        Some(identifier) => {
            print!("user {}", identifier);
            return match soundcloud::fetch_user_tracks(&identifier) {
                Ok(tracks) => tracks.iter()
                                    .map(|ref t| Track::from_sc_track(t))
                                    .collect::<Vec<_>>(),
                Err(_)     => vec![]
            }
        },
        None => ()
    }
    return vec![]
}

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

    #[test]
    fn test_extract_identifier() {
        let soundcloud_src = "https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/195425494/stream&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false&visual=true";
        match extract_identifier(soundcloud_src, super::SOUNDCLOUD_TRACK) {
            Some(identifier) => assert_eq!(identifier, "195425494".to_string()),
            None             => assert!(false)
        }
        let youtube_embed = "https://www.youtube.com/embed/X8tOngmlES0?rel=0";
        match extract_identifier(youtube_embed, super::YOUTUBE_EMBED) {
            Some(identifier) => assert_eq!(identifier, "X8tOngmlES0".to_string()),
            None             => assert!(false)
        }

        let youtube_watch = "https://www.youtube.com/watch?v=oDuif301F-8";
        match extract_identifier(youtube_watch, super::YOUTUBE_WATCH) {
            Some(identifier) => assert_eq!(identifier, "oDuif301F-8".to_string()),
            None             => assert!(false)
        }

        let youtube_list = "https://www.youtube.com/embed/videoseries?list=PLy8LZ8FM-o0ViuGAF68RAaXkQ8V-3dbTX";
        match extract_identifier(youtube_list, super::YOUTUBE_LIST) {
            Some(identifier) => assert_eq!(identifier, "PLy8LZ8FM-o0ViuGAF68RAaXkQ8V-3dbTX".to_string()),
            None             => assert!(false)
        }
    }
}
<commit_msg>Add test of scraper::extract() for entry that has youtube playlist<commit_after>extern crate html5ever;
extern crate tendril;
extern crate regex;
extern crate hyper;
extern crate string_cache;
extern crate url;

use html5ever::rcdom::{Document, Doctype, Text, Comment, Element};
use html5ever::rcdom::{RcDom, Handle};
use html5ever::{parse_document, Attribute};
use tendril::stream::TendrilSink;
use std::default::Default;

use regex::Regex;
use hyper::Client;
use hyper::header::Connection;
use hyper::header::ConnectionOption;

use Provider;
use Track;
use open_graph;
use soundcloud;
use youtube;
use error::Error;

use url::percent_encoding::{percent_decode};

static YOUTUBE_EMBED:       &'static str = r"www.youtube.com/embed/([a-zA-Z0-9_-].+)";
static YOUTUBE_LIST:        &'static str = r"www.youtube.com/embed/videoseries\?list=([a-zA-Z0-9_-]+)";
static YOUTUBE_WATCH:       &'static str = r"www.youtube.com/watch\?v=([a-zA-Z0-9_-]+)";
static SOUNDCLOUD_TRACK:    &'static str = r"api.soundcloud.com/tracks/([a-zA-Z0-9_-]+)";
static SOUNDCLOUD_PLAYLIST: &'static str = r"api.soundcloud.com/playlists/([a-zA-Z0-9_-]+)";
static SOUNDCLOUD_USER:     &'static str = r"api.soundcloud.com/users/([a-zA-Z0-9_-]+)";

#[derive(Debug)]
pub struct ScraperProduct {
    pub tracks: Vec<Track>,
    pub og_obj: Option<open_graph::Object>,
}

pub fn extract(url: &str) -> Result<ScraperProduct, Error> {
    let client = Client::new();
    let mut res = client.get(url)
        .header(Connection(vec![ConnectionOption::Close]))
        .send()
        .unwrap();
    if res.status.is_success() {
        let dom = parse_document(RcDom::default(), Default::default())
            .from_utf8()
            .read_from(&mut res)
            .unwrap();
        let mut tracks   = Vec::new();
        let mut og_props = Vec::new();
        walk(0, dom.document, &mut tracks, &mut og_props);
        let og_obj = if og_props.len() > 0 {
            Some(open_graph::Object::new(&og_props))
        } else {
            None
        };
        Ok(ScraperProduct {
            tracks: tracks,
            og_obj: og_obj
        })
    } else {
        println!("Failed to get entry html {}: {}", res.status, url);
        Err(Error::NotFound)
    }
}

// This is not proper HTML serialization, of course.
fn walk(indent: usize,
        handle: Handle,
        tracks: &mut Vec<Track>,
        og_props: &mut Vec<(String, String)>) {
    let node = handle.borrow();
    match node.node {
        Document         => (),
        Doctype(_, _, _) => (),
        Text(_)          => (),
        Comment(_)       => (),
        Element(ref name, _, ref attrs) => {
            let tag_name = name.local.as_ref();
            let mut ps = extract_open_graph_metadata_from_tag(tag_name, attrs);
            og_props.append(&mut ps);
            let ts = extract_tracks_from_tag(tag_name, attrs);
            for track in ts.iter().cloned() {
                if !(tracks).iter().any(|t| track == *t) {
                    (*tracks).push(track)
                }
            }
        }
    }
    for child in node.children.iter() {
        walk(indent+4, child.clone(), tracks, og_props);
    }
}

fn attr(attr_name: &str, attrs: &Vec<Attribute>) -> Option<String> {
    for attr in attrs.iter() {
        if attr.name.local.as_ref() == attr_name {
            return Some(attr.value.to_string())
        }
    }
    None
}

pub fn extract_tracks_from_tag(tag_name: &str,
                               attrs: &Vec<Attribute>) -> Vec<Track> {
    if tag_name == "iframe" {
        match attr("src", attrs) {
            Some(ref src) => extract_tracks_from_url(src.to_string()),
            None => vec![]
        }
    } else if tag_name == "a" || tag_name == "link" {
        match attr("href", attrs) {
            Some(ref href) => extract_tracks_from_url(href.to_string()),
            None => vec![]
        }
    } else {
        vec![]
    }
}

pub fn extract_open_graph_metadata_from_tag(tag_name: &str,
                                            attrs: &Vec<Attribute>) -> Vec<(String, String)> {

    let mut og_props = vec!();
    if tag_name == "meta" {
        match extract_open_graph_prop("property", attrs) {
            Some((key, content)) => og_props.push((key, content)),
            None                 => (),
        }
        match extract_open_graph_prop("name", attrs) {
            Some((key, content)) => og_props.push((key, content)),
            None                 => (),
        }
    }
    og_props
}

fn extract_open_graph_prop<'a>(attr_name: &str, attrs: &Vec<Attribute>) -> Option<(String, String)> {
    attr(attr_name, attrs)
        .and_then(|property|
                  if property.starts_with("og:") {
                      let end = property.chars().count();
                      let key = unsafe {
                          property.slice_unchecked(3, end)
                      }.to_string();
                      attr("content", attrs).map(|content| (key, content))
                  } else {
                      None
                  })
}

fn extract_identifier(value: &str, regex_str: &str) -> Option<String> {
    match Regex::new(regex_str) {
        Ok(re) => match re.captures(value) {
            Some(cap) => {
                let strs: Vec<&str> = cap[1].split('?').collect();
                return Some(strs[0].to_string())
            },
            None => None
        },
        Err(_) => None
    }
}

fn extract_tracks_from_url(url: String) -> Vec<Track> {
    let decoded = percent_decode(url.as_bytes()).decode_utf8_lossy().into_owned();
    match extract_identifier(&decoded, YOUTUBE_WATCH) {
        Some(identifier) => {
            return vec![Track::new(Provider::YouTube, identifier)]
        },
        None => ()
    }
    match extract_identifier(&decoded, YOUTUBE_LIST) {
        Some(identifier) => {
            return match youtube::fetch_playlist(&identifier) {
                Ok(res) => res.items.iter()
                                    .map(|ref i| Track::from_yt_playlist_item(i))
                                    .collect::<Vec<_>>(),
                Err(_)       => vec![]
            }
        },
        None => ()
    }
    match extract_identifier(&decoded, YOUTUBE_EMBED) {
        Some(identifier) => {
            return vec![Track::new(Provider::YouTube, identifier)]
        },
        None => ()
    }
    match extract_identifier(&decoded, SOUNDCLOUD_TRACK) {
        Some(identifier) => {
            return vec![Track::new(Provider::SoundCloud, identifier)]
        },
        None => ()
    }
    match extract_identifier(&decoded, SOUNDCLOUD_PLAYLIST) {
        Some(identifier) => {
            return match soundcloud::fetch_playlist(&identifier) {
                Ok(playlist) => playlist.tracks
                                        .iter()
                                        .map(|ref t| Track::from_sc_track(t))
                                        .collect::<Vec<_>>(),
                Err(_)       => vec![]
            }
        },
        None => ()
    }
    match extract_identifier(&decoded, SOUNDCLOUD_USER) {
        Some(identifier) => {
            print!("user {}", identifier);
            return match soundcloud::fetch_user_tracks(&identifier) {
                Ok(tracks) => tracks.iter()
                                    .map(|ref t| Track::from_sc_track(t))
                                    .collect::<Vec<_>>(),
                Err(_)     => vec![]
            }
        },
        None => ()
    }
    return vec![]
}

#[cfg(test)]
mod test {
    use super::extract;
    use super::extract_identifier;
    use Provider;

    #[test]
    fn test_extract_identifier() {
        let soundcloud_src = "https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/195425494/stream&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false&visual=true";
        match extract_identifier(soundcloud_src, super::SOUNDCLOUD_TRACK) {
            Some(identifier) => assert_eq!(identifier, "195425494".to_string()),
            None             => assert!(false)
        }
        let youtube_embed = "https://www.youtube.com/embed/X8tOngmlES0?rel=0";
        match extract_identifier(youtube_embed, super::YOUTUBE_EMBED) {
            Some(identifier) => assert_eq!(identifier, "X8tOngmlES0".to_string()),
            None             => assert!(false)
        }

        let youtube_watch = "https://www.youtube.com/watch?v=oDuif301F-8";
        match extract_identifier(youtube_watch, super::YOUTUBE_WATCH) {
            Some(identifier) => assert_eq!(identifier, "oDuif301F-8".to_string()),
            None             => assert!(false)
        }

        let youtube_list = "https://www.youtube.com/embed/videoseries?list=PLy8LZ8FM-o0ViuGAF68RAaXkQ8V-3dbTX";
        match extract_identifier(youtube_list, super::YOUTUBE_LIST) {
            Some(identifier) => assert_eq!(identifier, "PLy8LZ8FM-o0ViuGAF68RAaXkQ8V-3dbTX".to_string()),
            None             => assert!(false)
        }
    }
    #[test]
    fn test_extract() {
        let url = "http://spincoaster.com/spincoaster-breakout-2017";
        let product = extract(url).unwrap();
        for track in product.tracks.iter() {
            assert_eq!(track.provider, Provider::YouTube)
        }
        assert_eq!(product.tracks.len(), 15);
        assert!(true);
    }
}
<|endoftext|>


<commit_before>//! TODO
use std::cell::RefCell;
use std::f64;

#[derive(Clone, Debug)]
struct Centroid {
    sum: f64,
    count: f64,
}

impl Centroid {
    fn fuse(&self, other: &Centroid) -> Self {
        Self {
            count: self.count + other.count,
            sum: self.sum + other.sum,
        }
    }

    fn mean(&self) -> f64 {
        self.sum / self.count
    }
}

fn k1(q: f64, delta: f64) -> f64 {
    delta / (2. * f64::consts::PI) * (2. * q - 1.).asin()
}

fn k1_inv(k: f64, delta: f64) -> f64 {
    ((k * 2. * f64::consts::PI / delta).sin() + 1.) / 2.
}

/// Inner data structure for tdigest to enable inner mutablity.
#[derive(Debug)]
struct TDigestInner {
    centroids: Vec<Centroid>,
    compression_factor: f64,
    backlog: Vec<Centroid>,
    max_backlog_size: usize,
}

impl TDigestInner {
    fn new(compression_factor: f64, max_backlog_size: usize) -> Self {
        Self {
            centroids: vec![],
            compression_factor,
            backlog: vec![],
            max_backlog_size,
        }
    }

    fn is_empty(&self) -> bool {
        self.centroids.is_empty() && self.backlog.is_empty()
    }

    fn insert_weighted(&mut self, x: f64, w: f64) {
        self.backlog.push(Centroid { count: w, sum: x });

        if self.backlog.len() > self.max_backlog_size {
            self.compress();
        }
    }

    fn compress(&mut self) {
        // early return in case nothing to be done
        if self.backlog.is_empty() {
            return;
        }

        // TODO: use sort_by_cached_key once stable
        let mut x: Vec<(f64, Centroid)> = self
            .centroids
            .drain(..)
            .chain(self.backlog.drain(..))
            .map(|c| (c.mean(), c))
            .collect();
        x.sort_by(|t1, t2| t1.0.partial_cmp(&t2.0).unwrap());
        let mut x: Vec<Centroid> = x.drain(..).map(|t| t.1).collect();

        let s: f64 = x.iter().map(|c| c.count).sum();

        let mut q_0 = 0.;
        let mut q_limit = k1_inv(
            k1(q_0, self.compression_factor) + 1.,
            self.compression_factor,
        );

        let mut result = vec![];
        let mut current = x[0].clone();
        for next in x.drain(1..) {
            let q = q_0 + (current.count + next.count) / s;
            if q <= q_limit {
                current = current.fuse(&next);
            } else {
                q_0 += current.count / s;
                q_limit = k1_inv(
                    k1(q_0, self.compression_factor) + 1.,
                    self.compression_factor,
                );
                result.push(current);
                current = next;
            }
        }
        result.push(current);

        self.centroids = result;
    }

    fn quantile(&self, q: f64) -> f64 {
        // empty case
        if self.centroids.is_empty() {
            return f64::NAN;
        }

        let s: f64 = self.centroids.iter().map(|c| c.count).sum();
        let limit = s * q;

        // left tail?
        let c_first = &self.centroids[0];
        if limit < c_first.count {
            // TODO: interpolate w/ min
            return c_first.mean();
        }

        let mut cum = 0.;
        let mut idx = self.centroids.len() - 1;
        for (i, c) in self.centroids.iter().enumerate() {
            cum += c.count;
            if cum >= limit {
                idx = i;
                break;
            }
        }

        // right tail?
        if idx == self.centroids.len() - 1 {
            let c_last = &self.centroids[self.centroids.len() - 1];
            // TODO: interpolate w/ max
            return c_last.mean();
        }

        // default case
        let c_a = &self.centroids[idx];
        let c_b = &self.centroids[idx + 1];
        let t = (limit - cum) / c_b.count;
        t * c_b.mean() + (1. - t) * c_a.mean()
    }
}

/// TODO
///
/// https://github.com/tdunning/t-digest/blob/master/docs/t-digest-paper/histo.pdf
#[derive(Debug)]
pub struct TDigest {
    inner: RefCell<TDigestInner>,
}

impl TDigest {
    /// TODO
    pub fn new(compression_factor: f64, max_backlog_size: usize) -> Self {
        // TODO assert compression_factor

        Self {
            inner: RefCell::new(TDigestInner::new(compression_factor, max_backlog_size)),
        }
    }

    /// TODO
    pub fn compression_factor(&self) -> f64 {
        self.inner.borrow().compression_factor
    }

    /// TODO
    pub fn max_backlog_size(&self) -> usize {
        self.inner.borrow().max_backlog_size
    }

    /// Check whether the digest has not received any positives weights yet.
    pub fn is_empty(&self) -> bool {
        self.inner.borrow().is_empty()
    }

    /// Get number of centroids tracked by the digest.
    pub fn n_centroids(&self) -> usize {
        // first apply compression
        self.inner.borrow_mut().compress();

        self.inner.borrow().centroids.len()
    }

    /// TODO
    pub fn insert(&mut self, x: f64) {
        self.insert_weighted(x, 1.);
    }

    /// TODO
    pub fn insert_weighted(&mut self, x: f64, w: f64) {
        // TODO: assert w and early exit for 0
        // TODO: check x for non-NaN
        // TODO: check w for non-NaN

        self.inner.borrow_mut().insert_weighted(x, w)
    }

    /// TODO
    pub fn quantile(&self, q: f64) -> f64 {
        // TODO: check q

        // apply compression if required
        self.inner.borrow_mut().compress();

        // get quantile on immutable state
        self.inner.borrow().quantile(q)
    }
}

#[cfg(test)]
mod tests {
    use super::{k1, k1_inv, TDigest};
    use rand::distributions::StandardNormal;
    use rand::{Rng, SeedableRng};
    use rand_chacha::ChaChaRng;

    #[test]
    fn k_test1() {
        let delta = 0.5;
        let q = 0.5;

        let k = k1(q, delta);
        assert_eq!(k, 0.);

        let q2 = k1_inv(k, delta);
        assert_eq!(q2, q);
    }

    #[test]
    fn k_test2() {
        let delta = 0.7;
        let q = 0.5;

        let k = k1(q, delta);
        assert_eq!(k, 0.);

        let q2 = k1_inv(k, delta);
        assert_eq!(q2, q);
    }

    #[test]
    fn k_test3() {
        let delta = 0.7;
        let q = 0.7;

        let k = k1(q, delta);
        assert_ne!(k, 0.);

        let q2 = k1_inv(k, delta);
        assert_eq!(q2, q);
    }

    #[test]
    fn new_empty() {
        let compression_factor = 2.;
        let max_backlog_size = 13;
        let digest = TDigest::new(compression_factor, max_backlog_size);

        assert_eq!(digest.compression_factor(), 2.);
        assert_eq!(digest.max_backlog_size(), 13);
        assert_eq!(digest.n_centroids(), 0);
        assert!(digest.is_empty());
        assert!(digest.quantile(0.5).is_nan());
    }

    #[test]
    fn with_normal_distribution() {
        let compression_factor = 100.;
        let max_backlog_size = 10;
        let mut digest = TDigest::new(compression_factor, max_backlog_size);

        let mut rng = ChaChaRng::from_seed([0; 32]);

        let n = 100_000;
        for _ in 0..n {
            let x = rng.sample(StandardNormal);
            digest.insert(x);
        }

        // compression works
        assert!(digest.n_centroids() < 100);

        // test some known quantiles
        assert!((-1.2816 - digest.quantile(0.10)).abs() < 0.05);
        assert!((-0.6745 - digest.quantile(0.25)).abs() < 0.05);
        assert!((0.0000 - digest.quantile(0.5)).abs() < 0.05);
        assert!((0.6745 - digest.quantile(0.75)).abs() < 0.05);
        assert!((1.2816 - digest.quantile(0.90)).abs() < 0.05);
    }
}
<commit_msg>tdigest: add many asserts<commit_after>//! TODO
use std::cell::RefCell;
use std::f64;

#[derive(Clone, Debug)]
struct Centroid {
    sum: f64,
    count: f64,
}

impl Centroid {
    fn fuse(&self, other: &Centroid) -> Self {
        Self {
            count: self.count + other.count,
            sum: self.sum + other.sum,
        }
    }

    fn mean(&self) -> f64 {
        self.sum / self.count
    }
}

fn k1(q: f64, delta: f64) -> f64 {
    delta / (2. * f64::consts::PI) * (2. * q - 1.).asin()
}

fn k1_inv(k: f64, delta: f64) -> f64 {
    ((k * 2. * f64::consts::PI / delta).sin() + 1.) / 2.
}

/// Inner data structure for tdigest to enable inner mutablity.
#[derive(Debug)]
struct TDigestInner {
    centroids: Vec<Centroid>,
    compression_factor: f64,
    backlog: Vec<Centroid>,
    max_backlog_size: usize,
}

impl TDigestInner {
    fn new(compression_factor: f64, max_backlog_size: usize) -> Self {
        Self {
            centroids: vec![],
            compression_factor,
            backlog: vec![],
            max_backlog_size,
        }
    }

    fn is_empty(&self) -> bool {
        self.centroids.is_empty() && self.backlog.is_empty()
    }

    fn insert_weighted(&mut self, x: f64, w: f64) {
        self.backlog.push(Centroid { count: w, sum: x });

        if self.backlog.len() > self.max_backlog_size {
            self.compress();
        }
    }

    fn compress(&mut self) {
        // early return in case nothing to be done
        if self.backlog.is_empty() {
            return;
        }

        // TODO: use sort_by_cached_key once stable
        let mut x: Vec<(f64, Centroid)> = self
            .centroids
            .drain(..)
            .chain(self.backlog.drain(..))
            .map(|c| (c.mean(), c))
            .collect();
        x.sort_by(|t1, t2| t1.0.partial_cmp(&t2.0).unwrap());
        let mut x: Vec<Centroid> = x.drain(..).map(|t| t.1).collect();

        let s: f64 = x.iter().map(|c| c.count).sum();

        let mut q_0 = 0.;
        let mut q_limit = k1_inv(
            k1(q_0, self.compression_factor) + 1.,
            self.compression_factor,
        );

        let mut result = vec![];
        let mut current = x[0].clone();
        for next in x.drain(1..) {
            let q = q_0 + (current.count + next.count) / s;
            if q <= q_limit {
                current = current.fuse(&next);
            } else {
                q_0 += current.count / s;
                q_limit = k1_inv(
                    k1(q_0, self.compression_factor) + 1.,
                    self.compression_factor,
                );
                result.push(current);
                current = next;
            }
        }
        result.push(current);

        self.centroids = result;
    }

    fn quantile(&self, q: f64) -> f64 {
        // empty case
        if self.centroids.is_empty() {
            return f64::NAN;
        }

        let s: f64 = self.centroids.iter().map(|c| c.count).sum();
        let limit = s * q;

        // left tail?
        let c_first = &self.centroids[0];
        if limit < c_first.count {
            // TODO: interpolate w/ min
            return c_first.mean();
        }

        let mut cum = 0.;
        let mut idx = self.centroids.len() - 1;
        for (i, c) in self.centroids.iter().enumerate() {
            cum += c.count;
            if cum >= limit {
                idx = i;
                break;
            }
        }

        // right tail?
        if idx == self.centroids.len() - 1 {
            let c_last = &self.centroids[self.centroids.len() - 1];
            // TODO: interpolate w/ max
            return c_last.mean();
        }

        // default case
        let c_a = &self.centroids[idx];
        let c_b = &self.centroids[idx + 1];
        let t = (limit - cum) / c_b.count;
        t * c_b.mean() + (1. - t) * c_a.mean()
    }
}

/// TODO
///
/// https://github.com/tdunning/t-digest/blob/master/docs/t-digest-paper/histo.pdf
#[derive(Debug)]
pub struct TDigest {
    inner: RefCell<TDigestInner>,
}

impl TDigest {
    /// TODO
    pub fn new(compression_factor: f64, max_backlog_size: usize) -> Self {
        assert!(
            (compression_factor > 1.) && compression_factor.is_finite(),
            "compression_factor ({}) must be greater than 1 and finite",
            compression_factor
        );

        Self {
            inner: RefCell::new(TDigestInner::new(compression_factor, max_backlog_size)),
        }
    }

    /// TODO
    pub fn compression_factor(&self) -> f64 {
        self.inner.borrow().compression_factor
    }

    /// TODO
    pub fn max_backlog_size(&self) -> usize {
        self.inner.borrow().max_backlog_size
    }

    /// Check whether the digest has not received any positives weights yet.
    pub fn is_empty(&self) -> bool {
        self.inner.borrow().is_empty()
    }

    /// Get number of centroids tracked by the digest.
    pub fn n_centroids(&self) -> usize {
        // first apply compression
        self.inner.borrow_mut().compress();

        self.inner.borrow().centroids.len()
    }

    /// TODO
    pub fn insert(&mut self, x: f64) {
        self.insert_weighted(x, 1.);
    }

    /// TODO
    pub fn insert_weighted(&mut self, x: f64, w: f64) {
        assert!(x.is_finite(), "x ({}) must be finite", x);
        assert!(
            (w >= 0.) && w.is_finite(),
            "w ({}) must be greater or equal than zero and finite",
            w
        );

        // early return for zero-weight
        if w == 0. {
            return;
        }

        self.inner.borrow_mut().insert_weighted(x, w)
    }

    /// TODO
    pub fn quantile(&self, q: f64) -> f64 {
        assert!((q >= 0.) && (q <= 1.), "q ({}) must be in [0, 1]", q);

        // apply compression if required
        self.inner.borrow_mut().compress();

        // get quantile on immutable state
        self.inner.borrow().quantile(q)
    }
}

#[cfg(test)]
mod tests {
    use super::{k1, k1_inv, TDigest};
    use rand::distributions::StandardNormal;
    use rand::{Rng, SeedableRng};
    use rand_chacha::ChaChaRng;
    use std::f64;

    #[test]
    fn k_test1() {
        let delta = 0.5;
        let q = 0.5;

        let k = k1(q, delta);
        assert_eq!(k, 0.);

        let q2 = k1_inv(k, delta);
        assert_eq!(q2, q);
    }

    #[test]
    fn k_test2() {
        let delta = 0.7;
        let q = 0.5;

        let k = k1(q, delta);
        assert_eq!(k, 0.);

        let q2 = k1_inv(k, delta);
        assert_eq!(q2, q);
    }

    #[test]
    fn k_test3() {
        let delta = 0.7;
        let q = 0.7;

        let k = k1(q, delta);
        assert_ne!(k, 0.);

        let q2 = k1_inv(k, delta);
        assert_eq!(q2, q);
    }

    #[test]
    fn new_empty() {
        let compression_factor = 2.;
        let max_backlog_size = 13;
        let digest = TDigest::new(compression_factor, max_backlog_size);

        assert_eq!(digest.compression_factor(), 2.);
        assert_eq!(digest.max_backlog_size(), 13);
        assert_eq!(digest.n_centroids(), 0);
        assert!(digest.is_empty());
        assert!(digest.quantile(0.5).is_nan());
    }

    #[test]
    #[should_panic(expected = "compression_factor (1) must be greater than 1 and finite")]
    fn new_panics_compression_factor_a() {
        let compression_factor = 1.;
        let max_backlog_size = 13;
        TDigest::new(compression_factor, max_backlog_size);
    }

    #[test]
    #[should_panic(expected = "compression_factor (inf) must be greater than 1 and finite")]
    fn new_panics_compression_factor_b() {
        let compression_factor = f64::INFINITY;
        let max_backlog_size = 13;
        TDigest::new(compression_factor, max_backlog_size);
    }

    #[test]
    fn with_normal_distribution() {
        let compression_factor = 100.;
        let max_backlog_size = 10;
        let mut digest = TDigest::new(compression_factor, max_backlog_size);

        let mut rng = ChaChaRng::from_seed([0; 32]);

        let n = 100_000;
        for _ in 0..n {
            let x = rng.sample(StandardNormal);
            digest.insert(x);
        }

        // compression works
        assert!(digest.n_centroids() < 100);

        // test some known quantiles
        assert!((-1.2816 - digest.quantile(0.10)).abs() < 0.05);
        assert!((-0.6745 - digest.quantile(0.25)).abs() < 0.05);
        assert!((0.0000 - digest.quantile(0.5)).abs() < 0.05);
        assert!((0.6745 - digest.quantile(0.75)).abs() < 0.05);
        assert!((1.2816 - digest.quantile(0.90)).abs() < 0.05);
    }

    #[test]
    fn zero_weight() {
        let compression_factor = 2.;
        let max_backlog_size = 13;
        let mut digest = TDigest::new(compression_factor, max_backlog_size);

        digest.insert_weighted(13.37, 0.);

        assert_eq!(digest.compression_factor(), 2.);
        assert_eq!(digest.max_backlog_size(), 13);
        assert_eq!(digest.n_centroids(), 0);
        assert!(digest.is_empty());
        assert!(digest.quantile(0.5).is_nan());
    }

    #[test]
    #[should_panic(expected = "q (-1) must be in [0, 1]")]
    fn invalid_q_a() {
        let compression_factor = 2.;
        let max_backlog_size = 13;
        let digest = TDigest::new(compression_factor, max_backlog_size);
        digest.quantile(-1.);
    }

    #[test]
    #[should_panic(expected = "q (2) must be in [0, 1]")]
    fn invalid_q_b() {
        let compression_factor = 2.;
        let max_backlog_size = 13;
        let digest = TDigest::new(compression_factor, max_backlog_size);
        digest.quantile(2.);
    }

    #[test]
    #[should_panic(expected = "q (NaN) must be in [0, 1]")]
    fn invalid_q_c() {
        let compression_factor = 2.;
        let max_backlog_size = 13;
        let digest = TDigest::new(compression_factor, max_backlog_size);
        digest.quantile(f64::NAN);
    }

    #[test]
    #[should_panic(expected = "q (inf) must be in [0, 1]")]
    fn invalid_q_d() {
        let compression_factor = 2.;
        let max_backlog_size = 13;
        let digest = TDigest::new(compression_factor, max_backlog_size);
        digest.quantile(f64::INFINITY);
    }

    #[test]
    #[should_panic(expected = "w (-1) must be greater or equal than zero and finite")]
    fn invalid_w_a() {
        let compression_factor = 2.;
        let max_backlog_size = 13;
        let mut digest = TDigest::new(compression_factor, max_backlog_size);
        digest.insert_weighted(13.37, -1.);
    }

    #[test]
    #[should_panic(expected = "w (inf) must be greater or equal than zero and finite")]
    fn invalid_w_b() {
        let compression_factor = 2.;
        let max_backlog_size = 13;
        let mut digest = TDigest::new(compression_factor, max_backlog_size);
        digest.insert_weighted(13.37, f64::INFINITY);
    }

    #[test]
    #[should_panic(expected = "x (-inf) must be finite")]
    fn invalid_x_a() {
        let compression_factor = 2.;
        let max_backlog_size = 13;
        let mut digest = TDigest::new(compression_factor, max_backlog_size);
        digest.insert(f64::NEG_INFINITY);
    }

    #[test]
    #[should_panic(expected = "x (inf) must be finite")]
    fn invalid_x_b() {
        let compression_factor = 2.;
        let max_backlog_size = 13;
        let mut digest = TDigest::new(compression_factor, max_backlog_size);
        digest.insert(f64::INFINITY);
    }

    #[test]
    #[should_panic(expected = "x (NaN) must be finite")]
    fn invalid_x_c() {
        let compression_factor = 2.;
        let max_backlog_size = 13;
        let mut digest = TDigest::new(compression_factor, max_backlog_size);
        digest.insert(f64::NAN);
    }
}
<|endoftext|>


<commit_before>use luminance::{Dim2, Flat, Sampler};
use luminance_gl::gl33::Texture;
use image::{self, ImageResult};
#[cfg(feature = "hot-texture")]
use notify::RecommendedWatcher;
#[cfg(feature = "hot-texture")]
use std::collections::BTreeMap;
use std::ops::Deref;
use std::path::Path;
#[cfg(feature = "hot-texture")]
use std::path::PathBuf;
#[cfg(feature = "hot-texture")]
use std::sync::mpsc;
#[cfg(feature = "hot-texture")]
use std::sync::{Arc, Mutex};

pub use luminance::RGBA32F;

pub type TextureImage<F> = Texture<Flat, Dim2, F>;

/// Load an RGBA texture from an image at a path.
pub fn load_rgba_texture<P>(path: P, sampler: &Sampler, linear: bool) -> ImageResult<TextureImage<RGBA32F>> where P: AsRef<Path> {
  let image = try!(image::open(path)).to_rgba();
  let dim = image.dimensions();
  let raw: Vec<f32> = image.into_raw().into_iter().map(|x| {
    let y = x as f32 / 255.;

    if linear {
      y
    } else {
      y.powf(1. / 2.2)
    }
  }).collect();

  let tex = Texture::new(dim, 0, sampler);
  tex.upload_raw(false, &raw);

  Ok(tex)
}

/// Save an RGBA image on disk.
pub fn save_rgba_texture<P>(texture: &TextureImage<RGBA32F>, path: P) where P: AsRef<Path> {
  let texels = texture.get_raw_texels();
  let (w, h) = texture.size;
  let mut output = Vec::with_capacity((w * h) as usize);

  for texel in &texels {
    output.push((texel * 255.) as u8);
  }

  let _ = image::save_buffer(path, &output, w, h, image::ColorType::RGBA(8));
}

#[cfg(feature = "hot-texture")]
pub struct TextureImageBuilder {
  watcher: RecommendedWatcher,
  receivers: Arc<Mutex<BTreeMap<PathBuf, mpsc::Sender<()>>>>
}
#[cfg(not(feature = "hot-texture"))]
pub struct TextureImageBuilder {}

#[cfg(feature = "hot-texture")]
pub struct WrappedTextureImage {
  rx: mpsc::Receiver<()>,
  texture: TextureImage<RGBA32F>,
  sampler: Sampler,
  linear: bool,
  path: PathBuf
}
#[cfg(not(feature = "hot-texture"))]
pub struct WrappedTextureImage {
  texture: TextureImage<RGBA32F>
}

impl WrappedTextureImage {
  #[cfg(feature = "hot-texture")]
  fn reload(&mut self) {
    let texture = load_rgba_texture(self.path.as_path(), &self.sampler, self.linear);

    match texture {
      Ok(texture) => {
        self.texture = texture;
      },
      Err(err) => {
        err!("reloading texture has failed: {:?}", err);
      }
    }
  }

  #[cfg(feature = "hot-texture")]
  pub fn sync(&mut self) {
    if self.rx.try_recv().is_ok() {
      self.reload();
    }
  }
  #[cfg(not(feature = "hot-texture"))]
  pub fn sync(&mut self) {}
}

impl Deref for WrappedTextureImage {
  type Target = TextureImage<RGBA32F>;

  fn deref(&self) -> &Self::Target {
    &self.texture
  }
}
<commit_msg>Hot/cold textures! (#3)<commit_after>use luminance::{Dim2, Flat, Sampler};
use luminance_gl::gl33::Texture;
use image::{self, ImageResult};
use std::path::Path;

pub use luminance::RGBA32F;

/// Load an RGBA texture from an image at a path.
pub fn load_rgba_texture<P>(path: P, sampler: &Sampler, linear: bool) -> ImageResult<Texture<Flat, Dim2, RGBA32F>> where P: AsRef<Path> {
  let image = try!(image::open(path)).to_rgba();
  let dim = image.dimensions();
  let raw: Vec<f32> = image.into_raw().into_iter().map(|x| {
    let y = x as f32 / 255.;

    if linear {
      y
    } else {
      y.powf(1. / 2.2)
    }
  }).collect();

  let tex = Texture::new(dim, 0, sampler);
  tex.upload_raw(false, &raw);

  Ok(tex)
}

/// Save an RGBA image on disk.
pub fn save_rgba_texture<P>(texture: &Texture<Flat, Dim2, RGBA32F>, path: P) where P: AsRef<Path> {
  let texels = texture.get_raw_texels();
  let (w, h) = texture.size;
  let mut output = Vec::with_capacity((w * h) as usize);

  for texel in &texels {
    output.push((texel * 255.) as u8);
  }

  let _ = image::save_buffer(path, &output, w, h, image::ColorType::RGBA(8));
}

#[derive(Clone, Debug)]
pub enum TextureError {
  LoadingFailed(String)
}

#[cfg(feature = "hot-resource")]
mod hot {
  use super::*;

  use luminance::{Dim2, Flat, Sampler};
  use luminance_gl::gl33::Texture;
  use resource::ResourceManager;
  use std::ops::Deref;
  use std::path::{Path, PathBuf};
  use std::sync::mpsc;

  pub struct TextureImage {
    rx: mpsc::Receiver<()>,
    texture: Texture<Flat, Dim2, RGBA32F>,
    sampler: Sampler,
    linear: bool,
    path: PathBuf
  }

  impl TextureImage {
    pub fn load<P>(manager: &mut ResourceManager, path: P, sampler: &Sampler, linear: bool) -> Result<Self, TextureError> where P: AsRef<Path> {
      let path = path.as_ref();

      let tex = load_rgba_texture(path, sampler, linear);

      match tex {
        Ok(tex) => {
          // monitor that texture
          let (sx, rx) = mpsc::channel();

          manager.monitor(path, sx);

          Ok(TextureImage {
            rx: rx,
            texture: tex,
            sampler: *sampler,
            linear: linear,
            path: path.to_path_buf()
          })
        },
        Err(e) => {
          Err(TextureError::LoadingFailed(format!("{:?}", e)))
        }
      }
    }

    fn reload(&mut self) {
      let path = self.path.as_path();
      let tex = load_rgba_texture(path, &self.sampler, self.linear);

      match tex {
        Ok(tex) => {
          self.texture = tex;
          info!("reloaded texture {:?}", path);
        },
        Err(e) => {
          err!("reloading texture {:?} has failed: {:?}", path, e);
        }
      }
    }

    pub fn sync(&mut self) {
      if self.rx.try_recv().is_ok() {
        self.reload()
      }
    }
  }

  impl Deref for TextureImage {
    type Target = Texture<Flat, Dim2, RGBA32F>;

    fn deref(&self) -> &Self::Target {
      &self.texture
    }
  }
}

#[cfg(not(feature = "hot-resource"))]
mod cold {
  pub struct TextureImage(Texture<Flat, Dim2, RGBA32F>);

  impl TextureImage {
    pub fn load<P>(manager: &mut ResourceManager, path: P, sampler: &Sampler, linear: bool) -> Result<Self, TextureError> where P: AsRef<Path> {
      let tex = load_rgba_texture(path, sampler, linear);

      match tex {
        Ok(tex) => {
          Ok(TextureImage(tex))
        },
        Err(e) => {
          Err(TextureError::LoadingFailed(format!("{:?}", e)))
        }
      }
    }

    pub fn sync(&mut self) {}
  }

  impl Deref for TextureImage {
    type Target = Texture<Flat, Dim2, RGBA32F>;

    fn deref(&self) -> &Self::Target {
      &self.0
    }
  }
}

#[cfg(feature = "hot-resource")]
pub use self::hot::*;
#[cfg(not(feature = "hot-resource"))]
pub use self::cold::*;
<|endoftext|>


<commit_before>/// Multiply unsigned 128 bit integers, return upper 128 bits of the result
#[inline]
fn u128_mulhi(x: u128, y: u128) -> u128 {
    let x_lo = x as u64;
    let x_hi = (x >> 64) as u64;
    let y_lo = y as u64;
    let y_hi = (y >> 64) as u64;

    // handle possibility of overflow
    let carry = (x_lo as u128 * y_lo as u128) >> 64;
    let m = x_lo as u128 * y_hi as u128 + carry;
    let high1 = m >> 64;

    let m_lo = m as u64;
    let high2 = x_hi as u128 * y_lo as u128 + m_lo as u128 >> 64;

    x_hi as u128 * y_hi as u128 + high1 + high2
}

/// Divide `n` by 1e19 and return quotient and remainder
///
/// Integer division algorithm is based on the following paper:
///
///   T. Granlund and P. Montgomery, “Division by Invariant Integers Using Multiplication”
///   in Proc. of the SIGPLAN94 Conference on Programming Language Design and
///   Implementation, 1994, pp. 61–72
///
#[inline]
pub fn udivmod_1e19(n: u128) -> (u128, u64) {
    let d = 10_000_000_000_000_000_000_u64; // 10^19

    let quot = if n < 1 << 83 {
        ((n >> 19) as u64 / (d >> 19)) as u128
    } else {
        let factor = 156927543384667019095894735580191660403_u128;
        u128_mulhi(n, factor) >> 62
    };

    let rem = (n - quot * d as u128) as u64;
    debug_assert_eq!(quot, n / d as u128);
    debug_assert_eq!(rem as u128, n % d as u128);

    (quot, rem)
}
<commit_msg>fix: Fix compilation error on 1.0.0<commit_after>/// Multiply unsigned 128 bit integers, return upper 128 bits of the result
#[inline]
fn u128_mulhi(x: u128, y: u128) -> u128 {
    let x_lo = x as u64;
    let x_hi = (x >> 64) as u64;
    let y_lo = y as u64;
    let y_hi = (y >> 64) as u64;

    // handle possibility of overflow
    let carry = (x_lo as u128 * y_lo as u128) >> 64;
    let m = x_lo as u128 * y_hi as u128 + carry;
    let high1 = m >> 64;

    let m_lo = m as u64;
    let high2 = x_hi as u128 * y_lo as u128 + m_lo as u128 >> 64;

    x_hi as u128 * y_hi as u128 + high1 + high2
}

/// Divide `n` by 1e19 and return quotient and remainder
///
/// Integer division algorithm is based on the following paper:
///
///   T. Granlund and P. Montgomery, “Division by Invariant Integers Using Multiplication”
///   in Proc. of the SIGPLAN94 Conference on Programming Language Design and
///   Implementation, 1994, pp. 61–72
///
#[inline]
pub fn udivmod_1e19(n: u128) -> (u128, u64) {
    let d = 10_000_000_000_000_000_000_u64; // 10^19

    let quot = if n < 1 << 83 {
        ((n >> 19) as u64 / (d >> 19)) as u128
    } else {
        // avoid strange compilation error on old compiler
        let factor =
            (8507059173023461586_u64 as u128) << 64 | 10779635027931437427 as u128;
        u128_mulhi(n, factor) >> 62
    };

    let rem = (n - quot * d as u128) as u64;
    debug_assert_eq!(quot, n / d as u128);
    debug_assert_eq!(rem as u128, n % d as u128);

    (quot, rem)
}
<|endoftext|>


<commit_before>use lib::blockchain::*;
use lib::transaction::*;
use lib::consensus::*;
use std::collections::BTreeSet;
use serde_json;
use serde::Serialize;
use url::{Url};
use web::NodeList;

//Responses
#[derive(Serialize)]
struct MineResult {
    message: String,
    index: usize,
    transactions: BTreeSet<Transaction>,
    proof: u64,
    previous_hash: String
}

#[derive(Serialize)]
struct ChainResult<'a> {
    chain: &'a BTreeSet<Block>,
    length: usize
}

#[derive(Serialize)]
struct RegisterNodeResponse {
    message: String,
    total_nodes: usize
}

//tood: to make testable... couldn't create Rocket::State, derived from state crate in tests
pub fn mine(b: &mut Blockchain) -> Result<String, u32> {
    let mined_block = b.mine().expect("todo: map error to 500");
    let response = MineResult {
        message: "New Block Forged".into(),
        index: mined_block.index,
        transactions: mined_block.transactions.clone(),
        proof: mined_block.proof,
        previous_hash: mined_block.previous_hash.clone()
    };

   serialize(&response)
}

pub fn new_transaction(transaction: &Transaction, b: &mut Blockchain) -> Result<String, u32> {
    let index = b.new_transaction(transaction.clone());
    Ok(format!("Transaction added at block {}", index))
}

pub fn chain(b: &mut Blockchain) -> Result<String, u32> {

    let chain = b.chain();
    let response = ChainResult {
        chain: chain,
        length: chain.len()
    };
    serialize(&response)
}

pub fn register_node(node_list: &NodeList, b: &mut Blockchain) -> Result<String, u32> {

    let mut node_urls = Vec::<Url>::with_capacity(node_list.nodes.len());

    //Validate - all or nothing
    for node in &node_list.nodes {
        let parse_result = Url::parse(node);
        if parse_result.is_err() {
            warn!("Failed to parse {} {:?}", node, parse_result.err());
            return Err(400, /* all nodes must be valid */)
        }
        let url = parse_result.expect("validated");
        node_urls.push(url);
    }

    //Add
    for node_url in node_urls {
        b.register_node(node_url);
    }

    let response = RegisterNodeResponse {
        message: String::from("New nodes have been added"),
        total_nodes: b.nodes().len(),
    };

    serialize(&response)
}

pub fn consensus(b: &mut Blockchain) -> Result<String, u32> {

    let replaced = Consensus::resolve_conflicts(b);
    if replaced {
        return Ok(json!({
            "message": "Our chain was replaced",
            "new_chain": b.chain()
        }).to_string());
    }
    else
    {
        return Ok(json!({
            "message": "Our chain is authoritative",
            "chain": b.chain()
        }).to_string());
    }

}

fn serialize<T>(response: &T) -> Result<String, u32> where T: Serialize {
    match serde_json::to_string(&response) {
        Ok(serialized) => Ok(serialized),
        Err(e) => {
            error!("serialize error: {:?}", e);
            return Err(500); //include reason?
        }
    }
}

#[cfg(test)]
mod tests {
    use lib::blockchain::Blockchain;
    use web::api;

    #[test]
    fn mine() {
        let mut blockchain = Blockchain::new_with(1);
        let result = api::mine(&mut blockchain);
        assert!(result.is_ok(), format!("Failed to mine {:?}", result));
        println!("mine response: {}", result.unwrap());
    }
}
<commit_msg>remove panic on mining fail<commit_after>use lib::blockchain::*;
use lib::transaction::*;
use lib::consensus::*;
use std::collections::BTreeSet;
use serde_json;
use serde::Serialize;
use url::{Url};
use web::NodeList;

//Responses
#[derive(Serialize)]
struct MineResult {
    message: String,
    index: usize,
    transactions: BTreeSet<Transaction>,
    proof: u64,
    previous_hash: String
}

#[derive(Serialize)]
struct ChainResult<'a> {
    chain: &'a BTreeSet<Block>,
    length: usize
}

#[derive(Serialize)]
struct RegisterNodeResponse {
    message: String,
    total_nodes: usize
}

pub fn mine(b: &mut Blockchain) -> Result<String, u32> {
    match b.mine() {
        Ok(mined_block) => {
            let response = MineResult {
                message: "New Block Forged".into(),
                index: mined_block.index,
                transactions: mined_block.transactions.clone(),
                proof: mined_block.proof,
                previous_hash: mined_block.previous_hash.clone()
            };
            serialize(&response)
        },
        Err(e) => {
            error!("Failed to mind block. {:?}", e);
            Err(500)
        }
    }
}

pub fn new_transaction(transaction: &Transaction, b: &mut Blockchain) -> Result<String, u32> {
    let index = b.new_transaction(transaction.clone());
    Ok(format!("Transaction added at block {}", index))
}

pub fn chain(b: &mut Blockchain) -> Result<String, u32> {

    let chain = b.chain();
    let response = ChainResult {
        chain: chain,
        length: chain.len()
    };
    serialize(&response)
}

pub fn register_node(node_list: &NodeList, b: &mut Blockchain) -> Result<String, u32> {

    let mut node_urls = Vec::<Url>::with_capacity(node_list.nodes.len());

    //Validate - all or nothing
    for node in &node_list.nodes {
        let parse_result = Url::parse(node);
        if parse_result.is_err() {
            warn!("Failed to parse {} {:?}", node, parse_result.err());
            return Err(400, /* all nodes must be valid */)
        }
        let url = parse_result.expect("validated");
        node_urls.push(url);
    }

    //Add
    for node_url in node_urls {
        b.register_node(node_url);
    }

    let response = RegisterNodeResponse {
        message: String::from("New nodes have been added"),
        total_nodes: b.nodes().len(),
    };

    serialize(&response)
}

pub fn consensus(b: &mut Blockchain) -> Result<String, u32> {

    let replaced = Consensus::resolve_conflicts(b);
    if replaced {
        return Ok(json!({
            "message": "Our chain was replaced",
            "new_chain": b.chain()
        }).to_string());
    }
    else
    {
        return Ok(json!({
            "message": "Our chain is authoritative",
            "chain": b.chain()
        }).to_string());
    }

}

fn serialize<T>(response: &T) -> Result<String, u32> where T: Serialize {
    match serde_json::to_string(&response) {
        Ok(serialized) => Ok(serialized),
        Err(e) => {
            error!("serialize error: {:?}", e);
            return Err(500); //include reason?
        }
    }
}

#[cfg(test)]
mod tests {
    use lib::blockchain::Blockchain;
    use web::api;

    #[test]
    fn mine() {
        let mut blockchain = Blockchain::new_with(1);
        let result = api::mine(&mut blockchain);
        assert!(result.is_ok(), format!("Failed to mine {:?}", result));
        println!("mine response: {}", result.unwrap());
    }
}
<|endoftext|>


<commit_before>use std::collections::BTreeMap;

use anyhow::Result;
use log::debug;

use crate::{
    analysis::{cfg, dis},
    aspace::AddressSpace,
    module::Module,
    RVA, VA,
};

#[derive(Default)]
pub struct CallGraph {
    // call instruction indexes...
    /// map from function start to the addresses that call here.
    /// lookup via `call_instruction_functions` to figure out the functions that
    /// call here.
    pub calls_to: BTreeMap<VA, Vec<VA>>,
    /// map from an instruction to the addresses that it calls (usually one).
    pub calls_from: BTreeMap<VA, Vec<VA>>,

    // instruction membership indexes...
    /// map from function start to the instructions in its CFG that call
    /// elsewhere. lookup via `calls_to` to figoure out the functions that
    /// this function calls.
    pub function_call_instructions: BTreeMap<VA, Vec<VA>>,
    /// map from instruction to starts of functions whose CFGs contain the
    /// instruction (usually one).
    pub call_instruction_functions: BTreeMap<VA, Vec<VA>>,
}

pub fn build_call_graph(module: &Module, cfgs: &BTreeMap<VA, cfg::CFG>) -> Result<CallGraph> {
    debug!("call graph");

    let mut cg: CallGraph = Default::default();
    let decoder = dis::get_disassembler(module)?;

    for (&function, cfg) in cfgs.iter() {
        debug!("call graph: {:#x}", function);

        // ensure there are at least (empty) entries for all the keys in `functions`
        cg.function_call_instructions.entry(function).or_default();
        cg.calls_to.entry(function).or_default();

        for basic_block in cfg.basic_blocks.values() {
            let buf = module
                .address_space
                .read_bytes(basic_block.address, basic_block.length as usize)?;

            for (offset, insn) in dis::linear_disassemble(&decoder, &buf) {
                if let Ok(Some(insn)) = insn {
                    if matches!(insn.mnemonic, zydis::enums::Mnemonic::CALL) {
                        let va = basic_block.address + offset as RVA;
                        for flow in cfg::get_call_insn_flow(module, va, &insn)?.iter() {
                            if let cfg::Flow::Call(target) = *flow {
                                cg.calls_from.entry(va).or_default().push(target);
                                cg.calls_to.entry(target).or_default().push(va);
                                cg.function_call_instructions.entry(function).or_default().push(va);
                                cg.call_instruction_functions.entry(va).or_default().push(function);
                            }
                        }
                    }
                }
            }
        }
    }

    Ok(cg)
}

#[cfg(test)]
mod tests {
    use crate::{
        analysis::{call_graph, cfg::CFG, pe},
        rsrc::*,
        VA,
    };
    use anyhow::Result;
    use std::collections::BTreeMap;

    #[test]
    fn k32() -> Result<()> {
        let buf = get_buf(Rsrc::K32);
        let pe = crate::loader::pe::PE::from_bytes(&buf)?;

        let mut cfgs: BTreeMap<VA, CFG> = Default::default();
        for &function in pe::find_function_starts(&pe)?.iter() {
            if let Ok(cfg) = crate::analysis::cfg::build_cfg(&pe.module, function) {
                cfgs.insert(function, cfg);
            }
        }

        let cg = call_graph::build_call_graph(&pe.module, &cfgs)?;

        assert_eq!(cg.calls_to[&0x180001068].len(), 2);
        assert!(cg.calls_to[&0x180001068].iter().find(|&&v| v == 0x18000F775).is_some());
        assert!(cg.calls_to[&0x180001068].iter().find(|&&v| v == 0x180060504).is_some());
        assert!(cg.calls_from[&0x180060504]
            .iter()
            .find(|&&v| v == 0x180001068)
            .is_some());
        assert!(cg.call_instruction_functions[&0x180060504]
            .iter()
            .find(|&&v| v == 0x1800602C0)
            .is_some());
        assert!(cg.function_call_instructions[&0x1800602C0]
            .iter()
            .find(|&&v| v == 0x180060504)
            .is_some());

        Ok(())
    }
}
<commit_msg>analysis: call graph: use vec over smallvec<commit_after>use std::collections::BTreeMap;

use anyhow::Result;
use log::debug;

use crate::{
    analysis::{cfg, dis},
    aspace::AddressSpace,
    module::Module,
    RVA, VA,
};

#[derive(Default)]
pub struct CallGraph {
    // call instruction indexes...
    /// map from function start to the addresses that call here.
    /// lookup via `call_instruction_functions` to figure out the functions that
    /// call here.
    pub calls_to:   BTreeMap<VA, Vec<VA>>,
    /// map from an instruction to the addresses that it calls (usually one).
    pub calls_from: BTreeMap<VA, Vec<VA>>,

    // instruction membership indexes...
    /// map from function start to the instructions in its CFG that call
    /// elsewhere. lookup via `calls_to` to figoure out the functions that
    /// this function calls.
    pub function_call_instructions: BTreeMap<VA, Vec<VA>>,
    /// map from instruction to starts of functions whose CFGs contain the
    /// instruction (usually one).
    pub call_instruction_functions: BTreeMap<VA, Vec<VA>>,
}

pub fn build_call_graph(module: &Module, cfgs: &BTreeMap<VA, cfg::CFG>) -> Result<CallGraph> {
    debug!("call graph");

    let mut cg: CallGraph = Default::default();
    let decoder = dis::get_disassembler(module)?;

    for (&function, cfg) in cfgs.iter() {
        debug!("call graph: {:#x}", function);

        // ensure there are at least (empty) entries for all the keys in `functions`
        cg.function_call_instructions.entry(function).or_default();
        cg.calls_to.entry(function).or_default();

        for basic_block in cfg.basic_blocks.values() {
            let buf = module
                .address_space
                .read_bytes(basic_block.address, basic_block.length as usize)?;

            for (offset, insn) in dis::linear_disassemble(&decoder, &buf) {
                if let Ok(Some(insn)) = insn {
                    if matches!(insn.mnemonic, zydis::enums::Mnemonic::CALL) {
                        let va = basic_block.address + offset as RVA;
                        for flow in cfg::get_call_insn_flow(module, va, &insn)?.iter() {
                            if let cfg::Flow::Call(target) = *flow {
                                cg.calls_from.entry(va).or_default().push(target);
                                cg.calls_to.entry(target).or_default().push(va);
                                cg.function_call_instructions.entry(function).or_default().push(va);
                                cg.call_instruction_functions.entry(va).or_default().push(function);
                            }
                        }
                    }
                }
            }
        }
    }

    Ok(cg)
}

#[cfg(test)]
mod tests {
    use crate::{
        analysis::{call_graph, cfg::CFG, pe},
        rsrc::*,
        VA,
    };
    use anyhow::Result;
    use std::collections::BTreeMap;

    #[test]
    fn k32() -> Result<()> {
        let buf = get_buf(Rsrc::K32);
        let pe = crate::loader::pe::PE::from_bytes(&buf)?;

        let mut cfgs: BTreeMap<VA, CFG> = Default::default();
        for &function in pe::find_function_starts(&pe)?.iter() {
            if let Ok(cfg) = crate::analysis::cfg::build_cfg(&pe.module, function) {
                cfgs.insert(function, cfg);
            }
        }

        let cg = call_graph::build_call_graph(&pe.module, &cfgs)?;

        assert_eq!(cg.calls_to[&0x180001068].len(), 2);
        assert!(cg.calls_to[&0x180001068].iter().find(|&&v| v == 0x18000F775).is_some());
        assert!(cg.calls_to[&0x180001068].iter().find(|&&v| v == 0x180060504).is_some());
        assert!(cg.calls_from[&0x180060504]
            .iter()
            .find(|&&v| v == 0x180001068)
            .is_some());
        assert!(cg.call_instruction_functions[&0x180060504]
            .iter()
            .find(|&&v| v == 0x1800602C0)
            .is_some());
        assert!(cg.function_call_instructions[&0x1800602C0]
            .iter()
            .find(|&&v| v == 0x180060504)
            .is_some());

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


<commit_before>use url::Url;
use hyper::{self, Client};
use hyper::method::Method;
use rustc_serialize::json::{self, Json, Object};

use errors::WeChatError;

pub struct WeChatClient {
    pub appid: String,
    pub secret: String,
    pub access_token: String,
}

impl WeChatClient {
    pub fn new(appid: &str, secret: &str) -> WeChatClient {
        WeChatClient {
            appid: appid.to_owned(),
            secret: secret.to_owned(),
            access_token: "".to_owned(),
        }
    }

    pub fn with_access_token(appid: &str, secret: &str, access_token: &str) -> WeChatClient {
        WeChatClient {
            appid: appid.to_owned(),
            secret: secret.to_owned(),
            access_token: access_token.to_owned(),
        }
    }

    pub fn request(&self, method: Method, url: &str, params: Vec<(&str, &str)>, data: &Object) -> Result<Json, WeChatError> {
        let mut querys = params.clone();
        let mut http_url = Url::parse(url).unwrap();
        if !self.access_token.is_empty() {
            querys.push(("access_token", &self.access_token));
        }
        http_url.set_query_from_pairs(querys.into_iter());
        let body = if !data.is_empty() {
            match json::encode(data) {
                Ok(text) => text,
                Err(_) => "".to_owned(),
            }
        } else {
            "".to_owned()
        };
        let mut client = Client::new();
        let req = if method == Method::Post {
            client.post(http_url).body(&body)
        } else {
            client.get(http_url)
        };
        let mut res = match req.send() {
            Ok(_res) => _res,
            Err(_) => { return Err(WeChatError::ClientError { errcode: -1, errmsg: "".to_owned() }); }
        };
        if res.status != hyper::Ok {
            return Err(WeChatError::ClientError { errcode: -2, errmsg: "Request status error".to_owned() })
        }
        let obj = match Json::from_reader(&mut res) {
            Ok(decoded) => { decoded },
            Err(_) => { return Err(WeChatError::ClientError { errcode: -3, errmsg: "Json decode error".to_owned() }); }
        };
        Ok(obj)
    }

    pub fn post(&self, url: &str, params: Vec<(&str, &str)>, data: &Object) -> Result<Json, WeChatError> {
        self.request(Method::Post, url, params, data)
    }

    pub fn get(&self, url: &str, params: Vec<(&str, &str)>) -> Result<Json, WeChatError> {
        self.request(Method::Get, url, params, &Object::new())
    }
}<commit_msg>Use default WeChat API base url if url is not absolute<commit_after>use url::Url;
use hyper::{self, Client};
use hyper::method::Method;
use rustc_serialize::json::{self, Json, Object};

use errors::WeChatError;

pub struct WeChatClient {
    pub appid: String,
    pub secret: String,
    pub access_token: String,
}

impl WeChatClient {
    pub fn new(appid: &str, secret: &str) -> WeChatClient {
        WeChatClient {
            appid: appid.to_owned(),
            secret: secret.to_owned(),
            access_token: "".to_owned(),
        }
    }

    pub fn with_access_token(appid: &str, secret: &str, access_token: &str) -> WeChatClient {
        WeChatClient {
            appid: appid.to_owned(),
            secret: secret.to_owned(),
            access_token: access_token.to_owned(),
        }
    }

    pub fn request(&self, method: Method, url: &str, params: Vec<(&str, &str)>, data: &Object) -> Result<Json, WeChatError> {
        let mut http_url = if !(url.starts_with("http://") || url.starts_with("https://")) {
            let mut url_string = url.to_owned();
            url_string = url_string + "https://api.weixin.qq.com/cgi-bin/";
            Url::parse(&url_string).unwrap()
        } else {
            Url::parse(url).unwrap()
        };
        let mut querys = params.clone();
        if !self.access_token.is_empty() {
            querys.push(("access_token", &self.access_token));
        }
        http_url.set_query_from_pairs(querys.into_iter());
        let body = if !data.is_empty() {
            match json::encode(data) {
                Ok(text) => text,
                Err(_) => "".to_owned(),
            }
        } else {
            "".to_owned()
        };
        let mut client = Client::new();
        let req = if method == Method::Post {
            client.post(http_url).body(&body)
        } else {
            client.get(http_url)
        };
        let mut res = match req.send() {
            Ok(_res) => _res,
            Err(_) => { return Err(WeChatError::ClientError { errcode: -1, errmsg: "".to_owned() }); }
        };
        if res.status != hyper::Ok {
            return Err(WeChatError::ClientError { errcode: -2, errmsg: "Request status error".to_owned() })
        }
        let obj = match Json::from_reader(&mut res) {
            Ok(decoded) => { decoded },
            Err(_) => { return Err(WeChatError::ClientError { errcode: -3, errmsg: "Json decode error".to_owned() }); }
        };
        Ok(obj)
    }

    pub fn post(&self, url: &str, params: Vec<(&str, &str)>, data: &Object) -> Result<Json, WeChatError> {
        self.request(Method::Post, url, params, data)
    }

    pub fn get(&self, url: &str, params: Vec<(&str, &str)>) -> Result<Json, WeChatError> {
        self.request(Method::Get, url, params, &Object::new())
    }

    pub fn fetch_access_token(&mut self) {

    }
}<|endoftext|>