<commit_before>use std::{convert::TryInto, fmt};

use serde::Serialize;
use serde_json::{Error as JsonError, Map as JsonObject, Value as JsonValue};

pub mod value;

use value::Object as CanonicalJsonObject;

/// Returns a canonical JSON string according to Matrix specification.
///
/// This function should be preferred over `serde_json::to_string` since it checks the size of the
/// canonical string. Matrix canonical JSON enforces a size limit of less than 65,535 when sending
/// PDU's for the server-server protocol.
pub fn to_string<T: Serialize>(val: &T) -> Result<String, Error> {
    let s = serde_json::to_string(val).map_err(Error::SerDe)?;

    if s.as_bytes().len() > 65_535 {
        Err(Error::JsonSize)
    } else {
        Ok(s)
    }
}

/// The set of possible errors when serializing to canonical JSON.
#[derive(Debug)]
pub enum Error {
    /// The numeric value failed conversion to js_int::Int.
    IntConvert,

    /// The `CanonicalJsonValue` being serialized was larger than 65,535 bytes.
    JsonSize,

    /// An error occurred while serializing/deserializing.
    SerDe(JsonError),
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Error::IntConvert => f.write_str("number found is not a valid `js_int::Int`"),
            Error::JsonSize => f.write_str("JSON is larger than 65,535 byte max"),
            Error::SerDe(err) => write!(f, "serde Error: {}", err),
        }
    }
}

impl std::error::Error for Error {}

/// Fallible conversion from a `serde_json::Map` to a `CanonicalJsonObject`.
pub fn try_from_json_map(
    json: JsonObject<String, JsonValue>,
) -> Result<CanonicalJsonObject, Error> {
    json.into_iter().map(|(k, v)| Ok((k, v.try_into()?))).collect()
}

/// Fallible conversion from any value that impl's `Serialize` to a `CanonicalJsonValue`.
pub fn to_canonical_value<T: Serialize>(value: T) -> Result<value::CanonicalJsonValue, Error> {
    serde_json::to_value(value).map_err(Error::SerDe)?.try_into()
}

#[cfg(test)]
mod tests {
    use std::{collections::BTreeMap, convert::TryInto};

    use super::{
        to_canonical_value, to_string as to_canonical_json_string, try_from_json_map,
        value::CanonicalJsonValue,
    };

    use js_int::int;
    use serde_json::{from_str as from_json_str, json, to_string as to_json_string, Map};

    #[test]
    fn serialize_canon() {
        let json: CanonicalJsonValue = json!({
            "a": [1, 2, 3],
            "other": { "stuff": "hello" },
            "string": "Thing"
        })
        .try_into()
        .unwrap();

        let ser = to_canonical_json_string(&json).unwrap();
        let back = from_json_str::<CanonicalJsonValue>(&ser).unwrap();

        assert_eq!(json, back);
    }

    #[test]
    fn check_canonical_sorts_keys() {
        let json: CanonicalJsonValue = json!({
            "auth": {
                "success": true,
                "mxid": "@john.doe:example.com",
                "profile": {
                    "display_name": "John Doe",
                    "three_pids": [
                        {
                            "medium": "email",
                            "address": "john.doe@example.org"
                        },
                        {
                            "medium": "msisdn",
                            "address": "123456789"
                        }
                    ]
                }
            }
        })
        .try_into()
        .unwrap();

        assert_eq!(
            to_json_string(&json).unwrap(),
            r#"{"auth":{"mxid":"@john.doe:example.com","profile":{"display_name":"John Doe","three_pids":[{"address":"john.doe@example.org","medium":"email"},{"address":"123456789","medium":"msisdn"}]},"success":true}}"#
        )
    }

    #[test]
    fn serialize_map_to_canonical() {
        let mut expected = BTreeMap::new();
        expected.insert("foo".into(), CanonicalJsonValue::String("string".into()));
        expected.insert(
            "bar".into(),
            CanonicalJsonValue::Array(vec![
                CanonicalJsonValue::Integer(int!(0)),
                CanonicalJsonValue::Integer(int!(1)),
                CanonicalJsonValue::Integer(int!(2)),
            ]),
        );

        let mut map = Map::new();
        map.insert("foo".into(), json!("string"));
        map.insert("bar".into(), json!(vec![0, 1, 2,]));

        assert_eq!(try_from_json_map(map).unwrap(), expected);
    }

    #[test]
    fn to_canonical() {
        #[derive(Debug, serde::Serialize)]
        struct Thing {
            foo: String,
            bar: Vec<u8>,
        }
        let t = Thing { foo: "string".into(), bar: vec![0, 1, 2] };

        let mut expected = BTreeMap::new();
        expected.insert("foo".into(), CanonicalJsonValue::String("string".into()));
        expected.insert(
            "bar".into(),
            CanonicalJsonValue::Array(vec![
                CanonicalJsonValue::Integer(int!(0)),
                CanonicalJsonValue::Integer(int!(1)),
                CanonicalJsonValue::Integer(int!(2)),
            ]),
        );

        assert_eq!(to_canonical_value(t).unwrap(), CanonicalJsonValue::Object(expected));
    }
}
<commit_msg>Don't import serde_json::Map<commit_after>use std::{convert::TryInto, fmt};

use serde::Serialize;
use serde_json::{Error as JsonError, Value as JsonValue};

pub mod value;

use value::Object as CanonicalJsonObject;

/// Returns a canonical JSON string according to Matrix specification.
///
/// This function should be preferred over `serde_json::to_string` since it checks the size of the
/// canonical string. Matrix canonical JSON enforces a size limit of less than 65,535 when sending
/// PDU's for the server-server protocol.
pub fn to_string<T: Serialize>(val: &T) -> Result<String, Error> {
    let s = serde_json::to_string(val).map_err(Error::SerDe)?;

    if s.as_bytes().len() > 65_535 {
        Err(Error::JsonSize)
    } else {
        Ok(s)
    }
}

/// The set of possible errors when serializing to canonical JSON.
#[derive(Debug)]
pub enum Error {
    /// The numeric value failed conversion to js_int::Int.
    IntConvert,

    /// The `CanonicalJsonValue` being serialized was larger than 65,535 bytes.
    JsonSize,

    /// An error occurred while serializing/deserializing.
    SerDe(JsonError),
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Error::IntConvert => f.write_str("number found is not a valid `js_int::Int`"),
            Error::JsonSize => f.write_str("JSON is larger than 65,535 byte max"),
            Error::SerDe(err) => write!(f, "serde Error: {}", err),
        }
    }
}

impl std::error::Error for Error {}

/// Fallible conversion from a `serde_json::Map` to a `CanonicalJsonObject`.
pub fn try_from_json_map(
    json: serde_json::Map<String, JsonValue>,
) -> Result<CanonicalJsonObject, Error> {
    json.into_iter().map(|(k, v)| Ok((k, v.try_into()?))).collect()
}

/// Fallible conversion from any value that impl's `Serialize` to a `CanonicalJsonValue`.
pub fn to_canonical_value<T: Serialize>(value: T) -> Result<value::CanonicalJsonValue, Error> {
    serde_json::to_value(value).map_err(Error::SerDe)?.try_into()
}

#[cfg(test)]
mod tests {
    use std::{collections::BTreeMap, convert::TryInto};

    use super::{
        to_canonical_value, to_string as to_canonical_json_string, try_from_json_map,
        value::CanonicalJsonValue,
    };

    use js_int::int;
    use serde_json::{from_str as from_json_str, json, to_string as to_json_string};

    #[test]
    fn serialize_canon() {
        let json: CanonicalJsonValue = json!({
            "a": [1, 2, 3],
            "other": { "stuff": "hello" },
            "string": "Thing"
        })
        .try_into()
        .unwrap();

        let ser = to_canonical_json_string(&json).unwrap();
        let back = from_json_str::<CanonicalJsonValue>(&ser).unwrap();

        assert_eq!(json, back);
    }

    #[test]
    fn check_canonical_sorts_keys() {
        let json: CanonicalJsonValue = json!({
            "auth": {
                "success": true,
                "mxid": "@john.doe:example.com",
                "profile": {
                    "display_name": "John Doe",
                    "three_pids": [
                        {
                            "medium": "email",
                            "address": "john.doe@example.org"
                        },
                        {
                            "medium": "msisdn",
                            "address": "123456789"
                        }
                    ]
                }
            }
        })
        .try_into()
        .unwrap();

        assert_eq!(
            to_json_string(&json).unwrap(),
            r#"{"auth":{"mxid":"@john.doe:example.com","profile":{"display_name":"John Doe","three_pids":[{"address":"john.doe@example.org","medium":"email"},{"address":"123456789","medium":"msisdn"}]},"success":true}}"#
        )
    }

    #[test]
    fn serialize_map_to_canonical() {
        let mut expected = BTreeMap::new();
        expected.insert("foo".into(), CanonicalJsonValue::String("string".into()));
        expected.insert(
            "bar".into(),
            CanonicalJsonValue::Array(vec![
                CanonicalJsonValue::Integer(int!(0)),
                CanonicalJsonValue::Integer(int!(1)),
                CanonicalJsonValue::Integer(int!(2)),
            ]),
        );

        let mut map = serde_json::Map::new();
        map.insert("foo".into(), json!("string"));
        map.insert("bar".into(), json!(vec![0, 1, 2,]));

        assert_eq!(try_from_json_map(map).unwrap(), expected);
    }

    #[test]
    fn to_canonical() {
        #[derive(Debug, serde::Serialize)]
        struct Thing {
            foo: String,
            bar: Vec<u8>,
        }
        let t = Thing { foo: "string".into(), bar: vec![0, 1, 2] };

        let mut expected = BTreeMap::new();
        expected.insert("foo".into(), CanonicalJsonValue::String("string".into()));
        expected.insert(
            "bar".into(),
            CanonicalJsonValue::Array(vec![
                CanonicalJsonValue::Integer(int!(0)),
                CanonicalJsonValue::Integer(int!(1)),
                CanonicalJsonValue::Integer(int!(2)),
            ]),
        );

        assert_eq!(to_canonical_value(t).unwrap(), CanonicalJsonValue::Object(expected));
    }
}
<|endoftext|>


<commit_before>//! The Credentials Provider for Credentials stored in a profile inside of a Credentials file.

use regex::Regex;
use std::ascii::AsciiExt;
use std::collections::HashMap;
use std::env::{home_dir, var as env_var};
use std::fs;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::{Path, PathBuf};

use {AwsCredentials, CredentialsError, ProvideAwsCredentials, in_ten_minutes};

/// Provides AWS credentials from a profile in a credentials file.
#[derive(Clone, Debug)]
pub struct ProfileProvider {
    /// The File Path the Credentials File is located at.
    file_path: PathBuf,
    /// The Profile Path to parse out of the Credentials File.
    profile: String,
}

impl ProfileProvider {
    /// Create a new `ProfileProvider` for the default credentials file path and profile name.
    pub fn new() -> Result<ProfileProvider, CredentialsError> {
        let profile_location = match env_var("AWS_SHARED_CREDENTIALS_FILE") {
            Ok(path) => PathBuf::from(path),
            Err(_) => {
                match ProfileProvider::default_profile_location() {
                    Ok(path) => path,
                    Err(err) => return Err(err),
                }
            }
        };

        Ok(ProfileProvider {
            file_path: profile_location,
            profile: "default".to_owned(),
        })
    }

    /// Default credentials file location:
    /// `~/.aws/credentials` (Linux/Mac)
    /// `%USERPROFILE%\.aws\credentials` (Windows)
    fn default_profile_location() -> Result<PathBuf, CredentialsError> {
        match home_dir() {
            Some(home_path) => {
                let mut credentials_path = PathBuf::from(".aws");

                credentials_path.push("credentials");

                Ok(home_path.join(credentials_path))
            }
            None => Err(CredentialsError::new("The environment variable HOME must be set.")),
        }
    }

    /// Create a new `ProfileProvider` for the credentials file at the given path, using
    /// the given profile.
    pub fn with_configuration<F, P>(file_path: F, profile: P) -> ProfileProvider
    where F: Into<PathBuf>, P: Into<String> {
        ProfileProvider {
            file_path: file_path.into(),
            profile: profile.into(),
        }
    }

    /// Get a reference to the credentials file path.
    pub fn file_path(&self) -> &Path {
        self.file_path.as_ref()
    }

    /// Get a reference to the profile name.
    pub fn profile(&self) -> &str {
        &self.profile
    }

    /// Set the credentials file path.
    pub fn set_file_path<F>(&mut self, file_path: F) where F: Into<PathBuf> {
        self.file_path = file_path.into();
    }

    /// Set the profile name.
    pub fn set_profile<P>(&mut self, profile: P) where P: Into<String> {
        self.profile = profile.into();
    }
}

impl ProvideAwsCredentials for ProfileProvider {
    fn credentials(&self) -> Result<AwsCredentials, CredentialsError> {
        parse_credentials_file(self.file_path()).and_then(|mut profiles| {
            profiles.remove(self.profile()).ok_or_else(|| CredentialsError::new("profile not found"))
        })
    }
}

/// Parses a Credentials file into a Map of <ProfileName, AwsCredentials>
fn parse_credentials_file(file_path: &Path) -> Result<HashMap<String, AwsCredentials>, CredentialsError> {
    match fs::metadata(file_path) {
        Err(_) => return Err(
            CredentialsError::new(
                format!("Couldn't stat credentials file: [ {:?} ]. Non existant, or no permission.", file_path)
            )
        ),
        Ok(metadata) => {
            if !metadata.is_file() {
                return Err(CredentialsError::new(format!("Credentials file: [ {:?} ] is not a file.", file_path)));
            }
        }
    };

    let file = try!(File::open(file_path));

    let profile_regex = Regex::new(r"^\[([^\]]+)\]$").expect("Failed to compile regex");
    let mut profiles: HashMap<String, AwsCredentials> = HashMap::new();
    let mut access_key: Option<String> = None;
    let mut secret_key: Option<String> = None;
    let mut token: Option<String> = None;
    let mut profile_name: Option<String> = None;

    let file_lines = BufReader::new(&file);
    for (line_no, line) in file_lines.lines().enumerate() {
        let unwrapped_line: String = line.expect(&format!("Failed to read credentials file, line: {}", line_no));

        // skip empty lines
        if unwrapped_line.is_empty() {
            continue;
        }

        // skip comments
        if unwrapped_line.starts_with('#') {
            continue;
        }

        // handle the opening of named profile blocks
        if profile_regex.is_match(&unwrapped_line) {
            if profile_name.is_some() && access_key.is_some() && secret_key.is_some() {
                let creds = AwsCredentials::new(access_key.unwrap(), secret_key.unwrap(), token, in_ten_minutes());
                profiles.insert(profile_name.unwrap(), creds);
            }

            access_key = None;
            secret_key = None;
            token = None;

            let caps = profile_regex.captures(&unwrapped_line).unwrap();
            profile_name = Some(caps.get(1).unwrap().as_str().to_string());
            continue;
        }

        // otherwise look for key=value pairs we care about
        let lower_case_line = unwrapped_line.to_ascii_lowercase().to_string();

        if lower_case_line.contains("aws_access_key_id") &&
            access_key.is_none()
        {
            let v: Vec<&str> = unwrapped_line.split('=').collect();
            if !v.is_empty() {
                access_key = Some(v[1].trim_matches(' ').to_string());
            }
        } else if lower_case_line.contains("aws_secret_access_key") &&
            secret_key.is_none()
        {
            let v: Vec<&str> = unwrapped_line.split('=').collect();
            if !v.is_empty() {
                secret_key = Some(v[1].trim_matches(' ').to_string());
            }
        } else if lower_case_line.contains("aws_session_token") &&
            token.is_none()
        {
            let v: Vec<&str> = unwrapped_line.split('=').collect();
            if !v.is_empty() {
                token = Some(v[1].trim_matches(' ').to_string());
            }
        } else if lower_case_line.contains("region") ||
            lower_case_line.contains("output")
        {
            // Not Supported here, but valid according to: http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html#cli-config-files
            continue;
        } else {
            return Err(CredentialsError::new(format!("Invalid AWS Config line: [ {} ]", lower_case_line)))
        }

    }

    if profile_name.is_some() && access_key.is_some() && secret_key.is_some() {
        let creds = AwsCredentials::new(access_key.unwrap(), secret_key.unwrap(), token, in_ten_minutes());
        profiles.insert(profile_name.unwrap(), creds);
    }

    if profiles.is_empty() {
        return Err(CredentialsError::new("No credentials found."));
    }

    Ok(profiles)
}

#[cfg(test)]
mod tests {
    use std::env;
    use std::path::Path;

    use {CredentialsError, ProvideAwsCredentials};
    use super::*;

    #[test]
    fn parse_credentials_file_default_profile() {
        let result = super::parse_credentials_file(
            Path::new("tests/sample-data/default_profile_credentials")
        );
        assert!(result.is_ok());

        let profiles = result.ok().unwrap();
        assert_eq!(profiles.len(), 1);

        let default_profile = profiles.get("default").expect("No Default profile in default_profile_credentials");
        assert_eq!(default_profile.aws_access_key_id(), "foo");
        assert_eq!(default_profile.aws_secret_access_key(), "bar");
    }

    #[test]
    fn parse_credentials_file_multiple_profiles() {
        let result = super::parse_credentials_file(
            Path::new("tests/sample-data/multiple_profile_credentials")
        );
        assert!(result.is_ok());

        let profiles = result.ok().unwrap();
        assert_eq!(profiles.len(), 2);

        let foo_profile = profiles.get("foo").expect("No foo profile in multiple_profile_credentials");
        assert_eq!(foo_profile.aws_access_key_id(), "foo_access_key");
        assert_eq!(foo_profile.aws_secret_access_key(), "foo_secret_key");

        let bar_profile = profiles.get("bar").expect("No bar profile in multiple_profile_credentials");
        assert_eq!(bar_profile.aws_access_key_id(), "bar_access_key");
        assert_eq!(bar_profile.aws_secret_access_key(), "bar_secret_key");

    }

    #[test]
    fn parse_all_values_credentials_file() {
        let result = super::parse_credentials_file(
            Path::new("tests/sample-data/full_profile_credentials")
        );
        assert!(result.is_ok());

        let profiles = result.ok().unwrap();
        assert_eq!(profiles.len(), 1);

        let default_profile = profiles.get("default").expect("No default profile in full_profile_credentials");
        assert_eq!(default_profile.aws_access_key_id(), "foo");
        assert_eq!(default_profile.aws_secret_access_key(), "bar");
    }

    #[test]
    fn profile_provider_happy_path() {
        let provider = ProfileProvider::with_configuration(
            "tests/sample-data/multiple_profile_credentials",
            "foo",
        );
        let result = provider.credentials();

        assert!(result.is_ok());

        let creds = result.ok().unwrap();
        assert_eq!(creds.aws_access_key_id(), "foo_access_key");
        assert_eq!(creds.aws_secret_access_key(), "foo_secret_key");
    }

    #[test]
    fn profile_provider_via_environment_variable() {
        let credentials_path = "tests/sample-data/default_profile_credentials";
        env::set_var("AWS_SHARED_CREDENTIALS_FILE", credentials_path);
        let result = ProfileProvider::new();
        assert!(result.is_ok());
        let provider = result.unwrap();
        assert_eq!(provider.file_path().to_str().unwrap(), credentials_path);
        env::remove_var("AWS_SHARED_CREDENTIALS_FILE");
    }

    #[test]
    fn profile_provider_bad_profile() {
        let provider = ProfileProvider::with_configuration(
            "tests/sample-data/multiple_profile_credentials",
            "not_a_profile",
        );
        let result = provider.credentials();

        assert!(result.is_err());
        assert_eq!(result.err(), Some(CredentialsError::new("profile not found")));
    }

    #[test]
    fn profile_provider_profile_name() {
       let mut provider = ProfileProvider::new().unwrap();
       assert_eq!("default", provider.profile());
       provider.set_profile("foo");
       assert_eq!("foo", provider.profile());
    }

    #[test]
    fn existing_file_no_credentials() {
        let result = super::parse_credentials_file(Path::new("tests/sample-data/no_credentials"));
        assert_eq!(result.err(), Some(CredentialsError::new("No credentials found.")))
    }

    #[test]
    fn parse_credentials_bad_path() {
        let result = super::parse_credentials_file(Path::new("/bad/file/path"));
        assert_eq!(result.err(), Some(CredentialsError::new("Couldn\'t stat credentials file: [ \"/bad/file/path\" ]. Non existant, or no permission.")));
    }

    #[test]
    fn parse_credentials_directory_path() {
        let result = super::parse_credentials_file(Path::new("tests/"));
        assert_eq!(result.err(), Some(CredentialsError::new("Credentials file: [ \"tests/\" ] is not a file.")));
    }

    #[test]
    fn parse_credentials_invalid() {
        let result = super::parse_credentials_file(Path::new("tests/sample-data/invalid_profile_credentials"));
        assert_eq!(result.err(), Some(CredentialsError::new("Invalid AWS Config line: [ some_super_awesome_value = baz ]")));
    }
}
<commit_msg>Update profile.rs<commit_after>//! The Credentials Provider for Credentials stored in a profile inside of a Credentials file.

use regex::Regex;
use std::ascii::AsciiExt;
use std::collections::HashMap;
use std::env::{home_dir, var as env_var};
use std::fs;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::{Path, PathBuf};

use {AwsCredentials, CredentialsError, ProvideAwsCredentials, in_ten_minutes};

/// Provides AWS credentials from a profile in a credentials file.
#[derive(Clone, Debug)]
pub struct ProfileProvider {
    /// The File Path the Credentials File is located at.
    file_path: PathBuf,
    /// The Profile Path to parse out of the Credentials File.
    profile: String,
}

impl ProfileProvider {
    /// Create a new `ProfileProvider` for the default credentials file path and profile name.
    pub fn new() -> Result<ProfileProvider, CredentialsError> {
        let profile_location = match env_var("AWS_SHARED_CREDENTIALS_FILE") {
            Ok(path) => PathBuf::from(path),
            Err(_) => {
                match ProfileProvider::default_profile_location() {
                    Ok(path) => path,
                    Err(err) => return Err(err),
                }
            }
        };

        Ok(ProfileProvider {
            file_path: profile_location,
            profile: "default".to_owned(),
        })
    }

    /// Default credentials file location:
    /// `~/.aws/credentials` (Linux/Mac)
    /// `%USERPROFILE%\.aws\credentials` (Windows)
    fn default_profile_location() -> Result<PathBuf, CredentialsError> {
        match home_dir() {
            Some(home_path) => {
                let mut credentials_path = PathBuf::from(".aws");

                credentials_path.push("credentials");

                Ok(home_path.join(credentials_path))
            }
            None => Err(CredentialsError::new("The environment variable HOME must be set.")),
        }
    }

    /// Create a new `ProfileProvider` for the credentials file at the given path, using
    /// the given profile.
    pub fn with_configuration<F, P>(file_path: F, profile: P) -> ProfileProvider
    where F: Into<PathBuf>, P: Into<String> {
        ProfileProvider {
            file_path: file_path.into(),
            profile: profile.into(),
        }
    }

    /// Get a reference to the credentials file path.
    pub fn file_path(&self) -> &Path {
        self.file_path.as_ref()
    }

    /// Get a reference to the profile name.
    pub fn profile(&self) -> &str {
        &self.profile
    }

    /// Set the credentials file path.
    pub fn set_file_path<F>(&mut self, file_path: F) where F: Into<PathBuf> {
        self.file_path = file_path.into();
    }

    /// Set the profile name.
    pub fn set_profile<P>(&mut self, profile: P) where P: Into<String> {
        self.profile = profile.into();
    }
}

impl ProvideAwsCredentials for ProfileProvider {
    fn credentials(&self) -> Result<AwsCredentials, CredentialsError> {
        parse_credentials_file(self.file_path()).and_then(|mut profiles| {
            profiles.remove(self.profile()).ok_or_else(|| CredentialsError::new("profile not found"))
        })
    }
}

/// Parses a Credentials file into a Map of <ProfileName, AwsCredentials>
fn parse_credentials_file(file_path: &Path) -> Result<HashMap<String, AwsCredentials>, CredentialsError> {
    match fs::metadata(file_path) {
        Err(_) => return Err(
            CredentialsError::new(
                format!("Couldn't stat credentials file: [ {:?} ]. Non existant, or no permission.", file_path)
            )
        ),
        Ok(metadata) => {
            if !metadata.is_file() {
                return Err(CredentialsError::new(format!("Credentials file: [ {:?} ] is not a file.", file_path)));
            }
        }
    };

    let file = try!(File::open(file_path));

    let profile_regex = Regex::new(r"^\[([^\]]+)\]$").expect("Failed to compile regex");
    let mut profiles: HashMap<String, AwsCredentials> = HashMap::new();
    let mut access_key: Option<String> = None;
    let mut secret_key: Option<String> = None;
    let mut token: Option<String> = None;
    let mut profile_name: Option<String> = None;

    let file_lines = BufReader::new(&file);
    for (line_no, line) in file_lines.lines().enumerate() {
        let unwrapped_line: String = line.expect(&format!("Failed to read credentials file, line: {}", line_no));

        // skip empty lines
        if unwrapped_line.is_empty() {
            continue;
        }

        // skip comments
        if unwrapped_line.starts_with('#') {
            continue;
        }

        // handle the opening of named profile blocks
        if profile_regex.is_match(&unwrapped_line) {
            if profile_name.is_some() && access_key.is_some() && secret_key.is_some() {
                let creds = AwsCredentials::new(access_key.unwrap(), secret_key.unwrap(), token, in_ten_minutes());
                profiles.insert(profile_name.unwrap(), creds);
            }

            access_key = None;
            secret_key = None;
            token = None;

            let caps = profile_regex.captures(&unwrapped_line).unwrap();
            profile_name = Some(caps.get(1).unwrap().as_str().to_string());
            continue;
        }

        // otherwise look for key=value pairs we care about
        let lower_case_line = unwrapped_line.to_ascii_lowercase().to_string();

        if lower_case_line.contains("aws_access_key_id") &&
            access_key.is_none()
        {
            let v: Vec<&str> = unwrapped_line.split('=').collect();
            if !v.is_empty() {
                access_key = Some(v[1].trim_matches(' ').to_string());
            }
        } else if lower_case_line.contains("aws_secret_access_key") &&
            secret_key.is_none()
        {
            let v: Vec<&str> = unwrapped_line.split('=').collect();
            if !v.is_empty() {
                secret_key = Some(v[1].trim_matches(' ').to_string());
            }
        } else if lower_case_line.contains("aws_session_token") &&
            token.is_none()
        {
            let v: Vec<&str> = unwrapped_line.split('=').collect();
            if !v.is_empty() {
                token = Some(v[1].trim_matches(' ').to_string());
            }
        } else if lower_case_line.contains("aws_security_token")
        {
            if token.is_none() {
                let v: Vec<&str> = unwrapped_line.split('=').collect();
                if !v.is_empty() {
                    token = Some(v[1].trim_matches(' ').to_string());
                }
            }
        } else if lower_case_line.contains("region") ||
            lower_case_line.contains("output")
        {
            // Not Supported here, but valid according to: http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html#cli-config-files
            continue;
        } else {
            return Err(CredentialsError::new(format!("Invalid AWS Config line: [ {} ]", lower_case_line)))
        }

    }

    if profile_name.is_some() && access_key.is_some() && secret_key.is_some() {
        let creds = AwsCredentials::new(access_key.unwrap(), secret_key.unwrap(), token, in_ten_minutes());
        profiles.insert(profile_name.unwrap(), creds);
    }

    if profiles.is_empty() {
        return Err(CredentialsError::new("No credentials found."));
    }

    Ok(profiles)
}

#[cfg(test)]
mod tests {
    use std::env;
    use std::path::Path;

    use {CredentialsError, ProvideAwsCredentials};
    use super::*;

    #[test]
    fn parse_credentials_file_default_profile() {
        let result = super::parse_credentials_file(
            Path::new("tests/sample-data/default_profile_credentials")
        );
        assert!(result.is_ok());

        let profiles = result.ok().unwrap();
        assert_eq!(profiles.len(), 1);

        let default_profile = profiles.get("default").expect("No Default profile in default_profile_credentials");
        assert_eq!(default_profile.aws_access_key_id(), "foo");
        assert_eq!(default_profile.aws_secret_access_key(), "bar");
    }

    #[test]
    fn parse_credentials_file_multiple_profiles() {
        let result = super::parse_credentials_file(
            Path::new("tests/sample-data/multiple_profile_credentials")
        );
        assert!(result.is_ok());

        let profiles = result.ok().unwrap();
        assert_eq!(profiles.len(), 2);

        let foo_profile = profiles.get("foo").expect("No foo profile in multiple_profile_credentials");
        assert_eq!(foo_profile.aws_access_key_id(), "foo_access_key");
        assert_eq!(foo_profile.aws_secret_access_key(), "foo_secret_key");

        let bar_profile = profiles.get("bar").expect("No bar profile in multiple_profile_credentials");
        assert_eq!(bar_profile.aws_access_key_id(), "bar_access_key");
        assert_eq!(bar_profile.aws_secret_access_key(), "bar_secret_key");

    }

    #[test]
    fn parse_all_values_credentials_file() {
        let result = super::parse_credentials_file(
            Path::new("tests/sample-data/full_profile_credentials")
        );
        assert!(result.is_ok());

        let profiles = result.ok().unwrap();
        assert_eq!(profiles.len(), 1);

        let default_profile = profiles.get("default").expect("No default profile in full_profile_credentials");
        assert_eq!(default_profile.aws_access_key_id(), "foo");
        assert_eq!(default_profile.aws_secret_access_key(), "bar");
    }

    #[test]
    fn profile_provider_happy_path() {
        let provider = ProfileProvider::with_configuration(
            "tests/sample-data/multiple_profile_credentials",
            "foo",
        );
        let result = provider.credentials();

        assert!(result.is_ok());

        let creds = result.ok().unwrap();
        assert_eq!(creds.aws_access_key_id(), "foo_access_key");
        assert_eq!(creds.aws_secret_access_key(), "foo_secret_key");
    }

    #[test]
    fn profile_provider_via_environment_variable() {
        let credentials_path = "tests/sample-data/default_profile_credentials";
        env::set_var("AWS_SHARED_CREDENTIALS_FILE", credentials_path);
        let result = ProfileProvider::new();
        assert!(result.is_ok());
        let provider = result.unwrap();
        assert_eq!(provider.file_path().to_str().unwrap(), credentials_path);
        env::remove_var("AWS_SHARED_CREDENTIALS_FILE");
    }

    #[test]
    fn profile_provider_bad_profile() {
        let provider = ProfileProvider::with_configuration(
            "tests/sample-data/multiple_profile_credentials",
            "not_a_profile",
        );
        let result = provider.credentials();

        assert!(result.is_err());
        assert_eq!(result.err(), Some(CredentialsError::new("profile not found")));
    }

    #[test]
    fn profile_provider_profile_name() {
       let mut provider = ProfileProvider::new().unwrap();
       assert_eq!("default", provider.profile());
       provider.set_profile("foo");
       assert_eq!("foo", provider.profile());
    }

    #[test]
    fn existing_file_no_credentials() {
        let result = super::parse_credentials_file(Path::new("tests/sample-data/no_credentials"));
        assert_eq!(result.err(), Some(CredentialsError::new("No credentials found.")))
    }

    #[test]
    fn parse_credentials_bad_path() {
        let result = super::parse_credentials_file(Path::new("/bad/file/path"));
        assert_eq!(result.err(), Some(CredentialsError::new("Couldn\'t stat credentials file: [ \"/bad/file/path\" ]. Non existant, or no permission.")));
    }

    #[test]
    fn parse_credentials_directory_path() {
        let result = super::parse_credentials_file(Path::new("tests/"));
        assert_eq!(result.err(), Some(CredentialsError::new("Credentials file: [ \"tests/\" ] is not a file.")));
    }

    #[test]
    fn parse_credentials_invalid() {
        let result = super::parse_credentials_file(Path::new("tests/sample-data/invalid_profile_credentials"));
        assert_eq!(result.err(), Some(CredentialsError::new("Invalid AWS Config line: [ some_super_awesome_value = baz ]")));
    }
}
<|endoftext|>


<commit_before>use std::{
    fs,
    path::{Path, PathBuf},
};

use binary_poetry::PoetryBinary;
use binary_python::{BinaryTrait, PythonBinary};
use buildpack::{
    eyre::{self, bail},
    fs_utils::{copy_dir_all, symlink_dir, symlink_file},
    libcnb::{
        self,
        build::{BuildContext, BuildResult, BuildResultBuilder},
        data::{build_plan::BuildPlan, layer_content_metadata::LayerTypes, layer_name},
        detect::{DetectContext, DetectResult, DetectResultBuilder},
        generic::{GenericMetadata, GenericPlatform},
        layer::{Layer, LayerResult, LayerResultBuilder},
        Buildpack,
    },
    platform_is_stencila, toml, BuildpackTrait,
};

pub struct PythonBuildpack;

impl BuildpackTrait for PythonBuildpack {
    fn toml() -> &'static str {
        include_str!("../buildpack.toml")
    }
}

const POETRY_LOCK: &str = "poetry.lock";
const PYPROJECT_TOML: &str = "pyproject.toml";
const REQUIREMENTS_TXT: &str = "requirements.txt";
const RUNTIME_TXT: &str = "runtime.txt";
const TOOL_VERSIONS: &str = ".tool-versions";

const POETRY_INSTALL: &str = "install";
const POETRY_ADDREQ: &str = "addreq";

impl Buildpack for PythonBuildpack {
    type Platform = GenericPlatform;
    type Metadata = GenericMetadata;
    type Error = eyre::Report;

    fn detect(&self, _context: DetectContext<Self>) -> libcnb::Result<DetectResult, Self::Error> {
        // Read `.tool-versions` for Python version
        let tool_versions = Self::tool_versions();

        // Read `pyproject.toml` for Python version and packages
        let pyproject_toml = fs::read_to_string(PYPROJECT_TOML)
            .ok()
            .and_then(|json| toml::from_str::<toml::Value>(&json).ok());

        // May read `runtime.txt` for Python version
        let runtime_txt = PathBuf::from(RUNTIME_TXT);

        // Detect `poetry.lock`
        let poetry_lock = PathBuf::from(POETRY_LOCK);

        // Detect `requirements.txt`
        let requirements_txt = PathBuf::from(REQUIREMENTS_TXT);

        // Fail early
        if !(tool_versions.contains_key("python")
            || pyproject_toml.is_some()
            || runtime_txt.exists()
            || poetry_lock.exists()
            || requirements_txt.exists()
            || Self::any_exist(&["main.py", "index.py"]))
        {
            return DetectResultBuilder::fail().build();
        }

        let mut requires = Vec::new();
        let mut provides = Vec::new();

        // Resolve Python version from `.tool-versions`, `runtime.txt` or `pyproject.toml`
        let (version, source) = if let Some(version) = tool_versions.get("python") {
            (version.to_string(), TOOL_VERSIONS)
        } else if let Some(semver_req) = pyproject_toml
            .as_ref()
            .and_then(|project| project.get("tool"))
            .and_then(|tool| tool.get("poetry"))
            .and_then(|poetry| poetry.get("dependencies"))
            .and_then(|dependencies| dependencies.get("python"))
            .and_then(|semver_req| semver_req.as_str())
        {
            (semver_req.to_string(), PYPROJECT_TOML)
        } else if let Some(version) = fs::read_to_string(runtime_txt).ok().and_then(|content| {
            content
                .trim()
                .strip_prefix("python-")
                .map(|version| version.to_string())
        }) {
            (version, RUNTIME_TXT)
        } else {
            ("".to_string(), "")
        };

        // Require and provide Python
        let (require, provide) = Self::require_and_provide(
            format!("python {}", version).trim(),
            source,
            format!("Install Python {}", version).trim(),
        );
        requires.push(require);
        provides.push(provide);

        // Determine how PyPI packages are to be installed
        if pyproject_toml.is_some() || poetry_lock.exists() {
            let (require, provide) = Self::require_and_provide(
                ["poetry ", POETRY_INSTALL].concat(),
                if poetry_lock.exists() {
                    POETRY_LOCK
                } else {
                    PYPROJECT_TOML
                },
                "Install PyPI packages using `poetry install`",
            );
            requires.push(require);
            provides.push(provide);
        } else if requirements_txt.exists() {
            let (require, provide) = Self::require_and_provide(
                ["poetry ", POETRY_ADDREQ].concat(),
                REQUIREMENTS_TXT,
                "Install PyPI packages using `poetry add`",
            );
            requires.push(require);
            provides.push(provide);
        }

        let mut build_plan = BuildPlan::new();
        build_plan.requires = requires;
        build_plan.provides = provides;
        DetectResultBuilder::pass().build_plan(build_plan).build()
    }

    fn build(&self, context: BuildContext<Self>) -> libcnb::Result<BuildResult, Self::Error> {
        let entries: Vec<String> = context
            .buildpack_plan
            .entries
            .iter()
            .map(|entry| entry.name.clone())
            .collect();

        for entry in entries {
            let (name, args) = Self::split_entry_name(&entry);
            match name.as_str() {
                "python" => {
                    context.handle_layer(layer_name!("python"), PythonLayer::new(args))?;
                }
                "poetry" => {
                    context.handle_layer(layer_name!("poetry"), PoetryLayer::new(args))?;
                }
                _ => (),
            };
        }

        BuildResultBuilder::new().build()
    }
}

struct PythonLayer {
    /// The semver requirement for the Python binary
    requirement: String,
}

impl PythonLayer {
    fn new(args: Vec<String>) -> Self {
        PythonLayer {
            // Join args with commas because semver requirement parser expects it to be so
            requirement: args.join(",").trim().to_string(),
        }
    }
}

impl Layer for PythonLayer {
    type Buildpack = PythonBuildpack;
    type Metadata = GenericMetadata;

    fn types(&self) -> LayerTypes {
        LayerTypes {
            build: true,
            launch: true,
            cache: true,
        }
    }

    fn create(
        &self,
        context: &BuildContext<Self::Buildpack>,
        layer_path: &Path,
    ) -> Result<LayerResult<Self::Metadata>, eyre::Report> {
        // Ensure a version meeting the semver is installed
        let requirement = if self.requirement.is_empty() {
            None
        } else {
            Some(self.requirement.clone())
        };
        let python = PythonBinary {}.require_sync(requirement, true)?;

        // Symlink/copy the installation into the layer
        let dest = layer_path.join(python.version()?);
        if platform_is_stencila(&context.platform) {
            if python.is_stencila_install() {
                let source = python.grandparent()?;
                symlink_dir(source, &dest)?;
            } else {
                fs::create_dir_all(&dest)?;
                symlink_file(python.path, dest.join(python.name))?;
            }
        } else {
            #[allow(clippy::collapsible_else_if)]
            if python.is_stencila_install() {
                let source = python.grandparent()?;
                copy_dir_all(source, &dest)?;
            } else {
                bail!("Only able to build `python` layer if it has been installed by Stencila")
            }
        }

        LayerResultBuilder::new(GenericMetadata::default()).build()
    }
}

struct PoetryLayer {
    arg: String,
}

impl PoetryLayer {
    fn new(args: Vec<String>) -> Self {
        PoetryLayer {
            arg: args.first().cloned().unwrap_or_default(),
        }
    }
}

impl Layer for PoetryLayer {
    type Buildpack = PythonBuildpack;
    type Metadata = GenericMetadata;

    fn types(&self) -> LayerTypes {
        // Layer is available at build time and is cached but is not needed for
        // launch time because packages are installed into the `venv` of the
        // working directory
        LayerTypes {
            build: true,
            launch: false,
            cache: true,
        }
    }

    fn create(
        &self,
        context: &BuildContext<Self::Buildpack>,
        layer_path: &Path,
    ) -> Result<LayerResult<Self::Metadata>, eyre::Report> {
        // Require Poetry
        let mut poetry = PoetryBinary {}.require_sync(Some(">=1".to_string()), true)?;

        // If this is not a local build then make the layer the Poetry cache
        if !platform_is_stencila(&context.platform) {
            let cache_dir = layer_path.canonicalize()?;
            poetry.envs(&[("POETRY_CACHE_DIR", cache_dir.as_os_str())]);
        }

        // Do the install
        match self.arg.as_str() {
            POETRY_INSTALL => {
                poetry.run_sync(&["install"])?;
            }
            POETRY_ADDREQ => {
                // Poetry requires that `pyproject.toml` file exists for `add` to work
                // so we create one as minimal as possible. A relatively high lower bound
                // for Python is chosen because some packages will fail to install if they
                // require something higher.
                // TODO: Make the version, the version that is installed by this buildpack
                let pyproject_toml = PathBuf::from(PYPROJECT_TOML);
                if !pyproject_toml.exists() {
                    let name = context
                        .app_dir
                        .file_name()
                        .map(|name| name.to_string_lossy().to_string())
                        .unwrap_or_else(|| "temp".to_string());
                    let toml = format!(
                        r#"[tool.poetry]
name = "{}"
description = ""
version = "0.1.0"
authors = []

[tool.poetry.dependencies]
python = ">=3.8"
"#,
                        name
                    );
                    fs::write(pyproject_toml, toml)?;
                }
                for line in fs::read_to_string(REQUIREMENTS_TXT)?.lines() {
                    poetry.run_sync(&["add", line])?;
                }
            }
            _ => bail!("Unhandled arg: {}", self.arg),
        }

        LayerResultBuilder::new(GenericMetadata::default()).build()
    }
}
<commit_msg>fix(Python buildpack): Install into a `.venv` in the working directory<commit_after>use std::{
    ffi::OsString,
    fs,
    path::{Path, PathBuf},
};

use binary_poetry::PoetryBinary;
use binary_python::{BinaryTrait, PythonBinary};
use buildpack::{
    eyre::{self, bail},
    fs_utils::{copy_dir_all, symlink_dir, symlink_file},
    libcnb::{
        self,
        build::{BuildContext, BuildResult, BuildResultBuilder},
        data::{build_plan::BuildPlan, layer_content_metadata::LayerTypes, layer_name},
        detect::{DetectContext, DetectResult, DetectResultBuilder},
        generic::{GenericMetadata, GenericPlatform},
        layer::{Layer, LayerResult, LayerResultBuilder},
        Buildpack,
    },
    platform_is_stencila, toml, BuildpackTrait,
};

pub struct PythonBuildpack;

impl BuildpackTrait for PythonBuildpack {
    fn toml() -> &'static str {
        include_str!("../buildpack.toml")
    }
}

const POETRY_LOCK: &str = "poetry.lock";
const PYPROJECT_TOML: &str = "pyproject.toml";
const REQUIREMENTS_TXT: &str = "requirements.txt";
const RUNTIME_TXT: &str = "runtime.txt";
const TOOL_VERSIONS: &str = ".tool-versions";

const POETRY_INSTALL: &str = "install";
const POETRY_ADDREQ: &str = "addreq";

impl Buildpack for PythonBuildpack {
    type Platform = GenericPlatform;
    type Metadata = GenericMetadata;
    type Error = eyre::Report;

    fn detect(&self, _context: DetectContext<Self>) -> libcnb::Result<DetectResult, Self::Error> {
        // Read `.tool-versions` for Python version
        let tool_versions = Self::tool_versions();

        // Read `pyproject.toml` for Python version and packages
        let pyproject_toml = fs::read_to_string(PYPROJECT_TOML)
            .ok()
            .and_then(|json| toml::from_str::<toml::Value>(&json).ok());

        // May read `runtime.txt` for Python version
        let runtime_txt = PathBuf::from(RUNTIME_TXT);

        // Detect `poetry.lock`
        let poetry_lock = PathBuf::from(POETRY_LOCK);

        // Detect `requirements.txt`
        let requirements_txt = PathBuf::from(REQUIREMENTS_TXT);

        // Fail early
        if !(tool_versions.contains_key("python")
            || pyproject_toml.is_some()
            || runtime_txt.exists()
            || poetry_lock.exists()
            || requirements_txt.exists()
            || Self::any_exist(&["main.py", "index.py"]))
        {
            return DetectResultBuilder::fail().build();
        }

        let mut requires = Vec::new();
        let mut provides = Vec::new();

        // Resolve Python version from `.tool-versions`, `runtime.txt` or `pyproject.toml`
        let (version, source) = if let Some(version) = tool_versions.get("python") {
            (version.to_string(), TOOL_VERSIONS)
        } else if let Some(semver_req) = pyproject_toml
            .as_ref()
            .and_then(|project| project.get("tool"))
            .and_then(|tool| tool.get("poetry"))
            .and_then(|poetry| poetry.get("dependencies"))
            .and_then(|dependencies| dependencies.get("python"))
            .and_then(|semver_req| semver_req.as_str())
        {
            (semver_req.to_string(), PYPROJECT_TOML)
        } else if let Some(version) = fs::read_to_string(runtime_txt).ok().and_then(|content| {
            content
                .trim()
                .strip_prefix("python-")
                .map(|version| version.to_string())
        }) {
            (version, RUNTIME_TXT)
        } else {
            ("".to_string(), "")
        };

        // Require and provide Python
        let (require, provide) = Self::require_and_provide(
            format!("python {}", version).trim(),
            source,
            format!("Install Python {}", version).trim(),
        );
        requires.push(require);
        provides.push(provide);

        // Determine how PyPI packages are to be installed
        if pyproject_toml.is_some() || poetry_lock.exists() {
            let (require, provide) = Self::require_and_provide(
                ["poetry ", POETRY_INSTALL].concat(),
                if poetry_lock.exists() {
                    POETRY_LOCK
                } else {
                    PYPROJECT_TOML
                },
                "Install PyPI packages using `poetry install`",
            );
            requires.push(require);
            provides.push(provide);
        } else if requirements_txt.exists() {
            let (require, provide) = Self::require_and_provide(
                ["poetry ", POETRY_ADDREQ].concat(),
                REQUIREMENTS_TXT,
                "Install PyPI packages using `poetry add`",
            );
            requires.push(require);
            provides.push(provide);
        }

        let mut build_plan = BuildPlan::new();
        build_plan.requires = requires;
        build_plan.provides = provides;
        DetectResultBuilder::pass().build_plan(build_plan).build()
    }

    fn build(&self, context: BuildContext<Self>) -> libcnb::Result<BuildResult, Self::Error> {
        let entries: Vec<String> = context
            .buildpack_plan
            .entries
            .iter()
            .map(|entry| entry.name.clone())
            .collect();

        for entry in entries {
            let (name, args) = Self::split_entry_name(&entry);
            match name.as_str() {
                "python" => {
                    context.handle_layer(layer_name!("python"), PythonLayer::new(args))?;
                }
                "poetry" => {
                    context.handle_layer(layer_name!("poetry"), PoetryLayer::new(args))?;
                }
                _ => (),
            };
        }

        BuildResultBuilder::new().build()
    }
}

struct PythonLayer {
    /// The semver requirement for the Python binary
    requirement: String,
}

impl PythonLayer {
    fn new(args: Vec<String>) -> Self {
        PythonLayer {
            // Join args with commas because semver requirement parser expects it to be so
            requirement: args.join(",").trim().to_string(),
        }
    }
}

impl Layer for PythonLayer {
    type Buildpack = PythonBuildpack;
    type Metadata = GenericMetadata;

    fn types(&self) -> LayerTypes {
        LayerTypes {
            build: true,
            launch: true,
            cache: true,
        }
    }

    fn create(
        &self,
        context: &BuildContext<Self::Buildpack>,
        layer_path: &Path,
    ) -> Result<LayerResult<Self::Metadata>, eyre::Report> {
        // Ensure a version meeting the semver is installed
        let requirement = if self.requirement.is_empty() {
            None
        } else {
            Some(self.requirement.clone())
        };
        let python = PythonBinary {}.require_sync(requirement, true)?;

        // Symlink/copy the installation into the layer
        let dest = layer_path.join(python.version()?);
        if platform_is_stencila(&context.platform) {
            if python.is_stencila_install() {
                let source = python.grandparent()?;
                symlink_dir(source, &dest)?;
            } else {
                fs::create_dir_all(&dest)?;
                symlink_file(python.path, dest.join(python.name))?;
            }
        } else {
            #[allow(clippy::collapsible_else_if)]
            if python.is_stencila_install() {
                let source = python.grandparent()?;
                copy_dir_all(source, &dest)?;
            } else {
                bail!("Only able to build `python` layer if it has been installed by Stencila")
            }
        }

        LayerResultBuilder::new(GenericMetadata::default()).build()
    }
}

struct PoetryLayer {
    arg: String,
}

impl PoetryLayer {
    fn new(args: Vec<String>) -> Self {
        PoetryLayer {
            arg: args.first().cloned().unwrap_or_default(),
        }
    }
}

impl Layer for PoetryLayer {
    type Buildpack = PythonBuildpack;
    type Metadata = GenericMetadata;

    fn types(&self) -> LayerTypes {
        // Layer is available at build time and is cached but is not needed for
        // launch time because packages are installed into the `venv` of the
        // working directory
        LayerTypes {
            build: true,
            launch: false,
            cache: true,
        }
    }

    fn create(
        &self,
        context: &BuildContext<Self::Buildpack>,
        layer_path: &Path,
    ) -> Result<LayerResult<Self::Metadata>, eyre::Report> {
        // Require Poetry
        let mut poetry = PoetryBinary {}.require_sync(Some(">=1".to_string()), true)?;

        // If this is not a local build then ensure that a `.venv` folder in the working directory
        // is used (instead of a system level one) and make the layer the Poetry cache (instead of a system level one)
        if !platform_is_stencila(&context.platform) {
            poetry.envs(&[
                ("POETRY_VIRTUALENVS_IN_PROJECT", OsString::from("true")),
                ("POETRY_CACHE_DIR", layer_path.canonicalize()?.into()),
            ]);
        }

        // Do the install
        match self.arg.as_str() {
            POETRY_INSTALL => {
                poetry.run_sync(&["install"])?;
            }
            POETRY_ADDREQ => {
                // Poetry requires that `pyproject.toml` file exists for `add` to work
                // so we create one as minimal as possible. A relatively high lower bound
                // for Python is chosen because some packages will fail to install if they
                // require something higher.
                // TODO: Make the version, the version that is installed by this buildpack
                let pyproject_toml = PathBuf::from(PYPROJECT_TOML);
                if !pyproject_toml.exists() {
                    let name = context
                        .app_dir
                        .file_name()
                        .map(|name| name.to_string_lossy().to_string())
                        .unwrap_or_else(|| "temp".to_string());
                    let toml = format!(
                        r#"[tool.poetry]
name = "{}"
description = ""
version = "0.1.0"
authors = []

[tool.poetry.dependencies]
python = ">=3.8"
"#,
                        name
                    );
                    fs::write(pyproject_toml, toml)?;
                }
                for line in fs::read_to_string(REQUIREMENTS_TXT)?.lines() {
                    poetry.run_sync(&["add", line])?;
                }
            }
            _ => bail!("Unhandled arg: {}", self.arg),
        }

        LayerResultBuilder::new(GenericMetadata::default()).build()
    }
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2018 Boucher, Antoni <bouanto@zoho.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

//! Communication protocol between the UI process and the web processes.

use std::collections::VecDeque;
use std::io::{Cursor, Seek};
use std::io::SeekFrom::Start;

use gio::{
    Error,
    InputStream,
    InputStreamExtManual,
    IOStream,
    IOStreamExt,
    OutputStream,
    OutputStreamExt,
};
use gio_ext::WriteAsync;
use glib::PRIORITY_DEFAULT;
use relm_state::{
    Relm,
    Update,
    UpdateNew,
};
use rmp_serialize::{Decoder, Encoder};
use rustc_serialize::{Decodable, Encodable};

use Message;
use self::Msg::*;
use self::SendMode::{Async, Sync};

const BUFFER_SIZE: usize = 1024;
const HEADER_SIZE: usize = 4;

#[allow(missing_docs)]
pub struct Model {
    buffer: Vec<u8>,
    current_msg_size: Option<u32>,
    queue: VecDeque<Message>,
    reader: Option<InputStream>,
    relm: Relm<PluginProtocol>,
    sending_message: bool,
    _stream: IOStream,
    writer: Option<OutputStream>,
}

/// The variant MsgRead can be listened by the caller in order to get notified when a message is
/// received.
#[allow(missing_docs)]
#[derive(Msg)]
pub enum Msg {
    MsgRead(Message),
    Read((Vec<u8>, usize)),
    IOError(Error),
    Write(Message),
    Wrote,
}

// NOTE: safe because the main loop is ran on the main thread.
unsafe impl Send for Msg {}

/// This plugin protocol uses MessagePack to communicate.
pub struct PluginProtocol {
    model: Model,
}

impl Update for PluginProtocol {
    type Model = Model;
    type ModelParam = IOStream;
    type Msg = Msg;

    fn model(relm: &Relm<Self>, stream: IOStream) -> Model {
        let reader = stream.get_input_stream();
        let writer = stream.get_output_stream();
        if let Some(ref reader) = reader {
            connect_async_full!(reader, read_async(vec![0; BUFFER_SIZE], PRIORITY_DEFAULT), relm, Read,
                |(_, error)| IOError(error));
        }
        Model {
            buffer: vec![],
            current_msg_size: None,
            queue: VecDeque::new(),
            reader,
            relm: relm.clone(),
            sending_message: false,
            // Keep the stream to avoid it being closed.
            _stream: stream,
            writer,
        }
    }

    fn update(&mut self, message: Msg) {
        match message {
            // To be listened to by the user.
            IOError(_) => (),
            // To be listened by the user.
            MsgRead(_) => (),
            Read((mut buffer, size)) => {
                buffer.truncate(size);
                self.model.buffer.extend(&buffer);
                let mut msg_read = true;
                while msg_read && !self.model.buffer.is_empty() {
                    msg_read = false;
                    if self.model.current_msg_size.is_none() {
                        self.model.current_msg_size = buf_to_u32(&self.model.buffer);
                        if self.model.buffer.len() <= HEADER_SIZE {
                            self.model.buffer = vec![];
                        }
                        else {
                            self.model.buffer = self.model.buffer[HEADER_SIZE..].to_vec(); // TODO: avoid the copy.
                        }
                    }
                    if size > 0 {
                        if let Some(ref reader) = self.model.reader {
                            // FIXME: don't read in a loop. Continue reading when after a message
                            // is read.
                            connect_async_full!(reader, read_async(vec![0; BUFFER_SIZE], PRIORITY_DEFAULT),
                                self.model.relm, Read, |(_, error)| IOError(error));
                        }
                    }
                    if let Some(size) = self.model.current_msg_size {
                        let size = size as usize;
                        if self.model.buffer.len() >= size {
                            {
                                let mut decoder = Decoder::new(&self.model.buffer[..size]);
                                match Decodable::decode(&mut decoder) {
                                    Ok(msg) => {
                                        msg_read = true;
                                        self.model.relm.stream().emit(MsgRead(msg));
                                        self.model.current_msg_size = None;
                                    },
                                    Err(error) => {
                                        error!("Failed to deserialize message. {:?}", error);
                                    },
                                }
                            }
                            self.model.buffer = self.model.buffer[size..].to_vec(); // TODO: avoid the copy.
                        }
                    }
                }
            },
            Write(msg) => {
                self.model.queue.push_back(msg);
                self.send();
            },
            Wrote => {
                self.model.sending_message = false;
                self.send();
            },
        }
    }
}

impl UpdateNew for PluginProtocol {
    fn new(_relm: &Relm<Self>, model: Model) -> Self {
        PluginProtocol {
            model,
        }
    }
}

impl PluginProtocol {
    fn send(&mut self) {
        if let Some(ref writer) = self.model.writer {
            // TODO: implement back-pressure.
            if !self.model.sending_message {
                if let Some(msg) = self.model.queue.pop_front() {
                    self.model.sending_message = true;
                    send(writer, msg, Async(&self.model.relm));
                }
            }
        }
        else {
            error!("No writer for protocol");
        }
    }
}

fn buf_to_u32(buffer: &[u8]) -> Option<u32> {
    if buffer.len() >= HEADER_SIZE {
        Some(buffer[0] as u32 | (buffer[1] as u32) << 8 | (buffer[2] as u32) << 16 | (buffer[3] as u32) << 24)
    }
    else {
        None
    }
}

fn write_u32(buffer: &mut [u8], size: u32) {
    if buffer.len() >= 4 {
        buffer[0] = (size & 0xFF) as u8;
        buffer[1] = ((size >> 8) & 0xFF) as u8;
        buffer[2] = ((size >> 16) & 0xFF) as u8;
        buffer[3] = ((size >> 24) & 0xFF) as u8;
    }
}

/// Whether to send a message asynchronously or synchronously.
pub enum SendMode<'a> {
    /// The message Wrote is sent on a successful write and IOError if there was an error.
    Async(&'a Relm<PluginProtocol>),
    /// The error is shown when there's one.
    Sync,
}

/// Send a `msg` of the `writer`.
pub fn send(writer: &OutputStream, msg: Message, send_mode: SendMode) {
    // Reserve space to write the size.
    let buffer = vec![0; HEADER_SIZE];
    let mut cursor = Cursor::new(buffer);
    if cursor.seek(Start(HEADER_SIZE as u64)).is_ok() {
        match msg.encode(&mut Encoder::new(&mut &mut cursor)) {
            Ok(_) => {
                let mut buffer = cursor.into_inner();
                let size = buffer.len() - HEADER_SIZE;
                write_u32(&mut buffer, size as u32);
                match send_mode {
                    Async(relm) => {
                        connect_async_full!(writer, write_all_async(buffer, PRIORITY_DEFAULT), relm,
                        |_| Wrote,
                        |(_, error)| IOError(error)
                        );
                    },
                    Sync =>
                        if let Err(error) = writer.write_all(&buffer, None) {
                            // TODO: send these errors back to the web extension?
                            error!("Send error: {}", error);
                        }
                }
            },
            Err(error) => error!("Failed to serialize message. {}", error),
        }
    }
    else {
        error!("Failed to seek buffer.");
    }
}
<commit_msg>Fix to avoid reading multiple times in a row<commit_after>/*
 * Copyright (c) 2018 Boucher, Antoni <bouanto@zoho.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

//! Communication protocol between the UI process and the web processes.

use std::collections::VecDeque;
use std::io::{Cursor, Seek};
use std::io::SeekFrom::Start;

use gio::{
    Error,
    InputStream,
    InputStreamExtManual,
    IOStream,
    IOStreamExt,
    OutputStream,
    OutputStreamExt,
};
use gio_ext::WriteAsync;
use glib::PRIORITY_DEFAULT;
use relm_state::{
    Relm,
    Update,
    UpdateNew,
};
use rmp_serialize::{Decoder, Encoder};
use rustc_serialize::{Decodable, Encodable};

use Message;
use self::Msg::*;
use self::SendMode::{Async, Sync};

const BUFFER_SIZE: usize = 1024;
const HEADER_SIZE: usize = 4;

#[allow(missing_docs)]
pub struct Model {
    buffer: Vec<u8>,
    current_msg_size: Option<u32>,
    queue: VecDeque<Message>,
    reader: Option<InputStream>,
    relm: Relm<PluginProtocol>,
    sending_message: bool,
    _stream: IOStream,
    writer: Option<OutputStream>,
}

/// The variant MsgRead can be listened by the caller in order to get notified when a message is
/// received.
#[allow(missing_docs)]
#[derive(Msg)]
pub enum Msg {
    MsgRead(Message),
    Read((Vec<u8>, usize)),
    IOError(Error),
    Write(Message),
    Wrote,
}

// NOTE: safe because the main loop is ran on the main thread.
unsafe impl Send for Msg {}

/// This plugin protocol uses MessagePack to communicate.
pub struct PluginProtocol {
    model: Model,
}

impl Update for PluginProtocol {
    type Model = Model;
    type ModelParam = IOStream;
    type Msg = Msg;

    fn model(relm: &Relm<Self>, stream: IOStream) -> Model {
        let reader = stream.get_input_stream();
        let writer = stream.get_output_stream();
        if let Some(ref reader) = reader {
            connect_async_full!(reader, read_async(vec![0; BUFFER_SIZE], PRIORITY_DEFAULT), relm, Read,
                |(_, error)| IOError(error));
        }
        Model {
            buffer: vec![],
            current_msg_size: None,
            queue: VecDeque::new(),
            reader,
            relm: relm.clone(),
            sending_message: false,
            // Keep the stream to avoid it being closed.
            _stream: stream,
            writer,
        }
    }

    fn update(&mut self, message: Msg) {
        match message {
            // To be listened to by the user.
            IOError(_) => (),
            // To be listened by the user.
            MsgRead(_) => (),
            Read((mut buffer, size)) => {
                buffer.truncate(size);
                self.model.buffer.extend(&buffer);
                let mut msg_read = true;
                while msg_read && !self.model.buffer.is_empty() {
                    msg_read = false;
                    if self.model.current_msg_size.is_none() {
                        self.model.current_msg_size = buf_to_u32(&self.model.buffer);
                        if self.model.buffer.len() <= HEADER_SIZE {
                            self.model.buffer = vec![];
                        }
                        else {
                            self.model.buffer = self.model.buffer[HEADER_SIZE..].to_vec(); // TODO: avoid the copy.
                        }
                    }
                    if let Some(msg_size) = self.model.current_msg_size {
                        let msg_size = msg_size as usize;
                        if self.model.buffer.len() >= msg_size {
                            {
                                let mut decoder = Decoder::new(&self.model.buffer[..msg_size]);
                                match Decodable::decode(&mut decoder) {
                                    Ok(msg) => {
                                        self.model.relm.stream().emit(MsgRead(msg));
                                        self.model.current_msg_size = None;
                                    },
                                    Err(error) => {
                                        error!("Failed to deserialize message. {:?}", error);
                                    },
                                }
                            }
                            self.model.buffer = self.model.buffer[msg_size..].to_vec(); // TODO: avoid the copy.
                            msg_read = true;
                        }
                    }
                }
                if size > 0 {
                    if let Some(ref reader) = self.model.reader {
                        connect_async_full!(reader, read_async(vec![0; BUFFER_SIZE], PRIORITY_DEFAULT),
                        self.model.relm, Read, |(_, error)| IOError(error));
                    }
                }
            },
            Write(msg) => {
                self.model.queue.push_back(msg);
                self.send();
            },
            Wrote => {
                self.model.sending_message = false;
                self.send();
            },
        }
    }
}

impl UpdateNew for PluginProtocol {
    fn new(_relm: &Relm<Self>, model: Model) -> Self {
        PluginProtocol {
            model,
        }
    }
}

impl PluginProtocol {
    fn send(&mut self) {
        if let Some(ref writer) = self.model.writer {
            // TODO: implement back-pressure.
            if !self.model.sending_message {
                if let Some(msg) = self.model.queue.pop_front() {
                    self.model.sending_message = true;
                    send(writer, msg, Async(&self.model.relm));
                }
            }
        }
        else {
            error!("No writer for protocol");
        }
    }
}

fn buf_to_u32(buffer: &[u8]) -> Option<u32> {
    if buffer.len() >= HEADER_SIZE {
        Some(buffer[0] as u32 | (buffer[1] as u32) << 8 | (buffer[2] as u32) << 16 | (buffer[3] as u32) << 24)
    }
    else {
        None
    }
}

fn write_u32(buffer: &mut [u8], size: u32) {
    if buffer.len() >= 4 {
        buffer[0] = (size & 0xFF) as u8;
        buffer[1] = ((size >> 8) & 0xFF) as u8;
        buffer[2] = ((size >> 16) & 0xFF) as u8;
        buffer[3] = ((size >> 24) & 0xFF) as u8;
    }
}

/// Whether to send a message asynchronously or synchronously.
pub enum SendMode<'a> {
    /// The message Wrote is sent on a successful write and IOError if there was an error.
    Async(&'a Relm<PluginProtocol>),
    /// The error is shown when there's one.
    Sync,
}

/// Send a `msg` of the `writer`.
pub fn send(writer: &OutputStream, msg: Message, send_mode: SendMode) {
    // Reserve space to write the size.
    let buffer = vec![0; HEADER_SIZE];
    let mut cursor = Cursor::new(buffer);
    if cursor.seek(Start(HEADER_SIZE as u64)).is_ok() {
        match msg.encode(&mut Encoder::new(&mut &mut cursor)) {
            Ok(_) => {
                let mut buffer = cursor.into_inner();
                let size = buffer.len() - HEADER_SIZE;
                write_u32(&mut buffer, size as u32);
                match send_mode {
                    Async(relm) => {
                        connect_async_full!(writer, write_all_async(buffer, PRIORITY_DEFAULT), relm,
                        |_| Wrote,
                        |(_, error)| IOError(error)
                        );
                    },
                    Sync =>
                        if let Err(error) = writer.write_all(&buffer, None) {
                            // TODO: send these errors back to the web extension?
                            error!("Send error: {}", error);
                        }
                }
            },
            Err(error) => error!("Failed to serialize message. {}", error),
        }
    }
    else {
        error!("Failed to seek buffer.");
    }
}
<|endoftext|>


<commit_before>use crate::command_prelude::*;

use cargo::ops;

pub fn cli() -> App {
    subcommand("rustc")
        .setting(AppSettings::TrailingVarArg)
        .about("Compile a package, and pass extra options to the compiler")
        .arg(opt("quiet", "No output printed to stdout").short("q"))
        .arg(Arg::with_name("args").multiple(true).help("Rustc flags"))
        .arg_package("Package to build")
        .arg_jobs()
        .arg_targets_all(
            "Build only this package's library",
            "Build only the specified binary",
            "Build all binaries",
            "Build only the specified example",
            "Build all examples",
            "Build only the specified test target",
            "Build all tests",
            "Build only the specified bench target",
            "Build all benches",
            "Build all targets",
        )
        .arg_release("Build artifacts in release mode, with optimizations")
        .arg_profile("Build artifacts with the specified profile")
        .arg_features()
        .arg_target_triple("Target triple which compiles will be for")
        .arg_target_dir()
        .arg_manifest_path()
        .arg_message_format()
        .arg_unit_graph()
        .arg(
            opt(
                "--print-cfg",
                "Output the compiler configuration in JSON (unstable)",
            )
            .hidden(true),
        )
        .after_help("Run `cargo help rustc` for more detailed information.\n")
}

pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult {
    let ws = args.workspace(config)?;
    let mode = match args.value_of("profile") {
        Some("dev") | None => CompileMode::Build,
        Some("test") => CompileMode::Test,
        Some("bench") => CompileMode::Bench,
        Some("check") => CompileMode::Check { test: false },
        Some(mode) => {
            let err = anyhow::format_err!(
                "unknown profile: `{}`, use dev,
                                   test, or bench",
                mode
            );
            return Err(CliError::new(err, 101));
        }
    };
    let mut compile_opts = args.compile_options_for_single_package(
        config,
        mode,
        Some(&ws),
        ProfileChecking::Unchecked,
    )?;
    let target_args = values(args, "args");
    compile_opts.target_rustc_args = if target_args.is_empty() {
        None
    } else {
        Some(target_args)
    };
    if args.is_present("--print-cfg") {
        config.cli_unstable().fail_if_stable_opt("--print-cfg", 8923)?;
        ops::print_cfg(&ws, &compile_opts)?;
    } else {
        ops::compile(&ws, &compile_opts)?;
    }
    Ok(())
}
<commit_msg>Refactor to use constant for arg name<commit_after>use crate::command_prelude::*;

use cargo::ops;

const PRINT_CFG_ARG_NAME: &str = "print-cfg";

pub fn cli() -> App {
    subcommand("rustc")
        .setting(AppSettings::TrailingVarArg)
        .about("Compile a package, and pass extra options to the compiler")
        .arg(opt("quiet", "No output printed to stdout").short("q"))
        .arg(Arg::with_name("args").multiple(true).help("Rustc flags"))
        .arg_package("Package to build")
        .arg_jobs()
        .arg_targets_all(
            "Build only this package's library",
            "Build only the specified binary",
            "Build all binaries",
            "Build only the specified example",
            "Build all examples",
            "Build only the specified test target",
            "Build all tests",
            "Build only the specified bench target",
            "Build all benches",
            "Build all targets",
        )
        .arg_release("Build artifacts in release mode, with optimizations")
        .arg_profile("Build artifacts with the specified profile")
        .arg_features()
        .arg_target_triple("Target triple which compiles will be for")
        .arg_target_dir()
        .arg_manifest_path()
        .arg_message_format()
        .arg_unit_graph()
        .arg(
            opt(
                PRINT_CFG_ARG_NAME,
                "Output the compiler configuration in JSON (unstable)",
            )
            .hidden(true),
        )
        .after_help("Run `cargo help rustc` for more detailed information.\n")
}

pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult {
    let ws = args.workspace(config)?;
    let mode = match args.value_of("profile") {
        Some("dev") | None => CompileMode::Build,
        Some("test") => CompileMode::Test,
        Some("bench") => CompileMode::Bench,
        Some("check") => CompileMode::Check { test: false },
        Some(mode) => {
            let err = anyhow::format_err!(
                "unknown profile: `{}`, use dev,
                                   test, or bench",
                mode
            );
            return Err(CliError::new(err, 101));
        }
    };
    let mut compile_opts = args.compile_options_for_single_package(
        config,
        mode,
        Some(&ws),
        ProfileChecking::Unchecked,
    )?;
    let target_args = values(args, "args");
    compile_opts.target_rustc_args = if target_args.is_empty() {
        None
    } else {
        Some(target_args)
    };
    if args.is_present(PRINT_CFG_ARG_NAME) {
        config.cli_unstable().fail_if_stable_opt(PRINT_CFG_ARG_NAME, 8923)?;
        ops::print_cfg(&ws, &compile_opts)?;
    } else {
        ops::compile(&ws, &compile_opts)?;
    }
    Ok(())
}
<|endoftext|>


<commit_before>extern crate paasio;

/// test a few read scenarios
macro_rules! test_read {
    ($(#[$attr:meta])* $modname:ident ($input:expr, $len:expr)) => {
        mod $modname {
            use std::io::{Read, BufReader};
            use paasio::*;

            const CHUNK_SIZE: usize = 2;

            $(#[$attr])*
            #[test]
            fn test_read_passthrough() {
                let data = $input;
                let size = $len(&data);
                let mut reader = ReadStats::new(data);

                let mut buffer = Vec::with_capacity(size);
                let qty_read = reader.read_to_end(&mut buffer);

                assert!(qty_read.is_ok());
                assert_eq!(size, qty_read.unwrap());
                assert_eq!(size, buffer.len());
                // 2: first to read all the data, second to check that
                // there wasn't any more pending data which simply didn't
                // fit into the existing buffer
                assert_eq!(2, reader.reads());
                assert_eq!(size, reader.bytes_through());
            }

            $(#[$attr])*
            #[test]
            fn test_read_chunks() {
                let data = $input;
                let size = $len(&data);
                let mut reader = ReadStats::new(data);

                let mut buffer = [0_u8; CHUNK_SIZE];
                let mut chunks_read = 0;
                while reader.read(&mut buffer[..]).unwrap_or_else(|_| panic!("read failed at chunk {}", chunks_read+1)) > 0 {
                    chunks_read += 1;
                }

                assert_eq!(size / CHUNK_SIZE, chunks_read);
                // we read once more than the number of chunks, because the final
                // read returns 0 new bytes
                assert_eq!(1+chunks_read, reader.reads());
                assert_eq!(size, reader.bytes_through());
            }

            $(#[$attr])*
            #[test]
            fn test_read_buffered_chunks() {
                let data = $input;
                let size = $len(&data);
                let mut reader = BufReader::new(ReadStats::new(data));

                let mut buffer = [0_u8; CHUNK_SIZE];
                let mut chunks_read = 0;
                while reader.read(&mut buffer[..]).unwrap_or_else(|_| panic!("read failed at chunk {}", chunks_read+1)) > 0 {
                    chunks_read += 1;
                }

                assert_eq!(size / CHUNK_SIZE, chunks_read);
                // the BufReader should smooth out the reads, collecting into
                // a buffer and performing only two read operations:
                // the first collects everything into the buffer,
                // and the second ensures that no data remains
                assert_eq!(2, reader.get_ref().reads());
                assert_eq!(size, reader.get_ref().bytes_through());
            }
        }
    };
}

/// test a few write scenarios
macro_rules! test_write {
    ($(#[$attr:meta])* $modname:ident ($input:expr, $len:expr)) => {
        mod $modname {
            use std::io::{self, Write, BufWriter};
            use paasio::*;

            const CHUNK_SIZE: usize = 2;
            $(#[$attr])*
            #[test]
            fn test_write_passthrough() {
                let data = $input;
                let size = $len(&data);
                let mut writer = WriteStats::new(Vec::with_capacity(size));
                let written = writer.write(data);
                assert!(written.is_ok());
                assert_eq!(size, written.unwrap());
                assert_eq!(size, writer.bytes_through());
                assert_eq!(1, writer.writes());
                assert_eq!(data, writer.get_ref().as_slice());
            }

            $(#[$attr])*
            #[test]
            fn test_sink_oneshot() {
                let data = $input;
                let size = $len(&data);
                let mut writer = WriteStats::new(io::sink());
                let written = writer.write(data);
                assert!(written.is_ok());
                assert_eq!(size, written.unwrap());
                assert_eq!(size, writer.bytes_through());
                assert_eq!(1, writer.writes());
            }

            $(#[$attr])*
            #[test]
            fn test_sink_windowed() {
                let data = $input;
                let size = $len(&data);
                let mut writer = WriteStats::new(io::sink());

                let mut chunk_count = 0;
                for chunk in data.chunks(CHUNK_SIZE) {
                    chunk_count += 1;
                    let written = writer.write(chunk);
                    assert!(written.is_ok());
                    assert_eq!(CHUNK_SIZE, written.unwrap());
                }
                assert_eq!(size, writer.bytes_through());
                assert_eq!(chunk_count, writer.writes());
            }

            $(#[$attr])*
            #[test]
            fn test_sink_buffered_windowed() {
                let data = $input;
                let size = $len(&data);
                let mut writer = BufWriter::new(WriteStats::new(io::sink()));

                for chunk in data.chunks(CHUNK_SIZE) {
                    let written = writer.write(chunk);
                    assert!(written.is_ok());
                    assert_eq!(CHUNK_SIZE, written.unwrap());
                }
                // at this point, nothing should have yet been passed through to
                // our writer
                assert_eq!(0, writer.get_ref().bytes_through());
                assert_eq!(0, writer.get_ref().writes());

                // after flushing, everything should pass through in one go
                assert!(writer.flush().is_ok());
                assert_eq!(size, writer.get_ref().bytes_through());
                assert_eq!(1, writer.get_ref().writes());
            }
        }
    };
}

#[test]
fn test_create_stats() {
    let mut data: Vec<u8> = Vec::new();
    let _ = paasio::ReadStats::new(data.as_slice());
    let _ = paasio::WriteStats::new(data.as_mut_slice());
}

test_read!(#[ignore] read_string (
    "Twas brillig, and the slithy toves/Did gyre and gimble in the wabe:/All mimsy were the borogoves,/And the mome raths outgrabe.".as_bytes(),
    |d: &[u8]| d.len()
));
test_write!(#[ignore] write_string (
    "Beware the Jabberwock, my son!/The jaws that bite, the claws that catch!/Beware the Jubjub bird, and shun/The frumious Bandersnatch!".as_bytes(),
    |d: &[u8]| d.len()
));

test_read!(#[ignore]
read_byte_literal(
    &[1_u8, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144][..],
    |d: &[u8]| d.len()
));
test_write!(#[ignore]
write_byte_literal(
    &[
        2_u8, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61,
    ][..],
    |d: &[u8]| d.len()
));

test_read!(#[ignore]
read_file(
    ::std::fs::File::open("README.md").expect("readme must be present"),
    |f: &::std::fs::File| f.metadata().expect("metadata must be present").len() as usize
));
<commit_msg>paasio: ReadStats.get_ref method<commit_after>extern crate paasio;

/// test a few read scenarios
macro_rules! test_read {
    ($(#[$attr:meta])* $modname:ident ($input:expr, $len:expr)) => {
        mod $modname {
            use std::io::{Read, BufReader};
            use paasio::*;

            const CHUNK_SIZE: usize = 2;

            $(#[$attr])*
            #[test]
            fn test_read_passthrough() {
                let data = $input;
                let size = $len(&data);
                let mut reader = ReadStats::new(data);

                let mut buffer = Vec::with_capacity(size);
                let qty_read = reader.read_to_end(&mut buffer);

                assert!(qty_read.is_ok());
                assert_eq!(size, qty_read.unwrap());
                assert_eq!(size, buffer.len());
                // 2: first to read all the data, second to check that
                // there wasn't any more pending data which simply didn't
                // fit into the existing buffer
                assert_eq!(2, reader.reads());
                assert_eq!(size, reader.bytes_through());
            }

            $(#[$attr])*
            #[test]
            fn test_read_chunks() {
                let data = $input;
                let size = $len(&data);
                let mut reader = ReadStats::new(data);

                let mut buffer = [0_u8; CHUNK_SIZE];
                let mut chunks_read = 0;
                while reader.read(&mut buffer[..]).unwrap_or_else(|_| panic!("read failed at chunk {}", chunks_read+1)) > 0 {
                    chunks_read += 1;
                }

                assert_eq!(size / CHUNK_SIZE, chunks_read);
                // we read once more than the number of chunks, because the final
                // read returns 0 new bytes
                assert_eq!(1+chunks_read, reader.reads());
                assert_eq!(size, reader.bytes_through());
            }

            $(#[$attr])*
            #[test]
            fn test_read_buffered_chunks() {
                let data = $input;
                let size = $len(&data);
                let mut reader = BufReader::new(ReadStats::new(data));

                let mut buffer = [0_u8; CHUNK_SIZE];
                let mut chunks_read = 0;
                while reader.read(&mut buffer[..]).unwrap_or_else(|_| panic!("read failed at chunk {}", chunks_read+1)) > 0 {
                    chunks_read += 1;
                }

                assert_eq!(size / CHUNK_SIZE, chunks_read);
                // the BufReader should smooth out the reads, collecting into
                // a buffer and performing only two read operations:
                // the first collects everything into the buffer,
                // and the second ensures that no data remains
                assert_eq!(2, reader.get_ref().reads());
                assert_eq!(size, reader.get_ref().bytes_through());
            }
        }
    };
}

/// test a few write scenarios
macro_rules! test_write {
    ($(#[$attr:meta])* $modname:ident ($input:expr, $len:expr)) => {
        mod $modname {
            use std::io::{self, Write, BufWriter};
            use paasio::*;

            const CHUNK_SIZE: usize = 2;
            $(#[$attr])*
            #[test]
            fn test_write_passthrough() {
                let data = $input;
                let size = $len(&data);
                let mut writer = WriteStats::new(Vec::with_capacity(size));
                let written = writer.write(data);
                assert!(written.is_ok());
                assert_eq!(size, written.unwrap());
                assert_eq!(size, writer.bytes_through());
                assert_eq!(1, writer.writes());
                assert_eq!(data, writer.get_ref().as_slice());
            }

            $(#[$attr])*
            #[test]
            fn test_sink_oneshot() {
                let data = $input;
                let size = $len(&data);
                let mut writer = WriteStats::new(io::sink());
                let written = writer.write(data);
                assert!(written.is_ok());
                assert_eq!(size, written.unwrap());
                assert_eq!(size, writer.bytes_through());
                assert_eq!(1, writer.writes());
            }

            $(#[$attr])*
            #[test]
            fn test_sink_windowed() {
                let data = $input;
                let size = $len(&data);
                let mut writer = WriteStats::new(io::sink());

                let mut chunk_count = 0;
                for chunk in data.chunks(CHUNK_SIZE) {
                    chunk_count += 1;
                    let written = writer.write(chunk);
                    assert!(written.is_ok());
                    assert_eq!(CHUNK_SIZE, written.unwrap());
                }
                assert_eq!(size, writer.bytes_through());
                assert_eq!(chunk_count, writer.writes());
            }

            $(#[$attr])*
            #[test]
            fn test_sink_buffered_windowed() {
                let data = $input;
                let size = $len(&data);
                let mut writer = BufWriter::new(WriteStats::new(io::sink()));

                for chunk in data.chunks(CHUNK_SIZE) {
                    let written = writer.write(chunk);
                    assert!(written.is_ok());
                    assert_eq!(CHUNK_SIZE, written.unwrap());
                }
                // at this point, nothing should have yet been passed through to
                // our writer
                assert_eq!(0, writer.get_ref().bytes_through());
                assert_eq!(0, writer.get_ref().writes());

                // after flushing, everything should pass through in one go
                assert!(writer.flush().is_ok());
                assert_eq!(size, writer.get_ref().bytes_through());
                assert_eq!(1, writer.get_ref().writes());
            }
        }
    };
}

#[test]
fn test_create_stats() {
    let mut data: Vec<u8> = Vec::new();
    let _ = paasio::ReadStats::new(data.as_slice());
    let _ = paasio::WriteStats::new(data.as_mut_slice());
}

test_read!(#[ignore] read_string (
    "Twas brillig, and the slithy toves/Did gyre and gimble in the wabe:/All mimsy were the borogoves,/And the mome raths outgrabe.".as_bytes(),
    |d: &[u8]| d.len()
));
test_write!(#[ignore] write_string (
    "Beware the Jabberwock, my son!/The jaws that bite, the claws that catch!/Beware the Jubjub bird, and shun/The frumious Bandersnatch!".as_bytes(),
    |d: &[u8]| d.len()
));

test_read!(#[ignore]
read_byte_literal(
    &[1_u8, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144][..],
    |d: &[u8]| d.len()
));
test_write!(#[ignore]
write_byte_literal(
    &[
        2_u8, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61,
    ][..],
    |d: &[u8]| d.len()
));

test_read!(#[ignore]
read_file(
    ::std::fs::File::open("README.md").expect("readme must be present"),
    |f: &::std::fs::File| f.metadata().expect("metadata must be present").len() as usize
));

#[test]
#[ignore]
fn read_stats_by_ref_returns_wrapped_reader() {
    use paasio::ReadStats;

    let input =
        "Why, sometimes I've believed as many as six impossible things before breakfast".as_bytes();
    let reader = ReadStats::new(input);
    assert_eq!(reader.get_ref(), &input);
}
<|endoftext|>