<commit_before>use cssparser::{Parser, Token};
use std::f64::consts::*;

use drawing_ctx::ViewParams;
use error::*;
use parsers::Parse;
use parsers::ParseError;
use state::ComputedValues;

#[derive(Debug, PartialEq, Copy, Clone)]
pub enum LengthUnit {
    Default,
    Percent,
    FontEm,
    FontEx,
    Inch,
}

#[derive(Debug, PartialEq, Copy, Clone)]
pub enum LengthDir {
    Horizontal,
    Vertical,
    Both,
}

#[derive(Debug, PartialEq, Copy, Clone)]
pub struct Length {
    pub length: f64,
    pub unit: LengthUnit,
    dir: LengthDir,
}

impl Default for Length {
    fn default() -> Length {
        Length {
            length: 0.0,
            unit: LengthUnit::Default,
            dir: LengthDir::Both,
        }
    }
}

pub const POINTS_PER_INCH: f64 = 72.0;
const CM_PER_INCH: f64 = 2.54;
const MM_PER_INCH: f64 = 25.4;
const PICA_PER_INCH: f64 = 6.0;

// https://www.w3.org/TR/SVG/types.html#DataTypeLength
// https://www.w3.org/TR/2008/REC-CSS2-20080411/syndata.html#length-units
// Lengths have units.  When they need to be need resolved to
// units in the user's coordinate system, some unit types
// need to know if they are horizontal/vertical/both.  For example,
// a some_object.width="50%" is 50% with respect to the current
// viewport's width.  In this case, the @dir argument is used
// inside Length::normalize(), when it needs to know to what the
// length refers.

fn make_err() -> AttributeError {
    AttributeError::Parse(ParseError::new(
        "expected length: number(\"em\" | \"ex\" | \"px\" | \"in\" | \"cm\" | \"mm\" | \"pt\" | \
         \"pc\" | \"%\")?",
    ))
}

impl Parse for Length {
    type Data = LengthDir;
    type Err = AttributeError;

    fn parse(parser: &mut Parser, dir: LengthDir) -> Result<Length, AttributeError> {
        let length = Length::from_cssparser(parser, dir)?;

        parser.expect_exhausted().map_err(|_| make_err())?;

        Ok(length)
    }
}

impl Length {
    pub fn new(l: f64, unit: LengthUnit, dir: LengthDir) -> Length {
        Length {
            length: l,
            unit,
            dir,
        }
    }

    pub fn check_nonnegative(self) -> Result<Length, AttributeError> {
        if self.length >= 0.0 {
            Ok(self)
        } else {
            Err(AttributeError::Value(
                "value must be non-negative".to_string(),
            ))
        }
    }

    pub fn normalize(&self, values: &ComputedValues, params: &ViewParams) -> f64 {
        match self.unit {
            LengthUnit::Default => self.length,

            LengthUnit::Percent => match self.dir {
                LengthDir::Horizontal => self.length * params.view_box_width,
                LengthDir::Vertical => self.length * params.view_box_height,
                LengthDir::Both => {
                    self.length * viewport_percentage(params.view_box_width, params.view_box_height)
                }
            },

            LengthUnit::FontEm => self.length * font_size_from_values(values, params),

            LengthUnit::FontEx => self.length * font_size_from_values(values, params) / 2.0,

            LengthUnit::Inch => font_size_from_inch(self.length, self.dir, params),
        }
    }

    /// Returns the raw length after asserting units are either default or percent.
    #[inline]
    pub fn get_unitless(&self) -> f64 {
        assert!(self.unit == LengthUnit::Default || self.unit == LengthUnit::Percent);
        self.length
    }

    pub fn hand_normalize(
        &self,
        pixels_per_inch: f64,
        width_or_height: f64,
        font_size: f64,
    ) -> f64 {
        match self.unit {
            LengthUnit::Default => self.length,
            LengthUnit::Percent => self.length * width_or_height,
            LengthUnit::FontEm => self.length * font_size,
            LengthUnit::FontEx => self.length * font_size / 2.0,
            LengthUnit::Inch => self.length * pixels_per_inch,
        }
    }

    pub fn from_cssparser(parser: &mut Parser, dir: LengthDir) -> Result<Length, AttributeError> {
        let length = {
            let token = parser.next().map_err(|_| {
                AttributeError::Parse(ParseError::new(
                    "expected number and optional symbol, or number and percentage",
                ))
            })?;

            match *token {
                Token::Number { value, .. } => Length {
                    length: f64::from(value),
                    unit: LengthUnit::Default,
                    dir,
                },

                Token::Percentage { unit_value, .. } => Length {
                    length: f64::from(unit_value),
                    unit: LengthUnit::Percent,
                    dir,
                },

                Token::Dimension {
                    value, ref unit, ..
                } => {
                    let value = f64::from(value);

                    match unit.as_ref() {
                        "em" => Length {
                            length: value,
                            unit: LengthUnit::FontEm,
                            dir,
                        },

                        "ex" => Length {
                            length: value,
                            unit: LengthUnit::FontEx,
                            dir,
                        },

                        "pt" => Length {
                            length: value / POINTS_PER_INCH,
                            unit: LengthUnit::Inch,
                            dir,
                        },

                        "in" => Length {
                            length: value,
                            unit: LengthUnit::Inch,
                            dir,
                        },

                        "cm" => Length {
                            length: value / CM_PER_INCH,
                            unit: LengthUnit::Inch,
                            dir,
                        },

                        "mm" => Length {
                            length: value / MM_PER_INCH,
                            unit: LengthUnit::Inch,
                            dir,
                        },

                        "pc" => Length {
                            length: value / PICA_PER_INCH,
                            unit: LengthUnit::Inch,
                            dir,
                        },

                        "px" => Length {
                            length: value,
                            unit: LengthUnit::Default,
                            dir,
                        },

                        _ => return Err(make_err()),
                    }
                }

                _ => return Err(make_err()),
            }
        };

        Ok(length)
    }
}

fn font_size_from_inch(length: f64, dir: LengthDir, params: &ViewParams) -> f64 {
    match dir {
        LengthDir::Horizontal => length * params.dpi_x,
        LengthDir::Vertical => length * params.dpi_y,
        LengthDir::Both => length * viewport_percentage(params.dpi_x, params.dpi_y),
    }
}

fn font_size_from_values(values: &ComputedValues, params: &ViewParams) -> f64 {
    let v = &values.font_size.0.value();

    match v.unit {
        LengthUnit::Default => v.length,

        LengthUnit::Inch => font_size_from_inch(v.length, v.dir, params),

        LengthUnit::Percent | LengthUnit::FontEm | LengthUnit::FontEx => {
            unreachable!("ComputedValues can't have a relative font size")
        }
    }
}

fn viewport_percentage(x: f64, y: f64) -> f64 {
    // https://www.w3.org/TR/SVG/coords.html#Units
    // "For any other length value expressed as a percentage of the viewport, the
    // percentage is calculated as the specified percentage of
    // sqrt((actual-width)**2 + (actual-height)**2))/sqrt(2)."
    (x * x + y * y).sqrt() / SQRT_2
}

#[derive(Debug, PartialEq, Clone)]
pub enum Dasharray {
    None,
    Array(Vec<Length>),
}

impl Default for Dasharray {
    fn default() -> Dasharray {
        Dasharray::None
    }
}

impl Parse for Dasharray {
    type Data = ();
    type Err = AttributeError;

    fn parse(parser: &mut Parser, _: Self::Data) -> Result<Dasharray, AttributeError> {
        if parser.try(|p| p.expect_ident_matching("none")).is_ok() {
            Ok(Dasharray::None)
        } else {
            Ok(Dasharray::Array(parse_dash_array(parser)?))
        }
    }
}

// This does not handle "inherit" or "none" state, the caller is responsible for that.
fn parse_dash_array(parser: &mut Parser) -> Result<Vec<Length>, AttributeError> {
    let mut dasharray = Vec::new();

    loop {
        dasharray.push(
            Length::from_cssparser(parser, LengthDir::Both).and_then(Length::check_nonnegative)?,
        );

        if parser.is_exhausted() {
            break;
        } else if parser.try(|p| p.expect_comma()).is_ok() {
            continue;
        }
    }

    Ok(dasharray)
}

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

    #[test]
    fn parses_default() {
        assert_eq!(
            Length::parse_str("42", LengthDir::Horizontal),
            Ok(Length::new(
                42.0,
                LengthUnit::Default,
                LengthDir::Horizontal
            ))
        );

        assert_eq!(
            Length::parse_str("-42px", LengthDir::Horizontal),
            Ok(Length::new(
                -42.0,
                LengthUnit::Default,
                LengthDir::Horizontal
            ))
        );
    }

    #[test]
    fn parses_percent() {
        assert_eq!(
            Length::parse_str("50.0%", LengthDir::Horizontal),
            Ok(Length::new(0.5, LengthUnit::Percent, LengthDir::Horizontal))
        );
    }

    #[test]
    fn parses_font_em() {
        assert_eq!(
            Length::parse_str("22.5em", LengthDir::Vertical),
            Ok(Length::new(22.5, LengthUnit::FontEm, LengthDir::Vertical))
        );
    }

    #[test]
    fn parses_font_ex() {
        assert_eq!(
            Length::parse_str("22.5ex", LengthDir::Vertical),
            Ok(Length::new(22.5, LengthUnit::FontEx, LengthDir::Vertical))
        );
    }

    #[test]
    fn parses_physical_units() {
        assert_eq!(
            Length::parse_str("72pt", LengthDir::Both),
            Ok(Length::new(1.0, LengthUnit::Inch, LengthDir::Both))
        );

        assert_eq!(
            Length::parse_str("-22.5in", LengthDir::Both),
            Ok(Length::new(-22.5, LengthUnit::Inch, LengthDir::Both))
        );

        assert_eq!(
            Length::parse_str("-254cm", LengthDir::Both),
            Ok(Length::new(-100.0, LengthUnit::Inch, LengthDir::Both))
        );

        assert_eq!(
            Length::parse_str("254mm", LengthDir::Both),
            Ok(Length::new(10.0, LengthUnit::Inch, LengthDir::Both))
        );

        assert_eq!(
            Length::parse_str("60pc", LengthDir::Both),
            Ok(Length::new(10.0, LengthUnit::Inch, LengthDir::Both))
        );
    }

    #[test]
    fn empty_length_yields_error() {
        assert!(is_parse_error(&Length::parse_str("", LengthDir::Both)));
    }

    #[test]
    fn invalid_unit_yields_error() {
        assert!(is_parse_error(&Length::parse_str(
            "8furlong",
            LengthDir::Both
        )));
    }

    #[test]
    fn check_nonnegative_works() {
        assert!(
            Length::parse_str("0", LengthDir::Both)
                .and_then(|l| l.check_nonnegative())
                .is_ok()
        );
        assert!(
            Length::parse_str("-10", LengthDir::Both)
                .and_then(|l| l.check_nonnegative())
                .is_err()
        );
    }

    fn parse_dash_array_str(s: &str) -> Result<Dasharray, AttributeError> {
        Dasharray::parse_str(s, ())
    }

    #[test]
    fn parses_dash_array() {
        // helper to cut down boilderplate
        let length_parse = |s| Length::parse_str(s, LengthDir::Both).unwrap();

        let expected = Dasharray::Array(vec![
            length_parse("1"),
            length_parse("2in"),
            length_parse("3"),
            length_parse("4%"),
        ]);

        let sample_1 = Dasharray::Array(vec![length_parse("10"), length_parse("6")]);

        let sample_2 = Dasharray::Array(vec![
            length_parse("5"),
            length_parse("5"),
            length_parse("20"),
        ]);

        let sample_3 = Dasharray::Array(vec![
            length_parse("10px"),
            length_parse("20px"),
            length_parse("20px"),
        ]);

        let sample_4 = Dasharray::Array(vec![
            length_parse("25"),
            length_parse("5"),
            length_parse("5"),
            length_parse("5"),
        ]);

        let sample_5 = Dasharray::Array(vec![length_parse("3.1415926"), length_parse("8")]);
        let sample_6 = Dasharray::Array(vec![length_parse("5"), length_parse("3.14")]);
        let sample_7 = Dasharray::Array(vec![length_parse("2")]);

        assert_eq!(parse_dash_array_str("none").unwrap(), Dasharray::None);
        assert_eq!(parse_dash_array_str("1 2in,3 4%").unwrap(), expected);
        assert_eq!(parse_dash_array_str("10,6").unwrap(), sample_1);
        assert_eq!(parse_dash_array_str("5,5,20").unwrap(), sample_2);
        assert_eq!(parse_dash_array_str("10px 20px 20px").unwrap(), sample_3);
        assert_eq!(parse_dash_array_str("25  5 , 5 5").unwrap(), sample_4);
        assert_eq!(parse_dash_array_str("3.1415926,8").unwrap(), sample_5);
        assert_eq!(parse_dash_array_str("5, 3.14").unwrap(), sample_6);
        assert_eq!(parse_dash_array_str("2").unwrap(), sample_7);

        // Negative numbers
        assert_eq!(
            parse_dash_array_str("20,40,-20"),
            Err(AttributeError::Value(String::from(
                "value must be non-negative"
            )))
        );

        // Empty dash_array
        assert!(parse_dash_array_str("").is_err());
        assert!(parse_dash_array_str("\t  \n     ").is_err());
        assert!(parse_dash_array_str(",,,").is_err());
        assert!(parse_dash_array_str("10,  \t, 20 \n").is_err());
        // No trailing commas allowed, parse error
        assert!(parse_dash_array_str("10,").is_err());
        // A comma should be followed by a number
        assert!(parse_dash_array_str("20,,10").is_err());
    }
}
<commit_msg>Length: basic tests for normalization<commit_after>use cssparser::{Parser, Token};
use std::f64::consts::*;

use drawing_ctx::ViewParams;
use error::*;
use parsers::Parse;
use parsers::ParseError;
use state::ComputedValues;

#[derive(Debug, PartialEq, Copy, Clone)]
pub enum LengthUnit {
    Default,
    Percent,
    FontEm,
    FontEx,
    Inch,
}

#[derive(Debug, PartialEq, Copy, Clone)]
pub enum LengthDir {
    Horizontal,
    Vertical,
    Both,
}

#[derive(Debug, PartialEq, Copy, Clone)]
pub struct Length {
    pub length: f64,
    pub unit: LengthUnit,
    dir: LengthDir,
}

impl Default for Length {
    fn default() -> Length {
        Length {
            length: 0.0,
            unit: LengthUnit::Default,
            dir: LengthDir::Both,
        }
    }
}

pub const POINTS_PER_INCH: f64 = 72.0;
const CM_PER_INCH: f64 = 2.54;
const MM_PER_INCH: f64 = 25.4;
const PICA_PER_INCH: f64 = 6.0;

// https://www.w3.org/TR/SVG/types.html#DataTypeLength
// https://www.w3.org/TR/2008/REC-CSS2-20080411/syndata.html#length-units
// Lengths have units.  When they need to be need resolved to
// units in the user's coordinate system, some unit types
// need to know if they are horizontal/vertical/both.  For example,
// a some_object.width="50%" is 50% with respect to the current
// viewport's width.  In this case, the @dir argument is used
// inside Length::normalize(), when it needs to know to what the
// length refers.

fn make_err() -> AttributeError {
    AttributeError::Parse(ParseError::new(
        "expected length: number(\"em\" | \"ex\" | \"px\" | \"in\" | \"cm\" | \"mm\" | \"pt\" | \
         \"pc\" | \"%\")?",
    ))
}

impl Parse for Length {
    type Data = LengthDir;
    type Err = AttributeError;

    fn parse(parser: &mut Parser, dir: LengthDir) -> Result<Length, AttributeError> {
        let length = Length::from_cssparser(parser, dir)?;

        parser.expect_exhausted().map_err(|_| make_err())?;

        Ok(length)
    }
}

impl Length {
    pub fn new(l: f64, unit: LengthUnit, dir: LengthDir) -> Length {
        Length {
            length: l,
            unit,
            dir,
        }
    }

    pub fn check_nonnegative(self) -> Result<Length, AttributeError> {
        if self.length >= 0.0 {
            Ok(self)
        } else {
            Err(AttributeError::Value(
                "value must be non-negative".to_string(),
            ))
        }
    }

    pub fn normalize(&self, values: &ComputedValues, params: &ViewParams) -> f64 {
        match self.unit {
            LengthUnit::Default => self.length,

            LengthUnit::Percent => match self.dir {
                LengthDir::Horizontal => self.length * params.view_box_width,
                LengthDir::Vertical => self.length * params.view_box_height,
                LengthDir::Both => {
                    self.length * viewport_percentage(params.view_box_width, params.view_box_height)
                }
            },

            LengthUnit::FontEm => self.length * font_size_from_values(values, params),

            LengthUnit::FontEx => self.length * font_size_from_values(values, params) / 2.0,

            LengthUnit::Inch => font_size_from_inch(self.length, self.dir, params),
        }
    }

    /// Returns the raw length after asserting units are either default or percent.
    #[inline]
    pub fn get_unitless(&self) -> f64 {
        assert!(self.unit == LengthUnit::Default || self.unit == LengthUnit::Percent);
        self.length
    }

    pub fn hand_normalize(
        &self,
        pixels_per_inch: f64,
        width_or_height: f64,
        font_size: f64,
    ) -> f64 {
        match self.unit {
            LengthUnit::Default => self.length,
            LengthUnit::Percent => self.length * width_or_height,
            LengthUnit::FontEm => self.length * font_size,
            LengthUnit::FontEx => self.length * font_size / 2.0,
            LengthUnit::Inch => self.length * pixels_per_inch,
        }
    }

    pub fn from_cssparser(parser: &mut Parser, dir: LengthDir) -> Result<Length, AttributeError> {
        let length = {
            let token = parser.next().map_err(|_| {
                AttributeError::Parse(ParseError::new(
                    "expected number and optional symbol, or number and percentage",
                ))
            })?;

            match *token {
                Token::Number { value, .. } => Length {
                    length: f64::from(value),
                    unit: LengthUnit::Default,
                    dir,
                },

                Token::Percentage { unit_value, .. } => Length {
                    length: f64::from(unit_value),
                    unit: LengthUnit::Percent,
                    dir,
                },

                Token::Dimension {
                    value, ref unit, ..
                } => {
                    let value = f64::from(value);

                    match unit.as_ref() {
                        "em" => Length {
                            length: value,
                            unit: LengthUnit::FontEm,
                            dir,
                        },

                        "ex" => Length {
                            length: value,
                            unit: LengthUnit::FontEx,
                            dir,
                        },

                        "pt" => Length {
                            length: value / POINTS_PER_INCH,
                            unit: LengthUnit::Inch,
                            dir,
                        },

                        "in" => Length {
                            length: value,
                            unit: LengthUnit::Inch,
                            dir,
                        },

                        "cm" => Length {
                            length: value / CM_PER_INCH,
                            unit: LengthUnit::Inch,
                            dir,
                        },

                        "mm" => Length {
                            length: value / MM_PER_INCH,
                            unit: LengthUnit::Inch,
                            dir,
                        },

                        "pc" => Length {
                            length: value / PICA_PER_INCH,
                            unit: LengthUnit::Inch,
                            dir,
                        },

                        "px" => Length {
                            length: value,
                            unit: LengthUnit::Default,
                            dir,
                        },

                        _ => return Err(make_err()),
                    }
                }

                _ => return Err(make_err()),
            }
        };

        Ok(length)
    }
}

fn font_size_from_inch(length: f64, dir: LengthDir, params: &ViewParams) -> f64 {
    match dir {
        LengthDir::Horizontal => length * params.dpi_x,
        LengthDir::Vertical => length * params.dpi_y,
        LengthDir::Both => length * viewport_percentage(params.dpi_x, params.dpi_y),
    }
}

fn font_size_from_values(values: &ComputedValues, params: &ViewParams) -> f64 {
    let v = &values.font_size.0.value();

    match v.unit {
        LengthUnit::Default => v.length,

        LengthUnit::Inch => font_size_from_inch(v.length, v.dir, params),

        LengthUnit::Percent | LengthUnit::FontEm | LengthUnit::FontEx => {
            unreachable!("ComputedValues can't have a relative font size")
        }
    }
}

fn viewport_percentage(x: f64, y: f64) -> f64 {
    // https://www.w3.org/TR/SVG/coords.html#Units
    // "For any other length value expressed as a percentage of the viewport, the
    // percentage is calculated as the specified percentage of
    // sqrt((actual-width)**2 + (actual-height)**2))/sqrt(2)."
    (x * x + y * y).sqrt() / SQRT_2
}

#[derive(Debug, PartialEq, Clone)]
pub enum Dasharray {
    None,
    Array(Vec<Length>),
}

impl Default for Dasharray {
    fn default() -> Dasharray {
        Dasharray::None
    }
}

impl Parse for Dasharray {
    type Data = ();
    type Err = AttributeError;

    fn parse(parser: &mut Parser, _: Self::Data) -> Result<Dasharray, AttributeError> {
        if parser.try(|p| p.expect_ident_matching("none")).is_ok() {
            Ok(Dasharray::None)
        } else {
            Ok(Dasharray::Array(parse_dash_array(parser)?))
        }
    }
}

// This does not handle "inherit" or "none" state, the caller is responsible for that.
fn parse_dash_array(parser: &mut Parser) -> Result<Vec<Length>, AttributeError> {
    let mut dasharray = Vec::new();

    loop {
        dasharray.push(
            Length::from_cssparser(parser, LengthDir::Both).and_then(Length::check_nonnegative)?,
        );

        if parser.is_exhausted() {
            break;
        } else if parser.try(|p| p.expect_comma()).is_ok() {
            continue;
        }
    }

    Ok(dasharray)
}

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

    use float_eq_cairo::ApproxEqCairo;

    #[test]
    fn parses_default() {
        assert_eq!(
            Length::parse_str("42", LengthDir::Horizontal),
            Ok(Length::new(
                42.0,
                LengthUnit::Default,
                LengthDir::Horizontal
            ))
        );

        assert_eq!(
            Length::parse_str("-42px", LengthDir::Horizontal),
            Ok(Length::new(
                -42.0,
                LengthUnit::Default,
                LengthDir::Horizontal
            ))
        );
    }

    #[test]
    fn parses_percent() {
        assert_eq!(
            Length::parse_str("50.0%", LengthDir::Horizontal),
            Ok(Length::new(0.5, LengthUnit::Percent, LengthDir::Horizontal))
        );
    }

    #[test]
    fn parses_font_em() {
        assert_eq!(
            Length::parse_str("22.5em", LengthDir::Vertical),
            Ok(Length::new(22.5, LengthUnit::FontEm, LengthDir::Vertical))
        );
    }

    #[test]
    fn parses_font_ex() {
        assert_eq!(
            Length::parse_str("22.5ex", LengthDir::Vertical),
            Ok(Length::new(22.5, LengthUnit::FontEx, LengthDir::Vertical))
        );
    }

    #[test]
    fn parses_physical_units() {
        assert_eq!(
            Length::parse_str("72pt", LengthDir::Both),
            Ok(Length::new(1.0, LengthUnit::Inch, LengthDir::Both))
        );

        assert_eq!(
            Length::parse_str("-22.5in", LengthDir::Both),
            Ok(Length::new(-22.5, LengthUnit::Inch, LengthDir::Both))
        );

        assert_eq!(
            Length::parse_str("-254cm", LengthDir::Both),
            Ok(Length::new(-100.0, LengthUnit::Inch, LengthDir::Both))
        );

        assert_eq!(
            Length::parse_str("254mm", LengthDir::Both),
            Ok(Length::new(10.0, LengthUnit::Inch, LengthDir::Both))
        );

        assert_eq!(
            Length::parse_str("60pc", LengthDir::Both),
            Ok(Length::new(10.0, LengthUnit::Inch, LengthDir::Both))
        );
    }

    #[test]
    fn empty_length_yields_error() {
        assert!(is_parse_error(&Length::parse_str("", LengthDir::Both)));
    }

    #[test]
    fn invalid_unit_yields_error() {
        assert!(is_parse_error(&Length::parse_str(
            "8furlong",
            LengthDir::Both
        )));
    }

    #[test]
    fn check_nonnegative_works() {
        assert!(
            Length::parse_str("0", LengthDir::Both)
                .and_then(|l| l.check_nonnegative())
                .is_ok()
        );
        assert!(
            Length::parse_str("-10", LengthDir::Both)
                .and_then(|l| l.check_nonnegative())
                .is_err()
        );
    }

    #[test]
    fn normalize_default_works() {
        let params = ViewParams {
            dpi_x: 40.0,
            dpi_y: 40.0,
            view_box_width: 100.0,
            view_box_height: 100.0,
        };

        let values = ComputedValues::default();

        assert_approx_eq_cairo!(
            Length::new(10.0, LengthUnit::Default, LengthDir::Both).normalize(&values, &params),
            10.0
        );
    }

    #[test]
    fn normalize_absolute_units_works() {
        let params = ViewParams {
            dpi_x: 40.0,
            dpi_y: 50.0,
            view_box_width: 100.0,
            view_box_height: 100.0,
        };

        let values = ComputedValues::default();

        assert_approx_eq_cairo!(
            Length::new(10.0, LengthUnit::Inch, LengthDir::Horizontal).normalize(&values, &params),
            400.0
        );
        assert_approx_eq_cairo!(
            Length::new(10.0, LengthUnit::Inch, LengthDir::Vertical).normalize(&values, &params),
            500.0
        );
    }

    #[test]
    fn normalize_percent_works() {
        let params = ViewParams {
            dpi_x: 40.0,
            dpi_y: 40.0,
            view_box_width: 100.0,
            view_box_height: 200.0,
        };

        let values = ComputedValues::default();

        assert_approx_eq_cairo!(
            Length::new(0.05, LengthUnit::Percent, LengthDir::Horizontal)
                .normalize(&values, &params),
            5.0
        );
        assert_approx_eq_cairo!(
            Length::new(0.05, LengthUnit::Percent, LengthDir::Vertical).normalize(&values, &params),
            10.0
        );
    }

    fn parse_dash_array_str(s: &str) -> Result<Dasharray, AttributeError> {
        Dasharray::parse_str(s, ())
    }

    #[test]
    fn parses_dash_array() {
        // helper to cut down boilderplate
        let length_parse = |s| Length::parse_str(s, LengthDir::Both).unwrap();

        let expected = Dasharray::Array(vec![
            length_parse("1"),
            length_parse("2in"),
            length_parse("3"),
            length_parse("4%"),
        ]);

        let sample_1 = Dasharray::Array(vec![length_parse("10"), length_parse("6")]);

        let sample_2 = Dasharray::Array(vec![
            length_parse("5"),
            length_parse("5"),
            length_parse("20"),
        ]);

        let sample_3 = Dasharray::Array(vec![
            length_parse("10px"),
            length_parse("20px"),
            length_parse("20px"),
        ]);

        let sample_4 = Dasharray::Array(vec![
            length_parse("25"),
            length_parse("5"),
            length_parse("5"),
            length_parse("5"),
        ]);

        let sample_5 = Dasharray::Array(vec![length_parse("3.1415926"), length_parse("8")]);
        let sample_6 = Dasharray::Array(vec![length_parse("5"), length_parse("3.14")]);
        let sample_7 = Dasharray::Array(vec![length_parse("2")]);

        assert_eq!(parse_dash_array_str("none").unwrap(), Dasharray::None);
        assert_eq!(parse_dash_array_str("1 2in,3 4%").unwrap(), expected);
        assert_eq!(parse_dash_array_str("10,6").unwrap(), sample_1);
        assert_eq!(parse_dash_array_str("5,5,20").unwrap(), sample_2);
        assert_eq!(parse_dash_array_str("10px 20px 20px").unwrap(), sample_3);
        assert_eq!(parse_dash_array_str("25  5 , 5 5").unwrap(), sample_4);
        assert_eq!(parse_dash_array_str("3.1415926,8").unwrap(), sample_5);
        assert_eq!(parse_dash_array_str("5, 3.14").unwrap(), sample_6);
        assert_eq!(parse_dash_array_str("2").unwrap(), sample_7);

        // Negative numbers
        assert_eq!(
            parse_dash_array_str("20,40,-20"),
            Err(AttributeError::Value(String::from(
                "value must be non-negative"
            )))
        );

        // Empty dash_array
        assert!(parse_dash_array_str("").is_err());
        assert!(parse_dash_array_str("\t  \n     ").is_err());
        assert!(parse_dash_array_str(",,,").is_err());
        assert!(parse_dash_array_str("10,  \t, 20 \n").is_err());
        // No trailing commas allowed, parse error
        assert!(parse_dash_array_str("10,").is_err());
        // A comma should be followed by a number
        assert!(parse_dash_array_str("20,,10").is_err());
    }
}
<|endoftext|>


<commit_before>// A function which takes a closure as an argument and calls it.
fn apply<F>(f: F) where
    // The closure takes no input and returns nothing.
    F: FnOnce() {
    // ^ TODO: Try changing this to `Fn` or `FnMut`.

    f();
}

// A function which takes a closure and returns an `i32`.
fn apply_to_3<F>(f: F) -> i32 where
    // The closure takes an `i32` and returns an `i32`.
    F: Fn(i32) -> i32 {

    f(3)
}

fn main() {
    use std::mem;

    let greeting = "hello";
    // A non-copy type.
    // `to_owned` creates owned data from borrowed one
    let mut farewell = "goodbye".to_owned();

    // Capture 2 variables: `greeting` by reference and
    // `farewell` by value.
    let diary = || {
        // `greeting` is by reference: requires `Fn`.
        println!("I said {}.", greeting);

        // Mutation forces `farewell` to be captured by
        // mutable reference. Now requires `FnMut`.
        farewell.push_str("!!!");
        println!("Then I screamed {}.", farewell);
        println!("Now I can sleep. zzzzz");

        // Manually calling drop forces `farewell` to
        // be captured by value. Now requires `FnOnce`.
        mem::drop(farewell);
    };

    // Call the function which applies the closure.
    apply(diary);

    let double = |x| 2 * x;

    println!("3 doubled: {}", apply_to_3(double));
}
<commit_msg>Comment double closure parameter<commit_after>// A function which takes a closure as an argument and calls it.
fn apply<F>(f: F) where
    // The closure takes no input and returns nothing.
    F: FnOnce() {
    // ^ TODO: Try changing this to `Fn` or `FnMut`.

    f();
}

// A function which takes a closure and returns an `i32`.
fn apply_to_3<F>(f: F) -> i32 where
    // The closure takes an `i32` and returns an `i32`.
    F: Fn(i32) -> i32 {

    f(3)
}

fn main() {
    use std::mem;

    let greeting = "hello";
    // A non-copy type.
    // `to_owned` creates owned data from borrowed one
    let mut farewell = "goodbye".to_owned();

    // Capture 2 variables: `greeting` by reference and
    // `farewell` by value.
    let diary = || {
        // `greeting` is by reference: requires `Fn`.
        println!("I said {}.", greeting);

        // Mutation forces `farewell` to be captured by
        // mutable reference. Now requires `FnMut`.
        farewell.push_str("!!!");
        println!("Then I screamed {}.", farewell);
        println!("Now I can sleep. zzzzz");

        // Manually calling drop forces `farewell` to
        // be captured by value. Now requires `FnOnce`.
        mem::drop(farewell);
    };

    // Call the function which applies the closure.
    apply(diary);

    // `double` satisfies `apply_to_3`'s trait bound
    let double = |x| 2 * x;

    println!("3 doubled: {}", apply_to_3(double));
}
<|endoftext|>


<commit_before>//! Register diversions.
//!
//! Normally, a value is assigned to a single register or stack location by the register allocator.
//! Sometimes, it is necessary to move register values to a different register in order to satisfy
//! instruction constraints.
//!
//! These register diversions are local to an EBB. No values can be diverted when entering a new
//! EBB.

use entity::EntityMap;
use ir::{Value, ValueLoc};
use isa::RegUnit;

/// A diversion of a value from its original register location to a new register.
///
/// In IL, a diversion is represented by a `regmove` instruction, possibly a chain of them for the
/// same value.
///
/// When tracking diversions, the `from` field is the original assigned value location, and `to` is
/// the current one.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Diversion {
    /// The value that is diverted.
    pub value: Value,
    /// The original register value location.
    pub from: RegUnit,
    /// The current register value location.
    pub to: RegUnit,
}

impl Diversion {
    /// Make a new register diversion.
    pub fn new(value: Value, from: RegUnit, to: RegUnit) -> Diversion {
        Diversion { value, from, to }
    }
}

/// Keep track of register diversions in an EBB.
pub struct RegDiversions {
    current: Vec<Diversion>,
}

impl RegDiversions {
    /// Create a new empty diversion tracker.
    pub fn new() -> RegDiversions {
        RegDiversions { current: Vec::new() }
    }

    /// Clear the tracker, preparing for a new EBB.
    pub fn clear(&mut self) {
        self.current.clear()
    }

    /// Are there any diversions?
    pub fn is_empty(&self) -> bool {
        self.current.is_empty()
    }

    /// Get the current diversion of `value`, if any.
    pub fn diversion(&self, value: Value) -> Option<&Diversion> {
        self.current.iter().find(|d| d.value == value)
    }

    /// Get all current diversions.
    pub fn all(&self) -> &[Diversion] {
        self.current.as_slice()
    }

    /// Get the current register location for `value`. Fall back to the assignment map for
    /// non-diverted values.
    pub fn reg(&self, value: Value, locations: &EntityMap<Value, ValueLoc>) -> RegUnit {
        match self.diversion(value) {
            Some(d) => d.to,
            None => locations[value].unwrap_reg(),
        }
    }

    /// Record a register move.
    pub fn regmove(&mut self, value: Value, from: RegUnit, to: RegUnit) {
        if let Some(i) = self.current.iter().position(|d| d.value == value) {
            debug_assert_eq!(self.current[i].to, from, "Bad regmove chain for {}", value);
            if self.current[i].from != to {
                self.current[i].to = to;
            } else {
                self.current.swap_remove(i);
            }
        } else {
            self.current.push(Diversion::new(value, from, to));
        }
    }

    /// Drop any recorded register move for `value`.
    ///
    /// Returns the `to` register of the removed diversion.
    pub fn remove(&mut self, value: Value) -> Option<RegUnit> {
        self.current.iter().position(|d| d.value == value).map(
            |i| {
                self.current.swap_remove(i).to
            },
        )
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use ir::Value;
    use entity::EntityRef;

    #[test]
    fn inserts() {
        let mut divs = RegDiversions::new();
        let v1 = Value::new(1);
        let v2 = Value::new(2);

        divs.regmove(v1, 10, 12);
        assert_eq!(
            divs.diversion(v1),
            Some(&Diversion {
                value: v1,
                from: 10,
                to: 12,
            })
        );
        assert_eq!(divs.diversion(v2), None);

        divs.regmove(v1, 12, 11);
        assert_eq!(divs.diversion(v1).unwrap().to, 11);
        divs.regmove(v1, 11, 10);
        assert_eq!(divs.diversion(v1), None);
    }
}
<commit_msg>Add a RegDiversions::display() function.<commit_after>//! Register diversions.
//!
//! Normally, a value is assigned to a single register or stack location by the register allocator.
//! Sometimes, it is necessary to move register values to a different register in order to satisfy
//! instruction constraints.
//!
//! These register diversions are local to an EBB. No values can be diverted when entering a new
//! EBB.

use entity::EntityMap;
use ir::{Value, ValueLoc};
use isa::{RegUnit, RegInfo};
use std::fmt;

/// A diversion of a value from its original register location to a new register.
///
/// In IL, a diversion is represented by a `regmove` instruction, possibly a chain of them for the
/// same value.
///
/// When tracking diversions, the `from` field is the original assigned value location, and `to` is
/// the current one.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Diversion {
    /// The value that is diverted.
    pub value: Value,
    /// The original register value location.
    pub from: RegUnit,
    /// The current register value location.
    pub to: RegUnit,
}

impl Diversion {
    /// Make a new register diversion.
    pub fn new(value: Value, from: RegUnit, to: RegUnit) -> Diversion {
        Diversion { value, from, to }
    }
}

/// Keep track of register diversions in an EBB.
pub struct RegDiversions {
    current: Vec<Diversion>,
}

impl RegDiversions {
    /// Create a new empty diversion tracker.
    pub fn new() -> RegDiversions {
        RegDiversions { current: Vec::new() }
    }

    /// Clear the tracker, preparing for a new EBB.
    pub fn clear(&mut self) {
        self.current.clear()
    }

    /// Are there any diversions?
    pub fn is_empty(&self) -> bool {
        self.current.is_empty()
    }

    /// Get the current diversion of `value`, if any.
    pub fn diversion(&self, value: Value) -> Option<&Diversion> {
        self.current.iter().find(|d| d.value == value)
    }

    /// Get all current diversions.
    pub fn all(&self) -> &[Diversion] {
        self.current.as_slice()
    }

    /// Get the current register location for `value`. Fall back to the assignment map for
    /// non-diverted values.
    pub fn reg(&self, value: Value, locations: &EntityMap<Value, ValueLoc>) -> RegUnit {
        match self.diversion(value) {
            Some(d) => d.to,
            None => locations[value].unwrap_reg(),
        }
    }

    /// Record a register move.
    pub fn regmove(&mut self, value: Value, from: RegUnit, to: RegUnit) {
        if let Some(i) = self.current.iter().position(|d| d.value == value) {
            debug_assert_eq!(self.current[i].to, from, "Bad regmove chain for {}", value);
            if self.current[i].from != to {
                self.current[i].to = to;
            } else {
                self.current.swap_remove(i);
            }
        } else {
            self.current.push(Diversion::new(value, from, to));
        }
    }

    /// Drop any recorded register move for `value`.
    ///
    /// Returns the `to` register of the removed diversion.
    pub fn remove(&mut self, value: Value) -> Option<RegUnit> {
        self.current.iter().position(|d| d.value == value).map(
            |i| {
                self.current.swap_remove(i).to
            },
        )
    }

    /// Return an object that can display the diversions.
    pub fn display<'a, R: Into<Option<&'a RegInfo>>>(&'a self, regs: R) -> DisplayDiversions<'a> {
        DisplayDiversions(self, regs.into())
    }
}

/// Object that displays register diversions.
pub struct DisplayDiversions<'a>(&'a RegDiversions, Option<&'a RegInfo>);

impl<'a> fmt::Display for DisplayDiversions<'a> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{{")?;
        for div in self.0.all() {
            match self.1 {
                Some(regs) => {
                    write!(
                        f,
                        " {}: {} -> {}",
                        div.value,
                        regs.display_regunit(div.from),
                        regs.display_regunit(div.to)
                    )?
                }
                None => write!(f, " {}: %{} -> %{}", div.value, div.from, div.to)?,
            }
        }
        write!(f, " }}")
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use ir::Value;
    use entity::EntityRef;

    #[test]
    fn inserts() {
        let mut divs = RegDiversions::new();
        let v1 = Value::new(1);
        let v2 = Value::new(2);

        divs.regmove(v1, 10, 12);
        assert_eq!(
            divs.diversion(v1),
            Some(&Diversion {
                value: v1,
                from: 10,
                to: 12,
            })
        );
        assert_eq!(divs.diversion(v2), None);

        divs.regmove(v1, 12, 11);
        assert_eq!(divs.diversion(v1).unwrap().to, 11);
        divs.regmove(v1, 11, 10);
        assert_eq!(divs.diversion(v1), None);
    }
}
<|endoftext|>


<commit_before>extern crate clap;
extern crate image;

use clap::{Arg,App,SubCommand};
use std::fs::File;
use image::{FilterType, GenericImage, Pixel};
use image::DynamicImage;
use std::path::Path;

fn main() {
     let matches = App::new("IPCLI")
                          .version("0.1")
                          .author("Mikolaj Wawrzyniak <mikolaj.wawrzyniak at fh-dortmund.de>")
                          .about("Basic CLI for image processing")
                          .arg(Arg::with_name("operation")
                               .short("o")
                               .long("operation")
                               .help("Specifies operation to be done on the image")
                               .takes_value(true)
                               .required(true))
                          .arg(Arg::with_name("image")
                               .short("i")
                               .long("image")
                               .value_name("FILE")
                               .help("Opens specified image file and uses it for transformations.")
                               .takes_value(true)
                               .required(true))
                          .arg(Arg::with_name("save")
                               .short("s")
                               .long("save")
                               .value_name("FILE")
                               .help("Outputs transformed image into specified file OR uses the second file to calculate the perceptual hash values and outputs their difference")
                               .takes_value(true)
                               .required(true))
                          .get_matches();

    // Gets a value for config if supplied by user, or defaults to "default.conf"
    let imagePath = matches.value_of("image").unwrap_or("empty");
    println!("Transforming the image: {}", imagePath);

    let savePath = matches.value_of("save").unwrap_or("empty");
    println!("Into: {}", savePath);

    let operation = matches.value_of("operation").unwrap_or("empty");
    println!("Operation: {}", operation);

    match operation.as_ref(){
        "copy" => {openAndSave(imagePath, savePath)}
        "thumbnail" => {createThumnbail(imagePath, savePath)}
        _ => {println!("Not implemented yet!")}
    }
    //openAndSave(imagePath, savePath);
    // Vary the output based on how many times the user used the "verbose" flag
    // (i.e. 'myprog -v -v -v' or 'myprog -vvv' vs 'myprog -v'
    match matches.occurrences_of("v") {
        0 => println!("No verbose info"),
        1 => println!("Some verbose info"),
        2 => println!("Tons of verbose info"),
        3 | _ => println!("Don't be crazy"),
    }
}

fn createThumnbail(i: &str, s: &str){
    let img = image::open(i).expect("Opening image failed");
    // get the type of the image
    let thumbnail = img.resize(120,120, FilterType::Lanczos3);
    let ext: String = i.chars().skip(i.len()-3).take(3).collect();
    let mut out = File::create(s).unwrap();
    match ext.as_ref() {
        "jpg" => {thumbnail.save(&mut out, image::JPEG).expect("Saving image failed");}
        "png" => {thumbnail.save(&mut out, image::PNG).expect("Saving image failed");}
        _ => {println!("something else")}
    }
}

fn openAndSave(i: &str, s: &str){
    let img = image::open(i).expect("Opening image failed");
    // get the type of the image
    let ext: String = i.chars().skip(i.len()-3).take(3).collect();
    let mut out = File::create(s).unwrap();
    match ext.as_ref() {
        "jpg" => {img.save(&mut out, image::JPEG).expect("Saving image failed");}
        "png" => {img.save(&mut out, image::PNG).expect("Saving image failed");}
        _ => {println!("something else")}
    }

}
<commit_msg>Refactoring<commit_after>extern crate clap;
extern crate image;

use clap::{Arg,App,SubCommand};
use std::fs::File;
use image::{FilterType, GenericImage, Pixel};
use image::DynamicImage;
use std::path::Path;

fn main() {
     let matches = App::new("IPCLI")
                          .version("0.1")
                          .author("Mikolaj Wawrzyniak <mikolaj.wawrzyniak at fh-dortmund.de>")
                          .about("Basic CLI for image processing")
                          .arg(Arg::with_name("operation")
                               .short("o")
                               .long("operation")
                               .help("Specifies operation to be done on the image")
                               .takes_value(true)
                               .required(true))
                          .arg(Arg::with_name("image")
                               .short("i")
                               .long("image")
                               .value_name("FILE")
                               .help("Opens specified image file and uses it for transformations.")
                               .takes_value(true)
                               .required(true))
                          .arg(Arg::with_name("save")
                               .short("s")
                               .long("save")
                               .value_name("FILE")
                               .help("Outputs transformed image into specified file OR uses the second file to calculate the perceptual hash values and outputs their difference")
                               .takes_value(true)
                               .required(true))
                          .get_matches();

    let imagePath = matches.value_of("image").unwrap_or("empty");
    println!("Transforming the image: {}", imagePath);

    let savePath = matches.value_of("save").unwrap_or("empty");
    println!("Into: {}", savePath);

    let operation = matches.value_of("operation").unwrap_or("empty");
    println!("Operation: {}", operation);

    match operation.as_ref(){
        "copy" => {openAndSave(imagePath, savePath)}
        "thumbnail" => {createThumnbail(imagePath, savePath)}
        _ => {println!("Not implemented yet!")}
    }

    match matches.occurrences_of("v") {
        0 => println!("No verbose info"),
        1 => println!("Some verbose info"),
        2 => println!("Tons of verbose info"),
        3 | _ => println!("Don't be crazy"),
    }
}

fn createThumnbail(i: &str, s: &str){
    let img = image::open(i).expect("Opening image failed");
    let thumbnail = img.resize(120,120, FilterType::Lanczos3);
    saveFile(&thumbnail, &i, &s);

}

fn openAndSave(i: &str, s: &str){
    let img = image::open(i).expect("Opening image failed");
    saveFile(&img, &i, &s);
}

fn saveFile(img: &DynamicImage, i: &str, s: &str){
        let mut out = File::create(s).unwrap();
        let ext: String = i.chars().skip(i.len()-3).take(3).collect();
        match ext.as_ref() {
            "jpg" => {img.save(&mut out, image::JPEG).expect("Saving image failed");}
            "png" => {img.save(&mut out, image::PNG).expect("Saving image failed");}
            _ => {println!("something else")}
        }
}

<|endoftext|>


<commit_before>#[derive(Debug)]
enum Val {
	Int(i32),
	Float(f64),
	Str(String),
	List(Vec<Val>),
	Error(String),
	Obj(i32),
}

fn main() {
	let v1 = Val::Int(123);

	let v2 = Val::Str("foo".to_string());

	let v3 = Val::List(vec![
		Val::Int(1),
		Val::Str("foo".to_string()),
		Val::Int(3)
	]);

	let v4 = Val::Float(1.23);

	let v5 = Val::Obj(456);

	let v6 = Val::Error("INVARG".to_string());

	let vals = vec![v1, v2, v3, v4, v5, v6];

	for v in vals {
		println!("{:?}", v);
	}
}
<commit_msg>Toying with errors<commit_after>#[derive(Debug)]
enum Error {
	NONE,
	TYPE,
	DIV,
	PERM,
	PROPNF,
	// etc.
}

#[derive(Debug)]
enum Val {
	Int(i32),
	Float(f64),
	Str(String),
	List(Vec<Val>),
	Error(Error),
	Obj(i32)
}

fn main() {
	let v1 = Val::Int(123);

	let v2 = Val::Str("foo".to_string());

	let v3 = Val::List(vec![
		Val::Int(1),
		Val::Str("foo".to_string()),
		Val::Int(3)
	]);

	let v4 = Val::Float(1.23);

	let v5 = Val::Obj(456);

	let v6 = Val::Error(Error::NONE);

	let vals = vec![v1, v2, v3, v4, v5, v6];

	for v in vals {
		println!("{:?}", v);
	}
}
<|endoftext|>


<commit_before>use solicit::http::client::tls::TlsConnector;
use solicit::http::{Header, Response as HttpResponse};
use solicit::client::{Client};
use notification::*;
use response::*;
use openssl::ssl::{SslContext, SslMethod, SSL_VERIFY_NONE};
use openssl::x509::X509;
use openssl::crypto::pkey::PKey;
use time::{Tm, Timespec, at};
use rustc_serialize::json::{Json, Object};
use std::str;
use std::time::{Instant, Duration};
use std::fmt::Display;
use std::result::Result;
use std::thread;
use std::fs::File;
use std::io::Read;

static DEVELOPMENT: &'static str = "api.development.push.apple.com";
static PRODUCTION: &'static str = "api.push.apple.com";

pub struct Provider {
    pub client: Client,
    request_timeout: Duration
}

impl Provider {
    pub fn new(sandbox: bool, certificate: &str, private_key: &str) -> Provider {
        Provider::from_reader(sandbox,
                              &mut File::open(certificate).unwrap(),
                              &mut File::open(private_key).unwrap())
    }

    pub fn from_reader<R: Read>(sandbox: bool, certificate: &mut R, private_key: &mut R) -> Provider {
        let mut ctx = SslContext::new(SslMethod::Sslv23).unwrap();
        let host    = if sandbox { DEVELOPMENT } else { PRODUCTION };
        let x509 = X509::from_pem(certificate).unwrap();
        let pkey = PKey::private_key_from_pem(private_key).unwrap();

        ctx.set_cipher_list("DEFAULT").unwrap();
        ctx.set_certificate(&x509).unwrap();
        ctx.set_private_key(&pkey).unwrap();
        ctx.set_verify(SSL_VERIFY_NONE, None);

        let connector = TlsConnector::with_context(host, &ctx);
        let client    = Client::with_connector(connector).unwrap();

        Provider {
            client: client,
            request_timeout: Duration::new(2, 0),
        }
    }

    pub fn push(&self, notification: Notification) -> Result<Response, Response> {
        if let Ok(http_response) = self.request(notification) {
            let status        = Provider::fetch_status(http_response.status_code().ok());
            let apns_id       = Provider::fetch_apns_id(http_response.headers);
            let json          = str::from_utf8(&http_response.body).ok().and_then(|v| Json::from_str(v).ok());
            let object        = json.as_ref().and_then(|v| v.as_object());
            let reason        = Provider::fetch_reason(object);
            let timestamp     = Provider::fetch_timestamp(object);

            if status == APNSStatus::Success {
                Ok(Response {
                    status: status,
                    reason: reason,
                    timestamp: timestamp,
                    apns_id: apns_id,
                })
            } else {
                Err(Response {
                    status: status,
                    reason: reason,
                    timestamp: timestamp,
                    apns_id: apns_id,
                })
            }
        } else {
            Err(Response {
                status: APNSStatus::Timeout,
                reason: None,
                timestamp: None,
                apns_id: None,
            })
        }
    }

    fn request(&self, notification: Notification) -> Result<HttpResponse, APNSStatus> {
        let path = format!("/3/device/{}", notification.device_token).into_bytes();
        let body = notification.payload.to_string().into_bytes();
        let mut headers = Vec::new();
        headers.push(Provider::create_header("content_length", notification.payload.len()));
        if let Some(apns_id) = notification.options.apns_id {
            headers.push(Provider::create_header("apns-id", apns_id));
        }
        if let Some(apns_expiration) = notification.options.apns_expiration {
            headers.push(Provider::create_header("apns-expiration", apns_expiration));
        }
        if let Some(apns_priority) = notification.options.apns_priority {
            headers.push(Provider::create_header("apns-priority", apns_priority));
        }
        if let Some(apns_topic) = notification.options.apns_topic {
            headers.push(Provider::create_header("apns-topic", apns_topic));
        }
        let resp = self.client.post(&path, headers.as_slice(), body).unwrap();
        let now = Instant::now();

        while now.elapsed() < self.request_timeout {
            match resp.try_recv() {
                Ok(http_response) => return Ok(http_response),
                _                 => thread::park_timeout(Duration::from_millis(10)),
            }
        }
        Err(APNSStatus::Timeout)
    }

    fn create_header<'a, T: Display>(key: &'a str, value: T) -> Header<'a, 'a> {
        Header::new(key.as_bytes(), format!("{}", value).into_bytes())
    }

    fn fetch_status(code: Option<u16>) -> APNSStatus {
        match code {
            Some(200) => APNSStatus::Success,
            Some(400) => APNSStatus::BadRequest,
            Some(403) => APNSStatus::Forbidden,
            Some(405) => APNSStatus::MethodNotAllowed,
            Some(410) => APNSStatus::Unregistered,
            Some(413) => APNSStatus::PayloadTooLarge,
            Some(429) => APNSStatus::TooManyRequests,
            Some(500) => APNSStatus::InternalServerError,
            Some(503) => APNSStatus::ServiceUnavailable,
            _         => APNSStatus::Unknown,
        }
    }

    fn fetch_apns_id(headers: Vec<Header>) -> Option<String> {
        headers.iter().find(|&header| {
            match str::from_utf8(header.name()).unwrap() {
                "apns-id" => true,
                _         => false,
            }
        }).map(|header| {
            String::from_utf8(header.value().to_vec()).unwrap()
        })
    }

    fn fetch_reason(js_object: Option<&Object>) -> Option<APNSError> {
        let raw_reason = js_object.and_then(|v| v.get("reason")).and_then(|v| v.as_string());
        match raw_reason {
            Some("PayloadEmpty")              => Some(APNSError::PayloadEmpty),
            Some("PayloadTooLarge")           => Some(APNSError::PayloadTooLarge),
            Some("BadTopic")                  => Some(APNSError::BadTopic),
            Some("TopicDisallowed")           => Some(APNSError::TopicDisallowed),
            Some("BadMessageId")              => Some(APNSError::BadMessageId),
            Some("BadExpirationDate")         => Some(APNSError::BadExpirationDate),
            Some("BadPriority")               => Some(APNSError::BadPriority),
            Some("MissingDeviceToken")        => Some(APNSError::MissingDeviceToken),
            Some("BadDeviceToken")            => Some(APNSError::BadDeviceToken),
            Some("DeviceTokenNotForTopic")    => Some(APNSError::DeviceTokenNotForTopic),
            Some("Unresgistered")             => Some(APNSError::Unregistered),
            Some("DuplicateHeaders")          => Some(APNSError::DuplicateHeaders),
            Some("BadCertificateEnvironment") => Some(APNSError::BadCertificateEnvironment),
            Some("BadCertificate")            => Some(APNSError::BadCertificate),
            Some("Forbidden")                 => Some(APNSError::Forbidden),
            Some("BadPath")                   => Some(APNSError::BadPath),
            Some("MethodNotAllowed")          => Some(APNSError::MethodNotAllowed),
            Some("TooManyRequests")           => Some(APNSError::TooManyRequests),
            Some("IdleTimeout")               => Some(APNSError::IdleTimeout),
            Some("Shutdown")                  => Some(APNSError::Shutdown),
            Some("InternalServerError")       => Some(APNSError::InternalServerError),
            Some("ServiceUnavailable")        => Some(APNSError::ServiceUnavailable),
            Some("MissingTopic")              => Some(APNSError::MissingTopic),
            _                                 => None,
        }
    }

    fn fetch_timestamp(js_object: Option<&Object>) -> Option<Tm> {
        let raw_ts = js_object.and_then(|v| v.get("timestamp")).and_then(|v| v.as_i64());
        match raw_ts {
            Some(ts) => Some(at(Timespec::new(ts, 0))),
            None     => None,
        }
    }
}
<commit_msg>Now can be used with asynchronous calls<commit_after>use notification::*;
use response::*;
// Time and serialization
use time::{Tm, Timespec, at};
use rustc_serialize::json::*;
// Standard lib
use std::str;
use std::fmt::Display;
use std::result::Result;
use std::thread;
use std::fs::File;
use std::io::Read;
// Solicit
use solicit::http::client::tls::TlsConnector;
use solicit::http::{Header, HttpError, Response as HttpResponse};
use solicit::client::Client;
use solicit::http::ALPN_PROTOCOLS;
// Open SSL
use openssl::ssl::*;
use openssl::x509::X509;
use openssl::crypto::pkey::PKey;

static DEVELOPMENT: &'static str = "api.development.push.apple.com";
static PRODUCTION:  &'static str = "api.push.apple.com";

pub struct Provider {
    pub client: Client,
}

impl Provider {
    pub fn new(sandbox: bool, certificate: &str, private_key: &str) -> Provider {
        Provider::from_reader(sandbox,
                              &mut File::open(certificate).unwrap(),
                              &mut File::open(private_key).unwrap())
    }

    pub fn from_reader<R: Read>(sandbox: bool, certificate: &mut R, private_key: &mut R) -> Provider {
        let host    = if sandbox { DEVELOPMENT } else { PRODUCTION };
        let x509 = X509::from_pem(certificate).unwrap();
        let pkey = PKey::private_key_from_pem(private_key).unwrap();

        let mut ctx = SslContext::new(SslMethod::Tlsv1_2).unwrap();
        ctx.set_cipher_list("DEFAULT").unwrap();
        ctx.set_certificate(&x509).unwrap();
        ctx.set_private_key(&pkey).unwrap();
        ctx.set_options(SSL_OP_NO_COMPRESSION);
        ctx.set_alpn_protocols(ALPN_PROTOCOLS);
        ctx.set_npn_protocols(ALPN_PROTOCOLS);

        let connector = TlsConnector::with_context(host, &ctx);
        let client    = Client::with_connector(connector).unwrap();

        Provider {
            client: client,
        }
    }

    pub fn push(&self, notification: Notification) /*-> Result<Response, Response>*/ {
        self.request(notification);
        /*if let Ok(http_response) = self.request(notification) {
            let status        = Provider::fetch_status(http_response.status_code().ok());
            let apns_id       = Provider::fetch_apns_id(http_response.headers);
            let json          = str::from_utf8(&http_response.body).ok().and_then(|v| Json::from_str(v).ok());
            let object        = json.as_ref().and_then(|v| v.as_object());
            let reason        = Provider::fetch_reason(object);
            let timestamp     = Provider::fetch_timestamp(object);
            if status == APNSStatus::Success {
                Ok(Response {
                    status: status,
                    reason: reason,
                    timestamp: timestamp,
                    apns_id: apns_id,
                })
            } else {
                Err(Response {
                    status: status,
                    reason: reason,
                    timestamp: timestamp,
                    apns_id: apns_id,
                })
            }
        } else {
            Err(Response {
                status: APNSStatus::Timeout,
                reason: None,
                timestamp: None,
                apns_id: None,
            })
        }*/
    }

    fn request(&self, notification: Notification) /*-> Result<HttpResponse, APNSStatus>*/ {
        let path = format!("/3/device/{}", notification.device_token).into_bytes();
        let body = notification.payload.to_string().into_bytes();
        let mut headers = Vec::new();
        headers.push(Provider::create_header("content_length", notification.payload.len()));
        if let Some(apns_id) = notification.options.apns_id {
            headers.push(Provider::create_header("apns-id", apns_id));
        }
        if let Some(apns_expiration) = notification.options.apns_expiration {
            headers.push(Provider::create_header("apns-expiration", apns_expiration));
        }
        if let Some(apns_priority) = notification.options.apns_priority {
            headers.push(Provider::create_header("apns-priority", apns_priority));
        }
        if let Some(apns_topic) = notification.options.apns_topic {
            headers.push(Provider::create_header("apns-topic", apns_topic));
        }

        let this = self.client.clone();
        thread::spawn(move || {
            let resp = this.post(&path, headers.as_slice(), body).unwrap();
            match resp.recv() {
                Ok(response) => {
                    println!("Got response ... {}", response.status_code().ok().unwrap());
                },
                Err(why) => println!("{:?}", why),
            }
            /*if let response = resp.recv() {
                println!("Got response ... {}", response.status_code().ok().unwrap());
                for header in response.headers.iter() {
                    println!("  {}: {}",
                    str::from_utf8(header.name()).unwrap(),
                    str::from_utf8(header.value()).unwrap());
                }
            }
            else {
                println!("ERROR: !!!!");
            }*/
        });
        /*let now = Instant::now();
        let timeout = Duration::from_millis(2000);
        println!(">>> {:?}", now);
        while now.elapsed() < timeout {
                match rx.try_recv() {
                Ok(http_response) => return Ok(http_response),
                _                 => thread::park_timeout(Duration::from_millis(10)),
            }
        }
        Err(APNSStatus::Timeout)*/
    }

    fn create_header<'a, T: Display>(key: &'a str, value: T) -> Header<'a, 'a> {
        Header::new(key.as_bytes(), format!("{}", value).into_bytes())
    }

    fn fetch_status(code: Option<u16>) -> APNSStatus {
        match code {
            Some(200) => APNSStatus::Success,
            Some(400) => APNSStatus::BadRequest,
            Some(403) => APNSStatus::Forbidden,
            Some(405) => APNSStatus::MethodNotAllowed,
            Some(410) => APNSStatus::Unregistered,
            Some(413) => APNSStatus::PayloadTooLarge,
            Some(429) => APNSStatus::TooManyRequests,
            Some(500) => APNSStatus::InternalServerError,
            Some(503) => APNSStatus::ServiceUnavailable,
            _         => APNSStatus::Unknown,
        }
    }

    fn fetch_apns_id(headers: Vec<Header>) -> Option<String> {
        headers.iter().find(|&header| {
            match str::from_utf8(header.name()).unwrap() {
                "apns-id" => true,
                _         => false,
            }
        }).map(|header| {
            String::from_utf8(header.value().to_vec()).unwrap()
        })
    }

    fn fetch_reason(js_object: Option<&Object>) -> Option<APNSError> {
        let raw_reason = js_object.and_then(|v| v.get("reason")).and_then(|v| v.as_string());
        match raw_reason {
            Some("PayloadEmpty")              => Some(APNSError::PayloadEmpty),
            Some("PayloadTooLarge")           => Some(APNSError::PayloadTooLarge),
            Some("BadTopic")                  => Some(APNSError::BadTopic),
            Some("TopicDisallowed")           => Some(APNSError::TopicDisallowed),
            Some("BadMessageId")              => Some(APNSError::BadMessageId),
            Some("BadExpirationDate")         => Some(APNSError::BadExpirationDate),
            Some("BadPriority")               => Some(APNSError::BadPriority),
            Some("MissingDeviceToken")        => Some(APNSError::MissingDeviceToken),
            Some("BadDeviceToken")            => Some(APNSError::BadDeviceToken),
            Some("DeviceTokenNotForTopic")    => Some(APNSError::DeviceTokenNotForTopic),
            Some("Unresgistered")             => Some(APNSError::Unregistered),
            Some("DuplicateHeaders")          => Some(APNSError::DuplicateHeaders),
            Some("BadCertificateEnvironment") => Some(APNSError::BadCertificateEnvironment),
            Some("BadCertificate")            => Some(APNSError::BadCertificate),
            Some("Forbidden")                 => Some(APNSError::Forbidden),
            Some("BadPath")                   => Some(APNSError::BadPath),
            Some("MethodNotAllowed")          => Some(APNSError::MethodNotAllowed),
            Some("TooManyRequests")           => Some(APNSError::TooManyRequests),
            Some("IdleTimeout")               => Some(APNSError::IdleTimeout),
            Some("Shutdown")                  => Some(APNSError::Shutdown),
            Some("InternalServerError")       => Some(APNSError::InternalServerError),
            Some("ServiceUnavailable")        => Some(APNSError::ServiceUnavailable),
            Some("MissingTopic")              => Some(APNSError::MissingTopic),
            _                                 => None,
        }
    }

    fn fetch_timestamp(js_object: Option<&Object>) -> Option<Tm> {
        let raw_ts = js_object.and_then(|v| v.get("timestamp")).and_then(|v| v.as_i64());
        match raw_ts {
            Some(ts) => Some(at(Timespec::new(ts, 0))),
            None     => None,
        }
    }
}
<|endoftext|>


<commit_before>// Copyright 2014-2015 Johannes Köster, Vadim Nazarov, Patrick Marks
// Licensed under the MIT license (http://opensource.org/licenses/MIT)
// This file may not be copied, modified, or distributed
// except according to those terms.

//! Various alignment and distance computing algorithms.

use utils::TextSlice;

pub mod pairwise;
pub mod distance;
pub mod sparse;

/// Alignment operations supported are match, substitution, insertion, deletion
/// and clipping. Clipping is a special boundary condition where you are allowed
/// to clip off the beginning/end of the sequence for a fixed clip penalty. The
/// clip penalty could be different for the two sequences x and y, and the
/// clipping operations on both are distinguishable (Xclip and Yclip). The usize
/// value associated with the clipping operations are the lengths clipped. In case
/// of standard modes like Global, Semi-Global and Local alignment, the clip operations
/// are filtered out
#[derive(Eq, PartialEq, Debug, Copy, Clone, Serialize, Deserialize)]
pub enum AlignmentOperation {
    Match,
    Subst,
    Del,
    Ins,
    Xclip(usize),
    Yclip(usize),
}

/// The modes of alignment supported by the aligner include standard modes such as
/// Global, Semi-Global and Local alignment. In addition to this, user can also invoke
/// the custom mode. In the custom mode, users can explicitly specify the clipping penalties
/// for prefix and suffix of strings 'x' and 'y' independently. Under the hood the standard
/// modes are implemented as special cases of the custom mode with the clipping penalties
/// appropriately set
#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)]
pub enum AlignmentMode {
    Local,
    Semiglobal,
    Global,
    Custom,
}

/// We consider alignment between two sequences x and  y. x is the query or read sequence
/// and y is the reference or template sequence. An alignment, consisting of a score,
/// the start and end position of the alignment on sequence x and sequence y, the
/// lengths of sequences x and y, and the alignment edit operations. The start position
/// and end position of the alignment does not include the clipped regions. The length
/// of clipped regions are already encapsulated in the Alignment Operation.
#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
pub struct Alignment {
    /// Smith-Waterman alignment score
    pub score: i32,

    /// Start position of alignment in reference
    pub ystart: usize,

    /// Start position of alignment in query
    pub xstart: usize,

    /// End position of alignment in reference
    pub yend: usize,

    /// End position of alignment in query
    pub xend: usize,

    /// Length of the reference sequence
    pub ylen: usize,

    /// Length of the query sequence
    pub xlen: usize,

    /// Vector of alignment operations
    pub operations: Vec<AlignmentOperation>,
    pub mode: AlignmentMode,
}

impl Alignment {
    /// Return the pretty formatted alignment as a String. The string
    /// contains sets of 3 lines of length 100. First line is for the
    /// sequence x, second line is for the alignment operation and the
    /// the third line is for the sequence y. A '-' in the sequence
    /// indicates a blank (insertion/deletion). The operations follow
    /// the following convention: '|' for a match, '\' for a mismatch,
    /// '+' for an insertion, 'x' for a deletion and ' ' for clipping
    ///
    /// # Example
    ///
    /// ```
    /// use bio::alignment::pairwise::*;
    ///
    /// let x = b"CCGTCCGGCAAGGG";
    /// let y = b"AAAAACCGTTGACGGCCAA";
    /// let score = |a: u8, b: u8| if a == b {1i32} else {-2i32};
    ///
    /// let mut aligner = Aligner::with_capacity(x.len(), y.len(), -5, -1, &score);
    /// let alignment = aligner.semiglobal(x, y);
    /// println!("Semiglobal: \n{}\n", alignment.pretty(x, y));
    /// // Semiglobal:
    /// //      CCGTCCGGCAAGGG
    /// //      ||||++++\\|\||
    /// // AAAAACCGT----TGACGGCCAA

    /// let alignment = aligner.local(x, y);
    /// println!("Local: \n{}\n", alignment.pretty(x, y));
    /// // Local:
    /// //      CCGTCCGGCAAGGG
    /// //      ||||
    /// // AAAAACCGT          TGACGGCCAA

    /// let alignment = aligner.global(x, y);
    /// println!("Global: \n{}\n", alignment.pretty(x, y));
    /// // Global:
    /// // -----CCGT--CCGGCAAGGG
    /// // xxxxx||||xx\||||\|++\
    /// // AAAAACCGTTGACGGCCA--A
    /// ```
    pub fn pretty(&self, x: TextSlice, y: TextSlice) -> String {
        let mut x_pretty = String::new();
        let mut y_pretty = String::new();
        let mut inb_pretty = String::new();

        if !self.operations.is_empty() {
            let mut x_i: usize;
            let mut y_i: usize;

            // If the alignment mode is one of the standard ones, the prefix clipping is
            // implicit so we need to process it here
            match self.mode {
                AlignmentMode::Custom => {
                    x_i = 0;
                    y_i = 0;
                }
                _ => {
                    x_i = self.xstart;
                    y_i = self.ystart;
                    for k in 0..x_i {
                        x_pretty.push_str(&format!("{}", String::from_utf8_lossy(&[x[k]])));
                        inb_pretty.push(' ');
                        y_pretty.push(' ')
                    }
                    for k in 0..y_i {
                        y_pretty.push_str(&format!("{}", String::from_utf8_lossy(&[y[k]])));
                        inb_pretty.push(' ');
                        x_pretty.push(' ')
                    }
                }
            }

            // Process the alignment.
            for i in 0..self.operations.len() {
                match self.operations[i] {
                    AlignmentOperation::Match => {
                        x_pretty.push_str(&format!("{}", String::from_utf8_lossy(&[x[x_i]])));
                        x_i += 1;

                        inb_pretty.push_str("|");

                        y_pretty.push_str(&format!("{}", String::from_utf8_lossy(&[y[y_i]])));
                        y_i += 1;
                    }
                    AlignmentOperation::Subst => {
                        x_pretty.push_str(&format!("{}", String::from_utf8_lossy(&[x[x_i]])));
                        x_i += 1;

                        inb_pretty.push('\\');

                        y_pretty.push_str(&format!("{}", String::from_utf8_lossy(&[y[y_i]])));
                        y_i += 1;
                    }
                    AlignmentOperation::Del => {
                        x_pretty.push('-');

                        inb_pretty.push('x');

                        y_pretty.push_str(&format!("{}", String::from_utf8_lossy(&[y[y_i]])));
                        y_i += 1;
                    }
                    AlignmentOperation::Ins => {
                        x_pretty.push_str(&format!("{}", String::from_utf8_lossy(&[x[x_i]])));
                        x_i += 1;

                        inb_pretty.push('+');

                        y_pretty.push('-');
                    }
                    AlignmentOperation::Xclip(len) => {
                        for k in 0..len {
                            x_pretty.push_str(&format!("{}", String::from_utf8_lossy(&[x[k]])));
                            x_i += 1;

                            inb_pretty.push(' ');

                            y_pretty.push(' ')
                        }
                    }
                    AlignmentOperation::Yclip(len) => {
                        for k in 0..len {
                            y_pretty.push_str(&format!("{}", String::from_utf8_lossy(&[y[k]])));
                            y_i += 1;

                            inb_pretty.push(' ');

                            x_pretty.push(' ')
                        }
                    }
                }
            }

            // If the alignment mode is one of the standard ones, the suffix clipping is
            // implicit so we need to process it here
            match self.mode {
                AlignmentMode::Custom => {}
                _ => {
                    for k in x_i..self.xlen {
                        x_pretty.push_str(&format!("{}", String::from_utf8_lossy(&[x[k]])));
                        inb_pretty.push(' ');
                        y_pretty.push(' ')
                    }
                    for k in y_i..self.ylen {
                        y_pretty.push_str(&format!("{}", String::from_utf8_lossy(&[y[k]])));
                        inb_pretty.push(' ');
                        x_pretty.push(' ')
                    }
                }
            }
        }

        let mut s = String::new();
        let mut idx = 0;
        let step = 100; // Number of characters per line
        use std::cmp::min;

        assert_eq!(x_pretty.len(), inb_pretty.len());
        assert_eq!(y_pretty.len(), inb_pretty.len());

        let ml = x_pretty.len();

        while idx < ml {
            let rng = idx..min(idx + step, ml);
            s.push_str(&x_pretty[rng.clone()]);
            s.push_str("\n");

            s.push_str(&inb_pretty[rng.clone()]);
            s.push_str("\n");

            s.push_str(&y_pretty[rng]);
            s.push_str("\n");

            s.push_str("\n\n");
            idx += step;
        }

        s
    }

    /// Returns the optimal path in the alignment matrix
    pub fn path(&self) -> Vec<(usize, usize, AlignmentOperation)> {
        let mut path = Vec::new();

        if !self.operations.is_empty() {
            let last = match self.mode {
                AlignmentMode::Custom => (self.xlen, self.ylen),
                _ => (self.xend, self.yend),
            };
            let mut x_i = last.0;
            let mut y_i = last.1;

            let mut ops = self.operations.clone();
            ops.reverse();

            // Process the alignment.
            for i in 0..ops.len() {
                path.push((x_i, y_i, ops[i]));
                match ops[i] {
                    AlignmentOperation::Match => {
                        x_i -= 1;
                        y_i -= 1;
                    }
                    AlignmentOperation::Subst => {
                        x_i -= 1;
                        y_i -= 1;
                    }
                    AlignmentOperation::Del => {
                        y_i -= 1;
                    }
                    AlignmentOperation::Ins => {
                        x_i -= 1;
                    }
                    AlignmentOperation::Xclip(len) => {
                        x_i -= len;
                    }
                    AlignmentOperation::Yclip(len) => {
                        y_i -= len;
                    }
                }
            }
        }
        path.reverse();
        path
    }

    /// Filter out Xclip and Yclip operations from the list of operations. Useful
    /// when invoking the standard modes.
    pub fn filter_clip_operations(&mut self) {
        use self::AlignmentOperation::{Match, Subst, Ins, Del};
        self.operations
            .retain(|&ref x| (*x == Match || *x == Subst || *x == Ins || *x == Del));
    }

    /// Number of bases in reference sequence that are aligned
    pub fn y_aln_len(&self) -> usize {
        self.yend - self.ystart
    }

    /// Number of bases in query sequence that are aigned
    pub fn x_aln_len(&self) -> usize {
        self.xend - self.xstart
    }
}<commit_msg>restore non-htslib cigar string method & tests (#153)<commit_after>// Copyright 2014-2015 Johannes Köster, Vadim Nazarov, Patrick Marks
// Licensed under the MIT license (http://opensource.org/licenses/MIT)
// This file may not be copied, modified, or distributed
// except according to those terms.

//! Various alignment and distance computing algorithms.

use utils::TextSlice;

pub mod pairwise;
pub mod distance;
pub mod sparse;

/// Alignment operations supported are match, substitution, insertion, deletion
/// and clipping. Clipping is a special boundary condition where you are allowed
/// to clip off the beginning/end of the sequence for a fixed clip penalty. The
/// clip penalty could be different for the two sequences x and y, and the
/// clipping operations on both are distinguishable (Xclip and Yclip). The usize
/// value associated with the clipping operations are the lengths clipped. In case
/// of standard modes like Global, Semi-Global and Local alignment, the clip operations
/// are filtered out
#[derive(Eq, PartialEq, Debug, Copy, Clone, Serialize, Deserialize)]
pub enum AlignmentOperation {
    Match,
    Subst,
    Del,
    Ins,
    Xclip(usize),
    Yclip(usize),
}

/// The modes of alignment supported by the aligner include standard modes such as
/// Global, Semi-Global and Local alignment. In addition to this, user can also invoke
/// the custom mode. In the custom mode, users can explicitly specify the clipping penalties
/// for prefix and suffix of strings 'x' and 'y' independently. Under the hood the standard
/// modes are implemented as special cases of the custom mode with the clipping penalties
/// appropriately set
#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)]
pub enum AlignmentMode {
    Local,
    Semiglobal,
    Global,
    Custom,
}

/// We consider alignment between two sequences x and  y. x is the query or read sequence
/// and y is the reference or template sequence. An alignment, consisting of a score,
/// the start and end position of the alignment on sequence x and sequence y, the
/// lengths of sequences x and y, and the alignment edit operations. The start position
/// and end position of the alignment does not include the clipped regions. The length
/// of clipped regions are already encapsulated in the Alignment Operation.
#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
pub struct Alignment {
    /// Smith-Waterman alignment score
    pub score: i32,

    /// Start position of alignment in reference
    pub ystart: usize,

    /// Start position of alignment in query
    pub xstart: usize,

    /// End position of alignment in reference
    pub yend: usize,

    /// End position of alignment in query
    pub xend: usize,

    /// Length of the reference sequence
    pub ylen: usize,

    /// Length of the query sequence
    pub xlen: usize,

    /// Vector of alignment operations
    pub operations: Vec<AlignmentOperation>,
    pub mode: AlignmentMode,
}

impl Alignment {
    /// Calculate the cigar string from the alignment struct. x is the target string
    ///
    /// # Example
    ///
    /// ```
    /// use bio::alignment::{Alignment,AlignmentMode};
    /// use bio::alignment::AlignmentOperation::{Match, Subst, Ins, Del};
    /// let alignment = Alignment {
    ///     score: 5,
    ///     xstart: 3,
    ///     ystart: 0,
    ///     xend: 9,
    ///     yend: 10,
    ///     ylen: 10,
    ///     xlen: 10,
    ///     operations: vec![Match, Match, Match, Subst, Ins, Ins, Del, Del],
    ///     mode: AlignmentMode::Semiglobal
    /// };
    /// assert_eq!(alignment.cigar(false), "3S3=1X2I2D1S");
    /// ```
    pub fn cigar(&self, hard_clip: bool) -> String {

        match self.mode {
            AlignmentMode::Global => panic!(" Cigar fn not supported for Global Alignment mode"),
            AlignmentMode::Local => panic!(" Cigar fn not supported for Local Alignment mode"),
            _ => {}
        }

        let clip_str = if hard_clip { "H" } else { "S" };

        let add_op = |op: AlignmentOperation, k, cigar: &mut String| match op {
            AlignmentOperation::Match => cigar.push_str(&format!("{}{}", k, "=")),
            AlignmentOperation::Subst => cigar.push_str(&format!("{}{}", k, "X")),
            AlignmentOperation::Del => cigar.push_str(&format!("{}{}", k, "D")),
            AlignmentOperation::Ins => cigar.push_str(&format!("{}{}", k, "I")),
            _ => {}
        };

        let mut cigar = "".to_owned();
        if self.operations.is_empty() {
            return cigar;
        }

        let mut last = self.operations[0];
        if self.xstart > 0 {
            cigar.push_str(&format!("{}{}", self.xstart, clip_str))
        }
        let mut k = 1;
        for &op in self.operations[1..].iter() {
            if op == last {
                k += 1;
            } else {
                add_op(last, k, &mut cigar);
                k = 1;
            }
            last = op;
        }
        add_op(last, k, &mut cigar);
        if self.xlen > self.xend {
            cigar.push_str(&format!("{}{}", self.xlen - self.xend, clip_str))
        }

        cigar
    }

    /// Return the pretty formatted alignment as a String. The string
    /// contains sets of 3 lines of length 100. First line is for the
    /// sequence x, second line is for the alignment operation and the
    /// the third line is for the sequence y. A '-' in the sequence
    /// indicates a blank (insertion/deletion). The operations follow
    /// the following convention: '|' for a match, '\' for a mismatch,
    /// '+' for an insertion, 'x' for a deletion and ' ' for clipping
    ///
    /// # Example
    ///
    /// ```
    /// use bio::alignment::pairwise::*;
    ///
    /// let x = b"CCGTCCGGCAAGGG";
    /// let y = b"AAAAACCGTTGACGGCCAA";
    /// let score = |a: u8, b: u8| if a == b {1i32} else {-2i32};
    ///
    /// let mut aligner = Aligner::with_capacity(x.len(), y.len(), -5, -1, &score);
    /// let alignment = aligner.semiglobal(x, y);
    /// println!("Semiglobal: \n{}\n", alignment.pretty(x, y));
    /// // Semiglobal:
    /// //      CCGTCCGGCAAGGG
    /// //      ||||++++\\|\||
    /// // AAAAACCGT----TGACGGCCAA

    /// let alignment = aligner.local(x, y);
    /// println!("Local: \n{}\n", alignment.pretty(x, y));
    /// // Local:
    /// //      CCGTCCGGCAAGGG
    /// //      ||||
    /// // AAAAACCGT          TGACGGCCAA

    /// let alignment = aligner.global(x, y);
    /// println!("Global: \n{}\n", alignment.pretty(x, y));
    /// // Global:
    /// // -----CCGT--CCGGCAAGGG
    /// // xxxxx||||xx\||||\|++\
    /// // AAAAACCGTTGACGGCCA--A
    /// ```
    pub fn pretty(&self, x: TextSlice, y: TextSlice) -> String {
        let mut x_pretty = String::new();
        let mut y_pretty = String::new();
        let mut inb_pretty = String::new();

        if !self.operations.is_empty() {
            let mut x_i: usize;
            let mut y_i: usize;

            // If the alignment mode is one of the standard ones, the prefix clipping is
            // implicit so we need to process it here
            match self.mode {
                AlignmentMode::Custom => {
                    x_i = 0;
                    y_i = 0;
                }
                _ => {
                    x_i = self.xstart;
                    y_i = self.ystart;
                    for k in 0..x_i {
                        x_pretty.push_str(&format!("{}", String::from_utf8_lossy(&[x[k]])));
                        inb_pretty.push(' ');
                        y_pretty.push(' ')
                    }
                    for k in 0..y_i {
                        y_pretty.push_str(&format!("{}", String::from_utf8_lossy(&[y[k]])));
                        inb_pretty.push(' ');
                        x_pretty.push(' ')
                    }
                }
            }

            // Process the alignment.
            for i in 0..self.operations.len() {
                match self.operations[i] {
                    AlignmentOperation::Match => {
                        x_pretty.push_str(&format!("{}", String::from_utf8_lossy(&[x[x_i]])));
                        x_i += 1;

                        inb_pretty.push_str("|");

                        y_pretty.push_str(&format!("{}", String::from_utf8_lossy(&[y[y_i]])));
                        y_i += 1;
                    }
                    AlignmentOperation::Subst => {
                        x_pretty.push_str(&format!("{}", String::from_utf8_lossy(&[x[x_i]])));
                        x_i += 1;

                        inb_pretty.push('\\');

                        y_pretty.push_str(&format!("{}", String::from_utf8_lossy(&[y[y_i]])));
                        y_i += 1;
                    }
                    AlignmentOperation::Del => {
                        x_pretty.push('-');

                        inb_pretty.push('x');

                        y_pretty.push_str(&format!("{}", String::from_utf8_lossy(&[y[y_i]])));
                        y_i += 1;
                    }
                    AlignmentOperation::Ins => {
                        x_pretty.push_str(&format!("{}", String::from_utf8_lossy(&[x[x_i]])));
                        x_i += 1;

                        inb_pretty.push('+');

                        y_pretty.push('-');
                    }
                    AlignmentOperation::Xclip(len) => {
                        for k in 0..len {
                            x_pretty.push_str(&format!("{}", String::from_utf8_lossy(&[x[k]])));
                            x_i += 1;

                            inb_pretty.push(' ');

                            y_pretty.push(' ')
                        }
                    }
                    AlignmentOperation::Yclip(len) => {
                        for k in 0..len {
                            y_pretty.push_str(&format!("{}", String::from_utf8_lossy(&[y[k]])));
                            y_i += 1;

                            inb_pretty.push(' ');

                            x_pretty.push(' ')
                        }
                    }
                }
            }

            // If the alignment mode is one of the standard ones, the suffix clipping is
            // implicit so we need to process it here
            match self.mode {
                AlignmentMode::Custom => {}
                _ => {
                    for k in x_i..self.xlen {
                        x_pretty.push_str(&format!("{}", String::from_utf8_lossy(&[x[k]])));
                        inb_pretty.push(' ');
                        y_pretty.push(' ')
                    }
                    for k in y_i..self.ylen {
                        y_pretty.push_str(&format!("{}", String::from_utf8_lossy(&[y[k]])));
                        inb_pretty.push(' ');
                        x_pretty.push(' ')
                    }
                }
            }
        }

        let mut s = String::new();
        let mut idx = 0;
        let step = 100; // Number of characters per line
        use std::cmp::min;

        assert_eq!(x_pretty.len(), inb_pretty.len());
        assert_eq!(y_pretty.len(), inb_pretty.len());

        let ml = x_pretty.len();

        while idx < ml {
            let rng = idx..min(idx + step, ml);
            s.push_str(&x_pretty[rng.clone()]);
            s.push_str("\n");

            s.push_str(&inb_pretty[rng.clone()]);
            s.push_str("\n");

            s.push_str(&y_pretty[rng]);
            s.push_str("\n");

            s.push_str("\n\n");
            idx += step;
        }

        s
    }

    /// Returns the optimal path in the alignment matrix
    pub fn path(&self) -> Vec<(usize, usize, AlignmentOperation)> {
        let mut path = Vec::new();

        if !self.operations.is_empty() {
            let last = match self.mode {
                AlignmentMode::Custom => (self.xlen, self.ylen),
                _ => (self.xend, self.yend),
            };
            let mut x_i = last.0;
            let mut y_i = last.1;

            let mut ops = self.operations.clone();
            ops.reverse();

            // Process the alignment.
            for i in 0..ops.len() {
                path.push((x_i, y_i, ops[i]));
                match ops[i] {
                    AlignmentOperation::Match => {
                        x_i -= 1;
                        y_i -= 1;
                    }
                    AlignmentOperation::Subst => {
                        x_i -= 1;
                        y_i -= 1;
                    }
                    AlignmentOperation::Del => {
                        y_i -= 1;
                    }
                    AlignmentOperation::Ins => {
                        x_i -= 1;
                    }
                    AlignmentOperation::Xclip(len) => {
                        x_i -= len;
                    }
                    AlignmentOperation::Yclip(len) => {
                        y_i -= len;
                    }
                }
            }
        }
        path.reverse();
        path
    }

    /// Filter out Xclip and Yclip operations from the list of operations. Useful
    /// when invoking the standard modes.
    pub fn filter_clip_operations(&mut self) {
        use self::AlignmentOperation::{Match, Subst, Ins, Del};
        self.operations
            .retain(|&ref x| (*x == Match || *x == Subst || *x == Ins || *x == Del));
    }

    /// Number of bases in reference sequence that are aligned
    pub fn y_aln_len(&self) -> usize {
        self.yend - self.ystart
    }

    /// Number of bases in query sequence that are aigned
    pub fn x_aln_len(&self) -> usize {
        self.xend - self.xstart
    }
}

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

    #[test]
    fn test_cigar() {
        let alignment = Alignment {
            score: 5,
            xstart: 3,
            ystart: 0,
            xend: 9,
            yend: 10,
            ylen: 10,
            xlen: 10,
            operations: vec![Match, Match, Match, Subst, Ins, Ins, Del, Del],
            mode: AlignmentMode::Semiglobal,
        };
        assert_eq!(alignment.cigar(false), "3S3=1X2I2D1S");

        let alignment = Alignment {
            score: 5,
            xstart: 0,
            ystart: 5,
            xend: 4,
            yend: 10,
            ylen: 10,
            xlen: 5,
            operations: vec![Yclip(5), Match, Subst, Subst, Ins, Del, Del, Xclip(1)],
            mode: AlignmentMode::Custom,
        };
        assert_eq!(alignment.cigar(false), "1=2X1I2D1S");
        assert_eq!(alignment.cigar(true), "1=2X1I2D1H");

        let alignment = Alignment {
            score: 5,
            xstart: 0,
            ystart: 5,
            xend: 3,
            yend: 8,
            ylen: 10,
            xlen: 3,
            operations: vec![Yclip(5), Subst, Match, Subst, Yclip(2)],
            mode: AlignmentMode::Custom,
        };
        assert_eq!(alignment.cigar(false), "1X1=1X");

        let alignment = Alignment {
            score: 5,
            xstart: 0,
            ystart: 5,
            xend: 3,
            yend: 8,
            ylen: 10,
            xlen: 3,
            operations: vec![Subst, Match, Subst],
            mode: AlignmentMode::Semiglobal,
        };
        assert_eq!(alignment.cigar(false), "1X1=1X");
    }
}
<|endoftext|>