<commit_before>use std::collections::HashMap;
use std::fs;
use std::path::Path;

use crate::core::compiler::unit_dependencies;
use crate::core::compiler::UnitInterner;
use crate::core::compiler::{BuildConfig, BuildContext, CompileMode, Context, Kind, ProfileKind};
use crate::core::profiles::UnitFor;
use crate::core::Workspace;
use crate::ops;
use crate::util::errors::{CargoResult, CargoResultExt};
use crate::util::paths;
use crate::util::Config;

pub struct CleanOptions<'a> {
    pub config: &'a Config,
    /// A list of packages to clean. If empty, everything is cleaned.
    pub spec: Vec<String>,
    /// The target arch triple to clean, or None for the host arch
    pub target: Option<String>,
    /// Whether to clean the release directory
    pub release: bool,
    /// Whether a certain build profile was specified
    pub profile_specified: bool,
    /// Whether to clean the directory of a certain build profile
    pub profile_kind: ProfileKind,
    /// Whether to just clean the doc directory
    pub doc: bool,
}

/// Cleans the package's build artifacts.
pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> {
    let mut target_dir = ws.target_dir();
    let config = ws.config();

    // If the doc option is set, we just want to delete the doc directory.
    if opts.doc {
        target_dir = target_dir.join("doc");
        return rm_rf(&target_dir.into_path_unlocked(), config);
    }

    let (packages, resolve) = ops::resolve_ws(ws)?;
    let profiles = ws.profiles();

    // If the release option is set, we set target to release directory
    if opts.release {
        target_dir = target_dir.join(profiles.get_dir_name(&ProfileKind::Release));
    } else if opts.profile_specified {
        // After parsing profiles we know the dir-name of the profile, if a profile
        // was passed freom the command line. If so, delete only the directory of
        // that profile.
        let dir_name = profiles.get_dir_name(&opts.profile_kind);
        target_dir = target_dir.join(dir_name);
    }

    // If we have a spec, then we need to delete some packages, otherwise, just
    // remove the whole target directory and be done with it!
    //
    // Note that we don't bother grabbing a lock here as we're just going to
    // blow it all away anyway.
    if opts.spec.is_empty() {
        return rm_rf(&target_dir.into_path_unlocked(), config);
    }

    let interner = UnitInterner::new();
    let mut build_config = BuildConfig::new(config, Some(1), &opts.target, CompileMode::Build)?;
    let profile_kind = opts.profile_kind.clone();
    build_config.profile_kind = profile_kind.clone();
    let bcx = BuildContext::new(
        ws,
        &packages,
        opts.config,
        &build_config,
        profiles,
        &interner,
        HashMap::new(),
    )?;
    let mut units = Vec::new();

    for spec in opts.spec.iter() {
        // Translate the spec to a Package
        let pkgid = resolve.query(spec)?;
        let pkg = packages.get_one(pkgid)?;

        // Generate all relevant `Unit` targets for this package
        for target in pkg.targets() {
            for kind in [Kind::Host, Kind::Target].iter() {
                for mode in CompileMode::all_modes() {
                    for unit_for in UnitFor::all_values() {
                        let profile = if mode.is_run_custom_build() {
                            profiles.get_profile_run_custom_build(&profiles.get_profile(
                                pkg.package_id(),
                                ws.is_member(pkg),
                                *unit_for,
                                CompileMode::Build,
                                profile_kind.clone(),
                            ))
                        } else {
                            profiles.get_profile(
                                pkg.package_id(),
                                ws.is_member(pkg),
                                *unit_for,
                                *mode,
                                profile_kind.clone(),
                            )
                        };
                        let features = resolve.features_sorted(pkg.package_id());
                        units.push(
                            bcx.units
                                .intern(pkg, target, profile, *kind, *mode, features),
                        );
                    }
                }
            }
        }
    }

    let unit_dependencies =
        unit_dependencies::build_unit_dependencies(&bcx, &resolve, None, &units, &[])?;
    let mut cx = Context::new(config, &bcx, unit_dependencies)?;
    cx.prepare_units(None, &units)?;

    for unit in units.iter() {
        if unit.mode.is_doc() || unit.mode.is_doc_test() {
            // Cleaning individual rustdoc crates is currently not supported.
            // For example, the search index would need to be rebuilt to fully
            // remove it (otherwise you're left with lots of broken links).
            // Doc tests produce no output.
            continue;
        }
        rm_rf(&cx.files().fingerprint_dir(unit), config)?;
        if unit.target.is_custom_build() {
            if unit.mode.is_run_custom_build() {
                rm_rf(&cx.files().build_script_out_dir(unit), config)?;
            } else {
                rm_rf(&cx.files().build_script_dir(unit), config)?;
            }
            continue;
        }

        for output in cx.outputs(unit)?.iter() {
            rm_rf(&output.path, config)?;
            if let Some(ref dst) = output.hardlink {
                rm_rf(dst, config)?;
            }
        }
    }

    Ok(())
}

fn rm_rf(path: &Path, config: &Config) -> CargoResult<()> {
    let m = fs::metadata(path);
    if m.as_ref().map(|s| s.is_dir()).unwrap_or(false) {
        config
            .shell()
            .verbose(|shell| shell.status("Removing", path.display()))?;
        paths::remove_dir_all(path)
            .chain_err(|| failure::format_err!("could not remove build directory"))?;
    } else if m.is_ok() {
        config
            .shell()
            .verbose(|shell| shell.status("Removing", path.display()))?;
        paths::remove_file(path)
            .chain_err(|| failure::format_err!("failed to remove build artifact"))?;
    }
    Ok(())
}
<commit_msg>Typo fix<commit_after>use std::collections::HashMap;
use std::fs;
use std::path::Path;

use crate::core::compiler::unit_dependencies;
use crate::core::compiler::UnitInterner;
use crate::core::compiler::{BuildConfig, BuildContext, CompileMode, Context, Kind, ProfileKind};
use crate::core::profiles::UnitFor;
use crate::core::Workspace;
use crate::ops;
use crate::util::errors::{CargoResult, CargoResultExt};
use crate::util::paths;
use crate::util::Config;

pub struct CleanOptions<'a> {
    pub config: &'a Config,
    /// A list of packages to clean. If empty, everything is cleaned.
    pub spec: Vec<String>,
    /// The target arch triple to clean, or None for the host arch
    pub target: Option<String>,
    /// Whether to clean the release directory
    pub release: bool,
    /// Whether a certain build profile was specified
    pub profile_specified: bool,
    /// Whether to clean the directory of a certain build profile
    pub profile_kind: ProfileKind,
    /// Whether to just clean the doc directory
    pub doc: bool,
}

/// Cleans the package's build artifacts.
pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> {
    let mut target_dir = ws.target_dir();
    let config = ws.config();

    // If the doc option is set, we just want to delete the doc directory.
    if opts.doc {
        target_dir = target_dir.join("doc");
        return rm_rf(&target_dir.into_path_unlocked(), config);
    }

    let (packages, resolve) = ops::resolve_ws(ws)?;
    let profiles = ws.profiles();

    // If the release option is set, we set target to release directory
    if opts.release {
        target_dir = target_dir.join(profiles.get_dir_name(&ProfileKind::Release));
    } else if opts.profile_specified {
        // After parsing profiles we know the dir-name of the profile, if a profile
        // was passed from the command line. If so, delete only the directory of
        // that profile.
        let dir_name = profiles.get_dir_name(&opts.profile_kind);
        target_dir = target_dir.join(dir_name);
    }

    // If we have a spec, then we need to delete some packages, otherwise, just
    // remove the whole target directory and be done with it!
    //
    // Note that we don't bother grabbing a lock here as we're just going to
    // blow it all away anyway.
    if opts.spec.is_empty() {
        return rm_rf(&target_dir.into_path_unlocked(), config);
    }

    let interner = UnitInterner::new();
    let mut build_config = BuildConfig::new(config, Some(1), &opts.target, CompileMode::Build)?;
    let profile_kind = opts.profile_kind.clone();
    build_config.profile_kind = profile_kind.clone();
    let bcx = BuildContext::new(
        ws,
        &packages,
        opts.config,
        &build_config,
        profiles,
        &interner,
        HashMap::new(),
    )?;
    let mut units = Vec::new();

    for spec in opts.spec.iter() {
        // Translate the spec to a Package
        let pkgid = resolve.query(spec)?;
        let pkg = packages.get_one(pkgid)?;

        // Generate all relevant `Unit` targets for this package
        for target in pkg.targets() {
            for kind in [Kind::Host, Kind::Target].iter() {
                for mode in CompileMode::all_modes() {
                    for unit_for in UnitFor::all_values() {
                        let profile = if mode.is_run_custom_build() {
                            profiles.get_profile_run_custom_build(&profiles.get_profile(
                                pkg.package_id(),
                                ws.is_member(pkg),
                                *unit_for,
                                CompileMode::Build,
                                profile_kind.clone(),
                            ))
                        } else {
                            profiles.get_profile(
                                pkg.package_id(),
                                ws.is_member(pkg),
                                *unit_for,
                                *mode,
                                profile_kind.clone(),
                            )
                        };
                        let features = resolve.features_sorted(pkg.package_id());
                        units.push(
                            bcx.units
                                .intern(pkg, target, profile, *kind, *mode, features),
                        );
                    }
                }
            }
        }
    }

    let unit_dependencies =
        unit_dependencies::build_unit_dependencies(&bcx, &resolve, None, &units, &[])?;
    let mut cx = Context::new(config, &bcx, unit_dependencies)?;
    cx.prepare_units(None, &units)?;

    for unit in units.iter() {
        if unit.mode.is_doc() || unit.mode.is_doc_test() {
            // Cleaning individual rustdoc crates is currently not supported.
            // For example, the search index would need to be rebuilt to fully
            // remove it (otherwise you're left with lots of broken links).
            // Doc tests produce no output.
            continue;
        }
        rm_rf(&cx.files().fingerprint_dir(unit), config)?;
        if unit.target.is_custom_build() {
            if unit.mode.is_run_custom_build() {
                rm_rf(&cx.files().build_script_out_dir(unit), config)?;
            } else {
                rm_rf(&cx.files().build_script_dir(unit), config)?;
            }
            continue;
        }

        for output in cx.outputs(unit)?.iter() {
            rm_rf(&output.path, config)?;
            if let Some(ref dst) = output.hardlink {
                rm_rf(dst, config)?;
            }
        }
    }

    Ok(())
}

fn rm_rf(path: &Path, config: &Config) -> CargoResult<()> {
    let m = fs::metadata(path);
    if m.as_ref().map(|s| s.is_dir()).unwrap_or(false) {
        config
            .shell()
            .verbose(|shell| shell.status("Removing", path.display()))?;
        paths::remove_dir_all(path)
            .chain_err(|| failure::format_err!("could not remove build directory"))?;
    } else if m.is_ok() {
        config
            .shell()
            .verbose(|shell| shell.status("Removing", path.display()))?;
        paths::remove_file(path)
            .chain_err(|| failure::format_err!("failed to remove build artifact"))?;
    }
    Ok(())
}
<|endoftext|>


<commit_before>use semver::{Comparator, Op, Version, VersionReq};
use std::fmt::{self, Display};

#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub enum OptVersionReq {
    Any,
    Req(VersionReq),
    /// The exact locked version and the original version requirement.
    Locked(Version, VersionReq),
}

pub trait VersionExt {
    fn is_prerelease(&self) -> bool;
}

pub trait VersionReqExt {
    fn exact(version: &Version) -> Self;
}

impl VersionExt for Version {
    fn is_prerelease(&self) -> bool {
        !self.pre.is_empty()
    }
}

impl VersionReqExt for VersionReq {
    fn exact(version: &Version) -> Self {
        VersionReq {
            comparators: vec![Comparator {
                op: Op::Exact,
                major: version.major,
                minor: Some(version.minor),
                patch: Some(version.patch),
                pre: version.pre.clone(),
            }],
        }
    }
}

impl OptVersionReq {
    pub fn exact(version: &Version) -> Self {
        OptVersionReq::Req(VersionReq::exact(version))
    }

    pub fn is_exact(&self) -> bool {
        match self {
            OptVersionReq::Any => false,
            OptVersionReq::Req(req) => {
                req.comparators.len() == 1 && {
                    let cmp = &req.comparators[0];
                    cmp.op == Op::Exact && cmp.minor.is_some() && cmp.patch.is_some()
                }
            }
            OptVersionReq::Locked(..) => true,
        }
    }

    pub fn lock_to(&mut self, version: &Version) {
        assert!(self.matches(version), "cannot lock {} to {}", self, version);
        use OptVersionReq::*;
        let version = version.clone();
        *self = match self {
            Any => Locked(version, VersionReq::STAR),
            Req(req) => Locked(version, req.clone()),
            Locked(_, req) => Locked(version, req.clone()),
        };
    }

    pub fn is_locked(&self) -> bool {
        matches!(self, OptVersionReq::Locked(..))
    }

    /// Gets the version to which this req is locked, if any.
    pub fn locked_version(&self) -> Option<&Version> {
        match self {
            OptVersionReq::Locked(version, _) => Some(version),
            _ => None,
        }
    }

    pub fn matches(&self, version: &Version) -> bool {
        match self {
            OptVersionReq::Any => true,
            OptVersionReq::Req(req) => req.matches(version),
            OptVersionReq::Locked(v, _) => VersionReq::exact(v).matches(version),
        }
    }
}

impl Display for OptVersionReq {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            OptVersionReq::Any => f.write_str("*"),
            OptVersionReq::Req(req) => Display::fmt(req, f),
            OptVersionReq::Locked(_, req) => Display::fmt(req, f),
        }
    }
}

impl From<VersionReq> for OptVersionReq {
    fn from(req: VersionReq) -> Self {
        OptVersionReq::Req(req)
    }
}
<commit_msg>Compare version fields' equalities directly<commit_after>use semver::{Comparator, Op, Version, VersionReq};
use std::fmt::{self, Display};

#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub enum OptVersionReq {
    Any,
    Req(VersionReq),
    /// The exact locked version and the original version requirement.
    Locked(Version, VersionReq),
}

pub trait VersionExt {
    fn is_prerelease(&self) -> bool;
}

pub trait VersionReqExt {
    fn exact(version: &Version) -> Self;
}

impl VersionExt for Version {
    fn is_prerelease(&self) -> bool {
        !self.pre.is_empty()
    }
}

impl VersionReqExt for VersionReq {
    fn exact(version: &Version) -> Self {
        VersionReq {
            comparators: vec![Comparator {
                op: Op::Exact,
                major: version.major,
                minor: Some(version.minor),
                patch: Some(version.patch),
                pre: version.pre.clone(),
            }],
        }
    }
}

impl OptVersionReq {
    pub fn exact(version: &Version) -> Self {
        OptVersionReq::Req(VersionReq::exact(version))
    }

    pub fn is_exact(&self) -> bool {
        match self {
            OptVersionReq::Any => false,
            OptVersionReq::Req(req) => {
                req.comparators.len() == 1 && {
                    let cmp = &req.comparators[0];
                    cmp.op == Op::Exact && cmp.minor.is_some() && cmp.patch.is_some()
                }
            }
            OptVersionReq::Locked(..) => true,
        }
    }

    pub fn lock_to(&mut self, version: &Version) {
        assert!(self.matches(version), "cannot lock {} to {}", self, version);
        use OptVersionReq::*;
        let version = version.clone();
        *self = match self {
            Any => Locked(version, VersionReq::STAR),
            Req(req) => Locked(version, req.clone()),
            Locked(_, req) => Locked(version, req.clone()),
        };
    }

    pub fn is_locked(&self) -> bool {
        matches!(self, OptVersionReq::Locked(..))
    }

    /// Gets the version to which this req is locked, if any.
    pub fn locked_version(&self) -> Option<&Version> {
        match self {
            OptVersionReq::Locked(version, _) => Some(version),
            _ => None,
        }
    }

    pub fn matches(&self, version: &Version) -> bool {
        match self {
            OptVersionReq::Any => true,
            OptVersionReq::Req(req) => req.matches(version),
            OptVersionReq::Locked(v, _) => {
                v.major == version.major
                    && v.minor == version.minor
                    && v.patch == version.patch
                    && v.pre == version.pre
            }
        }
    }
}

impl Display for OptVersionReq {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            OptVersionReq::Any => f.write_str("*"),
            OptVersionReq::Req(req) => Display::fmt(req, f),
            OptVersionReq::Locked(_, req) => Display::fmt(req, f),
        }
    }
}

impl From<VersionReq> for OptVersionReq {
    fn from(req: VersionReq) -> Self {
        OptVersionReq::Req(req)
    }
}

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

    #[test]
    fn locked_has_the_same_with_exact() {
        fn test_versions(target_ver: &str, vers: &[&str]) {
            let ver = Version::parse(target_ver).unwrap();
            let exact = OptVersionReq::exact(&ver);
            let mut locked = exact.clone();
            locked.lock_to(&ver);
            for v in vers {
                let v = Version::parse(v).unwrap();
                assert_eq!(exact.matches(&v), locked.matches(&v));
            }
        }

        test_versions(
            "1.0.0",
            &["1.0.0", "1.0.1", "0.9.9", "0.10.0", "0.1.0", "1.0.0-pre"],
        );
        test_versions("0.9.0", &["0.9.0", "0.9.1", "1.9.0", "0.0.9", "0.9.0-pre"]);
        test_versions("0.0.2", &["0.0.2", "0.0.1", "0.0.3", "0.0.2-pre"]);
        test_versions(
            "0.1.0-beta2.a",
            &[
                "0.1.0-beta2.a",
                "0.9.1",
                "0.1.0",
                "0.1.1-beta2.a",
                "0.1.0-beta2",
            ],
        );
        test_versions("0.1.0+meta", &["0.1.0", "0.1.0+meta", "0.1.0+any"]);
    }
}
<|endoftext|>


<commit_before>use clap::{Arg, ArgMatches, App, SubCommand};

use tag::Tag;

/// Generates a `clap::SubCommand` to be integrated in the commandline-ui builder for building a
/// "tags --add foo --remove bar" subcommand to do tagging action.
pub fn tag_subcommand<'a, 'b>() -> App<'a, 'b> {
    SubCommand::with_name(tag_subcommand_name())
        .author("Matthias Beyer <mail@beyermatthias.de>")
        .version("0.1")
        .about("Add or remove tags")
        .arg(tag_add_arg())
        .arg(tag_remove_arg())
}

pub fn tag_add_arg<'a, 'b>() -> Arg<'a, 'b> {
    Arg::with_name(tag_subcommand_add_arg_name())
        .short("a")
        .long("add")
        .takes_value(true)
        .multiple(true)
        .help("Add tags, seperated by comma or by specifying multiple times")
}

pub fn tag_remove_arg<'a, 'b>() -> Arg<'a, 'b> {
    Arg::with_name(tag_subcommand_remove_arg_name())
        .short("r")
        .long("remove")
        .takes_value(true)
        .multiple(true)
        .help("Remove tags, seperated by comma or by specifying multiple times")
}

pub fn tag_subcommand_name() -> &'static str {
    "tags"
}

pub fn tag_subcommand_add_arg_name() -> &'static str {
    "add-tags"
}

pub fn tag_subcommand_remove_arg_name() -> &'static str {
    "remove-tags"
}

pub fn tag_subcommand_names() -> Vec<&'static str> {
    vec![tag_subcommand_add_arg_name(), tag_subcommand_remove_arg_name()]
}

/// Generates a `clap::Arg` which can be integrated into the commandline-ui builder for building a
/// "-t" or "--tags" argument which takes values for tagging actions (add, remove)
pub fn tag_argument<'a, 'b>() -> Arg<'a, 'b> {
    Arg::with_name(tag_argument_name())
        .short("t")
        .long("tags")
        .takes_value(true)
        .multiple(true)
        .help("Add or remove tags, prefixed by '+' (for adding) or '-' (for removing)")
}

pub fn tag_argument_name() -> &'static str {
    "specify-tags"
}

/// Get the tags which should be added from the commandline
///
/// Returns none if the argument was not specified
pub fn get_add_tags(matches: &ArgMatches) -> Option<Vec<Tag>> {
    if let Some(v) = extract_tags(matches, tag_subcommand_add_arg_name(), '+') {
        return Some(v);
    } else {
        matches
            .values_of(tag_subcommand_add_arg_name())
            .map(|values| values.map(String::from).collect())
    }
}

/// Get the tags which should be removed from the commandline
///
/// Returns none if the argument was not specified
pub fn get_remove_tags(matches: &ArgMatches) -> Option<Vec<Tag>> {
    extract_tags(matches, "remove-tags", '-')
}

fn extract_tags(matches: &ArgMatches, specifier: &str, specchar: char) -> Option<Vec<Tag>> {
    if let Some(submatch) = matches.subcommand_matches("tags") {
        submatch.values_of(specifier)
            .map(|values| values.map(String::from).collect())
    } else {
        matches.values_of("specify-tags")
            .map(|argmatches| {
                argmatches
                    .map(String::from)
                    .filter(|s| s.starts_with(specchar))
                    .map(|s| {
                        String::from(s.split_at(1).1)
                    })
                    .collect()
            })
    }
}

<commit_msg>Change get_remove_tags() to get to-remove-tags from top level if there are any<commit_after>use clap::{Arg, ArgMatches, App, SubCommand};

use tag::Tag;

/// Generates a `clap::SubCommand` to be integrated in the commandline-ui builder for building a
/// "tags --add foo --remove bar" subcommand to do tagging action.
pub fn tag_subcommand<'a, 'b>() -> App<'a, 'b> {
    SubCommand::with_name(tag_subcommand_name())
        .author("Matthias Beyer <mail@beyermatthias.de>")
        .version("0.1")
        .about("Add or remove tags")
        .arg(tag_add_arg())
        .arg(tag_remove_arg())
}

pub fn tag_add_arg<'a, 'b>() -> Arg<'a, 'b> {
    Arg::with_name(tag_subcommand_add_arg_name())
        .short("a")
        .long("add")
        .takes_value(true)
        .multiple(true)
        .help("Add tags, seperated by comma or by specifying multiple times")
}

pub fn tag_remove_arg<'a, 'b>() -> Arg<'a, 'b> {
    Arg::with_name(tag_subcommand_remove_arg_name())
        .short("r")
        .long("remove")
        .takes_value(true)
        .multiple(true)
        .help("Remove tags, seperated by comma or by specifying multiple times")
}

pub fn tag_subcommand_name() -> &'static str {
    "tags"
}

pub fn tag_subcommand_add_arg_name() -> &'static str {
    "add-tags"
}

pub fn tag_subcommand_remove_arg_name() -> &'static str {
    "remove-tags"
}

pub fn tag_subcommand_names() -> Vec<&'static str> {
    vec![tag_subcommand_add_arg_name(), tag_subcommand_remove_arg_name()]
}

/// Generates a `clap::Arg` which can be integrated into the commandline-ui builder for building a
/// "-t" or "--tags" argument which takes values for tagging actions (add, remove)
pub fn tag_argument<'a, 'b>() -> Arg<'a, 'b> {
    Arg::with_name(tag_argument_name())
        .short("t")
        .long("tags")
        .takes_value(true)
        .multiple(true)
        .help("Add or remove tags, prefixed by '+' (for adding) or '-' (for removing)")
}

pub fn tag_argument_name() -> &'static str {
    "specify-tags"
}

/// Get the tags which should be added from the commandline
///
/// Returns none if the argument was not specified
pub fn get_add_tags(matches: &ArgMatches) -> Option<Vec<Tag>> {
    if let Some(v) = extract_tags(matches, tag_subcommand_add_arg_name(), '+') {
        return Some(v);
    } else {
        matches
            .values_of(tag_subcommand_add_arg_name())
            .map(|values| values.map(String::from).collect())
    }
}

/// Get the tags which should be removed from the commandline
///
/// Returns none if the argument was not specified
pub fn get_remove_tags(matches: &ArgMatches) -> Option<Vec<Tag>> {
    if let Some(v) = extract_tags(matches, tag_subcommand_remove_arg_name(), '-') {
        return Some(v);
    } else {
        matches
            .values_of(tag_subcommand_remove_arg_name())
            .map(|values| values.map(String::from).collect())
    }
}

fn extract_tags(matches: &ArgMatches, specifier: &str, specchar: char) -> Option<Vec<Tag>> {
    if let Some(submatch) = matches.subcommand_matches("tags") {
        submatch.values_of(specifier)
            .map(|values| values.map(String::from).collect())
    } else {
        matches.values_of("specify-tags")
            .map(|argmatches| {
                argmatches
                    .map(String::from)
                    .filter(|s| s.starts_with(specchar))
                    .map(|s| {
                        String::from(s.split_at(1).1)
                    })
                    .collect()
            })
    }
}

<|endoftext|>


<commit_before>use screen::*;
use ui_utils;
use piston_window::{self, PistonWindow};
use conrod::{self, Widget, Positionable, Sizeable, Labelable};
use conrod::color::Colorable;

pub struct MainMenu {
    ui: conrod::Ui<piston_window::Glyphs>,
    state: Option<State>,
}

impl MainMenu {
    pub fn new(window: &PistonWindow, context: &mut Context) -> Self {
        MainMenu {
            ui: ui_utils::create_ui(&window, &context.assets_path),
            state: None,
        }
    }
}

enum State {
    NewGame,
    Options,
    Exit,
}

impl Screen for MainMenu {
    fn on_input(&mut self, _: &piston_window::Input, window: &PistonWindow, context: &mut Context) -> InputResults {
        self.ui.handle_event(window);
        let mut result = Vec::new();

        match self.state {
            Some(State::NewGame) => {
                result.push(InputResult::PopScreen);
                result.push(InputResult::PushScreen(Box::new(Battle::new(&window, context))));
            }
            Some(State::Options) => {
                result.push(InputResult::PushScreen(Box::new(Options::new(&window, context))));
            }
            Some(State::Exit) => {
                result.push(InputResult::PopScreen);
            }
            None => {}
        }

        result
    }

    fn on_draw(&mut self, _: &piston_window::RenderArgs, window: &PistonWindow) {
        self.ui.handle_event(window);
        window.draw_2d(|context, graphics| self.ui.draw_if_changed(context, graphics));
    }

    fn on_update(&mut self, _: &piston_window::UpdateArgs) {
        let button_color = conrod::color::rgb(0.4, 0.75, 0.6);

        let state = &mut self.state;

        self.ui.set_widgets(|ui| {
            conrod::Canvas::new()
                .pad(30.)
                .color(conrod::color::rgb(0.2, 0.35, 0.45))
                .scroll_kids()
                .set(CANVAS, ui);

            conrod::Button::new()
                .w_h(200.0, 50.0)
                .mid_left_of(CANVAS)
                .color(button_color)
                .label("New game")
                .react(|| *state = Some(State::NewGame))
                .set(NEW_GAME_BUTTON, ui);

            conrod::Button::new()
                .w_h(200.0, 50.0)
                .mid_left_of(CANVAS)
                .down_from(NEW_GAME_BUTTON, 45.0)
                .color(button_color)
                .label("Options")
                .react(|| *state = Some(State::Options))
                .set(OPTIONS_BUTTON, ui);

            conrod::Button::new()
                .w_h(200.0, 50.0)
                .mid_left_of(CANVAS)
                .down_from(OPTIONS_BUTTON, 45.0)
                .color(button_color)
                .label("Exit")
                .react(|| *state = Some(State::Exit))
                .set(EXIT_BUTTON, ui);
        });
    }
}

widget_ids! {
    CANVAS,
    NEW_GAME_BUTTON,
    OPTIONS_BUTTON,
    EXIT_BUTTON,
}
<commit_msg>MenuScreen state fix<commit_after>use screen::*;
use ui_utils;
use piston_window::{self, PistonWindow};
use conrod::{self, Widget, Positionable, Sizeable, Labelable};
use conrod::color::Colorable;

pub struct MainMenu {
    ui: conrod::Ui<piston_window::Glyphs>,
    state: Option<State>,
}

impl MainMenu {
    pub fn new(window: &PistonWindow, context: &mut Context) -> Self {
        MainMenu {
            ui: ui_utils::create_ui(&window, &context.assets_path),
            state: None,
        }
    }
}

enum State {
    NewGame,
    Options,
    Exit,
}

impl Screen for MainMenu {
    fn on_input(&mut self, _: &piston_window::Input, window: &PistonWindow, context: &mut Context) -> InputResults {
        self.ui.handle_event(window);
        let mut result = Vec::new();

        match self.state {
            Some(State::NewGame) => {
                result.push(InputResult::PopScreen);
                result.push(InputResult::PushScreen(Box::new(Battle::new(&window, context))));
            }
            Some(State::Options) => {
                result.push(InputResult::PushScreen(Box::new(Options::new(&window, context))));
            }
            Some(State::Exit) => {
                result.push(InputResult::PopScreen);
            }
            None => {}
        }

        self.state = None;
        result
    }

    fn on_draw(&mut self, _: &piston_window::RenderArgs, window: &PistonWindow) {
        self.ui.handle_event(window);
        window.draw_2d(|context, graphics| self.ui.draw_if_changed(context, graphics));
    }

    fn on_update(&mut self, _: &piston_window::UpdateArgs) {
        let button_color = conrod::color::rgb(0.4, 0.75, 0.6);

        let state = &mut self.state;

        self.ui.set_widgets(|ui| {
            conrod::Canvas::new()
                .pad(30.)
                .color(conrod::color::rgb(0.2, 0.35, 0.45))
                .scroll_kids()
                .set(CANVAS, ui);

            conrod::Button::new()
                .w_h(200.0, 50.0)
                .mid_left_of(CANVAS)
                .color(button_color)
                .label("New game")
                .react(|| *state = Some(State::NewGame))
                .set(NEW_GAME_BUTTON, ui);

            conrod::Button::new()
                .w_h(200.0, 50.0)
                .mid_left_of(CANVAS)
                .down_from(NEW_GAME_BUTTON, 45.0)
                .color(button_color)
                .label("Options")
                .react(|| *state = Some(State::Options))
                .set(OPTIONS_BUTTON, ui);

            conrod::Button::new()
                .w_h(200.0, 50.0)
                .mid_left_of(CANVAS)
                .down_from(OPTIONS_BUTTON, 45.0)
                .color(button_color)
                .label("Exit")
                .react(|| *state = Some(State::Exit))
                .set(EXIT_BUTTON, ui);
        });
    }
}

widget_ids! {
    CANVAS,
    NEW_GAME_BUTTON,
    OPTIONS_BUTTON,
    EXIT_BUTTON,
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

//! This module is responsible to build a single project. It does not handle
//! watch mode or other state.

mod apply_transforms;
mod artifact_content;
pub mod artifact_writer;
mod build_ir;
mod build_schema;
mod generate_artifacts;
pub mod generate_extra_artifacts;
mod is_operation_preloadable;
mod persist_operations;
mod source_control;
mod validate;

use crate::compiler_state::{ArtifactMapKind, CompilerState, ProjectName, SourceSetName};
use crate::config::{Config, ProjectConfig};
use crate::errors::BuildProjectError;
use crate::{artifact_map::ArtifactMap, graphql_asts::GraphQLAsts};
pub use apply_transforms::apply_transforms;
pub use apply_transforms::Programs;
use build_ir::BuildIRResult;
pub use build_ir::SourceHashes;
pub use build_schema::build_schema;
use common::{PerfLogEvent, PerfLogger};
use fnv::{FnvHashMap, FnvHashSet};
pub use generate_artifacts::{
    create_path_for_artifact, generate_artifacts, Artifact, ArtifactContent,
};
use generate_extra_artifacts::generate_extra_artifacts;
use graphql_ir::Program;
use interner::StringKey;
pub use is_operation_preloadable::is_operation_preloadable;
use log::info;
use relay_codegen::Printer;
use schema::SDLSchema;
pub use source_control::add_to_mercurial;
use std::{collections::hash_map::Entry, path::PathBuf, sync::Arc};
pub use validate::validate;

pub enum BuildProjectFailure {
    Error(BuildProjectError),
    Cancelled,
}

/// This program doesn't have IR transforms applied to it, so it's not optimized.
/// It's perfect for the LSP server: we have all the documents with
/// their locations to provide information to go_to_definition, hover, etc.
pub fn build_raw_program(
    project_config: &ProjectConfig,
    graphql_asts: &FnvHashMap<SourceSetName, GraphQLAsts>,
    schema: Arc<SDLSchema>,
    log_event: &impl PerfLogEvent,
) -> Result<Program, BuildProjectError> {
    let BuildIRResult { ir, .. } = log_event.time("build_ir_time", || {
        build_ir::build_ir(project_config, &schema, graphql_asts, false)
            .map_err(|errors| BuildProjectError::ValidationErrors { errors })
    })?;

    Ok(log_event.time("build_program_time", || {
        Program::from_definitions(schema, ir)
    }))
}

fn build_programs(
    config: &Config,
    project_config: &ProjectConfig,
    compiler_state: &CompilerState,
    graphql_asts: &FnvHashMap<SourceSetName, GraphQLAsts>,
    schema: Arc<SDLSchema>,
    log_event: &impl PerfLogEvent,
    perf_logger: Arc<impl PerfLogger + 'static>,
) -> Result<(Programs, Arc<SourceHashes>), BuildProjectFailure> {
    let project_name = project_config.name;
    let is_incremental_build =
        compiler_state.has_processed_changes() && !compiler_state.has_breaking_schema_change();

    // Build a type aware IR.
    let BuildIRResult {
        ir,
        base_fragment_names,
        source_hashes,
    } = log_event.time("build_ir_time", || {
        build_ir::build_ir(project_config, &schema, graphql_asts, is_incremental_build).map_err(
            |errors| BuildProjectFailure::Error(BuildProjectError::ValidationErrors { errors }),
        )
    })?;

    // Turn the IR into a base Program.
    let program = log_event.time("build_program_time", || {
        Program::from_definitions(schema, ir)
    });

    if compiler_state.has_pending_file_source_changes() {
        return Err(BuildProjectFailure::Cancelled);
    }

    // Call validation rules that go beyond type checking.
    log_event.time("validate_time", || {
        // TODO(T63482263): Pass connection interface from configuration
        validate(&program, &config.connection_interface).map_err(|errors| {
            BuildProjectFailure::Error(BuildProjectError::ValidationErrors { errors })
        })
    })?;

    // Apply various chains of transforms to create a set of output programs.
    let programs = log_event.time("apply_transforms_time", || {
        apply_transforms(
            project_name,
            Arc::new(program),
            Arc::new(base_fragment_names),
            &config.connection_interface,
            Arc::new(project_config.feature_flags.unwrap_or(config.feature_flags)),
            perf_logger,
        )
        .map_err(|errors| {
            BuildProjectFailure::Error(BuildProjectError::ValidationErrors { errors })
        })
    })?;

    Ok((programs, Arc::new(source_hashes)))
}

pub fn build_project(
    config: &Config,
    project_config: &ProjectConfig,
    compiler_state: &CompilerState,
    graphql_asts: &FnvHashMap<SourceSetName, GraphQLAsts>,
    perf_logger: Arc<impl PerfLogger + 'static>,
) -> Result<(ProjectName, Arc<SDLSchema>, Programs, Vec<Artifact>), BuildProjectFailure> {
    let log_event = perf_logger.create_event("build_project");
    let build_time = log_event.start("build_project_time");
    let project_name = project_config.name.lookup();
    log_event.string("project", project_name.to_string());
    info!("[{}] compiling...", project_name);

    // Construct a schema instance including project specific extensions.
    let schema = log_event
        .time("build_schema_time", || {
            Ok(build_schema(compiler_state, project_config)?)
        })
        .map_err(|errors| {
            BuildProjectFailure::Error(BuildProjectError::ValidationErrors { errors })
        })?;

    if compiler_state.has_pending_file_source_changes() {
        return Err(BuildProjectFailure::Cancelled);
    }

    // Apply different transform pipelines to produce the `Programs`.
    let (programs, source_hashes) = build_programs(
        config,
        project_config,
        compiler_state,
        graphql_asts,
        Arc::clone(&schema),
        &log_event,
        Arc::clone(&perf_logger),
    )?;

    if compiler_state.has_pending_file_source_changes() {
        return Err(BuildProjectFailure::Cancelled);
    }

    // Generate artifacts by collecting information from the `Programs`.
    let artifacts_timer = log_event.start("generate_artifacts_time");
    let artifacts = generate_artifacts(project_config, &programs, Arc::clone(&source_hashes))
        .map_err(BuildProjectFailure::Error)?;
    log_event.stop(artifacts_timer);

    log_event.number(
        "generated_artifacts",
        programs.reader.document_count() + programs.normalization.document_count(),
    );

    log_event.stop(build_time);
    perf_logger.complete_event(log_event);
    Ok((project_config.name, schema, programs, artifacts))
}

pub async fn commit_project(
    config: &Config,
    project_config: &ProjectConfig,
    perf_logger: Arc<impl PerfLogger + 'static>,
    schema: &SDLSchema,
    programs: Programs,
    mut artifacts: Vec<Artifact>,
    artifact_map: Arc<ArtifactMapKind>,
    // Definitions that are removed from the previous artifact map
    removed_definition_names: Vec<StringKey>,
    // Dirty artifacts that should be removed if no longer in the artifacts map
    mut artifacts_to_remove: FnvHashSet<PathBuf>,
) -> Result<ArtifactMap, BuildProjectError> {
    let log_event = perf_logger.create_event("commit_project");
    log_event.string("project", project_config.name.to_string());
    let commit_time = log_event.start("commit_project_time");

    if let Some(ref operation_persister) = config.operation_persister {
        if let Some(ref persist_config) = project_config.persist {
            let persist_operations_timer = log_event.start("persist_operations_time");
            persist_operations::persist_operations(
                &mut artifacts,
                &config.root_dir,
                &persist_config,
                config,
                &operation_persister,
                &log_event,
            )
            .await?;
            log_event.stop(persist_operations_timer);
        }
    }

    // In some cases we need to create additional (platform specific) artifacts
    // For that, we will use `generate_extra_operation_artifacts` from the configs
    if let Some(generate_extra_operation_artifacts_fn) = &config.generate_extra_operation_artifacts
    {
        log_event.time("generate_extra_operation_artifacts_time", || {
            generate_extra_artifacts(
                schema,
                project_config,
                &mut artifacts,
                generate_extra_operation_artifacts_fn,
            )
        });
    }

    // Write the generated artifacts to disk. This step is separate from
    // generating artifacts or persisting to avoid partial writes in case of
    // errors as much as possible.
    let next_artifact_map = match Arc::as_ref(&artifact_map) {
        ArtifactMapKind::Unconnected(existing_artifacts) => {
            let mut existing_artifacts = existing_artifacts.clone();
            let mut printer = Printer::with_dedupe();

            log_event.time("write_artifacts_time", || {
                for artifact in &artifacts {
                    if !existing_artifacts.remove(&artifact.path) {
                        info!(
                            "[{}] NEW: {:?} -> {:?}",
                            project_config.name, &artifact.source_definition_names, &artifact.path
                        );
                    }

                    let path = config.root_dir.join(&artifact.path);
                    let content =
                        artifact
                            .content
                            .as_bytes(config, project_config, &mut printer, schema);
                    config.artifact_writer.write_if_changed(path, content)?;
                }
                Ok(())
            })?;

            log_event.time("delete_artifacts_time", || {
                for remaining_artifact in &existing_artifacts {
                    let path = config.root_dir.join(remaining_artifact);
                    config.artifact_writer.remove(path)?;
                }
                Ok(())
            })?;

            ArtifactMap::from(artifacts)
        }
        ArtifactMapKind::Mapping(artifact_map) => {
            let mut printer = Printer::with_dedupe();
            let mut artifact_map = artifact_map.clone();
            let mut current_paths_map = ArtifactMap::default();

            log_event.time("write_artifacts_incremental_time", || {
                // Write or update artifacts
                for artifact in artifacts {
                    let path = config.root_dir.join(&artifact.path);
                    let content =
                        artifact
                            .content
                            .as_bytes(config, project_config, &mut printer, schema);
                    config.artifact_writer.write_if_changed(path, content)?;
                    current_paths_map.insert(artifact);
                }
                Ok(())
            })?;

            log_event.time("update_artifact_map_time", || {
                // All generated paths for removed definitions should be removed
                for name in &removed_definition_names {
                    if let Some(artifacts) = artifact_map.0.remove(&name) {
                        for artifact in artifacts {
                            artifacts_to_remove.insert(artifact.path);
                        }
                    }
                }
                // Update the artifact map, and delete any removed artifacts
                for (definition_name, artifact_records) in current_paths_map.0 {
                    match artifact_map.0.entry(definition_name) {
                        Entry::Occupied(mut entry) => {
                            let prev_records = entry.get_mut();
                            for prev_record in prev_records.drain(..) {
                                if !artifact_records.iter().any(|t| t.path == prev_record.path) {
                                    artifacts_to_remove.insert(prev_record.path);
                                }
                            }
                            prev_records.extend(artifact_records.into_iter());
                        }
                        Entry::Vacant(entry) => {
                            entry.insert(artifact_records);
                        }
                    }
                }
                // Filter out any artifact that is in the artifact map
                for artifacts in artifact_map.0.values() {
                    for artifact in artifacts {
                        artifacts_to_remove.remove(&artifact.path);
                    }
                }
            });

            log_event.time("delete_artifacts_incremental_time", || {
                // The remaining dirty artifacts are no longer required
                for path in artifacts_to_remove {
                    config.artifact_writer.remove(config.root_dir.join(path))?;
                }
                Ok(())
            })?;

            artifact_map
        }
    };

    info!(
        "[{}] compiled documents: {} reader, {} normalization, {} operation text",
        project_config.name,
        programs.reader.document_count(),
        programs.normalization.document_count(),
        programs.operation_text.document_count()
    );
    log_event.stop(commit_time);
    perf_logger.complete_event(log_event);

    Ok(next_artifact_map)
}
<commit_msg>remove old TODO<commit_after>/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

//! This module is responsible to build a single project. It does not handle
//! watch mode or other state.

mod apply_transforms;
mod artifact_content;
pub mod artifact_writer;
mod build_ir;
mod build_schema;
mod generate_artifacts;
pub mod generate_extra_artifacts;
mod is_operation_preloadable;
mod persist_operations;
mod source_control;
mod validate;

use crate::compiler_state::{ArtifactMapKind, CompilerState, ProjectName, SourceSetName};
use crate::config::{Config, ProjectConfig};
use crate::errors::BuildProjectError;
use crate::{artifact_map::ArtifactMap, graphql_asts::GraphQLAsts};
pub use apply_transforms::apply_transforms;
pub use apply_transforms::Programs;
use build_ir::BuildIRResult;
pub use build_ir::SourceHashes;
pub use build_schema::build_schema;
use common::{PerfLogEvent, PerfLogger};
use fnv::{FnvHashMap, FnvHashSet};
pub use generate_artifacts::{
    create_path_for_artifact, generate_artifacts, Artifact, ArtifactContent,
};
use generate_extra_artifacts::generate_extra_artifacts;
use graphql_ir::Program;
use interner::StringKey;
pub use is_operation_preloadable::is_operation_preloadable;
use log::info;
use relay_codegen::Printer;
use schema::SDLSchema;
pub use source_control::add_to_mercurial;
use std::{collections::hash_map::Entry, path::PathBuf, sync::Arc};
pub use validate::validate;

pub enum BuildProjectFailure {
    Error(BuildProjectError),
    Cancelled,
}

/// This program doesn't have IR transforms applied to it, so it's not optimized.
/// It's perfect for the LSP server: we have all the documents with
/// their locations to provide information to go_to_definition, hover, etc.
pub fn build_raw_program(
    project_config: &ProjectConfig,
    graphql_asts: &FnvHashMap<SourceSetName, GraphQLAsts>,
    schema: Arc<SDLSchema>,
    log_event: &impl PerfLogEvent,
) -> Result<Program, BuildProjectError> {
    let BuildIRResult { ir, .. } = log_event.time("build_ir_time", || {
        build_ir::build_ir(project_config, &schema, graphql_asts, false)
            .map_err(|errors| BuildProjectError::ValidationErrors { errors })
    })?;

    Ok(log_event.time("build_program_time", || {
        Program::from_definitions(schema, ir)
    }))
}

fn build_programs(
    config: &Config,
    project_config: &ProjectConfig,
    compiler_state: &CompilerState,
    graphql_asts: &FnvHashMap<SourceSetName, GraphQLAsts>,
    schema: Arc<SDLSchema>,
    log_event: &impl PerfLogEvent,
    perf_logger: Arc<impl PerfLogger + 'static>,
) -> Result<(Programs, Arc<SourceHashes>), BuildProjectFailure> {
    let project_name = project_config.name;
    let is_incremental_build =
        compiler_state.has_processed_changes() && !compiler_state.has_breaking_schema_change();

    // Build a type aware IR.
    let BuildIRResult {
        ir,
        base_fragment_names,
        source_hashes,
    } = log_event.time("build_ir_time", || {
        build_ir::build_ir(project_config, &schema, graphql_asts, is_incremental_build).map_err(
            |errors| BuildProjectFailure::Error(BuildProjectError::ValidationErrors { errors }),
        )
    })?;

    // Turn the IR into a base Program.
    let program = log_event.time("build_program_time", || {
        Program::from_definitions(schema, ir)
    });

    if compiler_state.has_pending_file_source_changes() {
        return Err(BuildProjectFailure::Cancelled);
    }

    // Call validation rules that go beyond type checking.
    log_event.time("validate_time", || {
        validate(&program, &config.connection_interface).map_err(|errors| {
            BuildProjectFailure::Error(BuildProjectError::ValidationErrors { errors })
        })
    })?;

    // Apply various chains of transforms to create a set of output programs.
    let programs = log_event.time("apply_transforms_time", || {
        apply_transforms(
            project_name,
            Arc::new(program),
            Arc::new(base_fragment_names),
            &config.connection_interface,
            Arc::new(project_config.feature_flags.unwrap_or(config.feature_flags)),
            perf_logger,
        )
        .map_err(|errors| {
            BuildProjectFailure::Error(BuildProjectError::ValidationErrors { errors })
        })
    })?;

    Ok((programs, Arc::new(source_hashes)))
}

pub fn build_project(
    config: &Config,
    project_config: &ProjectConfig,
    compiler_state: &CompilerState,
    graphql_asts: &FnvHashMap<SourceSetName, GraphQLAsts>,
    perf_logger: Arc<impl PerfLogger + 'static>,
) -> Result<(ProjectName, Arc<SDLSchema>, Programs, Vec<Artifact>), BuildProjectFailure> {
    let log_event = perf_logger.create_event("build_project");
    let build_time = log_event.start("build_project_time");
    let project_name = project_config.name.lookup();
    log_event.string("project", project_name.to_string());
    info!("[{}] compiling...", project_name);

    // Construct a schema instance including project specific extensions.
    let schema = log_event
        .time("build_schema_time", || {
            Ok(build_schema(compiler_state, project_config)?)
        })
        .map_err(|errors| {
            BuildProjectFailure::Error(BuildProjectError::ValidationErrors { errors })
        })?;

    if compiler_state.has_pending_file_source_changes() {
        return Err(BuildProjectFailure::Cancelled);
    }

    // Apply different transform pipelines to produce the `Programs`.
    let (programs, source_hashes) = build_programs(
        config,
        project_config,
        compiler_state,
        graphql_asts,
        Arc::clone(&schema),
        &log_event,
        Arc::clone(&perf_logger),
    )?;

    if compiler_state.has_pending_file_source_changes() {
        return Err(BuildProjectFailure::Cancelled);
    }

    // Generate artifacts by collecting information from the `Programs`.
    let artifacts_timer = log_event.start("generate_artifacts_time");
    let artifacts = generate_artifacts(project_config, &programs, Arc::clone(&source_hashes))
        .map_err(BuildProjectFailure::Error)?;
    log_event.stop(artifacts_timer);

    log_event.number(
        "generated_artifacts",
        programs.reader.document_count() + programs.normalization.document_count(),
    );

    log_event.stop(build_time);
    perf_logger.complete_event(log_event);
    Ok((project_config.name, schema, programs, artifacts))
}

pub async fn commit_project(
    config: &Config,
    project_config: &ProjectConfig,
    perf_logger: Arc<impl PerfLogger + 'static>,
    schema: &SDLSchema,
    programs: Programs,
    mut artifacts: Vec<Artifact>,
    artifact_map: Arc<ArtifactMapKind>,
    // Definitions that are removed from the previous artifact map
    removed_definition_names: Vec<StringKey>,
    // Dirty artifacts that should be removed if no longer in the artifacts map
    mut artifacts_to_remove: FnvHashSet<PathBuf>,
) -> Result<ArtifactMap, BuildProjectError> {
    let log_event = perf_logger.create_event("commit_project");
    log_event.string("project", project_config.name.to_string());
    let commit_time = log_event.start("commit_project_time");

    if let Some(ref operation_persister) = config.operation_persister {
        if let Some(ref persist_config) = project_config.persist {
            let persist_operations_timer = log_event.start("persist_operations_time");
            persist_operations::persist_operations(
                &mut artifacts,
                &config.root_dir,
                &persist_config,
                config,
                &operation_persister,
                &log_event,
            )
            .await?;
            log_event.stop(persist_operations_timer);
        }
    }

    // In some cases we need to create additional (platform specific) artifacts
    // For that, we will use `generate_extra_operation_artifacts` from the configs
    if let Some(generate_extra_operation_artifacts_fn) = &config.generate_extra_operation_artifacts
    {
        log_event.time("generate_extra_operation_artifacts_time", || {
            generate_extra_artifacts(
                schema,
                project_config,
                &mut artifacts,
                generate_extra_operation_artifacts_fn,
            )
        });
    }

    // Write the generated artifacts to disk. This step is separate from
    // generating artifacts or persisting to avoid partial writes in case of
    // errors as much as possible.
    let next_artifact_map = match Arc::as_ref(&artifact_map) {
        ArtifactMapKind::Unconnected(existing_artifacts) => {
            let mut existing_artifacts = existing_artifacts.clone();
            let mut printer = Printer::with_dedupe();

            log_event.time("write_artifacts_time", || {
                for artifact in &artifacts {
                    if !existing_artifacts.remove(&artifact.path) {
                        info!(
                            "[{}] NEW: {:?} -> {:?}",
                            project_config.name, &artifact.source_definition_names, &artifact.path
                        );
                    }

                    let path = config.root_dir.join(&artifact.path);
                    let content =
                        artifact
                            .content
                            .as_bytes(config, project_config, &mut printer, schema);
                    config.artifact_writer.write_if_changed(path, content)?;
                }
                Ok(())
            })?;

            log_event.time("delete_artifacts_time", || {
                for remaining_artifact in &existing_artifacts {
                    let path = config.root_dir.join(remaining_artifact);
                    config.artifact_writer.remove(path)?;
                }
                Ok(())
            })?;

            ArtifactMap::from(artifacts)
        }
        ArtifactMapKind::Mapping(artifact_map) => {
            let mut printer = Printer::with_dedupe();
            let mut artifact_map = artifact_map.clone();
            let mut current_paths_map = ArtifactMap::default();

            log_event.time("write_artifacts_incremental_time", || {
                // Write or update artifacts
                for artifact in artifacts {
                    let path = config.root_dir.join(&artifact.path);
                    let content =
                        artifact
                            .content
                            .as_bytes(config, project_config, &mut printer, schema);
                    config.artifact_writer.write_if_changed(path, content)?;
                    current_paths_map.insert(artifact);
                }
                Ok(())
            })?;

            log_event.time("update_artifact_map_time", || {
                // All generated paths for removed definitions should be removed
                for name in &removed_definition_names {
                    if let Some(artifacts) = artifact_map.0.remove(&name) {
                        for artifact in artifacts {
                            artifacts_to_remove.insert(artifact.path);
                        }
                    }
                }
                // Update the artifact map, and delete any removed artifacts
                for (definition_name, artifact_records) in current_paths_map.0 {
                    match artifact_map.0.entry(definition_name) {
                        Entry::Occupied(mut entry) => {
                            let prev_records = entry.get_mut();
                            for prev_record in prev_records.drain(..) {
                                if !artifact_records.iter().any(|t| t.path == prev_record.path) {
                                    artifacts_to_remove.insert(prev_record.path);
                                }
                            }
                            prev_records.extend(artifact_records.into_iter());
                        }
                        Entry::Vacant(entry) => {
                            entry.insert(artifact_records);
                        }
                    }
                }
                // Filter out any artifact that is in the artifact map
                for artifacts in artifact_map.0.values() {
                    for artifact in artifacts {
                        artifacts_to_remove.remove(&artifact.path);
                    }
                }
            });

            log_event.time("delete_artifacts_incremental_time", || {
                // The remaining dirty artifacts are no longer required
                for path in artifacts_to_remove {
                    config.artifact_writer.remove(config.root_dir.join(path))?;
                }
                Ok(())
            })?;

            artifact_map
        }
    };

    info!(
        "[{}] compiled documents: {} reader, {} normalization, {} operation text",
        project_config.name,
        programs.reader.document_count(),
        programs.normalization.document_count(),
        programs.operation_text.document_count()
    );
    log_event.stop(commit_time);
    perf_logger.complete_event(log_event);

    Ok(next_artifact_map)
}
<|endoftext|>


<commit_before>use crate::branching::BranchAnalysis;
use crate::config::{Config, RunType};
use crate::path_utils::{get_source_walker, is_source_file};
use lazy_static::lazy_static;
use log::trace;
use proc_macro2::{Span, TokenStream};
use quote::ToTokens;
use regex::Regex;
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::fs::File;
use std::io::{BufRead, BufReader, Read};
use std::path::{Path, PathBuf};
use syn::*;
use walkdir::WalkDir;

mod attributes;
mod expressions;
mod items;
mod macros;
mod statements;
#[cfg(test)]
mod tests;

pub(crate) mod prelude {
    pub(crate) use super::*;
    pub(crate) use attributes::*;
    pub(crate) use macros::*;
}

/// Enumeration representing which lines to ignore
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum Lines {
    /// Ignore all lines in the file
    All,
    /// A single line to ignore in the file
    Line(usize),
}

/// Represents the results of analysis of a single file. Does not store the file
/// in question as this is expected to be maintained by the user.
#[derive(Clone, Debug, Default)]
pub struct LineAnalysis {
    /// This represents lines that should be ignored in coverage
    /// but may be identifed as coverable in the DWARF tables
    pub ignore: HashSet<Lines>,
    /// This represents lines that should be included in coverage
    /// But may be ignored. Doesn't make sense to cover ALL the lines so this
    /// is just an index.
    pub cover: HashSet<usize>,
    /// Some logical lines may be split between physical lines this shows the
    /// mapping from physical line to logical line to prevent false positives
    /// from expressions split across physical lines
    pub logical_lines: HashMap<usize, usize>,
}

/// When the `LineAnalysis` results are mapped to their files there needs to be
/// an easy way to get the information back. For the container used implement
/// this trait
pub trait SourceAnalysisQuery {
    /// Returns true if the line in the given file should be ignored
    fn should_ignore(&self, path: &Path, l: &usize) -> bool;
    /// Takes a path and line number and normalises it to the logical line
    /// that should be represented in the statistics
    fn normalise(&self, path: &Path, l: usize) -> (PathBuf, usize);
}

#[derive(Copy, Clone, Debug)]
pub(crate) enum SubResult {
    Ok,
    Unreachable,
}

impl SourceAnalysisQuery for HashMap<PathBuf, LineAnalysis> {
    fn should_ignore(&self, path: &Path, l: &usize) -> bool {
        if self.contains_key(path) {
            self.get(path).unwrap().should_ignore(*l)
        } else {
            false
        }
    }

    fn normalise(&self, path: &Path, l: usize) -> (PathBuf, usize) {
        let pb = path.to_path_buf();
        match self.get(path) {
            Some(s) => match s.logical_lines.get(&l) {
                Some(o) => (pb, *o),
                _ => (pb, l),
            },
            _ => (pb, l),
        }
    }
}

impl LineAnalysis {
    /// Creates a new LineAnalysis object
    fn new() -> LineAnalysis {
        Default::default()
    }

    /// Ignore all lines in the file
    pub fn ignore_all(&mut self) {
        self.ignore.clear();
        self.cover.clear();
        self.ignore.insert(Lines::All);
    }

    /// Ignore all tokens in the given token stream
    pub fn ignore_tokens<T>(&mut self, tokens: T)
    where
        T: ToTokens,
    {
        for token in tokens.into_token_stream() {
            self.ignore_span(token.span());
        }
    }

    /// Adds the lines of the provided span to the ignore set
    pub fn ignore_span(&mut self, span: Span) {
        // If we're already ignoring everything no need to ignore this span
        if !self.ignore.contains(&Lines::All) {
            for i in span.start().line..(span.end().line + 1) {
                self.ignore.insert(Lines::Line(i));
                if self.cover.contains(&i) {
                    self.cover.remove(&i);
                }
            }
        }
    }

    /// Cover all tokens in the given tokenstream
    pub fn cover_token_stream(&mut self, tokens: TokenStream, contents: Option<&str>) {
        for token in tokens {
            self.cover_span(token.span(), contents);
        }
    }

    /// Adds the lines of the provided span to the cover set
    pub fn cover_span(&mut self, span: Span, contents: Option<&str>) {
        // Not checking for Lines::All because I trust we've called cover_span
        // for a reason.
        let mut useful_lines: HashSet<usize> = HashSet::new();
        if let Some(ref c) = contents {
            lazy_static! {
                static ref SINGLE_LINE: Regex = Regex::new(r"\s*//").unwrap();
                static ref MULTI_START: Regex = Regex::new(r"/\*").unwrap();
                static ref MULTI_END: Regex = Regex::new(r"\*/").unwrap();
            }
            let len = span.end().line - span.start().line;
            let mut is_comment = false;
            for (i, line) in c.lines().enumerate().skip(span.start().line - 1).take(len) {
                let is_code = if MULTI_START.is_match(line) {
                    if !MULTI_END.is_match(line) {
                        is_comment = true;
                    }
                    false
                } else if is_comment {
                    if MULTI_END.is_match(line) {
                        is_comment = false;
                    }
                    false
                } else {
                    true
                };
                if is_code && !SINGLE_LINE.is_match(line) {
                    useful_lines.insert(i + 1);
                }
            }
        }
        for i in span.start().line..(span.end().line + 1) {
            if !self.ignore.contains(&Lines::Line(i)) && useful_lines.contains(&i) {
                self.cover.insert(i);
            }
        }
    }

    /// Shows whether the line should be ignored by tarpaulin
    pub fn should_ignore(&self, line: usize) -> bool {
        self.ignore.contains(&Lines::Line(line)) || self.ignore.contains(&Lines::All)
    }

    /// Adds a line to the list of lines to ignore
    fn add_to_ignore(&mut self, lines: &[usize]) {
        if !self.ignore.contains(&Lines::All) {
            for l in lines {
                self.ignore.insert(Lines::Line(*l));
                if self.cover.contains(l) {
                    self.cover.remove(l);
                }
            }
        }
    }
}

pub struct SourceAnalysis {
    pub lines: HashMap<PathBuf, LineAnalysis>,
    pub branches: HashMap<PathBuf, BranchAnalysis>,
}

impl SourceAnalysis {
    pub fn new() -> Self {
        Self {
            lines: HashMap::new(),
            branches: HashMap::new(),
        }
    }

    pub fn get_line_analysis(&mut self, path: PathBuf) -> &mut LineAnalysis {
        self.lines.entry(path).or_insert_with(LineAnalysis::new)
    }

    pub fn get_branch_analysis(&mut self, path: PathBuf) -> &mut BranchAnalysis {
        self.branches
            .entry(path)
            .or_insert_with(BranchAnalysis::new)
    }

    pub fn get_analysis(config: &Config) -> Self {
        let mut result = Self::new();
        let mut ignored_files: HashSet<PathBuf> = HashSet::new();
        let root = config.root();

        for e in get_source_walker(config) {
            if !ignored_files.contains(e.path()) {
                result.analyse_package(e.path(), &root, &config, &mut ignored_files);
            } else {
                let mut analysis = LineAnalysis::new();
                analysis.ignore_all();
                result.lines.insert(e.path().to_path_buf(), analysis);
                ignored_files.remove(e.path());
            }
        }
        for e in &ignored_files {
            let mut analysis = LineAnalysis::new();
            analysis.ignore_all();
            result.lines.insert(e.to_path_buf(), analysis);
        }

        result.debug_printout(config);

        result
    }

    /// Analyses a package of the target crate.
    fn analyse_package(
        &mut self,
        path: &Path,
        root: &Path,
        config: &Config,
        filtered_files: &mut HashSet<PathBuf>,
    ) {
        if let Some(file) = path.to_str() {
            let skip_cause_test = config.ignore_tests && path.starts_with(root.join("tests"));
            let skip_cause_example = path.starts_with(root.join("examples"))
                && !config.run_types.contains(&RunType::Examples);
            if !(skip_cause_test || skip_cause_example) {
                let file = File::open(file);
                if let Ok(mut file) = file {
                    let mut content = String::new();
                    let _ = file.read_to_string(&mut content);
                    let file = parse_file(&content);
                    if let Ok(file) = file {
                        let mut analysis = LineAnalysis::new();
                        let ctx = Context {
                            config,
                            file_contents: &content,
                            file: path,
                            ignore_mods: RefCell::new(HashSet::new()),
                        };

                        find_ignorable_lines(&content, &mut analysis);
                        self.lines.insert(path.to_path_buf(), analysis);
                        self.process_items(&file.items, &ctx);
                        //process_items(&file.items, &ctx, &mut analysis);
                        // Check there's no conflict!
                        //self.lines.insert(path.to_path_buf(), analysis);

                        let mut ignored_files = ctx.ignore_mods.into_inner();
                        for f in ignored_files.drain() {
                            if f.is_file() {
                                filtered_files.insert(f);
                            } else {
                                let walker = WalkDir::new(f).into_iter();
                                for e in walker.filter_map(|e| e.ok()).filter(|e| is_source_file(e))
                                {
                                    filtered_files.insert(e.path().to_path_buf());
                                }
                            }
                        }
                        // This could probably be done with the DWARF if I could find a discriminating factor
                        // to why lib.rs:1 shows up as a real line!
                        if path.ends_with("src/lib.rs") {
                            analyse_lib_rs(path, &mut self.lines);
                        }
                    }
                }
            }
        }
    }

    /// Printout a debug summary of the results of source analysis if debug logging
    /// is enabled
    pub fn debug_printout(&self, config: &Config) {
        if config.debug {
            for (ref path, ref analysis) in &self.lines {
                trace!(
                    "Source analysis for {}",
                    config.strip_base_dir(path).display()
                );
                let mut lines = Vec::new();
                for l in &analysis.ignore {
                    match l {
                        Lines::All => {
                            lines.clear();
                            trace!("All lines are ignorable");
                            break;
                        }
                        Lines::Line(i) => {
                            lines.push(i);
                        }
                    }
                }
                if !lines.is_empty() {
                    lines.sort();
                    trace!("Ignorable lines: {:?}", lines);
                    lines.clear()
                }
                for c in &analysis.cover {
                    lines.push(c);
                }

                if !lines.is_empty() {
                    lines.sort();
                    trace!("Coverable lines: {:?}", lines);
                }
            }
        }
    }
}

/// Analyse the crates lib.rs for some common false positives
fn analyse_lib_rs(file: &Path, result: &mut HashMap<PathBuf, LineAnalysis>) {
    if let Ok(f) = File::open(file) {
        let read_file = BufReader::new(f);
        if let Some(Ok(first)) = read_file.lines().next() {
            if !(first.starts_with("pub") || first.starts_with("fn")) {
                let file = file.to_path_buf();
                if result.contains_key(&file) {
                    let l = result.get_mut(&file).unwrap();
                    l.add_to_ignore(&[1]);
                } else {
                    let mut l = LineAnalysis::new();
                    l.add_to_ignore(&[1]);
                    result.insert(file, l);
                }
            }
        }
    }
}

/// Provides context to the source analysis stage including the tarpaulin
/// config and the source code being analysed.
pub(crate) struct Context<'a> {
    /// Program config
    config: &'a Config,
    /// Contents of the source file
    file_contents: &'a str,
    /// path to the file being analysed
    file: &'a Path,
    /// Other parts of context are immutable like tarpaulin config and users
    /// source code. This is discovered during hence use of interior mutability
    ignore_mods: RefCell<HashSet<PathBuf>>,
}

/// Finds lines from the raw string which are ignorable.
/// These are often things like close braces, semi colons that may regiser as
/// false positives.
fn find_ignorable_lines(content: &str, analysis: &mut LineAnalysis) {
    lazy_static! {
        static ref IGNORABLE: Regex = Regex::new(r"^((\s*///)|([\[\]\{\}\(\)\s;\?,/]*$))").unwrap();
    }
    let lines = content
        .lines()
        .enumerate()
        .filter(|&(_, x)| IGNORABLE.is_match(&x))
        .map(|(i, _)| i + 1)
        .collect::<Vec<usize>>();
    analysis.add_to_ignore(&lines);

    let lines = content
        .lines()
        .enumerate()
        .filter(|&(_, x)| {
            let mut x = x.to_string();
            x.retain(|c| !c.is_whitespace());
            x == "}else{"
        })
        .map(|(i, _)| i + 1)
        .collect::<Vec<usize>>();
    analysis.add_to_ignore(&lines);
}

pub fn visit_generics(generics: &Generics, analysis: &mut LineAnalysis) {
    if let Some(ref wh) = generics.where_clause {
        analysis.ignore_tokens(wh);
    }
}
<commit_msg>Debug print branch analysis<commit_after>use crate::branching::BranchAnalysis;
use crate::config::{Config, RunType};
use crate::path_utils::{get_source_walker, is_source_file};
use lazy_static::lazy_static;
use log::trace;
use proc_macro2::{Span, TokenStream};
use quote::ToTokens;
use regex::Regex;
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::fs::File;
use std::io::{BufRead, BufReader, Read};
use std::path::{Path, PathBuf};
use syn::*;
use walkdir::WalkDir;

mod attributes;
mod expressions;
mod items;
mod macros;
mod statements;
#[cfg(test)]
mod tests;

pub(crate) mod prelude {
    pub(crate) use super::*;
    pub(crate) use attributes::*;
    pub(crate) use macros::*;
}

/// Enumeration representing which lines to ignore
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum Lines {
    /// Ignore all lines in the file
    All,
    /// A single line to ignore in the file
    Line(usize),
}

/// Represents the results of analysis of a single file. Does not store the file
/// in question as this is expected to be maintained by the user.
#[derive(Clone, Debug, Default)]
pub struct LineAnalysis {
    /// This represents lines that should be ignored in coverage
    /// but may be identifed as coverable in the DWARF tables
    pub ignore: HashSet<Lines>,
    /// This represents lines that should be included in coverage
    /// But may be ignored. Doesn't make sense to cover ALL the lines so this
    /// is just an index.
    pub cover: HashSet<usize>,
    /// Some logical lines may be split between physical lines this shows the
    /// mapping from physical line to logical line to prevent false positives
    /// from expressions split across physical lines
    pub logical_lines: HashMap<usize, usize>,
}

/// When the `LineAnalysis` results are mapped to their files there needs to be
/// an easy way to get the information back. For the container used implement
/// this trait
pub trait SourceAnalysisQuery {
    /// Returns true if the line in the given file should be ignored
    fn should_ignore(&self, path: &Path, l: &usize) -> bool;
    /// Takes a path and line number and normalises it to the logical line
    /// that should be represented in the statistics
    fn normalise(&self, path: &Path, l: usize) -> (PathBuf, usize);
}

#[derive(Copy, Clone, Debug)]
pub(crate) enum SubResult {
    Ok,
    Unreachable,
}

impl SourceAnalysisQuery for HashMap<PathBuf, LineAnalysis> {
    fn should_ignore(&self, path: &Path, l: &usize) -> bool {
        if self.contains_key(path) {
            self.get(path).unwrap().should_ignore(*l)
        } else {
            false
        }
    }

    fn normalise(&self, path: &Path, l: usize) -> (PathBuf, usize) {
        let pb = path.to_path_buf();
        match self.get(path) {
            Some(s) => match s.logical_lines.get(&l) {
                Some(o) => (pb, *o),
                _ => (pb, l),
            },
            _ => (pb, l),
        }
    }
}

impl LineAnalysis {
    /// Creates a new LineAnalysis object
    fn new() -> LineAnalysis {
        Default::default()
    }

    /// Ignore all lines in the file
    pub fn ignore_all(&mut self) {
        self.ignore.clear();
        self.cover.clear();
        self.ignore.insert(Lines::All);
    }

    /// Ignore all tokens in the given token stream
    pub fn ignore_tokens<T>(&mut self, tokens: T)
    where
        T: ToTokens,
    {
        for token in tokens.into_token_stream() {
            self.ignore_span(token.span());
        }
    }

    /// Adds the lines of the provided span to the ignore set
    pub fn ignore_span(&mut self, span: Span) {
        // If we're already ignoring everything no need to ignore this span
        if !self.ignore.contains(&Lines::All) {
            for i in span.start().line..(span.end().line + 1) {
                self.ignore.insert(Lines::Line(i));
                if self.cover.contains(&i) {
                    self.cover.remove(&i);
                }
            }
        }
    }

    /// Cover all tokens in the given tokenstream
    pub fn cover_token_stream(&mut self, tokens: TokenStream, contents: Option<&str>) {
        for token in tokens {
            self.cover_span(token.span(), contents);
        }
    }

    /// Adds the lines of the provided span to the cover set
    pub fn cover_span(&mut self, span: Span, contents: Option<&str>) {
        // Not checking for Lines::All because I trust we've called cover_span
        // for a reason.
        let mut useful_lines: HashSet<usize> = HashSet::new();
        if let Some(ref c) = contents {
            lazy_static! {
                static ref SINGLE_LINE: Regex = Regex::new(r"\s*//").unwrap();
                static ref MULTI_START: Regex = Regex::new(r"/\*").unwrap();
                static ref MULTI_END: Regex = Regex::new(r"\*/").unwrap();
            }
            let len = span.end().line - span.start().line;
            let mut is_comment = false;
            for (i, line) in c.lines().enumerate().skip(span.start().line - 1).take(len) {
                let is_code = if MULTI_START.is_match(line) {
                    if !MULTI_END.is_match(line) {
                        is_comment = true;
                    }
                    false
                } else if is_comment {
                    if MULTI_END.is_match(line) {
                        is_comment = false;
                    }
                    false
                } else {
                    true
                };
                if is_code && !SINGLE_LINE.is_match(line) {
                    useful_lines.insert(i + 1);
                }
            }
        }
        for i in span.start().line..(span.end().line + 1) {
            if !self.ignore.contains(&Lines::Line(i)) && useful_lines.contains(&i) {
                self.cover.insert(i);
            }
        }
    }

    /// Shows whether the line should be ignored by tarpaulin
    pub fn should_ignore(&self, line: usize) -> bool {
        self.ignore.contains(&Lines::Line(line)) || self.ignore.contains(&Lines::All)
    }

    /// Adds a line to the list of lines to ignore
    fn add_to_ignore(&mut self, lines: &[usize]) {
        if !self.ignore.contains(&Lines::All) {
            for l in lines {
                self.ignore.insert(Lines::Line(*l));
                if self.cover.contains(l) {
                    self.cover.remove(l);
                }
            }
        }
    }
}

pub struct SourceAnalysis {
    pub lines: HashMap<PathBuf, LineAnalysis>,
    pub branches: HashMap<PathBuf, BranchAnalysis>,
}

impl SourceAnalysis {
    pub fn new() -> Self {
        Self {
            lines: HashMap::new(),
            branches: HashMap::new(),
        }
    }

    pub fn get_line_analysis(&mut self, path: PathBuf) -> &mut LineAnalysis {
        self.lines.entry(path).or_insert_with(LineAnalysis::new)
    }

    pub fn get_branch_analysis(&mut self, path: PathBuf) -> &mut BranchAnalysis {
        self.branches
            .entry(path)
            .or_insert_with(BranchAnalysis::new)
    }

    pub fn get_analysis(config: &Config) -> Self {
        let mut result = Self::new();
        let mut ignored_files: HashSet<PathBuf> = HashSet::new();
        let root = config.root();

        for e in get_source_walker(config) {
            if !ignored_files.contains(e.path()) {
                result.analyse_package(e.path(), &root, &config, &mut ignored_files);
            } else {
                let mut analysis = LineAnalysis::new();
                analysis.ignore_all();
                result.lines.insert(e.path().to_path_buf(), analysis);
                ignored_files.remove(e.path());
            }
        }
        for e in &ignored_files {
            let mut analysis = LineAnalysis::new();
            analysis.ignore_all();
            result.lines.insert(e.to_path_buf(), analysis);
        }

        result.debug_printout(config);

        result
    }

    /// Analyses a package of the target crate.
    fn analyse_package(
        &mut self,
        path: &Path,
        root: &Path,
        config: &Config,
        filtered_files: &mut HashSet<PathBuf>,
    ) {
        if let Some(file) = path.to_str() {
            let skip_cause_test = config.ignore_tests && path.starts_with(root.join("tests"));
            let skip_cause_example = path.starts_with(root.join("examples"))
                && !config.run_types.contains(&RunType::Examples);
            if !(skip_cause_test || skip_cause_example) {
                let file = File::open(file);
                if let Ok(mut file) = file {
                    let mut content = String::new();
                    let _ = file.read_to_string(&mut content);
                    let file = parse_file(&content);
                    if let Ok(file) = file {
                        let mut analysis = LineAnalysis::new();
                        let ctx = Context {
                            config,
                            file_contents: &content,
                            file: path,
                            ignore_mods: RefCell::new(HashSet::new()),
                        };

                        find_ignorable_lines(&content, &mut analysis);
                        self.lines.insert(path.to_path_buf(), analysis);
                        self.process_items(&file.items, &ctx);
                        //process_items(&file.items, &ctx, &mut analysis);
                        // Check there's no conflict!
                        //self.lines.insert(path.to_path_buf(), analysis);

                        let mut ignored_files = ctx.ignore_mods.into_inner();
                        for f in ignored_files.drain() {
                            if f.is_file() {
                                filtered_files.insert(f);
                            } else {
                                let walker = WalkDir::new(f).into_iter();
                                for e in walker.filter_map(|e| e.ok()).filter(|e| is_source_file(e))
                                {
                                    filtered_files.insert(e.path().to_path_buf());
                                }
                            }
                        }
                        // This could probably be done with the DWARF if I could find a discriminating factor
                        // to why lib.rs:1 shows up as a real line!
                        if path.ends_with("src/lib.rs") {
                            analyse_lib_rs(path, &mut self.lines);
                        }
                    }
                }
            }
        }
    }

    /// Printout a debug summary of the results of source analysis if debug logging
    /// is enabled
    pub fn debug_printout(&self, config: &Config) {
        if config.debug {
            for (ref path, ref analysis) in &self.lines {
                trace!(
                    "Source analysis for {}",
                    config.strip_base_dir(path).display()
                );
                let mut lines = Vec::new();
                for l in &analysis.ignore {
                    match l {
                        Lines::All => {
                            lines.clear();
                            trace!("All lines are ignorable");
                            break;
                        }
                        Lines::Line(i) => {
                            lines.push(i);
                        }
                    }
                }
                if !lines.is_empty() {
                    lines.sort();
                    trace!("Ignorable lines: {:?}", lines);
                    lines.clear()
                }
                for c in &analysis.cover {
                    lines.push(c);
                }

                if !lines.is_empty() {
                    lines.sort();
                    trace!("Coverable lines: {:?}", lines);
                }
            }
            if config.branch_coverage {
                trace!("Branch analysis");
                trace!("{:?}", self.branches);
            }
        }
    }
}

/// Analyse the crates lib.rs for some common false positives
fn analyse_lib_rs(file: &Path, result: &mut HashMap<PathBuf, LineAnalysis>) {
    if let Ok(f) = File::open(file) {
        let read_file = BufReader::new(f);
        if let Some(Ok(first)) = read_file.lines().next() {
            if !(first.starts_with("pub") || first.starts_with("fn")) {
                let file = file.to_path_buf();
                if result.contains_key(&file) {
                    let l = result.get_mut(&file).unwrap();
                    l.add_to_ignore(&[1]);
                } else {
                    let mut l = LineAnalysis::new();
                    l.add_to_ignore(&[1]);
                    result.insert(file, l);
                }
            }
        }
    }
}

/// Provides context to the source analysis stage including the tarpaulin
/// config and the source code being analysed.
pub(crate) struct Context<'a> {
    /// Program config
    config: &'a Config,
    /// Contents of the source file
    file_contents: &'a str,
    /// path to the file being analysed
    file: &'a Path,
    /// Other parts of context are immutable like tarpaulin config and users
    /// source code. This is discovered during hence use of interior mutability
    ignore_mods: RefCell<HashSet<PathBuf>>,
}

/// Finds lines from the raw string which are ignorable.
/// These are often things like close braces, semi colons that may regiser as
/// false positives.
fn find_ignorable_lines(content: &str, analysis: &mut LineAnalysis) {
    lazy_static! {
        static ref IGNORABLE: Regex = Regex::new(r"^((\s*///)|([\[\]\{\}\(\)\s;\?,/]*$))").unwrap();
    }
    let lines = content
        .lines()
        .enumerate()
        .filter(|&(_, x)| IGNORABLE.is_match(&x))
        .map(|(i, _)| i + 1)
        .collect::<Vec<usize>>();
    analysis.add_to_ignore(&lines);

    let lines = content
        .lines()
        .enumerate()
        .filter(|&(_, x)| {
            let mut x = x.to_string();
            x.retain(|c| !c.is_whitespace());
            x == "}else{"
        })
        .map(|(i, _)| i + 1)
        .collect::<Vec<usize>>();
    analysis.add_to_ignore(&lines);
}

pub fn visit_generics(generics: &Generics, analysis: &mut LineAnalysis) {
    if let Some(ref wh) = generics.where_clause {
        analysis.ignore_tokens(wh);
    }
}
<|endoftext|>