<commit_before>use std::cell::RefCell;
use std::collections::{BTreeSet, HashMap, HashSet, VecDeque};
use std::io::{self, Write};

use cargo::core::{dependency::Kind, Dependency, Package, PackageId, Workspace};
use cargo::ops::{self, Packages};
use cargo::util::{CargoResult, Config};
use failure::{err_msg, format_err};
use serde::{Deserialize, Serialize};
use serde_json;
use tabwriter::TabWriter;

use super::pkg_status::*;
use super::Options;

/// An elaborate workspace containing resolved dependencies and
/// the update status of packages
pub struct ElaborateWorkspace<'ela> {
    pub workspace: &'ela Workspace<'ela>,
    pub pkgs: HashMap<PackageId, Package>,
    pub pkg_deps: HashMap<PackageId, HashMap<PackageId, Dependency>>,
    /// Map of package status
    pub pkg_status: RefCell<HashMap<Vec<&'ela PackageId>, PkgStatus>>,
    /// Whether using workspace mode
    pub workspace_mode: bool,
}

/// A struct to serialize to json with serde
#[derive(Serialize, Deserialize)]
pub struct CrateMetadata {
    pub crate_name: String,
    pub dependencies: HashSet<Metadata>,
}

#[derive(Serialize, Deserialize, Eq, Hash, PartialEq)]
pub struct Metadata {
    pub name: String,
    pub project: String,
    pub compat: String,
    pub latest: String,
    pub kind: Option<String>,
    pub platform: Option<String>,
}

impl<'ela> ElaborateWorkspace<'ela> {
    /// Elaborate a `Workspace`
    pub fn from_workspace(
        workspace: &'ela Workspace<'_>,
        options: &Options,
    ) -> CargoResult<ElaborateWorkspace<'ela>> {
        let specs = Packages::All.to_package_id_specs(workspace)?;
        let (packages, resolve) = ops::resolve_ws_precisely(
            workspace,
            None,
            &options.flag_features,
            options.all_features(),
            options.no_default_features(),
            &specs,
        )?;
        let mut pkgs = HashMap::new();
        let mut pkg_deps = HashMap::new();
        for pkg in packages.get_many(packages.package_ids())? {
            let pkg_id = pkg.package_id();
            pkgs.insert(pkg_id.clone(), pkg.clone());
            let deps = pkg.dependencies();
            let mut dep_map = HashMap::new();
            for dep_id in resolve.deps(pkg_id) {
                for d in deps {
                    if d.matches_id(dep_id.0) {
                        dep_map.insert(dep_id.0.clone(), d.clone());
                        break;
                    }
                }
            }
            pkg_deps.insert(pkg_id.clone(), dep_map);
        }

        Ok(ElaborateWorkspace {
            workspace,
            pkgs,
            pkg_deps,
            pkg_status: RefCell::new(HashMap::new()),
            workspace_mode: options.flag_workspace || workspace.current().is_err(),
        })
    }

    /// Determine root package based on current workspace and CLI options
    pub fn determine_root(&self, options: &Options) -> CargoResult<&PackageId> {
        if let Some(ref root_name) = options.flag_root {
            if let Ok(workspace_root) = self.workspace.current() {
                if root_name == workspace_root.name().as_str() {
                    Ok(workspace_root.package_id())
                } else {
                    for direct_dep in self.pkg_deps[workspace_root.package_id()].keys() {
                        if self.pkgs[direct_dep].name().as_str() == root_name {
                            return Ok(direct_dep);
                        }
                    }
                    return Err(err_msg(
                        "Root is neither the workspace root nor a direct dependency",
                    ));
                }
            } else {
                Err(err_msg(
                    "--root is not allowed when running against a virtual manifest",
                ))
            }
        } else {
            Ok(self.workspace.current()?.package_id())
        }
    }

    /// Find a member based on member name
    fn find_member(&self, member: &PackageId) -> CargoResult<&PackageId> {
        for m in self.workspace.members() {
            // members with the same name in a workspace is not allowed
            // even with different paths
            if member.name() == m.name() {
                return Ok(m.package_id());
            }
        }
        Err(format_err!("Workspace member {} not found", member.name()))
    }

    /// Find a contained package, which is a member or dependency inside the workspace
    fn find_contained_package(&self, name: &str) -> CargoResult<PackageId> {
        let root_path = self.workspace.root();
        for (pkg_id, pkg) in &self.pkgs {
            if pkg.manifest_path().starts_with(root_path) && pkg.name().as_str() == name {
                return Ok(pkg_id.clone());
            }
        }
        Err(format_err!("Cannot find package {} in workspace", name))
    }

    /// Find a direct dependency of a contained package
    pub fn find_direct_dependency(
        &self,
        dependency_name: &str,
        dependent_package_name: &str,
    ) -> CargoResult<PackageId> {
        let dependent_package = self.find_contained_package(dependent_package_name)?;
        for direct_dep in self.pkg_deps[&dependent_package].keys() {
            if direct_dep.name().as_str() == dependency_name {
                return Ok(direct_dep.clone());
            }
        }
        for (pkg_id, pkg) in &self.pkgs {
            if pkg.name().as_str() == dependency_name {
                return Ok(pkg_id.clone());
            }
        }
        Err(format_err!(
            "Direct dependency {} not found for package {}",
            dependency_name,
            dependent_package_name
        ))
    }

    /// Resolve compatible and latest status from the corresponding `ElaborateWorkspace`s
    pub fn resolve_status(
        &'ela self,
        compat: &ElaborateWorkspace<'_>,
        latest: &ElaborateWorkspace<'_>,
        options: &Options,
        _config: &Config,
        root: &'ela PackageId,
    ) -> CargoResult<()> {
        self.pkg_status.borrow_mut().clear();
        let (compat_root, latest_root) = if self.workspace_mode {
            (compat.find_member(root)?, latest.find_member(root)?)
        } else {
            (
                compat.determine_root(options)?,
                latest.determine_root(options)?,
            )
        };

        let mut queue = VecDeque::new();
        queue.push_back((vec![root], Some(compat_root), Some(latest_root)));
        while let Some((path, compat_pkg, latest_pkg)) = queue.pop_front() {
            let pkg = path.last().unwrap();
            let depth = path.len() as i32 - 1;
            // generate pkg_status
            let status = PkgStatus {
                compat: Status::from_versions(pkg.version(), compat_pkg.map(|p| p.version())),
                latest: Status::from_versions(pkg.version(), latest_pkg.map(|p| p.version())),
            };
            debug!(
                _config,
                "STATUS => PKG: {}; PATH: {:?}; COMPAT: {:?}; LATEST: {:?}; STATUS: {:?}",
                pkg,
                path,
                compat_pkg,
                latest_pkg,
                status
            );
            self.pkg_status.borrow_mut().insert(path.clone(), status);
            // next layer
            if options.flag_depth.is_none() || depth < options.flag_depth.unwrap() {
                self.pkg_deps[pkg]
                    .keys()
                    .filter(|dep| !path.contains(dep))
                    .for_each(|dep| {
                        let name = dep.name();
                        let compat_pkg = compat_pkg
                            .and_then(|id| compat.pkg_deps.get(id))
                            .map(|dep_map| dep_map.keys())
                            .and_then(|ref mut deps| deps.find(|dep| dep.name() == name));
                        let latest_pkg = latest_pkg
                            .and_then(|id| latest.pkg_deps.get(id))
                            .map(|dep_map| dep_map.keys())
                            .and_then(|ref mut deps| deps.find(|dep| dep.name() == name));
                        let mut path = path.clone();
                        path.push(dep);
                        queue.push_back((path, compat_pkg, latest_pkg));
                    });
            }
        }

        Ok(())
    }

    /// Print package status to `TabWriter`
    pub fn print_list(
        &'ela self,
        options: &Options,
        root: &'ela PackageId,
        preceding_line: bool,
    ) -> CargoResult<i32> {
        let mut lines = BTreeSet::new();
        let mut queue = VecDeque::new();
        queue.push_back(vec![root]);
        while let Some(path) = queue.pop_front() {
            let pkg = path.last().unwrap();
            let depth = path.len() as i32 - 1;
            // generate lines
            let status = &self.pkg_status.borrow_mut()[&path];
            if (status.compat.is_changed() || status.latest.is_changed())
                && (options.flag_packages.is_empty()
                    || options.flag_packages.contains(&pkg.name().to_string()))
            {
                // name version compatible latest kind platform
                let parent = path.get(path.len() - 2);
                if let Some(parent) = parent {
                    let dependency = &self.pkg_deps[parent][pkg];
                    let label = if self.workspace_mode
                        || parent == &self.workspace.current()?.package_id()
                    {
                        pkg.name().to_string()
                    } else {
                        format!("{}->{}", self.pkgs[parent].name(), pkg.name())
                    };
                    let line = format!(
                        "{}\t{}\t{}\t{}\t{:?}\t{}\n",
                        label,
                        pkg.version(),
                        status.compat.to_string(),
                        status.latest.to_string(),
                        dependency.kind(),
                        dependency
                            .platform()
                            .map(|p| p.to_string())
                            .unwrap_or_else(|| "---".to_owned())
                    );
                    lines.insert(line);
                } else {
                    let line = format!(
                        "{}\t{}\t{}\t{}\t---\t---\n",
                        pkg.name(),
                        pkg.version(),
                        status.compat.to_string(),
                        status.latest.to_string()
                    );
                    lines.insert(line);
                }
            }
            // next layer
            if options.flag_depth.is_none() || depth < options.flag_depth.unwrap() {
                self.pkg_deps[pkg]
                    .keys()
                    .filter(|dep| !path.contains(dep))
                    .filter(|dep| {
                        !self.workspace_mode
                            || !self.workspace.members().any(|mem| &mem.package_id() == dep)
                    })
                    .for_each(|dep| {
                        let mut path = path.clone();
                        path.push(dep);
                        queue.push_back(path);
                    });
            }
        }

        if lines.is_empty() {
            if !self.workspace_mode {
                println!("All dependencies are up to date, yay!");
            }
        } else {
            if preceding_line {
                println!();
            }
            if self.workspace_mode {
                println!("{}\n================", root.name());
            }
            let mut tw = TabWriter::new(vec![]);
            writeln!(&mut tw, "Name\tProject\tCompat\tLatest\tKind\tPlatform")?;
            writeln!(&mut tw, "----\t-------\t------\t------\t----\t--------")?;
            for line in &lines {
                write!(&mut tw, "{}", line)?;
            }
            tw.flush()?;
            write!(
                io::stdout(),
                "{}",
                String::from_utf8(tw.into_inner().unwrap()).unwrap()
            )?;
            io::stdout().flush()?;
        }

        Ok(lines.len() as i32)
    }

    pub fn print_json(
        &'ela self,
        options: &Options,
        root: &'ela PackageId,
        preceding_line: bool,
    ) -> CargoResult<i32> {
        let mut crate_graph = CrateMetadata {
            crate_name: root.name().to_string(),
            dependencies: HashSet::new(),
        };
        let mut queue = VecDeque::new();
        queue.push_back(vec![root]);

        while let Some(path) = queue.pop_front() {
            let pkg = path.last().unwrap();
            let depth = path.len() as i32 - 1;
            // generate lines
            let status = &self.pkg_status.borrow_mut()[&path];
            if (status.compat.is_changed() || status.latest.is_changed())
                && (options.flag_packages.is_empty()
                    || options.flag_packages.contains(&pkg.name().to_string()))
            {
                // name version compatible latest kind platform
                let parent = path.get(path.len() - 2);

                let mut line;

                if let Some(parent) = parent {
                    let dependency = &self.pkg_deps[parent][pkg];
                    let label = if self.workspace_mode
                        || parent == &self.workspace.current()?.package_id()
                    {
                        pkg.name().to_string()
                    } else {
                        format!("{}->{}", self.pkgs[parent].name(), pkg.name())
                    };

                    let dependency_type = match dependency.kind() {
                        Kind::Normal => "Normal",
                        Kind::Development => "Development",
                        Kind::Build => "Build",
                    };

                    line = Metadata {
                        name: label,
                        project: pkg.version().to_string(),
                        compat: status.compat.to_string(),
                        latest: status.latest.to_string(),
                        kind: Some(dependency_type.to_string()),
                        platform: dependency.platform().map(|p| p.to_string()),
                    };
                } else {
                    line = Metadata {
                        name: pkg.name().to_string(),
                        project: pkg.version().to_string(),
                        compat: status.compat.to_string(),
                        latest: status.latest.to_string(),
                        kind: None,
                        platform: None,
                    };
                }

                crate_graph.dependencies.insert(line);
            }
            // next layer
            if options.flag_depth.is_none() || depth < options.flag_depth.unwrap() {
                self.pkg_deps[pkg]
                    .keys()
                    .filter(|dep| !path.contains(dep))
                    .filter(|dep| {
                        !self.workspace_mode
                            || !self.workspace.members().any(|mem| &mem.package_id() == dep)
                    })
                    .for_each(|dep| {
                        let mut path = path.clone();
                        path.push(dep);
                        queue.push_back(path);
                    });
            }
        }

        if crate_graph.dependencies.is_empty() {
            if !self.workspace_mode {
                println!("All dependencies are up to date, yay!");
            }
        } else {
            if preceding_line {
                println!();
            }

            serde_json::to_writer(io::stdout(), &crate_graph)?;
        }

        Ok(crate_graph.dependencies.len() as i32)
    }
}
<commit_msg>Removed the string that prints if all dependencies are updated (artifact from print_lines mode)<commit_after>use std::cell::RefCell;
use std::collections::{BTreeSet, HashMap, HashSet, VecDeque};
use std::io::{self, Write};

use cargo::core::{Dependency, dependency::Kind, Package, PackageId, Workspace};
use cargo::ops::{self, Packages};
use cargo::util::{CargoResult, Config};
use failure::{err_msg, format_err};
use serde::{Deserialize, Serialize};
use serde_json;
use tabwriter::TabWriter;

use super::pkg_status::*;
use super::Options;

/// An elaborate workspace containing resolved dependencies and
/// the update status of packages
pub struct ElaborateWorkspace<'ela> {
    pub workspace: &'ela Workspace<'ela>,
    pub pkgs: HashMap<PackageId, Package>,
    pub pkg_deps: HashMap<PackageId, HashMap<PackageId, Dependency>>,
    /// Map of package status
    pub pkg_status: RefCell<HashMap<Vec<&'ela PackageId>, PkgStatus>>,
    /// Whether using workspace mode
    pub workspace_mode: bool,
}

/// A struct to serialize to json with serde
#[derive(Serialize, Deserialize)]
pub struct CrateMetadata {
    pub crate_name: String,
    pub dependencies: HashSet<Metadata>,
}

#[derive(Serialize, Deserialize, Eq, Hash, PartialEq)]
pub struct Metadata {
    pub name: String,
    pub project: String,
    pub compat: String,
    pub latest: String,
    pub kind: Option<String>,
    pub platform: Option<String>,
}

impl<'ela> ElaborateWorkspace<'ela> {
    /// Elaborate a `Workspace`
    pub fn from_workspace(
        workspace: &'ela Workspace<'_>,
        options: &Options,
    ) -> CargoResult<ElaborateWorkspace<'ela>> {
        let specs = Packages::All.to_package_id_specs(workspace)?;
        let (packages, resolve) = ops::resolve_ws_precisely(
            workspace,
            None,
            &options.flag_features,
            options.all_features(),
            options.no_default_features(),
            &specs,
        )?;
        let mut pkgs = HashMap::new();
        let mut pkg_deps = HashMap::new();
        for pkg in packages.get_many(packages.package_ids())? {
            let pkg_id = pkg.package_id();
            pkgs.insert(pkg_id.clone(), pkg.clone());
            let deps = pkg.dependencies();
            let mut dep_map = HashMap::new();
            for dep_id in resolve.deps(pkg_id) {
                for d in deps {
                    if d.matches_id(dep_id.0) {
                        dep_map.insert(dep_id.0.clone(), d.clone());
                        break;
                    }
                }
            }
            pkg_deps.insert(pkg_id.clone(), dep_map);
        }

        Ok(ElaborateWorkspace {
            workspace,
            pkgs,
            pkg_deps,
            pkg_status: RefCell::new(HashMap::new()),
            workspace_mode: options.flag_workspace || workspace.current().is_err(),
        })
    }

    /// Determine root package based on current workspace and CLI options
    pub fn determine_root(&self, options: &Options) -> CargoResult<&PackageId> {
        if let Some(ref root_name) = options.flag_root {
            if let Ok(workspace_root) = self.workspace.current() {
                if root_name == workspace_root.name().as_str() {
                    Ok(workspace_root.package_id())
                } else {
                    for direct_dep in self.pkg_deps[workspace_root.package_id()].keys() {
                        if self.pkgs[direct_dep].name().as_str() == root_name {
                            return Ok(direct_dep);
                        }
                    }
                    return Err(err_msg(
                        "Root is neither the workspace root nor a direct dependency",
                    ));
                }
            } else {
                Err(err_msg(
                    "--root is not allowed when running against a virtual manifest",
                ))
            }
        } else {
            Ok(self.workspace.current()?.package_id())
        }
    }

    /// Find a member based on member name
    fn find_member(&self, member: &PackageId) -> CargoResult<&PackageId> {
        for m in self.workspace.members() {
            // members with the same name in a workspace is not allowed
            // even with different paths
            if member.name() == m.name() {
                return Ok(m.package_id());
            }
        }
        Err(format_err!("Workspace member {} not found", member.name()))
    }

    /// Find a contained package, which is a member or dependency inside the workspace
    fn find_contained_package(&self, name: &str) -> CargoResult<PackageId> {
        let root_path = self.workspace.root();
        for (pkg_id, pkg) in &self.pkgs {
            if pkg.manifest_path().starts_with(root_path) && pkg.name().as_str() == name {
                return Ok(pkg_id.clone());
            }
        }
        Err(format_err!("Cannot find package {} in workspace", name))
    }

    /// Find a direct dependency of a contained package
    pub fn find_direct_dependency(
        &self,
        dependency_name: &str,
        dependent_package_name: &str,
    ) -> CargoResult<PackageId> {
        let dependent_package = self.find_contained_package(dependent_package_name)?;
        for direct_dep in self.pkg_deps[&dependent_package].keys() {
            if direct_dep.name().as_str() == dependency_name {
                return Ok(direct_dep.clone());
            }
        }
        for (pkg_id, pkg) in &self.pkgs {
            if pkg.name().as_str() == dependency_name {
                return Ok(pkg_id.clone());
            }
        }
        Err(format_err!(
            "Direct dependency {} not found for package {}",
            dependency_name,
            dependent_package_name
        ))
    }

    /// Resolve compatible and latest status from the corresponding `ElaborateWorkspace`s
    pub fn resolve_status(
        &'ela self,
        compat: &ElaborateWorkspace<'_>,
        latest: &ElaborateWorkspace<'_>,
        options: &Options,
        _config: &Config,
        root: &'ela PackageId,
    ) -> CargoResult<()> {
        self.pkg_status.borrow_mut().clear();
        let (compat_root, latest_root) = if self.workspace_mode {
            (compat.find_member(root)?, latest.find_member(root)?)
        } else {
            (
                compat.determine_root(options)?,
                latest.determine_root(options)?,
            )
        };

        let mut queue = VecDeque::new();
        queue.push_back((vec![root], Some(compat_root), Some(latest_root)));
        while let Some((path, compat_pkg, latest_pkg)) = queue.pop_front() {
            let pkg = path.last().unwrap();
            let depth = path.len() as i32 - 1;
            // generate pkg_status
            let status = PkgStatus {
                compat: Status::from_versions(pkg.version(), compat_pkg.map(|p| p.version())),
                latest: Status::from_versions(pkg.version(), latest_pkg.map(|p| p.version())),
            };
            debug!(
                _config,
                "STATUS => PKG: {}; PATH: {:?}; COMPAT: {:?}; LATEST: {:?}; STATUS: {:?}",
                pkg,
                path,
                compat_pkg,
                latest_pkg,
                status
            );
            self.pkg_status.borrow_mut().insert(path.clone(), status);
            // next layer
            if options.flag_depth.is_none() || depth < options.flag_depth.unwrap() {
                self.pkg_deps[pkg]
                    .keys()
                    .filter(|dep| !path.contains(dep))
                    .for_each(|dep| {
                        let name = dep.name();
                        let compat_pkg = compat_pkg
                            .and_then(|id| compat.pkg_deps.get(id))
                            .map(|dep_map| dep_map.keys())
                            .and_then(|ref mut deps| deps.find(|dep| dep.name() == name));
                        let latest_pkg = latest_pkg
                            .and_then(|id| latest.pkg_deps.get(id))
                            .map(|dep_map| dep_map.keys())
                            .and_then(|ref mut deps| deps.find(|dep| dep.name() == name));
                        let mut path = path.clone();
                        path.push(dep);
                        queue.push_back((path, compat_pkg, latest_pkg));
                    });
            }
        }

        Ok(())
    }

    /// Print package status to `TabWriter`
    pub fn print_list(
        &'ela self,
        options: &Options,
        root: &'ela PackageId,
        preceding_line: bool,
    ) -> CargoResult<i32> {
        let mut lines = BTreeSet::new();
        let mut queue = VecDeque::new();
        queue.push_back(vec![root]);
        while let Some(path) = queue.pop_front() {
            let pkg = path.last().unwrap();
            let depth = path.len() as i32 - 1;
            // generate lines
            let status = &self.pkg_status.borrow_mut()[&path];
            if (status.compat.is_changed() || status.latest.is_changed())
                && (options.flag_packages.is_empty()
                    || options.flag_packages.contains(&pkg.name().to_string()))
            {
                // name version compatible latest kind platform
                let parent = path.get(path.len() - 2);
                if let Some(parent) = parent {
                    let dependency = &self.pkg_deps[parent][pkg];
                    let label = if self.workspace_mode
                        || parent == &self.workspace.current()?.package_id()
                    {
                        pkg.name().to_string()
                    } else {
                        format!("{}->{}", self.pkgs[parent].name(), pkg.name())
                    };
                    let line = format!(
                        "{}\t{}\t{}\t{}\t{:?}\t{}\n",
                        label,
                        pkg.version(),
                        status.compat.to_string(),
                        status.latest.to_string(),
                        dependency.kind(),
                        dependency
                            .platform()
                            .map(|p| p.to_string())
                            .unwrap_or_else(|| "---".to_owned())
                    );
                    lines.insert(line);
                } else {
                    let line = format!(
                        "{}\t{}\t{}\t{}\t---\t---\n",
                        pkg.name(),
                        pkg.version(),
                        status.compat.to_string(),
                        status.latest.to_string()
                    );
                    lines.insert(line);
                }
            }
            // next layer
            if options.flag_depth.is_none() || depth < options.flag_depth.unwrap() {
                self.pkg_deps[pkg]
                    .keys()
                    .filter(|dep| !path.contains(dep))
                    .filter(|dep| {
                        !self.workspace_mode
                            || !self.workspace.members().any(|mem| &mem.package_id() == dep)
                    })
                    .for_each(|dep| {
                        let mut path = path.clone();
                        path.push(dep);
                        queue.push_back(path);
                    });
            }
        }

        if lines.is_empty() {
            if !self.workspace_mode {
                println!("All dependencies are up to date, yay!");
            }
        } else {
            if preceding_line {
                println!();
            }
            if self.workspace_mode {
                println!("{}\n================", root.name());
            }
            let mut tw = TabWriter::new(vec![]);
            writeln!(&mut tw, "Name\tProject\tCompat\tLatest\tKind\tPlatform")?;
            writeln!(&mut tw, "----\t-------\t------\t------\t----\t--------")?;
            for line in &lines {
                write!(&mut tw, "{}", line)?;
            }
            tw.flush()?;
            write!(
                io::stdout(),
                "{}",
                String::from_utf8(tw.into_inner().unwrap()).unwrap()
            )?;
            io::stdout().flush()?;
        }

        Ok(lines.len() as i32)
    }

    pub fn print_json(
        &'ela self,
        options: &Options,
        root: &'ela PackageId,
        preceding_line: bool,
    ) -> CargoResult<i32> {
        let mut crate_graph = CrateMetadata {
            crate_name: root.name().to_string(),
            dependencies: HashSet::new(),
        };
        let mut queue = VecDeque::new();
        queue.push_back(vec![root]);

        while let Some(path) = queue.pop_front() {
            let pkg = path.last().unwrap();
            let depth = path.len() as i32 - 1;
            // generate lines
            let status = &self.pkg_status.borrow_mut()[&path];
            if (status.compat.is_changed() || status.latest.is_changed())
                && (options.flag_packages.is_empty()
                    || options.flag_packages.contains(&pkg.name().to_string()))
            {
                // name version compatible latest kind platform
                let parent = path.get(path.len() - 2);

                let mut line;

                if let Some(parent) = parent {
                    let dependency = &self.pkg_deps[parent][pkg];
                    let label = if self.workspace_mode
                        || parent == &self.workspace.current()?.package_id()
                    {
                        pkg.name().to_string()
                    } else {
                        format!("{}->{}", self.pkgs[parent].name(), pkg.name())
                    };

                    let dependency_type =  match dependency.kind() {
                        Kind::Normal => "Normal",
                        Kind::Development => "Development",
                        Kind::Build => "Build"
                    };

                    line = Metadata {
                        name: label,
                        project: pkg.version().to_string(),
                        compat: status.compat.to_string(),
                        latest: status.latest.to_string(),
                        kind: Some(dependency_type.to_string()),
                        platform: dependency
                            .platform()
                            .map(|p| p.to_string())
                    };

                } else {
                    line = Metadata {
                        name: pkg.name().to_string(),
                        project: pkg.version().to_string(),
                        compat: status.compat.to_string(),
                        latest: status.latest.to_string(),
                        kind: None,
                        platform: None,
                    };
                }

                crate_graph.dependencies.insert(line);
            }
            // next layer
            if options.flag_depth.is_none() || depth < options.flag_depth.unwrap() {
                self.pkg_deps[pkg]
                    .keys()
                    .filter(|dep| !path.contains(dep))
                    .filter(|dep| {
                        !self.workspace_mode
                            || !self.workspace.members().any(|mem| &mem.package_id() == dep)
                    })
                    .for_each(|dep| {
                        let mut path = path.clone();
                        path.push(dep);
                        queue.push_back(path);
                    });
            }
        }

        serde_json::to_writer(io::stdout(), &crate_graph)?;

        Ok(crate_graph.dependencies.len() as i32)
    }
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// For lazy_static
#![allow(unsafe_code)]

use dom::TElement;
use element_state::*;
use error_reporting::{ParseErrorReporter, StdoutErrorReporter};
use media_queries::{Device, MediaType};
use properties::{PropertyDeclaration, PropertyDeclarationBlock};
use restyle_hints::{ElementSnapshot, RestyleHint, DependencySet};
use selector_impl::{SelectorImplExt, ServoSelectorImpl};
use selectors::Element;
use selectors::bloom::BloomFilter;
use selectors::matching::DeclarationBlock as GenericDeclarationBlock;
use selectors::matching::{Rule, SelectorMap};
use selectors::parser::SelectorImpl;
use smallvec::VecLike;
use std::collections::HashMap;
use std::hash::BuildHasherDefault;
use std::process;
use std::sync::Arc;
use style_traits::viewport::ViewportConstraints;
use stylesheets::{CSSRuleIteratorExt, Origin, Stylesheet};
use url::Url;
use util::opts;
use util::resource_files::read_resource_file;
use viewport::{MaybeNew, ViewportRuleCascade};

pub type DeclarationBlock = GenericDeclarationBlock<Vec<PropertyDeclaration>>;

lazy_static! {
    pub static ref USER_OR_USER_AGENT_STYLESHEETS: Vec<Stylesheet<ServoSelectorImpl>> = {
        let mut stylesheets = vec!();
        // FIXME: presentational-hints.css should be at author origin with zero specificity.
        //        (Does it make a difference?)
        for &filename in &["user-agent.css", "servo.css", "presentational-hints.css"] {
            match read_resource_file(&[filename]) {
                Ok(res) => {
                    let ua_stylesheet = Stylesheet::from_bytes(
                        &res,
                        Url::parse(&format!("chrome:///{:?}", filename)).unwrap(),
                        None,
                        None,
                        Origin::UserAgent,
                        box StdoutErrorReporter);
                    stylesheets.push(ua_stylesheet);
                }
                Err(..) => {
                    error!("Failed to load UA stylesheet {}!", filename);
                    process::exit(1);
                }
            }
        }
        for &(ref contents, ref url) in &opts::get().user_stylesheets {
            stylesheets.push(Stylesheet::from_bytes(
                &contents, url.clone(), None, None, Origin::User, box StdoutErrorReporter));
        }
        stylesheets
    };
}

lazy_static! {
    pub static ref QUIRKS_MODE_STYLESHEET: Stylesheet<ServoSelectorImpl> = {
        match read_resource_file(&["quirks-mode.css"]) {
            Ok(res) => {
                Stylesheet::from_bytes(
                    &res,
                    url!("chrome:///quirks-mode.css"),
                    None,
                    None,
                    Origin::UserAgent,
                    box StdoutErrorReporter)
            },
            Err(..) => {
                error!("Stylist failed to load 'quirks-mode.css'!");
                process::exit(1);
            }
        }
    };
}

#[derive(HeapSizeOf)]
pub struct Stylist<Impl: SelectorImplExt> {
    // Device that the stylist is currently evaluating against.
    pub device: Device,

    // Viewport constraints based on the current device.
    viewport_constraints: Option<ViewportConstraints>,

    // If true, the quirks-mode stylesheet is applied.
    quirks_mode: bool,

    // If true, the device has changed, and the stylist needs to be updated.
    is_device_dirty: bool,

    // The current selector maps, after evaluating media
    // rules against the current device.
    element_map: PerPseudoElementSelectorMap<Impl>,
    pseudos_map: HashMap<Impl::PseudoElement, PerPseudoElementSelectorMap<Impl>, BuildHasherDefault<::fnv::FnvHasher>>,
    rules_source_order: usize,

    // Selector dependencies used to compute restyle hints.
    state_deps: DependencySet<Impl>,
}

impl<Impl: SelectorImplExt> Stylist<Impl> {
    #[inline]
    pub fn new(device: Device) -> Stylist<Impl> {
        let mut stylist = Stylist {
            viewport_constraints: None,
            device: device,
            is_device_dirty: true,
            quirks_mode: false,

            element_map: PerPseudoElementSelectorMap::new(),
            pseudos_map: HashMap::with_hasher(Default::default()),
            rules_source_order: 0,
            state_deps: DependencySet::new(),
        };

        Impl::each_eagerly_cascaded_pseudo_element(|pseudo| {
            stylist.pseudos_map.insert(pseudo, PerPseudoElementSelectorMap::new());
        });

        // FIXME: Add iso-8859-9.css when the document’s encoding is ISO-8859-8.

        stylist
    }

    pub fn update(&mut self, doc_stylesheets: &[Arc<Stylesheet<Impl>>],
                  stylesheets_changed: bool) -> bool
                  where Impl: 'static {
        if !(self.is_device_dirty || stylesheets_changed) {
            return false;
        }
        self.element_map = PerPseudoElementSelectorMap::new();
        self.pseudos_map = HashMap::with_hasher(Default::default());
        self.rules_source_order = 0;
        self.state_deps.clear();

        for ref stylesheet in Impl::get_user_or_user_agent_stylesheets().iter() {
            self.add_stylesheet(&stylesheet);
        }

        if self.quirks_mode {
            if let Some(s) = Impl::get_quirks_mode_stylesheet() {
                self.add_stylesheet(s);
            }
        }

        for ref stylesheet in doc_stylesheets.iter() {
            self.add_stylesheet(stylesheet);
        }

        self.is_device_dirty = false;
        true
    }

    fn add_stylesheet(&mut self, stylesheet: &Stylesheet<Impl>) {
        let device = &self.device;
        if !stylesheet.is_effective_for_device(device) {
            return;
        }
        let mut rules_source_order = self.rules_source_order;

        // Take apart the StyleRule into individual Rules and insert
        // them into the SelectorMap of that priority.
        macro_rules! append(
            ($style_rule: ident, $priority: ident) => {
                if $style_rule.declarations.$priority.len() > 0 {
                    for selector in &$style_rule.selectors {
                        let map = if let Some(ref pseudo) = selector.pseudo_element {
                            self.pseudos_map.entry(pseudo.clone())
                                            .or_insert_with(PerPseudoElementSelectorMap::new)
                                            .borrow_for_origin(&stylesheet.origin)
                        } else {
                            self.element_map.borrow_for_origin(&stylesheet.origin)
                        };

                        map.$priority.insert(Rule {
                                selector: selector.compound_selectors.clone(),
                                declarations: DeclarationBlock {
                                    specificity: selector.specificity,
                                    declarations: $style_rule.declarations.$priority.clone(),
                                    source_order: rules_source_order,
                                },
                        });
                    }
                }
            };
        );

        for style_rule in stylesheet.effective_rules(&self.device).style() {
            append!(style_rule, normal);
            append!(style_rule, important);
            rules_source_order += 1;
            for selector in &style_rule.selectors {
                self.state_deps.note_selector(selector.compound_selectors.clone());
            }
        }
        self.rules_source_order = rules_source_order;
    }

    pub fn compute_restyle_hint<E>(&self, element: &E,
                                   snapshot: &ElementSnapshot,
                                   // NB: We need to pass current_state as an argument because
                                   // selectors::Element doesn't provide access to ElementState
                                   // directly, and computing it from the ElementState would be
                                   // more expensive than getting it directly from the caller.
                                   current_state: ElementState)
                                   -> RestyleHint
                                   where E: Element<Impl=Impl> + Clone {
        self.state_deps.compute_hint(element, snapshot, current_state)
    }

    pub fn set_device(&mut self, mut device: Device, stylesheets: &[Arc<Stylesheet<Impl>>]) {
        let cascaded_rule = stylesheets.iter()
            .flat_map(|s| s.effective_rules(&self.device).viewport())
            .cascade();

        self.viewport_constraints = ViewportConstraints::maybe_new(device.viewport_size, &cascaded_rule);
        if let Some(ref constraints) = self.viewport_constraints {
            device = Device::new(MediaType::Screen, constraints.size);
        }

        self.is_device_dirty |= stylesheets.iter().any(|stylesheet| {
                stylesheet.rules().media().any(|media_rule|
                    media_rule.evaluate(&self.device) != media_rule.evaluate(&device))
        });

        self.device = device;
    }

    pub fn viewport_constraints(&self) -> &Option<ViewportConstraints> {
        &self.viewport_constraints
    }

    pub fn set_quirks_mode(&mut self, enabled: bool) {
        self.quirks_mode = enabled;
    }

    /// Returns the applicable CSS declarations for the given element. This corresponds to
    /// `ElementRuleCollector` in WebKit.
    ///
    /// The returned boolean indicates whether the style is *shareable*; that is, whether the
    /// matched selectors are simple enough to allow the matching logic to be reduced to the logic
    /// in `css::matching::PrivateMatchMethods::candidate_element_allows_for_style_sharing`.
    pub fn push_applicable_declarations<E, V>(
                                        &self,
                                        element: &E,
                                        parent_bf: Option<&BloomFilter>,
                                        style_attribute: Option<&PropertyDeclarationBlock>,
                                        pseudo_element: Option<Impl::PseudoElement>,
                                        applicable_declarations: &mut V)
                                        -> bool
                                        where E: Element<Impl=Impl> + TElement,
                                              V: VecLike<DeclarationBlock> {
        assert!(!self.is_device_dirty);
        assert!(style_attribute.is_none() || pseudo_element.is_none(),
                "Style attributes do not apply to pseudo-elements");

        let map = match pseudo_element {
            Some(ref pseudo) => match self.pseudos_map.get(pseudo) {
                Some(map) => map,
                // TODO(emilio): get non eagerly-cascaded pseudo-element rules here.
                // Actually assume there are no rules applicable.
                None => return true,
            },
            None => &self.element_map,
        };

        let mut shareable = true;

        // Step 1: Normal user-agent rules.
        map.user_agent.normal.get_all_matching_rules(element,
                                                     parent_bf,
                                                     applicable_declarations,
                                                     &mut shareable);

        // Step 2: Presentational hints.
        let length = applicable_declarations.len();
        element.synthesize_presentational_hints_for_legacy_attributes(applicable_declarations);
        if applicable_declarations.len() != length {
            // Never share style for elements with preshints
            shareable = false;
        }

        // Step 3: User and author normal rules.
        map.user.normal.get_all_matching_rules(element,
                                               parent_bf,
                                               applicable_declarations,
                                               &mut shareable);
        map.author.normal.get_all_matching_rules(element,
                                                 parent_bf,
                                                 applicable_declarations,
                                                 &mut shareable);

        // Step 4: Normal style attributes.
        style_attribute.map(|sa| {
            shareable = false;
            applicable_declarations.push(
                GenericDeclarationBlock::from_declarations(sa.normal.clone()))
        });

        // Step 5: Author-supplied `!important` rules.
        map.author.important.get_all_matching_rules(element,
                                                    parent_bf,
                                                    applicable_declarations,
                                                    &mut shareable);

        // Step 6: `!important` style attributes.
        style_attribute.map(|sa| {
            shareable = false;
            applicable_declarations.push(
                GenericDeclarationBlock::from_declarations(sa.important.clone()))
        });

        // Step 7: User and UA `!important` rules.
        map.user.important.get_all_matching_rules(element,
                                                  parent_bf,
                                                  applicable_declarations,
                                                  &mut shareable);
        map.user_agent.important.get_all_matching_rules(element,
                                                        parent_bf,
                                                        applicable_declarations,
                                                        &mut shareable);

        shareable
    }

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

#[derive(HeapSizeOf)]
struct PerOriginSelectorMap<Impl: SelectorImpl> {
    normal: SelectorMap<Vec<PropertyDeclaration>, Impl>,
    important: SelectorMap<Vec<PropertyDeclaration>, Impl>,
}

impl<Impl: SelectorImpl> PerOriginSelectorMap<Impl> {
    #[inline]
    fn new() -> PerOriginSelectorMap<Impl> {
        PerOriginSelectorMap {
            normal: SelectorMap::new(),
            important: SelectorMap::new(),
        }
    }
}

#[derive(HeapSizeOf)]
struct PerPseudoElementSelectorMap<Impl: SelectorImpl> {
    user_agent: PerOriginSelectorMap<Impl>,
    author: PerOriginSelectorMap<Impl>,
    user: PerOriginSelectorMap<Impl>,
}

impl<Impl: SelectorImpl> PerPseudoElementSelectorMap<Impl> {
    #[inline]
    fn new() -> PerPseudoElementSelectorMap<Impl> {
        PerPseudoElementSelectorMap {
            user_agent: PerOriginSelectorMap::new(),
            author: PerOriginSelectorMap::new(),
            user: PerOriginSelectorMap::new(),
        }
    }

    #[inline]
    fn borrow_for_origin(&mut self, origin: &Origin) -> &mut PerOriginSelectorMap<Impl> {
        match *origin {
            Origin::UserAgent => &mut self.user_agent,
            Origin::Author => &mut self.author,
            Origin::User => &mut self.user,
        }
    }
}
<commit_msg>Auto merge of #10366 - emilio:style-docs, r=SimonSapin<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// For lazy_static
#![allow(unsafe_code)]

use dom::TElement;
use element_state::*;
use error_reporting::{ParseErrorReporter, StdoutErrorReporter};
use media_queries::{Device, MediaType};
use properties::{PropertyDeclaration, PropertyDeclarationBlock};
use restyle_hints::{ElementSnapshot, RestyleHint, DependencySet};
use selector_impl::{SelectorImplExt, ServoSelectorImpl};
use selectors::Element;
use selectors::bloom::BloomFilter;
use selectors::matching::DeclarationBlock as GenericDeclarationBlock;
use selectors::matching::{Rule, SelectorMap};
use selectors::parser::SelectorImpl;
use smallvec::VecLike;
use std::collections::HashMap;
use std::hash::BuildHasherDefault;
use std::process;
use std::sync::Arc;
use style_traits::viewport::ViewportConstraints;
use stylesheets::{CSSRuleIteratorExt, Origin, Stylesheet};
use url::Url;
use util::opts;
use util::resource_files::read_resource_file;
use viewport::{MaybeNew, ViewportRuleCascade};

pub type DeclarationBlock = GenericDeclarationBlock<Vec<PropertyDeclaration>>;

lazy_static! {
    pub static ref USER_OR_USER_AGENT_STYLESHEETS: Vec<Stylesheet<ServoSelectorImpl>> = {
        let mut stylesheets = vec!();
        // FIXME: presentational-hints.css should be at author origin with zero specificity.
        //        (Does it make a difference?)
        for &filename in &["user-agent.css", "servo.css", "presentational-hints.css"] {
            match read_resource_file(&[filename]) {
                Ok(res) => {
                    let ua_stylesheet = Stylesheet::from_bytes(
                        &res,
                        Url::parse(&format!("chrome:///{:?}", filename)).unwrap(),
                        None,
                        None,
                        Origin::UserAgent,
                        box StdoutErrorReporter);
                    stylesheets.push(ua_stylesheet);
                }
                Err(..) => {
                    error!("Failed to load UA stylesheet {}!", filename);
                    process::exit(1);
                }
            }
        }
        for &(ref contents, ref url) in &opts::get().user_stylesheets {
            stylesheets.push(Stylesheet::from_bytes(
                &contents, url.clone(), None, None, Origin::User, box StdoutErrorReporter));
        }
        stylesheets
    };
}

lazy_static! {
    pub static ref QUIRKS_MODE_STYLESHEET: Stylesheet<ServoSelectorImpl> = {
        match read_resource_file(&["quirks-mode.css"]) {
            Ok(res) => {
                Stylesheet::from_bytes(
                    &res,
                    url!("chrome:///quirks-mode.css"),
                    None,
                    None,
                    Origin::UserAgent,
                    box StdoutErrorReporter)
            },
            Err(..) => {
                error!("Stylist failed to load 'quirks-mode.css'!");
                process::exit(1);
            }
        }
    };
}

/// This structure holds all the selectors and device characteristics
/// for a given document. The selectors are converted into `Rule`s
/// (defined in rust-selectors), and introduced in a `SelectorMap`
/// depending on the pseudo-element (see `PerPseudoElementSelectorMap`),
/// stylesheet origin (see `PerOriginSelectorMap`), and priority
/// (see the `normal` and `important` fields in `PerOriginSelectorMap`).
///
/// This structure is effectively created once per pipeline, in the
/// LayoutThread corresponding to that pipeline.
///
/// The stylist is parameterized on `SelectorImplExt`, a trait that extends
/// `selectors::parser::SelectorImpl`, and that allows to customise what
/// pseudo-classes and pseudo-elements are parsed. This is actually either
/// `ServoSelectorImpl`, the implementation used by Servo's layout system in
/// regular builds, or `GeckoSelectorImpl`, the implementation used in the
/// geckolib port.
#[derive(HeapSizeOf)]
pub struct Stylist<Impl: SelectorImplExt> {
    /// Device that the stylist is currently evaluating against.
    pub device: Device,

    /// Viewport constraints based on the current device.
    viewport_constraints: Option<ViewportConstraints>,

    /// If true, the quirks-mode stylesheet is applied.
    quirks_mode: bool,

    /// If true, the device has changed, and the stylist needs to be updated.
    is_device_dirty: bool,

    /// The current selector maps, after evaluating media
    /// rules against the current device.
    element_map: PerPseudoElementSelectorMap<Impl>,
    /// The selector maps corresponding to a given pseudo-element
    /// (depending on the implementation)
    pseudos_map: HashMap<Impl::PseudoElement,
                         PerPseudoElementSelectorMap<Impl>,
                         BuildHasherDefault<::fnv::FnvHasher>>,
    rules_source_order: usize,

    /// Selector dependencies used to compute restyle hints.
    state_deps: DependencySet<Impl>,
}

impl<Impl: SelectorImplExt> Stylist<Impl> {
    #[inline]
    pub fn new(device: Device) -> Stylist<Impl> {
        let mut stylist = Stylist {
            viewport_constraints: None,
            device: device,
            is_device_dirty: true,
            quirks_mode: false,

            element_map: PerPseudoElementSelectorMap::new(),
            pseudos_map: HashMap::with_hasher(Default::default()),
            rules_source_order: 0,
            state_deps: DependencySet::new(),
        };

        Impl::each_eagerly_cascaded_pseudo_element(|pseudo| {
            stylist.pseudos_map.insert(pseudo, PerPseudoElementSelectorMap::new());
        });

        // FIXME: Add iso-8859-9.css when the document’s encoding is ISO-8859-8.

        stylist
    }

    pub fn update(&mut self, doc_stylesheets: &[Arc<Stylesheet<Impl>>],
                  stylesheets_changed: bool) -> bool
                  where Impl: 'static {
        if !(self.is_device_dirty || stylesheets_changed) {
            return false;
        }

        self.element_map = PerPseudoElementSelectorMap::new();
        self.pseudos_map = HashMap::with_hasher(Default::default());
        self.rules_source_order = 0;
        self.state_deps.clear();

        for ref stylesheet in Impl::get_user_or_user_agent_stylesheets().iter() {
            self.add_stylesheet(&stylesheet);
        }

        if self.quirks_mode {
            if let Some(s) = Impl::get_quirks_mode_stylesheet() {
                self.add_stylesheet(s);
            }
        }

        for ref stylesheet in doc_stylesheets.iter() {
            self.add_stylesheet(stylesheet);
        }

        self.is_device_dirty = false;
        true
    }

    fn add_stylesheet(&mut self, stylesheet: &Stylesheet<Impl>) {
        let device = &self.device;
        if !stylesheet.is_effective_for_device(device) {
            return;
        }
        let mut rules_source_order = self.rules_source_order;

        // Take apart the StyleRule into individual Rules and insert
        // them into the SelectorMap of that priority.
        macro_rules! append(
            ($style_rule: ident, $priority: ident) => {
                if !$style_rule.declarations.$priority.is_empty() {
                    for selector in &$style_rule.selectors {
                        let map = if let Some(ref pseudo) = selector.pseudo_element {
                            self.pseudos_map.entry(pseudo.clone())
                                            .or_insert_with(PerPseudoElementSelectorMap::new)
                                            .borrow_for_origin(&stylesheet.origin)
                        } else {
                            self.element_map.borrow_for_origin(&stylesheet.origin)
                        };

                        map.$priority.insert(Rule {
                                selector: selector.compound_selectors.clone(),
                                declarations: DeclarationBlock {
                                    specificity: selector.specificity,
                                    declarations: $style_rule.declarations.$priority.clone(),
                                    source_order: rules_source_order,
                                },
                        });
                    }
                }
            };
        );

        for style_rule in stylesheet.effective_rules(&self.device).style() {
            append!(style_rule, normal);
            append!(style_rule, important);
            rules_source_order += 1;
            for selector in &style_rule.selectors {
                self.state_deps.note_selector(selector.compound_selectors.clone());
            }
        }
        self.rules_source_order = rules_source_order;
    }

    pub fn compute_restyle_hint<E>(&self, element: &E,
                                   snapshot: &ElementSnapshot,
                                   // NB: We need to pass current_state as an argument because
                                   // selectors::Element doesn't provide access to ElementState
                                   // directly, and computing it from the ElementState would be
                                   // more expensive than getting it directly from the caller.
                                   current_state: ElementState)
                                   -> RestyleHint
                                   where E: Element<Impl=Impl> + Clone {
        self.state_deps.compute_hint(element, snapshot, current_state)
    }

    pub fn set_device(&mut self, mut device: Device, stylesheets: &[Arc<Stylesheet<Impl>>]) {
        let cascaded_rule = stylesheets.iter()
            .flat_map(|s| s.effective_rules(&self.device).viewport())
            .cascade();

        self.viewport_constraints = ViewportConstraints::maybe_new(device.viewport_size, &cascaded_rule);
        if let Some(ref constraints) = self.viewport_constraints {
            device = Device::new(MediaType::Screen, constraints.size);
        }

        self.is_device_dirty |= stylesheets.iter().any(|stylesheet| {
                stylesheet.rules().media().any(|media_rule|
                    media_rule.evaluate(&self.device) != media_rule.evaluate(&device))
        });

        self.device = device;
    }

    pub fn viewport_constraints(&self) -> &Option<ViewportConstraints> {
        &self.viewport_constraints
    }

    pub fn set_quirks_mode(&mut self, enabled: bool) {
        self.quirks_mode = enabled;
    }

    /// Returns the applicable CSS declarations for the given element.
    /// This corresponds to `ElementRuleCollector` in WebKit.
    ///
    /// The returned boolean indicates whether the style is *shareable*;
    /// that is, whether the matched selectors are simple enough to allow the
    /// matching logic to be reduced to the logic in
    /// `css::matching::PrivateMatchMethods::candidate_element_allows_for_style_sharing`.
    pub fn push_applicable_declarations<E, V>(
                                        &self,
                                        element: &E,
                                        parent_bf: Option<&BloomFilter>,
                                        style_attribute: Option<&PropertyDeclarationBlock>,
                                        pseudo_element: Option<Impl::PseudoElement>,
                                        applicable_declarations: &mut V)
                                        -> bool
                                        where E: Element<Impl=Impl> + TElement,
                                              V: VecLike<DeclarationBlock> {
        assert!(!self.is_device_dirty);
        assert!(style_attribute.is_none() || pseudo_element.is_none(),
                "Style attributes do not apply to pseudo-elements");

        let map = match pseudo_element {
            Some(ref pseudo) => match self.pseudos_map.get(pseudo) {
                Some(map) => map,
                // TODO(emilio): get non eagerly-cascaded pseudo-element rules here.
                // Actually assume there are no rules applicable.
                None => return true,
            },
            None => &self.element_map,
        };

        let mut shareable = true;

        // Step 1: Normal user-agent rules.
        map.user_agent.normal.get_all_matching_rules(element,
                                                     parent_bf,
                                                     applicable_declarations,
                                                     &mut shareable);

        // Step 2: Presentational hints.
        let length = applicable_declarations.len();
        element.synthesize_presentational_hints_for_legacy_attributes(applicable_declarations);
        if applicable_declarations.len() != length {
            // Never share style for elements with preshints
            shareable = false;
        }

        // Step 3: User and author normal rules.
        map.user.normal.get_all_matching_rules(element,
                                               parent_bf,
                                               applicable_declarations,
                                               &mut shareable);
        map.author.normal.get_all_matching_rules(element,
                                                 parent_bf,
                                                 applicable_declarations,
                                                 &mut shareable);

        // Step 4: Normal style attributes.
        style_attribute.map(|sa| {
            shareable = false;
            applicable_declarations.push(
                GenericDeclarationBlock::from_declarations(sa.normal.clone()))
        });

        // Step 5: Author-supplied `!important` rules.
        map.author.important.get_all_matching_rules(element,
                                                    parent_bf,
                                                    applicable_declarations,
                                                    &mut shareable);

        // Step 6: `!important` style attributes.
        style_attribute.map(|sa| {
            shareable = false;
            applicable_declarations.push(
                GenericDeclarationBlock::from_declarations(sa.important.clone()))
        });

        // Step 7: User and UA `!important` rules.
        map.user.important.get_all_matching_rules(element,
                                                  parent_bf,
                                                  applicable_declarations,
                                                  &mut shareable);
        map.user_agent.important.get_all_matching_rules(element,
                                                        parent_bf,
                                                        applicable_declarations,
                                                        &mut shareable);

        shareable
    }

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

/// Map that contains the CSS rules for a given origin.
#[derive(HeapSizeOf)]
struct PerOriginSelectorMap<Impl: SelectorImpl> {
    /// Rules that contains at least one property declararion with
    /// normal importance.
    normal: SelectorMap<Vec<PropertyDeclaration>, Impl>,
    /// Rules that contains at least one property declararion with
    /// !important.
    important: SelectorMap<Vec<PropertyDeclaration>, Impl>,
}

impl<Impl: SelectorImpl> PerOriginSelectorMap<Impl> {
    #[inline]
    fn new() -> PerOriginSelectorMap<Impl> {
        PerOriginSelectorMap {
            normal: SelectorMap::new(),
            important: SelectorMap::new(),
        }
    }
}

/// Map that contains the CSS rules for a specific PseudoElement
/// (or lack of PseudoElement).
#[derive(HeapSizeOf)]
struct PerPseudoElementSelectorMap<Impl: SelectorImpl> {
    /// Rules from user agent stylesheets
    user_agent: PerOriginSelectorMap<Impl>,
    /// Rules from author stylesheets
    author: PerOriginSelectorMap<Impl>,
    /// Rules from user stylesheets
    user: PerOriginSelectorMap<Impl>,
}

impl<Impl: SelectorImpl> PerPseudoElementSelectorMap<Impl> {
    #[inline]
    fn new() -> PerPseudoElementSelectorMap<Impl> {
        PerPseudoElementSelectorMap {
            user_agent: PerOriginSelectorMap::new(),
            author: PerOriginSelectorMap::new(),
            user: PerOriginSelectorMap::new(),
        }
    }

    #[inline]
    fn borrow_for_origin(&mut self, origin: &Origin) -> &mut PerOriginSelectorMap<Impl> {
        match *origin {
            Origin::UserAgent => &mut self.user_agent,
            Origin::Author => &mut self.author,
            Origin::User => &mut self.user,
        }
    }
}
<|endoftext|>


<commit_before>use flowgger::config::Config;
use flowgger::record::{Record, StructuredData, SDValue, SEVERITY_MAX};
use flowgger::utils;
use serde_json::de;
use serde_json::error::ErrorCode;
use serde_json::error::Error::Syntax;
use serde_json::value::Value;
use super::Decoder;

#[derive(Clone)]
pub struct GelfDecoder;

impl GelfDecoder {
    pub fn new(_config: &Config) -> GelfDecoder {
        GelfDecoder
    }
}

impl Decoder for GelfDecoder {
    fn decode(&self, line: &str) -> Result<Record, &'static str> {
        let mut sd = StructuredData::new(None);
        let mut ts = None;
        let mut hostname = None;
        let mut msg = None;
        let mut full_msg = None;
        let mut severity = None;

        let obj = match de::from_str(line) {
            x @ Ok(_) => x,
            Err(Syntax(ErrorCode::InvalidUnicodeCodePoint, _, _)) => {
                de::from_str(&line.replace('\n', r"\n"))
            }
            x @ _ => x,
        };
        let obj: Value = try!(obj.or(Err("Invalid GELF input, unable to parse as a JSON object")));
        let obj = try!(obj.as_object().ok_or("Empty GELF input"));
        for (key, value) in obj {
            match key.as_ref() {
                "timestamp" => ts = Some(try!(value.as_f64().ok_or("Invalid GELF timestamp"))),
                "host" => {
                    hostname = Some(try!(value.as_str()
                            .ok_or("GELF host name must be a string"))
                        .to_owned())
                }
                "short_message" => {
                    msg = Some(try!(value.as_str().ok_or("GELF short message must be a string"))
                        .to_owned())
                }
                "full_message" => {
                    full_msg = Some(try!(value.as_str()
                            .ok_or("GELF full message must be a string"))
                        .to_owned())
                }
                "version" => {
                    match try!(value.as_str().ok_or("GELF version must be a string")) {
                        "1.0" | "1.1" => {}
                        _ => return Err("Unsupported GELF version"),
                    }
                }
                "level" => {
                    let severity_given = try!(value.as_u64().ok_or("Invalid severity level"));
                    if severity_given > SEVERITY_MAX as u64 {
                        return Err("Invalid severity level (too high)");
                    }
                    severity = Some(severity_given as u8)
                }
                name => {
                    let sd_value: SDValue = match *value {
                        Value::String(ref value) => SDValue::String(value.to_owned()),
                        Value::Bool(value) => SDValue::Bool(value),
                        Value::F64(value) => SDValue::F64(value),
                        Value::I64(value) => SDValue::I64(value),
                        Value::U64(value) => SDValue::U64(value),
                        Value::Null => SDValue::Null,
                        _ => return Err("Invalid value type in structured data"),
                    };
                    let name = if name.starts_with('_') {
                        name.to_owned()
                    } else {
                        format!("_{}", name)
                    };
                    sd.pairs.push((name, sd_value));
                }
            }
        }
        let record = Record {
            ts: ts.unwrap_or_else(|| utils::PreciseTimestamp::now().as_f64()),
            hostname: try!(hostname.ok_or("Missing hostname")),
            facility: None,
            severity: severity,
            appname: None,
            procid: None,
            msgid: None,
            sd: if sd.pairs.is_empty() { None } else { Some(sd) },
            msg: msg,
            full_msg: full_msg,
        };
        Ok(record)
    }
}

#[test]
fn test_gelf() {
    let msg = r#"{"version":"1.1", "host": "example.org","short_message": "A short message that helps you identify what is going on", "full_message": "Backtrace here\n
more stuff", "timestamp": 1385053862.3072, "level": 1, "_user_id": 9001, "_some_info": "foo", "_some_env_var": "bar"}"#;
    let res = GelfDecoder.decode(msg).unwrap();
    assert!(res.ts == 1385053862.3072);
    assert!(res.hostname == "example.org");
    assert!(res.msg.unwrap() == "A short message that helps you identify what is going on");
    assert!(res.full_msg.unwrap() == "Backtrace here\n\nmore stuff");
    assert!(res.severity.unwrap() == 1);

    let sd = res.sd.unwrap();
    let pairs = sd.pairs;
    assert!(pairs.iter().cloned().any(|(k, v)| if let SDValue::U64(v) = v {
        k == "_user_id" && v == 9001
    } else {
        false
    }));
    assert!(pairs.iter().cloned().any(|(k, v)| if let SDValue::String(v) = v {
        k == "_some_info" && v == "foo"
    } else {
        false
    }));
    assert!(pairs.iter().cloned().any(|(k, v)| if let SDValue::String(v) = v {
        k == "_some_env_var" && v == "bar"
    } else {
        false
    }));
}
<commit_msg>Straight `\n` characters are *not* acceptable in GELF messages<commit_after>use flowgger::config::Config;
use flowgger::record::{Record, StructuredData, SDValue, SEVERITY_MAX};
use flowgger::utils;
use serde_json::de;
use serde_json::error::ErrorCode;
use serde_json::error::Error::Syntax;
use serde_json::value::Value;
use super::Decoder;

#[derive(Clone)]
pub struct GelfDecoder;

impl GelfDecoder {
    pub fn new(_config: &Config) -> GelfDecoder {
        GelfDecoder
    }
}

impl Decoder for GelfDecoder {
    fn decode(&self, line: &str) -> Result<Record, &'static str> {
        let mut sd = StructuredData::new(None);
        let mut ts = None;
        let mut hostname = None;
        let mut msg = None;
        let mut full_msg = None;
        let mut severity = None;

        let obj = match de::from_str(line) {
            x @ Ok(_) => x,
            Err(Syntax(ErrorCode::InvalidUnicodeCodePoint, _, _)) => {
                de::from_str(&line.replace('\n', r"\n"))
            }
            x @ _ => x,
        };
        let obj: Value = try!(obj.or(Err("Invalid GELF input, unable to parse as a JSON object")));
        let obj = try!(obj.as_object().ok_or("Empty GELF input"));
        for (key, value) in obj {
            match key.as_ref() {
                "timestamp" => ts = Some(try!(value.as_f64().ok_or("Invalid GELF timestamp"))),
                "host" => {
                    hostname = Some(try!(value.as_str()
                            .ok_or("GELF host name must be a string"))
                        .to_owned())
                }
                "short_message" => {
                    msg = Some(try!(value.as_str().ok_or("GELF short message must be a string"))
                        .to_owned())
                }
                "full_message" => {
                    full_msg = Some(try!(value.as_str()
                            .ok_or("GELF full message must be a string"))
                        .to_owned())
                }
                "version" => {
                    match try!(value.as_str().ok_or("GELF version must be a string")) {
                        "1.0" | "1.1" => {}
                        _ => return Err("Unsupported GELF version"),
                    }
                }
                "level" => {
                    let severity_given = try!(value.as_u64().ok_or("Invalid severity level"));
                    if severity_given > SEVERITY_MAX as u64 {
                        return Err("Invalid severity level (too high)");
                    }
                    severity = Some(severity_given as u8)
                }
                name => {
                    let sd_value: SDValue = match *value {
                        Value::String(ref value) => SDValue::String(value.to_owned()),
                        Value::Bool(value) => SDValue::Bool(value),
                        Value::F64(value) => SDValue::F64(value),
                        Value::I64(value) => SDValue::I64(value),
                        Value::U64(value) => SDValue::U64(value),
                        Value::Null => SDValue::Null,
                        _ => return Err("Invalid value type in structured data"),
                    };
                    let name = if name.starts_with('_') {
                        name.to_owned()
                    } else {
                        format!("_{}", name)
                    };
                    sd.pairs.push((name, sd_value));
                }
            }
        }
        let record = Record {
            ts: ts.unwrap_or_else(|| utils::PreciseTimestamp::now().as_f64()),
            hostname: try!(hostname.ok_or("Missing hostname")),
            facility: None,
            severity: severity,
            appname: None,
            procid: None,
            msgid: None,
            sd: if sd.pairs.is_empty() { None } else { Some(sd) },
            msg: msg,
            full_msg: full_msg,
        };
        Ok(record)
    }
}

#[test]
fn test_gelf() {
    let msg = r#"{"version":"1.1", "host": "example.org","short_message": "A short message that helps you identify what is going on", "full_message": "Backtrace here\n\nmore stuff", "timestamp": 1385053862.3072, "level": 1, "_user_id": 9001, "_some_info": "foo", "_some_env_var": "bar"}"#;
    let res = GelfDecoder.decode(msg).unwrap();
    assert!(res.ts == 1385053862.3072);
    assert!(res.hostname == "example.org");
    assert!(res.msg.unwrap() == "A short message that helps you identify what is going on");
    assert!(res.full_msg.unwrap() == "Backtrace here\n\nmore stuff");
    assert!(res.severity.unwrap() == 1);

    let sd = res.sd.unwrap();
    let pairs = sd.pairs;
    assert!(pairs.iter().cloned().any(|(k, v)| if let SDValue::U64(v) = v {
        k == "_user_id" && v == 9001
    } else {
        false
    }));
    assert!(pairs.iter().cloned().any(|(k, v)| if let SDValue::String(v) = v {
        k == "_some_info" && v == "foo"
    } else {
        false
    }));
    assert!(pairs.iter().cloned().any(|(k, v)| if let SDValue::String(v) = v {
        k == "_some_env_var" && v == "bar"
    } else {
        false
    }));
}
<|endoftext|>


<commit_before>//! Lexer analyzes raw input string and produces lexemes (tokens).
//! It is just a bridge to `rustc_lexer`.

use crate::{
    SyntaxError, SyntaxErrorKind,
    SyntaxKind::{self, *},
    TextRange, TextUnit,
};

/// A token of Rust source.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Token {
    /// The kind of token.
    pub kind: SyntaxKind,
    /// The length of the token.
    pub len: TextUnit,
}

/// Break a string up into its component tokens.
/// Beware that it checks for shebang first and its length contributes to resulting
/// tokens offsets.
pub fn tokenize(text: &str) -> (Vec<Token>, Vec<SyntaxError>) {
    // non-empty string is a precondtion of `rustc_lexer::strip_shebang()`.
    if text.is_empty() {
        return Default::default();
    }

    let mut tokens = Vec::new();
    let mut errors = Vec::new();

    let mut offset: usize = rustc_lexer::strip_shebang(text)
        .map(|shebang_len| {
            tokens.push(Token { kind: SHEBANG, len: TextUnit::from_usize(shebang_len) });
            shebang_len
        })
        .unwrap_or(0);

    let text_without_shebang = &text[offset..];

    for rustc_token in rustc_lexer::tokenize(text_without_shebang) {
        let token_len = TextUnit::from_usize(rustc_token.len);
        let token_range = TextRange::offset_len(TextUnit::from_usize(offset), token_len);

        let (syntax_kind, error) =
            rustc_token_kind_to_syntax_kind(&rustc_token.kind, &text[token_range]);

        tokens.push(Token { kind: syntax_kind, len: token_len });

        if let Some(error) = error {
            errors.push(SyntaxError::new(SyntaxErrorKind::TokenizeError(error), token_range));
        }

        offset += rustc_token.len;
    }

    (tokens, errors)
}

/// Returns `SyntaxKind` and `Option<SyntaxError>` of the first token
/// encountered at the beginning of the string.
///
/// Returns `None` if the string contains zero *or two or more* tokens.
/// The token is malformed if the returned error is not `None`.
///
/// Beware that unescape errors are not checked at tokenization time.
pub fn lex_single_syntax_kind(text: &str) -> Option<(SyntaxKind, Option<SyntaxError>)> {
    first_token(text)
        .filter(|(token, _)| token.len.to_usize() == text.len())
        .map(|(token, error)| (token.kind, error))
}

/// The same as `lex_single_syntax_kind()` but returns only `SyntaxKind` and
/// returns `None` if any tokenization error occured.
///
/// Beware that unescape errors are not checked at tokenization time.
pub fn lex_single_valid_syntax_kind(text: &str) -> Option<SyntaxKind> {
    first_token(text)
        .filter(|(token, error)| !error.is_some() && token.len.to_usize() == text.len())
        .map(|(token, _error)| token.kind)
}

/// Returns `SyntaxKind` and `Option<SyntaxError>` of the first token
/// encountered at the beginning of the string.
///
/// Returns `None` if the string contains zero tokens or if the token was parsed
/// with an error.
/// The token is malformed if the returned error is not `None`.
///
/// Beware that unescape errors are not checked at tokenization time.
fn first_token(text: &str) -> Option<(Token, Option<SyntaxError>)> {
    // non-empty string is a precondtion of `rustc_lexer::first_token()`.
    if text.is_empty() {
        return None;
    }

    let rustc_token = rustc_lexer::first_token(text);
    let (syntax_kind, error) = rustc_token_kind_to_syntax_kind(&rustc_token.kind, text);

    let token = Token { kind: syntax_kind, len: TextUnit::from_usize(rustc_token.len) };
    let error = error.map(|error| {
        SyntaxError::new(
            SyntaxErrorKind::TokenizeError(error),
            TextRange::from_to(TextUnit::from(0), TextUnit::of_str(text)),
        )
    });

    Some((token, error))
}

// FIXME: simplify TokenizeError to `SyntaxError(String, TextRange)` as per @matklad advice:
// https://github.com/rust-analyzer/rust-analyzer/pull/2911/files#r371175067

/// Describes the values of `SyntaxErrorKind::TokenizeError` enum variant.
/// It describes all the types of errors that may happen during the tokenization
/// of Rust source.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum TokenizeError {
    /// Base prefix was provided, but there were no digits
    /// after it, e.g. `0x`, `0b`.
    EmptyInt,
    /// Float exponent lacks digits e.g. `12.34e+`, `12.3E+`, `12e-`, `1_E-`,
    EmptyExponent,

    /// Block comment lacks trailing delimiter `*/`
    UnterminatedBlockComment,
    /// Character literal lacks trailing delimiter `'`
    UnterminatedChar,
    /// Characterish byte literal lacks trailing delimiter `'`
    UnterminatedByte,
    /// String literal lacks trailing delimiter `"`
    UnterminatedString,
    /// Byte string literal lacks trailing delimiter `"`
    UnterminatedByteString,
    /// Raw literal lacks trailing delimiter e.g. `"##`
    UnterminatedRawString,
    /// Raw byte string literal lacks trailing delimiter e.g. `"##`
    UnterminatedRawByteString,

    /// Raw string lacks a quote after the pound characters e.g. `r###`
    UnstartedRawString,
    /// Raw byte string lacks a quote after the pound characters e.g. `br###`
    UnstartedRawByteString,

    /// Lifetime starts with a number e.g. `'4ever`
    LifetimeStartsWithNumber,
}

fn rustc_token_kind_to_syntax_kind(
    rustc_token_kind: &rustc_lexer::TokenKind,
    token_text: &str,
) -> (SyntaxKind, Option<TokenizeError>) {
    // A note on an intended tradeoff:
    // We drop some useful infromation here (see patterns with double dots `..`)
    // Storing that info in `SyntaxKind` is not possible due to its layout requirements of
    // being `u16` that come from `rowan::SyntaxKind`.

    let syntax_kind = {
        use rustc_lexer::TokenKind as TK;
        use TokenizeError as TE;

        match rustc_token_kind {
            TK::LineComment => COMMENT,

            TK::BlockComment { terminated: true } => COMMENT,
            TK::BlockComment { terminated: false } => {
                return (COMMENT, Some(TE::UnterminatedBlockComment));
            }

            TK::Whitespace => WHITESPACE,

            TK::Ident => {
                if token_text == "_" {
                    UNDERSCORE
                } else {
                    SyntaxKind::from_keyword(token_text).unwrap_or(IDENT)
                }
            }

            TK::RawIdent => IDENT,
            TK::Literal { kind, .. } => return match_literal_kind(&kind),

            TK::Lifetime { starts_with_number: false } => LIFETIME,
            TK::Lifetime { starts_with_number: true } => {
                return (LIFETIME, Some(TE::LifetimeStartsWithNumber))
            }

            TK::Semi => SEMI,
            TK::Comma => COMMA,
            TK::Dot => DOT,
            TK::OpenParen => L_PAREN,
            TK::CloseParen => R_PAREN,
            TK::OpenBrace => L_CURLY,
            TK::CloseBrace => R_CURLY,
            TK::OpenBracket => L_BRACK,
            TK::CloseBracket => R_BRACK,
            TK::At => AT,
            TK::Pound => POUND,
            TK::Tilde => TILDE,
            TK::Question => QUESTION,
            TK::Colon => COLON,
            TK::Dollar => DOLLAR,
            TK::Eq => EQ,
            TK::Not => EXCL,
            TK::Lt => L_ANGLE,
            TK::Gt => R_ANGLE,
            TK::Minus => MINUS,
            TK::And => AMP,
            TK::Or => PIPE,
            TK::Plus => PLUS,
            TK::Star => STAR,
            TK::Slash => SLASH,
            TK::Caret => CARET,
            TK::Percent => PERCENT,
            TK::Unknown => ERROR,
        }
    };

    return (syntax_kind, None);

    fn match_literal_kind(kind: &rustc_lexer::LiteralKind) -> (SyntaxKind, Option<TokenizeError>) {
        use rustc_lexer::LiteralKind as LK;
        use TokenizeError as TE;

        #[rustfmt::skip]
        let syntax_kind = match *kind {
            LK::Int { empty_int: false, .. } => INT_NUMBER,
            LK::Int { empty_int: true, .. } => {
                return (INT_NUMBER, Some(TE::EmptyInt))
            }

            LK::Float { empty_exponent: false, .. } => FLOAT_NUMBER,
            LK::Float { empty_exponent: true, .. } => {
                return (FLOAT_NUMBER, Some(TE::EmptyExponent))
            }

            LK::Char { terminated: true } => CHAR,
            LK::Char { terminated: false } => {
                return (CHAR, Some(TE::UnterminatedChar))
            }

            LK::Byte { terminated: true } => BYTE,
            LK::Byte { terminated: false } => {
                return (BYTE, Some(TE::UnterminatedByte))
            }

            LK::Str { terminated: true } => STRING,
            LK::Str { terminated: false } => {
                return (STRING, Some(TE::UnterminatedString))
            }

            LK::ByteStr { terminated: true } => BYTE_STRING,
            LK::ByteStr { terminated: false } => {
                return (BYTE_STRING, Some(TE::UnterminatedByteString))
            }

            LK::RawStr { started: true, terminated: true, .. } => RAW_STRING,
            LK::RawStr { started: true, terminated: false, .. } => {
                return (RAW_STRING, Some(TE::UnterminatedRawString))
            }
            LK::RawStr { started: false, .. } => {
                return (RAW_STRING, Some(TE::UnstartedRawString))
            }

            LK::RawByteStr { started: true, terminated: true, .. } => RAW_BYTE_STRING,
            LK::RawByteStr { started: true, terminated: false, .. } => {
                return (RAW_BYTE_STRING, Some(TE::UnterminatedRawByteString))
            }
            LK::RawByteStr { started: false, .. } => {
                return (RAW_BYTE_STRING, Some(TE::UnstartedRawByteString))
            }
        };

        (syntax_kind, None)
    }
}
<commit_msg>ra_syntax: rename first_token() -> lex_first_token()<commit_after>//! Lexer analyzes raw input string and produces lexemes (tokens).
//! It is just a bridge to `rustc_lexer`.

use crate::{
    SyntaxError, SyntaxErrorKind,
    SyntaxKind::{self, *},
    TextRange, TextUnit,
};

/// A token of Rust source.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Token {
    /// The kind of token.
    pub kind: SyntaxKind,
    /// The length of the token.
    pub len: TextUnit,
}

/// Break a string up into its component tokens.
/// Beware that it checks for shebang first and its length contributes to resulting
/// tokens offsets.
pub fn tokenize(text: &str) -> (Vec<Token>, Vec<SyntaxError>) {
    // non-empty string is a precondtion of `rustc_lexer::strip_shebang()`.
    if text.is_empty() {
        return Default::default();
    }

    let mut tokens = Vec::new();
    let mut errors = Vec::new();

    let mut offset: usize = rustc_lexer::strip_shebang(text)
        .map(|shebang_len| {
            tokens.push(Token { kind: SHEBANG, len: TextUnit::from_usize(shebang_len) });
            shebang_len
        })
        .unwrap_or(0);

    let text_without_shebang = &text[offset..];

    for rustc_token in rustc_lexer::tokenize(text_without_shebang) {
        let token_len = TextUnit::from_usize(rustc_token.len);
        let token_range = TextRange::offset_len(TextUnit::from_usize(offset), token_len);

        let (syntax_kind, error) =
            rustc_token_kind_to_syntax_kind(&rustc_token.kind, &text[token_range]);

        tokens.push(Token { kind: syntax_kind, len: token_len });

        if let Some(error) = error {
            errors.push(SyntaxError::new(SyntaxErrorKind::TokenizeError(error), token_range));
        }

        offset += rustc_token.len;
    }

    (tokens, errors)
}

/// Returns `SyntaxKind` and `Option<SyntaxError>` of the first token
/// encountered at the beginning of the string.
///
/// Returns `None` if the string contains zero *or two or more* tokens.
/// The token is malformed if the returned error is not `None`.
///
/// Beware that unescape errors are not checked at tokenization time.
pub fn lex_single_syntax_kind(text: &str) -> Option<(SyntaxKind, Option<SyntaxError>)> {
    lex_first_token(text)
        .filter(|(token, _)| token.len.to_usize() == text.len())
        .map(|(token, error)| (token.kind, error))
}

/// The same as `lex_single_syntax_kind()` but returns only `SyntaxKind` and
/// returns `None` if any tokenization error occured.
///
/// Beware that unescape errors are not checked at tokenization time.
pub fn lex_single_valid_syntax_kind(text: &str) -> Option<SyntaxKind> {
    lex_first_token(text)
        .filter(|(token, error)| !error.is_some() && token.len.to_usize() == text.len())
        .map(|(token, _error)| token.kind)
}

/// Returns `SyntaxKind` and `Option<SyntaxError>` of the first token
/// encountered at the beginning of the string.
///
/// Returns `None` if the string contains zero tokens or if the token was parsed
/// with an error.
/// The token is malformed if the returned error is not `None`.
///
/// Beware that unescape errors are not checked at tokenization time.
fn lex_first_token(text: &str) -> Option<(Token, Option<SyntaxError>)> {
    // non-empty string is a precondtion of `rustc_lexer::first_token()`.
    if text.is_empty() {
        return None;
    }

    let rustc_token = rustc_lexer::first_token(text);
    let (syntax_kind, error) = rustc_token_kind_to_syntax_kind(&rustc_token.kind, text);

    let token = Token { kind: syntax_kind, len: TextUnit::from_usize(rustc_token.len) };
    let error = error.map(|error| {
        SyntaxError::new(
            SyntaxErrorKind::TokenizeError(error),
            TextRange::from_to(TextUnit::from(0), TextUnit::of_str(text)),
        )
    });

    Some((token, error))
}

// FIXME: simplify TokenizeError to `SyntaxError(String, TextRange)` as per @matklad advice:
// https://github.com/rust-analyzer/rust-analyzer/pull/2911/files#r371175067

/// Describes the values of `SyntaxErrorKind::TokenizeError` enum variant.
/// It describes all the types of errors that may happen during the tokenization
/// of Rust source.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum TokenizeError {
    /// Base prefix was provided, but there were no digits
    /// after it, e.g. `0x`, `0b`.
    EmptyInt,
    /// Float exponent lacks digits e.g. `12.34e+`, `12.3E+`, `12e-`, `1_E-`,
    EmptyExponent,

    /// Block comment lacks trailing delimiter `*/`
    UnterminatedBlockComment,
    /// Character literal lacks trailing delimiter `'`
    UnterminatedChar,
    /// Characterish byte literal lacks trailing delimiter `'`
    UnterminatedByte,
    /// String literal lacks trailing delimiter `"`
    UnterminatedString,
    /// Byte string literal lacks trailing delimiter `"`
    UnterminatedByteString,
    /// Raw literal lacks trailing delimiter e.g. `"##`
    UnterminatedRawString,
    /// Raw byte string literal lacks trailing delimiter e.g. `"##`
    UnterminatedRawByteString,

    /// Raw string lacks a quote after the pound characters e.g. `r###`
    UnstartedRawString,
    /// Raw byte string lacks a quote after the pound characters e.g. `br###`
    UnstartedRawByteString,

    /// Lifetime starts with a number e.g. `'4ever`
    LifetimeStartsWithNumber,
}

fn rustc_token_kind_to_syntax_kind(
    rustc_token_kind: &rustc_lexer::TokenKind,
    token_text: &str,
) -> (SyntaxKind, Option<TokenizeError>) {
    // A note on an intended tradeoff:
    // We drop some useful infromation here (see patterns with double dots `..`)
    // Storing that info in `SyntaxKind` is not possible due to its layout requirements of
    // being `u16` that come from `rowan::SyntaxKind`.

    let syntax_kind = {
        use rustc_lexer::TokenKind as TK;
        use TokenizeError as TE;

        match rustc_token_kind {
            TK::LineComment => COMMENT,

            TK::BlockComment { terminated: true } => COMMENT,
            TK::BlockComment { terminated: false } => {
                return (COMMENT, Some(TE::UnterminatedBlockComment));
            }

            TK::Whitespace => WHITESPACE,

            TK::Ident => {
                if token_text == "_" {
                    UNDERSCORE
                } else {
                    SyntaxKind::from_keyword(token_text).unwrap_or(IDENT)
                }
            }

            TK::RawIdent => IDENT,
            TK::Literal { kind, .. } => return match_literal_kind(&kind),

            TK::Lifetime { starts_with_number: false } => LIFETIME,
            TK::Lifetime { starts_with_number: true } => {
                return (LIFETIME, Some(TE::LifetimeStartsWithNumber))
            }

            TK::Semi => SEMI,
            TK::Comma => COMMA,
            TK::Dot => DOT,
            TK::OpenParen => L_PAREN,
            TK::CloseParen => R_PAREN,
            TK::OpenBrace => L_CURLY,
            TK::CloseBrace => R_CURLY,
            TK::OpenBracket => L_BRACK,
            TK::CloseBracket => R_BRACK,
            TK::At => AT,
            TK::Pound => POUND,
            TK::Tilde => TILDE,
            TK::Question => QUESTION,
            TK::Colon => COLON,
            TK::Dollar => DOLLAR,
            TK::Eq => EQ,
            TK::Not => EXCL,
            TK::Lt => L_ANGLE,
            TK::Gt => R_ANGLE,
            TK::Minus => MINUS,
            TK::And => AMP,
            TK::Or => PIPE,
            TK::Plus => PLUS,
            TK::Star => STAR,
            TK::Slash => SLASH,
            TK::Caret => CARET,
            TK::Percent => PERCENT,
            TK::Unknown => ERROR,
        }
    };

    return (syntax_kind, None);

    fn match_literal_kind(kind: &rustc_lexer::LiteralKind) -> (SyntaxKind, Option<TokenizeError>) {
        use rustc_lexer::LiteralKind as LK;
        use TokenizeError as TE;

        #[rustfmt::skip]
        let syntax_kind = match *kind {
            LK::Int { empty_int: false, .. } => INT_NUMBER,
            LK::Int { empty_int: true, .. } => {
                return (INT_NUMBER, Some(TE::EmptyInt))
            }

            LK::Float { empty_exponent: false, .. } => FLOAT_NUMBER,
            LK::Float { empty_exponent: true, .. } => {
                return (FLOAT_NUMBER, Some(TE::EmptyExponent))
            }

            LK::Char { terminated: true } => CHAR,
            LK::Char { terminated: false } => {
                return (CHAR, Some(TE::UnterminatedChar))
            }

            LK::Byte { terminated: true } => BYTE,
            LK::Byte { terminated: false } => {
                return (BYTE, Some(TE::UnterminatedByte))
            }

            LK::Str { terminated: true } => STRING,
            LK::Str { terminated: false } => {
                return (STRING, Some(TE::UnterminatedString))
            }

            LK::ByteStr { terminated: true } => BYTE_STRING,
            LK::ByteStr { terminated: false } => {
                return (BYTE_STRING, Some(TE::UnterminatedByteString))
            }

            LK::RawStr { started: true, terminated: true, .. } => RAW_STRING,
            LK::RawStr { started: true, terminated: false, .. } => {
                return (RAW_STRING, Some(TE::UnterminatedRawString))
            }
            LK::RawStr { started: false, .. } => {
                return (RAW_STRING, Some(TE::UnstartedRawString))
            }

            LK::RawByteStr { started: true, terminated: true, .. } => RAW_BYTE_STRING,
            LK::RawByteStr { started: true, terminated: false, .. } => {
                return (RAW_BYTE_STRING, Some(TE::UnterminatedRawByteString))
            }
            LK::RawByteStr { started: false, .. } => {
                return (RAW_BYTE_STRING, Some(TE::UnstartedRawByteString))
            }
        };

        (syntax_kind, None)
    }
}
<|endoftext|>


<commit_before>//
// Copyright 2014-2016 the slack-rs authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//
// This is a simple example of using slack-rs.
// You can run it with `cargo run --example slack_example -- <api_key>`
//
// NOTE: This will post in the #general channel of the account you connect
// to.
//

use slack;
use slack::{Event, RtmClient};

struct MyHandler;

#[allow(unused_variables)]
impl slack::EventHandler for MyHandler {
    fn on_event(&mut self, cli: &RtmClient, event: Event) {
        println!("on_event(event: {:?})", event);
    }

    fn on_close(&mut self, cli: &RtmClient) {
        println!("on_close");
    }

    fn on_connect(&mut self, cli: &RtmClient) {
        println!("on_connect");
        // find the general channel id from the `StartResponse`
        let general_channel_id = cli.start_response()
            .channels
            .as_ref()
            .and_then(|channels| {
                          channels
                              .iter()
                              .find(|chan| match chan.name {
                                        None => false,
                                        Some(ref name) => name == "general",
                                    })
                      })
            .and_then(|chan| chan.id.as_ref())
            .expect("general channel not found");
        let _ = cli.sender().send_message(&general_channel_id, "Hello world! (rtm)");
        // Send a message over the real time api websocket
    }
}

fn main() {
    let args: Vec<String> = std::env::args().collect();
    let api_key = match args.len() {
        0 | 1 => panic!("No api-key in args! Usage: cargo run --example slack_example -- <api-key>"),
        x => args[x - 1].clone(),
    };
    let mut handler = MyHandler;
    let r = RtmClient::login_and_run(&api_key, &mut handler);
    match r {
        Ok(_) => {}
        Err(err) => panic!("Error: {}", err),
    }
}
<commit_msg>have example post message in response to Hello event rather than connect<commit_after>//
// Copyright 2014-2016 the slack-rs authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//
// This is a simple example of using slack-rs.
// You can run it with `cargo run --example slack_example -- <api_key>`
//
// NOTE: This will post in the #general channel of the account you connect
// to.
//

use slack;
use slack::{Event, RtmClient};

struct MyHandler;

#[allow(unused_variables)]
impl slack::EventHandler for MyHandler {
    fn on_event(&mut self, cli: &RtmClient, event: Event) {
        println!("on_event(event: {:?})", event);
        if let Event::Hello = event {
            // find the general channel id from the `StartResponse`
            let general_channel_id = cli
                .start_response()
                .channels
                .as_ref()
                .and_then(|channels| {
                    channels.iter().find(|chan| match chan.name {
                        None => false,
                        Some(ref name) => name == "general",
                    })
                })
                .and_then(|chan| chan.id.as_ref())
                .expect("general channel not found");
            let _ = cli
                .sender()
                .send_message(&general_channel_id, "Hello world! (rtm)");
            // Send a message over the real time api websocket
        }
    }

    fn on_close(&mut self, cli: &RtmClient) {
        println!("on_close");
    }

    fn on_connect(&mut self, cli: &RtmClient) {
        println!("on_connect");
    }
}

fn main() {
    let args: Vec<String> = std::env::args().collect();
    let api_key = match args.len() {
        0 | 1 => {
            panic!("No api-key in args! Usage: cargo run --example slack_example -- <api-key>")
        }
        x => args[x - 1].clone(),
    };
    let mut handler = MyHandler;
    let r = RtmClient::login_and_run(&api_key, &mut handler);
    match r {
        Ok(_) => {}
        Err(err) => panic!("Error: {}", err),
    }
}
<|endoftext|>