<commit_before>//! A variant of `SortedMap` that preserves insertion order.

use std::hash::{Hash, Hasher};
use std::iter::FromIterator;

use crate::stable_hasher::{HashStable, StableHasher};
use rustc_index::vec::{Idx, IndexVec};

/// An indexed multi-map that preserves insertion order while permitting both *O*(log *n*) lookup of
/// an item by key and *O*(1) lookup by index.
///
/// This data structure is a hybrid of an [`IndexVec`] and a [`SortedMap`]. Like `IndexVec`,
/// `SortedIndexMultiMap` assigns a typed index to each item while preserving insertion order.
/// Like `SortedMap`, `SortedIndexMultiMap` has efficient lookup of items by key. However, this
/// is accomplished by sorting an array of item indices instead of the items themselves.
///
/// Unlike `SortedMap`, this data structure can hold multiple equivalent items at once, so the
/// `get_by_key` method and its variants return an iterator instead of an `Option`. Equivalent
/// items will be yielded in insertion order.
///
/// Unlike a general-purpose map like `BTreeSet` or `HashSet`, `SortedMap` and
/// `SortedIndexMultiMap` require *O*(*n*) time to insert a single item. This is because we may need
/// to insert into the middle of the sorted array. Users should avoid mutating this data structure
/// in-place.
///
/// [`SortedMap`]: super::SortedMap
#[derive(Clone, Debug)]
pub struct SortedIndexMultiMap<I: Idx, K, V> {
    /// The elements of the map in insertion order.
    items: IndexVec<I, (K, V)>,

    /// Indices of the items in the set, sorted by the item's key.
    idx_sorted_by_item_key: Vec<I>,
}

impl<I: Idx, K: Ord, V> SortedIndexMultiMap<I, K, V> {
    pub fn new() -> Self {
        SortedIndexMultiMap { items: IndexVec::new(), idx_sorted_by_item_key: Vec::new() }
    }

    pub fn len(&self) -> usize {
        self.items.len()
    }

    pub fn is_empty(&self) -> bool {
        self.items.is_empty()
    }

    /// Returns an iterator over the items in the map in insertion order.
    pub fn into_iter(self) -> impl DoubleEndedIterator<Item = (K, V)> {
        self.items.into_iter()
    }

    /// Returns an iterator over the items in the map in insertion order along with their indices.
    pub fn into_iter_enumerated(self) -> impl DoubleEndedIterator<Item = (I, (K, V))> {
        self.items.into_iter_enumerated()
    }

    /// Returns an iterator over the items in the map in insertion order.
    pub fn iter(&self) -> impl '_ + DoubleEndedIterator<Item = (&K, &V)> {
        self.items.iter().map(|(ref k, ref v)| (k, v))
    }

    /// Returns an iterator over the items in the map in insertion order along with their indices.
    pub fn iter_enumerated(&self) -> impl '_ + DoubleEndedIterator<Item = (I, (&K, &V))> {
        self.items.iter_enumerated().map(|(i, (ref k, ref v))| (i, (k, v)))
    }

    /// Returns the item in the map with the given index.
    pub fn get(&self, idx: I) -> Option<&(K, V)> {
        self.items.get(idx)
    }

    /// Returns an iterator over the items in the map that are equal to `key`.
    ///
    /// If there are multiple items that are equivalent to `key`, they will be yielded in
    /// insertion order.
    pub fn get_by_key(&'a self, key: K) -> impl 'a + Iterator<Item = &'a V> {
        self.get_by_key_enumerated(key).map(|(_, v)| v)
    }

    /// Returns an iterator over the items in the map that are equal to `key` along with their
    /// indices.
    ///
    /// If there are multiple items that are equivalent to `key`, they will be yielded in
    /// insertion order.
    pub fn get_by_key_enumerated(&'a self, key: K) -> impl '_ + Iterator<Item = (I, &V)> {
        let lower_bound = self.idx_sorted_by_item_key.partition_point(|&i| self.items[i].0 < key);
        self.idx_sorted_by_item_key[lower_bound..].iter().map_while(move |&i| {
            let (k, v) = &self.items[i];
            (k == &key).then_some((i, v))
        })
    }
}

impl<I: Idx, K: Eq, V: Eq> Eq for SortedIndexMultiMap<I, K, V> {}
impl<I: Idx, K: PartialEq, V: PartialEq> PartialEq for SortedIndexMultiMap<I, K, V> {
    fn eq(&self, other: &Self) -> bool {
        // No need to compare the sorted index. If the items are the same, the index will be too.
        self.items == other.items
    }
}

impl<I: Idx, K, V> Hash for SortedIndexMultiMap<I, K, V>
where
    K: Hash,
    V: Hash,
{
    fn hash<H: Hasher>(&self, hasher: &mut H) {
        self.items.hash(hasher)
    }
}
impl<I: Idx, K, V, C> HashStable<C> for SortedIndexMultiMap<I, K, V>
where
    K: HashStable<C>,
    V: HashStable<C>,
{
    fn hash_stable(&self, ctx: &mut C, hasher: &mut StableHasher) {
        self.items.hash_stable(ctx, hasher)
    }
}

impl<I: Idx, K: Ord, V> FromIterator<(K, V)> for SortedIndexMultiMap<I, K, V> {
    fn from_iter<J>(iter: J) -> Self
    where
        J: IntoIterator<Item = (K, V)>,
    {
        let items = IndexVec::from_iter(iter);
        let mut idx_sorted_by_item_key: Vec<_> = items.indices().collect();

        // `sort_by_key` is stable, so insertion order is preserved for duplicate items.
        idx_sorted_by_item_key.sort_by_key(|&idx| &items[idx].0);

        SortedIndexMultiMap { items, idx_sorted_by_item_key }
    }
}

impl<I: Idx, K, V> std::ops::Index<I> for SortedIndexMultiMap<I, K, V> {
    type Output = V;

    fn index(&self, idx: I) -> &Self::Output {
        &self.items[idx].1
    }
}

#[cfg(tests)]
mod tests;
<commit_msg>Rollup merge of #90787 - JohnTitor:inline-sorted-index-map, r=oli-obk<commit_after>//! A variant of `SortedMap` that preserves insertion order.

use std::hash::{Hash, Hasher};
use std::iter::FromIterator;

use crate::stable_hasher::{HashStable, StableHasher};
use rustc_index::vec::{Idx, IndexVec};

/// An indexed multi-map that preserves insertion order while permitting both *O*(log *n*) lookup of
/// an item by key and *O*(1) lookup by index.
///
/// This data structure is a hybrid of an [`IndexVec`] and a [`SortedMap`]. Like `IndexVec`,
/// `SortedIndexMultiMap` assigns a typed index to each item while preserving insertion order.
/// Like `SortedMap`, `SortedIndexMultiMap` has efficient lookup of items by key. However, this
/// is accomplished by sorting an array of item indices instead of the items themselves.
///
/// Unlike `SortedMap`, this data structure can hold multiple equivalent items at once, so the
/// `get_by_key` method and its variants return an iterator instead of an `Option`. Equivalent
/// items will be yielded in insertion order.
///
/// Unlike a general-purpose map like `BTreeSet` or `HashSet`, `SortedMap` and
/// `SortedIndexMultiMap` require *O*(*n*) time to insert a single item. This is because we may need
/// to insert into the middle of the sorted array. Users should avoid mutating this data structure
/// in-place.
///
/// [`SortedMap`]: super::SortedMap
#[derive(Clone, Debug)]
pub struct SortedIndexMultiMap<I: Idx, K, V> {
    /// The elements of the map in insertion order.
    items: IndexVec<I, (K, V)>,

    /// Indices of the items in the set, sorted by the item's key.
    idx_sorted_by_item_key: Vec<I>,
}

impl<I: Idx, K: Ord, V> SortedIndexMultiMap<I, K, V> {
    #[inline]
    pub fn new() -> Self {
        SortedIndexMultiMap { items: IndexVec::new(), idx_sorted_by_item_key: Vec::new() }
    }

    #[inline]
    pub fn len(&self) -> usize {
        self.items.len()
    }

    #[inline]
    pub fn is_empty(&self) -> bool {
        self.items.is_empty()
    }

    /// Returns an iterator over the items in the map in insertion order.
    #[inline]
    pub fn into_iter(self) -> impl DoubleEndedIterator<Item = (K, V)> {
        self.items.into_iter()
    }

    /// Returns an iterator over the items in the map in insertion order along with their indices.
    #[inline]
    pub fn into_iter_enumerated(self) -> impl DoubleEndedIterator<Item = (I, (K, V))> {
        self.items.into_iter_enumerated()
    }

    /// Returns an iterator over the items in the map in insertion order.
    #[inline]
    pub fn iter(&self) -> impl '_ + DoubleEndedIterator<Item = (&K, &V)> {
        self.items.iter().map(|(ref k, ref v)| (k, v))
    }

    /// Returns an iterator over the items in the map in insertion order along with their indices.
    #[inline]
    pub fn iter_enumerated(&self) -> impl '_ + DoubleEndedIterator<Item = (I, (&K, &V))> {
        self.items.iter_enumerated().map(|(i, (ref k, ref v))| (i, (k, v)))
    }

    /// Returns the item in the map with the given index.
    #[inline]
    pub fn get(&self, idx: I) -> Option<&(K, V)> {
        self.items.get(idx)
    }

    /// Returns an iterator over the items in the map that are equal to `key`.
    ///
    /// If there are multiple items that are equivalent to `key`, they will be yielded in
    /// insertion order.
    #[inline]
    pub fn get_by_key(&'a self, key: K) -> impl 'a + Iterator<Item = &'a V> {
        self.get_by_key_enumerated(key).map(|(_, v)| v)
    }

    /// Returns an iterator over the items in the map that are equal to `key` along with their
    /// indices.
    ///
    /// If there are multiple items that are equivalent to `key`, they will be yielded in
    /// insertion order.
    #[inline]
    pub fn get_by_key_enumerated(&'a self, key: K) -> impl '_ + Iterator<Item = (I, &V)> {
        let lower_bound = self.idx_sorted_by_item_key.partition_point(|&i| self.items[i].0 < key);
        self.idx_sorted_by_item_key[lower_bound..].iter().map_while(move |&i| {
            let (k, v) = &self.items[i];
            (k == &key).then_some((i, v))
        })
    }
}

impl<I: Idx, K: Eq, V: Eq> Eq for SortedIndexMultiMap<I, K, V> {}
impl<I: Idx, K: PartialEq, V: PartialEq> PartialEq for SortedIndexMultiMap<I, K, V> {
    fn eq(&self, other: &Self) -> bool {
        // No need to compare the sorted index. If the items are the same, the index will be too.
        self.items == other.items
    }
}

impl<I: Idx, K, V> Hash for SortedIndexMultiMap<I, K, V>
where
    K: Hash,
    V: Hash,
{
    fn hash<H: Hasher>(&self, hasher: &mut H) {
        self.items.hash(hasher)
    }
}
impl<I: Idx, K, V, C> HashStable<C> for SortedIndexMultiMap<I, K, V>
where
    K: HashStable<C>,
    V: HashStable<C>,
{
    fn hash_stable(&self, ctx: &mut C, hasher: &mut StableHasher) {
        self.items.hash_stable(ctx, hasher)
    }
}

impl<I: Idx, K: Ord, V> FromIterator<(K, V)> for SortedIndexMultiMap<I, K, V> {
    fn from_iter<J>(iter: J) -> Self
    where
        J: IntoIterator<Item = (K, V)>,
    {
        let items = IndexVec::from_iter(iter);
        let mut idx_sorted_by_item_key: Vec<_> = items.indices().collect();

        // `sort_by_key` is stable, so insertion order is preserved for duplicate items.
        idx_sorted_by_item_key.sort_by_key(|&idx| &items[idx].0);

        SortedIndexMultiMap { items, idx_sorted_by_item_key }
    }
}

impl<I: Idx, K, V> std::ops::Index<I> for SortedIndexMultiMap<I, K, V> {
    type Output = V;

    fn index(&self, idx: I) -> &Self::Output {
        &self.items[idx].1
    }
}

#[cfg(tests)]
mod tests;
<|endoftext|>


<commit_before>//! Workspace information we get from cargo consists of two pieces. The first is
//! the output of `cargo metadata`. The second is the output of running
//! `build.rs` files (`OUT_DIR` env var, extra cfg flags) and compiling proc
//! macro.
//!
//! This module implements this second part. We use "build script" terminology
//! here, but it covers procedural macros as well.

use std::{
    path::PathBuf,
    process::{Command, Stdio},
};

use anyhow::Result;
use cargo_metadata::{camino::Utf8Path, Message};
use la_arena::ArenaMap;
use paths::AbsPathBuf;
use rustc_hash::FxHashMap;
use serde::Deserialize;

use crate::{cfg_flag::CfgFlag, CargoConfig, CargoWorkspace, Package};

#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct WorkspaceBuildScripts {
    pub(crate) outputs: ArenaMap<Package, BuildScriptOutput>,
    error: Option<String>,
}

#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub(crate) struct BuildScriptOutput {
    /// List of config flags defined by this package's build script.
    pub(crate) cfgs: Vec<CfgFlag>,
    /// List of cargo-related environment variables with their value.
    ///
    /// If the package has a build script which defines environment variables,
    /// they can also be found here.
    pub(crate) envs: Vec<(String, String)>,
    /// Directory where a build script might place its output.
    pub(crate) out_dir: Option<AbsPathBuf>,
    /// Path to the proc-macro library file if this package exposes proc-macros.
    pub(crate) proc_macro_dylib_path: Option<AbsPathBuf>,
}

impl WorkspaceBuildScripts {
    pub fn run(
        config: &CargoConfig,
        workspace: &CargoWorkspace,
        progress: &dyn Fn(String),
    ) -> Result<WorkspaceBuildScripts> {
        let mut cmd = Command::new(toolchain::cargo());

        if config.wrap_rustc_in_build_scripts {
            // Setup RUSTC_WRAPPER to point to `rust-analyzer` binary itself. We use
            // that to compile only proc macros and build scripts during the initial
            // `cargo check`.
            let myself = std::env::current_exe()?;
            cmd.env("RUSTC_WRAPPER", myself);
            cmd.env("RA_RUSTC_WRAPPER", "1");
        }
        cmd.current_dir(workspace.workspace_root());
        cmd.args(&["check", "--quiet", "--workspace", "--message-format=json"]);

        // --all-targets includes tests, benches and examples in addition to the
        // default lib and bins. This is an independent concept from the --targets
        // flag below.
        cmd.arg("--all-targets");

        if let Some(target) = &config.target {
            cmd.args(&["--target", target]);
        }

        if config.all_features {
            cmd.arg("--all-features");
        } else {
            if config.no_default_features {
                cmd.arg("--no-default-features");
            }
            if !config.features.is_empty() {
                cmd.arg("--features");
                cmd.arg(config.features.join(" "));
            }
        }

        cmd.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::null());

        let mut res = WorkspaceBuildScripts::default();
        // NB: Cargo.toml could have been modified between `cargo metadata` and
        // `cargo check`. We shouldn't assume that package ids we see here are
        // exactly those from `config`.
        let mut by_id: FxHashMap<String, Package> = FxHashMap::default();

        for package in workspace.packages() {
            res.outputs.insert(package, BuildScriptOutput::default());
            by_id.insert(workspace[package].id.clone(), package);
        }

        let mut callback_err = None;
        let mut stderr = String::new();
        let output = stdx::process::streaming_output(
            cmd,
            &mut |line| {
                if callback_err.is_some() {
                    return;
                }

                // Copy-pasted from existing cargo_metadata. It seems like we
                // should be using sered_stacker here?
                let mut deserializer = serde_json::Deserializer::from_str(line);
                deserializer.disable_recursion_limit();
                let message = Message::deserialize(&mut deserializer)
                    .unwrap_or_else(|_| Message::TextLine(line.to_string()));

                match message {
                    Message::BuildScriptExecuted(message) => {
                        let package = match by_id.get(&message.package_id.repr) {
                            Some(it) => *it,
                            None => return,
                        };
                        let cfgs = {
                            let mut acc = Vec::new();
                            for cfg in message.cfgs {
                                match cfg.parse::<CfgFlag>() {
                                    Ok(it) => acc.push(it),
                                    Err(err) => {
                                        callback_err = Some(anyhow::format_err!(
                                            "invalid cfg from cargo-metadata: {}",
                                            err
                                        ));
                                        return;
                                    }
                                };
                            }
                            acc
                        };
                        let package_build_data = &mut res.outputs[package];
                        // cargo_metadata crate returns default (empty) path for
                        // older cargos, which is not absolute, so work around that.
                        if !message.out_dir.as_str().is_empty() {
                            let out_dir =
                                AbsPathBuf::assert(PathBuf::from(message.out_dir.into_os_string()));
                            package_build_data.out_dir = Some(out_dir);
                            package_build_data.cfgs = cfgs;
                        }

                        package_build_data.envs = message.env;
                    }
                    Message::CompilerArtifact(message) => {
                        let package = match by_id.get(&message.package_id.repr) {
                            Some(it) => *it,
                            None => return,
                        };

                        progress(format!("metadata {}", message.target.name));

                        if message.target.kind.iter().any(|k| k == "proc-macro") {
                            // Skip rmeta file
                            if let Some(filename) =
                                message.filenames.iter().find(|name| is_dylib(name))
                            {
                                let filename = AbsPathBuf::assert(PathBuf::from(&filename));
                                res.outputs[package].proc_macro_dylib_path = Some(filename);
                            }
                        }
                    }
                    Message::CompilerMessage(message) => {
                        progress(message.target.name);
                    }
                    Message::BuildFinished(_) => {}
                    Message::TextLine(_) => {}
                    _ => {}
                }
            },
            &mut |line| {
                stderr.push_str(line);
                stderr.push('\n');
            },
        )?;

        for package in workspace.packages() {
            let package_build_data = &mut res.outputs[package];
            // inject_cargo_env(package, package_build_data);
            if let Some(out_dir) = &package_build_data.out_dir {
                // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!()
                if let Some(out_dir) = out_dir.as_os_str().to_str().map(|s| s.to_owned()) {
                    package_build_data.envs.push(("OUT_DIR".to_string(), out_dir));
                }
            }
        }

        if !output.status.success() {
            if stderr.is_empty() {
                stderr = "cargo check failed".to_string();
            }
            res.error = Some(stderr)
        }

        Ok(res)
    }

    pub fn error(&self) -> Option<&str> {
        self.error.as_deref()
    }
}

// FIXME: File a better way to know if it is a dylib.
fn is_dylib(path: &Utf8Path) -> bool {
    match path.extension().map(|e| e.to_string().to_lowercase()) {
        None => false,
        Some(ext) => matches!(ext.as_str(), "dll" | "dylib" | "so"),
    }
}
<commit_msg>Reduce visibility of `WorkspaceBuildScripts::run`<commit_after>//! Workspace information we get from cargo consists of two pieces. The first is
//! the output of `cargo metadata`. The second is the output of running
//! `build.rs` files (`OUT_DIR` env var, extra cfg flags) and compiling proc
//! macro.
//!
//! This module implements this second part. We use "build script" terminology
//! here, but it covers procedural macros as well.

use std::{
    path::PathBuf,
    process::{Command, Stdio},
};

use anyhow::Result;
use cargo_metadata::{camino::Utf8Path, Message};
use la_arena::ArenaMap;
use paths::AbsPathBuf;
use rustc_hash::FxHashMap;
use serde::Deserialize;

use crate::{cfg_flag::CfgFlag, CargoConfig, CargoWorkspace, Package};

#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct WorkspaceBuildScripts {
    pub(crate) outputs: ArenaMap<Package, BuildScriptOutput>,
    error: Option<String>,
}

#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub(crate) struct BuildScriptOutput {
    /// List of config flags defined by this package's build script.
    pub(crate) cfgs: Vec<CfgFlag>,
    /// List of cargo-related environment variables with their value.
    ///
    /// If the package has a build script which defines environment variables,
    /// they can also be found here.
    pub(crate) envs: Vec<(String, String)>,
    /// Directory where a build script might place its output.
    pub(crate) out_dir: Option<AbsPathBuf>,
    /// Path to the proc-macro library file if this package exposes proc-macros.
    pub(crate) proc_macro_dylib_path: Option<AbsPathBuf>,
}

impl WorkspaceBuildScripts {
    pub(crate) fn run(
        config: &CargoConfig,
        workspace: &CargoWorkspace,
        progress: &dyn Fn(String),
    ) -> Result<WorkspaceBuildScripts> {
        let mut cmd = Command::new(toolchain::cargo());

        if config.wrap_rustc_in_build_scripts {
            // Setup RUSTC_WRAPPER to point to `rust-analyzer` binary itself. We use
            // that to compile only proc macros and build scripts during the initial
            // `cargo check`.
            let myself = std::env::current_exe()?;
            cmd.env("RUSTC_WRAPPER", myself);
            cmd.env("RA_RUSTC_WRAPPER", "1");
        }
        cmd.current_dir(workspace.workspace_root());
        cmd.args(&["check", "--quiet", "--workspace", "--message-format=json"]);

        // --all-targets includes tests, benches and examples in addition to the
        // default lib and bins. This is an independent concept from the --targets
        // flag below.
        cmd.arg("--all-targets");

        if let Some(target) = &config.target {
            cmd.args(&["--target", target]);
        }

        if config.all_features {
            cmd.arg("--all-features");
        } else {
            if config.no_default_features {
                cmd.arg("--no-default-features");
            }
            if !config.features.is_empty() {
                cmd.arg("--features");
                cmd.arg(config.features.join(" "));
            }
        }

        cmd.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::null());

        let mut res = WorkspaceBuildScripts::default();
        // NB: Cargo.toml could have been modified between `cargo metadata` and
        // `cargo check`. We shouldn't assume that package ids we see here are
        // exactly those from `config`.
        let mut by_id: FxHashMap<String, Package> = FxHashMap::default();

        for package in workspace.packages() {
            res.outputs.insert(package, BuildScriptOutput::default());
            by_id.insert(workspace[package].id.clone(), package);
        }

        let mut callback_err = None;
        let mut stderr = String::new();
        let output = stdx::process::streaming_output(
            cmd,
            &mut |line| {
                if callback_err.is_some() {
                    return;
                }

                // Copy-pasted from existing cargo_metadata. It seems like we
                // should be using sered_stacker here?
                let mut deserializer = serde_json::Deserializer::from_str(line);
                deserializer.disable_recursion_limit();
                let message = Message::deserialize(&mut deserializer)
                    .unwrap_or_else(|_| Message::TextLine(line.to_string()));

                match message {
                    Message::BuildScriptExecuted(message) => {
                        let package = match by_id.get(&message.package_id.repr) {
                            Some(it) => *it,
                            None => return,
                        };
                        let cfgs = {
                            let mut acc = Vec::new();
                            for cfg in message.cfgs {
                                match cfg.parse::<CfgFlag>() {
                                    Ok(it) => acc.push(it),
                                    Err(err) => {
                                        callback_err = Some(anyhow::format_err!(
                                            "invalid cfg from cargo-metadata: {}",
                                            err
                                        ));
                                        return;
                                    }
                                };
                            }
                            acc
                        };
                        let package_build_data = &mut res.outputs[package];
                        // cargo_metadata crate returns default (empty) path for
                        // older cargos, which is not absolute, so work around that.
                        if !message.out_dir.as_str().is_empty() {
                            let out_dir =
                                AbsPathBuf::assert(PathBuf::from(message.out_dir.into_os_string()));
                            package_build_data.out_dir = Some(out_dir);
                            package_build_data.cfgs = cfgs;
                        }

                        package_build_data.envs = message.env;
                    }
                    Message::CompilerArtifact(message) => {
                        let package = match by_id.get(&message.package_id.repr) {
                            Some(it) => *it,
                            None => return,
                        };

                        progress(format!("metadata {}", message.target.name));

                        if message.target.kind.iter().any(|k| k == "proc-macro") {
                            // Skip rmeta file
                            if let Some(filename) =
                                message.filenames.iter().find(|name| is_dylib(name))
                            {
                                let filename = AbsPathBuf::assert(PathBuf::from(&filename));
                                res.outputs[package].proc_macro_dylib_path = Some(filename);
                            }
                        }
                    }
                    Message::CompilerMessage(message) => {
                        progress(message.target.name);
                    }
                    Message::BuildFinished(_) => {}
                    Message::TextLine(_) => {}
                    _ => {}
                }
            },
            &mut |line| {
                stderr.push_str(line);
                stderr.push('\n');
            },
        )?;

        for package in workspace.packages() {
            let package_build_data = &mut res.outputs[package];
            // inject_cargo_env(package, package_build_data);
            if let Some(out_dir) = &package_build_data.out_dir {
                // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!()
                if let Some(out_dir) = out_dir.as_os_str().to_str().map(|s| s.to_owned()) {
                    package_build_data.envs.push(("OUT_DIR".to_string(), out_dir));
                }
            }
        }

        if !output.status.success() {
            if stderr.is_empty() {
                stderr = "cargo check failed".to_string();
            }
            res.error = Some(stderr)
        }

        Ok(res)
    }

    pub fn error(&self) -> Option<&str> {
        self.error.as_deref()
    }
}

// FIXME: File a better way to know if it is a dylib.
fn is_dylib(path: &Utf8Path) -> bool {
    match path.extension().map(|e| e.to_string().to_lowercase()) {
        None => false,
        Some(ext) => matches!(ext.as_str(), "dll" | "dylib" | "so"),
    }
}
<|endoftext|>


<commit_before>//! Handling of `preserveAspectRatio` values
//!
//! This module handles `preserveAspectRatio` values [per the SVG specification][spec].
//! We have an [`AspectRatio`] struct which encapsulates such a value.
//!
//! ```ignore
//! assert_eq!(
//!     AspectRatio::parse("xMidYMid", ()),
//!     Ok(AspectRatio {
//!         defer: false,
//!         align: Some(Align {
//!             x: X(Align1D::Mid),
//!             y: Y(Align1D::Mid),
//!             fit: FitMode::Meet,
//!         }),
//!     })
//! );
//! ```
//!
//! [`AspectRatio`]: struct.AspectRatio.html
//! [spec]: https://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute

use cssparser::Parser;
use error::*;
use parsers::Parse;
use parsers::ParseError;
use std::ops::Deref;

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct AspectRatio {
    defer: bool,
    align: Option<Align>,
}

impl Default for AspectRatio {
    fn default() -> AspectRatio {
        AspectRatio {
            defer: false,
            align: Some(Align::default()),
        }
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum FitMode {
    Meet,
    Slice,
}

impl Default for FitMode {
    fn default() -> FitMode {
        FitMode::Meet
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
struct Align {
    x: X,
    y: Y,
    fit: FitMode,
}

impl Default for Align {
    fn default() -> Align {
        Align {
            x: X(Align1D::Mid),
            y: Y(Align1D::Mid),
            fit: FitMode::default(),
        }
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum Align1D {
    Min,
    Mid,
    Max,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
struct X(Align1D);
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
struct Y(Align1D);

impl Deref for X {
    type Target = Align1D;

    fn deref(&self) -> &Align1D {
        &self.0
    }
}

impl Deref for Y {
    type Target = Align1D;

    fn deref(&self) -> &Align1D {
        &self.0
    }
}

impl Align1D {
    fn compute(self, dest_pos: f64, dest_size: f64, obj_size: f64) -> f64 {
        match self {
            Align1D::Min => dest_pos,
            Align1D::Mid => dest_pos + (dest_size - obj_size) / 2.0,
            Align1D::Max => dest_pos + dest_size - obj_size,
        }
    }
}

impl AspectRatio {
    pub fn is_slice(&self) -> bool {
        match self.align {
            Some(Align {
                fit: FitMode::Slice,
                ..
            }) => true,

            _ => false,
        }
    }

    pub fn compute(
        &self,
        object_width: f64,
        object_height: f64,
        dest_x: f64,
        dest_y: f64,
        dest_width: f64,
        dest_height: f64,
    ) -> (f64, f64, f64, f64) {
        match self.align {
            None => (dest_x, dest_y, dest_width, dest_height),

            Some(Align { x, y, fit }) => {
                let w_factor = dest_width / object_width;
                let h_factor = dest_height / object_height;
                let factor = match fit {
                    FitMode::Meet => w_factor.min(h_factor),
                    FitMode::Slice => w_factor.max(h_factor),
                };

                let w = object_width * factor;
                let h = object_height * factor;

                let xpos = x.compute(dest_x, dest_width, w);
                let ypos = y.compute(dest_y, dest_height, h);

                (xpos, ypos, w, h)
            }
        }
    }

    fn parse_input<'i, 't>(p: &mut Parser<'i, 't>) -> Result<AspectRatio, ()> {
        let defer = p.try(|p| p.expect_ident_matching("defer")).is_ok();

        let align_xy = p.try(|p| {
            p.expect_ident()
                .map_err(|_| ())
                .and_then(|ident| Align::parse_xy(ident))
        })?;

        let fit = p
            .try(|p| {
                p.expect_ident()
                    .map_err(|_| ())
                    .and_then(|ident| FitMode::parse(ident))
            }).unwrap_or(FitMode::default());

        p.expect_exhausted().map_err(|_| ())?;

        let align = align_xy.map(|(x, y)| Align { x, y, fit });

        Ok(AspectRatio { defer, align })
    }
}

impl Align {
    fn parse_xy(s: &str) -> Result<Option<(X, Y)>, ()> {
        use self::Align1D::*;

        match s {
            "none" => Ok(None),

            "xMinYMin" => Ok(Some((X(Min), Y(Min)))),
            "xMidYMin" => Ok(Some((X(Mid), Y(Min)))),
            "xMaxYMin" => Ok(Some((X(Max), Y(Min)))),

            "xMinYMid" => Ok(Some((X(Min), Y(Mid)))),
            "xMidYMid" => Ok(Some((X(Mid), Y(Mid)))),
            "xMaxYMid" => Ok(Some((X(Max), Y(Mid)))),

            "xMinYMax" => Ok(Some((X(Min), Y(Max)))),
            "xMidYMax" => Ok(Some((X(Mid), Y(Max)))),
            "xMaxYMax" => Ok(Some((X(Max), Y(Max)))),

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

impl FitMode {
    fn parse(s: &str) -> Result<FitMode, ()> {
        match s {
            "meet" => Ok(FitMode::Meet),
            "slice" => Ok(FitMode::Slice),
            _ => Err(()),
        }
    }
}

impl Parse for AspectRatio {
    type Data = ();
    type Err = ValueErrorKind;

    fn parse(parser: &mut Parser<'_, '_>, _: ()) -> Result<AspectRatio, ValueErrorKind> {
        AspectRatio::parse_input(parser).map_err(|_| {
            ValueErrorKind::Parse(ParseError::new(
                "expected \"[defer] <align> [meet | slice]\"",
            ))
        })
    }
}

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

    #[test]
    fn parsing_invalid_strings_yields_error() {
        assert!(AspectRatio::parse_str("", ()).is_err());
        assert!(AspectRatio::parse_str("defer", ()).is_err());
        assert!(AspectRatio::parse_str("defer foo", ()).is_err());
        assert!(AspectRatio::parse_str("defer xmidymid", ()).is_err());
        assert!(AspectRatio::parse_str("defer xMidYMid foo", ()).is_err());
        assert!(AspectRatio::parse_str("xmidymid", ()).is_err());
        assert!(AspectRatio::parse_str("xMidYMid foo", ()).is_err());
        assert!(AspectRatio::parse_str("defer xMidYMid meet foo", ()).is_err());
    }

    #[test]
    fn parses_valid_strings() {
        assert_eq!(
            AspectRatio::parse_str("defer none", ()),
            Ok(AspectRatio {
                defer: true,
                align: None,
            },)
        );

        assert_eq!(
            AspectRatio::parse_str("xMidYMid", ()),
            Ok(AspectRatio {
                defer: false,
                align: Some(Align {
                    x: X(Align1D::Mid),
                    y: Y(Align1D::Mid),
                    fit: FitMode::Meet,
                },),
            },)
        );

        assert_eq!(
            AspectRatio::parse_str("defer xMidYMid", ()),
            Ok(AspectRatio {
                defer: true,
                align: Some(Align {
                    x: X(Align1D::Mid),
                    y: Y(Align1D::Mid),
                    fit: FitMode::Meet,
                },),
            },)
        );

        assert_eq!(
            AspectRatio::parse_str("defer xMinYMax", ()),
            Ok(AspectRatio {
                defer: true,
                align: Some(Align {
                    x: X(Align1D::Min),
                    y: Y(Align1D::Max),
                    fit: FitMode::Meet,
                },),
            },)
        );

        assert_eq!(
            AspectRatio::parse_str("defer xMaxYMid meet", ()),
            Ok(AspectRatio {
                defer: true,
                align: Some(Align {
                    x: X(Align1D::Max),
                    y: Y(Align1D::Mid),
                    fit: FitMode::Meet,
                },),
            },)
        );

        assert_eq!(
            AspectRatio::parse_str("defer xMinYMax slice", ()),
            Ok(AspectRatio {
                defer: true,
                align: Some(Align {
                    x: X(Align1D::Min),
                    y: Y(Align1D::Max),
                    fit: FitMode::Slice,
                },),
            },)
        );
    }

    #[test]
    fn aligns() {
        let foo = AspectRatio::parse_str("xMinYMin meet", ()).unwrap();
        let foo = foo.compute(1.0, 10.0, 0.0, 0.0, 10.0, 1.0);
        assert_eq!(foo, (0.0, 0.0, 0.1, 1.0));

        let foo = AspectRatio::parse_str("xMinYMin slice", ()).unwrap();
        let foo = foo.compute(1.0, 10.0, 0.0, 0.0, 10.0, 1.0);
        assert_eq!(foo, (0.0, 0.0, 10.0, 100.0));

        let foo = AspectRatio::parse_str("xMinYMid meet", ()).unwrap();
        let foo = foo.compute(1.0, 10.0, 0.0, 0.0, 10.0, 1.0);
        assert_eq!(foo, (0.0, 0.0, 0.1, 1.0));

        let foo = AspectRatio::parse_str("xMinYMid slice", ()).unwrap();
        let foo = foo.compute(1.0, 10.0, 0.0, 0.0, 10.0, 1.0);

        assert_eq!(foo, (0.0, -49.5, 10.0, 100.0));

        let foo = AspectRatio::parse_str("xMinYMax meet", ()).unwrap();
        let foo = foo.compute(1.0, 10.0, 0.0, 0.0, 10.0, 1.0);
        assert_eq!(foo, (0.0, 0.0, 0.1, 1.0));

        let foo = AspectRatio::parse_str("xMinYMax slice", ()).unwrap();
        let foo = foo.compute(1.0, 10.0, 0.0, 0.0, 10.0, 1.0);
        assert_eq!(foo, (0.0, -99.0, 10.0, 100.0));

        let foo = AspectRatio::parse_str("xMidYMin meet", ()).unwrap();
        let foo = foo.compute(1.0, 10.0, 0.0, 0.0, 10.0, 1.0);
        assert_eq!(foo, (4.95, 0.0, 0.1, 1.0));

        let foo = AspectRatio::parse_str("xMidYMin slice", ()).unwrap();
        let foo = foo.compute(1.0, 10.0, 0.0, 0.0, 10.0, 1.0);
        assert_eq!(foo, (0.0, 0.0, 10.0, 100.0));

        let foo = AspectRatio::parse_str("xMidYMid meet", ()).unwrap();
        let foo = foo.compute(1.0, 10.0, 0.0, 0.0, 10.0, 1.0);
        assert_eq!(foo, (4.95, 0.0, 0.1, 1.0));

        let foo = AspectRatio::parse_str("xMidYMid slice", ()).unwrap();
        let foo = foo.compute(1.0, 10.0, 0.0, 0.0, 10.0, 1.0);
        assert_eq!(foo, (0.0, -49.5, 10.0, 100.0));

        let foo = AspectRatio::parse_str("xMidYMax meet", ()).unwrap();
        let foo = foo.compute(1.0, 10.0, 0.0, 0.0, 10.0, 1.0);
        assert_eq!(foo, (4.95, 0.0, 0.1, 1.0));

        let foo = AspectRatio::parse_str("xMidYMax slice", ()).unwrap();
        let foo = foo.compute(1.0, 10.0, 0.0, 0.0, 10.0, 1.0);
        assert_eq!(foo, (0.0, -99.0, 10.0, 100.0));

        let foo = AspectRatio::parse_str("xMaxYMin meet", ()).unwrap();
        let foo = foo.compute(1.0, 10.0, 0.0, 0.0, 10.0, 1.0);
        assert_eq!(foo, (9.9, 0.0, 0.1, 1.0));

        let foo = AspectRatio::parse_str("xMaxYMin slice", ()).unwrap();
        let foo = foo.compute(1.0, 10.0, 0.0, 0.0, 10.0, 1.0);
        assert_eq!(foo, (0.0, 0.0, 10.0, 100.0));

        let foo = AspectRatio::parse_str("xMaxYMid meet", ()).unwrap();
        let foo = foo.compute(1.0, 10.0, 0.0, 0.0, 10.0, 1.0);
        assert_eq!(foo, (9.9, 0.0, 0.1, 1.0));

        let foo = AspectRatio::parse_str("xMaxYMid slice", ()).unwrap();
        let foo = foo.compute(1.0, 10.0, 0.0, 0.0, 10.0, 1.0);
        assert_eq!(foo, (0.0, -49.5, 10.0, 100.0));

        let foo = AspectRatio::parse_str("xMaxYMax meet", ()).unwrap();
        let foo = foo.compute(1.0, 10.0, 0.0, 0.0, 10.0, 1.0);
        assert_eq!(foo, (9.9, 0.0, 0.1, 1.0));

        let foo = AspectRatio::parse_str("xMaxYMax slice", ()).unwrap();
        let foo = foo.compute(1.0, 10.0, 0.0, 0.0, 10.0, 1.0);
        assert_eq!(foo, (0.0, -99.0, 10.0, 100.0));
    }
}
<commit_msg>AspectRatio: don't use a helper function to parse<commit_after>//! Handling of `preserveAspectRatio` values
//!
//! This module handles `preserveAspectRatio` values [per the SVG specification][spec].
//! We have an [`AspectRatio`] struct which encapsulates such a value.
//!
//! ```ignore
//! assert_eq!(
//!     AspectRatio::parse("xMidYMid", ()),
//!     Ok(AspectRatio {
//!         defer: false,
//!         align: Some(Align {
//!             x: X(Align1D::Mid),
//!             y: Y(Align1D::Mid),
//!             fit: FitMode::Meet,
//!         }),
//!     })
//! );
//! ```
//!
//! [`AspectRatio`]: struct.AspectRatio.html
//! [spec]: https://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute

use cssparser::Parser;
use error::*;
use parsers::Parse;
use parsers::ParseError;
use std::ops::Deref;

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct AspectRatio {
    defer: bool,
    align: Option<Align>,
}

impl Default for AspectRatio {
    fn default() -> AspectRatio {
        AspectRatio {
            defer: false,
            align: Some(Align::default()),
        }
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum FitMode {
    Meet,
    Slice,
}

impl Default for FitMode {
    fn default() -> FitMode {
        FitMode::Meet
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
struct Align {
    x: X,
    y: Y,
    fit: FitMode,
}

impl Default for Align {
    fn default() -> Align {
        Align {
            x: X(Align1D::Mid),
            y: Y(Align1D::Mid),
            fit: FitMode::default(),
        }
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum Align1D {
    Min,
    Mid,
    Max,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
struct X(Align1D);
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
struct Y(Align1D);

impl Deref for X {
    type Target = Align1D;

    fn deref(&self) -> &Align1D {
        &self.0
    }
}

impl Deref for Y {
    type Target = Align1D;

    fn deref(&self) -> &Align1D {
        &self.0
    }
}

impl Align1D {
    fn compute(self, dest_pos: f64, dest_size: f64, obj_size: f64) -> f64 {
        match self {
            Align1D::Min => dest_pos,
            Align1D::Mid => dest_pos + (dest_size - obj_size) / 2.0,
            Align1D::Max => dest_pos + dest_size - obj_size,
        }
    }
}

impl AspectRatio {
    pub fn is_slice(&self) -> bool {
        match self.align {
            Some(Align {
                fit: FitMode::Slice,
                ..
            }) => true,

            _ => false,
        }
    }

    pub fn compute(
        &self,
        object_width: f64,
        object_height: f64,
        dest_x: f64,
        dest_y: f64,
        dest_width: f64,
        dest_height: f64,
    ) -> (f64, f64, f64, f64) {
        match self.align {
            None => (dest_x, dest_y, dest_width, dest_height),

            Some(Align { x, y, fit }) => {
                let w_factor = dest_width / object_width;
                let h_factor = dest_height / object_height;
                let factor = match fit {
                    FitMode::Meet => w_factor.min(h_factor),
                    FitMode::Slice => w_factor.max(h_factor),
                };

                let w = object_width * factor;
                let h = object_height * factor;

                let xpos = x.compute(dest_x, dest_width, w);
                let ypos = y.compute(dest_y, dest_height, h);

                (xpos, ypos, w, h)
            }
        }
    }
}

impl Align {
    fn parse_xy(s: &str) -> Result<Option<(X, Y)>, ValueErrorKind> {
        use self::Align1D::*;

        match s {
            "none" => Ok(None),

            "xMinYMin" => Ok(Some((X(Min), Y(Min)))),
            "xMidYMin" => Ok(Some((X(Mid), Y(Min)))),
            "xMaxYMin" => Ok(Some((X(Max), Y(Min)))),

            "xMinYMid" => Ok(Some((X(Min), Y(Mid)))),
            "xMidYMid" => Ok(Some((X(Mid), Y(Mid)))),
            "xMaxYMid" => Ok(Some((X(Max), Y(Mid)))),

            "xMinYMax" => Ok(Some((X(Min), Y(Max)))),
            "xMidYMax" => Ok(Some((X(Mid), Y(Max)))),
            "xMaxYMax" => Ok(Some((X(Max), Y(Max)))),

            _ => Err(ValueErrorKind::Parse(ParseError::new("invalid alignment"))),
        }
    }
}

impl FitMode {
    fn parse(s: &str) -> Result<FitMode, ValueErrorKind> {
        match s {
            "meet" => Ok(FitMode::Meet),
            "slice" => Ok(FitMode::Slice),
            _ => Err(ValueErrorKind::Parse(ParseError::new("invalid fit mode"))),
        }
    }
}

impl Parse for AspectRatio {
    type Data = ();
    type Err = ValueErrorKind;

    fn parse(parser: &mut Parser<'_, '_>, _: ()) -> Result<AspectRatio, ValueErrorKind> {
        let defer = parser.try(|p| p.expect_ident_matching("defer")).is_ok();

        let align_xy = parser.try(|p| {
            p.expect_ident()
                .map_err(|_| ValueErrorKind::Parse(ParseError::new("expected identifier")))
                .and_then(|ident| Align::parse_xy(ident))
        })?;

        let fit = parser
            .try(|p| {
                p.expect_ident()
                    .map_err(|_| ValueErrorKind::Parse(ParseError::new("expected identifier")))
                    .and_then(|ident| FitMode::parse(ident))
            }).unwrap_or(FitMode::default());

        parser
            .expect_exhausted()
            .map_err(|_| ValueErrorKind::Parse(ParseError::new("extra data in AspectRatio")))?;

        let align = align_xy.map(|(x, y)| Align { x, y, fit });

        Ok(AspectRatio { defer, align })
    }
}

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

    #[test]
    fn parsing_invalid_strings_yields_error() {
        assert!(AspectRatio::parse_str("", ()).is_err());
        assert!(AspectRatio::parse_str("defer", ()).is_err());
        assert!(AspectRatio::parse_str("defer foo", ()).is_err());
        assert!(AspectRatio::parse_str("defer xmidymid", ()).is_err());
        assert!(AspectRatio::parse_str("defer xMidYMid foo", ()).is_err());
        assert!(AspectRatio::parse_str("xmidymid", ()).is_err());
        assert!(AspectRatio::parse_str("xMidYMid foo", ()).is_err());
        assert!(AspectRatio::parse_str("defer xMidYMid meet foo", ()).is_err());
    }

    #[test]
    fn parses_valid_strings() {
        assert_eq!(
            AspectRatio::parse_str("defer none", ()),
            Ok(AspectRatio {
                defer: true,
                align: None,
            },)
        );

        assert_eq!(
            AspectRatio::parse_str("xMidYMid", ()),
            Ok(AspectRatio {
                defer: false,
                align: Some(Align {
                    x: X(Align1D::Mid),
                    y: Y(Align1D::Mid),
                    fit: FitMode::Meet,
                },),
            },)
        );

        assert_eq!(
            AspectRatio::parse_str("defer xMidYMid", ()),
            Ok(AspectRatio {
                defer: true,
                align: Some(Align {
                    x: X(Align1D::Mid),
                    y: Y(Align1D::Mid),
                    fit: FitMode::Meet,
                },),
            },)
        );

        assert_eq!(
            AspectRatio::parse_str("defer xMinYMax", ()),
            Ok(AspectRatio {
                defer: true,
                align: Some(Align {
                    x: X(Align1D::Min),
                    y: Y(Align1D::Max),
                    fit: FitMode::Meet,
                },),
            },)
        );

        assert_eq!(
            AspectRatio::parse_str("defer xMaxYMid meet", ()),
            Ok(AspectRatio {
                defer: true,
                align: Some(Align {
                    x: X(Align1D::Max),
                    y: Y(Align1D::Mid),
                    fit: FitMode::Meet,
                },),
            },)
        );

        assert_eq!(
            AspectRatio::parse_str("defer xMinYMax slice", ()),
            Ok(AspectRatio {
                defer: true,
                align: Some(Align {
                    x: X(Align1D::Min),
                    y: Y(Align1D::Max),
                    fit: FitMode::Slice,
                },),
            },)
        );
    }

    #[test]
    fn aligns() {
        let foo = AspectRatio::parse_str("xMinYMin meet", ()).unwrap();
        let foo = foo.compute(1.0, 10.0, 0.0, 0.0, 10.0, 1.0);
        assert_eq!(foo, (0.0, 0.0, 0.1, 1.0));

        let foo = AspectRatio::parse_str("xMinYMin slice", ()).unwrap();
        let foo = foo.compute(1.0, 10.0, 0.0, 0.0, 10.0, 1.0);
        assert_eq!(foo, (0.0, 0.0, 10.0, 100.0));

        let foo = AspectRatio::parse_str("xMinYMid meet", ()).unwrap();
        let foo = foo.compute(1.0, 10.0, 0.0, 0.0, 10.0, 1.0);
        assert_eq!(foo, (0.0, 0.0, 0.1, 1.0));

        let foo = AspectRatio::parse_str("xMinYMid slice", ()).unwrap();
        let foo = foo.compute(1.0, 10.0, 0.0, 0.0, 10.0, 1.0);

        assert_eq!(foo, (0.0, -49.5, 10.0, 100.0));

        let foo = AspectRatio::parse_str("xMinYMax meet", ()).unwrap();
        let foo = foo.compute(1.0, 10.0, 0.0, 0.0, 10.0, 1.0);
        assert_eq!(foo, (0.0, 0.0, 0.1, 1.0));

        let foo = AspectRatio::parse_str("xMinYMax slice", ()).unwrap();
        let foo = foo.compute(1.0, 10.0, 0.0, 0.0, 10.0, 1.0);
        assert_eq!(foo, (0.0, -99.0, 10.0, 100.0));

        let foo = AspectRatio::parse_str("xMidYMin meet", ()).unwrap();
        let foo = foo.compute(1.0, 10.0, 0.0, 0.0, 10.0, 1.0);
        assert_eq!(foo, (4.95, 0.0, 0.1, 1.0));

        let foo = AspectRatio::parse_str("xMidYMin slice", ()).unwrap();
        let foo = foo.compute(1.0, 10.0, 0.0, 0.0, 10.0, 1.0);
        assert_eq!(foo, (0.0, 0.0, 10.0, 100.0));

        let foo = AspectRatio::parse_str("xMidYMid meet", ()).unwrap();
        let foo = foo.compute(1.0, 10.0, 0.0, 0.0, 10.0, 1.0);
        assert_eq!(foo, (4.95, 0.0, 0.1, 1.0));

        let foo = AspectRatio::parse_str("xMidYMid slice", ()).unwrap();
        let foo = foo.compute(1.0, 10.0, 0.0, 0.0, 10.0, 1.0);
        assert_eq!(foo, (0.0, -49.5, 10.0, 100.0));

        let foo = AspectRatio::parse_str("xMidYMax meet", ()).unwrap();
        let foo = foo.compute(1.0, 10.0, 0.0, 0.0, 10.0, 1.0);
        assert_eq!(foo, (4.95, 0.0, 0.1, 1.0));

        let foo = AspectRatio::parse_str("xMidYMax slice", ()).unwrap();
        let foo = foo.compute(1.0, 10.0, 0.0, 0.0, 10.0, 1.0);
        assert_eq!(foo, (0.0, -99.0, 10.0, 100.0));

        let foo = AspectRatio::parse_str("xMaxYMin meet", ()).unwrap();
        let foo = foo.compute(1.0, 10.0, 0.0, 0.0, 10.0, 1.0);
        assert_eq!(foo, (9.9, 0.0, 0.1, 1.0));

        let foo = AspectRatio::parse_str("xMaxYMin slice", ()).unwrap();
        let foo = foo.compute(1.0, 10.0, 0.0, 0.0, 10.0, 1.0);
        assert_eq!(foo, (0.0, 0.0, 10.0, 100.0));

        let foo = AspectRatio::parse_str("xMaxYMid meet", ()).unwrap();
        let foo = foo.compute(1.0, 10.0, 0.0, 0.0, 10.0, 1.0);
        assert_eq!(foo, (9.9, 0.0, 0.1, 1.0));

        let foo = AspectRatio::parse_str("xMaxYMid slice", ()).unwrap();
        let foo = foo.compute(1.0, 10.0, 0.0, 0.0, 10.0, 1.0);
        assert_eq!(foo, (0.0, -49.5, 10.0, 100.0));

        let foo = AspectRatio::parse_str("xMaxYMax meet", ()).unwrap();
        let foo = foo.compute(1.0, 10.0, 0.0, 0.0, 10.0, 1.0);
        assert_eq!(foo, (9.9, 0.0, 0.1, 1.0));

        let foo = AspectRatio::parse_str("xMaxYMax slice", ()).unwrap();
        let foo = foo.compute(1.0, 10.0, 0.0, 0.0, 10.0, 1.0);
        assert_eq!(foo, (0.0, -99.0, 10.0, 100.0));
    }
}
<|endoftext|>


<commit_before>extern crate desync;
extern crate futures;

use desync::Desync;

mod scheduler;
use self::scheduler::timeout::*;

use futures::future;

use std::sync::*;
use std::time::*;
use std::thread::*;

#[derive(Debug)]
struct TestData {
    val: u32
}

#[test]
fn retrieve_data_synchronously() {
    let desynced = Desync::new(TestData { val: 0 });

    assert!(desynced.sync(|data| data.val) == 0);
}

#[test]
fn retrieve_data_into_local_var() {
    let desynced = Desync::new(TestData { val: 42 });
    let mut val = 0;

    desynced.sync(|data| val = data.val);

    assert!(val == 42);
}

#[test]
fn update_data_asynchronously() {
    let desynced = Desync::new(TestData { val: 0 });

    desynced.desync(|data| {
        sleep(Duration::from_millis(100));
        data.val = 42;
    });

    assert!(desynced.sync(|data| data.val) == 42);
}

#[test]
fn update_data_asynchronously_1000_times() {
    for _i in 0..1000 {
        timeout(|| {
            let desynced = Desync::new(TestData { val: 0 });

            desynced.desync(|data| {
                data.val = 42;
            });
            desynced.desync(|data| {
                data.val = 43;
            });

            assert!(desynced.sync(|data| data.val) == 43);
        }, 500);
    }
}

#[test]
fn update_data_with_future() {
    timeout(|| {
        use futures::executor;

        let desynced = Desync::new(TestData { val: 0 });

        desynced.desync(|data| {
            sleep(Duration::from_millis(100));
            data.val = 42;
        });

        executor::block_on(async {
            let future = desynced.future_desync(|data| { future::ready(data.val) });
            assert!(future.await.unwrap() == 42);
        });
    }, 500);
}

#[test]
fn update_data_with_future_1000_times() {
    // Seems to timeout fairly reliably after signalling the future
    use futures::executor;

    for _i in 0..1000 {
        timeout(|| {
            let desynced = Desync::new(TestData { val: 0 });

            desynced.desync(|data| {
                data.val = 42;
            });
            desynced.desync(|data| {
                data.val = 43;
            });

            executor::block_on(async {
                let future = desynced.future_desync(|data| Box::pin(future::ready(data.val)));

                assert!(future.await.unwrap() == 43);
            });
        }, 500);
    }
}

#[test]
fn update_data_with_future_sync() {
    timeout(|| {
        use futures::executor;

        let desynced = Desync::new(TestData { val: 0 });

        desynced.desync(|data| {
            sleep(Duration::from_millis(100));
            data.val = 42;
        });

        executor::block_on(async {
            let future = desynced.future_sync(|data| { future::ready(data.val) });
            assert!(future.await.unwrap() == 42);
        });
    }, 500);
}

#[test]
fn update_data_with_future_sync_1000_times() {
    // Seems to timeout fairly reliably after signalling the future
    use futures::executor;

    for _i in 0..1000 {
        timeout(|| {
            let desynced = Desync::new(TestData { val: 0 });

            desynced.desync(|data| {
                data.val = 42;
            });
            desynced.desync(|data| {
                data.val = 43;
            });

            executor::block_on(async {
                let future = desynced.future_sync(|data| future::ready(data.val));

                assert!(future.await.unwrap() == 43);
            });
        }, 500);
    }
}

#[test]
fn dropping_while_running_isnt_obviously_bad() {
    let desynced = Desync::new(TestData { val: 0 });

    desynced.desync(|data| {
        sleep(Duration::from_millis(100));
        data.val = 42;
    });
    desynced.desync(|data| {
        sleep(Duration::from_millis(100));
        data.val = 42;
    });
}

#[test]
fn wait_for_future() {
    // TODO: occasional test failure that happens if the future 'arrives' before the queue is empty
    // (Because we need a future that arrives when the queue is actually suspended)
    timeout(|| {
        use futures::executor;
        use futures::channel::oneshot;

        // We use a oneshot as our future, and a mpsc channel to track progress
        let desynced                = Desync::new(0);
        let (future_tx, future_rx)  = oneshot::channel();

        // First value 0 -> 1
        desynced.desync(|val| {
            // Sleep here so the future should be waiting for us
            sleep(Duration::from_millis(100));
            assert!(*val == 0);
            *val = 1;
        });

        // Future should go 1 -> 2, but takes whatever future_tx sends
        let future = desynced.after(future_rx, |val, future_result| {
            assert!(*val == 1);
            *val = future_result.unwrap();

            // Return '4' to anything listening for this future
            4
        });

        // Finally, 3
        desynced.desync(move |val| { assert!(*val == 2); *val = 3 });

        executor::block_on(async {
            // Send '2' to the future
            future_tx.send(2).unwrap();

            // Future should resolve to 4
            assert!(future.await == Ok(4));

            // Final value should be 3
            assert!(desynced.sync(|val| *val) == 3);
        })
    }, 500);
}

#[test]
fn future_and_sync() {
    // This test seems to produce different behaviour if it's run by itself (this sleep tends to force it to run after the other tests and thus fail)
    // So far the failure seems reliable when this test is running exclusively
    sleep(Duration::from_millis(1000));

    use std::thread;
    use futures::channel::oneshot;

    // The idea here is we perform an action with a future() and read the result back with a sync() (which is a way you can mix-and-match
    // programming models with desync)
    //
    // The 'core' runs a request as a future, waiting for the channel result. We store the result in sync_request, and then retrieve
    // it again by calling sync - as Desync always runs things sequentially, it guarantees the ordering (something that's much harder
    // to achieve with a mutex)
    let (send, recv)    = oneshot::channel::<i32>();
    let core            = Desync::new(0);
    let sync_request    = Desync::new(None);

    // Send a request to the 'core' via the sync reqeust and store the result
    let _ = sync_request.future_desync(move |data| {
        async move {
            let result = core.future_desync(move |_core| {
                async move {
                    Some(recv.await.unwrap())
                }
            }).await;

            *data = result.unwrap();
        }
    });

    // Signal the future after a delay
    thread::spawn(move || {
        thread::sleep(Duration::from_millis(50));
        send.send(42).ok();
    });

    // Retrieve the result once the future completes
    let result = sync_request.sync(|req| req.take());

    // Should retrieve the value generated in the future
    assert!(result == Some(42));
}

#[test]
fn double_future_and_sync() {
    use std::thread;

    // TODO: signal with channels instead of using thread::sleep

    // This test will queue two futures here, each of which will need to return to another desync
    // If two futures are scheduled and triggered in a row when draining a queue that both signal
    let core        = Arc::new(Desync::new(()));

    let initiator_1 = Desync::new(None);
    let initiator_2 = Desync::new(None);
    let initiator_3 = Desync::new(None);

    let core_1      = Arc::clone(&core);
    initiator_1.future_desync(move |val| {
        async move {
            // Wait for a task on the core
            *val = core_1.future_desync(move |_| {
                async move { thread::sleep(Duration::from_millis(400)); Some(1) }
            }).await.unwrap();
        }
    }).detach();

    let core_2      = Arc::clone(&core);
    initiator_2.future_desync(move |val| {
        async move {
            // Wait for the original initiator to start its future
            thread::sleep(Duration::from_millis(100));

            // Wait for a task on the core
            *val = core_2.future_desync(move |_| {
                async move { thread::sleep(Duration::from_millis(200)); Some(2) }
            }).await.unwrap();
        }
    }).detach();

    let core_3      = Arc::clone(&core);
    initiator_3.future_desync(move |val| {
        async move {
            // Wait for the original initiator to start its future
            thread::sleep(Duration::from_millis(200));

            // Wait for a task on the core
            *val = core_3.future_desync(move |_| {
                async move { thread::sleep(Duration::from_millis(200)); Some(3) }
            }).await.unwrap();
        }
    }).detach();

    // Wait for the result from the futures synchronously
    assert!(initiator_3.sync(|val| { *val }) == Some(3));
    assert!(initiator_2.sync(|val| { *val }) == Some(2));
    assert!(initiator_1.sync(|val| { *val }) == Some(1));
}

#[test]
fn try_sync_succeeds_on_idle_queue() {
    timeout(|| {
        let core        = Desync::new(0);

        // Queue is doing nothing, so try_sync should succeed
        let sync_result = core.try_sync(|val| {
            *val = 42;
            1
        });

        // Queue is idle, so we should receive a result
        assert!(sync_result == Ok(1));

        // Double-check that the value was updated
        assert!(core.sync(|val| *val) == 42);
    }, 500);
}

#[test]
fn try_sync_succeeds_on_idle_queue_after_async_job() {
    timeout(|| {
        use std::thread;
        let core        = Desync::new(0);

        // Schedule something asynchronously and wait for it to complete
        core.desync(|_val| thread::sleep(Duration::from_millis(50)));
        core.sync(|_val| { });

        // Queue is doing nothing, so try_sync should succeed
        let sync_result = core.try_sync(|val| {
            *val= 42;
            1
        });

        // Queue is idle, so we should receive a result
        assert!(sync_result == Ok(1));

        // Double-check that the value was updated
        assert!(core.sync(|val| *val) == 42);
    }, 500);
}

#[test]
fn try_sync_fails_on_busy_queue() {
    timeout(|| {
        use std::sync::mpsc::*;

        let core        = Desync::new(0);

        // Schedule on the queue and block it
        let (tx, rx)    = channel();

        core.desync(move |_val| { rx.recv().ok(); });

        // Queue is busy, so try_sync should fail
        let sync_result = core.try_sync(|val| {
            *val = 42;
            1
        });

        // Queue is idle, so we should receive a result
        assert!(sync_result.is_err());

        // Unblock the queue
        tx.send(1).ok();

        // Double-check that the value was not updated
        assert!((core.sync(|val| *val)) == 0);
    }, 500);
}
<commit_msg>As well as the panicking problem, I thought there might be an issue if the entire thread pool gets blocked by 'wait in background' tasks: we need a way to wake these up again somehow.<commit_after>extern crate desync;
extern crate futures;

use desync::Desync;

mod scheduler;
use self::scheduler::timeout::*;

use futures::future;

use std::sync::*;
use std::time::*;
use std::thread::*;

#[derive(Debug)]
struct TestData {
    val: u32
}

#[test]
fn retrieve_data_synchronously() {
    let desynced = Desync::new(TestData { val: 0 });

    assert!(desynced.sync(|data| data.val) == 0);
}

#[test]
fn retrieve_data_into_local_var() {
    let desynced = Desync::new(TestData { val: 42 });
    let mut val = 0;

    desynced.sync(|data| val = data.val);

    assert!(val == 42);
}

#[test]
fn update_data_asynchronously() {
    let desynced = Desync::new(TestData { val: 0 });

    desynced.desync(|data| {
        sleep(Duration::from_millis(100));
        data.val = 42;
    });

    assert!(desynced.sync(|data| data.val) == 42);
}

#[test]
fn update_data_asynchronously_1000_times() {
    for _i in 0..1000 {
        timeout(|| {
            let desynced = Desync::new(TestData { val: 0 });

            desynced.desync(|data| {
                data.val = 42;
            });
            desynced.desync(|data| {
                data.val = 43;
            });

            assert!(desynced.sync(|data| data.val) == 43);
        }, 500);
    }
}

#[test]
fn update_data_with_future() {
    timeout(|| {
        use futures::executor;

        let desynced = Desync::new(TestData { val: 0 });

        desynced.desync(|data| {
            sleep(Duration::from_millis(100));
            data.val = 42;
        });

        executor::block_on(async {
            let future = desynced.future_desync(|data| { future::ready(data.val) });
            assert!(future.await.unwrap() == 42);
        });
    }, 500);
}

#[test]
fn update_data_with_future_1000_times() {
    // Seems to timeout fairly reliably after signalling the future
    use futures::executor;

    for _i in 0..1000 {
        timeout(|| {
            let desynced = Desync::new(TestData { val: 0 });

            desynced.desync(|data| {
                data.val = 42;
            });
            desynced.desync(|data| {
                data.val = 43;
            });

            executor::block_on(async {
                let future = desynced.future_desync(|data| Box::pin(future::ready(data.val)));

                assert!(future.await.unwrap() == 43);
            });
        }, 500);
    }
}

#[test]
fn update_data_with_future_sync() {
    timeout(|| {
        use futures::executor;

        let desynced = Desync::new(TestData { val: 0 });

        desynced.desync(|data| {
            sleep(Duration::from_millis(100));
            data.val = 42;
        });

        executor::block_on(async {
            let future = desynced.future_sync(|data| { future::ready(data.val) });
            assert!(future.await.unwrap() == 42);
        });
    }, 500);
}

#[test]
fn update_data_with_future_sync_1000_times() {
    // Seems to timeout fairly reliably after signalling the future
    use futures::executor;

    for _i in 0..1000 {
        timeout(|| {
            let desynced = Desync::new(TestData { val: 0 });

            desynced.desync(|data| {
                data.val = 42;
            });
            desynced.desync(|data| {
                data.val = 43;
            });

            executor::block_on(async {
                let future = desynced.future_sync(|data| future::ready(data.val));

                assert!(future.await.unwrap() == 43);
            });
        }, 500);
    }
}

#[test]
fn dropping_while_running_isnt_obviously_bad() {
    let desynced = Desync::new(TestData { val: 0 });

    desynced.desync(|data| {
        sleep(Duration::from_millis(100));
        data.val = 42;
    });
    desynced.desync(|data| {
        sleep(Duration::from_millis(100));
        data.val = 42;
    });
}

#[test]
fn wait_for_future() {
    // TODO: occasional test failure that happens if the future 'arrives' before the queue is empty
    // (Because we need a future that arrives when the queue is actually suspended)
    timeout(|| {
        use futures::executor;
        use futures::channel::oneshot;

        // We use a oneshot as our future, and a mpsc channel to track progress
        let desynced                = Desync::new(0);
        let (future_tx, future_rx)  = oneshot::channel();

        // First value 0 -> 1
        desynced.desync(|val| {
            // Sleep here so the future should be waiting for us
            sleep(Duration::from_millis(100));
            assert!(*val == 0);
            *val = 1;
        });

        // Future should go 1 -> 2, but takes whatever future_tx sends
        let future = desynced.after(future_rx, |val, future_result| {
            assert!(*val == 1);
            *val = future_result.unwrap();

            // Return '4' to anything listening for this future
            4
        });

        // Finally, 3
        desynced.desync(move |val| { assert!(*val == 2); *val = 3 });

        executor::block_on(async {
            // Send '2' to the future
            future_tx.send(2).unwrap();

            // Future should resolve to 4
            assert!(future.await == Ok(4));

            // Final value should be 3
            assert!(desynced.sync(|val| *val) == 3);
        })
    }, 500);
}

#[test]
fn future_and_sync() {
    // This test seems to produce different behaviour if it's run by itself (this sleep tends to force it to run after the other tests and thus fail)
    // So far the failure seems reliable when this test is running exclusively
    sleep(Duration::from_millis(1000));

    use std::thread;
    use futures::channel::oneshot;

    // The idea here is we perform an action with a future() and read the result back with a sync() (which is a way you can mix-and-match
    // programming models with desync)
    //
    // The 'core' runs a request as a future, waiting for the channel result. We store the result in sync_request, and then retrieve
    // it again by calling sync - as Desync always runs things sequentially, it guarantees the ordering (something that's much harder
    // to achieve with a mutex)
    let (send, recv)    = oneshot::channel::<i32>();
    let core            = Desync::new(0);
    let sync_request    = Desync::new(None);

    // Send a request to the 'core' via the sync reqeust and store the result
    let _ = sync_request.future_desync(move |data| {
        async move {
            let result = core.future_desync(move |_core| {
                async move {
                    Some(recv.await.unwrap())
                }
            }).await;

            *data = result.unwrap();
        }
    });

    // Signal the future after a delay
    thread::spawn(move || {
        thread::sleep(Duration::from_millis(50));
        send.send(42).ok();
    });

    // Retrieve the result once the future completes
    let result = sync_request.sync(|req| req.take());

    // Should retrieve the value generated in the future
    assert!(result == Some(42));
}

#[test]
fn double_future_and_sync() {
    use std::thread;

    // TODO: signal with channels instead of using thread::sleep

    // This test will queue two futures here, each of which will need to return to another desync
    // If two futures are scheduled and triggered in a row when draining a queue that both signal
    let core        = Arc::new(Desync::new(()));

    let initiator_1 = Desync::new(None);
    let initiator_2 = Desync::new(None);
    let initiator_3 = Desync::new(None);

    let core_1      = Arc::clone(&core);
    initiator_1.future_desync(move |val| {
        async move {
            // Wait for a task on the core
            *val = core_1.future_desync(move |_| {
                async move { thread::sleep(Duration::from_millis(400)); Some(1) }
            }).await.unwrap();
        }
    }).detach();

    let core_2      = Arc::clone(&core);
    initiator_2.future_desync(move |val| {
        async move {
            // Wait for the original initiator to start its future
            thread::sleep(Duration::from_millis(100));

            // Wait for a task on the core
            *val = core_2.future_desync(move |_| {
                async move { thread::sleep(Duration::from_millis(200)); Some(2) }
            }).await.unwrap();
        }
    }).detach();

    let core_3      = Arc::clone(&core);
    initiator_3.future_desync(move |val| {
        async move {
            // Wait for the original initiator to start its future
            thread::sleep(Duration::from_millis(200));

            // Wait for a task on the core
            *val = core_3.future_desync(move |_| {
                async move { thread::sleep(Duration::from_millis(200)); Some(3) }
            }).await.unwrap();
        }
    }).detach();

    // Wait for the result from the futures synchronously
    assert!(initiator_3.sync(|val| { *val }) == Some(3));
    assert!(initiator_2.sync(|val| { *val }) == Some(2));
    assert!(initiator_1.sync(|val| { *val }) == Some(1));
}

#[test]
fn try_sync_succeeds_on_idle_queue() {
    timeout(|| {
        let core        = Desync::new(0);

        // Queue is doing nothing, so try_sync should succeed
        let sync_result = core.try_sync(|val| {
            *val = 42;
            1
        });

        // Queue is idle, so we should receive a result
        assert!(sync_result == Ok(1));

        // Double-check that the value was updated
        assert!(core.sync(|val| *val) == 42);
    }, 500);
}

#[test]
fn try_sync_succeeds_on_idle_queue_after_async_job() {
    timeout(|| {
        use std::thread;
        let core        = Desync::new(0);

        // Schedule something asynchronously and wait for it to complete
        core.desync(|_val| thread::sleep(Duration::from_millis(50)));
        core.sync(|_val| { });

        // Queue is doing nothing, so try_sync should succeed
        let sync_result = core.try_sync(|val| {
            *val= 42;
            1
        });

        // Queue is idle, so we should receive a result
        assert!(sync_result == Ok(1));

        // Double-check that the value was updated
        assert!(core.sync(|val| *val) == 42);
    }, 500);
}

#[test]
fn try_sync_fails_on_busy_queue() {
    timeout(|| {
        use std::sync::mpsc::*;

        let core        = Desync::new(0);

        // Schedule on the queue and block it
        let (tx, rx)    = channel();

        core.desync(move |_val| { rx.recv().ok(); });

        // Queue is busy, so try_sync should fail
        let sync_result = core.try_sync(|val| {
            *val = 42;
            1
        });

        // Queue is idle, so we should receive a result
        assert!(sync_result.is_err());

        // Unblock the queue
        tx.send(1).ok();

        // Double-check that the value was not updated
        assert!((core.sync(|val| *val)) == 0);
    }, 500);
}

#[test]
fn fill_thread_pool_with_wait_in_background() {
    timeout(|| {
        use std::thread;
        use std::sync::mpsc;
        use std::time::{Duration};

        // This is potentially an issue with how we do thread stealing: the idea here is to create
        // 'WaitInBackground' status syncs across the whole thread pool, so when a 'foreground' sync
        // finishes there are no threads available to carry it on.
        //
        // A 'green' threads library would be ideal here as we could re-use the blocked threads: this
        // test mostly looks at the case where this isn't available.
        let (waiting_send, waiting_recv)    = mpsc::channel();
        let (release_send, release_recv)    = mpsc::channel();
        let wait_on                         = Arc::new(Desync::new(()));

        // In a background thread, block 'wait_on' until something is sent to the 'release' channel
        let background_wait                 = Arc::clone(&wait_on);
        thread::spawn(move || {
            background_wait.sync(move |_| {
                // Tell the main thread that we've reached this point (desync will steal the thread we created, which is how we exhaust the thread pool later on)
                waiting_send.send(()).unwrap();

                println!("Blocking Desync waiting...");

                // Wait for the release message
                release_recv.recv().unwrap();

                println!("Blocking Desync finished (unblocking)");
            });
        });

        // Wait for the spawned thread to signal that it has started running
        waiting_recv.recv().unwrap();

        // Create a bunch of desyncs and tell them to wait on the same sync (this will eventually fill the thread pool with threads that are waiting for a background desync)
        let background_desyncs = (0..100).into_iter().map(|_| Arc::new(Desync::new(()))).collect::<Vec<_>>();

        background_desyncs.iter()
            .for_each(|background| {
                let background_wait = Arc::clone(&wait_on);

                background.desync(move |_| {
                    // Doesn't really matter what the task is...
                    println!("Waiting for blocking Desync...");
                    background_wait.sync(|_| { });
                    println!("Finished background task");
                })
            });

        // Sleep to allow the thread pool to saturate (tests all run in parallel so this will be most reliable when this test is run by itself)
        // (Another problem as this blocks the scheduler when it fails is it could cause other tests to time out, so definitely run this by itself
        // to check if there are issues)
        thread::sleep(Duration::from_millis(100));

        // Send the release so our background thread finishes, and now the background desyncs can run
        println!("Releasing blocking desync...");
        release_send.send(()).unwrap();

        // Should be able to sync with all the background tasks
        println!("Synchronising with background tasks...");
        background_desyncs.into_iter()
            .for_each(|background| background.sync(|_| { }));

        // Should also be able to sync with the 'wait_on' desync (after all the background things)
        println!("Synchronising with blocking desync to finish");
        wait_on.sync(|_| { });
    }, 20000);
}
<|endoftext|>


<commit_before>use std::io::prelude::*;
use std::io::BufReader;
use std::io::Error;
use std::net::TcpStream;
use std::str::FromStr;
use std::fmt;

use bufstream::BufStream;

use config::Config;

pub struct Irc
{
    config: Config,
    connected: bool,
}

struct IrcMessage {
    raw_message: String,
    prefix: String,
    command: String,
    params: Vec<String>
}

impl IrcMessage {
    fn new(raw_msg: &str) -> IrcMessage {
        let mut raw: &str = raw_msg;
        let mut new_msg = IrcMessage {
            raw_message: String::new(),
            prefix: String::new(),
            command: String::new(),
            params: Vec::with_capacity(15)
        };

        new_msg.raw_message = String::from_str(raw).unwrap();
        if raw.starts_with(":") {
            let first_whitespace: u32 = match raw.find(' ') {
                Some(x) => x as u32,
                None => 0u32
            };
            new_msg.prefix = String::from_str(raw.substr(1, first_whitespace - 1)).unwrap();
            raw = raw.substr(first_whitespace + 1, raw.len() as u32 - (first_whitespace+1));
        }

        if raw.contains(' ') {
            let space_index = match raw.find(' ') {
                Some(x) => x as u32,
                None => 0u32,
            };
            new_msg.command = String::from(raw.substr(0, space_index));
            raw = raw.substr(space_index + 1, raw.len() as u32 - (space_index + 1));

            // Parse parameters
            let mut parameters: Vec<String> = Vec::new();
            while raw != "" {
                if raw.starts_with(":") {
                    parameters.push(String::from(raw.substr(1, raw.len() as u32 - 1)));
                    break;
                }

                if !raw.contains(' ') {
                    parameters.push(String::from(raw));
                    raw = "";
                    break;
                }
                let space_index = match raw.find(' ') {
                    Some(x) => x as u32,
                    None => 0u32
                };
                parameters.push(String::from(raw.substr(0, space_index)));
                raw = raw.substr(space_index + 1, raw.len() as u32 - (space_index + 1));
            }
            new_msg.params = parameters;
        }

        return new_msg;
    }
}

trait Substring {
    fn substr(&self, start_index: u32, length: u32) -> &str;
}

impl Substring for str {
    fn substr(&self, start_index: u32, length: u32) -> &str {
        return &self[start_index as usize .. start_index as usize + length as usize];
    }
}

impl Irc
{
    pub fn new(config: Config) -> Irc
    {
        return Irc { config: config, connected: false }
    }

    pub fn run(mut self: Self)
    {
        self.connected = false;

        // top kek this line
        match TcpStream::connect(&format!("{}:{}", self.config.server.hostname, self.config.server.port)[..]) {
            Ok(mut tcp_stream) => {
                info!("Connected");
                self.connected = true;

                let mut stream = BufStream::new(&tcp_stream);
                let mut buffer = String::new();

                send_raw_message(&mut stream, &format!("NICK {}", self.config.server.nickname));
                send_raw_message(&mut stream, &format!("USER {} 0 * :eIRC bot", self.config.server.username));
                send_raw_message(&mut stream, &format!("JOIN {}", self.config.server.channels.join(" ")));

                while stream.read_line(&mut buffer).unwrap() > 0 {
                    let message = IrcMessage::new(&buffer);
                    info!(">> {}", message.raw_message);

                    if message.command == "PING" {
                        let mut reply = String::from("PONG :");
                        reply = reply + &message.params[0];
                        send_raw_message(&mut stream, &reply);
                    }

                    if message.command == "PRIVMSG" {
                        if message.params[1].starts_with(".ping") {
                            let mut reply = String::from("PRIVMSG ");
                            reply = reply + &message.params[0];
                            reply = reply + " :PONG right in yo face!";
                            send_raw_message(&mut stream, &reply);
                        }
                    }

                    buffer.clear();
                }
            },

            Err(e) => panic!("Failed to connect to host: {}", e),
        };
    }
}

fn send_raw_message<W: Write>(w: &mut W, msg: &String) -> Result<(), Error> {
    let mut message = format!("{}\r\n", msg);

    try!(w.write(message.as_bytes()));
    w.flush();
    info!("<< {}", msg);

    Ok(())
}
<commit_msg>irc: trim lines receives and sent when logging<commit_after>use std::io::prelude::*;
use std::io::BufReader;
use std::io::Error;
use std::net::TcpStream;
use std::str::FromStr;
use std::fmt;

use bufstream::BufStream;

use config::Config;

pub struct Irc
{
    config: Config,
    connected: bool,
}

struct IrcMessage {
    raw_message: String,
    prefix: String,
    command: String,
    params: Vec<String>
}

impl IrcMessage {
    fn new(raw_msg: &str) -> IrcMessage {
        let mut raw: &str = raw_msg;
        let mut new_msg = IrcMessage {
            raw_message: String::new(),
            prefix: String::new(),
            command: String::new(),
            params: Vec::with_capacity(15)
        };

        new_msg.raw_message = String::from_str(raw).unwrap();
        if raw.starts_with(":") {
            let first_whitespace: u32 = match raw.find(' ') {
                Some(x) => x as u32,
                None => 0u32
            };
            new_msg.prefix = String::from_str(raw.substr(1, first_whitespace - 1)).unwrap();
            raw = raw.substr(first_whitespace + 1, raw.len() as u32 - (first_whitespace+1));
        }

        if raw.contains(' ') {
            let space_index = match raw.find(' ') {
                Some(x) => x as u32,
                None => 0u32,
            };
            new_msg.command = String::from(raw.substr(0, space_index));
            raw = raw.substr(space_index + 1, raw.len() as u32 - (space_index + 1));

            // Parse parameters
            let mut parameters: Vec<String> = Vec::new();
            while raw != "" {
                if raw.starts_with(":") {
                    parameters.push(String::from(raw.substr(1, raw.len() as u32 - 1)));
                    break;
                }

                if !raw.contains(' ') {
                    parameters.push(String::from(raw));
                    raw = "";
                    break;
                }
                let space_index = match raw.find(' ') {
                    Some(x) => x as u32,
                    None => 0u32
                };
                parameters.push(String::from(raw.substr(0, space_index)));
                raw = raw.substr(space_index + 1, raw.len() as u32 - (space_index + 1));
            }
            new_msg.params = parameters;
        }

        return new_msg;
    }
}

trait Substring {
    fn substr(&self, start_index: u32, length: u32) -> &str;
}

impl Substring for str {
    fn substr(&self, start_index: u32, length: u32) -> &str {
        return &self[start_index as usize .. start_index as usize + length as usize];
    }
}

impl Irc
{
    pub fn new(config: Config) -> Irc
    {
        return Irc { config: config, connected: false }
    }

    pub fn run(mut self: Self)
    {
        self.connected = false;

        // top kek this line
        match TcpStream::connect(&format!("{}:{}", self.config.server.hostname, self.config.server.port)[..]) {
            Ok(mut tcp_stream) => {
                info!("Connected");
                self.connected = true;

                let mut stream = BufStream::new(&tcp_stream);
                let mut buffer = String::new();

                send_raw_message(&mut stream, &format!("NICK {}", self.config.server.nickname));
                send_raw_message(&mut stream, &format!("USER {} 0 * :eIRC bot", self.config.server.username));
                send_raw_message(&mut stream, &format!("JOIN {}", self.config.server.channels.join(" ")));

                while stream.read_line(&mut buffer).unwrap() > 0 {
                    let message = IrcMessage::new(&buffer);
                    info!(">> {}", message.raw_message.trim());

                    if message.command == "PING" {
                        let mut reply = String::from("PONG :");
                        reply = reply + &message.params[0];
                        send_raw_message(&mut stream, &reply);
                    }

                    if message.command == "PRIVMSG" {
                        if message.params[1].starts_with(".ping") {
                            let mut reply = String::from("PRIVMSG ");
                            reply = reply + &message.params[0];
                            reply = reply + " :PONG right in yo face!";
                            send_raw_message(&mut stream, &reply);
                        }
                    }

                    buffer.clear();
                }
            },

            Err(e) => panic!("Failed to connect to host: {}", e),
        };
    }
}

fn send_raw_message<W: Write>(w: &mut W, msg: &String) -> Result<(), Error> {
    let mut message = format!("{}\r\n", msg);

    try!(w.write(message.as_bytes()));
    w.flush();
    info!("<< {}", msg.trim());

    Ok(())
}
<|endoftext|>


<commit_before>#![feature(associated_consts)]
#![feature(use_extern_macros)]
#![allow(plugin_as_library)]

#[macro_use]
extern crate tapioca;

infer_api!(httpbin, "https://raw.githubusercontent.com/OJFord/tapioca/master/tests/schemata/httpbin.yml");

#[test]
fn response_ref() {
    use httpbin::anything_ref;

    let test_vec: Vec<String> = vec!["foobar".into(), "bazzer".into()];
    let query = anything_ref::get::QueryParams {
        array: test_vec.clone(),
    };

    match anything_ref::get(query) {
        Ok(response) => match response.body() {
            anything_ref::get::OkBody::Status200(body) => assert_eq!(body.args.array, test_vec),
            _ => assert!(false),
        },
        _ => assert!(false),
    }
}

#[test]
fn response_array() {
    use httpbin::anything_array;

    let test_vec: Vec<f32> = vec![1.2, 2.3, 4.5];
    let query = anything_array::get::QueryParams {
        array: test_vec.clone(),
    };

    match anything_array::get(query) {
        Ok(response) => match response.body() {
            anything_array::get::OkBody::Status200(body) => assert_eq!(
                body.args.array,
                test_vec.iter().map(ToString::to_string).collect::<Vec<_>>()
            ),
            _ => assert!(false),
        },
        _ => assert!(false),
    }
}

#[test]
fn request() {
    use httpbin::patch;

    let req_body = patch::patch::RequestBody {
        musthave: "foobar",
        ifyouwant: Some(vec![]),
    };

    match patch::patch(req_body.clone()) {
        Ok(response) => match response.body() {
            patch::patch::OkBody::Status200(body) => assert_eq!(body.json, req_body),
            _ => assert!(false),
        },
        _ => assert!(false),
    }
}
<commit_msg>Fix type errors in test<commit_after>#![feature(associated_consts)]
#![feature(use_extern_macros)]
#![allow(plugin_as_library)]

#[macro_use]
extern crate tapioca;

infer_api!(httpbin, "https://raw.githubusercontent.com/OJFord/tapioca/master/tests/schemata/httpbin.yml");

#[test]
fn response_ref() {
    use httpbin::anything_ref;

    let test_vec: Vec<String> = vec!["foobar".into(), "bazzer".into()];
    let query = anything_ref::get::QueryParams {
        array: test_vec.clone(),
    };

    match anything_ref::get(query) {
        Ok(response) => match response.body() {
            anything_ref::get::OkBody::Status200(body) => assert_eq!(body.args.array, test_vec),
            _ => assert!(false),
        },
        _ => assert!(false),
    }
}

#[test]
fn response_array() {
    use httpbin::anything_array;

    let test_vec: Vec<f32> = vec![1.2, 2.3, 4.5];
    let query = anything_array::get::QueryParams {
        array: test_vec.clone(),
    };

    match anything_array::get(query) {
        Ok(response) => match response.body() {
            anything_array::get::OkBody::Status200(body) => assert_eq!(
                body.args.array,
                test_vec.iter().map(ToString::to_string).collect::<Vec<_>>()
            ),
            _ => assert!(false),
        },
        _ => assert!(false),
    }
}

#[test]
fn request() {
    use httpbin::patch;

    let req_body = patch::patch::RequestBody {
        musthave: "foobar".into(),
        ifyouwant: Some(vec![]),
    };

    match patch::patch(&req_body.clone()) {
        Ok(response) => match response.body() {
            patch::patch::OkBody::Status200(body) => assert_eq!(body.json, req_body),
            _ => assert!(false),
        },
        _ => assert!(false),
    }
}
<|endoftext|>