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

#[derive(Copy, Clone, PartialEq, Eq)]
pub enum Token {
    Output,
    Input,
    Loop,
    EndLoop,
    Move(i32),
    Add(i32, i32),
    Set(i32, i32),
    MulCopy(i32, i32, i32),
    Scan(i32),
    LoadOut(i32, i32),
    LoadOutSet(i32),
    If(i32),
    EndIf,
}
use Token::*;

impl fmt::Debug for Token {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Output =>
                write!(f, "Output"),
            Input =>
                write!(f, "Input"),
            Loop =>
                write!(f, "Loop"),
            EndLoop =>
                write!(f, "EndLoop"),
            Move(offset) =>
                write!(f, "Move(offset={})", offset),
            Add(offset, value) =>
                write!(f, "Add(offset={}, value={})", offset, value),
            Set(offset, value) =>
                write!(f, "Set(offset={}, value={})", offset, value),
            MulCopy(src, dest, mul) =>
                write!(f, "MulCopy(src={}, dest={}, mul={})", src, dest, mul),
            Scan(offset) =>
                write!(f, "Scan(offset={})", offset),
            LoadOut(offset, add) =>
                write!(f, "LoadOut(offset={}, add={})", offset, add),
            LoadOutSet(value) =>
                write!(f, "LoadOutSet(value={})", value),
            If(offset) =>
                write!(f, "If(offset={})", offset),
            EndIf =>
                write!(f, "EndIf\n"),
        }
    }
}

pub fn parse(code: &str) -> Vec<Token> {
    let mut tokens = Vec::with_capacity(code.len());
    for i in code.chars() {
        match i {
            '+' => tokens.push(Add(0, 1)),
            '-' => tokens.push(Add(0, -1)),
            '>' => tokens.push(Move(1)),
            '<' => tokens.push(Move(-1)),
            '[' => tokens.push(Loop),
            ']' => tokens.push(EndLoop),
            ',' => tokens.push(Input),
            '.' => {
                tokens.push(LoadOut(0, 0));
                tokens.push(Output);
            },
            _ => ()
        };
    }

    tokens
    }

pub fn optimize(tokens: Vec<Token>) -> Vec<Token> {
    let mut newtokens: Vec<Token> = Vec::with_capacity(tokens.len());
    let mut shift = 0;
    let mut do_output = false;
    // With HashMap, the order sometimes switches
    // in recursion, and the optimizer never exits.
    let mut adds: BTreeMap<i32, i32> = BTreeMap::new();
    let mut sets: BTreeMap<i32, i32> = BTreeMap::new();
    let mut pre_loop_sets: BTreeMap<i32, i32> = BTreeMap::new();

    for token in tokens.iter() {
        if *token == EndLoop && newtokens.last() == Some(&Loop) && shift == 0 && adds.contains_key(&0) {
            if adds.len() == 1 {
                newtokens.pop(); // Remove Loop
                if !sets.is_empty() {
                    newtokens.push(If(0));
                    for (offset, value) in sets.iter() {
                        newtokens.push(Set(*offset, *value));
                    }
                    sets.clear();
                    newtokens.push(Set(0, 0));
                    newtokens.push(EndIf);
                } else {
                    sets.insert(0, 0);
                }
                pre_loop_sets.clear();
                adds.clear();
                continue
            } else if adds.get(&0) == Some(&-1) {
                newtokens.pop(); // Remove Loop
                if !sets.is_empty() {
                    newtokens.push(If(0));
                    for (offset, value) in sets.iter() {
                        newtokens.push(Set(*offset, *value));
                    }
                }
                for (offset, value) in adds.iter() {
                    if *offset != 0 {
                        let src = 0;
                        let dest = *offset;
                        let mul = *value;
                        if pre_loop_sets.contains_key(&src) {
                            let val = pre_loop_sets.get(&src).unwrap() * mul;
                            newtokens.push(Add(dest, val));
                        } else {
                            newtokens.push(MulCopy(src, dest, mul));
                        }
                    }
                }
                if !sets.is_empty() {
                    newtokens.push(EndIf);
                }
                pre_loop_sets.clear();
                adds.clear();
                sets.clear();
                sets.insert(0, 0);
                continue
            }
        }

        match *token {
            Loop => {
                pre_loop_sets.clear();
                for (offset, value) in sets.iter() {
                    pre_loop_sets.insert(*offset+shift, *value);
                }
            },
            Set(..) | Add(..) | Move(_) => {},
            _ => pre_loop_sets.clear()
        }

        match *token {
            Set(..) | Add(..) | Move(_) | LoadOut(..) | LoadOutSet(_) | Output => {},
            _ => {
               if do_output {
                   newtokens.push(Output);
                   do_output = false;
               }

               for (offset, value) in sets.iter() {
                  newtokens.push(Set(*offset, *value));
               }
               for (offset, value) in adds.iter() {
                  newtokens.push(Add(*offset, *value));
               }
               sets.clear();
               adds.clear();
            }
        }

        if shift != 0 {
           match *token {
               Loop | Input | Scan(_) => {
                   newtokens.push(Move(shift));
                   shift = 0;
               },
               _ => {}
           }
        }

        match *token {
            Set(mut offset, val) => {
                offset += shift;
                // Add before Set does nothing; remove it
                adds.remove(&offset);
                sets.insert(offset, val);
            },
            Add(mut offset, mut val) => {
                offset += shift;
                if sets.contains_key(&offset) {
                    val = sets.get(&offset).unwrap() + val;
                    sets.insert(offset, val);
                } else {
                    val = adds.get(&offset).unwrap_or(&0) + val;
                    adds.insert(offset, val);
                }
            },
            MulCopy(src, dest, mul) =>
                newtokens.push(MulCopy(src+shift, dest+shift, mul)),
            // XXX Deal with shift in if, if those are ever generated
            If(offset) =>
                newtokens.push(If(offset+shift)),
            Move(offset) =>
                shift += offset,
            Output =>
                do_output = true,
            LoadOut(mut offset, add) => {
                offset += shift;
                if sets.contains_key(&offset) {
                    newtokens.push(LoadOutSet(sets.get(&offset).unwrap() + add));
                } else {
                    newtokens.push(LoadOut(offset, adds.get(&offset).unwrap_or(&0) + add));
                }
            },
            EndLoop => {
                if newtokens.last() == Some(&Loop) && shift != 0 && sets.is_empty() && adds.is_empty() {
                    newtokens.pop(); // Remove StartLoop
                    newtokens.push(Scan(shift));
                } else {
                    if shift != 0 {
                        newtokens.push(Move(shift));
                    }
                    newtokens.push(EndLoop);
                }
                shift = 0;
            },
            EndIf | LoadOutSet(_) | Loop | Input | Scan(_) =>
                newtokens.push(*token),
        }
    }

    // Any remaining add/set/shift is ignored, as it would have no effect
    if do_output {
        newtokens.push(Output);
    }

    // Optimize recursively
    if newtokens != tokens {
        optimize(newtokens)
    } else {
        newtokens
    }
}
<commit_msg>Correct indentation<commit_after>use std::collections::BTreeMap;
use std::fmt;

#[derive(Copy, Clone, PartialEq, Eq)]
pub enum Token {
    Output,
    Input,
    Loop,
    EndLoop,
    Move(i32),
    Add(i32, i32),
    Set(i32, i32),
    MulCopy(i32, i32, i32),
    Scan(i32),
    LoadOut(i32, i32),
    LoadOutSet(i32),
    If(i32),
    EndIf,
}
use Token::*;

impl fmt::Debug for Token {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Output =>
                write!(f, "Output"),
            Input =>
                write!(f, "Input"),
            Loop =>
                write!(f, "Loop"),
            EndLoop =>
                write!(f, "EndLoop"),
            Move(offset) =>
                write!(f, "Move(offset={})", offset),
            Add(offset, value) =>
                write!(f, "Add(offset={}, value={})", offset, value),
            Set(offset, value) =>
                write!(f, "Set(offset={}, value={})", offset, value),
            MulCopy(src, dest, mul) =>
                write!(f, "MulCopy(src={}, dest={}, mul={})", src, dest, mul),
            Scan(offset) =>
                write!(f, "Scan(offset={})", offset),
            LoadOut(offset, add) =>
                write!(f, "LoadOut(offset={}, add={})", offset, add),
            LoadOutSet(value) =>
                write!(f, "LoadOutSet(value={})", value),
            If(offset) =>
                write!(f, "If(offset={})", offset),
            EndIf =>
                write!(f, "EndIf\n"),
        }
    }
}

pub fn parse(code: &str) -> Vec<Token> {
    let mut tokens = Vec::with_capacity(code.len());
    for i in code.chars() {
        match i {
            '+' => tokens.push(Add(0, 1)),
            '-' => tokens.push(Add(0, -1)),
            '>' => tokens.push(Move(1)),
            '<' => tokens.push(Move(-1)),
            '[' => tokens.push(Loop),
            ']' => tokens.push(EndLoop),
            ',' => tokens.push(Input),
            '.' => {
                tokens.push(LoadOut(0, 0));
                tokens.push(Output);
            },
            _ => ()
        };
    }

    tokens
}

pub fn optimize(tokens: Vec<Token>) -> Vec<Token> {
    let mut newtokens: Vec<Token> = Vec::with_capacity(tokens.len());
    let mut shift = 0;
    let mut do_output = false;
    // With HashMap, the order sometimes switches
    // in recursion, and the optimizer never exits.
    let mut adds: BTreeMap<i32, i32> = BTreeMap::new();
    let mut sets: BTreeMap<i32, i32> = BTreeMap::new();
    let mut pre_loop_sets: BTreeMap<i32, i32> = BTreeMap::new();

    for token in tokens.iter() {
        if *token == EndLoop && newtokens.last() == Some(&Loop) && shift == 0 && adds.contains_key(&0) {
            if adds.len() == 1 {
                newtokens.pop(); // Remove Loop
                if !sets.is_empty() {
                    newtokens.push(If(0));
                    for (offset, value) in sets.iter() {
                        newtokens.push(Set(*offset, *value));
                    }
                    sets.clear();
                    newtokens.push(Set(0, 0));
                    newtokens.push(EndIf);
                } else {
                    sets.insert(0, 0);
                }
                pre_loop_sets.clear();
                adds.clear();
                continue
            } else if adds.get(&0) == Some(&-1) {
                newtokens.pop(); // Remove Loop
                if !sets.is_empty() {
                    newtokens.push(If(0));
                    for (offset, value) in sets.iter() {
                        newtokens.push(Set(*offset, *value));
                    }
                }
                for (offset, value) in adds.iter() {
                    if *offset != 0 {
                        let src = 0;
                        let dest = *offset;
                        let mul = *value;
                        if pre_loop_sets.contains_key(&src) {
                            let val = pre_loop_sets.get(&src).unwrap() * mul;
                            newtokens.push(Add(dest, val));
                        } else {
                            newtokens.push(MulCopy(src, dest, mul));
                        }
                    }
                }
                if !sets.is_empty() {
                    newtokens.push(EndIf);
                }
                pre_loop_sets.clear();
                adds.clear();
                sets.clear();
                sets.insert(0, 0);
                continue
            }
        }

        match *token {
            Loop => {
                pre_loop_sets.clear();
                for (offset, value) in sets.iter() {
                    pre_loop_sets.insert(*offset+shift, *value);
                }
            },
            Set(..) | Add(..) | Move(_) => {},
            _ => pre_loop_sets.clear()
        }

        match *token {
            Set(..) | Add(..) | Move(_) | LoadOut(..) | LoadOutSet(_) | Output => {},
            _ => {
               if do_output {
                   newtokens.push(Output);
                   do_output = false;
               }

               for (offset, value) in sets.iter() {
                  newtokens.push(Set(*offset, *value));
               }
               for (offset, value) in adds.iter() {
                  newtokens.push(Add(*offset, *value));
               }
               sets.clear();
               adds.clear();
            }
        }

        if shift != 0 {
           match *token {
               Loop | Input | Scan(_) => {
                   newtokens.push(Move(shift));
                   shift = 0;
               },
               _ => {}
           }
        }

        match *token {
            Set(mut offset, val) => {
                offset += shift;
                // Add before Set does nothing; remove it
                adds.remove(&offset);
                sets.insert(offset, val);
            },
            Add(mut offset, mut val) => {
                offset += shift;
                if sets.contains_key(&offset) {
                    val = sets.get(&offset).unwrap() + val;
                    sets.insert(offset, val);
                } else {
                    val = adds.get(&offset).unwrap_or(&0) + val;
                    adds.insert(offset, val);
                }
            },
            MulCopy(src, dest, mul) =>
                newtokens.push(MulCopy(src+shift, dest+shift, mul)),
            // XXX Deal with shift in if, if those are ever generated
            If(offset) =>
                newtokens.push(If(offset+shift)),
            Move(offset) =>
                shift += offset,
            Output =>
                do_output = true,
            LoadOut(mut offset, add) => {
                offset += shift;
                if sets.contains_key(&offset) {
                    newtokens.push(LoadOutSet(sets.get(&offset).unwrap() + add));
                } else {
                    newtokens.push(LoadOut(offset, adds.get(&offset).unwrap_or(&0) + add));
                }
            },
            EndLoop => {
                if newtokens.last() == Some(&Loop) && shift != 0 && sets.is_empty() && adds.is_empty() {
                    newtokens.pop(); // Remove StartLoop
                    newtokens.push(Scan(shift));
                } else {
                    if shift != 0 {
                        newtokens.push(Move(shift));
                    }
                    newtokens.push(EndLoop);
                }
                shift = 0;
            },
            EndIf | LoadOutSet(_) | Loop | Input | Scan(_) =>
                newtokens.push(*token),
        }
    }

    // Any remaining add/set/shift is ignored, as it would have no effect
    if do_output {
        newtokens.push(Output);
    }

    // Optimize recursively
    if newtokens != tokens {
        optimize(newtokens)
    } else {
        newtokens
    }
}
<|endoftext|>


<commit_before>//! # Mime
//!
//! Mime is now Media Type, technically, but `Mime` is more immediately
//! understandable, so the main type here is `Mime`.
//!
//! ## What is Mime?
//!
//! Example mime string: `text/plain;charset=utf-8`
//!
//! ```rust
//! # use std::from_str::FromStr;
//! use mime::{Mime, Text, Plain, Charset, Utf8};
//! let mime: Mime = FromStr::from_str("text/plain;charset=utf-8").unwrap();
//! assert_eq!(mime, Mime(Text, Plain, vec![(Charset, Utf8)]));
//! ```

#![license = "MIT"]
#![doc(html_root_url = "http://seanmonstar.github.io/mime.rs")]
#![experimental]
#![feature(macro_rules, phase)]

#[phase(plugin, link)]
extern crate log;

#[cfg(test)]
extern crate test;

use std::ascii::OwnedAsciiExt;
use std::cmp::Equiv;
use std::fmt;
use std::from_str::FromStr;
use std::iter::Enumerate;
use std::str::Chars;

macro_rules! inspect(
    ($s:expr, $t:expr) => ({
        let t = $t;
        debug!("inspect {}: {}", $s, t);
        t
    })
)

/// Mime, or Media Type. Encapsulates common registers types.
///
/// Consider that a traditional mime type contains a "top level type",
/// a "sub level type", and 0-N "parameters". And they're all strings.
/// Strings everywhere. Strings mean typos. Rust has type safety. We should
/// use types!
///
/// So, Mime bundles together this data into types so the compiler can catch
/// your typos.
///
/// This improves things so you use match without Strings:
///
/// ```rust
/// use std::from_str::from_str;
/// use mime::{Mime, Application, Json};
///
/// let mime: mime::Mime = from_str("application/json").unwrap();
///
/// match mime {
///     Mime(Application, Json, _) => println!("matched json!"),
///     _ => ()
/// }
/// ```
#[deriving(Clone, PartialEq)]
pub struct Mime(pub TopLevel, pub SubLevel, pub Vec<Param>);

macro_rules! enoom (
    (pub enum $en:ident; $ext:ident; $($ty:ident, $text:expr;)*) => (

        #[deriving(Clone, PartialEq)]
        pub enum $en {
            $($ty),*,
            $ext(String)
        }

        impl fmt::Show for $en {
            fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
                match *self {
                    $($ty => $text),*,
                    $ext(ref s) => return s.fmt(fmt)
                }.fmt(fmt)
            }
        }

        impl FromStr for $en {
            fn from_str(s: &str) -> Option<$en> {
                Some(match s {
                    $(_s if _s == $text => $ty),*,
                    s => $ext(inspect!(stringify!($ext), s).to_string())
                })
            }
        }
    )
)

enoom! {
    pub enum TopLevel;
    TopExt;
    TopStar, "*"; // remove Top prefix if enums gain namespaces
    Text, "text";
    Image, "image";
    Audio, "audio";
    Video, "video";
    Application, "application";
    Multipart, "multipart";
    Message, "message";
    Model, "model";
}

enoom! {
    pub enum SubLevel;
    SubExt;
    SubStar, "*"; // remove Sub prefix if enums gain namespaces

    // common text/*
    Plain, "plain";
    Html, "html";
    Xml, "xml";
    Javascript, "javascript";
    Css, "css";

    // common application/*
    Json, "json";

    // common image/*
    Png, "png";
    Gif, "gif";
    Bmp, "bmp";
    Jpeg, "jpeg";
}

enoom! {
    pub enum Attr;
    AttrExt;
    Charset, "charset";
    Q, "q";
}

enoom! {
    pub enum Value;
    ValueExt;
    Utf8, "utf-8";
}

pub type Param = (Attr, Value);

impl Equiv<Mime> for Mime {
    fn equiv(&self, other: &Mime) -> bool {
        //im so sorry
        //TODO: be less sorry. dont to_string()
        self.to_string() == other.to_string()
    }
}

impl fmt::Show for Mime {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        let Mime(ref top, ref sub, ref params) = *self;
        try!(write!(fmt, "{}/{}", top, sub));
        fmt_params(params.as_slice(), fmt)
    }
}

impl FromStr for Mime {
    fn from_str(raw: &str) -> Option<Mime> {
        let ascii = raw.to_string().into_ascii_lower(); // lifetimes :(
        let raw = ascii.as_slice();
        let len = raw.len();
        let mut iter = raw.chars().enumerate();
        let mut params = vec![];
        // toplevel
        let mut start;
        let mut top;
        loop {
            match inspect!("top iter", iter.next()) {
                Some((0, c)) if is_restricted_name_first_char(c) => (),
                Some((i, c)) if i > 0 && is_restricted_name_char(c) => (),
                Some((i, '/')) if i > 0 => match FromStr::from_str(raw.slice_to(i)) {
                    Some(t) => {
                        top = t;
                        start = i + 1;
                        break;
                    }
                    None => return None
                },
                _ => return None // EOF and no toplevel is no Mime
            };

        }

        // sublevel
        let mut sub;
        loop {
            match inspect!("sub iter", iter.next()) {
                Some((i, c)) if i == start && is_restricted_name_first_char(c) => (),
                Some((i, c)) if i > start && is_restricted_name_char(c) => (),
                Some((i, ';')) if i > start => match FromStr::from_str(raw.slice(start, i)) {
                    Some(s) => {
                        sub = s;
                        start = i + 1;
                        break;
                    }
                    None => return None
                },
                None => match FromStr::from_str(raw.slice_from(start)) {
                    Some(s) => return Some(Mime(top, s, params)),
                    None => return None
                },
                _ => return None
            };
        }

        // params
        debug!("starting params, len={}", len);
        loop {
            match inspect!("param", param_from_str(raw, &mut iter, start)) {
                Some((p, end)) => {
                    params.push(p);
                    start = end;
                    if start >= len {
                        break;
                    }
                }
                None => break
            }
        }

        Some(Mime(top, sub, params))
    }
}

fn param_from_str(raw: &str, iter: &mut Enumerate<Chars>, mut start: uint) -> Option<(Param, uint)> {
    let mut attr;
    debug!("param_from_str, start={}", start);
    loop {
        match inspect!("attr iter", iter.next()) {
            Some((i, ' ')) if i == start => start = i + 1,
            Some((i, c)) if i == start && is_restricted_name_first_char(c) => (),
            Some((i, c)) if i > start && is_restricted_name_char(c) => (),
            Some((i, '=')) if i > start => match FromStr::from_str(raw.slice(start, i)) {
                Some(a) => {
                    attr = inspect!("attr", a);
                    start = i + 1;
                    break;
                },
                None => return None
            },
            _ => return None
        }
    }
    let mut value;
    // values must be restrict-name-char or "anything goes"
    let mut is_quoted = false;
    loop {
        match inspect!("value iter", iter.next()) {
            Some((i, '"')) if i == start => {
                debug!("quoted");
                is_quoted = true;
                start = i + 1;
            },
            Some((i, c)) if i == start && is_restricted_name_first_char(c) => (),
            Some((i, '"')) if i > start && is_quoted => match FromStr::from_str(raw.slice(start, i)) {
                Some(v) => {
                    value = v;
                    start = i + 1;
                    break;
                },
                None => return None
            },
            Some((i, c)) if i > start && is_quoted || is_restricted_name_char(c) => (),
            Some((i, ';')) if i > start => match FromStr::from_str(raw.slice(start, i)) {
                Some(v) => {
                    value = v;
                    start = i + 1;
                    break;
                },
                None => return None
            },
            None => match FromStr::from_str(raw.slice_from(start)) {
                Some(v) => {
                    value = v;
                    start = raw.len();
                    break;
                },
                None => return None
            },

            _ => return None
        }
    }

    Some(((attr, value), start))
}

// From [RFC6838](http://tools.ietf.org/html/rfc6838#section-4.2):
//
// > All registered media types MUST be assigned top-level type and
// > subtype names.  The combination of these names serves to uniquely
// > identify the media type, and the subtype name facet (or the absence
// > of one) identifies the registration tree.  Both top-level type and
// > subtype names are case-insensitive.
// >
// > Type and subtype names MUST conform to the following ABNF:
// >
// >     type-name = restricted-name
// >     subtype-name = restricted-name
// >
// >     restricted-name = restricted-name-first *126restricted-name-chars
// >     restricted-name-first  = ALPHA / DIGIT
// >     restricted-name-chars  = ALPHA / DIGIT / "!" / "#" /
// >                              "$" / "&" / "-" / "^" / "_"
// >     restricted-name-chars =/ "." ; Characters before first dot always
// >                                  ; specify a facet name
// >     restricted-name-chars =/ "+" ; Characters after last plus always
// >                                  ; specify a structured syntax suffix
//
fn is_restricted_name_first_char(c: char) -> bool {
    match c {
        'a'...'z' |
        '0'...'9' => true,
        _ => false
    }
}

fn is_restricted_name_char(c: char) -> bool {
    if is_restricted_name_first_char(c) {
        true
    } else {
        match c {
            '!' |
            '#' |
            '$' |
            '&' |
            '-' |
            '^' |
            '.' |
            '+' |
            '_' => true,
            _ => false
        }
    }
}

#[inline]
fn fmt_params<T: AsSlice<Param>>(params: T, fmt: &mut fmt::Formatter) -> fmt::Result {
    for param in params.as_slice().iter() {
        try!(fmt_param(param, fmt));
    }
    Ok(())
}

#[inline]
fn fmt_param(param: &Param, fmt: &mut fmt::Formatter) -> fmt::Result {
    let (ref attr, ref value) = *param;
    write!(fmt, "; {}={}", attr, value)
}

#[cfg(test)]
mod tests {
    use std::from_str::{FromStr, from_str};
    use test::Bencher;
    use super::{Mime, Text, Plain, Charset, Utf8, AttrExt, ValueExt};

    #[test]
    fn test_mime_show() {
        let mime = Mime(Text, Plain, vec![]);
        assert_eq!(mime.to_string(), "text/plain".to_string());
        let mime = Mime(Text, Plain, vec![(Charset, Utf8)]);
        assert_eq!(mime.to_string(), "text/plain; charset=utf-8".to_string());
    }

    #[test]
    fn test_mime_from_str() {
        assert_eq!(FromStr::from_str("text/plain"), Some(Mime(Text, Plain, vec![])));
        assert_eq!(FromStr::from_str("TEXT/PLAIN"), Some(Mime(Text, Plain, vec![])));
        assert_eq!(FromStr::from_str("text/plain; charset=utf-8"), Some(Mime(Text, Plain, vec![(Charset, Utf8)])));
        assert_eq!(FromStr::from_str("text/plain;charset=\"utf-8\""), Some(Mime(Text, Plain, vec![(Charset, Utf8)])));
        assert_eq!(FromStr::from_str("text/plain; charset=utf-8; foo=bar"),
            Some(Mime(Text, Plain, vec![(Charset, Utf8),
                                        (AttrExt("foo".to_string()), ValueExt("bar".to_string())) ])));
    }

    #[bench]
    fn bench_show(b: &mut Bencher) {
        let mime = Mime(Text, Plain, vec![(Charset, Utf8), (AttrExt("foo".to_string()), ValueExt("bar".to_string()))]);
        b.bytes = mime.to_string().as_bytes().len() as u64;
        b.iter(|| mime.to_string())
    }

    #[bench]
    fn bench_from_str(b: &mut Bencher) {
        let s = "text/plain; charset=utf-8; foo=bar";
        b.bytes = s.as_bytes().len() as u64;
        b.iter(|| from_str::<Mime>(s))
    }
}
<commit_msg>Use AsciiExt instead of OwnedAsciiExt<commit_after>//! # Mime
//!
//! Mime is now Media Type, technically, but `Mime` is more immediately
//! understandable, so the main type here is `Mime`.
//!
//! ## What is Mime?
//!
//! Example mime string: `text/plain;charset=utf-8`
//!
//! ```rust
//! # use std::from_str::FromStr;
//! use mime::{Mime, Text, Plain, Charset, Utf8};
//! let mime: Mime = FromStr::from_str("text/plain;charset=utf-8").unwrap();
//! assert_eq!(mime, Mime(Text, Plain, vec![(Charset, Utf8)]));
//! ```

#![license = "MIT"]
#![doc(html_root_url = "http://seanmonstar.github.io/mime.rs")]
#![experimental]
#![feature(macro_rules, phase)]

#[phase(plugin, link)]
extern crate log;

#[cfg(test)]
extern crate test;

use std::ascii::AsciiExt;
use std::cmp::Equiv;
use std::fmt;
use std::from_str::FromStr;
use std::iter::Enumerate;
use std::str::Chars;

macro_rules! inspect(
    ($s:expr, $t:expr) => ({
        let t = $t;
        debug!("inspect {}: {}", $s, t);
        t
    })
)

/// Mime, or Media Type. Encapsulates common registers types.
///
/// Consider that a traditional mime type contains a "top level type",
/// a "sub level type", and 0-N "parameters". And they're all strings.
/// Strings everywhere. Strings mean typos. Rust has type safety. We should
/// use types!
///
/// So, Mime bundles together this data into types so the compiler can catch
/// your typos.
///
/// This improves things so you use match without Strings:
///
/// ```rust
/// use std::from_str::from_str;
/// use mime::{Mime, Application, Json};
///
/// let mime: mime::Mime = from_str("application/json").unwrap();
///
/// match mime {
///     Mime(Application, Json, _) => println!("matched json!"),
///     _ => ()
/// }
/// ```
#[deriving(Clone, PartialEq)]
pub struct Mime(pub TopLevel, pub SubLevel, pub Vec<Param>);

macro_rules! enoom (
    (pub enum $en:ident; $ext:ident; $($ty:ident, $text:expr;)*) => (

        #[deriving(Clone, PartialEq)]
        pub enum $en {
            $($ty),*,
            $ext(String)
        }

        impl fmt::Show for $en {
            fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
                match *self {
                    $($ty => $text),*,
                    $ext(ref s) => return s.fmt(fmt)
                }.fmt(fmt)
            }
        }

        impl FromStr for $en {
            fn from_str(s: &str) -> Option<$en> {
                Some(match s {
                    $(_s if _s == $text => $ty),*,
                    s => $ext(inspect!(stringify!($ext), s).to_string())
                })
            }
        }
    )
)

enoom! {
    pub enum TopLevel;
    TopExt;
    TopStar, "*"; // remove Top prefix if enums gain namespaces
    Text, "text";
    Image, "image";
    Audio, "audio";
    Video, "video";
    Application, "application";
    Multipart, "multipart";
    Message, "message";
    Model, "model";
}

enoom! {
    pub enum SubLevel;
    SubExt;
    SubStar, "*"; // remove Sub prefix if enums gain namespaces

    // common text/*
    Plain, "plain";
    Html, "html";
    Xml, "xml";
    Javascript, "javascript";
    Css, "css";

    // common application/*
    Json, "json";

    // common image/*
    Png, "png";
    Gif, "gif";
    Bmp, "bmp";
    Jpeg, "jpeg";
}

enoom! {
    pub enum Attr;
    AttrExt;
    Charset, "charset";
    Q, "q";
}

enoom! {
    pub enum Value;
    ValueExt;
    Utf8, "utf-8";
}

pub type Param = (Attr, Value);

impl Equiv<Mime> for Mime {
    fn equiv(&self, other: &Mime) -> bool {
        //im so sorry
        //TODO: be less sorry. dont to_string()
        self.to_string() == other.to_string()
    }
}

impl fmt::Show for Mime {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        let Mime(ref top, ref sub, ref params) = *self;
        try!(write!(fmt, "{}/{}", top, sub));
        fmt_params(params.as_slice(), fmt)
    }
}

impl FromStr for Mime {
    fn from_str(raw: &str) -> Option<Mime> {
        let ascii = raw.to_ascii_lower(); // lifetimes :(
        let raw = ascii.as_slice();
        let len = raw.len();
        let mut iter = raw.chars().enumerate();
        let mut params = vec![];
        // toplevel
        let mut start;
        let mut top;
        loop {
            match inspect!("top iter", iter.next()) {
                Some((0, c)) if is_restricted_name_first_char(c) => (),
                Some((i, c)) if i > 0 && is_restricted_name_char(c) => (),
                Some((i, '/')) if i > 0 => match FromStr::from_str(raw.slice_to(i)) {
                    Some(t) => {
                        top = t;
                        start = i + 1;
                        break;
                    }
                    None => return None
                },
                _ => return None // EOF and no toplevel is no Mime
            };

        }

        // sublevel
        let mut sub;
        loop {
            match inspect!("sub iter", iter.next()) {
                Some((i, c)) if i == start && is_restricted_name_first_char(c) => (),
                Some((i, c)) if i > start && is_restricted_name_char(c) => (),
                Some((i, ';')) if i > start => match FromStr::from_str(raw.slice(start, i)) {
                    Some(s) => {
                        sub = s;
                        start = i + 1;
                        break;
                    }
                    None => return None
                },
                None => match FromStr::from_str(raw.slice_from(start)) {
                    Some(s) => return Some(Mime(top, s, params)),
                    None => return None
                },
                _ => return None
            };
        }

        // params
        debug!("starting params, len={}", len);
        loop {
            match inspect!("param", param_from_str(raw, &mut iter, start)) {
                Some((p, end)) => {
                    params.push(p);
                    start = end;
                    if start >= len {
                        break;
                    }
                }
                None => break
            }
        }

        Some(Mime(top, sub, params))
    }
}

fn param_from_str(raw: &str, iter: &mut Enumerate<Chars>, mut start: uint) -> Option<(Param, uint)> {
    let mut attr;
    debug!("param_from_str, start={}", start);
    loop {
        match inspect!("attr iter", iter.next()) {
            Some((i, ' ')) if i == start => start = i + 1,
            Some((i, c)) if i == start && is_restricted_name_first_char(c) => (),
            Some((i, c)) if i > start && is_restricted_name_char(c) => (),
            Some((i, '=')) if i > start => match FromStr::from_str(raw.slice(start, i)) {
                Some(a) => {
                    attr = inspect!("attr", a);
                    start = i + 1;
                    break;
                },
                None => return None
            },
            _ => return None
        }
    }
    let mut value;
    // values must be restrict-name-char or "anything goes"
    let mut is_quoted = false;
    loop {
        match inspect!("value iter", iter.next()) {
            Some((i, '"')) if i == start => {
                debug!("quoted");
                is_quoted = true;
                start = i + 1;
            },
            Some((i, c)) if i == start && is_restricted_name_first_char(c) => (),
            Some((i, '"')) if i > start && is_quoted => match FromStr::from_str(raw.slice(start, i)) {
                Some(v) => {
                    value = v;
                    start = i + 1;
                    break;
                },
                None => return None
            },
            Some((i, c)) if i > start && is_quoted || is_restricted_name_char(c) => (),
            Some((i, ';')) if i > start => match FromStr::from_str(raw.slice(start, i)) {
                Some(v) => {
                    value = v;
                    start = i + 1;
                    break;
                },
                None => return None
            },
            None => match FromStr::from_str(raw.slice_from(start)) {
                Some(v) => {
                    value = v;
                    start = raw.len();
                    break;
                },
                None => return None
            },

            _ => return None
        }
    }

    Some(((attr, value), start))
}

// From [RFC6838](http://tools.ietf.org/html/rfc6838#section-4.2):
//
// > All registered media types MUST be assigned top-level type and
// > subtype names.  The combination of these names serves to uniquely
// > identify the media type, and the subtype name facet (or the absence
// > of one) identifies the registration tree.  Both top-level type and
// > subtype names are case-insensitive.
// >
// > Type and subtype names MUST conform to the following ABNF:
// >
// >     type-name = restricted-name
// >     subtype-name = restricted-name
// >
// >     restricted-name = restricted-name-first *126restricted-name-chars
// >     restricted-name-first  = ALPHA / DIGIT
// >     restricted-name-chars  = ALPHA / DIGIT / "!" / "#" /
// >                              "$" / "&" / "-" / "^" / "_"
// >     restricted-name-chars =/ "." ; Characters before first dot always
// >                                  ; specify a facet name
// >     restricted-name-chars =/ "+" ; Characters after last plus always
// >                                  ; specify a structured syntax suffix
//
fn is_restricted_name_first_char(c: char) -> bool {
    match c {
        'a'...'z' |
        '0'...'9' => true,
        _ => false
    }
}

fn is_restricted_name_char(c: char) -> bool {
    if is_restricted_name_first_char(c) {
        true
    } else {
        match c {
            '!' |
            '#' |
            '$' |
            '&' |
            '-' |
            '^' |
            '.' |
            '+' |
            '_' => true,
            _ => false
        }
    }
}

#[inline]
fn fmt_params<T: AsSlice<Param>>(params: T, fmt: &mut fmt::Formatter) -> fmt::Result {
    for param in params.as_slice().iter() {
        try!(fmt_param(param, fmt));
    }
    Ok(())
}

#[inline]
fn fmt_param(param: &Param, fmt: &mut fmt::Formatter) -> fmt::Result {
    let (ref attr, ref value) = *param;
    write!(fmt, "; {}={}", attr, value)
}

#[cfg(test)]
mod tests {
    use std::from_str::{FromStr, from_str};
    use test::Bencher;
    use super::{Mime, Text, Plain, Charset, Utf8, AttrExt, ValueExt};

    #[test]
    fn test_mime_show() {
        let mime = Mime(Text, Plain, vec![]);
        assert_eq!(mime.to_string(), "text/plain".to_string());
        let mime = Mime(Text, Plain, vec![(Charset, Utf8)]);
        assert_eq!(mime.to_string(), "text/plain; charset=utf-8".to_string());
    }

    #[test]
    fn test_mime_from_str() {
        assert_eq!(FromStr::from_str("text/plain"), Some(Mime(Text, Plain, vec![])));
        assert_eq!(FromStr::from_str("TEXT/PLAIN"), Some(Mime(Text, Plain, vec![])));
        assert_eq!(FromStr::from_str("text/plain; charset=utf-8"), Some(Mime(Text, Plain, vec![(Charset, Utf8)])));
        assert_eq!(FromStr::from_str("text/plain;charset=\"utf-8\""), Some(Mime(Text, Plain, vec![(Charset, Utf8)])));
        assert_eq!(FromStr::from_str("text/plain; charset=utf-8; foo=bar"),
            Some(Mime(Text, Plain, vec![(Charset, Utf8),
                                        (AttrExt("foo".to_string()), ValueExt("bar".to_string())) ])));
    }

    #[bench]
    fn bench_show(b: &mut Bencher) {
        let mime = Mime(Text, Plain, vec![(Charset, Utf8), (AttrExt("foo".to_string()), ValueExt("bar".to_string()))]);
        b.bytes = mime.to_string().as_bytes().len() as u64;
        b.iter(|| mime.to_string())
    }

    #[bench]
    fn bench_from_str(b: &mut Bencher) {
        let s = "text/plain; charset=utf-8; foo=bar";
        b.bytes = s.as_bytes().len() as u64;
        b.iter(|| from_str::<Mime>(s))
    }
}
<|endoftext|>


<commit_before>#![crate_type = "lib"]
#![crate_name = "currency"]

use std::cmp::PartialEq;
use std::cmp::PartialOrd;
use std::cmp::Ordering;

use std::ops::Add;
use std::ops::Sub;
use std::ops::Mul;
use std::ops::Div;

use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Result;

use std::marker::Copy;

/// Represents currency through an optional symbol and amount of coin.
///
/// Each 100 coins results in a banknote. (100 is formatted as 1.00)
/// The currency will be formatted as such:
///     Currency('$', 432) ==> "$4.32"
pub struct Currency(pub Option<char>, pub i64);

impl Currency {
    /// Creates a blank Currency as Currency(None, 0)
    fn new() -> Currency {
        Currency(None, 0)
    }

    /// Parses a string literal and turns it into a currency.
    ///
    /// Parsing ignores spaces and commas, only taking note of the digits and
    /// leading sign.
    ///
    /// # Examples
    /// Currency::from_string("$4.32") -> Currency(Some('$'), 432)
    /// Currency::from_string("424.44") -> Currency(None, 42444)
    /// Currency::from_string("@12") -> Currency(Some('@'), 1200)
    ///
    /// # Failures
    /// Fails to take note of the floating points position.
    /// Currency::from_string("$42.012) -> Currency(Some('$'), 42012)
    /// Currency::from_string("42.") -> Currency(None, 42)
    ///
    /// # Panics
    /// Panics if a number fails to be parsed; this only occurs if the string
    /// argument has no numbers in it.
    ///
    /// # Safety
    /// If a decimal point is intended to be marked, always use '.'
    /// A "European style" ',' will be ignored.
    /// String::from_string("€4.32") instead of String::from_string("€4,32")
    fn from_string(s: &str) -> Currency {
        // Try to find the sign
        let mut sign = None;
        let mut unicode: u8 = s.chars().next().unwrap() as u8;
        // If the first character is not a letter
        if unicode <= 0x30 || unicode >= 0x39 {
            sign = Some(unicode as char);
        }

        // Find the numbers
        let mut should_multiply = true; // May later change if a '.' is specified
        let mut coin_str = String::new();
        for c in s.chars() {
            unicode = c as u8;
            // Only pay attention to numbers
            if unicode >= 0x30 && unicode <= 0x39 {
                coin_str = coin_str + &c.to_string();
            }
            // If coins are explicitly specified (via a '.'), then we shouldn't
            // multiply at the end
            if unicode == 0x2E {
                should_multiply = false;
            }
        }
        // Parse out the resulting number
        let mut coin: i64 = coin_str.parse()
            .ok()
            .expect("Failed to convert string to currency");

        if should_multiply {
            coin *= 100;
        }

        // Return result
        Currency(sign, coin)
    }
}

/// Overloads the '==' operator for Currency objects.
///
/// # Panics
/// Panics if the two comparators are different types of currency, as denoted by
/// the Currency's symbol.
impl PartialEq<Currency> for Currency {
    fn eq(&self, rhs: &Currency) -> bool {
        self.0 == rhs.0 && self.1 == rhs.1
    }

    fn ne(&self, rhs: &Currency) -> bool {
        self.0 != rhs.0 || self.1 != rhs.1
    }
}

/// Overloads the order operators for Currency objects.
///
/// These operators include '<', '<=', '>', and '>='.
///
/// # Panics
/// Panics if the two comparators are different types of currency, as denoted by
/// the Currency's symbol.
impl PartialOrd<Currency> for Currency {
    fn partial_cmp(&self, rhs: &Currency) -> Option<Ordering> {
        if self.0 == rhs.0 {
            if self < rhs { return Some(Ordering::Less) }
            if self == rhs { return Some(Ordering::Equal) }
            if self > rhs { return Some(Ordering::Greater) }
        }
        None
    }

    // TODO: sign checking here
    fn lt(&self, rhs: &Currency) -> bool {
        self.1 < rhs.1
    }
    fn le(&self, rhs: &Currency) -> bool {
        self < rhs || self == rhs
    }
    fn gt(&self, rhs: &Currency) -> bool {
        self.1 > rhs.1
    }
    fn ge(&self, rhs: &Currency) -> bool {
        self > rhs || self == rhs
    }
}

/// Overloads the '+' operator for Currency objects.
///
/// # Panics
/// Panics if the two addends are different types of currency, as denoted by the
/// Currency's symbol.
impl Add for Currency {
    type Output = Currency;

    fn add(self, rhs: Currency) -> Currency {
        if self.0 == rhs.0 {
            Currency(self.0, self.1 + rhs.1)
        } else {
            panic!("Cannot do arithmetic on two different types of currency!");
        }
    }
}

/// Overloads the '-' operator for Currency objects.
///
/// # Panics
/// Panics if the minuend and subtrahend are two different types of currency,
/// as denoted by the Currency's symbol.
impl Sub for Currency {
    type Output = Currency;

    fn sub(self, rhs: Currency) -> Currency {
        if self.0 == rhs.0 {
            Currency(self.0, self.1 - rhs.1)
        } else {
            panic!("Cannot do arithmetic on two different types of currency!");
        }
    }
}

/// Overloads the '*' operator for Currency objects.
///
/// Allows a Currency to be multiplied by an i64.
impl Mul<i64> for Currency {
    type Output = Currency;

    fn mul(self, rhs: i64) -> Currency {
        Currency(self.0, self.1 * rhs)
    }
}

/// Overloads the '*' operator for i64.
///
/// Allows an i64 to be multiplied by a Currency.
/// Completes the commutative property for i64 multiplied by Currency.
impl Mul<Currency> for i64 {
    type Output = Currency;

    fn mul(self, rhs: Currency) -> Currency {
        Currency(rhs.0, rhs.1 * self)
    }
}

/// Overloads the '/' operator for Currency objects.
///
/// Allows a Currency to be divided by an i64.
impl Div<i64> for Currency {
    type Output = Currency;

    fn div(self, rhs: i64) -> Currency {
        Currency(self.0, self.1 / rhs)
    }
}

/// Allows Currencies to be displayed as Strings
///
/// # Examples
/// Currency(Some('$'), 1210).to_string() == "$12.10"
/// Currency(None, 1210.to_string() == "12.10"
impl Display for Currency {
    fn fmt(&self, f: &mut Formatter) -> Result {
        let decimal = (self.1 / 100).to_string()
            + &('.').to_string()
            + &(self.1 % 100).to_string();
        match self.0 {
            Some(c) => write!(f, "{}{}", c, decimal),
            None    => write!(f, "{}", decimal),
        }
    }
}

/// Allows Currencies to be copied, rather than using move semantics.
impl Copy for Currency { }
impl Clone for Currency {
    fn clone(&self) -> Currency {
        *self
    }
}

#[test]
fn eq_works() {
    let a = Currency(Some('$'), 1210);
    let b = Currency(Some('$'), 1210);
    let c = Currency(Some('$'), 1251);

    assert!(a == b);
    assert!(b == b);
    assert!(b == a);
    assert!(a != c);
}

#[test]
fn ord_works() {
    let a = Currency(Some('$'), 1210);
    let b = Currency(Some('$'), 1211);
    let c = Currency(Some('$'), 1311);
    let d = Currency(Some('$'), 1210);

    assert_eq!(a.partial_cmp(&b), Some(Ordering::Less));
    assert_eq!(a.partial_cmp(&c), Some(Ordering::Less));
    assert_eq!(a.partial_cmp(&d), Some(Ordering::Equal));
    assert_eq!(c.partial_cmp(&a), Some(Ordering::Greater));

    assert!(a < b);
    assert!(a < c);
    assert!(a <= a);
    assert!(a <= c);
    assert!(b > a);
    assert!(c > a);
    assert!(a >= a);
    assert!(c >= a);
}

#[test]
fn arithmetic_works() {
    let x = Currency(Some('$'), 1206);
    let y = Currency(Some('$'), 1143);

    assert!(x + y == Currency(Some('$'), 2349)
         && y + x == Currency(Some('$'), 2349));
    assert!(x - y == Currency(Some('$'), 63));
    assert!(y - x == Currency(Some('$'), -63));
    assert!(x * 2 == Currency(Some('$'), 2412)
         && 2 * x == Currency(Some('$'), 2412));
    assert!(x / 2 == Currency(Some('$'), 603));
}

#[test]
fn parse_works() {
    let a = Currency(Some('$'), 1210);
    let b = Currency::from_string("$12.10");
    assert!(a == b);

    let c = Currency(Some('$'), 1200);
    let d = Currency::from_string("$12");
    assert!(c == d);
}

#[test]
fn display_works() {
    assert!(Currency(Some('$'), 1210).to_string() == "$12.10");
    assert!(Currency(None, 1210).to_string() == "12.10");
}
<commit_msg>Added inlines and modified methods to be pub<commit_after>#![crate_type = "lib"]
#![crate_name = "currency"]

use std::cmp::PartialEq;
use std::cmp::PartialOrd;
use std::cmp::Ordering;

use std::ops::Add;
use std::ops::Sub;
use std::ops::Mul;
use std::ops::Div;

use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Result;

use std::marker::Copy;

/// Represents currency through an optional symbol and amount of coin.
///
/// Each 100 coins results in a banknote. (100 is formatted as 1.00)
/// The currency will be formatted as such:
///     Currency(Some('$'), 432) ==> "$4.32"
pub struct Currency(pub Option<char>, pub i64);

impl Currency {
    /// Creates a blank Currency as Currency(None, 0)
    ///
    /// # Examples
    ///
    /// ```
    /// let mut c = Currency::new();
    /// ```
    #[inline]
    #[allow(dead_code)]
    pub fn new() -> Currency {
        Currency(None, 0)
    }

    /// Parses a string literal and turns it into a currency.
    ///
    /// Parsing ignores spaces and commas, only taking note of the digits and
    /// leading sign.
    ///
    /// # Examples
    /// ```
    /// Currency::from_string("$4.32") -> Currency(Some('$'), 432)
    /// Currency::from_string("424.44") -> Currency(None, 42444)
    /// Currency::from_string("@12") -> Currency(Some('@'), 1200)
    /// ```
    ///
    /// # Failures
    /// Fails to take note of the floating points position.
    /// ```
    /// Currency::from_string("$42.012) -> Currency(Some('$'), 42012)
    /// Currency::from_string("42.") -> Currency(None, 42)
    /// ```
    ///
    /// # Panics
    /// Panics if a number fails to be parsed; this only occurs if the string
    /// argument has no numbers in it.
    ///
    /// # Safety
    /// If a decimal point is intended to be marked, always use '.'
    /// A "European style" ',' will be ignored.
    /// String::from_string("€4.32") instead of String::from_string("€4,32")
    #[allow(dead_code)]
    pub fn from_string(s: &str) -> Currency {
        // Try to find the sign
        let mut sign = None;
        let mut unicode: u8 = s.chars().next().unwrap() as u8;
        // If the first character is not a letter
        if unicode <= 0x30 || unicode >= 0x39 {
            sign = Some(unicode as char);
        }

        // Find the numbers
        let mut should_multiply = true; // May later change if '.' is specified
        let mut coin_str = String::new();
        for c in s.chars() {
            unicode = c as u8;
            // Only pay attention to numbers
            if unicode >= 0x30 && unicode <= 0x39 {
                coin_str = coin_str + &c.to_string();
            }
            // If coins are explicitly specified (via a '.'), then we shouldn't
            // multiply at the end
            if unicode == 0x2E {
                should_multiply = false;
            }
        }
        // Parse out the resulting number
        let mut coin: i64 = coin_str.parse()
            .ok()
            .expect("Failed to convert string to currency");

        if should_multiply {
            coin *= 100;
        }

        // Return result
        Currency(sign, coin)
    }
}

/// Overloads the '==' operator for Currency objects.
///
/// # Panics
/// Panics if the two comparators are different types of currency, as denoted by
/// the Currency's symbol.
impl PartialEq<Currency> for Currency {
    #[inline]
    fn eq(&self, rhs: &Currency) -> bool {
        self.0 == rhs.0 && self.1 == rhs.1
    }

    #[inline]
    fn ne(&self, rhs: &Currency) -> bool {
        self.0 != rhs.0 || self.1 != rhs.1
    }
}

/// Overloads the order operators for Currency objects.
///
/// These operators include '<', '<=', '>', and '>='.
///
/// # Panics
/// Panics if the two comparators are different types of currency, as denoted by
/// the Currency's symbol.
impl PartialOrd<Currency> for Currency {
    #[inline]
    fn partial_cmp(&self, rhs: &Currency) -> Option<Ordering> {
        if self.0 == rhs.0 {
            if self < rhs { return Some(Ordering::Less) }
            if self == rhs { return Some(Ordering::Equal) }
            if self > rhs { return Some(Ordering::Greater) }
        }
        None
    }

    #[inline]
    fn lt(&self, rhs: &Currency) -> bool {
        if self.0 == rhs.0 {
            self.1 < rhs.1
        }
        else {
            panic!("Cannot compare two different types of currency.");
        }
    }
    #[inline]
    fn le(&self, rhs: &Currency) -> bool {
        self < rhs || self == rhs
    }
    #[inline]
    fn gt(&self, rhs: &Currency) -> bool {
        if self.0 == rhs.0 {
            self.1 > rhs.1
        }
        else {
            panic!("Cannot compare two different types of currency.");
        }
    }
    #[inline]
    fn ge(&self, rhs: &Currency) -> bool {
        self > rhs || self == rhs
    }
}

/// Overloads the '+' operator for Currency objects.
///
/// # Panics
/// Panics if the two addends are different types of currency, as denoted by the
/// Currency's symbol.
impl Add for Currency {
    type Output = Currency;

    #[inline]
    fn add(self, rhs: Currency) -> Currency {
        if self.0 == rhs.0 {
            Currency(self.0, self.1 + rhs.1)
        } else {
            panic!("Cannot add two different types of currency!");
        }
    }
}

/// Overloads the '-' operator for Currency objects.
///
/// # Panics
/// Panics if the minuend and subtrahend are two different types of currency,
/// as denoted by the Currency's symbol.
impl Sub for Currency {
    type Output = Currency;

    #[inline]
    fn sub(self, rhs: Currency) -> Currency {
        if self.0 == rhs.0 {
            Currency(self.0, self.1 - rhs.1)
        } else {
            panic!("Cannot subtract two different types of currency!");
        }
    }
}

/// Overloads the '*' operator for Currency objects.
///
/// Allows a Currency to be multiplied by an i64.
impl Mul<i64> for Currency {
    type Output = Currency;

    #[inline]
    fn mul(self, rhs: i64) -> Currency {
        Currency(self.0, self.1 * rhs)
    }
}

/// Overloads the '*' operator for i64.
///
/// Allows an i64 to be multiplied by a Currency.
/// Completes the commutative property for i64 multiplied by Currency.
impl Mul<Currency> for i64 {
    type Output = Currency;

    #[inline]
    fn mul(self, rhs: Currency) -> Currency {
        Currency(rhs.0, rhs.1 * self)
    }
}

/// Overloads the '/' operator for Currency objects.
///
/// Allows a Currency to be divided by an i64.
impl Div<i64> for Currency {
    type Output = Currency;

    #[inline]
    fn div(self, rhs: i64) -> Currency {
        Currency(self.0, self.1 / rhs)
    }
}

/// Allows Currencies to be displayed as Strings
///
/// # Examples
/// ```
/// Currency(Some('$'), 1210).to_string() == "$12.10"
/// Currency(None, 1210.to_string() == "12.10"
/// ```
impl Display for Currency {
    #[inline]
    fn fmt(&self, f: &mut Formatter) -> Result {
        let decimal = (self.1 / 100).to_string()
            + &('.').to_string()
            + &(self.1 % 100).to_string();
        match self.0 {
            Some(c) => write!(f, "{}{}", c, decimal),
            None    => write!(f, "{}", decimal),
        }
    }
}

/// Allows Currencies to be copied, rather than using move semantics.
impl Copy for Currency { }
impl Clone for Currency {
    #[inline]
    fn clone(&self) -> Currency { *self }
}

#[test]
fn eq_works() {
    let a = Currency(Some('$'), 1210);
    let b = Currency(Some('$'), 1210);
    let c = Currency(Some('$'), 1251);

    assert!(a == b);
    assert!(b == b);
    assert!(b == a);
    assert!(a != c);
}

#[test]
fn ord_works() {
    let a = Currency(Some('$'), 1210);
    let b = Currency(Some('$'), 1211);
    let c = Currency(Some('$'), 1311);
    let d = Currency(Some('$'), 1210);

    assert_eq!(a.partial_cmp(&b), Some(Ordering::Less));
    assert_eq!(a.partial_cmp(&c), Some(Ordering::Less));
    assert_eq!(a.partial_cmp(&d), Some(Ordering::Equal));
    assert_eq!(c.partial_cmp(&a), Some(Ordering::Greater));

    assert!(a < b);
    assert!(a < c);
    assert!(a <= a);
    assert!(a <= c);
    assert!(b > a);
    assert!(c > a);
    assert!(a >= a);
    assert!(c >= a);
}

#[test]
fn arithmetic_works() {
    let x = Currency(Some('$'), 1206);
    let y = Currency(Some('$'), 1143);

    assert!(x + y == Currency(Some('$'), 2349)
         && y + x == Currency(Some('$'), 2349));
    assert!(x - y == Currency(Some('$'), 63));
    assert!(y - x == Currency(Some('$'), -63));
    assert!(x * 2 == Currency(Some('$'), 2412)
         && 2 * x == Currency(Some('$'), 2412));
    assert!(x / 2 == Currency(Some('$'), 603));
}

#[test]
fn parse_works() {
    let a = Currency(Some('$'), 1210);
    let b = Currency::from_string("$12.10");
    assert!(a == b);

    let c = Currency(Some('$'), 1200);
    let d = Currency::from_string("$12");
    assert!(c == d);
}

#[test]
fn display_works() {
    assert!(Currency(Some('$'), 1210).to_string() == "$12.10");
    assert!(Currency(None, 1210).to_string() == "12.10");
}<|endoftext|>


<commit_before>#[macro_use]
extern crate log;
extern crate getopts;
extern crate unix_daemonize;
extern crate byteorder;
extern crate udt;
extern crate time;
extern crate sodiumoxide;
extern crate rustc_serialize;

pub mod connection;

use unix_daemonize::{daemonize_redirect, ChdirMode};
use std::process::Command;
use std::net::{SocketAddr, SocketAddrV4, IpAddr};
use std::str;
use std::env;
use std::thread;
use std::str::FromStr;
use std::fs::{OpenOptions, File};
use std::path::{Path, PathBuf};
use std::time::{Instant, Duration};
use std::io::{Cursor, Error, Seek, SeekFrom, stderr, Read, Write};
use log::{LogRecord, LogLevel, LogMetadata};
use std::sync::mpsc;
use byteorder::{ReadBytesExt, WriteBytesExt, LittleEndian};
use sodiumoxide::crypto::secretbox;
use sodiumoxide::crypto::secretbox::xsalsa20poly1305::Key;
use rustc_serialize::hex::{FromHex, ToHex};

// TODO config
const INITIAL_ACCEPT_TIMEOUT_SECONDS: u64 = 60;

macro_rules! overprint {
    ($fmt: expr) => {
        print!(concat!("\x1b[2K\r", $fmt));
        std::io::stdout().flush().unwrap();
    };
    ($fmt:expr, $($arg:tt)*) => {
        print!(concat!("\x1b[2K\r", $fmt) , $($arg)*);
        std::io::stdout().flush().unwrap();
    };
}

macro_rules! die {
    ($fmt: expr) => {
        error!($fmt);
        panic!($fmt);
    };
    ($fmt:expr, $($arg:tt)*) => {
        error!($fmt, $($arg)*);
        panic!($fmt, $($arg)*);
    };
}

pub struct Server<'a> {
    pub ip: String,
    filename: &'a str,
    conn: connection::Server,
}

#[allow(dead_code)]
enum ShoopErrKind {
    Severed,
    Fatal,
}

struct ShoopErr {
    kind: ShoopErrKind,
    msg: Option<String>,
    finished: u64,
}

pub struct ShoopLogger;

impl log::Log for ShoopLogger {
    fn enabled(&self, metadata: &LogMetadata) -> bool {
        metadata.level() <= LogLevel::Info
    }

    fn log(&self, record: &LogRecord) {
        if self.enabled(record.metadata()) {
            let line = format!("{} - {}\n", record.level(), record.args());
            print!("{}", line);
        }
    }
}

impl ShoopLogger {
    pub fn init() -> Result<(), log::SetLoggerError> {
        log::set_logger(|max_log_level| {
            max_log_level.set(log::LogLevelFilter::Info);
            Box::new(ShoopLogger)
        })
    }
}

impl ShoopErr {
    pub fn new(kind: ShoopErrKind, msg: &str, finished: u64) -> ShoopErr {
        ShoopErr { kind: kind, msg: Some(String::from(msg)), finished: finished }
    }

    #[allow(dead_code)]
    pub fn from(err: Error, finished: u64) -> ShoopErr {
        ShoopErr { kind: ShoopErrKind::Severed, msg: Some(format!("{:?}", err)), finished: finished }
    }
}

impl<'a> Server<'a> {
    pub fn new(port_range : connection::PortRange,
               filename   : &str)
        -> Server
    {
        let sshconnstr = match env::var("SSH_CONNECTION") {
            Ok(s) => s.trim().to_owned(),
            Err(_) => { die!("SSH_CONNECTION env variable unset and required. Quitting."); }
        };
        let sshconn: Vec<&str> = sshconnstr.split(" ").collect();
        let ip = sshconn[2].to_owned();
        let key = secretbox::gen_key();
        let Key(keybytes) = key;
        let port = connection::Server::get_open_port(&port_range).unwrap();
        println!("shoop 0 {} {} {}", ip, port, keybytes.to_hex());

        let stdout = Some(Path::new(&env::var("HOME").unwrap()).join(".shoop.log"));
        let stderr = Some(Path::new(&env::var("HOME").unwrap()).join(".shoop.log"));
        daemonize_redirect(stdout, stderr, ChdirMode::ChdirRoot).unwrap();

        let conn = connection::Server::new(IpAddr::from_str(&ip).unwrap(), port, key);
        Server { ip: ip, conn: conn, filename: filename }
    }

    pub fn start(&self) {
        self.conn.listen().unwrap();

        let mut connection_count: usize = 0;
        info!("listening...");
        loop {
            info!("waiting for connection...");
            let (tx, rx) = mpsc::channel();
            if connection_count == 0 {
                thread::spawn(move || {
                    thread::sleep(Duration::from_secs(INITIAL_ACCEPT_TIMEOUT_SECONDS));
                    if let Err(_) = rx.try_recv() {
                        error!("timed out waiting for initial connection. exiting.");
                        std::process::exit(1);
                    }
                });
            }
            let client = match self.conn.accept() {
                Ok(client) => client,
                Err(e) => { die!("error on sock accept() {:?}", e); }
            };
            connection_count += 1;
            tx.send(()).unwrap();
            info!("accepted connection!");
            if let Ok(starthdr) = client.recv() {
                let version = starthdr[0];
                let mut rdr = Cursor::new(starthdr);
                rdr.set_position(1);
                let offset = rdr.read_u64::<LittleEndian>().unwrap();
                if version == 0x00 {
                    match self.send_file(&client, offset) {
                        Ok(_) => {
                            info!("done sending file");
                            let _ = client.close();
                            break;
                        }
                        Err(ShoopErr{ kind: ShoopErrKind::Severed, msg, finished}) => {
                            info!("connection severed, msg: {:?}, finished: {}", msg, finished);
                            let _ = client.close();
                            continue;
                        }
                        Err(ShoopErr{ kind: ShoopErrKind::Fatal, msg, finished}) => {
                            info!("connection fatal, msg: {:?}, finished: {}", msg, finished);
                            panic!("{:?}", msg);
                        }
                    }
                } else {
                    die!("unrecognized version");
                }
            } else {
                die!("failed to receive version byte from client");
            }
        }
        info!("exiting listen loop.");
    }

    fn send_file(&self, client: &connection::ServerConnection, offset: u64) -> Result<(), ShoopErr> {
        let mut f = File::open(self.filename).unwrap();
        f.seek(SeekFrom::Start(offset)).unwrap();
        let metadata = f.metadata().unwrap();

        let remaining = metadata.len() - offset;
        info!("total {} bytes", remaining);

        let mut wtr = vec![];
        wtr.write_u64::<LittleEndian>(remaining).unwrap();
        match client.send(&wtr[..]) {
            Ok(()) => {
                return Err(ShoopErr::new(ShoopErrKind::Severed, "failed to write filesize header before timeout", remaining))
            },
            Err(e) => {
                return Err(ShoopErr::new(ShoopErrKind::Severed, &format!("{:?}", e), remaining))
            }
        }
        let mut payload = vec![0; 1300];
        f.seek(SeekFrom::Start(offset)).unwrap();
        info!("sending file...");
        loop {
            match f.read(&mut payload) {
                Ok(0) => {
                    break;
                }
                Ok(read) => {
                    match client.send(&payload[0..read]) {
                        Ok(()) => {
                            return Err(ShoopErr::new(ShoopErrKind::Severed, "failed to write filesize header before timeout", remaining))
                        },
                        Err(e) => {
                            return Err(ShoopErr::new(ShoopErrKind::Severed, &format!("{:?}", e), remaining))
                        }
                    }
                },
                Err(e) => {
                    client.close().expect("Error closing stream");
                    error!("failed to read from file.");
                    panic!("{:?}", e);
                }
            }
        }

        client.close().expect("Error closing stream.");
        Ok(())
    }
}

pub fn download(remote_ssh_host : &str,
                port_range      : connection::PortRange,
                remote_path     : &str,
                local_path      : PathBuf)
{
    let cmd = format!("shoop -s '{}' -p {}", remote_path, port_range);
    // println!("addr: {}, path: {}, cmd: {}", addr, path, cmd);

    overprint!(" - establishing SSH session...");
    assert!(command_exists("ssh"), "`ssh` is required!");
    let output = Command::new("ssh")
                         .arg(remote_ssh_host.to_owned())
                         .arg(cmd)
                         .output()
                         .unwrap_or_else(|e| {
                             panic!("failed to execute process: {}", e);
                         });
    let infostring = String::from_utf8_lossy(&output.stdout).to_owned().trim().to_owned();
    let info: Vec<&str> = infostring.split(" ").collect();
    if info.len() != 5 {
        panic!("Unexpected response from server. Are you suuuuure shoop is setup on the server?");
    }

    let (magic, version, ip, port, keyhex) = (info[0], info[1], info[2], info[3], info[4]);
    overprint!(" - opening UDT connection...");
    if magic != "shoop" || version != "0" {
        panic!("Unexpected response from server. Are you suuuuure shoop is setup on the server?");
    }

    let mut keybytes = [0u8; 32];
    keybytes.copy_from_slice(&keyhex.from_hex().unwrap()[..]);
    let key = Key(keybytes);
    let addr: SocketAddr = SocketAddr::V4(SocketAddrV4::from_str(&format!("{}:{}", ip, port)[..]).unwrap());
    let conn = connection::Client::new(addr, key);

    let mut offset = 0u64;
    let mut filesize = None;
    let start_ts = Instant::now();
    loop {
        match conn.connect() {
            Ok(()) => {
                overprint!(" - connection opened, shakin' hands, makin' frands");
            },
            Err(e) => {
                panic!("errrrrrrr connecting to {}:{} - {:?}", ip, port, e);
            }
        }
        let mut wtr = vec![];
        wtr.push(0);
        wtr.write_u64::<LittleEndian>(offset).unwrap();
        match conn.send(&wtr[..]) {
            Err(_) => { conn.close().unwrap(); continue; }
            _      => {}
        }

        match conn.recv() {
            Ok(msg) => {
                if msg.len() == 0 {
                    panic!("failed to get filesize from server, probable timeout.");
                }
                let mut rdr = Cursor::new(msg);
                filesize = filesize.or(Some(rdr.read_u64::<LittleEndian>().unwrap()));
                overprint!(" + downloading {} ({:.1}MB)\n", local_path.to_string_lossy(), (filesize.unwrap() as f64)/(1024f64*1024f64));
                match recv_file(&conn, filesize.unwrap(), &local_path, offset) {
                     Ok(_) => {
                         break;
                     }
                     Err(ShoopErr{ kind: ShoopErrKind::Severed, msg: _, finished}) => {
                         println!(" * [[SEVERED]]");
                         offset = finished;
                     }
                     Err(ShoopErr{ kind: ShoopErrKind::Fatal, msg, finished: _}) => {
                         panic!("{:?}", msg);
                     }
                }
            }
            Err(_) => {}
        }
        let _ = conn.close();
    }

    let elapsed = start_ts.elapsed().as_secs();
    let fmt_time = if elapsed < 60 {
        format!("{}s", elapsed)
    } else if elapsed < 60 * 60 {
        format!("{}m{}s", elapsed / 60, elapsed % 60)
    } else {
        format!("{}h{}m{}s", elapsed / (60 * 60), elapsed / 60, elapsed % 60)
    };
    println!("shooped it all up in {}", fmt_time);
}

fn command_exists(command: &str) -> bool {
    match Command::new("which").arg(command).output() {
        Ok(output) => output.status.success(),
        Err(_)     => false
    }
}

fn recv_file(conn: &connection::Client, filesize: u64, filename: &PathBuf, offset: u64) -> Result<(), ShoopErr> {
    let mut f = OpenOptions::new().write(true).create(true).truncate(false).open(filename).unwrap();
    f.seek(SeekFrom::Start(offset)).unwrap();
    let start = Instant::now();
    let mut ts = Instant::now();
    let mut total = offset;
    let mut speed_ts = Instant::now();
    let mut speed_total = total;
    let mut speed = 0u64;
    loop {
        let buf = try!(conn.recv()
                           .map_err(|e| ShoopErr::new(ShoopErrKind::Severed, &format!("{:?}", e), total)));
        if buf.len() < 1 {
            return Err(ShoopErr::new(ShoopErrKind::Severed, "empty msg", total));
        }

        f.write_all(&buf[..]).unwrap();
        total += buf.len() as u64;
        let speed_elapsed = speed_ts.elapsed();
        if speed_elapsed > Duration::new(1, 0) {
            speed = ((total - speed_total) as f64 / ((speed_elapsed.as_secs() as f64) + (speed_elapsed.subsec_nanos() as f64) / 1_000_000_000f64)) as u64;
            speed_ts = Instant::now();
            speed_total = total;
        }
        let speedfmt = if speed < 1024 {
            format!("{} b/s", speed)
        } else if speed < 1024 * 1024 {
            format!("{} kb/s", speed / 1024)
        } else {
            format!("{:.1} MB/s", ((speed / 1024) as f64) / 1024f64)
        };

        if ts.elapsed() > Duration::new(0, 100_000_000) {
            overprint!("   {:.1}M / {:.1}M ({:.1}%) [ {} ]", (total as f64)/(1024f64*1024f64), (filesize as f64)/(1024f64*1024f64), (total as f64) / (filesize as f64) * 100f64, speedfmt);
            ts = Instant::now();
        }
        if total >= filesize {
            overprint!("   {0:.1}M / {0:.1}M (100%) [ avg {1:.1} MB/s ]", (filesize as f64)/(1024f64*1024f64), ((total - offset) / start.elapsed().as_secs() / 1024) as f64 / 1024f64);
            println!("\ndone.");
            break;
        }
    }
    let _ = conn.close();
    Ok(())
}

<commit_msg>don't die on success<commit_after>#[macro_use]
extern crate log;
extern crate getopts;
extern crate unix_daemonize;
extern crate byteorder;
extern crate udt;
extern crate time;
extern crate sodiumoxide;
extern crate rustc_serialize;

pub mod connection;

use unix_daemonize::{daemonize_redirect, ChdirMode};
use std::process::Command;
use std::net::{SocketAddr, SocketAddrV4, IpAddr};
use std::str;
use std::env;
use std::thread;
use std::str::FromStr;
use std::fs::{OpenOptions, File};
use std::path::{Path, PathBuf};
use std::time::{Instant, Duration};
use std::io::{Cursor, Error, Seek, SeekFrom, stderr, Read, Write};
use log::{LogRecord, LogLevel, LogMetadata};
use std::sync::mpsc;
use byteorder::{ReadBytesExt, WriteBytesExt, LittleEndian};
use sodiumoxide::crypto::secretbox;
use sodiumoxide::crypto::secretbox::xsalsa20poly1305::Key;
use rustc_serialize::hex::{FromHex, ToHex};

// TODO config
const INITIAL_ACCEPT_TIMEOUT_SECONDS: u64 = 60;

macro_rules! overprint {
    ($fmt: expr) => {
        print!(concat!("\x1b[2K\r", $fmt));
        std::io::stdout().flush().unwrap();
    };
    ($fmt:expr, $($arg:tt)*) => {
        print!(concat!("\x1b[2K\r", $fmt) , $($arg)*);
        std::io::stdout().flush().unwrap();
    };
}

macro_rules! die {
    ($fmt: expr) => {
        error!($fmt);
        panic!($fmt);
    };
    ($fmt:expr, $($arg:tt)*) => {
        error!($fmt, $($arg)*);
        panic!($fmt, $($arg)*);
    };
}

pub struct Server<'a> {
    pub ip: String,
    filename: &'a str,
    conn: connection::Server,
}

#[allow(dead_code)]
enum ShoopErrKind {
    Severed,
    Fatal,
}

struct ShoopErr {
    kind: ShoopErrKind,
    msg: Option<String>,
    finished: u64,
}

pub struct ShoopLogger;

impl log::Log for ShoopLogger {
    fn enabled(&self, metadata: &LogMetadata) -> bool {
        metadata.level() <= LogLevel::Info
    }

    fn log(&self, record: &LogRecord) {
        if self.enabled(record.metadata()) {
            let line = format!("{} - {}\n", record.level(), record.args());
            print!("{}", line);
        }
    }
}

impl ShoopLogger {
    pub fn init() -> Result<(), log::SetLoggerError> {
        log::set_logger(|max_log_level| {
            max_log_level.set(log::LogLevelFilter::Info);
            Box::new(ShoopLogger)
        })
    }
}

impl ShoopErr {
    pub fn new(kind: ShoopErrKind, msg: &str, finished: u64) -> ShoopErr {
        ShoopErr { kind: kind, msg: Some(String::from(msg)), finished: finished }
    }

    #[allow(dead_code)]
    pub fn from(err: Error, finished: u64) -> ShoopErr {
        ShoopErr { kind: ShoopErrKind::Severed, msg: Some(format!("{:?}", err)), finished: finished }
    }
}

impl<'a> Server<'a> {
    pub fn new(port_range : connection::PortRange,
               filename   : &str)
        -> Server
    {
        let sshconnstr = match env::var("SSH_CONNECTION") {
            Ok(s) => s.trim().to_owned(),
            Err(_) => { die!("SSH_CONNECTION env variable unset and required. Quitting."); }
        };
        let sshconn: Vec<&str> = sshconnstr.split(" ").collect();
        let ip = sshconn[2].to_owned();
        let key = secretbox::gen_key();
        let Key(keybytes) = key;
        let port = connection::Server::get_open_port(&port_range).unwrap();
        println!("shoop 0 {} {} {}", ip, port, keybytes.to_hex());

        let stdout = Some(Path::new(&env::var("HOME").unwrap()).join(".shoop.log"));
        let stderr = Some(Path::new(&env::var("HOME").unwrap()).join(".shoop.log"));
        daemonize_redirect(stdout, stderr, ChdirMode::ChdirRoot).unwrap();

        let conn = connection::Server::new(IpAddr::from_str(&ip).unwrap(), port, key);
        Server { ip: ip, conn: conn, filename: filename }
    }

    pub fn start(&self) {
        self.conn.listen().unwrap();

        let mut connection_count: usize = 0;
        info!("listening...");
        loop {
            info!("waiting for connection...");
            let (tx, rx) = mpsc::channel();
            if connection_count == 0 {
                thread::spawn(move || {
                    thread::sleep(Duration::from_secs(INITIAL_ACCEPT_TIMEOUT_SECONDS));
                    if let Err(_) = rx.try_recv() {
                        error!("timed out waiting for initial connection. exiting.");
                        std::process::exit(1);
                    }
                });
            }
            let client = match self.conn.accept() {
                Ok(client) => client,
                Err(e) => { die!("error on sock accept() {:?}", e); }
            };
            connection_count += 1;
            tx.send(()).unwrap();
            info!("accepted connection!");
            if let Ok(starthdr) = client.recv() {
                let version = starthdr[0];
                let mut rdr = Cursor::new(starthdr);
                rdr.set_position(1);
                let offset = rdr.read_u64::<LittleEndian>().unwrap();
                if version == 0x00 {
                    match self.send_file(&client, offset) {
                        Ok(_) => {
                            info!("done sending file");
                            let _ = client.close();
                            break;
                        }
                        Err(ShoopErr{ kind: ShoopErrKind::Severed, msg, finished}) => {
                            info!("connection severed, msg: {:?}, finished: {}", msg, finished);
                            let _ = client.close();
                            continue;
                        }
                        Err(ShoopErr{ kind: ShoopErrKind::Fatal, msg, finished}) => {
                            info!("connection fatal, msg: {:?}, finished: {}", msg, finished);
                            panic!("{:?}", msg);
                        }
                    }
                } else {
                    die!("unrecognized version");
                }
            } else {
                die!("failed to receive version byte from client");
            }
        }
        info!("exiting listen loop.");
    }

    fn send_file(&self, client: &connection::ServerConnection, offset: u64) -> Result<(), ShoopErr> {
        let mut f = File::open(self.filename).unwrap();
        f.seek(SeekFrom::Start(offset)).unwrap();
        let metadata = f.metadata().unwrap();

        let remaining = metadata.len() - offset;
        info!("total {} bytes", remaining);

        let mut wtr = vec![];
        wtr.write_u64::<LittleEndian>(remaining).unwrap();
        match client.send(&wtr[..]) {
            Ok(()) => { info!("wrote filesize header.") },
            Err(e) => {
                return Err(ShoopErr::new(ShoopErrKind::Severed, &format!("{:?}", e), remaining))
            }
        }
        let mut payload = vec![0; 1300];
        f.seek(SeekFrom::Start(offset)).unwrap();
        info!("sending file...");
        loop {
            match f.read(&mut payload) {
                Ok(0) => {
                    break;
                }
                Ok(read) => {
                    match client.send(&payload[0..read]) {
                        Ok(()) => { },
                        Err(e) => {
                            return Err(ShoopErr::new(ShoopErrKind::Severed, &format!("{:?}", e), remaining))
                        }
                    }
                },
                Err(e) => {
                    client.close().expect("Error closing stream");
                    error!("failed to read from file.");
                    panic!("{:?}", e);
                }
            }
        }

        client.close().expect("Error closing stream.");
        Ok(())
    }
}

pub fn download(remote_ssh_host : &str,
                port_range      : connection::PortRange,
                remote_path     : &str,
                local_path      : PathBuf)
{
    let cmd = format!("shoop -s '{}' -p {}", remote_path, port_range);
    // println!("addr: {}, path: {}, cmd: {}", addr, path, cmd);

    overprint!(" - establishing SSH session...");
    assert!(command_exists("ssh"), "`ssh` is required!");
    let output = Command::new("ssh")
                         .arg(remote_ssh_host.to_owned())
                         .arg(cmd)
                         .output()
                         .unwrap_or_else(|e| {
                             panic!("failed to execute process: {}", e);
                         });
    let infostring = String::from_utf8_lossy(&output.stdout).to_owned().trim().to_owned();
    let info: Vec<&str> = infostring.split(" ").collect();
    if info.len() != 5 {
        panic!("Unexpected response from server. Are you suuuuure shoop is setup on the server?");
    }

    let (magic, version, ip, port, keyhex) = (info[0], info[1], info[2], info[3], info[4]);
    overprint!(" - opening UDT connection...");
    if magic != "shoop" || version != "0" {
        panic!("Unexpected response from server. Are you suuuuure shoop is setup on the server?");
    }

    let mut keybytes = [0u8; 32];
    keybytes.copy_from_slice(&keyhex.from_hex().unwrap()[..]);
    let key = Key(keybytes);
    let addr: SocketAddr = SocketAddr::V4(SocketAddrV4::from_str(&format!("{}:{}", ip, port)[..]).unwrap());
    let conn = connection::Client::new(addr, key);

    let mut offset = 0u64;
    let mut filesize = None;
    let start_ts = Instant::now();
    loop {
        match conn.connect() {
            Ok(()) => {
                overprint!(" - connection opened, shakin' hands, makin' frands");
            },
            Err(e) => {
                panic!("errrrrrrr connecting to {}:{} - {:?}", ip, port, e);
            }
        }
        let mut wtr = vec![];
        wtr.push(0);
        wtr.write_u64::<LittleEndian>(offset).unwrap();
        match conn.send(&wtr[..]) {
            Err(_) => { conn.close().unwrap(); continue; }
            _      => {}
        }

        match conn.recv() {
            Ok(msg) => {
                if msg.len() == 0 {
                    panic!("failed to get filesize from server, probable timeout.");
                }
                let mut rdr = Cursor::new(msg);
                filesize = filesize.or(Some(rdr.read_u64::<LittleEndian>().unwrap()));
                overprint!(" + downloading {} ({:.1}MB)\n", local_path.to_string_lossy(), (filesize.unwrap() as f64)/(1024f64*1024f64));
                match recv_file(&conn, filesize.unwrap(), &local_path, offset) {
                     Ok(_) => {
                         break;
                     }
                     Err(ShoopErr{ kind: ShoopErrKind::Severed, msg: _, finished}) => {
                         println!(" * [[SEVERED]]");
                         offset = finished;
                     }
                     Err(ShoopErr{ kind: ShoopErrKind::Fatal, msg, finished: _}) => {
                         panic!("{:?}", msg);
                     }
                }
            }
            Err(_) => {}
        }
        let _ = conn.close();
    }

    let elapsed = start_ts.elapsed().as_secs();
    let fmt_time = if elapsed < 60 {
        format!("{}s", elapsed)
    } else if elapsed < 60 * 60 {
        format!("{}m{}s", elapsed / 60, elapsed % 60)
    } else {
        format!("{}h{}m{}s", elapsed / (60 * 60), elapsed / 60, elapsed % 60)
    };
    println!("shooped it all up in {}", fmt_time);
}

fn command_exists(command: &str) -> bool {
    match Command::new("which").arg(command).output() {
        Ok(output) => output.status.success(),
        Err(_)     => false
    }
}

fn recv_file(conn: &connection::Client, filesize: u64, filename: &PathBuf, offset: u64) -> Result<(), ShoopErr> {
    let mut f = OpenOptions::new().write(true).create(true).truncate(false).open(filename).unwrap();
    f.seek(SeekFrom::Start(offset)).unwrap();
    let start = Instant::now();
    let mut ts = Instant::now();
    let mut total = offset;
    let mut speed_ts = Instant::now();
    let mut speed_total = total;
    let mut speed = 0u64;
    loop {
        let buf = try!(conn.recv()
                           .map_err(|e| ShoopErr::new(ShoopErrKind::Severed, &format!("{:?}", e), total)));
        if buf.len() < 1 {
            return Err(ShoopErr::new(ShoopErrKind::Severed, "empty msg", total));
        }

        f.write_all(&buf[..]).unwrap();
        total += buf.len() as u64;
        let speed_elapsed = speed_ts.elapsed();
        if speed_elapsed > Duration::new(1, 0) {
            speed = ((total - speed_total) as f64 / ((speed_elapsed.as_secs() as f64) + (speed_elapsed.subsec_nanos() as f64) / 1_000_000_000f64)) as u64;
            speed_ts = Instant::now();
            speed_total = total;
        }
        let speedfmt = if speed < 1024 {
            format!("{} b/s", speed)
        } else if speed < 1024 * 1024 {
            format!("{} kb/s", speed / 1024)
        } else {
            format!("{:.1} MB/s", ((speed / 1024) as f64) / 1024f64)
        };

        if ts.elapsed() > Duration::new(0, 100_000_000) {
            overprint!("   {:.1}M / {:.1}M ({:.1}%) [ {} ]", (total as f64)/(1024f64*1024f64), (filesize as f64)/(1024f64*1024f64), (total as f64) / (filesize as f64) * 100f64, speedfmt);
            ts = Instant::now();
        }
        if total >= filesize {
            overprint!("   {0:.1}M / {0:.1}M (100%) [ avg {1:.1} MB/s ]", (filesize as f64)/(1024f64*1024f64), ((total - offset) / start.elapsed().as_secs() / 1024) as f64 / 1024f64);
            println!("\ndone.");
            break;
        }
    }
    let _ = conn.close();
    Ok(())
}

<|endoftext|>


<commit_before>//! [![github]](https://github.com/dtolnay/serde-yaml) [![crates-io]](https://crates.io/crates/serde-yaml) [![docs-rs]](https://docs.rs/serde-yaml)
//!
//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logoColor=white&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K
//!
//!

//!
//! This crate is a Rust library for using the [Serde] serialization framework
//! with data in [YAML] file format.
//!
//! This library does not reimplement a YAML parser; it uses [yaml-rust] which
//! is a pure Rust YAML 1.2 implementation.
//!
//! [Serde]: https://github.com/serde-rs/serde
//! [YAML]: https://yaml.org/
//! [yaml-rust]: https://github.com/chyh1990/yaml-rust
//!
//! # Examples
//!
//! ```
//! use std::collections::BTreeMap;
//!
//! fn main() -> Result<(), serde_yaml::Error> {
//!     // You have some type.
//!     let mut map = BTreeMap::new();
//!     map.insert("x".to_string(), 1.0);
//!     map.insert("y".to_string(), 2.0);
//!
//!     // Serialize it to a YAML string.
//!     let s = serde_yaml::to_string(&map)?;
//!     assert_eq!(s, "---\nx: 1.0\ny: 2.0\n");
//!
//!     // Deserialize it back to a Rust type.
//!     let deserialized_map: BTreeMap<String, f64> = serde_yaml::from_str(&s)?;
//!     assert_eq!(map, deserialized_map);
//!     Ok(())
//! }
//! ```
//!
//! ## Using Serde derive
//!
//! It can also be used with Serde's serialization code generator `serde_derive` to
//! handle structs and enums defined in your own program.
//!
//! ```
//! # use serde_derive::{Serialize, Deserialize};
//! use serde::{Serialize, Deserialize};
//!
//! #[derive(Debug, PartialEq, Serialize, Deserialize)]
//! struct Point {
//!     x: f64,
//!     y: f64,
//! }
//!
//! fn main() -> Result<(), serde_yaml::Error> {
//!     let point = Point { x: 1.0, y: 2.0 };
//!
//!     let s = serde_yaml::to_string(&point)?;
//!     assert_eq!(s, "---\nx: 1.0\ny: 2.0\n");
//!
//!     let deserialized_point: Point = serde_yaml::from_str(&s)?;
//!     assert_eq!(point, deserialized_point);
//!     Ok(())
//! }
//! ```

#![doc(html_root_url = "https://docs.rs/serde_yaml/0.8.18")]
#![deny(missing_docs)]
// Suppressed clippy_pedantic lints
#![allow(
    // private Deserializer::next
    clippy::should_implement_trait,
    // things are often more readable this way
    clippy::cast_lossless,
    clippy::module_name_repetitions,
    clippy::needless_pass_by_value,
    clippy::option_if_let_else,
    clippy::single_match_else,
    // code is acceptable
    clippy::cast_possible_wrap,
    clippy::cast_precision_loss,
    // noisy
    clippy::missing_errors_doc,
    clippy::must_use_candidate,
)]

pub use crate::de::{from_reader, from_slice, from_str, Deserializer};
pub use crate::error::{Error, Location, Result};
pub use crate::ser::{to_string, to_vec, to_writer, Serializer};
pub use crate::value::{from_value, to_value, Index, Number, Sequence, Value};

#[doc(inline)]
pub use crate::mapping::Mapping;

/// Entry points for deserializing with pre-existing state.
///
/// These functions are only exposed this way because we don't yet expose a
/// Deserializer type. Data formats that have a public Deserializer should not
/// copy these signatures.
pub mod seed {
    pub use super::de::{from_reader_seed, from_slice_seed, from_str_seed};
}

mod de;
mod error;
pub mod mapping;
mod number;
mod path;
mod ser;
mod value;
<commit_msg>Silence match_same_arms Clippy pedantic lint<commit_after>//! [![github]](https://github.com/dtolnay/serde-yaml) [![crates-io]](https://crates.io/crates/serde-yaml) [![docs-rs]](https://docs.rs/serde-yaml)
//!
//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logoColor=white&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K
//!
//!

//!
//! This crate is a Rust library for using the [Serde] serialization framework
//! with data in [YAML] file format.
//!
//! This library does not reimplement a YAML parser; it uses [yaml-rust] which
//! is a pure Rust YAML 1.2 implementation.
//!
//! [Serde]: https://github.com/serde-rs/serde
//! [YAML]: https://yaml.org/
//! [yaml-rust]: https://github.com/chyh1990/yaml-rust
//!
//! # Examples
//!
//! ```
//! use std::collections::BTreeMap;
//!
//! fn main() -> Result<(), serde_yaml::Error> {
//!     // You have some type.
//!     let mut map = BTreeMap::new();
//!     map.insert("x".to_string(), 1.0);
//!     map.insert("y".to_string(), 2.0);
//!
//!     // Serialize it to a YAML string.
//!     let s = serde_yaml::to_string(&map)?;
//!     assert_eq!(s, "---\nx: 1.0\ny: 2.0\n");
//!
//!     // Deserialize it back to a Rust type.
//!     let deserialized_map: BTreeMap<String, f64> = serde_yaml::from_str(&s)?;
//!     assert_eq!(map, deserialized_map);
//!     Ok(())
//! }
//! ```
//!
//! ## Using Serde derive
//!
//! It can also be used with Serde's serialization code generator `serde_derive` to
//! handle structs and enums defined in your own program.
//!
//! ```
//! # use serde_derive::{Serialize, Deserialize};
//! use serde::{Serialize, Deserialize};
//!
//! #[derive(Debug, PartialEq, Serialize, Deserialize)]
//! struct Point {
//!     x: f64,
//!     y: f64,
//! }
//!
//! fn main() -> Result<(), serde_yaml::Error> {
//!     let point = Point { x: 1.0, y: 2.0 };
//!
//!     let s = serde_yaml::to_string(&point)?;
//!     assert_eq!(s, "---\nx: 1.0\ny: 2.0\n");
//!
//!     let deserialized_point: Point = serde_yaml::from_str(&s)?;
//!     assert_eq!(point, deserialized_point);
//!     Ok(())
//! }
//! ```

#![doc(html_root_url = "https://docs.rs/serde_yaml/0.8.18")]
#![deny(missing_docs)]
// Suppressed clippy_pedantic lints
#![allow(
    // private Deserializer::next
    clippy::should_implement_trait,
    // things are often more readable this way
    clippy::cast_lossless,
    clippy::match_same_arms,
    clippy::module_name_repetitions,
    clippy::needless_pass_by_value,
    clippy::option_if_let_else,
    clippy::single_match_else,
    // code is acceptable
    clippy::cast_possible_wrap,
    clippy::cast_precision_loss,
    // noisy
    clippy::missing_errors_doc,
    clippy::must_use_candidate,
)]

pub use crate::de::{from_reader, from_slice, from_str, Deserializer};
pub use crate::error::{Error, Location, Result};
pub use crate::ser::{to_string, to_vec, to_writer, Serializer};
pub use crate::value::{from_value, to_value, Index, Number, Sequence, Value};

#[doc(inline)]
pub use crate::mapping::Mapping;

/// Entry points for deserializing with pre-existing state.
///
/// These functions are only exposed this way because we don't yet expose a
/// Deserializer type. Data formats that have a public Deserializer should not
/// copy these signatures.
pub mod seed {
    pub use super::de::{from_reader_seed, from_slice_seed, from_str_seed};
}

mod de;
mod error;
pub mod mapping;
mod number;
mod path;
mod ser;
mod value;
<|endoftext|>


<commit_before>extern crate num;

use std::ops;

/// 2D grid coordinate
#[derive(Clone, Copy, Eq, Debug, PartialEq)]
pub struct Coord<T> {
    pub x: T,
    pub y: T
}

impl<T> Coord<T> {
    /// Create a grid coordinate at (x, y)
    pub fn new(x: T, y: T) -> Coord<T> {
        Coord {
            x: x,
            y: y
        }
    }
}

/// Rectangle defined by inclusive minimum and maximum coordinates
#[derive(Clone, Copy, Eq, Debug, PartialEq)]
pub struct Rect<T: Copy> {
    /// Minimum coordinate (inclusive)
    pub min_coord: Coord<T>,

    /// Maximum coordinate (inclusive)
    pub max_coord: Coord<T>
}

impl<T: Copy + PartialOrd> Rect<T> {
    /// Create a new Rect defined by inclusive minimum and maximum
    /// coordinates. If min_coord is greater than max_coord on either
    /// axis then None is returned.
    pub fn new(min_coord: Coord<T>, max_coord: Coord<T>) -> Option<Rect<T>> {
        if min_coord.x <= max_coord.x && min_coord.y <= max_coord.y {
            Some(Rect {
                min_coord: min_coord,
                max_coord: max_coord
            })
        }
        else {
            None
        }
    }

    pub fn iter(&self) -> RectIter<T> {
        RectIter {
            rect: *self,
            cur_coord: self.min_coord
        }
    }
}

pub struct RectIter<T: Copy> {
    rect: Rect<T>,
    cur_coord: Coord<T>
}

impl<T: Copy + Ord + ops::Add<Output=T> + num::One> Iterator for RectIter<T> {
    type Item = Coord<T>;

    fn next(&mut self) -> Option<Self::Item> {
        if self.cur_coord.y <= self.rect.max_coord.y {
            let result = Some(self.cur_coord);
            self.cur_coord.x = self.cur_coord.x + T::one();
            if self.cur_coord.x > self.rect.max_coord.x {
                self.cur_coord.x = self.rect.min_coord.x;
                self.cur_coord.y = self.cur_coord.y + T::one();
            }
            result
        }
        else {
            None
        }
    }
}

#[test]
fn test_rect_iter() {
    let rect = Rect::new(Coord::new(1, 2), Coord::new(3, 4)).unwrap();
    let coords: Vec<Coord<u8>> = rect.iter().collect();
    assert_eq!(coords, [
        Coord::new(1, 2), Coord::new(2, 2), Coord::new(3, 2),
        Coord::new(1, 3), Coord::new(2, 3), Coord::new(3, 3),
        Coord::new(1, 4), Coord::new(2, 4), Coord::new(3, 4)]);
}

// pub struct RectIter<'s, S: 's, T> {
//     data: &'s [S],
//     cur_elem: *const S,
//     cur_coord:
//     full: Rect<T>,
//     part: Rect<T>
// }

// impl<'s, S: 's, T> Iterator for RectIter<'s, S, T> {
//     type Item = (Coord<T>, &'s S);

//     fn next(&mut self) -> Option<Self::Item> {
//         if cur {

//         }
//     }
// }
<commit_msg>Checkpoint<commit_after>extern crate num;

use std::ops::Add;
use num::Unsigned;

/// 2D grid coordinate
#[derive(Clone, Copy, Eq, Debug, PartialEq)]
pub struct Coord<T: Unsigned> {
    pub x: T,
    pub y: T
}

impl<T: Unsigned> Coord<T> {
    /// Create a grid coordinate at (x, y)
    pub fn new(x: T, y: T) -> Coord<T> {
        Coord {
            x: x,
            y: y
        }
    }
}

/// Rectangle defined by inclusive minimum and maximum coordinates
#[derive(Clone, Copy, Eq, Debug, PartialEq)]
pub struct Rect<T: Copy + Unsigned> {
    /// Minimum coordinate (inclusive)
    pub min_coord: Coord<T>,

    /// Maximum coordinate (inclusive)
    pub max_coord: Coord<T>
}

impl<T: Copy + PartialOrd + Unsigned> Rect<T> {
    /// Create a new Rect defined by inclusive minimum and maximum
    /// coordinates. If min_coord is greater than max_coord on either
    /// axis then None is returned.
    pub fn new(min_coord: Coord<T>, max_coord: Coord<T>) -> Option<Rect<T>> {
        if min_coord.x <= max_coord.x && min_coord.y <= max_coord.y {
            Some(Rect {
                min_coord: min_coord,
                max_coord: max_coord
            })
        }
        else {
            None
        }
    }

    /// Iterate from minimum coord to maximum coord by row.
    pub fn iter(&self) -> RectIter<T> {
        RectIter {
            rect: *self,
            cur_coord: self.min_coord
        }
    }
}

pub struct RectIter<T: Copy + Unsigned> {
    rect: Rect<T>,
    cur_coord: Coord<T>
}

impl<T: Copy + Ord + Unsigned + Add<Output=T> + num::One> Iterator for RectIter<T> {
    type Item = Coord<T>;

    fn next(&mut self) -> Option<Self::Item> {
        if self.cur_coord.y <= self.rect.max_coord.y {
            let result = Some(self.cur_coord);
            self.cur_coord.x = self.cur_coord.x + T::one();
            if self.cur_coord.x > self.rect.max_coord.x {
                self.cur_coord.x = self.rect.min_coord.x;
                self.cur_coord.y = self.cur_coord.y + T::one();
            }
            result
        }
        else {
            None
        }
    }
}

#[test]
fn test_rect_iter() {
    let rect = Rect::new(Coord::new(1, 2), Coord::new(3, 4)).unwrap();
    let coords: Vec<Coord<u8>> = rect.iter().collect();
    assert_eq!(coords, [
        Coord::new(1, 2), Coord::new(2, 2), Coord::new(3, 2),
        Coord::new(1, 3), Coord::new(2, 3), Coord::new(3, 3),
        Coord::new(1, 4), Coord::new(2, 4), Coord::new(3, 4)]);
}
<|endoftext|>