<commit_before>pub mod lines;

#[macro_use]
extern crate nom;

extern crate regex;
extern crate memmap;
extern crate memchr;
extern crate ascii;

use std::path::Path;
use std::cmp;
use std::fmt;

use memmap::{Mmap, Protection};
use memchr::memchr;

// Why is it called partialEq?
#[derive(Debug, PartialEq, Default, Clone)]
pub struct Count {
    pub code: u32,
    pub comment: u32,
    pub blank: u32,
    pub lines: u32,
}

pub struct LangTotal {
    pub files: u32,
    pub count: Count,
}

impl Count {
    pub fn merge(&mut self, o: &Count) {
        self.code += o.code;
        self.comment += o.comment;
        self.blank += o.blank;
        self.lines += o.lines;
    }
}

pub enum LineConfig<'a> {
    Multi {
        single_start: &'a str,
        multi_start: &'a str,
        multi_end: &'a str,
    },
    Single {
        single_start: &'a str,
    },
}

// Do any languages actually use utf8 chars as comment chars?
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)]
pub enum Lang {
    C,
    CCppHeader,
    Rust,
    Ruby,
    Haskell,
    Perl,
    BourneShell,
    Make,
    INI,
    Assembly,
    Yacc,
    Awk,
    XML,
    Unrecognized,
}

impl Lang {
    pub fn to_s(&self) -> &str {
        match *self {
            Lang::C => "C",
            Lang::CCppHeader => "C/C++ Header",
            Lang::Rust => "Rust",
            Lang::Ruby => "Ruby",
            Lang::Haskell => "Haskell",
            Lang::Perl => "Perl",
            Lang::BourneShell => "Bourne Shell",
            Lang::Make => "Make",
            Lang::INI => "INI",
            Lang::Assembly => "Assembly",
            Lang::Yacc => "Yacc",
            Lang::Awk => "Awk",
            Lang::XML => "XMl",
            Lang::Unrecognized => "Unrecognized",
        }
    }
}

impl fmt::Display for Lang {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.pad(self.to_s())
    }
}

pub fn lang_from_ext(filepath: &str) -> Lang {
    let path = Path::new(filepath);
    let ext = match path.extension() {
        Some(os_str) => os_str.to_str().unwrap().to_lowercase(),
        None => path.file_name().unwrap().to_str().unwrap().to_lowercase(),
    };

    match &*ext {
        "c" => Lang::C,
        "h" | "hh" | "hpp" | "hxx" => Lang::CCppHeader,
        "rs" => Lang::Rust,
        "hs" => Lang::Haskell,
        "pl" => Lang::Perl,
        "rb" => Lang::Ruby,
        "makefile" | "mk" => Lang::Make,
        "ini" => Lang::INI,
        "s" | "asm" => Lang::Assembly,
        "y" => Lang::Yacc,
        "awk" => Lang::Awk,
        "xml" => Lang::XML,

        // TODO(cgag): What's the correct extension? Any? Pragma?
        "sh" => Lang::BourneShell,
        // Probably dumb to just default to C.
        _ => Lang::Unrecognized,
    }
}

pub fn counter_config_for_lang<'a>(lang: &Lang) -> LineConfig<'a> {
    enum CT<'a> {
        Single(&'a str),
        Multi(&'a str, &'a str, &'a str),
    }
    // use self::ConfigTuple::*;

    let c_style = CT::Multi("//", "/*", "*/");
    let sh_style = CT::Single("#");

    let ctuple = match *lang {
        Lang::Haskell => CT::Multi("--", "{-", "-}"),
        Lang::Perl => CT::Multi("#", "=pod", "=cut"),
        Lang::INI => CT::Single(";"),
        // TODO(cgag): Well, some architectures use ;, @, |, etc.
        // Need a way to specify more than one possible comment char.
        Lang::Assembly => CT::Multi("#", "/*", "*/"),
        // TODO(cgag): Welp, single is not always necessary
        Lang::XML => CT::Multi("GIEBBBLLLlXKJJKJK", "<!--", "-->"),
        Lang::BourneShell | Lang::Ruby | Lang::Make | Lang::Awk => sh_style,
        // TODO(cgag): not 100% that yacc belongs here.
        Lang::C | Lang::CCppHeader | Lang::Rust | Lang::Yacc => c_style,
        // Default to C style
        Lang::Unrecognized => c_style,
    };

    match ctuple {
        CT::Multi(single, start, end) => {
            LineConfig::Multi {
                single_start: single,
                multi_start: start,
                multi_end: end,
            }
        }
        CT::Single(single) => LineConfig::Single { single_start: single },
    }
}

struct AsciiLines<'a> {
    buf: &'a [u8],
    pos: usize,
}

struct Ascii<'a>(&'a [u8]);
impl<'a> Ascii<'a> {
    fn lines(&self) -> AsciiLines {
        AsciiLines {
            buf: self.0,
            pos: 0,
        }
    }
}

// Appears to work, now we just neeed
impl<'a> Iterator for AsciiLines<'a> {
    type Item = &'a [u8];

    fn next(&mut self) -> Option<&'a [u8]> {
        match memchr(b'\n', &self.buf[self.pos..self.buf.len()]) {
            Some(n) => {
                let start = self.pos;
                self.pos = self.pos + n + 1;
                // - 1 to drop \n char
                Some(&self.buf[start..(self.pos - 1)])
            }
            None => {
                if self.pos == self.buf.len() {
                    return None;
                }
                let start = self.pos;
                self.pos = self.buf.len();
                Some(&self.buf[start..self.pos])
            }
        }
    }
}

pub fn count(filepath: &str) -> Count {
    let lang = lang_from_ext(filepath);
    let config = counter_config_for_lang(&lang);
    match config {
        LineConfig::Single { single_start: single } => count_mmap_unsafe_single(filepath, single),
        LineConfig::Multi { single_start: single, multi_start, multi_end } => {
            count_mmap_unsafe_multi(filepath, single, multi_start, multi_end)
        }
    }
}

pub fn count_mmap_unsafe_single(filepath: &str, single_start: &str) -> Count {
    let fmmap = match Mmap::open_path(filepath, Protection::Read) {
        Ok(mmap) => mmap,
        Err(e) => {
            println!("mmap err for {}: {}", filepath, e);
            return Count::default();
        }
    };
    let bytes: &[u8] = unsafe { fmmap.as_slice() };

    let mut lines = 0;
    let mut code = 0;
    let mut comments = 0;
    let mut blanks = 0;

    let a = Ascii(bytes);
    for byte_line in a.lines() {
        let line = match std::str::from_utf8(byte_line) {
            Ok(s) => s,
            Err(_) => return Count::default(),
        };
        lines += 1;

        let trimmed = line.trim_left();
        if trimmed.is_empty() {
            blanks += 1;
        } else if trimmed.starts_with(single_start) {
            comments += 1;
        } else {
            code += 1;
        }
    }

    Count {
        code: code,
        comment: comments,
        blank: blanks,
        lines: lines,
    }
}

pub fn count_mmap_unsafe_multi(filepath: &str,
                               single_start: &str,
                               multi_start: &str,
                               multi_end: &str)
                               -> Count {

    let single_line_start = single_start;
    let multiline_start = multi_start;
    let multiline_end = multi_end;

    let fmmap = match Mmap::open_path(filepath, Protection::Read) {
        Ok(mmap) => mmap,
        Err(e) => {
            println!("mmap err for {}: {}", filepath, e);
            return Count::default();
        }
    };
    let bytes: &[u8] = unsafe { fmmap.as_slice() };

    let mut lines = 0;
    let mut code = 0;
    let mut comments = 0;
    let mut blanks = 0;

    let mut in_comment = false;

    let a = Ascii(bytes);
    for byte_line in a.lines() {
        let line = match std::str::from_utf8(byte_line) {
            Ok(s) => s,
            Err(_) => return Count::default(),
        };
        lines += 1;

        let trimmed = line.trim_left();
        if trimmed.is_empty() {
            blanks += 1;
            continue;
        };

        if !in_comment && trimmed.starts_with(single_line_start) {
            comments += 1;
            continue;
        }

        if !(trimmed.contains(multiline_start) || trimmed.contains(multiline_end)) {
            if in_comment {
                comments += 1;
            } else {
                code += 1;
            }
            continue;
        }

        let start_len = multiline_start.len();
        let end_len = multiline_end.len();

        let mut pos = 0;
        let mut found_code = false;
        'outer: while pos < trimmed.len() {
            // TODO(cgag): must be a less stupid way to do this
            for i in pos..(pos + cmp::max(start_len, end_len) + 1) {
                if !trimmed.is_char_boundary(i) {
                    pos += 1;
                    continue 'outer;
                }
            }

            if pos + start_len <= trimmed.len() &&
               &trimmed[pos..pos + start_len] == multiline_start {
                pos += start_len;
                in_comment = true;
            } else if pos + end_len <= trimmed.len() && &trimmed[pos..pos + end_len] == multiline_end {
                pos += end_len;
                in_comment = false;
            } else if !in_comment {
                found_code = true;
                pos += 1;
            } else {
                pos += 1;
            }
        }

        if found_code {
            code += 1;
        } else {
            comments += 1;
        }
    }

    Count {
        code: code,
        comment: comments,
        blank: blanks,
        lines: lines,
    }
}
<commit_msg>steps towards more comment configurations<commit_after>pub mod lines;

#[macro_use]
extern crate nom;

extern crate regex;
extern crate memmap;
extern crate memchr;
extern crate ascii;

use std::path::Path;
use std::cmp;
use std::fmt;

use memmap::{Mmap, Protection};
use memchr::memchr;

// Why is it called partialEq?
#[derive(Debug, PartialEq, Default, Clone)]
pub struct Count {
    pub code: u32,
    pub comment: u32,
    pub blank: u32,
    pub lines: u32,
}

pub struct LangTotal {
    pub files: u32,
    pub count: Count,
}

impl Count {
    pub fn merge(&mut self, o: &Count) {
        self.code += o.code;
        self.comment += o.comment;
        self.blank += o.blank;
        self.lines += o.lines;
    }
}

pub enum LineConfig<'a> {
    SingleMulti {
        single_start: &'a str,
        multi_start: &'a str,
        multi_end: &'a str,
    },
    SingleOnly {
        single_start: &'a str,
    },
    MultiOnly {
        multi_start: &'a str,
        multi_end: &'a str,
    }, /* Everything {
        *     singles: Vec<&'a str>,
        *     multies: Vec<(&'a str, &'a str)>,
        * }, */
}

// Do any languages actually use utf8 chars as comment chars?
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)]
pub enum Lang {
    C,
    CCppHeader,
    Rust,
    Ruby,
    Haskell,
    Perl,
    BourneShell,
    Make,
    INI,
    Assembly,
    Yacc,
    Awk,
    XML,
    Unrecognized,
}

impl Lang {
    pub fn to_s(&self) -> &str {
        match *self {
            Lang::C => "C",
            Lang::CCppHeader => "C/C++ Header",
            Lang::Rust => "Rust",
            Lang::Ruby => "Ruby",
            Lang::Haskell => "Haskell",
            Lang::Perl => "Perl",
            Lang::BourneShell => "Bourne Shell",
            Lang::Make => "Make",
            Lang::INI => "INI",
            Lang::Assembly => "Assembly",
            Lang::Yacc => "Yacc",
            Lang::Awk => "Awk",
            Lang::XML => "XMl",
            Lang::Unrecognized => "Unrecognized",
        }
    }
}

impl fmt::Display for Lang {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.pad(self.to_s())
    }
}

pub fn lang_from_ext(filepath: &str) -> Lang {
    let path = Path::new(filepath);
    let ext = match path.extension() {
        Some(os_str) => os_str.to_str().unwrap().to_lowercase(),
        None => path.file_name().unwrap().to_str().unwrap().to_lowercase(),
    };

    match &*ext {
        "c" => Lang::C,
        "h" | "hh" | "hpp" | "hxx" => Lang::CCppHeader,
        "rs" => Lang::Rust,
        "hs" => Lang::Haskell,
        "pl" => Lang::Perl,
        "rb" => Lang::Ruby,
        "makefile" | "mk" => Lang::Make,
        "ini" => Lang::INI,
        "s" | "asm" => Lang::Assembly,
        "y" => Lang::Yacc,
        "awk" => Lang::Awk,
        "xml" => Lang::XML,

        // TODO(cgag): What's the correct extension? Any? Pragma?
        "sh" => Lang::BourneShell,
        // Probably dumb to just default to C.
        _ => Lang::Unrecognized,
    }
}

pub fn counter_config_for_lang<'a>(lang: &Lang) -> LineConfig<'a> {
    enum CT<'a> {
        SO(&'a str),
        MO(&'a str, &'a str),
        SM(&'a str, &'a str, &'a str),
    }
    // use self::ConfigTuple::*;

    let c_style = CT::SM("//", "/*", "*/");
    let sh_style = CT::SO("#");

    let ctuple = match *lang {
        Lang::Haskell => CT::SM("--", "{-", "-}"),
        Lang::Perl => CT::SM("#", "=pod", "=cut"),
        Lang::INI => CT::SO(";"),
        // TODO(cgag): Well, some architectures use ;, @, |, etc.
        // Need a way to specify more than one possible comment char.
        Lang::Assembly => CT::SM("#", "/*", "*/"),
        // TODO(cgag): Welp, single is not always necessary
        Lang::XML => CT::MO("<!--", "-->"),
        Lang::BourneShell | Lang::Ruby | Lang::Make | Lang::Awk => sh_style,
        // TODO(cgag): not 100% that yacc belongs here.
        Lang::C | Lang::CCppHeader | Lang::Rust | Lang::Yacc => c_style,
        // Default to C style
        Lang::Unrecognized => c_style,
    };

    match ctuple {
        CT::SM(single, start, end) => {
            LineConfig::SingleMulti {
                single_start: single,
                multi_start: start,
                multi_end: end,
            }
        }
        CT::SO(single) => LineConfig::SingleOnly { single_start: single },
        CT::MO(start, end) => {
            LineConfig::MultiOnly {
                multi_start: start,
                multi_end: end,
            }
        }
    }
}

struct AsciiLines<'a> {
    buf: &'a [u8],
    pos: usize,
}

struct Ascii<'a>(&'a [u8]);
impl<'a> Ascii<'a> {
    fn lines(&self) -> AsciiLines {
        AsciiLines {
            buf: self.0,
            pos: 0,
        }
    }
}

// Appears to work, now we just neeed
impl<'a> Iterator for AsciiLines<'a> {
    type Item = &'a [u8];

    fn next(&mut self) -> Option<&'a [u8]> {
        match memchr(b'\n', &self.buf[self.pos..self.buf.len()]) {
            Some(n) => {
                let start = self.pos;
                self.pos = self.pos + n + 1;
                // - 1 to drop \n char
                Some(&self.buf[start..(self.pos - 1)])
            }
            None => {
                if self.pos == self.buf.len() {
                    return None;
                }
                let start = self.pos;
                self.pos = self.buf.len();
                Some(&self.buf[start..self.pos])
            }
        }
    }
}

pub fn count(filepath: &str) -> Count {
    let lang = lang_from_ext(filepath);
    let config = counter_config_for_lang(&lang);
    match config {
        LineConfig::SingleOnly { single_start: single } => count_single_only(filepath, single),
        LineConfig::SingleMulti { single_start: single, multi_start, multi_end } => {
            count_single_multi(filepath, single, multi_start, multi_end)
        }
        LineConfig::MultiOnly { multi_start: mstart, multi_end: mend } => {
            count_multi_only(filepath, mstart, mend)
        }
    }
}

pub fn count_single_only(filepath: &str, single_start: &str) -> Count {
    let fmmap = match Mmap::open_path(filepath, Protection::Read) {
        Ok(mmap) => mmap,
        Err(e) => {
            println!("mmap err for {}: {}", filepath, e);
            return Count::default();
        }
    };
    let bytes: &[u8] = unsafe { fmmap.as_slice() };

    let mut lines = 0;
    let mut code = 0;
    let mut comments = 0;
    let mut blanks = 0;

    let a = Ascii(bytes);
    for byte_line in a.lines() {
        let line = match std::str::from_utf8(byte_line) {
            Ok(s) => s,
            Err(_) => return Count::default(),
        };
        lines += 1;

        let trimmed = line.trim_left();
        if trimmed.is_empty() {
            blanks += 1;
        } else if trimmed.starts_with(single_start) {
            comments += 1;
        } else {
            code += 1;
        }
    }

    Count {
        code: code,
        comment: comments,
        blank: blanks,
        lines: lines,
    }
}

pub fn count_multi_only(filepath: &str, multi_start: &str, multi_end: &str) -> Count {
    // this is a duplicate of count_single_multi without the check for single comment.
    // Basically removes one branch.  Probably pointless: benchmark.
    let multiline_start = multi_start;
    let multiline_end = multi_end;

    let fmmap = match Mmap::open_path(filepath, Protection::Read) {
        Ok(mmap) => mmap,
        Err(e) => {
            println!("mmap err for {}: {}", filepath, e);
            return Count::default();
        }
    };
    let bytes: &[u8] = unsafe { fmmap.as_slice() };

    let mut lines = 0;
    let mut code = 0;
    let mut comments = 0;
    let mut blanks = 0;

    let mut in_comment = false;

    let a = Ascii(bytes);
    for byte_line in a.lines() {
        let line = match std::str::from_utf8(byte_line) {
            Ok(s) => s,
            Err(_) => return Count::default(),
        };
        lines += 1;

        let trimmed = line.trim_left();
        if trimmed.is_empty() {
            blanks += 1;
            continue;
        };

        if !(trimmed.contains(multiline_start) || trimmed.contains(multiline_end)) {
            if in_comment {
                comments += 1;
            } else {
                code += 1;
            }
            continue;
        }

        let start_len = multiline_start.len();
        let end_len = multiline_end.len();

        let mut pos = 0;
        let mut found_code = false;
        'outer: while pos < trimmed.len() {
            // TODO(cgag): must be a less stupid way to do this
            for i in pos..(pos + cmp::max(start_len, end_len) + 1) {
                if !trimmed.is_char_boundary(i) {
                    pos += 1;
                    continue 'outer;
                }
            }

            if pos + start_len <= trimmed.len() &&
               &trimmed[pos..pos + start_len] == multiline_start {
                pos += start_len;
                in_comment = true;
            } else if pos + end_len <= trimmed.len() && &trimmed[pos..pos + end_len] == multiline_end {
                pos += end_len;
                in_comment = false;
            } else if !in_comment {
                found_code = true;
                pos += 1;
            } else {
                pos += 1;
            }
        }

        if found_code {
            code += 1;
        } else {
            comments += 1;
        }
    }

    Count {
        code: code,
        comment: comments,
        blank: blanks,
        lines: lines,
    }
}

pub fn count_single_multi(filepath: &str,
                          single_start: &str,
                          multi_start: &str,
                          multi_end: &str)
                          -> Count {

    let single_line_start = single_start;
    let multiline_start = multi_start;
    let multiline_end = multi_end;

    let fmmap = match Mmap::open_path(filepath, Protection::Read) {
        Ok(mmap) => mmap,
        Err(e) => {
            println!("mmap err for {}: {}", filepath, e);
            return Count::default();
        }
    };
    let bytes: &[u8] = unsafe { fmmap.as_slice() };

    let mut lines = 0;
    let mut code = 0;
    let mut comments = 0;
    let mut blanks = 0;

    let mut in_comment = false;

    let a = Ascii(bytes);
    for byte_line in a.lines() {
        let line = match std::str::from_utf8(byte_line) {
            Ok(s) => s,
            Err(_) => return Count::default(),
        };
        lines += 1;

        let trimmed = line.trim_left();
        if trimmed.is_empty() {
            blanks += 1;
            continue;
        };

        if !in_comment && trimmed.starts_with(single_line_start) {
            comments += 1;
            continue;
        }

        if !(trimmed.contains(multiline_start) || trimmed.contains(multiline_end)) {
            if in_comment {
                comments += 1;
            } else {
                code += 1;
            }
            continue;
        }

        let start_len = multiline_start.len();
        let end_len = multiline_end.len();

        let mut pos = 0;
        let mut found_code = false;
        'outer: while pos < trimmed.len() {
            // TODO(cgag): must be a less stupid way to do this
            for i in pos..(pos + cmp::max(start_len, end_len) + 1) {
                if !trimmed.is_char_boundary(i) {
                    pos += 1;
                    continue 'outer;
                }
            }

            if pos + start_len <= trimmed.len() &&
               &trimmed[pos..pos + start_len] == multiline_start {
                pos += start_len;
                in_comment = true;
            } else if pos + end_len <= trimmed.len() && &trimmed[pos..pos + end_len] == multiline_end {
                pos += end_len;
                in_comment = false;
            } else if !in_comment {
                found_code = true;
                pos += 1;
            } else {
                pos += 1;
            }
        }

        if found_code {
            code += 1;
        } else {
            comments += 1;
        }
    }

    Count {
        code: code,
        comment: comments,
        blank: blanks,
        lines: lines,
    }
}
<|endoftext|>


<commit_before>extern crate curl;
extern crate url;
extern crate rustc_serialize;

use std::str;
use std::borrow::Cow;
use std::collections::HashMap;
use std::sync::Arc;
use std::thread;
use std::thread::JoinHandle;

use curl::http;
use url::Url;

use rustc_serialize::Decodable;
use rustc_serialize::json;
pub use rustc_serialize::json::{Json, BuilderError, DecoderError};

#[derive(Clone)]
pub struct Firebase {
    url: Arc<Url>,
}

impl Firebase {
    pub fn new(url: &str) -> Result<Self, ParseError> {
        let url = try!( parse(&url) );
        if url.scheme != "https" {
            return Err(ParseError::UrlIsNotHTTPS);
        }
        try!( unwrap_path(&url) );

        Ok(Firebase {
            url: Arc::new(url),
        })
    }

    pub fn from_url(url: &Url) -> Result<Self, ParseError> {
        let url = url.clone();
        try!( unwrap_path(&url) );

        Ok(Firebase {
            url: Arc::new(url),
        })
    }

    pub fn authed(url: &str, auth_token: &str) -> Result<Self, ParseError> {
        let mut url = try!( parse(&url) );
        if url.scheme != "https" {
            return Err(ParseError::UrlIsNotHTTPS);
        }
        try!( unwrap_path(&url) );

        let opts = vec![ (AUTH, auth_token) ];
        url.set_query_from_pairs(opts.into_iter());

        Ok(Firebase {
            url: Arc::new(url),
        })
    }

    pub fn at(&self, add_path: &str) -> Result<Self, ParseError> {
        let mut url = (*self.url).clone();

        { // Add path to original path, already checked for path.
            let mut path = url.path_mut().unwrap();
            // Remove .json from the old path's end.
            if let Some(end) = path.pop() {
                path.push(end.trim_right_matches(".json").to_string());
            }
            let add_path = add_path.trim_matches('/');
            let add_path = if !add_path.ends_with(".json") {
                Cow::Owned(add_path.to_string() + ".json")
            } else {
                Cow::Borrowed(add_path)
            };

            for component in add_path.split("/").into_iter() {
                path.push(component.to_string());
            }
        }

        Ok(Firebase {
            url: Arc::new(url),
        })
    }

    pub fn ops(&self, opts: &FbOps) -> FirebaseParams {
        FirebaseParams::from_ops(&self.url, opts)
    }

    fn with_params<T: ToString>(&self, key: &'static str, value: T) -> FirebaseParams {
        FirebaseParams::new(&self.url, key, value)
    }

    pub fn get(&self) -> Result<Response, ReqErr> {
        self.request(Method::GET, None)
    }

    pub fn set(&self, data: &str) -> Result<Response, ReqErr> {
        self.request(Method::PUT, Some(data))
    }

    pub fn push(&self, data: &str) -> Result<Response, ReqErr> {
        self.request(Method::POST, Some(data))
    }

    pub fn update(&self, data: &str) -> Result<Response, ReqErr> {
        self.request(Method::PATCH, Some(data))
    }

    pub fn remove(&self) -> Result<Response, ReqErr> {
        self.request(Method::DELETE, None)
    }

    pub fn get_async<F>(&self, callback: F) -> JoinHandle<()>
    where F: Fn(Result<Response, ReqErr>) + Send + 'static {
        Firebase::request_url_async(&self.url, Method::GET, None, callback)
    }

    pub fn set_async<S, F>(&self, data: S, callback: F) -> JoinHandle<()>
    where F: Fn(Result<Response, ReqErr>) + Send + 'static, S: Into<String> {
        Firebase::request_url_async(&self.url, Method::PUT, Some(data.into()), callback)
    }

    pub fn push_async<S, F>(&self, data: S, callback: F) -> JoinHandle<()>
    where F: Fn(Result<Response, ReqErr>) + Send + 'static, S: Into<String> {
        Firebase::request_url_async(&self.url, Method::POST, Some(data.into()), callback)
    }

    pub fn update_async<S, F>(&self, data: S, callback: F) -> JoinHandle<()>
    where F: Fn(Result<Response, ReqErr>) + Send + 'static, S: Into<String> {
        Firebase::request_url_async(&self.url, Method::PATCH, Some(data.into()), callback)
    }

    pub fn remove_async<F>(&self, callback: F) -> JoinHandle<()>
    where F: Fn(Result<Response, ReqErr>) + Send + 'static {
        Firebase::request_url_async(&self.url, Method::DELETE, None, callback)
    }

    pub fn order_by(&self, key: &str) -> FirebaseParams {
        self.with_params(ORDER_BY, key)
    }

    pub fn limit_to_first(&self, count: u32) -> FirebaseParams {
        self.with_params(LIMIT_TO_FIRST, count)
    }

    pub fn limit_to_last(&self, count: u32) -> FirebaseParams {
        self.with_params(LIMIT_TO_LAST, count)
    }

    pub fn start_at(&self, index: u32) -> FirebaseParams {
        self.with_params(START_AT, index)
    }

    pub fn end_at(&self, index: u32) -> FirebaseParams {
        self.with_params(END_AT, index)
    }

    pub fn equal_to(&self, value: u32) -> FirebaseParams {
        self.with_params(EQUAL_TO, value)
    }

    pub fn shallow(&self, flag: bool) -> FirebaseParams {
        self.with_params(SHALLOW, flag)
    }

    pub fn format(&self) -> FirebaseParams {
        self.with_params(FORMAT, EXPORT)
    }

    pub fn get_url(&self) -> String {
        self.url.serialize()
    }

    #[inline]
    fn request(&self, method: Method, data: Option<&str>) -> Result<Response, ReqErr> {
        Firebase::request_url(&self.url, method, data)
    }

    fn request_url(url: &Url, method: Method, data: Option<&str>) -> Result<Response, ReqErr> {
        let mut handler = http::handle();

        let req = match method {
            Method::GET     => handler.get(   url),
            Method::POST    => handler.post(  url, data.unwrap()),
            Method::PUT     => handler.put(   url, data.unwrap()),
            Method::PATCH   => handler.patch( url, data.unwrap()),
            Method::DELETE  => handler.delete(url),
        };

        let res = match req.exec() {
            Ok(r)  => r,
            Err(e) => return Err(ReqErr::NetworkErr(e)),
        };

        let body = match str::from_utf8(res.get_body()) {
            Ok(b)  => b,
            Err(e) => return Err(ReqErr::RespNotUTF8(e)),
        };

        Ok(Response {
            body: body.to_string(),
            code: res.get_code(),
        })
    }

    fn request_url_async<F>(url: &Arc<Url>, method: Method, data: Option<String>, callback: F) -> JoinHandle<()>
    where F: Fn(Result<Response, ReqErr>) + Send + 'static {
        // Fast, because its in an arc.
        let url = url.clone();

        thread::spawn(move || {
            callback(Firebase::request_url(&url, method, data.as_ref().map(|s| s as &str)));
        })
    }
}

#[derive(Clone)]
pub struct FirebaseParams {
    url: Arc<Url>,
    params: HashMap<&'static str, String>,
}

impl FirebaseParams {
    pub fn get(&self) -> Result<Response, ReqErr> {
        Firebase::request_url(&self.url, Method::GET, None)
    }

    pub fn get_async<F>(&self, callback: F) -> JoinHandle<()>
    where F: Fn(Result<Response, ReqErr>) + Send + 'static {
        Firebase::request_url_async(&self.url, Method::GET, None, callback)
    }

    pub fn order_by(self, key: &str) -> Self {
        self.add_param(ORDER_BY, key)
    }

    pub fn limit_to_first(self, count: u32) -> Self {
        self.add_param(LIMIT_TO_FIRST, count)
    }

    pub fn limit_to_last(self, count: u32) -> Self {
        self.add_param(LIMIT_TO_LAST, count)
    }

    pub fn start_at(self, index: u32) -> Self {
        self.add_param(START_AT, index)
    }

    pub fn end_at(self, index: u32) -> Self {
        self.add_param(END_AT, index)
    }

    pub fn equal_to(self, value: u32) -> Self {
        self.add_param(EQUAL_TO, value)
    }

    pub fn shallow(self, flag: bool) -> Self {
        self.add_param(SHALLOW, flag)
    }

    pub fn format(self) -> Self {
        self.add_param(FORMAT, EXPORT)
    }

    pub fn get_url(&self) -> String {
        self.url.serialize()
    }

    fn add_param<T: ToString>(mut self, key: &'static str, value: T) -> Self {
        let value = value.to_string();
        self.params.insert(key, value);
        self.set_params();
        self
    }

    fn set_params(&mut self) {
        // Only clones the url when edited. This is CoW
        // Many threads can run requests without ever cloning the url.
        let mut url = (*self.url).clone();
        url.set_query_from_pairs(self.params.iter().map(|(&k, v)| (k, v as &str)));
        self.url = Arc::new(url);
    }

    fn get_auth(url: &Url) -> HashMap<&'static str, String> {
        let mut pair: HashMap<&'static str, String> = HashMap::new();

        if let Some(queries) = url.query_pairs() {
            for &(ref k, ref v) in queries.iter() {
                if k == AUTH {
                    pair.insert(AUTH, v.to_string());
                }
            }
        }
        pair
    }

    fn new<T: ToString>(url: &Url, key: &'static str, value: T) -> Self {
        let me = FirebaseParams {
            url: Arc::new(url.clone()),
            params: FirebaseParams::get_auth(&url),
        };
        me.add_param(key, value)
    }

    fn from_ops(url: &Url, opts: &FbOps) -> Self {
        let mut me = FirebaseParams {
            url: Arc::new(url.clone()),
            params: FirebaseParams::get_auth(&url),
        };
        if let Some(order) = opts.order_by {
            me.params.insert(ORDER_BY, order.to_string());
        }
        if let Some(first) = opts.limit_to_first {
            me.params.insert(LIMIT_TO_FIRST, first.to_string());
        }
        if let Some(last) = opts.limit_to_last {
            me.params.insert(LIMIT_TO_LAST, last.to_string());
        }
        if let Some(start) = opts.start_at {
            me.params.insert(START_AT, start.to_string());
        }
        if let Some(end) = opts.end_at {
            me.params.insert(END_AT, end.to_string());
        }
        if let Some(equal) = opts.equal_to {
            me.params.insert(EQUAL_TO, equal.to_string());
        }
        if let Some(shallow) = opts.shallow {
            me.params.insert(SHALLOW, shallow.to_string());
        }
        if let Some(format) = opts.format {
            if format {
                me.params.insert(FORMAT, EXPORT.to_string());
            }
        }
        // Copy all of the params into the url.
        me.set_params();
        me
    }
}

enum Method {
    GET,
    POST,
    PUT,
    PATCH,
    DELETE,
}

const ORDER_BY:       &'static str = "orderBy";
const LIMIT_TO_FIRST: &'static str = "limitToFirst";
const LIMIT_TO_LAST:  &'static str = "limitToLast";
const START_AT:       &'static str = "startAt";
const END_AT:         &'static str = "endAt";
const EQUAL_TO:       &'static str = "equalTo";
const SHALLOW:        &'static str = "shallow";
const FORMAT:         &'static str = "format";
const EXPORT:         &'static str = "export";
const AUTH:           &'static str = "auth";

#[derive(Debug)]
pub struct FbOps<'l> {
    pub order_by:       Option<&'l str>,
    pub limit_to_first: Option<u32>,
    pub limit_to_last:  Option<u32>,
    pub start_at:       Option<u32>,
    pub end_at:         Option<u32>,
    pub equal_to:       Option<u32>,
    pub shallow:        Option<bool>,
    pub format:         Option<bool>,
}

impl<'l> Default for FbOps<'l> {
    fn default() -> Self {
        FbOps {
            order_by:       None,
            limit_to_first: None,
            limit_to_last:  None,
            start_at:       None,
            end_at:         None,
            equal_to:       None,
            shallow:        None,
            format:         None,
        }
    }
}

#[derive(Debug)]
pub enum ReqErr {
    ReqNotJSON,
    RespNotUTF8(str::Utf8Error),
    NetworkErr(curl::ErrCode),
}

#[derive(Debug)]
pub enum ParseError {
    UrlHasNoPath,
    UrlIsNotHTTPS,
    Parser(url::ParseError),
}

#[derive(Debug)]
pub struct Response {
    pub body: String,
    pub code: u32,
}

impl Response {
    pub fn is_success(&self) -> bool {
        self.code == 200
    }

    pub fn json(&self) -> Result<Json, BuilderError> {
        Json::from_str(&self.body)
    }

    pub fn parse<D>(&self) -> Result<D, DecoderError> where D: Decodable {
        json::decode(&self.body)
    }
}

fn parse(url: &str) -> Result<Url, ParseError> {
    match Url::parse(&url) {
        Ok(u)  => Ok(u),
        Err(e) => Err(ParseError::Parser(e)),
    }
}

fn unwrap_path(url: &Url) -> Result<&[String], ParseError> {
    match url.path() {
        None    => return Err(ParseError::UrlHasNoPath),
        Some(p) => return Ok(p),
    }
}

// pub trait ToJsonStr {
//     fn to_json_str(&Self) -> Result<&str, >;
// }
<commit_msg>Tried to make requests that need input json to take an encodable struct, failed because of no Trait specialization, Rust will soon have it though<commit_after>extern crate curl;
extern crate url;
extern crate rustc_serialize;

use std::str;
use std::borrow::Cow;
use std::collections::HashMap;
use std::sync::Arc;
use std::thread;
use std::thread::JoinHandle;

use curl::http;
use url::Url;

use rustc_serialize::Decodable;
use rustc_serialize::json;
pub use rustc_serialize::json::{Json, BuilderError, DecoderError};

#[derive(Clone)]
pub struct Firebase {
    url: Arc<Url>,
}

impl Firebase {
    pub fn new(url: &str) -> Result<Self, ParseError> {
        let url = try!( parse(&url) );
        if url.scheme != "https" {
            return Err(ParseError::UrlIsNotHTTPS);
        }
        try!( unwrap_path(&url) );

        Ok(Firebase {
            url: Arc::new(url),
        })
    }

    pub fn from_url(url: &Url) -> Result<Self, ParseError> {
        let url = url.clone();
        try!( unwrap_path(&url) );

        Ok(Firebase {
            url: Arc::new(url),
        })
    }

    pub fn authed(url: &str, auth_token: &str) -> Result<Self, ParseError> {
        let mut url = try!( parse(&url) );
        if url.scheme != "https" {
            return Err(ParseError::UrlIsNotHTTPS);
        }
        try!( unwrap_path(&url) );

        let opts = vec![ (AUTH, auth_token) ];
        url.set_query_from_pairs(opts.into_iter());

        Ok(Firebase {
            url: Arc::new(url),
        })
    }

    pub fn at(&self, add_path: &str) -> Result<Self, ParseError> {
        let mut url = (*self.url).clone();

        { // Add path to original path, already checked for path.
            let mut path = url.path_mut().unwrap();
            // Remove .json from the old path's end.
            if let Some(end) = path.pop() {
                path.push(end.trim_right_matches(".json").to_string());
            }
            let add_path = add_path.trim_matches('/');
            let add_path = if !add_path.ends_with(".json") {
                Cow::Owned(add_path.to_string() + ".json")
            } else {
                Cow::Borrowed(add_path)
            };

            for component in add_path.split("/").into_iter() {
                path.push(component.to_string());
            }
        }

        Ok(Firebase {
            url: Arc::new(url),
        })
    }

    pub fn ops(&self, opts: &FbOps) -> FirebaseParams {
        FirebaseParams::from_ops(&self.url, opts)
    }

    fn with_params<T: ToString>(&self, key: &'static str, value: T) -> FirebaseParams {
        FirebaseParams::new(&self.url, key, value)
    }

    pub fn get(&self) -> Result<Response, ReqErr> {
        self.request(Method::GET, None)
    }

    pub fn set(&self, data: &str) -> Result<Response, ReqErr> {
        self.request(Method::PUT, Some(data))
    }

    pub fn push(&self, data: &str) -> Result<Response, ReqErr> {
        self.request(Method::POST, Some(data))
    }

    pub fn update(&self, data: &str) -> Result<Response, ReqErr> {
        self.request(Method::PATCH, Some(data))
    }

    pub fn remove(&self) -> Result<Response, ReqErr> {
        self.request(Method::DELETE, None)
    }

    pub fn get_async<F>(&self, callback: F) -> JoinHandle<()>
    where F: Fn(Result<Response, ReqErr>) + Send + 'static {
        Firebase::request_url_async(&self.url, Method::GET, None, callback)
    }

    pub fn set_async<S, F>(&self, data: S, callback: F) -> JoinHandle<()>
    where F: Fn(Result<Response, ReqErr>) + Send + 'static, S: Into<String> {
        Firebase::request_url_async(&self.url, Method::PUT, Some(data.into()), callback)
    }

    pub fn push_async<S, F>(&self, data: S, callback: F) -> JoinHandle<()>
    where F: Fn(Result<Response, ReqErr>) + Send + 'static, S: Into<String> {
        Firebase::request_url_async(&self.url, Method::POST, Some(data.into()), callback)
    }

    pub fn update_async<S, F>(&self, data: S, callback: F) -> JoinHandle<()>
    where F: Fn(Result<Response, ReqErr>) + Send + 'static, S: Into<String> {
        Firebase::request_url_async(&self.url, Method::PATCH, Some(data.into()), callback)
    }

    pub fn remove_async<F>(&self, callback: F) -> JoinHandle<()>
    where F: Fn(Result<Response, ReqErr>) + Send + 'static {
        Firebase::request_url_async(&self.url, Method::DELETE, None, callback)
    }

    pub fn order_by(&self, key: &str) -> FirebaseParams {
        self.with_params(ORDER_BY, key)
    }

    pub fn limit_to_first(&self, count: u32) -> FirebaseParams {
        self.with_params(LIMIT_TO_FIRST, count)
    }

    pub fn limit_to_last(&self, count: u32) -> FirebaseParams {
        self.with_params(LIMIT_TO_LAST, count)
    }

    pub fn start_at(&self, index: u32) -> FirebaseParams {
        self.with_params(START_AT, index)
    }

    pub fn end_at(&self, index: u32) -> FirebaseParams {
        self.with_params(END_AT, index)
    }

    pub fn equal_to(&self, value: u32) -> FirebaseParams {
        self.with_params(EQUAL_TO, value)
    }

    pub fn shallow(&self, flag: bool) -> FirebaseParams {
        self.with_params(SHALLOW, flag)
    }

    pub fn format(&self) -> FirebaseParams {
        self.with_params(FORMAT, EXPORT)
    }

    pub fn get_url(&self) -> String {
        self.url.serialize()
    }

    #[inline]
    fn request(&self, method: Method, data: Option<&str>) -> Result<Response, ReqErr> {
        Firebase::request_url(&self.url, method, data)
    }

    fn request_url(url: &Url, method: Method, data: Option<&str>) -> Result<Response, ReqErr> {
        let mut handler = http::handle();

        let req = match method {
            Method::GET     => handler.get(   url),
            Method::POST    => handler.post(  url, data.unwrap()),
            Method::PUT     => handler.put(   url, data.unwrap()),
            Method::PATCH   => handler.patch( url, data.unwrap()),
            Method::DELETE  => handler.delete(url),
        };

        let res = match req.exec() {
            Ok(r)  => r,
            Err(e) => return Err(ReqErr::NetworkErr(e)),
        };

        let body = match str::from_utf8(res.get_body()) {
            Ok(b)  => b,
            Err(e) => return Err(ReqErr::RespNotUTF8(e)),
        };

        Ok(Response {
            body: body.to_string(),
            code: res.get_code(),
        })
    }

    fn request_url_async<F>(url: &Arc<Url>, method: Method, data: Option<String>, callback: F) -> JoinHandle<()>
    where F: Fn(Result<Response, ReqErr>) + Send + 'static {
        // Fast, because its in an arc.
        let url = url.clone();

        thread::spawn(move || {
            callback(Firebase::request_url(&url, method, data.as_ref().map(|s| s as &str)));
        })
    }
}

#[derive(Clone)]
pub struct FirebaseParams {
    url: Arc<Url>,
    params: HashMap<&'static str, String>,
}

impl FirebaseParams {
    pub fn get(&self) -> Result<Response, ReqErr> {
        Firebase::request_url(&self.url, Method::GET, None)
    }

    pub fn get_async<F>(&self, callback: F) -> JoinHandle<()>
    where F: Fn(Result<Response, ReqErr>) + Send + 'static {
        Firebase::request_url_async(&self.url, Method::GET, None, callback)
    }

    pub fn order_by(self, key: &str) -> Self {
        self.add_param(ORDER_BY, key)
    }

    pub fn limit_to_first(self, count: u32) -> Self {
        self.add_param(LIMIT_TO_FIRST, count)
    }

    pub fn limit_to_last(self, count: u32) -> Self {
        self.add_param(LIMIT_TO_LAST, count)
    }

    pub fn start_at(self, index: u32) -> Self {
        self.add_param(START_AT, index)
    }

    pub fn end_at(self, index: u32) -> Self {
        self.add_param(END_AT, index)
    }

    pub fn equal_to(self, value: u32) -> Self {
        self.add_param(EQUAL_TO, value)
    }

    pub fn shallow(self, flag: bool) -> Self {
        self.add_param(SHALLOW, flag)
    }

    pub fn format(self) -> Self {
        self.add_param(FORMAT, EXPORT)
    }

    pub fn get_url(&self) -> String {
        self.url.serialize()
    }

    fn add_param<T: ToString>(mut self, key: &'static str, value: T) -> Self {
        let value = value.to_string();
        self.params.insert(key, value);
        self.set_params();
        self
    }

    fn set_params(&mut self) {
        // Only clones the url when edited. This is CoW
        // Many threads can run requests without ever cloning the url.
        let mut url = (*self.url).clone();
        url.set_query_from_pairs(self.params.iter().map(|(&k, v)| (k, v as &str)));
        self.url = Arc::new(url);
    }

    fn get_auth(url: &Url) -> HashMap<&'static str, String> {
        let mut pair: HashMap<&'static str, String> = HashMap::new();

        if let Some(queries) = url.query_pairs() {
            for &(ref k, ref v) in queries.iter() {
                if k == AUTH {
                    pair.insert(AUTH, v.to_string());
                }
            }
        }
        pair
    }

    fn new<T: ToString>(url: &Url, key: &'static str, value: T) -> Self {
        let me = FirebaseParams {
            url: Arc::new(url.clone()),
            params: FirebaseParams::get_auth(&url),
        };
        me.add_param(key, value)
    }

    fn from_ops(url: &Url, opts: &FbOps) -> Self {
        let mut me = FirebaseParams {
            url: Arc::new(url.clone()),
            params: FirebaseParams::get_auth(&url),
        };
        if let Some(order) = opts.order_by {
            me.params.insert(ORDER_BY, order.to_string());
        }
        if let Some(first) = opts.limit_to_first {
            me.params.insert(LIMIT_TO_FIRST, first.to_string());
        }
        if let Some(last) = opts.limit_to_last {
            me.params.insert(LIMIT_TO_LAST, last.to_string());
        }
        if let Some(start) = opts.start_at {
            me.params.insert(START_AT, start.to_string());
        }
        if let Some(end) = opts.end_at {
            me.params.insert(END_AT, end.to_string());
        }
        if let Some(equal) = opts.equal_to {
            me.params.insert(EQUAL_TO, equal.to_string());
        }
        if let Some(shallow) = opts.shallow {
            me.params.insert(SHALLOW, shallow.to_string());
        }
        if let Some(format) = opts.format {
            if format {
                me.params.insert(FORMAT, EXPORT.to_string());
            }
        }
        // Copy all of the params into the url.
        me.set_params();
        me
    }
}

enum Method {
    GET,
    POST,
    PUT,
    PATCH,
    DELETE,
}

const ORDER_BY:       &'static str = "orderBy";
const LIMIT_TO_FIRST: &'static str = "limitToFirst";
const LIMIT_TO_LAST:  &'static str = "limitToLast";
const START_AT:       &'static str = "startAt";
const END_AT:         &'static str = "endAt";
const EQUAL_TO:       &'static str = "equalTo";
const SHALLOW:        &'static str = "shallow";
const FORMAT:         &'static str = "format";
const EXPORT:         &'static str = "export";
const AUTH:           &'static str = "auth";

#[derive(Debug)]
pub struct FbOps<'l> {
    pub order_by:       Option<&'l str>,
    pub limit_to_first: Option<u32>,
    pub limit_to_last:  Option<u32>,
    pub start_at:       Option<u32>,
    pub end_at:         Option<u32>,
    pub equal_to:       Option<u32>,
    pub shallow:        Option<bool>,
    pub format:         Option<bool>,
}

impl<'l> Default for FbOps<'l> {
    fn default() -> Self {
        FbOps {
            order_by:       None,
            limit_to_first: None,
            limit_to_last:  None,
            start_at:       None,
            end_at:         None,
            equal_to:       None,
            shallow:        None,
            format:         None,
        }
    }
}

#[derive(Debug)]
pub enum ReqErr {
    ReqNotJSON,
    RespNotUTF8(str::Utf8Error),
    NetworkErr(curl::ErrCode),
}

#[derive(Debug)]
pub enum ParseError {
    UrlHasNoPath,
    UrlIsNotHTTPS,
    Parser(url::ParseError),
}

#[derive(Debug)]
pub struct Response {
    pub body: String,
    pub code: u32,
}

impl Response {
    pub fn is_success(&self) -> bool {
        self.code == 200
    }

    pub fn json(&self) -> Result<Json, BuilderError> {
        Json::from_str(&self.body)
    }

    pub fn parse<D>(&self) -> Result<D, DecoderError> where D: Decodable {
        json::decode(&self.body)
    }
}

fn parse(url: &str) -> Result<Url, ParseError> {
    match Url::parse(&url) {
        Ok(u)  => Ok(u),
        Err(e) => Err(ParseError::Parser(e)),
    }
}

fn unwrap_path(url: &Url) -> Result<&[String], ParseError> {
    match url.path() {
        None    => return Err(ParseError::UrlHasNoPath),
        Some(p) => return Ok(p),
    }
}

// This code will happen when Trait Specialization becomes available
// in rust.
// pub trait ToJsonStr {
//     fn to_json_str(&self) -> Result<Cow<str>, /* TODO */ Box<Error>>;
// }
//
// impl<'l> ToJsonStr for &'l str {
//     fn to_json_str(&self) -> Result<Cow<str>, /* TODO */ Box<Error>> {
//         Ok(Cow::Borrowed(self))
//     }
// }
//
// impl<S> ToJsonStr for S where S: Encodable {
//     fn to_json_str(&self) -> Result<Cow<str>, /* TODO */ Box<Error>> {
//         match json::encode(self) {
//             Ok(encoded) => Ok(Cow::Owned(encoded)),
//             Err(e)      => Err(Box::new(e)),
//         }
//     }
// }
<|endoftext|>


<commit_before>extern crate time;
extern crate regex;
extern crate rustc_serialize;

use regex::Regex;
use rustc_serialize::json::{self, ToJson, Json};

use std::io::{self, Read, Write};
use std::fs::File;
use std::collections::BTreeMap;

enum OutputStream {
    File(File),
    Stdout(io::Stdout),
}

#[derive(RustcEncodable)]
struct Output {
    lines: u64,
    start: Json,
    end: Json,
    analysis: Analysis,
}

#[derive(RustcEncodable)]
struct Analysis {
    msg_per_person: BTreeMap<String, u64>,
    avg_per_person: BTreeMap<String, u64>,
    chr_per_person: BTreeMap<String, u64>,
    most_used_word: BTreeMap<String, u64>,
    //most_used_emoji: BTreeMap<String, u64>,
    msg_per_day: BTreeMap<String, u64>,
}

struct JsonTime(time::Tm);

impl ToJson for JsonTime {
    fn to_json(&self) -> Json {
        Json::String(time::strftime("%FT%TZ", &self.0).ok().unwrap())
    }
}

pub fn preprocess(input: &str) -> Result<(), io::Error> {
    let mut f = try!(File::open(input));
    let mut o = try!(File::create("tmp"));

    let mut s = String::new();
    try!(f.read_to_string(&mut s));

    let re = Regex::new(r"\n+(\D)").unwrap();
    try!(o.write_all(&re.replace_all(&s, " $1").into_bytes()));
    Ok(())
}

pub fn analyse(input: &str, out: Option<String>) -> Result<String, io::Error> {
    // Lees de input in een string s
    let mut f = try!(File::open(input));
	let mut s = String::new();
    try!(f.read_to_string(&mut s));

    // De regex die gebruikt wordt om alle benodigde informatie te
    // extraheren
    let berichtreg = Regex::new(r"(?P<datum>\d{4}/\d{2}/\d{2}, \d{2}:\d{2}) - (?:(?P<naam>[\w ]+): (?P<bericht>.*)|(?P<speciaal>.*))").unwrap();
    let woordreg = Regex::new(r"(\b[^\s]+\b)").unwrap();

    // De variabeles voor de informatie die moet worden verzameld
	let mut lines = 0;
    let mut eerste_tijd = None;
    let mut laatste_tijd = None;
    let mut tekens_per_naam: BTreeMap<String, u64> = BTreeMap::new();
    let mut berichten_per_naam: BTreeMap<String, u64> = BTreeMap::new();
    let mut meest_gebruikte_woorden: BTreeMap<String, u64> = BTreeMap::new();
    let mut berichten_per_dag: BTreeMap<String, u64> = BTreeMap::new();
    let mut begint_meeste_gesprekken: BTreeMap<String, u64> = BTreeMap::new();

    // Regex de input string s en iterate over de bericht matches
	for cap in berichtreg.captures_iter(&s) {
        // De informatie uit de berichten
        let naam = cap.name("naam").unwrap_or("");
        let bericht = cap.name("bericht").unwrap_or("");
        let datum = cap.name("datum").unwrap_or("");
        let dag = JsonTime(time::strptime(datum, "%Y/%m/%d").ok().unwrap()).to_json().to_string();

        for cap in woordreg.captures_iter(&bericht) {
            let woord = cap.at(1).unwrap_or("");
            if meest_gebruikte_woorden.contains_key(woord) {
                if let Some(x) = meest_gebruikte_woorden.get_mut(woord) {
                    *x += 1;
                }
            } else if naam != "" {
                meest_gebruikte_woorden.insert(woord.to_string(), 1);
            }
        }

        if eerste_tijd.is_none() {
            eerste_tijd = Some(time::strptime(datum, "%Y/%m/%d, %H:%M"));
        }

        if tekens_per_naam.contains_key(naam) {
            if let Some(x) = tekens_per_naam.get_mut(naam) {
                *x += bericht.len() as u64;
            }
        } else if naam != "" {
            tekens_per_naam.insert(naam.to_string(), bericht.len() as u64);
        }

        if berichten_per_dag.contains_key(&dag) {
            if let Some(x) = berichten_per_dag.get_mut(&dag) {
                *x += 1;
            }
        } else {
            if begint_meeste_gesprekken.contains_key(naam) {
                if let Some(x) = begint_meeste_gesprekken.get_mut(naam) {
                    *x += 1;
                }
            } else {
                begint_meeste_gesprekken.insert(naam.to_string(), 1);
            }
            berichten_per_dag.insert(dag, 1);
        }

        if berichten_per_naam.contains_key(naam) {
            if let Some(x) = berichten_per_naam.get_mut(naam) {
                *x += 1;
            }
        } else if naam != "" {
            berichten_per_naam.insert(naam.to_string(), 1);
        }
		lines += 1;
        laatste_tijd = Some(datum);
	}

    let start_tijd = JsonTime(eerste_tijd.unwrap().ok().unwrap());
    let eind_tijd = JsonTime(time::strptime(laatste_tijd.unwrap(), "%Y/%m/%d, %H:%M").ok().unwrap());

    let mut gemiddelde_per_naam: BTreeMap<String, u64> = BTreeMap::new();

    for (naam, x) in &tekens_per_naam {
        let berichten = berichten_per_naam.get(naam).unwrap();
        gemiddelde_per_naam.insert(naam.to_owned(), x / berichten);
    }

    let mut meest_gebruikte_woorden2: BTreeMap<String, u64> = BTreeMap::new();
    for (woord, x) in meest_gebruikte_woorden {
        if x > 10 {
            meest_gebruikte_woorden2.insert(woord, x);
        }
    }

    let a = Analysis {
        msg_per_person: berichten_per_naam,
        avg_per_person: gemiddelde_per_naam,
        chr_per_person: tekens_per_naam,
        most_used_word: meest_gebruikte_woorden2,
        msg_per_day: berichten_per_dag,
        //most_used_emoji: meest_gebruikte_emoji,
    };

    let o = Output {
        lines: lines,
        start: start_tijd.to_json(),
        end: eind_tijd.to_json(),
        analysis: a,
    };

    let mut output = match out {
        Some(ref x) => OutputStream::File((File::open(x).ok().unwrap())),
        None => OutputStream::Stdout(io::stdout()),
    };

    match output {
	   OutputStream::File(ref mut x) => write!(x, "{}", json::encode(&o).unwrap()).unwrap(),
       OutputStream::Stdout(ref mut x) => write!(x, "{}", json::encode(&o).unwrap()).unwrap(),
    }

    Ok(out.unwrap_or("stdout".to_string()))
}<commit_msg>Added things<commit_after>extern crate time;
extern crate regex;
extern crate rustc_serialize;

use regex::Regex;
use rustc_serialize::json::{self, ToJson, Json};

use std::io::{self, Read, Write};
use std::fs::File;
use std::collections::BTreeMap;

enum OutputStream {
    File(File),
    Stdout(io::Stdout),
}

#[derive(RustcEncodable)]
struct Output {
    lines: u64,
    start: Json,
    end: Json,
    analysis: Analysis,
}

#[derive(RustcEncodable)]
struct Analysis {
    msg_total: u64,
    media_total: u64,
    title_changes: BTreeMap<String, Vec<String>>,
    starts_most_chats: BTreeMap<String, u64>,
    msg_per_person: BTreeMap<String, u64>,
    media_per_person: BTreeMap<String, u64>,
    avg_per_person: BTreeMap<String, u64>,
    chr_per_person: BTreeMap<String, u64>,
    msg_per_day: BTreeMap<String, u64>,
    most_used_word: BTreeMap<String, u64>,
}

struct JsonTime(time::Tm);

impl ToJson for JsonTime {
    fn to_json(&self) -> Json {
        Json::String(time::strftime("%FT%TZ", &self.0).ok().unwrap())
    }
}

pub fn preprocess(input: &str) -> Result<(), io::Error> {
    let mut f = try!(File::open(input));
    let mut o = try!(File::create("tmp"));

    let mut s = String::new();
    try!(f.read_to_string(&mut s));

    let re = Regex::new(r"\n+(\D)").unwrap();
    try!(o.write_all(&re.replace_all(&s, " $1").into_bytes()));
    Ok(())
}

pub fn analyse(input: &str, out: Option<String>) -> Result<String, io::Error> {
    // Lees de input in een string s
    let mut f = try!(File::open(input));
	let mut s = String::new();
    try!(f.read_to_string(&mut s));

    // De regex die gebruikt wordt om alle benodigde informatie te
    // extraheren
    let berichtreg = Regex::new(r"(?P<datum>\d{4}/\d{2}/\d{2}, \d{2}:\d{2}) - (?:(?P<naam>[\w ]+): (?P<bericht>.*)|(?P<naamspeciaal>.+)(?P<speciaal> changed.+)|(?P<naambeheerder>.+) added (?P<naamjoin>.+)|(?P<naamleft>.+) left)").unwrap();
    let woordreg = Regex::new(r"(\b[^\s]+\b)").unwrap();
    let mediareg = Regex::new(r"<Media omitted>").unwrap();
    let titelreg = Regex::new(r"^changed the subject to “(.+)”").unwrap();

    // De variabeles voor de informatie die moet worden verzameld
	let mut lines = 0;
    let mut berichten = 0;
    let mut media = 0;
    let mut eerste_tijd = None;
    let mut laatste_tijd = None;
    let mut analyse_per_naam: BTreeMap<String, Json> = BTreeMap::new();
    let mut tekens_per_naam: BTreeMap<String, u64> = BTreeMap::new();
    let mut berichten_per_naam: BTreeMap<String, u64> = BTreeMap::new();
    let mut media_per_naam: BTreeMap<String, u64> = BTreeMap::new();
    let mut meest_gebruikte_woorden: BTreeMap<String, u64> = BTreeMap::new();
    let mut berichten_per_dag: BTreeMap<String, u64> = BTreeMap::new();
    let mut begint_meeste_gesprekken: BTreeMap<String, u64> = BTreeMap::new(); //Detecteerd niet daadwerkelijk gesprekken, maar kijkt wie elke dag het gesprek begint.
    let mut titel_verandering: BTreeMap<String, Vec<String>> = BTreeMap::new();

    // Regex de input string s en iterate over de bericht matches
	for cap in berichtreg.captures_iter(&s) {
        // De informatie uit de berichten
        let naam = cap.name("naam").unwrap_or("");
        let bericht = cap.name("bericht").unwrap_or("");
        let naamspeciaal = cap.name("naamspeciaal").unwrap_or("");
        let speciaal = cap.name("speciaal").unwrap_or("");
        let datum = cap.name("datum").unwrap_or("");
        let dag = JsonTime(time::strptime(datum, "%Y/%m/%d").ok().unwrap()).to_json().to_string();

        if mediareg.is_match(bericht) {
            media += 1;
        } else {
            berichten += 1;
        }

        if speciaal != "" {
            if let Some(captures) = titelreg.captures(&speciaal) {
                titel_verandering.insert(dag.to_string(), vec![naamspeciaal.to_string(), captures.at(1).unwrap().to_string()]);
            }
        }

        for cap in woordreg.captures_iter(&bericht) {
            let woord = cap.at(1).unwrap_or("");
            if meest_gebruikte_woorden.contains_key(woord) {
                if let Some(x) = meest_gebruikte_woorden.get_mut(woord) {
                    *x += 1;
                }
            } else if naam != "" {
                meest_gebruikte_woorden.insert(woord.to_string(), 1);
            }
        }

        if eerste_tijd.is_none() {
            eerste_tijd = Some(time::strptime(datum, "%Y/%m/%d, %H:%M"));
        }

        if tekens_per_naam.contains_key(naam) {
            if let Some(x) = tekens_per_naam.get_mut(naam) {
                *x += bericht.len() as u64;
            }
        } else if naam != "" {
            tekens_per_naam.insert(naam.to_string(), bericht.len() as u64);
        }

        if berichten_per_dag.contains_key(&dag) {
            if let Some(x) = berichten_per_dag.get_mut(&dag) {
                *x += 1;
            }
        } else {
            if begint_meeste_gesprekken.contains_key(naam) {
                if let Some(x) = begint_meeste_gesprekken.get_mut(naam) {
                    *x += 1;
                }
            } else {
                begint_meeste_gesprekken.insert(naam.to_string(), 1);
            }
            berichten_per_dag.insert(dag, 1);
        }

        if berichten_per_naam.contains_key(naam) {
            if let Some(x) = berichten_per_naam.get_mut(naam) {
                *x += 1;
            }
        } else if naam != "" && bericht != "" {
            berichten_per_naam.insert(naam.to_string(), 1);
        }

        if media_per_naam.contains_key(naam) {
            if let Some(x) = media_per_naam.get_mut(naam) {
                if mediareg.is_match(bericht){
                    *x += 1;
                }
            }
        } else if naam != "" && mediareg.is_match(bericht) {
            media_per_naam.insert(naam.to_string(), 1);
        }
		lines += 1;
        laatste_tijd = Some(datum);
	}

    let start_tijd = JsonTime(eerste_tijd.unwrap().ok().unwrap());
    let eind_tijd = JsonTime(time::strptime(laatste_tijd.unwrap(), "%Y/%m/%d, %H:%M").ok().unwrap());

    let mut gemiddelde_per_naam: BTreeMap<String, u64> = BTreeMap::new();

    for (naam, x) in &tekens_per_naam {
        let berichten = berichten_per_naam.get(naam).unwrap();
        gemiddelde_per_naam.insert(naam.to_owned(), x / berichten);
    }

    let mut meest_gebruikte_woorden2: BTreeMap<String, u64> = BTreeMap::new();
    for (woord, x) in meest_gebruikte_woorden {
        if x > 10 {
            meest_gebruikte_woorden2.insert(woord, x);
        }
    }

    let a = Analysis {
        msg_total: berichten,
        media_total: media,
        title_changes: titel_verandering,
        starts_most_chats: begint_meeste_gesprekken,
        msg_per_person: berichten_per_naam,
        media_per_person: media_per_naam,
        avg_per_person: gemiddelde_per_naam,
        chr_per_person: tekens_per_naam,
        msg_per_day: berichten_per_dag,
        most_used_word: meest_gebruikte_woorden2,
    };

    let o = Output {
        lines: lines,
        start: start_tijd.to_json(),
        end: eind_tijd.to_json(),
        analysis: a,
    };

    let mut output = match out {
        Some(ref x) => OutputStream::File((File::open(x).ok().unwrap())),
        None => OutputStream::Stdout(io::stdout()),
    };

    match output {
	   OutputStream::File(ref mut x) => write!(x, "{}", json::encode(&o).unwrap()).unwrap(),
       OutputStream::Stdout(ref mut x) => write!(x, "{}", json::encode(&o).unwrap()).unwrap(),
    }

    Ok(out.unwrap_or("stdout".to_string()))
}<|endoftext|>


<commit_before>#![feature(macro_rules)]

extern crate time;
extern crate conduit;
extern crate "conduit-middleware" as middleware;

use std::str::{MaybeOwned, Slice, Owned};
use std::fmt::Show;
use std::io::util::NullReader;
use time::{Tm, strptime, ParseError};
use conduit::Request;
use middleware::Middleware;

type Response = Result<conduit::Response, Box<Show + 'static>>;

pub struct ConditionalGet;

impl Middleware for ConditionalGet {
    fn after(&self, req: &mut Request, res: Response) -> Response {
        let mut res = try!(res);

        match req.method() {
            conduit::Get | conduit::Head => {
                if is_ok(&res) && is_fresh(req, &res) {
                    res.status = (304, "Not Modified");
                    res.headers.pop_equiv(&"Content-Type");
                    res.headers.pop_equiv(&"Content-Length");
                    res.body = box NullReader as Box<Reader + Send>;
                }
            },
            _ => ()
        }

        Ok(res)
    }
}

fn is_ok(response: &conduit::Response) -> bool {
    match *response {
        conduit::Response { status: (200, _), .. } => true,
        _ => false
    }
}

fn is_fresh(req: &Request, res: &conduit::Response) -> bool {
    let modified_since = req.headers().find("If-Modified-Since").map(header_val);
    let none_match     = req.headers().find("If-None-Match").map(header_val);

    if modified_since.as_ref().or_else(|| none_match.as_ref()).is_none() {
        return false;
    }

    let mut success = true;

    modified_since.and_then(|modified_since| {
        parse_http_date(modified_since).map_err(|_| { success = false; }).ok()
    }).map(|parsed| {
        success = success && is_modified_since(parsed, res);
    });

    none_match.map(|none_match| {
        success = success && etag_matches(none_match, res);
    });

    success
}

fn etag_matches<S: Str>(none_match: S, res: &conduit::Response) -> bool {
    res.headers.find_equiv(&"ETag").map(|etag| {
        res_header_val(etag).as_slice() == none_match.as_slice()
    }).unwrap_or(false)
}

fn is_modified_since(modified_since: Tm, res: &conduit::Response) -> bool {
    res.headers.find_equiv(&"Last-Modified").and_then(|last_modified| {
        parse_http_date(res_header_val(last_modified).as_slice()).ok()
    }).map(|last_modified| {
        modified_since.to_timespec() >= last_modified.to_timespec()
    }).unwrap_or(false)
}

fn header_val<'a>(header: Vec<&'a str>) -> MaybeOwned<'a> {
    if header.len() == 1 {
        Slice(header[0])
    } else {
        Owned(header.concat())
    }
}

fn res_header_val<'a>(header: &'a Vec<String>) -> MaybeOwned<'a> {
    if header.len() == 1 {
        Slice(header[0].as_slice())
    } else {
        Owned(header.concat())
    }
}

fn parse_http_date<S: Str>(string: S) -> Result<Tm, ()> {
    let string = string.as_slice();

    parse_rfc1123(string)
        .or_else(|_| parse_rfc850(string))
        .or_else(|_| parse_asctime(string))
        .map_err(|_| ())
}

fn parse_rfc1123(string: &str) -> Result<Tm, ParseError> {
    time::strptime(string, "%a, %d %b %Y %T GMT")
}

fn parse_rfc850(string: &str) -> Result<Tm, ParseError> {
    time::strptime(string, "%a, %d-%m-%y %T GMT")
}

fn parse_asctime(string: &str) -> Result<Tm, ParseError> {
    time::strptime(string, "%a %m%t%d %T %Y")
}

#[cfg(test)]
mod tests {
    extern crate "conduit-test" as test;

    use std::fmt::Show;
    use std::io::MemReader;
    use std::collections::HashMap;
    use time;
    use time::Tm;
    use conduit;
    use conduit::{Request, Response, Handler};
    use middleware::MiddlewareBuilder;

    use super::ConditionalGet;

    macro_rules! returning(
        ($code:expr, $($header:expr => $value:expr),+) => ({
            let mut headers = HashMap::new();
            $(headers.insert($header.to_string(), vec!($value.to_string()));)+
            let handler = SimpleHandler::new(headers, $code, "hello");
            let mut stack = MiddlewareBuilder::new(handler);
            stack.add(ConditionalGet);
            stack
        });
        ($($header:expr => $value:expr),+) => ({
            returning!((200, "OK"), $($header => $value),+)
        })
    )

    macro_rules! request(
        ($($header:expr => $value:expr),+) => ({
            let mut req = test::MockRequest::new(conduit::Get, "/");
            $(req.header($header, $value.to_string());)+
            req
        })
    )

    #[test]
    fn test_sends_304() {
        let handler = returning!("Last-Modified" => httpdate(time::now()));
        expect_304(handler.call(&mut request!(
            "If-Modified-Since" => httpdate(time::now())
        ) as &mut conduit::Request));
    }

    #[test]
    fn test_sends_304_if_older_than_now() {
        let handler = returning!("Last-Modified" => before_now());
        expect_304(handler.call(&mut request!(
            "If-Modified-Since" => httpdate(time::now())
        ) as &mut conduit::Request));
    }

    #[test]
    fn test_sends_304_with_etag() {
        let handler = returning!("ETag" => "1234");
        expect_304(handler.call(&mut request!(
            "If-None-Match" => "1234"
        ) as &mut conduit::Request));
    }

    #[test]
    fn test_sends_200_with_fresh_time_but_not_etag() {
        let handler = returning!("Last-Modified" => before_now(), "ETag" => "1234");
        expect_200(handler.call(&mut request!(
            "If-Modified-Since" => now(),
            "If-None-Match" => "4321"
        ) as &mut conduit::Request));
    }

    #[test]
    fn test_sends_200_with_fresh_etag_but_not_time() {
        let handler = returning!("Last-Modified" => now(), "ETag" => "1234");
        expect_200(handler.call(&mut request!(
            "If-Modified-Since" => before_now(),
            "If-None-Match" => "1234"
        ) as &mut conduit::Request));
    }

    #[test]
    fn test_sends_200_with_fresh_etag() {
        let handler = returning!("ETag" => "1234");
        expect_200(handler.call(&mut request!(
            "If-None-Match" => "4321"
        ) as &mut conduit::Request));
    }

    #[test]
    fn test_sends_200_with_fresh_time() {
        let handler = returning!("Last-Modified" => now());
        expect_200(handler.call(&mut request!(
            "If-Modified-Since" => before_now()
        ) as &mut conduit::Request));
    }

    #[test]
    fn test_sends_304_with_fresh_time_and_etag() {
        let handler = returning!("Last-Modified" => before_now(), "ETag" => "1234");
        expect_304(handler.call(&mut request!(
            "If-Modified-Since" => now(),
            "If-None-Match" => "1234"
        ) as &mut conduit::Request));
    }

    #[test]
    fn test_does_not_affect_non_200() {
        let code = (302, "Found");
        let handler = returning!(code, "Last-Modified" => before_now(), "ETag" => "1234");
        expect(code, handler.call(&mut request!(
            "If-Modified-Since" => now(),
            "If-None-Match" => "1234"
        ) as &mut conduit::Request));
    }

    #[test]
    fn test_does_not_affect_malformed_timestamp() {
        let bad_stamp = time::now().strftime("%Y-%m-%d %H:%M:%S %z");
        let handler = returning!("Last-Modified" => before_now());
        expect_200(handler.call(&mut request!(
            "If-Modified-Since" => bad_stamp
        ) as &mut conduit::Request));
    }

    fn expect_304(response: Result<Response, Box<Show>>) {
        let mut response = response.ok().expect("No response");
        let body = response.body.read_to_string().ok().expect("No body");

        assert_eq!(response.status, (304, "Not Modified"));
        assert_eq!(body.as_slice(), "");
    }

    fn expect_200(response: Result<Response, Box<Show>>) {
        expect((200, "OK"), response);
    }

    fn expect(status: (uint, &'static str), response: Result<Response, Box<Show>>) {
        let mut response = response.ok().expect("No response");
        let body = response.body.read_to_string().ok().expect("No body");

        assert_eq!(response.status, status);
        assert_eq!(body.as_slice(), "hello");
    }

    struct SimpleHandler {
        map: HashMap<String, Vec<String>>,
        status: Status,
        body: &'static str
    }

    type Status = (uint, &'static str);

    impl SimpleHandler {
        fn new(map: HashMap<String, Vec<String>>, status: Status, body: &'static str) -> SimpleHandler {
            SimpleHandler { map: map, status: status, body: body }
        }
    }

    impl Handler for SimpleHandler {
        fn call(&self, _: &mut Request) -> Result<Response, Box<Show + 'static>> {
            Ok(Response {
                status: self.status,
                headers: self.map.clone(),
                body: box MemReader::new(self.body.to_string().into_bytes()) as Box<Reader + Send>
            })
        }
    }

    fn before_now() -> String {
        let mut now = time::now();
        now.tm_year -= 1;
        httpdate(now)
    }

    fn now() -> String {
        httpdate(time::now())
    }

    fn httpdate(time: Tm) -> String {
        time.strftime("%a, %d-%m-%y %T GMT").unwrap()
    }
}
<commit_msg>Update to rust master<commit_after>#![feature(macro_rules)]

extern crate time;
extern crate conduit;
extern crate "conduit-middleware" as middleware;

use std::str::{MaybeOwned, Slice, Owned};
use std::fmt::Show;
use std::io::util::NullReader;
use time::{Tm, strptime, ParseError};
use conduit::Request;
use middleware::Middleware;

type Response = Result<conduit::Response, Box<Show + 'static>>;

pub struct ConditionalGet;

impl Middleware for ConditionalGet {
    fn after(&self, req: &mut Request, res: Response) -> Response {
        let mut res = try!(res);

        match req.method() {
            conduit::Get | conduit::Head => {
                if is_ok(&res) && is_fresh(req, &res) {
                    res.status = (304, "Not Modified");
                    res.headers.pop_equiv("Content-Type");
                    res.headers.pop_equiv("Content-Length");
                    res.body = box NullReader as Box<Reader + Send>;
                }
            },
            _ => ()
        }

        Ok(res)
    }
}

fn is_ok(response: &conduit::Response) -> bool {
    match *response {
        conduit::Response { status: (200, _), .. } => true,
        _ => false
    }
}

fn is_fresh(req: &Request, res: &conduit::Response) -> bool {
    let modified_since = req.headers().find("If-Modified-Since").map(header_val);
    let none_match     = req.headers().find("If-None-Match").map(header_val);

    if modified_since.as_ref().or_else(|| none_match.as_ref()).is_none() {
        return false;
    }

    let mut success = true;

    modified_since.and_then(|modified_since| {
        parse_http_date(modified_since).map_err(|_| { success = false; }).ok()
    }).map(|parsed| {
        success = success && is_modified_since(parsed, res);
    });

    none_match.map(|none_match| {
        success = success && etag_matches(none_match, res);
    });

    success
}

fn etag_matches<S: Str>(none_match: S, res: &conduit::Response) -> bool {
    res.headers.find_equiv("ETag").map(|etag| {
        res_header_val(etag).as_slice() == none_match.as_slice()
    }).unwrap_or(false)
}

fn is_modified_since(modified_since: Tm, res: &conduit::Response) -> bool {
    res.headers.find_equiv("Last-Modified").and_then(|last_modified| {
        parse_http_date(res_header_val(last_modified).as_slice()).ok()
    }).map(|last_modified| {
        modified_since.to_timespec() >= last_modified.to_timespec()
    }).unwrap_or(false)
}

fn header_val<'a>(header: Vec<&'a str>) -> MaybeOwned<'a> {
    if header.len() == 1 {
        Slice(header[0])
    } else {
        Owned(header.concat())
    }
}

fn res_header_val<'a>(header: &'a Vec<String>) -> MaybeOwned<'a> {
    if header.len() == 1 {
        Slice(header[0].as_slice())
    } else {
        Owned(header.concat())
    }
}

fn parse_http_date<S: Str>(string: S) -> Result<Tm, ()> {
    let string = string.as_slice();

    parse_rfc1123(string)
        .or_else(|_| parse_rfc850(string))
        .or_else(|_| parse_asctime(string))
        .map_err(|_| ())
}

fn parse_rfc1123(string: &str) -> Result<Tm, ParseError> {
    time::strptime(string, "%a, %d %b %Y %T GMT")
}

fn parse_rfc850(string: &str) -> Result<Tm, ParseError> {
    time::strptime(string, "%a, %d-%m-%y %T GMT")
}

fn parse_asctime(string: &str) -> Result<Tm, ParseError> {
    time::strptime(string, "%a %m%t%d %T %Y")
}

#[cfg(test)]
mod tests {
    extern crate "conduit-test" as test;

    use std::fmt::Show;
    use std::io::MemReader;
    use std::collections::HashMap;
    use time;
    use time::Tm;
    use conduit;
    use conduit::{Request, Response, Handler};
    use middleware::MiddlewareBuilder;

    use super::ConditionalGet;

    macro_rules! returning(
        ($code:expr, $($header:expr => $value:expr),+) => ({
            let mut headers = HashMap::new();
            $(headers.insert($header.to_string(), vec!($value.to_string()));)+
            let handler = SimpleHandler::new(headers, $code, "hello");
            let mut stack = MiddlewareBuilder::new(handler);
            stack.add(ConditionalGet);
            stack
        });
        ($($header:expr => $value:expr),+) => ({
            returning!((200, "OK"), $($header => $value),+)
        })
    )

    macro_rules! request(
        ($($header:expr => $value:expr),+) => ({
            let mut req = test::MockRequest::new(conduit::Get, "/");
            $(req.header($header, $value.to_string());)+
            req
        })
    )

    #[test]
    fn test_sends_304() {
        let handler = returning!("Last-Modified" => httpdate(time::now()));
        expect_304(handler.call(&mut request!(
            "If-Modified-Since" => httpdate(time::now())
        ) as &mut conduit::Request));
    }

    #[test]
    fn test_sends_304_if_older_than_now() {
        let handler = returning!("Last-Modified" => before_now());
        expect_304(handler.call(&mut request!(
            "If-Modified-Since" => httpdate(time::now())
        ) as &mut conduit::Request));
    }

    #[test]
    fn test_sends_304_with_etag() {
        let handler = returning!("ETag" => "1234");
        expect_304(handler.call(&mut request!(
            "If-None-Match" => "1234"
        ) as &mut conduit::Request));
    }

    #[test]
    fn test_sends_200_with_fresh_time_but_not_etag() {
        let handler = returning!("Last-Modified" => before_now(), "ETag" => "1234");
        expect_200(handler.call(&mut request!(
            "If-Modified-Since" => now(),
            "If-None-Match" => "4321"
        ) as &mut conduit::Request));
    }

    #[test]
    fn test_sends_200_with_fresh_etag_but_not_time() {
        let handler = returning!("Last-Modified" => now(), "ETag" => "1234");
        expect_200(handler.call(&mut request!(
            "If-Modified-Since" => before_now(),
            "If-None-Match" => "1234"
        ) as &mut conduit::Request));
    }

    #[test]
    fn test_sends_200_with_fresh_etag() {
        let handler = returning!("ETag" => "1234");
        expect_200(handler.call(&mut request!(
            "If-None-Match" => "4321"
        ) as &mut conduit::Request));
    }

    #[test]
    fn test_sends_200_with_fresh_time() {
        let handler = returning!("Last-Modified" => now());
        expect_200(handler.call(&mut request!(
            "If-Modified-Since" => before_now()
        ) as &mut conduit::Request));
    }

    #[test]
    fn test_sends_304_with_fresh_time_and_etag() {
        let handler = returning!("Last-Modified" => before_now(), "ETag" => "1234");
        expect_304(handler.call(&mut request!(
            "If-Modified-Since" => now(),
            "If-None-Match" => "1234"
        ) as &mut conduit::Request));
    }

    #[test]
    fn test_does_not_affect_non_200() {
        let code = (302, "Found");
        let handler = returning!(code, "Last-Modified" => before_now(), "ETag" => "1234");
        expect(code, handler.call(&mut request!(
            "If-Modified-Since" => now(),
            "If-None-Match" => "1234"
        ) as &mut conduit::Request));
    }

    #[test]
    fn test_does_not_affect_malformed_timestamp() {
        let bad_stamp = time::now().strftime("%Y-%m-%d %H:%M:%S %z");
        let handler = returning!("Last-Modified" => before_now());
        expect_200(handler.call(&mut request!(
            "If-Modified-Since" => bad_stamp
        ) as &mut conduit::Request));
    }

    fn expect_304(response: Result<Response, Box<Show>>) {
        let mut response = response.ok().expect("No response");
        let body = response.body.read_to_string().ok().expect("No body");

        assert_eq!(response.status, (304, "Not Modified"));
        assert_eq!(body.as_slice(), "");
    }

    fn expect_200(response: Result<Response, Box<Show>>) {
        expect((200, "OK"), response);
    }

    fn expect(status: (uint, &'static str), response: Result<Response, Box<Show>>) {
        let mut response = response.ok().expect("No response");
        let body = response.body.read_to_string().ok().expect("No body");

        assert_eq!(response.status, status);
        assert_eq!(body.as_slice(), "hello");
    }

    struct SimpleHandler {
        map: HashMap<String, Vec<String>>,
        status: Status,
        body: &'static str
    }

    type Status = (uint, &'static str);

    impl SimpleHandler {
        fn new(map: HashMap<String, Vec<String>>, status: Status, body: &'static str) -> SimpleHandler {
            SimpleHandler { map: map, status: status, body: body }
        }
    }

    impl Handler for SimpleHandler {
        fn call(&self, _: &mut Request) -> Result<Response, Box<Show + 'static>> {
            Ok(Response {
                status: self.status,
                headers: self.map.clone(),
                body: box MemReader::new(self.body.to_string().into_bytes()) as Box<Reader + Send>
            })
        }
    }

    fn before_now() -> String {
        let mut now = time::now();
        now.tm_year -= 1;
        httpdate(now)
    }

    fn now() -> String {
        httpdate(time::now())
    }

    fn httpdate(time: Tm) -> String {
        time.strftime("%a, %d-%m-%y %T GMT").unwrap()
    }
}
<|endoftext|>


<commit_before>#![deny(warnings)]

extern crate heatmap;
extern crate hsl;
extern crate png;
extern crate rusttype;

use heatmap::Heatmap;
use hsl::HSL;
use png::HasParameters;
use rusttype::{FontCollection, PixelsXY, PositionedGlyph, point};
use std::fs::File;
use std::io::BufWriter;
use std::path::Path;

const US: u64 = 1_000;
const MS: u64 = 1_000 * US;

#[allow(dead_code)]
pub struct Waterfall {
    config: Config,
}

pub struct Config {}

impl Default for Config {
    fn default() -> Config {
        Config {}
    }
}

impl Config {
    pub fn new() -> Config {
        Default::default()
    }

    pub fn build(self) -> Waterfall {
        Waterfall::configured(self)
    }
}

struct Label {
    value: u64,
    text: String,
}

impl Default for Waterfall {
    fn default() -> Waterfall {
        Waterfall::configured(Config::default())
    }
}

impl Waterfall {
    pub fn new() -> Waterfall {
        Default::default()
    }

    pub fn configure() -> Config {
        Config::default()
    }

    fn configured(config: Config) -> Waterfall {
        Waterfall { config: config }
    }

    pub fn render_png(&mut self, heatmap: &Heatmap, file: String) {
        let height = heatmap.num_slices() as usize;
        let width = heatmap.histogram_buckets() as usize;

        // build buffer from data
        let mut buffer = ImageBuffer::<ColorRgb>::new(width, height);
        let max = find_max(heatmap);
        let mut x;
        let mut y = 0;

        // loop to color the pixels
        for slice in heatmap {
            x = 0;
            for bucket in &slice.histogram() {
                let value = (bucket.count() as f64) / (bucket.width() as f64);
                let pixel = color_from_value(value, max);
                buffer.set_pixel(x, y, pixel);
                x += 1;
            }
            y += 1;
        }

        // latency annotations
        let labels: Vec<Label> = vec![
            Label {
                value: 200,
                text: "200nS".to_string(),
            },
            Label {
                value: 500,
                text: "500nS".to_string(),
            },
            Label {
                value: US,
                text: "1uS".to_string(),
            },
            Label {
                value: 2 * US,
                text: "2uS".to_string(),
            },
            Label {
                value: 5 * US,
                text: "5uS".to_string(),
            },
            Label {
                value: 10 * US,
                text: "10uS".to_string(),
            },
            Label {
                value: 20 * US,
                text: "20uS".to_string(),
            },
            Label {
                value: 50 * US,
                text: "50uS".to_string(),
            },
            Label {
                value: 100 * US,
                text: "100uS".to_string(),
            },
            Label {
                value: 200 * US,
                text: "200uS".to_string(),
            },
            Label {
                value: 500 * US,
                text: "500uS".to_string(),
            },
            Label {
                value: MS,
                text: "1mS".to_string(),
            },
            Label {
                value: 2 * MS,
                text: "2mS".to_string(),
            },
            Label {
                value: 5 * MS,
                text: "5mS".to_string(),
            },
            Label {
                value: 10 * MS,
                text: "10mS".to_string(),
            },
            Label {
                value: 20 * MS,
                text: "20mS".to_string(),
            },
            Label {
                value: 50 * MS,
                text: "50mS".to_string(),
            },
            Label {
                value: 100 * MS,
                text: "100mS".to_string(),
            },
            Label {
                value: 200 * MS,
                text: "200mS".to_string(),
            },
            Label {
                value: 500 * MS,
                text: "500mS".to_string(),
            },
        ];

        let mut l = 0;
        y = 0;

        for slice in heatmap {
            x = 0;
            for bucket in &slice.histogram() {
                if (y % 60) == 0 {
                    if x == 0 {
                        let hour = y / 3600;
                        let minute = y / 60;
                        let time = format!("{:02}:{:02}", hour, minute);
                        let overlay = string_buffer(&time, 25.0);
                        buffer.overlay(&overlay, x, y);
                        buffer.horizontal_line(y, ColorRgb { r: 0, g: 0, b: 0 });
                    }
                    let v = bucket.value();
                    if (l < labels.len()) && (v >= labels[l].value) {
                        let overlay = string_buffer(&labels[l].text, 25.0);
                        buffer.overlay(&overlay, x, y);
                        buffer.vertical_line(x, ColorRgb { r: 0, g: 0, b: 0 });
                        l += 1;
                    }
                }
                x += 1;
            }
            y += 1;
        }

        let _ = buffer.write_png(file.clone());
    }
}

fn find_max(heatmap: &Heatmap) -> f64 {
    let mut max = 0.0;

    for slice in heatmap {
        for bucket in &slice.histogram() {
            let value = (bucket.count() as f64) / (bucket.width() as f64);
            if value > max {
                max = value;
            }
        }
    }
    max
}

fn string_buffer(string: &str, size: f32) -> ImageBuffer<ColorRgb> {
    // load font
    let font_data = include_bytes!("../assets/ubuntumono/UbuntuMono-Regular.ttf");
    let collection = FontCollection::from_bytes(font_data as &[u8]);
    let font = collection.into_font().unwrap();

    // size and scaling
    let height: f32 = size;
    let pixel_height = height.ceil() as usize;
    let scale = PixelsXY(height * 1.0, height);

    let v_metrics = font.v_metrics(scale);
    let offset = point(0.0, v_metrics.ascent);

    let glyphs: Vec<PositionedGlyph> = font.layout(string, scale, offset).collect();

    let width = glyphs
        .iter()
        .map(|g| g.h_metrics().advance_width)
        .fold(0.0, |x, y| x + y)
        .ceil() as usize;

    let mut overlay = ImageBuffer::<ColorRgb>::new(width, pixel_height);

    for g in glyphs {
        if let Some(bb) = g.pixel_bounding_box() {
            g.draw(|x, y, v| {
                let x = (x as i32 + bb.min.x) as usize;
                let y = (y as i32 + bb.min.y) as usize;
                if v > 0.25 {
                    overlay.set_pixel(
                        x,
                        y,
                        ColorRgb {
                            r: 255,
                            g: 255,
                            b: 255,
                        },
                    );
                }
            })
        }
    }

    overlay
}

fn color_from_value(value: f64, max: f64) -> ColorRgb {
    let value = value / max;

    let knee = 0.10_f64;

    let hsl = if value < knee {
        let l = 0.25_f64 + 0.25_f64 * value / knee;
        HSL {
            h: 236_f64,
            s: 1_f64,
            l: l,
        }
    } else {
        let h_per_deg: f64 = 236_f64 / (1.0_f64 - knee);
        let deg = (value - knee) * h_per_deg;

        HSL {
            h: (236_f64 - deg),
            s: 1_f64,
            l: 0.50_f64,
        }
    };

    let (r, g, b) = hsl.to_rgb();

    ColorRgb { r: r, g: g, b: b }
}

#[derive(Clone, Copy, Debug, PartialEq)]
struct ColorRgb {
    r: u8,
    g: u8,
    b: u8,
}

struct ImageBuffer<T> {
    buffer: Vec<Vec<T>>,
    height: usize,
    width: usize,
}

impl ImageBuffer<ColorRgb> {
    pub fn new(width: usize, height: usize) -> ImageBuffer<ColorRgb> {
        let background = ColorRgb { r: 0, g: 0, b: 0 };
        let mut row = Vec::<ColorRgb>::with_capacity(width);
        for _ in 0..width {
            row.push(background);
        }
        let mut buffer = Vec::<Vec<ColorRgb>>::with_capacity(height);
        for _ in 0..height {
            buffer.push(row.clone());
        }
        ImageBuffer {
            buffer: buffer,
            height: height,
            width: width,
        }
    }

    pub fn write_png(self, file: String) -> Result<(), &'static str> {
        let mut buffer = Vec::<u8>::with_capacity(self.height * self.width);
        for row in 0..self.height {
            for col in 0..self.width {
                let pixel = self.buffer[row][col];
                buffer.push(pixel.r);
                buffer.push(pixel.g);
                buffer.push(pixel.b);
            }
        }
        let path = &Path::new(&file);
        if let Ok(file) = File::create(path) {
            let w = BufWriter::new(file);
            let mut encoder = png::Encoder::new(w, self.width as u32, self.height as u32);
            encoder.set(png::ColorType::RGB).set(png::BitDepth::Eight);
            if let Ok(mut writer) = encoder.write_header() {
                if writer.write_image_data(&buffer).is_ok() {
                    Ok(())
                } else {
                    Err("Error writing PNG data")
                }
            } else {
                Err("Error writing PNG header")
            }
        } else {
            Err("Error creating file")
        }
    }

    pub fn set_pixel(&mut self, x: usize, y: usize, value: ColorRgb) {
        if x < self.width && y < self.height {
            self.buffer[y][x] = value;
        }
    }

    pub fn overlay(&mut self, other: &ImageBuffer<ColorRgb>, x: usize, y: usize) {
        let ignore = ColorRgb { r: 0, g: 0, b: 0 };
        for sx in 0..other.width {
            for sy in 0..other.height {
                if (other.buffer[sy][sx] != ignore) &&
                    (((sy + y) < self.height) && ((sx + x) < self.width))
                {
                    self.buffer[(sy + y)][(sx + x)] = other.buffer[sy][sx];
                }
            }
        }
    }

    pub fn horizontal_line(&mut self, y: usize, color: ColorRgb) {
        for x in 0..self.width {
            self.buffer[y][x] = color;
        }
    }

    pub fn vertical_line(&mut self, x: usize, color: ColorRgb) {
        for y in 0..self.height {
            self.buffer[y][x] = color;
        }
    }
}
<commit_msg>fix: needless pass by value warnings (#21)<commit_after>#![deny(warnings)]

extern crate heatmap;
extern crate hsl;
extern crate png;
extern crate rusttype;

use heatmap::Heatmap;
use hsl::HSL;
use png::HasParameters;
use rusttype::{FontCollection, PixelsXY, PositionedGlyph, point};
use std::fs::File;
use std::io::BufWriter;
use std::path::Path;

const US: u64 = 1_000;
const MS: u64 = 1_000 * US;

#[allow(dead_code)]
pub struct Waterfall {
    config: Config,
}

pub struct Config {}

impl Default for Config {
    fn default() -> Config {
        Config {}
    }
}

impl Config {
    pub fn new() -> Config {
        Default::default()
    }

    pub fn build(self) -> Waterfall {
        Waterfall::configured(self)
    }
}

struct Label {
    value: u64,
    text: String,
}

impl Default for Waterfall {
    fn default() -> Waterfall {
        Waterfall::configured(Config::default())
    }
}

impl Waterfall {
    pub fn new() -> Waterfall {
        Default::default()
    }

    pub fn configure() -> Config {
        Config::default()
    }

    fn configured(config: Config) -> Waterfall {
        Waterfall { config: config }
    }

    pub fn render_png(&mut self, heatmap: &Heatmap, file: &str) {
        let height = heatmap.num_slices() as usize;
        let width = heatmap.histogram_buckets() as usize;

        // build buffer from data
        let mut buffer = ImageBuffer::<ColorRgb>::new(width, height);
        let max = find_max(heatmap);
        let mut x;
        let mut y = 0;

        // loop to color the pixels
        for slice in heatmap {
            x = 0;
            for bucket in &slice.histogram() {
                let value = (bucket.count() as f64) / (bucket.width() as f64);
                let pixel = color_from_value(value, max);
                buffer.set_pixel(x, y, pixel);
                x += 1;
            }
            y += 1;
        }

        // latency annotations
        let labels: Vec<Label> = vec![
            Label {
                value: 200,
                text: "200nS".to_string(),
            },
            Label {
                value: 500,
                text: "500nS".to_string(),
            },
            Label {
                value: US,
                text: "1uS".to_string(),
            },
            Label {
                value: 2 * US,
                text: "2uS".to_string(),
            },
            Label {
                value: 5 * US,
                text: "5uS".to_string(),
            },
            Label {
                value: 10 * US,
                text: "10uS".to_string(),
            },
            Label {
                value: 20 * US,
                text: "20uS".to_string(),
            },
            Label {
                value: 50 * US,
                text: "50uS".to_string(),
            },
            Label {
                value: 100 * US,
                text: "100uS".to_string(),
            },
            Label {
                value: 200 * US,
                text: "200uS".to_string(),
            },
            Label {
                value: 500 * US,
                text: "500uS".to_string(),
            },
            Label {
                value: MS,
                text: "1mS".to_string(),
            },
            Label {
                value: 2 * MS,
                text: "2mS".to_string(),
            },
            Label {
                value: 5 * MS,
                text: "5mS".to_string(),
            },
            Label {
                value: 10 * MS,
                text: "10mS".to_string(),
            },
            Label {
                value: 20 * MS,
                text: "20mS".to_string(),
            },
            Label {
                value: 50 * MS,
                text: "50mS".to_string(),
            },
            Label {
                value: 100 * MS,
                text: "100mS".to_string(),
            },
            Label {
                value: 200 * MS,
                text: "200mS".to_string(),
            },
            Label {
                value: 500 * MS,
                text: "500mS".to_string(),
            },
        ];

        let mut l = 0;
        y = 0;

        for slice in heatmap {
            x = 0;
            for bucket in &slice.histogram() {
                if (y % 60) == 0 {
                    if x == 0 {
                        let hour = y / 3600;
                        let minute = y / 60;
                        let time = format!("{:02}:{:02}", hour, minute);
                        let overlay = string_buffer(&time, 25.0);
                        buffer.overlay(&overlay, x, y);
                        buffer.horizontal_line(y, ColorRgb { r: 0, g: 0, b: 0 });
                    }
                    let v = bucket.value();
                    if (l < labels.len()) && (v >= labels[l].value) {
                        let overlay = string_buffer(&labels[l].text, 25.0);
                        buffer.overlay(&overlay, x, y);
                        buffer.vertical_line(x, ColorRgb { r: 0, g: 0, b: 0 });
                        l += 1;
                    }
                }
                x += 1;
            }
            y += 1;
        }

        let _ = buffer.write_png(file);
    }
}

fn find_max(heatmap: &Heatmap) -> f64 {
    let mut max = 0.0;

    for slice in heatmap {
        for bucket in &slice.histogram() {
            let value = (bucket.count() as f64) / (bucket.width() as f64);
            if value > max {
                max = value;
            }
        }
    }
    max
}

fn string_buffer(string: &str, size: f32) -> ImageBuffer<ColorRgb> {
    // load font
    let font_data = include_bytes!("../assets/ubuntumono/UbuntuMono-Regular.ttf");
    let collection = FontCollection::from_bytes(font_data as &[u8]);
    let font = collection.into_font().unwrap();

    // size and scaling
    let height: f32 = size;
    let pixel_height = height.ceil() as usize;
    let scale = PixelsXY(height * 1.0, height);

    let v_metrics = font.v_metrics(scale);
    let offset = point(0.0, v_metrics.ascent);

    let glyphs: Vec<PositionedGlyph> = font.layout(string, scale, offset).collect();

    let width = glyphs
        .iter()
        .map(|g| g.h_metrics().advance_width)
        .fold(0.0, |x, y| x + y)
        .ceil() as usize;

    let mut overlay = ImageBuffer::<ColorRgb>::new(width, pixel_height);

    for g in glyphs {
        if let Some(bb) = g.pixel_bounding_box() {
            g.draw(|x, y, v| {
                let x = (x as i32 + bb.min.x) as usize;
                let y = (y as i32 + bb.min.y) as usize;
                if v > 0.25 {
                    overlay.set_pixel(
                        x,
                        y,
                        ColorRgb {
                            r: 255,
                            g: 255,
                            b: 255,
                        },
                    );
                }
            })
        }
    }

    overlay
}

fn color_from_value(value: f64, max: f64) -> ColorRgb {
    let value = value / max;

    let knee = 0.10_f64;

    let hsl = if value < knee {
        let l = 0.25_f64 + 0.25_f64 * value / knee;
        HSL {
            h: 236_f64,
            s: 1_f64,
            l: l,
        }
    } else {
        let h_per_deg: f64 = 236_f64 / (1.0_f64 - knee);
        let deg = (value - knee) * h_per_deg;

        HSL {
            h: (236_f64 - deg),
            s: 1_f64,
            l: 0.50_f64,
        }
    };

    let (r, g, b) = hsl.to_rgb();

    ColorRgb { r: r, g: g, b: b }
}

#[derive(Clone, Copy, Debug, PartialEq)]
struct ColorRgb {
    r: u8,
    g: u8,
    b: u8,
}

struct ImageBuffer<T> {
    buffer: Vec<Vec<T>>,
    height: usize,
    width: usize,
}

impl ImageBuffer<ColorRgb> {
    pub fn new(width: usize, height: usize) -> ImageBuffer<ColorRgb> {
        let background = ColorRgb { r: 0, g: 0, b: 0 };
        let mut row = Vec::<ColorRgb>::with_capacity(width);
        for _ in 0..width {
            row.push(background);
        }
        let mut buffer = Vec::<Vec<ColorRgb>>::with_capacity(height);
        for _ in 0..height {
            buffer.push(row.clone());
        }
        ImageBuffer {
            buffer: buffer,
            height: height,
            width: width,
        }
    }

    pub fn write_png(self, file: &str) -> Result<(), &'static str> {
        let mut buffer = Vec::<u8>::with_capacity(self.height * self.width);
        for row in 0..self.height {
            for col in 0..self.width {
                let pixel = self.buffer[row][col];
                buffer.push(pixel.r);
                buffer.push(pixel.g);
                buffer.push(pixel.b);
            }
        }
        let path = &Path::new(&file);
        if let Ok(file) = File::create(path) {
            let w = BufWriter::new(file);
            let mut encoder = png::Encoder::new(w, self.width as u32, self.height as u32);
            encoder.set(png::ColorType::RGB).set(png::BitDepth::Eight);
            if let Ok(mut writer) = encoder.write_header() {
                if writer.write_image_data(&buffer).is_ok() {
                    Ok(())
                } else {
                    Err("Error writing PNG data")
                }
            } else {
                Err("Error writing PNG header")
            }
        } else {
            Err("Error creating file")
        }
    }

    pub fn set_pixel(&mut self, x: usize, y: usize, value: ColorRgb) {
        if x < self.width && y < self.height {
            self.buffer[y][x] = value;
        }
    }

    pub fn overlay(&mut self, other: &ImageBuffer<ColorRgb>, x: usize, y: usize) {
        let ignore = ColorRgb { r: 0, g: 0, b: 0 };
        for sx in 0..other.width {
            for sy in 0..other.height {
                if (other.buffer[sy][sx] != ignore) &&
                    (((sy + y) < self.height) && ((sx + x) < self.width))
                {
                    self.buffer[(sy + y)][(sx + x)] = other.buffer[sy][sx];
                }
            }
        }
    }

    pub fn horizontal_line(&mut self, y: usize, color: ColorRgb) {
        for x in 0..self.width {
            self.buffer[y][x] = color;
        }
    }

    pub fn vertical_line(&mut self, x: usize, color: ColorRgb) {
        for y in 0..self.height {
            self.buffer[y][x] = color;
        }
    }
}
<|endoftext|>