<commit_before>//! Algorithm module for executing Algorithmia algorithms
//!
//! # Examples
//!
//! ```no_run
//! use algorithmia::Algorithmia;
//! use algorithmia::algo::{Algorithm, AlgoOutput, Version};
//!
//! // Initialize with an API key
//! let client = Algorithmia::client("111112222233333444445555566");
//! let moving_avg = client.algo("timeseries", "SimpleMovingAverage", Version::Minor(0,1));
//!
//! // Run the algorithm using a type safe decoding of the output to Vec<int>
//! //   since this algorithm outputs results as a JSON array of integers
//! let input = (vec![0,1,2,3,15,4,5,6,7], 3);
//! let result: Vec<f64> = moving_avg.pipe(&input).unwrap().result().unwrap();
//! println!("Completed with result: {:?}", result);
//! ```

use client::{Body, HttpClient};
use error::{Error, ApiErrorResponse};

use rustc_serialize::{json, Decodable, Encodable};
use rustc_serialize::json::Json;
use rustc_serialize::base64::FromBase64;
use hyper::header::ContentType;
use hyper::mime::{Mime, TopLevel, SubLevel};
use hyper::{self, Url};
use hyper::client::response::Response;

use std::io::{self, Read, Write};
use std::str::FromStr;
use std::fmt;

static ALGORITHM_BASE_PATH: &'static str = "v1/algo";

enum AlgoInput<'a> {
    Text(&'a str),
    Binary(&'a [u8]),
    Json(String)
}

/// Algorithmia algorithm
pub struct Algorithm {
    pub path: String,
    client: HttpClient,
}

#[derive(RustcDecodable, Debug)]
pub struct AlgoMetadata {
    pub duration: f32,
    pub stdout: Option<String>,
    pub alerts: Option<Vec<String>>,
    pub content_type: String,
}

/// Generic struct for decoding an algorithm response JSON
#[derive(RustcDecodable, Debug)]
pub struct AlgoOutput<T> {
    pub metadata: AlgoMetadata,
    pub result: T,
}

pub struct AlgoResponse {
    pub metadata: AlgoMetadata,
    result: AlgoResult,
}

enum AlgoResult {
    Void,
    Text(String),
    Json(String),
    Binary(Vec<u8>),
}

impl Algorithm {
    pub fn new(client: HttpClient, algo_uri: &str) -> Algorithm {
        let path = match algo_uri {
            p if p.starts_with("algo://") => &p[7..],
            p if p.starts_with("/") => &p[1..],
            p => p,
        };

        Algorithm {
            client: client,
            path: path.to_string(),
        }
    }

    /// Get the API Endpoint URL for this Algorithm
    pub fn to_url(&self) -> Url {
        let url_string = format!("{}/{}/{}", self.client.base_url, ALGORITHM_BASE_PATH, self.path);
        Url::parse(&url_string).unwrap()
    }

    /// Get the Algorithmia algo URI for this Algorithm
    pub fn to_algo_uri(&self) -> String {
        format!("algo://{}", self.path)
    }

    /// Execute an algorithm with automatic JSON encoding of input and decoding of response
    ///
    /// input_data must be JSON-encodable
    ///     use `#[derive(RustcEncodable)]` for complex input
    ///
    /// You must explicitly specify the output type `T`
    ///     `pipe` will attempt to decode the response into AlgoOutput<T>
    ///
    /// If decoding fails, it will attempt to decode into `ApiError`
    ///     and if that fails, it will error with `DecoderErrorWithContext`
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use algorithmia::Algorithmia;
    /// # use algorithmia::algo::{Algorithm, AlgoOutput, Version};
    /// let client = Algorithmia::client("111112222233333444445555566");
    /// let moving_avg = client.algo("timeseries", "SimpleMovingAverage", Version::Minor(0,1));
    /// let input = (vec![0,1,2,3,15,4,5,6,7], 3);
    /// match moving_avg.pipe(&input) {
    ///     Ok(response) => println!("{}", response.result_json().unwrap()),
    ///     Err(err) => println!("ERROR: {}", err),
    /// };
    /// ```
    pub fn pipe<'a, I: Into<AlgoInput<'a>>>(&'a self, input_data: I) -> Result<AlgoResponse, Error> {
        let mut res = try!(match input_data.into() {
            AlgoInput::Text(text) => self.pipe_as(text, Mime(TopLevel::Text, SubLevel::Plain, vec![])),
            AlgoInput::Json(json) => self.pipe_as(&*json, Mime(TopLevel::Application, SubLevel::Json, vec![])),
            AlgoInput::Binary(bytes) => self.pipe_as(bytes, Mime(TopLevel::Application, SubLevel::Ext("octet-stream".into()), vec![])),
        });

        let mut res_json = String::new();
        try!(res.read_to_string(&mut res_json));
        res_json.parse()
    }

    /// Execute an algorithm with explicitly set content-type
    ///
    ///
    /// `pipe` provides a JSON encoding/decoding wrapper around this method
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use algorithmia::Algorithmia;
    /// # use algorithmia::algo::{Algorithm, Version};
    /// let client = Algorithmia::client("111112222233333444445555566");
    /// let minmax  = client.algo("codeb34v3r", "FindMinMax", Version::Minor(0,1));
    ///
    /// let output = match minmax.pipe_json("[2,3,4]") {
    ///    Ok(response) => response.result_json().unwrap().to_owned(),
    ///    Err(err) => panic!("{}", err),
    /// };
    pub fn pipe_json(&self, json_input: &str) -> Result<AlgoResponse, Error> {
        let mut res = try!(self.pipe_as(json_input, Mime(TopLevel::Application, SubLevel::Json, vec![])));

        let mut res_json = String::new();
        try!(res.read_to_string(&mut res_json));
        res_json.parse()
    }

    pub fn pipe_as<'a, B: Into<Body<'a>>>(&'a self, input_data: B, content_type: Mime) -> Result<Response, hyper::error::Error> {
        let req = self.client.post(self.to_url())
            .header(ContentType(content_type))
            .body(input_data);

        req.send()
    }
}

impl AlgoResponse {
    pub fn result_json(&self) -> Result<&str, Error> {
        match self.result {
            AlgoResult::Json(ref json) => Ok(&json),
            _ => Err(Error::ContentTypeError(self.metadata.content_type.clone())),
        }
    }

    pub fn result_bytes(&self) -> Result<&[u8], Error> {
        match self.result {
            AlgoResult::Binary(ref bytes) => Ok(&bytes),
            _ => Err(Error::ContentTypeError(self.metadata.content_type.clone())),
        }
    }

    pub fn result_str(&self) -> Result<&str, Error> {
        match self.result {
            AlgoResult::Text(ref text) => Ok(&text),
            _ => Err(Error::ContentTypeError(self.metadata.content_type.clone())),
        }
    }

    pub fn result<D: Decodable>(&self) -> Result<D, Error> {
        let res_json = try!(self.result_json());
        json::decode::<D>(&res_json).map_err(|err| err.into())
    }
}

impl FromStr for AlgoResponse {
    type Err = Error;
    fn from_str(json_str: &str) -> Result<Self, Self::Err> {
        // Early return if the response decodes into ApiErrorResponse
        if let Ok(err_res) = json::decode::<ApiErrorResponse>(json_str) {
            return Err(err_res.error.into())
        }

        // Parse into Json object
        let data = match Json::from_str(json_str) {
            Ok(d) => d,
            Err(err) => return Err(json::DecoderError::ParseError(err).into()),
        };

        // Construct the AlgoMetadata object
        let metadata = match data.search("metadata") {
            Some(meta_json) => match json::decode::<AlgoMetadata>(&meta_json.to_string()) {
                Ok(meta) => meta,
                Err(err) => return Err(err.into()),
            },
            None => return Err(json::DecoderError::MissingFieldError("metadata".into()).into()),
        };

        // Construct the AlgoResult object
        let result = match (&*metadata.content_type, data.search("result")) {
            ("void", _) => AlgoResult::Void,
            ("json", Some(json)) => AlgoResult::Json(json.to_string()),
            ("text", Some(json)) => match json.as_string() {
                Some(text) => AlgoResult::Text(text.into()),
                None => return Err(Error::ContentTypeError("invalid text".into())),
            },
            ("binary", Some(json)) => AlgoResult::Binary(try!(json.to_string().from_base64())),
            (_, None) => return Err(json::DecoderError::MissingFieldError("result".into()).into()),
            (content_type, _) => return Err(Error::ContentTypeError(content_type.into())),
        };

        // Construct the AlgoResponse object
        Ok(AlgoResponse{ metadata: metadata, result: result})
    }
}

impl fmt::Display for AlgoResponse {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self.result {
            AlgoResult::Void => Err(fmt::Error),
            AlgoResult::Text(ref s) | AlgoResult::Json(ref s) => f.write_str(s),
            AlgoResult::Binary(ref bytes) => f.write_str(&String::from_utf8_lossy(bytes)),
        }
    }
}

impl Read for AlgoResponse {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        let mut out = buf; // why do I need this binding?
        match self.result {
            AlgoResult::Void => Err(io::Error::new(io::ErrorKind::Other, "cannot read void content type")),
            AlgoResult::Text(ref s) | AlgoResult::Json(ref s) => out.write(s.as_bytes()),
            AlgoResult::Binary(ref bytes) => out.write(bytes),
        }
    }
}

impl <'a> From<&'a str> for AlgoInput<'a> {
    fn from(text: &'a str) -> Self {
        AlgoInput::Text(text)
    }
}

impl <'a> From<&'a [u8]> for AlgoInput<'a> {
    fn from(bytes: &'a [u8]) -> Self {
        AlgoInput::Binary(bytes)
    }
}

impl <'a, E: Encodable> From<&'a E> for AlgoInput<'a> {
    fn from(encodable: &'a E) -> Self {
        // TODO: remove unwrap - either find a way to Box the encodable object and let pipe() encode it
        //       or store a result and let pipe() do error handling
        AlgoInput::Json(json::encode(encodable).unwrap())
    }
}

#[cfg(test)]
mod tests {
    use Algorithmia;
    use algo::version::Version;
    use super::*;

    fn mock_client() -> Algorithmia { Algorithmia::client("") }

    #[test]
    fn test_algo_without_version_to_url() {
        let mock_client = mock_client();
        let algorithm = mock_client.algo_from_str("/anowell/Pinky");
        assert_eq!(algorithm.to_url().serialize_path().unwrap(), "/v1/algo/anowell/Pinky");
    }

    #[test]
    fn test_algo_without_prefix_to_url() {
        let mock_client = mock_client();
        let algorithm = mock_client.algo_from_str("anowell/Pinky/0.1.0");
        assert_eq!(algorithm.to_url().serialize_path().unwrap(), "/v1/algo/anowell/Pinky/0.1.0");
    }

    #[test]
    fn test_algo_with_prefix_to_url() {
        let mock_client = mock_client();
        let algorithm = mock_client.algo_from_str("algo://anowell/Pinky/0.1");
        assert_eq!(algorithm.to_url().serialize_path().unwrap(), "/v1/algo/anowell/Pinky/0.1");
    }

    #[test]
    fn test_algo_typesafe_to_url() {
        let mock_client = mock_client();
        let algorithm = mock_client.algo("anowell", "Pinky", Version::Hash("abcdef123456"));
        assert_eq!(algorithm.to_url().serialize_path().unwrap(), "/v1/algo/anowell/Pinky/abcdef123456");
    }

    #[test]
    fn test_json_decoding() {
        let json_output = r#"{"metadata":{"duration":0.46739511,"content_type":"json"},"result":[5,41]}"#;
        let expected = AlgoOutput{ metadata: AlgoMetadata { duration: 0.46739511f32, stdout: None, alerts: None, content_type: "json".into()} , result: [5, 41] };
        let decoded = json_output.parse::<AlgoResponse>().unwrap();
        assert_eq!(expected.metadata.duration, decoded.metadata.duration);
        assert_eq!(expected.result, &*decoded.result::<Vec<i32>>().unwrap());
    }
}
<commit_msg>Fix binary response parsing<commit_after>//! Algorithm module for executing Algorithmia algorithms
//!
//! # Examples
//!
//! ```no_run
//! use algorithmia::Algorithmia;
//! use algorithmia::algo::{Algorithm, AlgoOutput, Version};
//!
//! // Initialize with an API key
//! let client = Algorithmia::client("111112222233333444445555566");
//! let moving_avg = client.algo("timeseries", "SimpleMovingAverage", Version::Minor(0,1));
//!
//! // Run the algorithm using a type safe decoding of the output to Vec<int>
//! //   since this algorithm outputs results as a JSON array of integers
//! let input = (vec![0,1,2,3,15,4,5,6,7], 3);
//! let result: Vec<f64> = moving_avg.pipe(&input).unwrap().result().unwrap();
//! println!("Completed with result: {:?}", result);
//! ```

use client::{Body, HttpClient};
use error::{Error, ApiErrorResponse};

use rustc_serialize::{json, Decodable, Encodable};
use rustc_serialize::json::Json;
use rustc_serialize::base64::FromBase64;
use hyper::header::ContentType;
use hyper::mime::{Mime, TopLevel, SubLevel};
use hyper::{self, Url};
use hyper::client::response::Response;

use std::io::{self, Read, Write};
use std::str::FromStr;
use std::fmt;

static ALGORITHM_BASE_PATH: &'static str = "v1/algo";

enum AlgoInput<'a> {
    Text(&'a str),
    Binary(&'a [u8]),
    Json(String)
}

/// Algorithmia algorithm
pub struct Algorithm {
    pub path: String,
    client: HttpClient,
}

#[derive(RustcDecodable, Debug)]
pub struct AlgoMetadata {
    pub duration: f32,
    pub stdout: Option<String>,
    pub alerts: Option<Vec<String>>,
    pub content_type: String,
}

/// Generic struct for decoding an algorithm response JSON
#[derive(RustcDecodable, Debug)]
pub struct AlgoOutput<T> {
    pub metadata: AlgoMetadata,
    pub result: T,
}

pub struct AlgoResponse {
    pub metadata: AlgoMetadata,
    result: AlgoResult,
}

enum AlgoResult {
    Void,
    Text(String),
    Json(String),
    Binary(Vec<u8>),
}

impl Algorithm {
    pub fn new(client: HttpClient, algo_uri: &str) -> Algorithm {
        let path = match algo_uri {
            p if p.starts_with("algo://") => &p[7..],
            p if p.starts_with("/") => &p[1..],
            p => p,
        };

        Algorithm {
            client: client,
            path: path.to_string(),
        }
    }

    /// Get the API Endpoint URL for this Algorithm
    pub fn to_url(&self) -> Url {
        let url_string = format!("{}/{}/{}", self.client.base_url, ALGORITHM_BASE_PATH, self.path);
        Url::parse(&url_string).unwrap()
    }

    /// Get the Algorithmia algo URI for this Algorithm
    pub fn to_algo_uri(&self) -> String {
        format!("algo://{}", self.path)
    }

    /// Execute an algorithm with automatic JSON encoding of input and decoding of response
    ///
    /// input_data must be JSON-encodable
    ///     use `#[derive(RustcEncodable)]` for complex input
    ///
    /// You must explicitly specify the output type `T`
    ///     `pipe` will attempt to decode the response into AlgoOutput<T>
    ///
    /// If decoding fails, it will attempt to decode into `ApiError`
    ///     and if that fails, it will error with `DecoderErrorWithContext`
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use algorithmia::Algorithmia;
    /// # use algorithmia::algo::{Algorithm, AlgoOutput, Version};
    /// let client = Algorithmia::client("111112222233333444445555566");
    /// let moving_avg = client.algo("timeseries", "SimpleMovingAverage", Version::Minor(0,1));
    /// let input = (vec![0,1,2,3,15,4,5,6,7], 3);
    /// match moving_avg.pipe(&input) {
    ///     Ok(response) => println!("{}", response.result_json().unwrap()),
    ///     Err(err) => println!("ERROR: {}", err),
    /// };
    /// ```
    pub fn pipe<'a, I: Into<AlgoInput<'a>>>(&'a self, input_data: I) -> Result<AlgoResponse, Error> {
        let mut res = try!(match input_data.into() {
            AlgoInput::Text(text) => self.pipe_as(text, Mime(TopLevel::Text, SubLevel::Plain, vec![])),
            AlgoInput::Json(json) => self.pipe_as(&*json, Mime(TopLevel::Application, SubLevel::Json, vec![])),
            AlgoInput::Binary(bytes) => self.pipe_as(bytes, Mime(TopLevel::Application, SubLevel::Ext("octet-stream".into()), vec![])),
        });

        let mut res_json = String::new();
        try!(res.read_to_string(&mut res_json));
        res_json.parse()
    }

    /// Execute an algorithm with explicitly set content-type
    ///
    ///
    /// `pipe` provides a JSON encoding/decoding wrapper around this method
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use algorithmia::Algorithmia;
    /// # use algorithmia::algo::{Algorithm, Version};
    /// let client = Algorithmia::client("111112222233333444445555566");
    /// let minmax  = client.algo("codeb34v3r", "FindMinMax", Version::Minor(0,1));
    ///
    /// let output = match minmax.pipe_json("[2,3,4]") {
    ///    Ok(response) => response.result_json().unwrap().to_owned(),
    ///    Err(err) => panic!("{}", err),
    /// };
    pub fn pipe_json(&self, json_input: &str) -> Result<AlgoResponse, Error> {
        let mut res = try!(self.pipe_as(json_input, Mime(TopLevel::Application, SubLevel::Json, vec![])));

        let mut res_json = String::new();
        try!(res.read_to_string(&mut res_json));
        res_json.parse()
    }

    pub fn pipe_as<'a, B: Into<Body<'a>>>(&'a self, input_data: B, content_type: Mime) -> Result<Response, hyper::error::Error> {
        let req = self.client.post(self.to_url())
            .header(ContentType(content_type))
            .body(input_data);

        req.send()
    }
}

impl AlgoResponse {
    pub fn result_json(&self) -> Result<&str, Error> {
        match self.result {
            AlgoResult::Json(ref json) => Ok(&json),
            _ => Err(Error::ContentTypeError(self.metadata.content_type.clone())),
        }
    }

    pub fn result_bytes(&self) -> Result<&[u8], Error> {
        match self.result {
            AlgoResult::Binary(ref bytes) => Ok(&bytes),
            _ => Err(Error::ContentTypeError(self.metadata.content_type.clone())),
        }
    }

    pub fn result_str(&self) -> Result<&str, Error> {
        match self.result {
            AlgoResult::Text(ref text) => Ok(&text),
            _ => Err(Error::ContentTypeError(self.metadata.content_type.clone())),
        }
    }

    pub fn result<D: Decodable>(&self) -> Result<D, Error> {
        let res_json = try!(self.result_json());
        json::decode::<D>(&res_json).map_err(|err| err.into())
    }
}

impl FromStr for AlgoResponse {
    type Err = Error;
    fn from_str(json_str: &str) -> Result<Self, Self::Err> {
        // Early return if the response decodes into ApiErrorResponse
        if let Ok(err_res) = json::decode::<ApiErrorResponse>(json_str) {
            return Err(err_res.error.into())
        }

        // Parse into Json object
        let data = match Json::from_str(json_str) {
            Ok(d) => d,
            Err(err) => return Err(json::DecoderError::ParseError(err).into()),
        };

        // Construct the AlgoMetadata object
        let metadata = match data.search("metadata") {
            Some(meta_json) => match json::decode::<AlgoMetadata>(&meta_json.to_string()) {
                Ok(meta) => meta,
                Err(err) => return Err(err.into()),
            },
            None => return Err(json::DecoderError::MissingFieldError("metadata".into()).into()),
        };

        // Construct the AlgoResult object
        let result = match (&*metadata.content_type, data.search("result")) {
            ("void", _) => AlgoResult::Void,
            ("json", Some(json)) => AlgoResult::Json(json.to_string()),
            ("text", Some(json)) => match json.as_string() {
                Some(text) => AlgoResult::Text(text.into()),
                None => return Err(Error::ContentTypeError("invalid text".into())),
            },
            ("binary", Some(json)) => match json.as_string() {
                Some(text) => AlgoResult::Binary(try!(text.from_base64())),
                None => return Err(Error::ContentTypeError("invalid text".into())),
            },
            (_, None) => return Err(json::DecoderError::MissingFieldError("result".into()).into()),
            (content_type, _) => return Err(Error::ContentTypeError(content_type.into())),
        };

        // Construct the AlgoResponse object
        Ok(AlgoResponse{ metadata: metadata, result: result})
    }
}

impl fmt::Display for AlgoResponse {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self.result {
            AlgoResult::Void => Err(fmt::Error),
            AlgoResult::Text(ref s) | AlgoResult::Json(ref s) => f.write_str(s),
            AlgoResult::Binary(ref bytes) => f.write_str(&String::from_utf8_lossy(bytes)),
        }
    }
}

impl Read for AlgoResponse {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        let mut out = buf; // why do I need this binding?
        match self.result {
            AlgoResult::Void => Err(io::Error::new(io::ErrorKind::Other, "cannot read void content type")),
            AlgoResult::Text(ref s) | AlgoResult::Json(ref s) => out.write(s.as_bytes()),
            AlgoResult::Binary(ref bytes) => out.write(bytes),
        }
    }
}

impl <'a> From<&'a str> for AlgoInput<'a> {
    fn from(text: &'a str) -> Self {
        AlgoInput::Text(text)
    }
}

impl <'a> From<&'a [u8]> for AlgoInput<'a> {
    fn from(bytes: &'a [u8]) -> Self {
        AlgoInput::Binary(bytes)
    }
}

impl <'a, E: Encodable> From<&'a E> for AlgoInput<'a> {
    fn from(encodable: &'a E) -> Self {
        // TODO: remove unwrap - either find a way to Box the encodable object and let pipe() encode it
        //       or store a result and let pipe() do error handling
        AlgoInput::Json(json::encode(encodable).unwrap())
    }
}

#[cfg(test)]
mod tests {
    use Algorithmia;
    use algo::version::Version;
    use super::*;

    fn mock_client() -> Algorithmia { Algorithmia::client("") }

    #[test]
    fn test_algo_without_version_to_url() {
        let mock_client = mock_client();
        let algorithm = mock_client.algo_from_str("/anowell/Pinky");
        assert_eq!(algorithm.to_url().serialize_path().unwrap(), "/v1/algo/anowell/Pinky");
    }

    #[test]
    fn test_algo_without_prefix_to_url() {
        let mock_client = mock_client();
        let algorithm = mock_client.algo_from_str("anowell/Pinky/0.1.0");
        assert_eq!(algorithm.to_url().serialize_path().unwrap(), "/v1/algo/anowell/Pinky/0.1.0");
    }

    #[test]
    fn test_algo_with_prefix_to_url() {
        let mock_client = mock_client();
        let algorithm = mock_client.algo_from_str("algo://anowell/Pinky/0.1");
        assert_eq!(algorithm.to_url().serialize_path().unwrap(), "/v1/algo/anowell/Pinky/0.1");
    }

    #[test]
    fn test_algo_typesafe_to_url() {
        let mock_client = mock_client();
        let algorithm = mock_client.algo("anowell", "Pinky", Version::Hash("abcdef123456"));
        assert_eq!(algorithm.to_url().serialize_path().unwrap(), "/v1/algo/anowell/Pinky/abcdef123456");
    }

    #[test]
    fn test_json_decoding() {
        let json_output = r#"{"metadata":{"duration":0.46739511,"content_type":"json"},"result":[5,41]}"#;
        let expected = AlgoOutput{ metadata: AlgoMetadata { duration: 0.46739511f32, stdout: None, alerts: None, content_type: "json".into()} , result: [5, 41] };
        let decoded = json_output.parse::<AlgoResponse>().unwrap();
        assert_eq!(expected.metadata.duration, decoded.metadata.duration);
        assert_eq!(expected.result, &*decoded.result::<Vec<i32>>().unwrap());
    }
}
<|endoftext|>


<commit_before>use sass::output_style::SassOutputStyle;
use token::{Lexeme, Token};
use ast::number_value::NumberValue;
use context::Context;
use error::{Result, SassError, ErrorKind};

#[derive(Debug, Clone, PartialEq)]
pub enum Expression {
    List(Vec<Expression>),
    Number(NumberValue),
    Operator(Lexeme),
    String(Lexeme),
}

impl From<Lexeme> for Expression {
    fn from(lexeme: Lexeme) -> Expression {
        match lexeme.token {
            Token::Number(..) =>
                Expression::Number(
                    NumberValue::from_scalar(lexeme)
                ),
            Token::Plus | Token::Minus | Token::Star |
                          Token::Slash | Token::Percent =>
                Expression::Operator(lexeme),
            _ => Expression::String(lexeme),
        }
    }
}

impl Expression {
    #[allow(unused_variables)]
    pub fn to_string(&self, style: SassOutputStyle) -> String {
        match *self {
            Expression::List(ref elements) => {
                elements.iter().map(|e|
                    e.to_string(style)
                ).collect::<Vec<_>>().join(" ")
            },
            Expression::Number(ref nv) => nv.to_string(),
            Expression::Operator(ref lex) => lex.token.to_string(),
            Expression::String(ref lex) => lex.token.to_string(),
        }
    }

    fn parse_parenthetical<T: Iterator<Item = Result<Lexeme>>>(tokenizer: &mut T) -> Result<Expression> {
        let mut list = vec![];
        while let Some(Ok(lexeme)) = tokenizer.next() {
            match lexeme.token {
                Token::RightParen => {
                    return Ok(Expression::List(list))
                },
                Token::LeftParen => {
                    list.push(
                        try!(Expression::parse_parenthetical(tokenizer))
                    );
                },
                Token::Number(_, _) => {
                    list.push(
                        Expression::Number(NumberValue::from_scalar(lexeme))
                    );
                },
                Token::Plus | Token::Minus | Token::Star |
                              Token::Slash | Token::Percent => {
                    list.push(Expression::Operator(lexeme));
                },
                Token::Comment(_) => {},
                _ => {
                    list.push(Expression::String(lexeme));
                }
            }
        }

        let error_offset = match list.pop() {
            Some(Expression::String(lexeme)) => lexeme.offset.unwrap_or(0),
            Some(Expression::Operator(lexeme)) => lexeme.offset.unwrap_or(0),
            Some(Expression::Number(nv)) => nv.offset.unwrap_or(0),
            Some(Expression::List(_)) => unreachable!(), // for now until nested lists
            None => 0,
        };
        Err(SassError {
            offset: error_offset,
            kind: ErrorKind::UnexpectedEof,
            message: String::from(
                "Expected right paren while parsing a value expression; reached EOF instead."
            ),
        })
    }

    pub fn parse<T: Iterator<Item = Result<Lexeme>>>(tokenizer: &mut T) -> Result<Expression> {
        let mut list = vec![];
        while let Some(Ok(lexeme)) = tokenizer.next() {
            match lexeme.token {
                Token::Semicolon => {
                    if list.len() == 1 {
                        return Ok(list.pop().unwrap())
                    } else {
                        return Ok(Expression::List(list))
                    }
                },
                Token::LeftParen => {
                    list.push(
                        try!(Expression::parse_parenthetical(tokenizer))
                    );
                },
                Token::Comment(_) => {},
                _ => list.push(lexeme.into()),
            }
        }

        let error_offset = match list.pop() {
            Some(Expression::String(lexeme)) => lexeme.offset.unwrap_or(0),
            Some(Expression::Operator(lexeme)) => lexeme.offset.unwrap_or(0),
            Some(Expression::Number(nv)) => nv.offset.unwrap_or(0),
            Some(Expression::List(_)) => unreachable!(), // for now until nested lists
            None => 0,
        };
        Err(SassError {
            offset: error_offset,
            kind: ErrorKind::UnexpectedEof,
            message: String::from(
                "Expected semicolon while parsing a value expression; reached EOF instead."
            ),
        })
    }

    fn evaluate_list(exprs: Vec<Expression>, context: &Context, force_slash: bool) -> Expression {

        let mut last_was_an_operator = true;
        // TODO: I'd love to have more types here, to ensure only values are
        // on the value stack and only operators are on the operator stack...
        let mut value_stack: Vec<Expression> = Vec::new();
        let mut op_stack: Vec<Expression> = Vec::new();

        // Split into value stacks and operator stacks

        let mut exprs = exprs.into_iter();
        while let Some(part) = exprs.next() {
            debug!("Processing list item {:#?}", part);
            match part {
                Expression::Number(nv) => {
                    if last_was_an_operator {
                        debug!("Push on value stack {:#?}", nv);
                        value_stack.push(Expression::Number(nv));
                    } else {
                        let list = create_list(
                            value_stack.pop(),
                            Expression::Number(nv),
                        );
                        debug!("Push on list on value stack {:#?}", list);
                        value_stack.push(list);
                    }
                    last_was_an_operator = false;
                },
                Expression::Operator(Lexeme {
                    token: Token::Slash, offset: o }) if !force_slash => {
                    let list = create_list(
                        value_stack.pop(),
                        Expression::Operator(Lexeme {
                            token: Token::Slash, offset: o
                        }),
                    );
                    debug!("Push on list on value stack {:#?}", list);
                    value_stack.push(list);
                },
                Expression::Operator(op) => {
                    debug!("Push on op stack {:#?}", op);
                    op_stack.push(Expression::Operator(op));
                    last_was_an_operator = true;
                },
                Expression::String(lex) => {
                    let var_eval = context.get_variable(&lex)
                                    .unwrap_or(Expression::String(lex));

                    if last_was_an_operator {
                        value_stack.push(var_eval);
                    } else {
                        let list = create_list(
                            value_stack.pop(),
                            var_eval,
                        );
                        value_stack.push(list);
                    }
                    last_was_an_operator = false;
                },
                Expression::List(list) => {
                    debug!("Push list on value stack {:#?}", list);
                    value_stack.push(Expression::List(list));
                    last_was_an_operator = false;
                },
            }
        }

        // Process the stacks
        while !op_stack.is_empty() {
            let op = op_stack.pop().unwrap();
            let second = value_stack.pop().expect("Expected a second argument on the value stack");
            let first = value_stack.pop().expect("Expected a first argument on the value stack");

            let math_result = first.apply_math(op, second, context, force_slash);
            debug!("Math result: {:#?}", math_result);

            value_stack.push(math_result);
        }

        value_stack.pop().unwrap()
    }

    pub fn evaluate(self, context: &Context) -> Expression {
        match self {
            Expression::Number(nv) => Expression::Number(nv),
            Expression::Operator(op) => Expression::Operator(op),
            Expression::String(lex) => {
                context.get_variable(&lex).unwrap_or(Expression::String(lex))
            },
            Expression::List(exprs) => {
                Expression::evaluate_list(exprs, context, false)
            },
        }
    }

    fn is_slash(&self) -> bool {
        match self {
            &Expression::Operator(ref o) if o.token == Token::Slash => true,
            _ => false,
        }
    }

    fn is_plus(&self) -> bool {
        match self {
            &Expression::Operator(ref o) if o.token == Token::Plus => true,
            _ => false,
        }
    }

    fn apply_math(self, operator: Expression, second: Expression, context: &Context, force_slash: bool) -> Expression {
        debug!("Applying math to:\nfirst: {:#?}\nop: {:#?}\nsecond: {:#?}", self, operator, second);
        match (self.clone(), second.clone()) {
            (Expression::Number(f), Expression::Number(s)) => {
                match operator {
                    Expression::Operator(Lexeme {
                        token: Token::Slash, offset: o }) => {
                        if force_slash || (f.computed || s.computed) {
                            let result = f.apply_math(
                                &Lexeme {
                                    token: Token::Slash,
                                    offset: o
                                },
                                &s
                            );
                            Expression::Number(result)
                        } else {
                            Expression::List(vec![self, operator.clone(), second.clone()])
                        }
                    },
                    Expression::Operator(o) => {
                        let result = f.apply_math(&o, &s);
                        Expression::Number(result)
                    },
                    _ => unreachable!(),
                }
            },
            (Expression::List(f), Expression::List(s)) => {
                let eval_first = Expression::evaluate_list(f, context, true);
                let eval_second = Expression::evaluate_list(s, context, true);
                eval_first.apply_math(operator, eval_second, context, true)
            },
            (Expression::List(f), Expression::Number(s)) => {
                let eval_first = Expression::evaluate_list(f, context, true);
                eval_first.apply_math(operator, Expression::Number(s), context, true)
            },
            (Expression::Number(f), Expression::List(s)) => {
                if s.iter().any(|e| e.is_slash()) {
                    let evaled_list = Expression::evaluate_list(s, context, true);
                    Expression::Number(f).apply_math(
                        operator,
                        evaled_list,
                        context,
                        true
                    )
                } else {
                    if let Some((ref first_in_list, rest_of_list)) =
                            s.split_first() {
                        let new_first = if operator.is_plus() {
                            Expression::String(
                                Lexeme {
                                    token: Token::String(
                                        format!("{}{}", f, first_in_list.to_string(SassOutputStyle::Expanded))
                                    ),
                                    offset: f.offset,
                                }
                            )
                        } else {
                            Expression::Number(f).apply_math(
                                operator,
                                (*first_in_list).clone(),
                                context,
                                true
                            )
                        };

                        let mut new_list = vec![new_first];
                        new_list.extend_from_slice(rest_of_list);
                        Expression::List(new_list)
                    } else {
                        panic!("Trying to perform an operation on a number and a list; expected to get a list with something in it");
                    }
                }
            },
            _ => unimplemented!(),
        }
    }
}

fn create_list(head: Option<Expression>, tail: Expression) -> Expression {
    let mut list = match head {
        Some(Expression::List(v)) => v,
        Some(e) => vec![e],
        None => vec![],
    };
    list.push(tail);
    Expression::List(list)
}

#[cfg(test)]
mod tests {
    use super::*;
    use token::{Lexeme, Token};
    use ast::number_value::NumberValue;
    use context::Context;

    fn semicolon() -> Lexeme {
        Lexeme { token: Token::Semicolon, offset: None }
    }

    fn blue() -> Lexeme {
        Lexeme { token: Token::String("blue".into()), offset: None }
    }

    fn plus() -> Lexeme {
        Lexeme { token: Token::Plus, offset: None }
    }

    fn slash() -> Lexeme {
        Lexeme { token: Token::Slash, offset: None }
    }

    fn one_lexeme() -> Lexeme {
        Lexeme { token: Token::Number(1.0, None), offset: None }
    }

    fn one() -> NumberValue {
        NumberValue::from_scalar(one_lexeme())
    }

    fn two_lexeme() -> Lexeme {
        Lexeme { token: Token::Number(2.0, None), offset: None }
    }

    fn two() -> NumberValue {
        NumberValue::from_scalar(two_lexeme())
    }

    fn one_px_lexeme() -> Lexeme {
        Lexeme { token: Token::Number(1.0, Some("px".into())), offset: None }
    }

    fn one_px() -> NumberValue {
        NumberValue::from_scalar(one_px_lexeme())
    }

    #[test]
    fn it_parses_a_single_string() {
        let mut fake_tokenizer = vec![Ok(blue()), Ok(semicolon())].into_iter();
        assert_eq!(
            Expression::parse(&mut fake_tokenizer),
            Ok(Expression::String(blue()))
        );
    }

    #[test]
    fn it_parses_a_list() {
        let mut fake_tokenizer = vec![
            Ok(one_lexeme()),
            Ok(plus()),
            Ok(one_lexeme()),
            Ok(semicolon())
        ].into_iter();
        assert_eq!(
            Expression::parse(&mut fake_tokenizer),
            Ok(Expression::List(vec![
                Expression::Number(one()),
                Expression::Operator(plus()),
                Expression::Number(one()),
            ]))
        );
    }

    #[test]
    fn it_parses_a_number_without_units() {
        let mut fake_tokenizer = vec![
            Ok(one_lexeme()),
            Ok(semicolon())
        ].into_iter();
        assert_eq!(
            Expression::parse(&mut fake_tokenizer),
            Ok(Expression::Number(one()))
        );
    }

    #[test]
    fn it_parses_a_number_with_units() {
        let mut fake_tokenizer = vec![
            Ok(one_px_lexeme()),
            Ok(semicolon())
        ].into_iter();
        assert_eq!(
            Expression::parse(&mut fake_tokenizer),
            Ok(Expression::Number(one_px()))
        );
    }

    #[test]
    fn it_evaluates_a_list() {
        let ex = Expression::List(vec![
            Expression::Number(one()),
            Expression::Operator(slash()),
            Expression::Number(two()),
            Expression::Operator(plus()),
            Expression::Number(one()),
            Expression::Operator(slash()),
            Expression::Number(two()),
        ]);
        let fake_context = Context::new();
        assert_eq!(
            ex.evaluate(&fake_context),
            Expression::Number(NumberValue {
                scalar: 1.0,
                computed: true,
                units: None,
                offset: None,
            })
        );
    }
}
<commit_msg>Parse parenthetical can use Expression From Lexeme too<commit_after>use sass::output_style::SassOutputStyle;
use token::{Lexeme, Token};
use ast::number_value::NumberValue;
use context::Context;
use error::{Result, SassError, ErrorKind};

#[derive(Debug, Clone, PartialEq)]
pub enum Expression {
    List(Vec<Expression>),
    Number(NumberValue),
    Operator(Lexeme),
    String(Lexeme),
}

impl From<Lexeme> for Expression {
    fn from(lexeme: Lexeme) -> Expression {
        match lexeme.token {
            Token::Number(..) =>
                Expression::Number(
                    NumberValue::from_scalar(lexeme)
                ),
            Token::Plus | Token::Minus | Token::Star |
                          Token::Slash | Token::Percent =>
                Expression::Operator(lexeme),
            _ => Expression::String(lexeme),
        }
    }
}

impl Expression {
    #[allow(unused_variables)]
    pub fn to_string(&self, style: SassOutputStyle) -> String {
        match *self {
            Expression::List(ref elements) => {
                elements.iter().map(|e|
                    e.to_string(style)
                ).collect::<Vec<_>>().join(" ")
            },
            Expression::Number(ref nv) => nv.to_string(),
            Expression::Operator(ref lex) => lex.token.to_string(),
            Expression::String(ref lex) => lex.token.to_string(),
        }
    }

    fn parse_parenthetical<T: Iterator<Item = Result<Lexeme>>>(tokenizer: &mut T) -> Result<Expression> {
        let mut list = vec![];
        while let Some(Ok(lexeme)) = tokenizer.next() {
            match lexeme.token {
                Token::RightParen => {
                    return Ok(Expression::List(list))
                },
                Token::LeftParen => {
                    list.push(
                        try!(Expression::parse_parenthetical(tokenizer))
                    );
                },
                Token::Comment(_) => {},
                _ => list.push(lexeme.into()),
            }
        }

        let error_offset = match list.pop() {
            Some(Expression::String(lexeme)) => lexeme.offset.unwrap_or(0),
            Some(Expression::Operator(lexeme)) => lexeme.offset.unwrap_or(0),
            Some(Expression::Number(nv)) => nv.offset.unwrap_or(0),
            Some(Expression::List(_)) => unreachable!(), // for now until nested lists
            None => 0,
        };
        Err(SassError {
            offset: error_offset,
            kind: ErrorKind::UnexpectedEof,
            message: String::from(
                "Expected right paren while parsing a value expression; reached EOF instead."
            ),
        })
    }

    pub fn parse<T: Iterator<Item = Result<Lexeme>>>(tokenizer: &mut T) -> Result<Expression> {
        let mut list = vec![];
        while let Some(Ok(lexeme)) = tokenizer.next() {
            match lexeme.token {
                Token::Semicolon => {
                    if list.len() == 1 {
                        return Ok(list.pop().unwrap())
                    } else {
                        return Ok(Expression::List(list))
                    }
                },
                Token::LeftParen => {
                    list.push(
                        try!(Expression::parse_parenthetical(tokenizer))
                    );
                },
                Token::Comment(_) => {},
                _ => list.push(lexeme.into()),
            }
        }

        let error_offset = match list.pop() {
            Some(Expression::String(lexeme)) => lexeme.offset.unwrap_or(0),
            Some(Expression::Operator(lexeme)) => lexeme.offset.unwrap_or(0),
            Some(Expression::Number(nv)) => nv.offset.unwrap_or(0),
            Some(Expression::List(_)) => unreachable!(), // for now until nested lists
            None => 0,
        };
        Err(SassError {
            offset: error_offset,
            kind: ErrorKind::UnexpectedEof,
            message: String::from(
                "Expected semicolon while parsing a value expression; reached EOF instead."
            ),
        })
    }

    fn evaluate_list(exprs: Vec<Expression>, context: &Context, force_slash: bool) -> Expression {

        let mut last_was_an_operator = true;
        // TODO: I'd love to have more types here, to ensure only values are
        // on the value stack and only operators are on the operator stack...
        let mut value_stack: Vec<Expression> = Vec::new();
        let mut op_stack: Vec<Expression> = Vec::new();

        // Split into value stacks and operator stacks

        let mut exprs = exprs.into_iter();
        while let Some(part) = exprs.next() {
            debug!("Processing list item {:#?}", part);
            match part {
                Expression::Number(nv) => {
                    if last_was_an_operator {
                        debug!("Push on value stack {:#?}", nv);
                        value_stack.push(Expression::Number(nv));
                    } else {
                        let list = create_list(
                            value_stack.pop(),
                            Expression::Number(nv),
                        );
                        debug!("Push on list on value stack {:#?}", list);
                        value_stack.push(list);
                    }
                    last_was_an_operator = false;
                },
                Expression::Operator(Lexeme {
                    token: Token::Slash, offset: o }) if !force_slash => {
                    let list = create_list(
                        value_stack.pop(),
                        Expression::Operator(Lexeme {
                            token: Token::Slash, offset: o
                        }),
                    );
                    debug!("Push on list on value stack {:#?}", list);
                    value_stack.push(list);
                },
                Expression::Operator(op) => {
                    debug!("Push on op stack {:#?}", op);
                    op_stack.push(Expression::Operator(op));
                    last_was_an_operator = true;
                },
                Expression::String(lex) => {
                    let var_eval = context.get_variable(&lex)
                                    .unwrap_or(Expression::String(lex));

                    if last_was_an_operator {
                        value_stack.push(var_eval);
                    } else {
                        let list = create_list(
                            value_stack.pop(),
                            var_eval,
                        );
                        value_stack.push(list);
                    }
                    last_was_an_operator = false;
                },
                Expression::List(list) => {
                    debug!("Push list on value stack {:#?}", list);
                    value_stack.push(Expression::List(list));
                    last_was_an_operator = false;
                },
            }
        }

        // Process the stacks
        while !op_stack.is_empty() {
            let op = op_stack.pop().unwrap();
            let second = value_stack.pop().expect("Expected a second argument on the value stack");
            let first = value_stack.pop().expect("Expected a first argument on the value stack");

            let math_result = first.apply_math(op, second, context, force_slash);
            debug!("Math result: {:#?}", math_result);

            value_stack.push(math_result);
        }

        value_stack.pop().unwrap()
    }

    pub fn evaluate(self, context: &Context) -> Expression {
        match self {
            Expression::Number(nv) => Expression::Number(nv),
            Expression::Operator(op) => Expression::Operator(op),
            Expression::String(lex) => {
                context.get_variable(&lex).unwrap_or(Expression::String(lex))
            },
            Expression::List(exprs) => {
                Expression::evaluate_list(exprs, context, false)
            },
        }
    }

    fn is_slash(&self) -> bool {
        match self {
            &Expression::Operator(ref o) if o.token == Token::Slash => true,
            _ => false,
        }
    }

    fn is_plus(&self) -> bool {
        match self {
            &Expression::Operator(ref o) if o.token == Token::Plus => true,
            _ => false,
        }
    }

    fn apply_math(self, operator: Expression, second: Expression, context: &Context, force_slash: bool) -> Expression {
        debug!("Applying math to:\nfirst: {:#?}\nop: {:#?}\nsecond: {:#?}", self, operator, second);
        match (self.clone(), second.clone()) {
            (Expression::Number(f), Expression::Number(s)) => {
                match operator {
                    Expression::Operator(Lexeme {
                        token: Token::Slash, offset: o }) => {
                        if force_slash || (f.computed || s.computed) {
                            let result = f.apply_math(
                                &Lexeme {
                                    token: Token::Slash,
                                    offset: o
                                },
                                &s
                            );
                            Expression::Number(result)
                        } else {
                            Expression::List(vec![self, operator.clone(), second.clone()])
                        }
                    },
                    Expression::Operator(o) => {
                        let result = f.apply_math(&o, &s);
                        Expression::Number(result)
                    },
                    _ => unreachable!(),
                }
            },
            (Expression::List(f), Expression::List(s)) => {
                let eval_first = Expression::evaluate_list(f, context, true);
                let eval_second = Expression::evaluate_list(s, context, true);
                eval_first.apply_math(operator, eval_second, context, true)
            },
            (Expression::List(f), Expression::Number(s)) => {
                let eval_first = Expression::evaluate_list(f, context, true);
                eval_first.apply_math(operator, Expression::Number(s), context, true)
            },
            (Expression::Number(f), Expression::List(s)) => {
                if s.iter().any(|e| e.is_slash()) {
                    let evaled_list = Expression::evaluate_list(s, context, true);
                    Expression::Number(f).apply_math(
                        operator,
                        evaled_list,
                        context,
                        true
                    )
                } else {
                    if let Some((ref first_in_list, rest_of_list)) =
                            s.split_first() {
                        let new_first = if operator.is_plus() {
                            Expression::String(
                                Lexeme {
                                    token: Token::String(
                                        format!("{}{}", f, first_in_list.to_string(SassOutputStyle::Expanded))
                                    ),
                                    offset: f.offset,
                                }
                            )
                        } else {
                            Expression::Number(f).apply_math(
                                operator,
                                (*first_in_list).clone(),
                                context,
                                true
                            )
                        };

                        let mut new_list = vec![new_first];
                        new_list.extend_from_slice(rest_of_list);
                        Expression::List(new_list)
                    } else {
                        panic!("Trying to perform an operation on a number and a list; expected to get a list with something in it");
                    }
                }
            },
            _ => unimplemented!(),
        }
    }
}

fn create_list(head: Option<Expression>, tail: Expression) -> Expression {
    let mut list = match head {
        Some(Expression::List(v)) => v,
        Some(e) => vec![e],
        None => vec![],
    };
    list.push(tail);
    Expression::List(list)
}

#[cfg(test)]
mod tests {
    use super::*;
    use token::{Lexeme, Token};
    use ast::number_value::NumberValue;
    use context::Context;

    fn semicolon() -> Lexeme {
        Lexeme { token: Token::Semicolon, offset: None }
    }

    fn blue() -> Lexeme {
        Lexeme { token: Token::String("blue".into()), offset: None }
    }

    fn plus() -> Lexeme {
        Lexeme { token: Token::Plus, offset: None }
    }

    fn slash() -> Lexeme {
        Lexeme { token: Token::Slash, offset: None }
    }

    fn one_lexeme() -> Lexeme {
        Lexeme { token: Token::Number(1.0, None), offset: None }
    }

    fn one() -> NumberValue {
        NumberValue::from_scalar(one_lexeme())
    }

    fn two_lexeme() -> Lexeme {
        Lexeme { token: Token::Number(2.0, None), offset: None }
    }

    fn two() -> NumberValue {
        NumberValue::from_scalar(two_lexeme())
    }

    fn one_px_lexeme() -> Lexeme {
        Lexeme { token: Token::Number(1.0, Some("px".into())), offset: None }
    }

    fn one_px() -> NumberValue {
        NumberValue::from_scalar(one_px_lexeme())
    }

    #[test]
    fn it_parses_a_single_string() {
        let mut fake_tokenizer = vec![Ok(blue()), Ok(semicolon())].into_iter();
        assert_eq!(
            Expression::parse(&mut fake_tokenizer),
            Ok(Expression::String(blue()))
        );
    }

    #[test]
    fn it_parses_a_list() {
        let mut fake_tokenizer = vec![
            Ok(one_lexeme()),
            Ok(plus()),
            Ok(one_lexeme()),
            Ok(semicolon())
        ].into_iter();
        assert_eq!(
            Expression::parse(&mut fake_tokenizer),
            Ok(Expression::List(vec![
                Expression::Number(one()),
                Expression::Operator(plus()),
                Expression::Number(one()),
            ]))
        );
    }

    #[test]
    fn it_parses_a_number_without_units() {
        let mut fake_tokenizer = vec![
            Ok(one_lexeme()),
            Ok(semicolon())
        ].into_iter();
        assert_eq!(
            Expression::parse(&mut fake_tokenizer),
            Ok(Expression::Number(one()))
        );
    }

    #[test]
    fn it_parses_a_number_with_units() {
        let mut fake_tokenizer = vec![
            Ok(one_px_lexeme()),
            Ok(semicolon())
        ].into_iter();
        assert_eq!(
            Expression::parse(&mut fake_tokenizer),
            Ok(Expression::Number(one_px()))
        );
    }

    #[test]
    fn it_evaluates_a_list() {
        let ex = Expression::List(vec![
            Expression::Number(one()),
            Expression::Operator(slash()),
            Expression::Number(two()),
            Expression::Operator(plus()),
            Expression::Number(one()),
            Expression::Operator(slash()),
            Expression::Number(two()),
        ]);
        let fake_context = Context::new();
        assert_eq!(
            ex.evaluate(&fake_context),
            Expression::Number(NumberValue {
                scalar: 1.0,
                computed: true,
                units: None,
                offset: None,
            })
        );
    }
}
<|endoftext|>


<commit_before>// Copyright © 2016, Canal TP and/or its affiliates. All rights reserved.
//
// This file is part of Navitia,
//     the software to build cool stuff with public transport.
//
// Hope you'll enjoy and contribute to this project,
//     powered by Canal TP (www.canaltp.fr).
// Help us simplify mobility and open public transport:
//     a non ending quest to the responsive locomotion way of traveling!
//
// LICENCE: This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU Affero General Public
// License as published by the Free Software Foundation, either
// version 3 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public
// License along with this program. If not, see
// <http://www.gnu.org/licenses/>.
//
// Stay tuned using
// twitter @navitia
// IRC #navitia on freenode
// https://groups.google.com/d/forum/navitia
// www.navitia.io

use mimirsbrunn::stops::*;
use slog_scope::{info, warn};
use std::cmp::Ordering;
use std::collections::{hash_map::DefaultHasher, HashMap};
use std::hash::BuildHasherDefault;
use std::path::PathBuf;
use structopt::StructOpt;
use transit_model::objects as navitia;
use typed_index_collection::Idx;

#[derive(Debug, StructOpt)]
struct Args {
    /// NTFS directory.
    #[structopt(short = "i", long = "input", parse(from_os_str), default_value = ".")]
    input: PathBuf,
    /// Name of the dataset.
    #[structopt(short = "d", long = "dataset", default_value = "fr")]
    dataset: String,
    /// Elasticsearch parameters.
    #[structopt(
        short = "c",
        long = "connection-string",
        default_value = "http://localhost:9200/munin"
    )]
    connection_string: String,
    /// Deprecated option.
    #[structopt(short = "C", long = "city-level")]
    city_level: Option<String>,
    /// Number of shards for the es index
    #[structopt(short = "s", long = "nb-shards", default_value = "1")]
    nb_shards: usize,
    /// Number of replicas for the es index
    #[structopt(short = "r", long = "nb-replicas", default_value = "1")]
    nb_replicas: usize,
}

fn get_lines(
    idx: Idx<navitia::StopArea>,
    navitia: &transit_model::Model,
) -> Vec<places::stop::Line> {
    use places::stop::FromTransitModel;
    let mut lines: Vec<_> = navitia
        .get_corresponding_from_idx(idx)
        .into_iter()
        .map(|l_idx| places::stop::Line::from_transit_model(l_idx, navitia))
        .collect();

    // we want the lines to be sorted in a way where
    // line-3 is before line-11, so be use a human_sort
    lines.sort_by(|lhs, rhs| {
        match (&lhs.sort_order, &rhs.sort_order) {
            (None, Some(_)) => Ordering::Greater,
            (Some(_), None) => Ordering::Less,
            (Some(s), Some(o)) => s.cmp(o),
            (None, None) => Ordering::Equal,
        }
        .then_with(|| match (&lhs.code, &rhs.code) {
            (Some(l), Some(r)) => human_sort::compare(l, r),
            _ => Ordering::Equal,
        })
        .then_with(|| human_sort::compare(&lhs.name, &rhs.name))
    });
    lines
}

fn to_mimir(
    idx: Idx<navitia::StopArea>,
    stop_area: &navitia::StopArea,
    navitia: &transit_model::Model,
) -> places::stop::Stop {
    let commercial_modes = navitia
        .get_corresponding_from_idx(idx)
        .into_iter()
        .map(|cm_idx| places::stop::CommercialMode {
            id: places::stop::normalize_id("commercial_mode", &navitia.commercial_modes[cm_idx].id),
            name: navitia.commercial_modes[cm_idx].name.clone(),
        })
        .collect();
    let physical_modes = navitia
        .get_corresponding_from_idx(idx)
        .into_iter()
        .map(|pm_idx| places::stop::PhysicalMode {
            id: places::stop::normalize_id("physical_mode", &navitia.physical_modes[pm_idx].id),
            name: navitia.physical_modes[pm_idx].name.clone(),
        })
        .collect();
    let comments = stop_area
        .comment_links
        .iter()
        .filter_map(|comment_id| {
            let res = navitia.comments.get(comment_id);
            if res.is_none() {
                warn!("Could not retrieve comments for id {}", comment_id);
            }
            res
        })
        .map(|comment| places::stop::Comment {
            name: comment.name.clone(),
        })
        .collect();
    let feed_publishers = navitia
        .get_corresponding_from_idx(idx)
        .into_iter()
        .map(|contrib_idx| places::stop::FeedPublisher {
            id: navitia.contributors[contrib_idx].id.clone(),
            name: navitia.contributors[contrib_idx].name.clone(),
            license: navitia.contributors[contrib_idx]
                .license
                .clone()
                .unwrap_or_else(|| "".into()),
            url: navitia.contributors[contrib_idx]
                .website
                .clone()
                .unwrap_or_else(|| "".into()),
        })
        .collect();
    let coord = places::coord::Coord::new(stop_area.coord.lon, stop_area.coord.lat);

    let lines = get_lines(idx, navitia);

    places::stop::Stop {
        id: places::stop::normalize_id("stop_area", &stop_area.id),
        label: stop_area.name.clone(),
        name: stop_area.name.clone(),
        coord,
        approx_coord: Some(coord.into()),
        commercial_modes,
        physical_modes,
        lines,
        comments,
        timezone: stop_area
            .timezone
            .clone()
            .map(chrono_tz::Tz::name)
            .map(str::to_owned)
            .unwrap_or_default(),
        codes: stop_area
            .codes
            .iter()
            .map(|&(ref t, ref v)| places::code::Code {
                name: t.clone(),
                value: v.clone(),
            })
            .collect(),
        properties: stop_area
            .object_properties
            .iter()
            .map(|&(ref k, ref v)| places::Property {
                key: k.clone(),
                value: v.clone(),
            })
            .collect(),
        feed_publishers,
        ..Default::default()
    }
}

#[tokio::main]
async fn main() {
    mimirsbrunn::utils::launch_async(Box::new(run)).await;
}

async fn run(args: Args) -> Result<(), transit_model::Error> {
    info!("Launching ntfs2mimir...");

    if args.city_level.is_some() {
        warn!("city-level option is deprecated, it now has no effect.");
    }

    let navitia = transit_model::ntfs::read(&args.input)?;

    let nb_stop_points: HashMap<String, u32, BuildHasherDefault<DefaultHasher>> = navitia
        .stop_areas
        .iter()
        .map(|(idx, sa)| {
            let id = places::stop::normalize_id("stop_area", &sa.id);
            let nb_stop_points = navitia
                .get_corresponding_from_idx::<_, navitia::StopPoint>(idx)
                .len();
            (id, nb_stop_points as u32)
        })
        .collect();

    let mut stops: Vec<places::stop::Stop> = navitia
        .stop_areas
        .iter()
        .map(|(idx, sa)| to_mimir(idx, sa, &navitia))
        .collect();

    initialize_weights(stops.iter_mut(), &nb_stop_points);

    import_stops(stops, &args.connection_string, &args.dataset).await?;

    Ok(())
}

#[test]
fn test_bad_connection_string() {
    let args = Args {
        input: PathBuf::from("./tests/fixtures/ntfs"),
        connection_string: "http://localhost:1".to_string(),
        dataset: "bob".to_string(),
        city_level: None,
        nb_replicas: 1,
        nb_shards: 1,
    };
    let causes = run(args)
        .unwrap_err()
        .iter_chain()
        .map(|cause| format!("{}", cause))
        .collect::<Vec<String>>();
    assert_eq!(
        causes,
        [
            "Error occurred when importing stops into bob on http://localhost:1: Error: HTTP Error while creating template template_addr".to_string(),
            "Error: HTTP Error while creating template template_addr".to_string(),
        ]
    );
}

#[test]
fn test_bad_file() {
    let args = Args {
        input: PathBuf::from("./tests/fixtures/not_exist"),
        connection_string: "http://localhost:9200".to_string(),
        dataset: "bob".to_string(),
        city_level: None,
        nb_replicas: 1,
        nb_shards: 1,
    };
    let causes = run(args)
        .unwrap_err()
        .iter_chain()
        .map(|cause| format!("{}", cause))
        .collect::<Vec<String>>();
    assert_eq!(
        causes,
        [
            "Error reading \"./tests/fixtures/not_exist/contributors.txt\"",
            "No such file or directory (os error 2)",
        ]
    );
}
<commit_msg>Fix clippy warning<commit_after>// Copyright © 2016, Canal TP and/or its affiliates. All rights reserved.
//
// This file is part of Navitia,
//     the software to build cool stuff with public transport.
//
// Hope you'll enjoy and contribute to this project,
//     powered by Canal TP (www.canaltp.fr).
// Help us simplify mobility and open public transport:
//     a non ending quest to the responsive locomotion way of traveling!
//
// LICENCE: This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU Affero General Public
// License as published by the Free Software Foundation, either
// version 3 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public
// License along with this program. If not, see
// <http://www.gnu.org/licenses/>.
//
// Stay tuned using
// twitter @navitia
// IRC #navitia on freenode
// https://groups.google.com/d/forum/navitia
// www.navitia.io

use mimirsbrunn::stops::*;
use slog_scope::{info, warn};
use std::cmp::Ordering;
use std::collections::{hash_map::DefaultHasher, HashMap};
use std::hash::BuildHasherDefault;
use std::path::PathBuf;
use structopt::StructOpt;
use transit_model::objects as navitia;
use typed_index_collection::Idx;

#[derive(Debug, StructOpt)]
struct Args {
    /// NTFS directory.
    #[structopt(short = "i", long = "input", parse(from_os_str), default_value = ".")]
    input: PathBuf,
    /// Name of the dataset.
    #[structopt(short = "d", long = "dataset", default_value = "fr")]
    dataset: String,
    /// Elasticsearch parameters.
    #[structopt(
        short = "c",
        long = "connection-string",
        default_value = "http://localhost:9200/munin"
    )]
    connection_string: String,
    /// Deprecated option.
    #[structopt(short = "C", long = "city-level")]
    city_level: Option<String>,
    /// Number of shards for the es index
    #[structopt(short = "s", long = "nb-shards", default_value = "1")]
    nb_shards: usize,
    /// Number of replicas for the es index
    #[structopt(short = "r", long = "nb-replicas", default_value = "1")]
    nb_replicas: usize,
}

fn get_lines(
    idx: Idx<navitia::StopArea>,
    navitia: &transit_model::Model,
) -> Vec<places::stop::Line> {
    use places::stop::FromTransitModel;
    let mut lines: Vec<_> = navitia
        .get_corresponding_from_idx(idx)
        .into_iter()
        .map(|l_idx| places::stop::Line::from_transit_model(l_idx, navitia))
        .collect();

    // we want the lines to be sorted in a way where
    // line-3 is before line-11, so be use a human_sort
    lines.sort_by(|lhs, rhs| {
        match (&lhs.sort_order, &rhs.sort_order) {
            (None, Some(_)) => Ordering::Greater,
            (Some(_), None) => Ordering::Less,
            (Some(s), Some(o)) => s.cmp(o),
            (None, None) => Ordering::Equal,
        }
        .then_with(|| match (&lhs.code, &rhs.code) {
            (Some(l), Some(r)) => human_sort::compare(l, r),
            _ => Ordering::Equal,
        })
        .then_with(|| human_sort::compare(&lhs.name, &rhs.name))
    });
    lines
}

fn to_mimir(
    idx: Idx<navitia::StopArea>,
    stop_area: &navitia::StopArea,
    navitia: &transit_model::Model,
) -> places::stop::Stop {
    let commercial_modes = navitia
        .get_corresponding_from_idx(idx)
        .into_iter()
        .map(|cm_idx| places::stop::CommercialMode {
            id: places::stop::normalize_id("commercial_mode", &navitia.commercial_modes[cm_idx].id),
            name: navitia.commercial_modes[cm_idx].name.clone(),
        })
        .collect();
    let physical_modes = navitia
        .get_corresponding_from_idx(idx)
        .into_iter()
        .map(|pm_idx| places::stop::PhysicalMode {
            id: places::stop::normalize_id("physical_mode", &navitia.physical_modes[pm_idx].id),
            name: navitia.physical_modes[pm_idx].name.clone(),
        })
        .collect();
    let comments = stop_area
        .comment_links
        .iter()
        .filter_map(|comment_id| {
            let res = navitia.comments.get(comment_id);
            if res.is_none() {
                warn!("Could not retrieve comments for id {}", comment_id);
            }
            res
        })
        .map(|comment| places::stop::Comment {
            name: comment.name.clone(),
        })
        .collect();
    let feed_publishers = navitia
        .get_corresponding_from_idx(idx)
        .into_iter()
        .map(|contrib_idx| places::stop::FeedPublisher {
            id: navitia.contributors[contrib_idx].id.clone(),
            name: navitia.contributors[contrib_idx].name.clone(),
            license: navitia.contributors[contrib_idx]
                .license
                .clone()
                .unwrap_or_else(|| "".into()),
            url: navitia.contributors[contrib_idx]
                .website
                .clone()
                .unwrap_or_else(|| "".into()),
        })
        .collect();
    let coord = places::coord::Coord::new(stop_area.coord.lon, stop_area.coord.lat);

    let lines = get_lines(idx, navitia);

    places::stop::Stop {
        id: places::stop::normalize_id("stop_area", &stop_area.id),
        label: stop_area.name.clone(),
        name: stop_area.name.clone(),
        coord,
        approx_coord: Some(coord.into()),
        commercial_modes,
        physical_modes,
        lines,
        comments,
        timezone: stop_area
            .timezone
            .map(chrono_tz::Tz::name)
            .map(str::to_owned)
            .unwrap_or_default(),
        codes: stop_area
            .codes
            .iter()
            .map(|&(ref t, ref v)| places::code::Code {
                name: t.clone(),
                value: v.clone(),
            })
            .collect(),
        properties: stop_area
            .object_properties
            .iter()
            .map(|&(ref k, ref v)| places::Property {
                key: k.clone(),
                value: v.clone(),
            })
            .collect(),
        feed_publishers,
        ..Default::default()
    }
}

#[tokio::main]
async fn main() {
    mimirsbrunn::utils::launch_async(Box::new(run)).await;
}

async fn run(args: Args) -> Result<(), transit_model::Error> {
    info!("Launching ntfs2mimir...");

    if args.city_level.is_some() {
        warn!("city-level option is deprecated, it now has no effect.");
    }

    let navitia = transit_model::ntfs::read(&args.input)?;

    let nb_stop_points: HashMap<String, u32, BuildHasherDefault<DefaultHasher>> = navitia
        .stop_areas
        .iter()
        .map(|(idx, sa)| {
            let id = places::stop::normalize_id("stop_area", &sa.id);
            let nb_stop_points = navitia
                .get_corresponding_from_idx::<_, navitia::StopPoint>(idx)
                .len();
            (id, nb_stop_points as u32)
        })
        .collect();

    let mut stops: Vec<places::stop::Stop> = navitia
        .stop_areas
        .iter()
        .map(|(idx, sa)| to_mimir(idx, sa, &navitia))
        .collect();

    initialize_weights(stops.iter_mut(), &nb_stop_points);

    import_stops(stops, &args.connection_string, &args.dataset).await?;

    Ok(())
}

#[test]
fn test_bad_connection_string() {
    let args = Args {
        input: PathBuf::from("./tests/fixtures/ntfs"),
        connection_string: "http://localhost:1".to_string(),
        dataset: "bob".to_string(),
        city_level: None,
        nb_replicas: 1,
        nb_shards: 1,
    };
    let causes = run(args)
        .unwrap_err()
        .iter_chain()
        .map(|cause| format!("{}", cause))
        .collect::<Vec<String>>();
    assert_eq!(
        causes,
        [
            "Error occurred when importing stops into bob on http://localhost:1: Error: HTTP Error while creating template template_addr".to_string(),
            "Error: HTTP Error while creating template template_addr".to_string(),
        ]
    );
}

#[test]
fn test_bad_file() {
    let args = Args {
        input: PathBuf::from("./tests/fixtures/not_exist"),
        connection_string: "http://localhost:9200".to_string(),
        dataset: "bob".to_string(),
        city_level: None,
        nb_replicas: 1,
        nb_shards: 1,
    };
    let causes = run(args)
        .unwrap_err()
        .iter_chain()
        .map(|cause| format!("{}", cause))
        .collect::<Vec<String>>();
    assert_eq!(
        causes,
        [
            "Error reading \"./tests/fixtures/not_exist/contributors.txt\"",
            "No such file or directory (os error 2)",
        ]
    );
}
<|endoftext|>


<commit_before>// Copyright 2015-2016 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

// Inspired by Paul Woolcock's cargo-fmt (https://github.com/pwoolcoc/cargo-fmt/)

#![cfg(not(test))]
#![deny(warnings)]

extern crate cargo_metadata;
extern crate getopts;
extern crate serde_json as json;

use std::collections::HashSet;
use std::env;
use std::fs;
use std::hash::{Hash, Hasher};
use std::io::{self, Write};
use std::iter::FromIterator;
use std::path::{Path, PathBuf};
use std::process::{Command, ExitStatus};
use std::str;

use getopts::{Matches, Options};

fn main() {
    let exit_status = execute();
    std::io::stdout().flush().unwrap();
    std::process::exit(exit_status);
}

const SUCCESS: i32 = 0;
const FAILURE: i32 = 1;

fn execute() -> i32 {
    let mut opts = getopts::Options::new();
    opts.optflag("h", "help", "show this message");
    opts.optflag("q", "quiet", "no output printed to stdout");
    opts.optflag("v", "verbose", "use verbose output");
    opts.optmulti(
        "p",
        "package",
        "specify package to format (only usable in workspaces)",
        "<package>",
    );
    opts.optflag("", "version", "print rustfmt version and exit");
    opts.optflag("", "all", "format all packages (only usable in workspaces)");

    // If there is any invalid argument passed to `cargo fmt`, return without formatting.
    let mut is_package_arg = false;
    for arg in env::args().skip(2).take_while(|a| a != "--") {
        if arg.starts_with('-') {
            is_package_arg = arg.starts_with("--package");
        } else if !is_package_arg {
            print_usage_to_stderr(&opts, &format!("Invalid argument: `{}`.", arg));
            return FAILURE;
        } else {
            is_package_arg = false;
        }
    }

    let matches = match opts.parse(env::args().skip(1).take_while(|a| a != "--")) {
        Ok(m) => m,
        Err(e) => {
            print_usage_to_stderr(&opts, &e.to_string());
            return FAILURE;
        }
    };

    let verbosity = match (matches.opt_present("v"), matches.opt_present("q")) {
        (false, false) => Verbosity::Normal,
        (false, true) => Verbosity::Quiet,
        (true, false) => Verbosity::Verbose,
        (true, true) => {
            print_usage_to_stderr(&opts, "quiet mode and verbose mode are not compatible");
            return FAILURE;
        }
    };

    if matches.opt_present("h") {
        print_usage_to_stdout(&opts, "");
        return SUCCESS;
    }

    if matches.opt_present("version") {
        return handle_command_status(get_version(verbosity), &opts);
    }

    let strategy = CargoFmtStrategy::from_matches(&matches);
    handle_command_status(format_crate(verbosity, &strategy), &opts)
}

macro_rules! print_usage {
    ($print:ident, $opts:ident, $reason:expr) => {{
        let msg = format!("{}\nusage: cargo fmt [options]", $reason);
        $print!(
            "{}\nThis utility formats all bin and lib files of the current crate using rustfmt. \
             Arguments after `--` are passed to rustfmt.",
            $opts.usage(&msg)
        );
    }};
}

fn print_usage_to_stdout(opts: &Options, reason: &str) {
    print_usage!(println, opts, reason);
}

fn print_usage_to_stderr(opts: &Options, reason: &str) {
    print_usage!(eprintln, opts, reason);
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Verbosity {
    Verbose,
    Normal,
    Quiet,
}

fn handle_command_status(status: Result<ExitStatus, io::Error>, opts: &getopts::Options) -> i32 {
    match status {
        Err(e) => {
            print_usage_to_stderr(opts, &e.to_string());
            FAILURE
        }
        Ok(status) => {
            if status.success() {
                SUCCESS
            } else {
                status.code().unwrap_or(FAILURE)
            }
        }
    }
}

fn get_version(verbosity: Verbosity) -> Result<ExitStatus, io::Error> {
    run_rustfmt(&[], &[String::from("--version")], verbosity)
}

fn format_crate(
    verbosity: Verbosity,
    strategy: &CargoFmtStrategy,
) -> Result<ExitStatus, io::Error> {
    let rustfmt_args = get_fmt_args();
    let targets = if rustfmt_args.iter().any(|s| s == "--dump-default-config") {
        HashSet::new()
    } else {
        get_targets(strategy)?
    };

    // Currently only bin and lib files get formatted
    let files: Vec<_> = targets
        .into_iter()
        .inspect(|t| {
            if verbosity == Verbosity::Verbose {
                println!("[{}] {:?}", t.kind, t.path)
            }
        })
        .map(|t| t.path)
        .collect();

    run_rustfmt(&files, &rustfmt_args, verbosity)
}

fn get_fmt_args() -> Vec<String> {
    // All arguments after -- are passed to rustfmt
    env::args().skip_while(|a| a != "--").skip(1).collect()
}

/// Target uses a `path` field for equality and hashing.
#[derive(Debug)]
pub struct Target {
    /// A path to the main source file of the target.
    path: PathBuf,
    /// A kind of target (e.g. lib, bin, example, ...).
    kind: String,
}

impl Target {
    pub fn from_target(target: &cargo_metadata::Target) -> Self {
        let path = PathBuf::from(&target.src_path);
        let canonicalized = fs::canonicalize(&path).unwrap_or(path);

        Target {
            path: canonicalized,
            kind: target.kind[0].clone(),
        }
    }
}

impl PartialEq for Target {
    fn eq(&self, other: &Target) -> bool {
        self.path == other.path
    }
}

impl Eq for Target {}

impl Hash for Target {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.path.hash(state);
    }
}

#[derive(Debug, PartialEq, Eq)]
pub enum CargoFmtStrategy {
    /// Format every packages and dependencies.
    All,
    /// Format packages that are specified by the command line argument.
    Some(Vec<String>),
    /// Format the root packages only.
    Root,
}

impl CargoFmtStrategy {
    pub fn from_matches(matches: &Matches) -> CargoFmtStrategy {
        match (matches.opt_present("all"), matches.opt_present("p")) {
            (false, false) => CargoFmtStrategy::Root,
            (true, _) => CargoFmtStrategy::All,
            (false, true) => CargoFmtStrategy::Some(matches.opt_strs("p")),
        }
    }
}

/// Based on the specified `CargoFmtStrategy`, returns a set of main source files.
fn get_targets(strategy: &CargoFmtStrategy) -> Result<HashSet<Target>, io::Error> {
    let mut targets = HashSet::new();

    match *strategy {
        CargoFmtStrategy::Root => get_targets_root_only(&mut targets)?,
        CargoFmtStrategy::All => get_targets_recursive(None, &mut targets, &mut HashSet::new())?,
        CargoFmtStrategy::Some(ref hitlist) => get_targets_with_hitlist(hitlist, &mut targets)?,
    }

    if targets.is_empty() {
        Err(io::Error::new(
            io::ErrorKind::Other,
            "Failed to find targets".to_owned(),
        ))
    } else {
        Ok(targets)
    }
}

fn get_targets_root_only(targets: &mut HashSet<Target>) -> Result<(), io::Error> {
    let metadata = get_cargo_metadata(None)?;
    let current_dir = env::current_dir()?.canonicalize()?;
    let current_dir_manifest = current_dir.join("Cargo.toml");
    let workspace_root_path = PathBuf::from(&metadata.workspace_root).canonicalize()?;
    let in_workspace_root = workspace_root_path == current_dir;

    for package in metadata.packages {
        if in_workspace_root || PathBuf::from(&package.manifest_path) == current_dir_manifest {
            for target in package.targets {
                targets.insert(Target::from_target(&target));
            }
        }
    }

    Ok(())
}

fn get_targets_recursive(
    manifest_path: Option<&Path>,
    mut targets: &mut HashSet<Target>,
    visited: &mut HashSet<String>,
) -> Result<(), io::Error> {
    let metadata = get_cargo_metadata(manifest_path)?;

    for package in metadata.packages {
        add_targets(&package.targets, &mut targets);

        // Look for local dependencies.
        for dependency in package.dependencies {
            if dependency.source.is_some() || visited.contains(&dependency.name) {
                continue;
            }

            let mut manifest_path = PathBuf::from(&package.manifest_path);

            manifest_path.pop();
            manifest_path.push(&dependency.name);
            manifest_path.push("Cargo.toml");

            if manifest_path.exists() {
                visited.insert(dependency.name);
                get_targets_recursive(Some(&manifest_path), &mut targets, visited)?;
            }
        }
    }

    Ok(())
}

fn get_targets_with_hitlist(
    hitlist: &[String],
    targets: &mut HashSet<Target>,
) -> Result<(), io::Error> {
    let metadata = get_cargo_metadata(None)?;

    let mut workspace_hitlist: HashSet<&String> = HashSet::from_iter(hitlist);

    for package in metadata.packages {
        if workspace_hitlist.remove(&package.name) {
            for target in package.targets {
                targets.insert(Target::from_target(&target));
            }
        }
    }

    if workspace_hitlist.is_empty() {
        Ok(())
    } else {
        let package = workspace_hitlist.iter().next().unwrap();
        Err(io::Error::new(
            io::ErrorKind::InvalidInput,
            format!("package `{}` is not a member of the workspace", package),
        ))
    }
}

fn add_targets(target_paths: &[cargo_metadata::Target], targets: &mut HashSet<Target>) {
    for target in target_paths {
        targets.insert(Target::from_target(target));
    }
}

fn run_rustfmt(
    files: &[PathBuf],
    fmt_args: &[String],
    verbosity: Verbosity,
) -> Result<ExitStatus, io::Error> {
    let stdout = if verbosity == Verbosity::Quiet {
        std::process::Stdio::null()
    } else {
        std::process::Stdio::inherit()
    };

    if verbosity == Verbosity::Verbose {
        print!("rustfmt");
        for a in fmt_args {
            print!(" {}", a);
        }
        for f in files {
            print!(" {}", f.display());
        }
        println!();
    }

    let mut command = Command::new("rustfmt")
        .stdout(stdout)
        .args(files)
        .args(fmt_args)
        .spawn()
        .map_err(|e| match e.kind() {
            io::ErrorKind::NotFound => io::Error::new(
                io::ErrorKind::Other,
                "Could not run rustfmt, please make sure it is in your PATH.",
            ),
            _ => e,
        })?;

    command.wait()
}

fn get_cargo_metadata(manifest_path: Option<&Path>) -> Result<cargo_metadata::Metadata, io::Error> {
    match cargo_metadata::metadata(manifest_path) {
        Ok(metadata) => Ok(metadata),
        Err(..) => Err(io::Error::new(
            io::ErrorKind::Other,
            "`cargo manifest` failed.",
        )),
    }
}
<commit_msg>update rustfmt flags that should work without targets<commit_after>// Copyright 2015-2016 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

// Inspired by Paul Woolcock's cargo-fmt (https://github.com/pwoolcoc/cargo-fmt/)

#![cfg(not(test))]
#![deny(warnings)]

extern crate cargo_metadata;
extern crate getopts;
extern crate serde_json as json;

use std::collections::HashSet;
use std::env;
use std::fs;
use std::hash::{Hash, Hasher};
use std::io::{self, Write};
use std::iter::FromIterator;
use std::path::{Path, PathBuf};
use std::process::{Command, ExitStatus};
use std::str;

use getopts::{Matches, Options};

fn main() {
    let exit_status = execute();
    std::io::stdout().flush().unwrap();
    std::process::exit(exit_status);
}

const SUCCESS: i32 = 0;
const FAILURE: i32 = 1;

fn execute() -> i32 {
    let mut opts = getopts::Options::new();
    opts.optflag("h", "help", "show this message");
    opts.optflag("q", "quiet", "no output printed to stdout");
    opts.optflag("v", "verbose", "use verbose output");
    opts.optmulti(
        "p",
        "package",
        "specify package to format (only usable in workspaces)",
        "<package>",
    );
    opts.optflag("", "version", "print rustfmt version and exit");
    opts.optflag("", "all", "format all packages (only usable in workspaces)");

    // If there is any invalid argument passed to `cargo fmt`, return without formatting.
    let mut is_package_arg = false;
    for arg in env::args().skip(2).take_while(|a| a != "--") {
        if arg.starts_with('-') {
            is_package_arg = arg.starts_with("--package");
        } else if !is_package_arg {
            print_usage_to_stderr(&opts, &format!("Invalid argument: `{}`.", arg));
            return FAILURE;
        } else {
            is_package_arg = false;
        }
    }

    let matches = match opts.parse(env::args().skip(1).take_while(|a| a != "--")) {
        Ok(m) => m,
        Err(e) => {
            print_usage_to_stderr(&opts, &e.to_string());
            return FAILURE;
        }
    };

    let verbosity = match (matches.opt_present("v"), matches.opt_present("q")) {
        (false, false) => Verbosity::Normal,
        (false, true) => Verbosity::Quiet,
        (true, false) => Verbosity::Verbose,
        (true, true) => {
            print_usage_to_stderr(&opts, "quiet mode and verbose mode are not compatible");
            return FAILURE;
        }
    };

    if matches.opt_present("h") {
        print_usage_to_stdout(&opts, "");
        return SUCCESS;
    }

    if matches.opt_present("version") {
        return handle_command_status(get_version(verbosity), &opts);
    }

    let strategy = CargoFmtStrategy::from_matches(&matches);
    handle_command_status(format_crate(verbosity, &strategy), &opts)
}

macro_rules! print_usage {
    ($print:ident, $opts:ident, $reason:expr) => {{
        let msg = format!("{}\nusage: cargo fmt [options]", $reason);
        $print!(
            "{}\nThis utility formats all bin and lib files of the current crate using rustfmt. \
             Arguments after `--` are passed to rustfmt.",
            $opts.usage(&msg)
        );
    }};
}

fn print_usage_to_stdout(opts: &Options, reason: &str) {
    print_usage!(println, opts, reason);
}

fn print_usage_to_stderr(opts: &Options, reason: &str) {
    print_usage!(eprintln, opts, reason);
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Verbosity {
    Verbose,
    Normal,
    Quiet,
}

fn handle_command_status(status: Result<ExitStatus, io::Error>, opts: &getopts::Options) -> i32 {
    match status {
        Err(e) => {
            print_usage_to_stderr(opts, &e.to_string());
            FAILURE
        }
        Ok(status) => {
            if status.success() {
                SUCCESS
            } else {
                status.code().unwrap_or(FAILURE)
            }
        }
    }
}

fn get_version(verbosity: Verbosity) -> Result<ExitStatus, io::Error> {
    run_rustfmt(&[], &[String::from("--version")], verbosity)
}

fn format_crate(
    verbosity: Verbosity,
    strategy: &CargoFmtStrategy,
) -> Result<ExitStatus, io::Error> {
    let rustfmt_args = get_fmt_args();
    let targets = if rustfmt_args
        .iter()
        .any(|s| ["--print-config", "-h", "--help", "-V", "--verison"]
             .contains(&s.as_str()))
    {
        HashSet::new()
    } else {
        get_targets(strategy)?
    };

    // Currently only bin and lib files get formatted
    let files: Vec<_> = targets
        .into_iter()
        .inspect(|t| {
            if verbosity == Verbosity::Verbose {
                println!("[{}] {:?}", t.kind, t.path)
            }
        })
        .map(|t| t.path)
        .collect();

    run_rustfmt(&files, &rustfmt_args, verbosity)
}

fn get_fmt_args() -> Vec<String> {
    // All arguments after -- are passed to rustfmt
    env::args().skip_while(|a| a != "--").skip(1).collect()
}

/// Target uses a `path` field for equality and hashing.
#[derive(Debug)]
pub struct Target {
    /// A path to the main source file of the target.
    path: PathBuf,
    /// A kind of target (e.g. lib, bin, example, ...).
    kind: String,
}

impl Target {
    pub fn from_target(target: &cargo_metadata::Target) -> Self {
        let path = PathBuf::from(&target.src_path);
        let canonicalized = fs::canonicalize(&path).unwrap_or(path);

        Target {
            path: canonicalized,
            kind: target.kind[0].clone(),
        }
    }
}

impl PartialEq for Target {
    fn eq(&self, other: &Target) -> bool {
        self.path == other.path
    }
}

impl Eq for Target {}

impl Hash for Target {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.path.hash(state);
    }
}

#[derive(Debug, PartialEq, Eq)]
pub enum CargoFmtStrategy {
    /// Format every packages and dependencies.
    All,
    /// Format packages that are specified by the command line argument.
    Some(Vec<String>),
    /// Format the root packages only.
    Root,
}

impl CargoFmtStrategy {
    pub fn from_matches(matches: &Matches) -> CargoFmtStrategy {
        match (matches.opt_present("all"), matches.opt_present("p")) {
            (false, false) => CargoFmtStrategy::Root,
            (true, _) => CargoFmtStrategy::All,
            (false, true) => CargoFmtStrategy::Some(matches.opt_strs("p")),
        }
    }
}

/// Based on the specified `CargoFmtStrategy`, returns a set of main source files.
fn get_targets(strategy: &CargoFmtStrategy) -> Result<HashSet<Target>, io::Error> {
    let mut targets = HashSet::new();

    match *strategy {
        CargoFmtStrategy::Root => get_targets_root_only(&mut targets)?,
        CargoFmtStrategy::All => get_targets_recursive(None, &mut targets, &mut HashSet::new())?,
        CargoFmtStrategy::Some(ref hitlist) => get_targets_with_hitlist(hitlist, &mut targets)?,
    }

    if targets.is_empty() {
        Err(io::Error::new(
            io::ErrorKind::Other,
            "Failed to find targets".to_owned(),
        ))
    } else {
        Ok(targets)
    }
}

fn get_targets_root_only(targets: &mut HashSet<Target>) -> Result<(), io::Error> {
    let metadata = get_cargo_metadata(None)?;
    let current_dir = env::current_dir()?.canonicalize()?;
    let current_dir_manifest = current_dir.join("Cargo.toml");
    let workspace_root_path = PathBuf::from(&metadata.workspace_root).canonicalize()?;
    let in_workspace_root = workspace_root_path == current_dir;

    for package in metadata.packages {
        if in_workspace_root || PathBuf::from(&package.manifest_path) == current_dir_manifest {
            for target in package.targets {
                targets.insert(Target::from_target(&target));
            }
        }
    }

    Ok(())
}

fn get_targets_recursive(
    manifest_path: Option<&Path>,
    mut targets: &mut HashSet<Target>,
    visited: &mut HashSet<String>,
) -> Result<(), io::Error> {
    let metadata = get_cargo_metadata(manifest_path)?;

    for package in metadata.packages {
        add_targets(&package.targets, &mut targets);

        // Look for local dependencies.
        for dependency in package.dependencies {
            if dependency.source.is_some() || visited.contains(&dependency.name) {
                continue;
            }

            let mut manifest_path = PathBuf::from(&package.manifest_path);

            manifest_path.pop();
            manifest_path.push(&dependency.name);
            manifest_path.push("Cargo.toml");

            if manifest_path.exists() {
                visited.insert(dependency.name);
                get_targets_recursive(Some(&manifest_path), &mut targets, visited)?;
            }
        }
    }

    Ok(())
}

fn get_targets_with_hitlist(
    hitlist: &[String],
    targets: &mut HashSet<Target>,
) -> Result<(), io::Error> {
    let metadata = get_cargo_metadata(None)?;

    let mut workspace_hitlist: HashSet<&String> = HashSet::from_iter(hitlist);

    for package in metadata.packages {
        if workspace_hitlist.remove(&package.name) {
            for target in package.targets {
                targets.insert(Target::from_target(&target));
            }
        }
    }

    if workspace_hitlist.is_empty() {
        Ok(())
    } else {
        let package = workspace_hitlist.iter().next().unwrap();
        Err(io::Error::new(
            io::ErrorKind::InvalidInput,
            format!("package `{}` is not a member of the workspace", package),
        ))
    }
}

fn add_targets(target_paths: &[cargo_metadata::Target], targets: &mut HashSet<Target>) {
    for target in target_paths {
        targets.insert(Target::from_target(target));
    }
}

fn run_rustfmt(
    files: &[PathBuf],
    fmt_args: &[String],
    verbosity: Verbosity,
) -> Result<ExitStatus, io::Error> {
    let stdout = if verbosity == Verbosity::Quiet {
        std::process::Stdio::null()
    } else {
        std::process::Stdio::inherit()
    };

    if verbosity == Verbosity::Verbose {
        print!("rustfmt");
        for a in fmt_args {
            print!(" {}", a);
        }
        for f in files {
            print!(" {}", f.display());
        }
        println!();
    }

    let mut command = Command::new("rustfmt")
        .stdout(stdout)
        .args(files)
        .args(fmt_args)
        .spawn()
        .map_err(|e| match e.kind() {
            io::ErrorKind::NotFound => io::Error::new(
                io::ErrorKind::Other,
                "Could not run rustfmt, please make sure it is in your PATH.",
            ),
            _ => e,
        })?;

    command.wait()
}

fn get_cargo_metadata(manifest_path: Option<&Path>) -> Result<cargo_metadata::Metadata, io::Error> {
    match cargo_metadata::metadata(manifest_path) {
        Ok(metadata) => Ok(metadata),
        Err(..) => Err(io::Error::new(
            io::ErrorKind::Other,
            "`cargo manifest` failed.",
        )),
    }
}
<|endoftext|>