<commit_before>use std::io::prelude::*;
use std::fs::{self, File};
use std::path::{self, Path, PathBuf};

use semver::VersionReq;
use tar::Archive;
use flate2::{GzBuilder, Compression};
use flate2::read::GzDecoder;

use core::{SourceId, Package, PackageId};
use core::dependency::Kind;
use sources::PathSource;
use util::{self, CargoResult, human, internal, ChainError, Config};
use ops;

struct Bomb { path: Option<PathBuf> }

impl Drop for Bomb {
    fn drop(&mut self) {
        match self.path.as_ref() {
            Some(path) => { let _ = fs::remove_file(path); }
            None => {}
        }
    }
}

pub fn package(manifest_path: &Path,
               config: &Config,
               verify: bool,
               list: bool,
               metadata: bool) -> CargoResult<Option<PathBuf>> {
    let mut src = try!(PathSource::for_path(manifest_path.parent().unwrap(),
                                            config));
    let pkg = try!(src.root_package());

    if metadata {
        try!(check_metadata(&pkg, config));
    }

    try!(check_dependencies(&pkg, config));

    if list {
        let root = pkg.root();
        let mut list: Vec<_> = try!(src.list_files(&pkg)).iter().map(|file| {
            util::without_prefix(&file, &root).unwrap().to_path_buf()
        }).collect();
        list.sort();
        for file in list.iter() {
            println!("{}", file.display());
        }
        return Ok(None)
    }

    let filename = format!("package/{}-{}.crate", pkg.name(), pkg.version());
    let target_dir = config.target_dir(&pkg);
    let dst = target_dir.join(&filename);
    if fs::metadata(&dst).is_ok() { return Ok(Some(dst)) }

    let mut bomb = Bomb { path: Some(dst.clone()) };

    try!(config.shell().status("Packaging", pkg.package_id().to_string()));
    try!(tar(&pkg, &src, config, &dst).chain_error(|| {
        human("failed to prepare local package for uploading")
    }));
    if verify {
        try!(run_verify(config, &pkg, &dst).chain_error(|| {
            human("failed to verify package tarball")
        }))
    }
    Ok(Some(bomb.path.take().unwrap()))
}

// check that the package has some piece of metadata that a human can
// use to tell what the package is about.
#[allow(deprecated)] // connect => join in 1.3
fn check_metadata(pkg: &Package, config: &Config) -> CargoResult<()> {
    let md = pkg.manifest().metadata();

    let mut missing = vec![];

    macro_rules! lacking {
        ($( $($field: ident)||* ),*) => {{
            $(
                if $(md.$field.as_ref().map_or(true, |s| s.is_empty()))&&* {
                    $(missing.push(stringify!($field).replace("_", "-"));)*
                }
            )*
        }}
    }
    lacking!(description, license || license_file, documentation || homepage || repository);

    if !missing.is_empty() {
        let mut things = missing[..missing.len() - 1].connect(", ");
        // things will be empty if and only if length == 1 (i.e. the only case
        // to have no `or`).
        if !things.is_empty() {
            things.push_str(" or ");
        }
        things.push_str(&missing.last().unwrap());

        try!(config.shell().warn(
            &format!("warning: manifest has no {things}. \
                    See http://doc.crates.io/manifest.html#package-metadata for more info.",
                    things = things)))
    }
    Ok(())
}

// Warn about wildcard deps which will soon be prohibited on crates.io
#[allow(deprecated)] // connect => join in 1.3
fn check_dependencies(pkg: &Package, config: &Config) -> CargoResult<()> {
    let wildcard = VersionReq::parse("*").unwrap();

    let mut wildcard_deps = vec![];
    for dep in pkg.dependencies() {
        if dep.kind() != Kind::Development && dep.version_req() == &wildcard {
            wildcard_deps.push(dep.name());
        }
    }

    if !wildcard_deps.is_empty() {
        let deps = wildcard_deps.connect(", ");
        try!(config.shell().warn(
            "warning: some dependencies have wildcard (\"*\") version constraints. \
             On December 11th, 2015, crates.io will begin rejecting packages with \
             wildcard dependency constraints. See \
             http://doc.crates.io/crates-io.html#using-crates.io-based-crates \
             for information on version constraints."));
        try!(config.shell().warn(
            &format!("dependencies for these crates have wildcard constraints: {}", deps)));
    }
    Ok(())
}

fn tar(pkg: &Package, src: &PathSource, config: &Config,
       dst: &Path) -> CargoResult<()> {

    if fs::metadata(&dst).is_ok() {
        bail!("destination already exists: {}", dst.display())
    }

    try!(fs::create_dir_all(dst.parent().unwrap()));

    let tmpfile = try!(File::create(dst));

    // Prepare the encoder and its header
    let filename = Path::new(dst.file_name().unwrap());
    let encoder = GzBuilder::new().filename(try!(util::path2bytes(filename)))
                                  .write(tmpfile, Compression::Best);

    // Put all package files into a compressed archive
    let ar = Archive::new(encoder);
    let root = pkg.root();
    for file in try!(src.list_files(pkg)).iter() {
        if &**file == dst { continue }
        let relative = util::without_prefix(&file, &root).unwrap();
        try!(check_filename(relative));
        let relative = try!(relative.to_str().chain_error(|| {
            human(format!("non-utf8 path in source directory: {}",
                          relative.display()))
        }));
        let mut file = try!(File::open(file));
        try!(config.shell().verbose(|shell| {
            shell.status("Archiving", &relative)
        }));
        let path = format!("{}-{}{}{}", pkg.name(), pkg.version(),
                           path::MAIN_SEPARATOR, relative);
        try!(ar.append_file(&path, &mut file).chain_error(|| {
            internal(format!("could not archive source file `{}`", relative))
        }));
    }
    try!(ar.finish());
    Ok(())
}

fn run_verify(config: &Config, pkg: &Package, tar: &Path)
              -> CargoResult<()> {
    try!(config.shell().status("Verifying", pkg));

    let f = try!(GzDecoder::new(try!(File::open(tar))));
    let dst = pkg.root().join(&format!("target/package/{}-{}",
                                       pkg.name(), pkg.version()));
    if fs::metadata(&dst).is_ok() {
        try!(fs::remove_dir_all(&dst));
    }
    let mut archive = Archive::new(f);
    try!(archive.unpack(dst.parent().unwrap()));
    let manifest_path = dst.join("Cargo.toml");

    // When packages are uploaded to the registry, all path dependencies are
    // implicitly converted to registry-based dependencies, so we rewrite those
    // dependencies here.
    //
    // We also make sure to point all paths at `dst` instead of the previous
    // location that the package was originally read from. In locking the
    // `SourceId` we're telling it that the corresponding `PathSource` will be
    // considered updated and we won't actually read any packages.
    let registry = try!(SourceId::for_central(config));
    let precise = Some("locked".to_string());
    let new_src = try!(SourceId::for_path(&dst)).with_precise(precise);
    let new_pkgid = try!(PackageId::new(pkg.name(), pkg.version(), &new_src));
    let new_summary = pkg.summary().clone().map_dependencies(|d| {
        if !d.source_id().is_path() { return d }
        d.clone_inner().set_source_id(registry.clone()).into_dependency()
    });
    let mut new_manifest = pkg.manifest().clone();
    new_manifest.set_summary(new_summary.override_id(new_pkgid));
    let new_pkg = Package::new(new_manifest, &manifest_path);

    // Now that we've rewritten all our path dependencies, compile it!
    try!(ops::compile_pkg(&new_pkg, None, &ops::CompileOptions {
        config: config,
        jobs: None,
        target: None,
        features: &[],
        no_default_features: false,
        spec: &[],
        filter: ops::CompileFilter::Everything,
        exec_engine: None,
        release: false,
        mode: ops::CompileMode::Build,
        target_rustdoc_args: None,
        target_rustc_args: None,
    }));

    Ok(())
}

// It can often be the case that files of a particular name on one platform
// can't actually be created on another platform. For example files with colons
// in the name are allowed on Unix but not on Windows.
//
// To help out in situations like this, issue about weird filenames when
// packaging as a "heads up" that something may not work on other platforms.
fn check_filename(file: &Path) -> CargoResult<()> {
    let name = match file.file_name() {
        Some(name) => name,
        None => return Ok(()),
    };
    let name = match name.to_str() {
        Some(name) => name,
        None => {
            bail!("path does not have a unicode filename which may not unpack \
                   on all platforms: {}", file.display())
        }
    };
    let bad_chars = ['/', '\\', '<', '>', ':', '"', '|', '?', '*'];
    for c in bad_chars.iter().filter(|c| name.contains(**c)) {
        bail!("cannot package a filename with a special character `{}`: {}",
              c, file.display())
    }
    Ok(())
}
<commit_msg>Auto merge of #2186 - alexcrichton:transaction, r=brson<commit_after>use std::io::prelude::*;
use std::fs::{self, File};
use std::path::{self, Path, PathBuf};

use semver::VersionReq;
use tar::Archive;
use flate2::{GzBuilder, Compression};
use flate2::read::GzDecoder;

use core::{SourceId, Package, PackageId};
use core::dependency::Kind;
use sources::PathSource;
use util::{self, CargoResult, human, internal, ChainError, Config};
use ops;

pub fn package(manifest_path: &Path,
               config: &Config,
               verify: bool,
               list: bool,
               metadata: bool) -> CargoResult<Option<PathBuf>> {
    let mut src = try!(PathSource::for_path(manifest_path.parent().unwrap(),
                                            config));
    let pkg = try!(src.root_package());

    if metadata {
        try!(check_metadata(&pkg, config));
    }

    try!(check_dependencies(&pkg, config));

    if list {
        let root = pkg.root();
        let mut list: Vec<_> = try!(src.list_files(&pkg)).iter().map(|file| {
            util::without_prefix(&file, &root).unwrap().to_path_buf()
        }).collect();
        list.sort();
        for file in list.iter() {
            println!("{}", file.display());
        }
        return Ok(None)
    }

    let filename = format!("{}-{}.crate", pkg.name(), pkg.version());
    let dir = config.target_dir(&pkg).join("package");
    let dst = dir.join(&filename);
    if fs::metadata(&dst).is_ok() {
        return Ok(Some(dst))
    }

    // Package up and test a temporary tarball and only move it to the final
    // location if it actually passes all our tests. Any previously existing
    // tarball can be assumed as corrupt or invalid, so we just blow it away if
    // it exists.
    try!(config.shell().status("Packaging", pkg.package_id().to_string()));
    let tmp_dst = dir.join(format!(".{}", filename));
    let _ = fs::remove_file(&tmp_dst);
    try!(tar(&pkg, &src, config, &tmp_dst, &filename).chain_error(|| {
        human("failed to prepare local package for uploading")
    }));
    if verify {
        try!(run_verify(config, &pkg, &tmp_dst).chain_error(|| {
            human("failed to verify package tarball")
        }))
    }
    try!(fs::rename(&tmp_dst, &dst).chain_error(|| {
        human("failed to move temporary tarball into final location")
    }));
    Ok(Some(dst))
}

// check that the package has some piece of metadata that a human can
// use to tell what the package is about.
#[allow(deprecated)] // connect => join in 1.3
fn check_metadata(pkg: &Package, config: &Config) -> CargoResult<()> {
    let md = pkg.manifest().metadata();

    let mut missing = vec![];

    macro_rules! lacking {
        ($( $($field: ident)||* ),*) => {{
            $(
                if $(md.$field.as_ref().map_or(true, |s| s.is_empty()))&&* {
                    $(missing.push(stringify!($field).replace("_", "-"));)*
                }
            )*
        }}
    }
    lacking!(description, license || license_file, documentation || homepage || repository);

    if !missing.is_empty() {
        let mut things = missing[..missing.len() - 1].connect(", ");
        // things will be empty if and only if length == 1 (i.e. the only case
        // to have no `or`).
        if !things.is_empty() {
            things.push_str(" or ");
        }
        things.push_str(&missing.last().unwrap());

        try!(config.shell().warn(
            &format!("warning: manifest has no {things}. \
                    See http://doc.crates.io/manifest.html#package-metadata for more info.",
                    things = things)))
    }
    Ok(())
}

// Warn about wildcard deps which will soon be prohibited on crates.io
#[allow(deprecated)] // connect => join in 1.3
fn check_dependencies(pkg: &Package, config: &Config) -> CargoResult<()> {
    let wildcard = VersionReq::parse("*").unwrap();

    let mut wildcard_deps = vec![];
    for dep in pkg.dependencies() {
        if dep.kind() != Kind::Development && dep.version_req() == &wildcard {
            wildcard_deps.push(dep.name());
        }
    }

    if !wildcard_deps.is_empty() {
        let deps = wildcard_deps.connect(", ");
        try!(config.shell().warn(
            "warning: some dependencies have wildcard (\"*\") version constraints. \
             On December 11th, 2015, crates.io will begin rejecting packages with \
             wildcard dependency constraints. See \
             http://doc.crates.io/crates-io.html#using-crates.io-based-crates \
             for information on version constraints."));
        try!(config.shell().warn(
            &format!("dependencies for these crates have wildcard constraints: {}", deps)));
    }
    Ok(())
}

fn tar(pkg: &Package,
       src: &PathSource,
       config: &Config,
       dst: &Path,
       filename: &str) -> CargoResult<()> {
    if fs::metadata(&dst).is_ok() {
        bail!("destination already exists: {}", dst.display())
    }

    try!(fs::create_dir_all(dst.parent().unwrap()));

    let tmpfile = try!(File::create(dst));

    // Prepare the encoder and its header
    let filename = Path::new(filename);
    let encoder = GzBuilder::new().filename(try!(util::path2bytes(filename)))
                                  .write(tmpfile, Compression::Best);

    // Put all package files into a compressed archive
    let ar = Archive::new(encoder);
    let root = pkg.root();
    for file in try!(src.list_files(pkg)).iter() {
        if &**file == dst { continue }
        let relative = util::without_prefix(&file, &root).unwrap();
        try!(check_filename(relative));
        let relative = try!(relative.to_str().chain_error(|| {
            human(format!("non-utf8 path in source directory: {}",
                          relative.display()))
        }));
        let mut file = try!(File::open(file));
        try!(config.shell().verbose(|shell| {
            shell.status("Archiving", &relative)
        }));
        let path = format!("{}-{}{}{}", pkg.name(), pkg.version(),
                           path::MAIN_SEPARATOR, relative);
        try!(ar.append_file(&path, &mut file).chain_error(|| {
            internal(format!("could not archive source file `{}`", relative))
        }));
    }
    try!(ar.finish());
    Ok(())
}

fn run_verify(config: &Config, pkg: &Package, tar: &Path)
              -> CargoResult<()> {
    try!(config.shell().status("Verifying", pkg));

    let f = try!(GzDecoder::new(try!(File::open(tar))));
    let dst = pkg.root().join(&format!("target/package/{}-{}",
                                       pkg.name(), pkg.version()));
    if fs::metadata(&dst).is_ok() {
        try!(fs::remove_dir_all(&dst));
    }
    let mut archive = Archive::new(f);
    try!(archive.unpack(dst.parent().unwrap()));
    let manifest_path = dst.join("Cargo.toml");

    // When packages are uploaded to the registry, all path dependencies are
    // implicitly converted to registry-based dependencies, so we rewrite those
    // dependencies here.
    //
    // We also make sure to point all paths at `dst` instead of the previous
    // location that the package was originally read from. In locking the
    // `SourceId` we're telling it that the corresponding `PathSource` will be
    // considered updated and we won't actually read any packages.
    let registry = try!(SourceId::for_central(config));
    let precise = Some("locked".to_string());
    let new_src = try!(SourceId::for_path(&dst)).with_precise(precise);
    let new_pkgid = try!(PackageId::new(pkg.name(), pkg.version(), &new_src));
    let new_summary = pkg.summary().clone().map_dependencies(|d| {
        if !d.source_id().is_path() { return d }
        d.clone_inner().set_source_id(registry.clone()).into_dependency()
    });
    let mut new_manifest = pkg.manifest().clone();
    new_manifest.set_summary(new_summary.override_id(new_pkgid));
    let new_pkg = Package::new(new_manifest, &manifest_path);

    // Now that we've rewritten all our path dependencies, compile it!
    try!(ops::compile_pkg(&new_pkg, None, &ops::CompileOptions {
        config: config,
        jobs: None,
        target: None,
        features: &[],
        no_default_features: false,
        spec: &[],
        filter: ops::CompileFilter::Everything,
        exec_engine: None,
        release: false,
        mode: ops::CompileMode::Build,
        target_rustdoc_args: None,
        target_rustc_args: None,
    }));

    Ok(())
}

// It can often be the case that files of a particular name on one platform
// can't actually be created on another platform. For example files with colons
// in the name are allowed on Unix but not on Windows.
//
// To help out in situations like this, issue about weird filenames when
// packaging as a "heads up" that something may not work on other platforms.
fn check_filename(file: &Path) -> CargoResult<()> {
    let name = match file.file_name() {
        Some(name) => name,
        None => return Ok(()),
    };
    let name = match name.to_str() {
        Some(name) => name,
        None => {
            bail!("path does not have a unicode filename which may not unpack \
                   on all platforms: {}", file.display())
        }
    };
    let bad_chars = ['/', '\\', '<', '>', ':', '"', '|', '?', '*'];
    for c in bad_chars.iter().filter(|c| name.contains(**c)) {
        bail!("cannot package a filename with a special character `{}`: {}",
              c, file.display())
    }
    Ok(())
}
<|endoftext|>


<commit_before>use std::collections::HashMap;
use std::fmt::{self, Debug, Formatter};
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};

use rustc_serialize::hex::ToHex;
use rustc_serialize::json;

use core::{Package, PackageId, Summary, SourceId, Source, Dependency, Registry};
use sources::PathSource;
use util::{CargoResult, human, ChainError, Config, Sha256};
use util::paths;

pub struct DirectorySource<'cfg> {
    source_id: SourceId,
    root: PathBuf,
    packages: HashMap<PackageId, (Package, Checksum)>,
    config: &'cfg Config,
}

#[derive(RustcDecodable)]
struct Checksum {
    package: String,
    files: HashMap<String, String>,
}

impl<'cfg> DirectorySource<'cfg> {
    pub fn new(path: &Path, id: &SourceId, config: &'cfg Config)
               -> DirectorySource<'cfg> {
        DirectorySource {
            source_id: id.clone(),
            root: path.to_path_buf(),
            config: config,
            packages: HashMap::new(),
        }
    }
}

impl<'cfg> Debug for DirectorySource<'cfg> {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        write!(f, "DirectorySource {{ root: {:?} }}", self.root)
    }
}

impl<'cfg> Registry for DirectorySource<'cfg> {
    fn query(&mut self, dep: &Dependency) -> CargoResult<Vec<Summary>> {
        let packages = self.packages.values().map(|p| &p.0);
        let matches = packages.filter(|pkg| dep.matches(pkg.summary()));
        let summaries = matches.map(|pkg| pkg.summary().clone());
        Ok(summaries.collect())
    }

    fn supports_checksums(&self) -> bool {
        true
    }
}

impl<'cfg> Source for DirectorySource<'cfg> {
    fn source_id(&self) -> &SourceId {
        &self.source_id
    }

    fn update(&mut self) -> CargoResult<()> {
        self.packages.clear();
        let entries = self.root.read_dir().chain_error(|| {
            human(format!("failed to read root of directory source: {}",
                          self.root.display()))
        })?;

        for entry in entries {
            let entry = entry?;
            let path = entry.path();
            let mut src = PathSource::new(&path, &self.source_id, self.config);
            src.update()?;
            let pkg = src.root_package()?;

            let cksum_file = path.join(".cargo-checksum.json");
            let cksum = paths::read(&path.join(cksum_file)).chain_error(|| {
                human(format!("failed to load checksum `.cargo-checksum.json` \
                               of {} v{}",
                              pkg.package_id().name(),
                              pkg.package_id().version()))

            })?;
            let cksum: Checksum = json::decode(&cksum).chain_error(|| {
                human(format!("failed to decode `.cargo-checksum.json` of \
                               {} v{}",
                              pkg.package_id().name(),
                              pkg.package_id().version()))
            })?;

            let mut manifest = pkg.manifest().clone();
            let summary = manifest.summary().clone();
            manifest.set_summary(summary.set_checksum(cksum.package.clone()));
            let pkg = Package::new(manifest, pkg.manifest_path());
            self.packages.insert(pkg.package_id().clone(), (pkg, cksum));
        }

        Ok(())
    }

    fn download(&mut self, id: &PackageId) -> CargoResult<Package> {
        self.packages.get(id).map(|p| &p.0).cloned().chain_error(|| {
            human(format!("failed to find package with id: {}", id))
        })
    }

    fn fingerprint(&self, pkg: &Package) -> CargoResult<String> {
        Ok(pkg.package_id().version().to_string())
    }

    fn verify(&self, id: &PackageId) -> CargoResult<()> {
        let (pkg, cksum) = match self.packages.get(id) {
            Some(&(ref pkg, ref cksum)) => (pkg, cksum),
            None => bail!("failed to find entry for `{}` in directory source",
                          id),
        };

        let mut buf = [0; 16 * 1024];
        for (file, cksum) in cksum.files.iter() {
            let mut h = Sha256::new();
            let file = pkg.root().join(file);

            (|| -> CargoResult<()> {
                let mut f = File::open(&file)?;
                loop {
                    match f.read(&mut buf)? {
                        0 => return Ok(()),
                        n => h.update(&buf[..n]),
                    }
                }
            }).chain_error(|| {
                human(format!("failed to calculate checksum of: {}",
                              file.display()))
            })?;

            let actual = h.finish().to_hex();
            if &*actual != cksum {
                bail!("\
                    the listed checksum of `{}` has changed:\n\
                    expected: {}\n\
                    actual:   {}\n\
                    \n\
                    directory sources are not intended to be edited, if \
                    modifications are required then it is recommended \
                    that [replace] is used with a forked copy of the \
                    source\
                ", file.display(), cksum, actual);
            }
        }

        Ok(())
    }
}
<commit_msg>Auto merge of #3417 - alexcrichton:fix-svn, r=brson<commit_after>use std::collections::HashMap;
use std::fmt::{self, Debug, Formatter};
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};

use rustc_serialize::hex::ToHex;
use rustc_serialize::json;

use core::{Package, PackageId, Summary, SourceId, Source, Dependency, Registry};
use sources::PathSource;
use util::{CargoResult, human, ChainError, Config, Sha256};
use util::paths;

pub struct DirectorySource<'cfg> {
    source_id: SourceId,
    root: PathBuf,
    packages: HashMap<PackageId, (Package, Checksum)>,
    config: &'cfg Config,
}

#[derive(RustcDecodable)]
struct Checksum {
    package: String,
    files: HashMap<String, String>,
}

impl<'cfg> DirectorySource<'cfg> {
    pub fn new(path: &Path, id: &SourceId, config: &'cfg Config)
               -> DirectorySource<'cfg> {
        DirectorySource {
            source_id: id.clone(),
            root: path.to_path_buf(),
            config: config,
            packages: HashMap::new(),
        }
    }
}

impl<'cfg> Debug for DirectorySource<'cfg> {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        write!(f, "DirectorySource {{ root: {:?} }}", self.root)
    }
}

impl<'cfg> Registry for DirectorySource<'cfg> {
    fn query(&mut self, dep: &Dependency) -> CargoResult<Vec<Summary>> {
        let packages = self.packages.values().map(|p| &p.0);
        let matches = packages.filter(|pkg| dep.matches(pkg.summary()));
        let summaries = matches.map(|pkg| pkg.summary().clone());
        Ok(summaries.collect())
    }

    fn supports_checksums(&self) -> bool {
        true
    }
}

impl<'cfg> Source for DirectorySource<'cfg> {
    fn source_id(&self) -> &SourceId {
        &self.source_id
    }

    fn update(&mut self) -> CargoResult<()> {
        self.packages.clear();
        let entries = self.root.read_dir().chain_error(|| {
            human(format!("failed to read root of directory source: {}",
                          self.root.display()))
        })?;

        for entry in entries {
            let entry = entry?;
            let path = entry.path();

            // Ignore hidden/dot directories as they typically don't contain
            // crates and otherwise may conflict with a VCS
            // (rust-lang/cargo#3414).
            if let Some(s) = path.file_name().and_then(|s| s.to_str()) {
                if s.starts_with(".") {
                    continue
                }
            }

            let mut src = PathSource::new(&path, &self.source_id, self.config);
            src.update()?;
            let pkg = src.root_package()?;

            let cksum_file = path.join(".cargo-checksum.json");
            let cksum = paths::read(&path.join(cksum_file)).chain_error(|| {
                human(format!("failed to load checksum `.cargo-checksum.json` \
                               of {} v{}",
                              pkg.package_id().name(),
                              pkg.package_id().version()))

            })?;
            let cksum: Checksum = json::decode(&cksum).chain_error(|| {
                human(format!("failed to decode `.cargo-checksum.json` of \
                               {} v{}",
                              pkg.package_id().name(),
                              pkg.package_id().version()))
            })?;

            let mut manifest = pkg.manifest().clone();
            let summary = manifest.summary().clone();
            manifest.set_summary(summary.set_checksum(cksum.package.clone()));
            let pkg = Package::new(manifest, pkg.manifest_path());
            self.packages.insert(pkg.package_id().clone(), (pkg, cksum));
        }

        Ok(())
    }

    fn download(&mut self, id: &PackageId) -> CargoResult<Package> {
        self.packages.get(id).map(|p| &p.0).cloned().chain_error(|| {
            human(format!("failed to find package with id: {}", id))
        })
    }

    fn fingerprint(&self, pkg: &Package) -> CargoResult<String> {
        Ok(pkg.package_id().version().to_string())
    }

    fn verify(&self, id: &PackageId) -> CargoResult<()> {
        let (pkg, cksum) = match self.packages.get(id) {
            Some(&(ref pkg, ref cksum)) => (pkg, cksum),
            None => bail!("failed to find entry for `{}` in directory source",
                          id),
        };

        let mut buf = [0; 16 * 1024];
        for (file, cksum) in cksum.files.iter() {
            let mut h = Sha256::new();
            let file = pkg.root().join(file);

            (|| -> CargoResult<()> {
                let mut f = File::open(&file)?;
                loop {
                    match f.read(&mut buf)? {
                        0 => return Ok(()),
                        n => h.update(&buf[..n]),
                    }
                }
            }).chain_error(|| {
                human(format!("failed to calculate checksum of: {}",
                              file.display()))
            })?;

            let actual = h.finish().to_hex();
            if &*actual != cksum {
                bail!("\
                    the listed checksum of `{}` has changed:\n\
                    expected: {}\n\
                    actual:   {}\n\
                    \n\
                    directory sources are not intended to be edited, if \
                    modifications are required then it is recommended \
                    that [replace] is used with a forked copy of the \
                    source\
                ", file.display(), cksum, actual);
            }
        }

        Ok(())
    }
}
<|endoftext|>


<commit_before>// Copyright 2020 Google LLC
//
// 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
//
//    https://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.

use crate::{Error as EngineError, IncludeCppEngine, RebuildDependencyRecorder};
use proc_macro2::TokenStream;
use quote::ToTokens;
use std::{fmt::Display, io::Read, path::PathBuf};
use std::{panic::UnwindSafe, path::Path, rc::Rc};
use syn::Item;

/// Errors which may occur when parsing a Rust source file to discover
/// and interpret include_cxx macros.
#[derive(Debug)]
pub enum ParseError {
    /// Unable to open the source file
    FileOpen(std::io::Error),
    /// The .rs file couldn't be read.
    FileRead(std::io::Error),
    /// The .rs file couldn't be parsed.
    Syntax(syn::Error),
    /// The include CPP macro could not be expanded into
    /// Rust bindings to C++, because of some problem during the conversion
    /// process. This could be anything from a C++ parsing error to some
    /// C++ feature that autocxx can't yet handle and isn't able to skip
    /// over. It could also cover errors in your syntax of the `include_cpp`
    /// macro or the directives inside.
    AutocxxCodegenError(EngineError),
}

impl Display for ParseError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ParseError::FileOpen(err) => write!(f, "Unable to open file: {}", err)?,
            ParseError::FileRead(err) => write!(f, "Unable to read file: {}", err)?,
            ParseError::Syntax(err) => write!(f, "Syntax error parsing Rust file: {}", err)?,
            ParseError::AutocxxCodegenError(err) => {
                write!(f, "Unable to parse include_cpp! macro: {}", err)?
            }
        }
        Ok(())
    }
}

/// Parse a Rust file, and spot any include_cpp macros within it.
pub fn parse_file<P1: AsRef<Path>>(rs_file: P1) -> Result<ParsedFile, ParseError> {
    let mut source = String::new();
    let mut file = std::fs::File::open(rs_file).map_err(ParseError::FileOpen)?;
    file.read_to_string(&mut source)
        .map_err(ParseError::FileRead)?;
    proc_macro2::fallback::force();
    let source = syn::parse_file(&source).map_err(ParseError::Syntax)?;
    parse_file_contents(source)
}

fn parse_file_contents(source: syn::File) -> Result<ParsedFile, ParseError> {
    let mut results = Vec::new();
    for item in source.items {
        if let Item::Macro(ref mac) = item {
            if mac.mac.path.is_ident("include_cpp") {
                let include_cpp = crate::IncludeCppEngine::new_from_syn(mac.mac.clone())
                    .map_err(ParseError::AutocxxCodegenError)?;
                results.push(Segment::Autocxx(include_cpp));
                continue;
            }
        }
        results.push(Segment::Other(item));
    }
    Ok(ParsedFile(results))
}

/// A Rust file parsed by autocxx. May contain zero or more autocxx 'engines',
/// i.e. the `IncludeCpp` class, corresponding to zero or more include_cpp
/// macros within this file. Also contains `syn::Item` structures for all
/// the rest of the Rust code, such that it can be reconstituted if necessary.
pub struct ParsedFile(Vec<Segment>);

#[allow(clippy::large_enum_variant)]
enum Segment {
    Autocxx(IncludeCppEngine),
    Other(Item),
}

impl ParsedFile {
    /// Get all the autocxxes in this parsed file.
    pub fn get_autocxxes(&self) -> Vec<&IncludeCppEngine> {
        self.0
            .iter()
            .filter_map(|s| match s {
                Segment::Autocxx(includecpp) => Some(includecpp),
                Segment::Other(_) => None,
            })
            .collect()
    }

    pub fn get_autocxxes_mut(&mut self) -> Vec<&mut IncludeCppEngine> {
        self.0
            .iter_mut()
            .filter_map(|s| match s {
                Segment::Autocxx(includecpp) => Some(includecpp),
                Segment::Other(_) => None,
            })
            .collect()
    }

    pub fn resolve_all(
        &mut self,
        autocxx_inc: Vec<PathBuf>,
        definitions: &[impl AsRef<str>],
        dep_recorder: Option<Box<dyn RebuildDependencyRecorder>>,
    ) -> Result<(), ParseError> {
        let inner_dep_recorder: Option<Rc<dyn RebuildDependencyRecorder>> =
            dep_recorder.map(Rc::from);
        for include_cpp in self.get_autocxxes_mut() {
            let dep_recorder: Option<Box<dyn RebuildDependencyRecorder>> = match &inner_dep_recorder
            {
                None => None,
                Some(inner_dep_recorder) => Some(Box::new(CompositeDepRecorder::new(
                    inner_dep_recorder.clone(),
                ))),
            };
            let defs: Vec<String> = definitions.iter().map(|d| d.as_ref().to_string()).collect();
            include_cpp
                .generate(autocxx_inc.clone(), &defs, dep_recorder)
                .map_err(ParseError::AutocxxCodegenError)?
        }
        Ok(())
    }
}

impl ToTokens for ParsedFile {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        for seg in &self.0 {
            match seg {
                Segment::Other(item) => item.to_tokens(tokens),
                Segment::Autocxx(autocxx) => {
                    let these_tokens = autocxx.generate_rs();
                    tokens.extend(these_tokens);
                }
            }
        }
    }
}

/// Shenanigans required to share the same RebuildDependencyRecorder
/// with all of the include_cpp instances in this one file.
#[derive(Debug, Clone)]
struct CompositeDepRecorder(Rc<dyn RebuildDependencyRecorder>);

impl CompositeDepRecorder {
    fn new(inner: Rc<dyn RebuildDependencyRecorder>) -> Self {
        CompositeDepRecorder(inner)
    }
}

impl UnwindSafe for CompositeDepRecorder {}

impl RebuildDependencyRecorder for CompositeDepRecorder {
    fn record_header_file_dependency(&self, filename: &str) {
        self.0.record_header_file_dependency(filename);
    }
}
<commit_msg>Suppress clippy lint.<commit_after>// Copyright 2020 Google LLC
//
// 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
//
//    https://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.

use crate::{Error as EngineError, IncludeCppEngine, RebuildDependencyRecorder};
use proc_macro2::TokenStream;
use quote::ToTokens;
use std::{fmt::Display, io::Read, path::PathBuf};
use std::{panic::UnwindSafe, path::Path, rc::Rc};
use syn::Item;

/// Errors which may occur when parsing a Rust source file to discover
/// and interpret include_cxx macros.
#[derive(Debug)]
pub enum ParseError {
    /// Unable to open the source file
    FileOpen(std::io::Error),
    /// The .rs file couldn't be read.
    FileRead(std::io::Error),
    /// The .rs file couldn't be parsed.
    Syntax(syn::Error),
    /// The include CPP macro could not be expanded into
    /// Rust bindings to C++, because of some problem during the conversion
    /// process. This could be anything from a C++ parsing error to some
    /// C++ feature that autocxx can't yet handle and isn't able to skip
    /// over. It could also cover errors in your syntax of the `include_cpp`
    /// macro or the directives inside.
    AutocxxCodegenError(EngineError),
}

impl Display for ParseError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ParseError::FileOpen(err) => write!(f, "Unable to open file: {}", err)?,
            ParseError::FileRead(err) => write!(f, "Unable to read file: {}", err)?,
            ParseError::Syntax(err) => write!(f, "Syntax error parsing Rust file: {}", err)?,
            ParseError::AutocxxCodegenError(err) => {
                write!(f, "Unable to parse include_cpp! macro: {}", err)?
            }
        }
        Ok(())
    }
}

/// Parse a Rust file, and spot any include_cpp macros within it.
pub fn parse_file<P1: AsRef<Path>>(rs_file: P1) -> Result<ParsedFile, ParseError> {
    let mut source = String::new();
    let mut file = std::fs::File::open(rs_file).map_err(ParseError::FileOpen)?;
    file.read_to_string(&mut source)
        .map_err(ParseError::FileRead)?;
    proc_macro2::fallback::force();
    let source = syn::parse_file(&source).map_err(ParseError::Syntax)?;
    parse_file_contents(source)
}

fn parse_file_contents(source: syn::File) -> Result<ParsedFile, ParseError> {
    let mut results = Vec::new();
    for item in source.items {
        if let Item::Macro(ref mac) = item {
            if mac.mac.path.is_ident("include_cpp") {
                let include_cpp = crate::IncludeCppEngine::new_from_syn(mac.mac.clone())
                    .map_err(ParseError::AutocxxCodegenError)?;
                results.push(Segment::Autocxx(include_cpp));
                continue;
            }
        }
        results.push(Segment::Other(item));
    }
    Ok(ParsedFile(results))
}

/// A Rust file parsed by autocxx. May contain zero or more autocxx 'engines',
/// i.e. the `IncludeCpp` class, corresponding to zero or more include_cpp
/// macros within this file. Also contains `syn::Item` structures for all
/// the rest of the Rust code, such that it can be reconstituted if necessary.
pub struct ParsedFile(Vec<Segment>);

#[allow(clippy::large_enum_variant)]
enum Segment {
    Autocxx(IncludeCppEngine),
    Other(Item),
}

impl ParsedFile {
    /// Get all the autocxxes in this parsed file.
    pub fn get_autocxxes(&self) -> Vec<&IncludeCppEngine> {
        self.0
            .iter()
            .filter_map(|s| match s {
                Segment::Autocxx(includecpp) => Some(includecpp),
                Segment::Other(_) => None,
            })
            .collect()
    }

    pub fn get_autocxxes_mut(&mut self) -> Vec<&mut IncludeCppEngine> {
        self.0
            .iter_mut()
            .filter_map(|s| match s {
                Segment::Autocxx(includecpp) => Some(includecpp),
                Segment::Other(_) => None,
            })
            .collect()
    }

    pub fn resolve_all(
        &mut self,
        autocxx_inc: Vec<PathBuf>,
        definitions: &[impl AsRef<str>],
        dep_recorder: Option<Box<dyn RebuildDependencyRecorder>>,
    ) -> Result<(), ParseError> {
        let inner_dep_recorder: Option<Rc<dyn RebuildDependencyRecorder>> =
            dep_recorder.map(Rc::from);
        for include_cpp in self.get_autocxxes_mut() {
            #[allow(clippy::manual_map)] // because of dyn shenanigans
            let dep_recorder: Option<Box<dyn RebuildDependencyRecorder>> = match &inner_dep_recorder
            {
                None => None,
                Some(inner_dep_recorder) => Some(Box::new(CompositeDepRecorder::new(
                    inner_dep_recorder.clone(),
                ))),
            };
            let defs: Vec<String> = definitions.iter().map(|d| d.as_ref().to_string()).collect();
            include_cpp
                .generate(autocxx_inc.clone(), &defs, dep_recorder)
                .map_err(ParseError::AutocxxCodegenError)?
        }
        Ok(())
    }
}

impl ToTokens for ParsedFile {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        for seg in &self.0 {
            match seg {
                Segment::Other(item) => item.to_tokens(tokens),
                Segment::Autocxx(autocxx) => {
                    let these_tokens = autocxx.generate_rs();
                    tokens.extend(these_tokens);
                }
            }
        }
    }
}

/// Shenanigans required to share the same RebuildDependencyRecorder
/// with all of the include_cpp instances in this one file.
#[derive(Debug, Clone)]
struct CompositeDepRecorder(Rc<dyn RebuildDependencyRecorder>);

impl CompositeDepRecorder {
    fn new(inner: Rc<dyn RebuildDependencyRecorder>) -> Self {
        CompositeDepRecorder(inner)
    }
}

impl UnwindSafe for CompositeDepRecorder {}

impl RebuildDependencyRecorder for CompositeDepRecorder {
    fn record_header_file_dependency(&self, filename: &str) {
        self.0.record_header_file_dependency(filename);
    }
}
<|endoftext|>


<commit_before>use bot::LalafellBot;
use commands::*;
use tasks::AutoTagTask;

use discord::model::{LiveServer, PublicChannel, UserId, ServerId, permissions};

use std::sync::Arc;

pub struct UpdateTagCommand {
  bot: Arc<LalafellBot>
}

impl UpdateTagCommand {
  pub fn new(bot: Arc<LalafellBot>) -> UpdateTagCommand {
    UpdateTagCommand {
      bot: bot
    }
  }
}

impl HasBot for UpdateTagCommand {
  fn bot(&self) -> Arc<LalafellBot> {
    self.bot.clone()
  }
}

#[derive(Debug, Deserialize)]
pub struct Params {
  who: Option<MentionOrId>
}

impl HasParams for UpdateTagCommand {
  type Params = Params;
}

impl<'a> PublicChannelCommand<'a> for UpdateTagCommand {
  fn run(&self, message: &Message, server: &LiveServer, channel: &PublicChannel, params: &[&str]) -> CommandResult<'a> {
    let params = self.params("", params)?;
    let id = if let Some(who) = params.who {
      let can_manage_roles = server.permissions_for(channel.id, message.author.id).contains(permissions::MANAGE_ROLES);
      if !can_manage_roles {
        return Err(ExternalCommandFailure::default()
          .message(|e: EmbedBuilder| e
            .title("Not enough permissions.")
            .description("You don't have enough permissions to update other people's tags."))
          .wrap());
      }
      *who
    } else {
      message.author.id
    };
    let user: Option<(UserId, ServerId, u64)> = {
      let database = self.bot.database.read().unwrap();
      database.autotags.users.iter()
        .find(|u| u.user_id == id.0 && u.server_id == channel.server_id.0)
        .map(|u| (UserId(u.user_id), ServerId(u.server_id), u.character_id))
    };
    let (user_id, server_id, character_id) = match user {
      Some(u) => u,
      None => return if id == message.author.id {
        Err("You are not set up with a tag. Use `!autotag` to tag yourself.".into())
      } else {
        Err(format!("{} is not set up with a tag.", id.mention()).into())
      }
    };
    let option_state = self.bot.state.read().unwrap();
    let state = match option_state.as_ref() {
      Some(st) => st,
      None => return Err("I'm not fully synced with Discord! Please try again later.".into())
    };
    match AutoTagTask::update_tag(self.bot.as_ref(), state, user_id, server_id, character_id) {
      Ok(Some(err)) => Err(err.into()),
      Err(e) => Err(e.into()),
      Ok(None) => Ok(CommandSuccess::default())
    }
  }
}
<commit_msg>refactor: use match<commit_after>use bot::LalafellBot;
use commands::*;
use tasks::AutoTagTask;

use discord::model::{LiveServer, PublicChannel, UserId, ServerId, permissions};

use std::sync::Arc;

pub struct UpdateTagCommand {
  bot: Arc<LalafellBot>
}

impl UpdateTagCommand {
  pub fn new(bot: Arc<LalafellBot>) -> UpdateTagCommand {
    UpdateTagCommand {
      bot: bot
    }
  }
}

impl HasBot for UpdateTagCommand {
  fn bot(&self) -> Arc<LalafellBot> {
    self.bot.clone()
  }
}

#[derive(Debug, Deserialize)]
pub struct Params {
  who: Option<MentionOrId>
}

impl HasParams for UpdateTagCommand {
  type Params = Params;
}

impl<'a> PublicChannelCommand<'a> for UpdateTagCommand {
  fn run(&self, message: &Message, server: &LiveServer, channel: &PublicChannel, params: &[&str]) -> CommandResult<'a> {
    let params = self.params("", params)?;
    let id = match params.who {
      Some(who) => {
        let can_manage_roles = server.permissions_for(channel.id, message.author.id).contains(permissions::MANAGE_ROLES);
        if !can_manage_roles {
          return Err(ExternalCommandFailure::default()
            .message(|e: EmbedBuilder| e
              .title("Not enough permissions.")
              .description("You don't have enough permissions to update other people's tags."))
            .wrap());
        }
        *who
      },
      None => message.author.id
    };
    let user: Option<(UserId, ServerId, u64)> = {
      let database = self.bot.database.read().unwrap();
      database.autotags.users.iter()
        .find(|u| u.user_id == id.0 && u.server_id == channel.server_id.0)
        .map(|u| (UserId(u.user_id), ServerId(u.server_id), u.character_id))
    };
    let (user_id, server_id, character_id) = match user {
      Some(u) => u,
      None => return if id == message.author.id {
        Err("You are not set up with a tag. Use `!autotag` to tag yourself.".into())
      } else {
        Err(format!("{} is not set up with a tag.", id.mention()).into())
      }
    };
    let option_state = self.bot.state.read().unwrap();
    let state = match option_state.as_ref() {
      Some(st) => st,
      None => return Err("I'm not fully synced with Discord! Please try again later.".into())
    };
    match AutoTagTask::update_tag(self.bot.as_ref(), state, user_id, server_id, character_id) {
      Ok(Some(err)) => Err(err.into()),
      Err(e) => Err(e.into()),
      Ok(None) => Ok(CommandSuccess::default())
    }
  }
}
<|endoftext|>


<commit_before>extern crate tiny_http;

#[cfg(not(feature = "ssl"))]
fn main() { println!("This example requires the `ssl` feature to be enabled"); }

#[cfg(feature = "ssl")]
fn main() {
    use tiny_http::{Server, Response};

    let server = Server::https("0.0.0.0:8000", tiny_http::SslConfig {
        certificate: include_bytes!("ssl-cert.pem").to_vec(),
        private_key: include_bytes!("ssl-key.pem").to_vec(),
    }).unwrap();

    println!("Note: connecting to this server will likely give you a warning from your browser
              because the connection is unsecure. This is because the certificate used by this
              example is self-signed. With a real certificate, you wouldn't get this warning.");

    for request in server.incoming_requests() {
        println!("received request! method: {:?}, url: {:?}, headers: {:?}",
            request.method(),
            request.url(),
            request.headers()
        );

        let response = Response::from_string("hello world");
        request.respond(response);
    }
}
<commit_msg>Improve warning<commit_after>extern crate tiny_http;

#[cfg(not(feature = "ssl"))]
fn main() { println!("This example requires the `ssl` feature to be enabled"); }

#[cfg(feature = "ssl")]
fn main() {
    use tiny_http::{Server, Response};

    let server = Server::https("0.0.0.0:8000", tiny_http::SslConfig {
        certificate: include_bytes!("ssl-cert.pem").to_vec(),
        private_key: include_bytes!("ssl-key.pem").to_vec(),
    }).unwrap();

    println!("Note: connecting to this server will likely give you a warning from your browser \
              because the connection is unsecure. This is because the certificate used by this \
              example is self-signed. With a real certificate, you wouldn't get this warning.");

    for request in server.incoming_requests() {
        println!("received request! method: {:?}, url: {:?}, headers: {:?}",
            request.method(),
            request.url(),
            request.headers()
        );

        let response = Response::from_string("hello world");
        request.respond(response);
    }
}
<|endoftext|>


<commit_before>use ecs::*;
use game::*;
use util::LeakyReserver;
use math::Coord;

struct Env {
    sh: SpatialHashTable,
    ctx: EcsCtx,
    ids: LeakyReserver<EntityId>,
}

impl Env {
    fn new() -> Self {
        Env {
            sh: SpatialHashTable::new(),
            ctx: EcsCtx::new(),
            ids: LeakyReserver::new(),
        }
    }
}

#[test]
fn insert_remove() {
    let mut env = Env::new();

    let mut action = EcsAction::new();
    let coord = Coord::new(1, 2);

    let id = {
        let mut entity = action.entity_mut(env.ids.reserve());
        entity.insert_position(coord);
        entity.insert_solid();

        entity.id()
    };

    assert!(!env.sh.get(coord).solid());
    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action); // this resets the action so it can be reused
    assert!(env.sh.get(coord).solid());

    action.entity_mut(id).remove_solid();

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);
    assert!(!env.sh.get(coord).solid());
}

#[test]
fn insert_move() {
    let mut env = Env::new();

    let mut action = EcsAction::new();
    let start_coord = Coord::new(1, 2);
    let end_coord = Coord::new(1, 3);

    let id = {
        let mut entity = action.entity_mut(env.ids.reserve());
        entity.insert_position(start_coord);
        entity.insert_solid();

        entity.id()
    };

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);

    action.entity_mut(id).insert_position(end_coord);

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);

    assert!(!env.sh.get(start_coord).solid());
    assert!(env.sh.get(end_coord).solid());
}

#[test]
fn remove_position() {
    let mut env = Env::new();

    let mut action = EcsAction::new();
    let start_coord = Coord::new(1, 2);

    let id = {
        let mut entity = action.entity_mut(env.ids.reserve());
        entity.insert_position(start_coord);
        entity.insert_solid();

        entity.id()
    };

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);

    action.entity_mut(id).remove_position();

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);

    assert!(!env.sh.get(start_coord).solid());
}

#[test]
fn insert_solid() {
    let mut env = Env::new();

    let mut action = EcsAction::new();
    let start_coord = Coord::new(1, 2);

    let id = {
        let mut entity = action.entity_mut(env.ids.reserve());
        entity.insert_position(start_coord);

        entity.id()
    };

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);

    action.entity_mut(id).insert_solid();

    assert!(!env.sh.get(start_coord).solid());

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);

    assert!(env.sh.get(start_coord).solid());
}

#[test]
fn track_opacity() {
    let mut env = Env::new();

    let mut action = EcsAction::new();
    let start_coord = Coord::new(1, 2);
    let end_coord = Coord::new(1, 3);

    // initialise with no opacity
    let id = {
        let mut entity = action.entity_mut(env.ids.reserve());
        entity.insert_position(start_coord);

        entity.id()
    };

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);

    assert_eq!((env.sh.get(start_coord).opacity() * 10.0).round(), 0.0 * 10.0);

    // add an opacity of 0.5
    action.entity_mut(id).insert_opacity(0.5);

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);

    assert_eq!((env.sh.get(start_coord).opacity() * 10.0).round(), 0.5 * 10.0);

    // decrease opacity to 0.2
    action.entity_mut(id).insert_opacity(0.2);

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);

    assert_eq!((env.sh.get(start_coord).opacity() * 10.0).round(), 0.2 * 10.0);

    // move the entity
    action.entity_mut(id).insert_position(end_coord);

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);
    assert_eq!((env.sh.get(start_coord).opacity() * 10.0).round(), 0.0 * 10.0);
    assert_eq!((env.sh.get(end_coord).opacity() * 10.0).round(), 0.2 * 10.0);
}

#[test]
fn insert_move_multiple() {
     let mut env = Env::new();

    let mut action = EcsAction::new();
    let start_coord = Coord::new(1, 2);
    let end_coord = Coord::new(1, 3);

    // add solid entity
    let id_a = {
        let mut entity = action.entity_mut(env.ids.reserve());
        entity.insert_position(start_coord);
        entity.insert_solid();

        entity.id()
    };

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);

    assert!(env.sh.get(start_coord).solid());

    // add second solid entity in same cell
    let id_b = {
        let mut entity = action.entity_mut(env.ids.reserve());
        entity.insert_position(start_coord);
        entity.insert_solid();

        entity.id()
    };

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);

    assert!(env.sh.get(start_coord).solid());

    // move original entity
    action.entity_mut(id_a).insert_position(end_coord);

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);

    assert!(env.sh.get(start_coord).solid());
    assert!(env.sh.get(end_coord).solid());

    // move second entity
    action.entity_mut(id_b).insert_position(end_coord);

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);

    assert!(!env.sh.get(start_coord).solid());
    assert!(env.sh.get(end_coord).solid());

    // move both entities in single action
    action.entity_mut(id_a).insert_position(start_coord);
    action.entity_mut(id_b).insert_position(start_coord);

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);

    assert!(env.sh.get(start_coord).solid());
    assert!(!env.sh.get(end_coord).solid());
}

#[test]
fn entity_set() {
    let mut env = Env::new();

    let mut action = EcsAction::new();

    let coord_a = Coord::new(1, 2);
    let coord_b = Coord::new(1, 3);

    let id_a = {
        let mut entity = action.entity_mut(env.ids.reserve());
        entity.insert_position(coord_a);
        entity.id()
    };

    let id_b = {
        let mut entity = action.entity_mut(env.ids.reserve());
        entity.insert_position(coord_a);
        entity.id()
    };

    assert!(env.sh.get(coord_a).entity_ids().is_empty());

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);

    {
        let entities = env.sh.get(coord_a).entity_ids();
        assert!(entities.contains(id_a));
        assert!(entities.contains(id_b));
        assert!(entities.len() == 2);
    }

    action.entity_mut(id_b).insert_position(coord_b);
    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);

    {
        let entities_a = env.sh.get(coord_a).entity_ids();
        let entities_b = env.sh.get(coord_b).entity_ids();

        assert!(entities_a.len() == 1);
        assert!(entities_b.len() == 1);
        assert!(entities_a.contains(id_a));
        assert!(entities_b.contains(id_b));
    }
}
<commit_msg>Added move and swap tests for spatial hash<commit_after>use ecs::*;
use game::*;
use util::LeakyReserver;
use math::Coord;

struct Env {
    sh: SpatialHashTable,
    ctx: EcsCtx,
    ids: LeakyReserver<EntityId>,
}

impl Env {
    fn new() -> Self {
        Env {
            sh: SpatialHashTable::new(),
            ctx: EcsCtx::new(),
            ids: LeakyReserver::new(),
        }
    }
}

#[test]
fn insert_remove() {
    let mut env = Env::new();

    let mut action = EcsAction::new();
    let coord = Coord::new(1, 2);

    let id = {
        let mut entity = action.entity_mut(env.ids.reserve());
        entity.insert_position(coord);
        entity.insert_solid();

        entity.id()
    };

    assert!(!env.sh.get(coord).solid());
    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action); // this resets the action so it can be reused
    assert!(env.sh.get(coord).solid());

    action.entity_mut(id).remove_solid();

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);
    assert!(!env.sh.get(coord).solid());
}

#[test]
fn insert_move() {
    let mut env = Env::new();

    let mut action = EcsAction::new();
    let start_coord = Coord::new(1, 2);
    let end_coord = Coord::new(1, 3);

    let id = {
        let mut entity = action.entity_mut(env.ids.reserve());
        entity.insert_position(start_coord);
        entity.insert_solid();

        entity.id()
    };

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);

    action.entity_mut(id).insert_position(end_coord);

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);

    assert!(!env.sh.get(start_coord).solid());
    assert!(env.sh.get(end_coord).solid());
}

#[test]
fn remove_position() {
    let mut env = Env::new();

    let mut action = EcsAction::new();
    let start_coord = Coord::new(1, 2);

    let id = {
        let mut entity = action.entity_mut(env.ids.reserve());
        entity.insert_position(start_coord);
        entity.insert_solid();

        entity.id()
    };

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);

    action.entity_mut(id).remove_position();

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);

    assert!(!env.sh.get(start_coord).solid());
}

#[test]
fn insert_solid() {
    let mut env = Env::new();

    let mut action = EcsAction::new();
    let start_coord = Coord::new(1, 2);

    let id = {
        let mut entity = action.entity_mut(env.ids.reserve());
        entity.insert_position(start_coord);

        entity.id()
    };

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);

    action.entity_mut(id).insert_solid();

    assert!(!env.sh.get(start_coord).solid());

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);

    assert!(env.sh.get(start_coord).solid());
}

#[test]
fn track_opacity() {
    let mut env = Env::new();

    let mut action = EcsAction::new();
    let start_coord = Coord::new(1, 2);
    let end_coord = Coord::new(1, 3);

    // initialise with no opacity
    let id = {
        let mut entity = action.entity_mut(env.ids.reserve());
        entity.insert_position(start_coord);

        entity.id()
    };

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);

    assert_eq!((env.sh.get(start_coord).opacity() * 10.0).round(),
        0.0 * 10.0);

    // add an opacity of 0.5
    action.entity_mut(id).insert_opacity(0.5);

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);

    assert_eq!((env.sh.get(start_coord).opacity() * 10.0).round(),
        (0.5_f64 * 10.0).round());

    // decrease opacity to 0.2
    action.entity_mut(id).insert_opacity(0.2);

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);

    assert_eq!((env.sh.get(start_coord).opacity() * 10.0).round(), 0.2 * 10.0);

    // move the entity
    action.entity_mut(id).insert_position(end_coord);

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);
    assert_eq!((env.sh.get(start_coord).opacity() * 10.0).round(), 0.0 * 10.0);
    assert_eq!((env.sh.get(end_coord).opacity() * 10.0).round(), 0.2 * 10.0);
}

#[test]
fn insert_move_multiple() {
     let mut env = Env::new();

    let mut action = EcsAction::new();
    let start_coord = Coord::new(1, 2);
    let end_coord = Coord::new(1, 3);

    // add solid entity
    let id_a = {
        let mut entity = action.entity_mut(env.ids.reserve());
        entity.insert_position(start_coord);
        entity.insert_solid();

        entity.id()
    };

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);

    assert!(env.sh.get(start_coord).solid());

    // add second solid entity in same cell
    let id_b = {
        let mut entity = action.entity_mut(env.ids.reserve());
        entity.insert_position(start_coord);
        entity.insert_solid();

        entity.id()
    };

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);

    assert!(env.sh.get(start_coord).solid());

    // move original entity
    action.entity_mut(id_a).insert_position(end_coord);

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);

    assert!(env.sh.get(start_coord).solid());
    assert!(env.sh.get(end_coord).solid());

    // move second entity
    action.entity_mut(id_b).insert_position(end_coord);

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);

    assert!(!env.sh.get(start_coord).solid());
    assert!(env.sh.get(end_coord).solid());

    // move both entities in single action
    action.entity_mut(id_a).insert_position(start_coord);
    action.entity_mut(id_b).insert_position(start_coord);

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);

    assert!(env.sh.get(start_coord).solid());
    assert!(!env.sh.get(end_coord).solid());
}

#[test]
fn entity_set() {
    let mut env = Env::new();

    let mut action = EcsAction::new();

    let coord_a = Coord::new(1, 2);
    let coord_b = Coord::new(1, 3);

    let id_a = {
        let mut entity = action.entity_mut(env.ids.reserve());
        entity.insert_position(coord_a);
        entity.id()
    };

    let id_b = {
        let mut entity = action.entity_mut(env.ids.reserve());
        entity.insert_position(coord_a);
        entity.id()
    };

    assert!(env.sh.get(coord_a).entity_ids().is_empty());

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);

    {
        let entities = env.sh.get(coord_a).entity_ids();
        assert!(entities.contains(id_a));
        assert!(entities.contains(id_b));
        assert!(entities.len() == 2);
    }

    action.entity_mut(id_b).insert_position(coord_b);
    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);

    {
        let entities_a = env.sh.get(coord_a).entity_ids();
        let entities_b = env.sh.get(coord_b).entity_ids();

        assert!(entities_a.len() == 1);
        assert!(entities_b.len() == 1);
        assert!(entities_a.contains(id_a));
        assert!(entities_b.contains(id_b));
    }
}

#[test]
fn component_move() {

    let mut env = Env::new();
    let mut action = EcsAction::new();

    let coord_a = Coord::new(1, 2);
    let coord_b = Coord::new(1, 3);

    let id_a = {
        let mut entity = action.entity_mut(env.ids.reserve());
        entity.insert_position(coord_a);
        entity.insert_solid();
        entity.id()
    };

    let id_b = {
        let mut entity = action.entity_mut(env.ids.reserve());
        entity.insert_position(coord_b);
        entity.insert_opacity(0.5);
        entity.id()
    };

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);

    assert!(env.sh.get(coord_a).solid());
    assert!(!env.sh.get(coord_b).solid());
    assert_eq!((env.sh.get(coord_a).opacity() * 10.0).round(),
        (0_f64 * 10.0).round());
    assert_eq!((env.sh.get(coord_b).opacity() * 10.0).round(),
        (0.5_f64 * 10.0).round());

    action.move_solid(id_a, id_b);
    action.move_opacity(id_b, id_a);

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);

    assert!(!env.sh.get(coord_a).solid());
    assert!(env.sh.get(coord_b).solid());
    assert_eq!((env.sh.get(coord_a).opacity() * 10.0).round(),
        (0.5_f64 * 10.0).round());
    assert_eq!((env.sh.get(coord_b).opacity() * 10.0).round(),
        (0_f64 * 10.0).round());
}

#[test]
fn component_swap() {

    let mut env = Env::new();
    let mut action = EcsAction::new();

    let coord_a = Coord::new(1, 2);
    let coord_b = Coord::new(1, 3);

    let id_a = {
        let mut entity = action.entity_mut(env.ids.reserve());
        entity.insert_position(coord_a);
        entity.insert_opacity(1.0);
        entity.id()
    };

    let id_b = {
        let mut entity = action.entity_mut(env.ids.reserve());
        entity.insert_position(coord_b);
        entity.insert_opacity(0.5);
        entity.id()
    };

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);

    assert_eq!((env.sh.get(coord_a).opacity() * 10.0).round(),
        (1.0_f64 * 10.0).round());
    assert_eq!((env.sh.get(coord_b).opacity() * 10.0).round(),
        (0.5_f64 * 10.0).round());

    action.swap_opacity(id_b, id_a);

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);

    assert_eq!((env.sh.get(coord_a).opacity() * 10.0).round(),
        (0.5_f64 * 10.0).round());
    assert_eq!((env.sh.get(coord_b).opacity() * 10.0).round(),
        (1.0_f64 * 10.0).round());
}

#[test]
fn component_empty_swap() {

    let mut env = Env::new();
    let mut action = EcsAction::new();

    let coord_a = Coord::new(1, 2);
    let coord_b = Coord::new(1, 3);

    let id_a = {
        let mut entity = action.entity_mut(env.ids.reserve());
        entity.insert_position(coord_a);
        entity.id()
    };

    let id_b = {
        let mut entity = action.entity_mut(env.ids.reserve());
        entity.insert_position(coord_b);
        entity.insert_opacity(0.5);
        entity.id()
    };

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);

    assert_eq!((env.sh.get(coord_a).opacity() * 10.0).round(),
        (0.0_f64 * 10.0).round());
    assert_eq!((env.sh.get(coord_b).opacity() * 10.0).round(),
        (0.5_f64 * 10.0).round());

    action.swap_opacity(id_b, id_a);

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);

    assert_eq!((env.sh.get(coord_a).opacity() * 10.0).round(),
        (0.5_f64 * 10.0).round());
    assert_eq!((env.sh.get(coord_b).opacity() * 10.0).round(),
        (0_f64 * 10.0).round());
}

#[test]
fn component_position_swap() {

    let mut env = Env::new();
    let mut action = EcsAction::new();

    let coord_a = Coord::new(1, 2);
    let coord_b = Coord::new(1, 3);

    let id_a = {
        let mut entity = action.entity_mut(env.ids.reserve());
        entity.insert_position(coord_a);
        entity.insert_opacity(1.0);
        entity.id()
    };

    let id_b = {
        let mut entity = action.entity_mut(env.ids.reserve());
        entity.insert_position(coord_b);
        entity.insert_opacity(0.5);
        entity.id()
    };

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);

    assert_eq!((env.sh.get(coord_a).opacity() * 10.0).round(),
        (1.0_f64 * 10.0).round());
    assert_eq!((env.sh.get(coord_b).opacity() * 10.0).round(),
        (0.5_f64 * 10.0).round());

    action.swap_position(id_b, id_a);

    env.sh.update(ActionEnv::new(&env.ctx, 0), &action);
    env.ctx.commit(&mut action);

    assert_eq!((env.sh.get(coord_a).opacity() * 10.0).round(),
        (0.5_f64 * 10.0).round());
    assert_eq!((env.sh.get(coord_b).opacity() * 10.0).round(),
        (1.0_f64 * 10.0).round());
}
<|endoftext|>


<commit_before>use engine::{Game, Command, MessageType, Tile};
use gui::screens::{self, Screen, ScreenChange};
use gui::{Console, Colors, Key};
use gui::chars;
use util::units::{AsTuple, Direction, Offset, Point, Rectangle, Size};

#[allow(missing_copy_implementations)]
pub struct GameScreen {
    map_frame: Rectangle,
    map_view: Point,
    info_frame: Rectangle,
    message_frame: Rectangle,
}

impl GameScreen {
    pub fn new() -> Box<Screen> {
        Box::new(
            GameScreen {
                map_frame: Rectangle::new(Point::new(16, 1), Size::new(63, 38)),
                map_view: Point::new(0, 0),
                info_frame: Rectangle::new(Point::new(1, 1), Size::new(13, 48)),
                message_frame: Rectangle::new(Point::new(16, 41), Size::new(63, 8)),
            }
        )
    }
}

impl Screen for GameScreen {

    #[allow(unused)]
    fn input(&mut self, game: &mut Game, console: &mut Console) -> Option<ScreenChange> {
        if let Some(key) = console.check_for_keypress() {
            match key {
                Key::Up => {
                    game.do_command(Command::Walk(Direction::Up));
                },
                Key::Down => {
                    game.do_command(Command::Walk(Direction::Down));
                },
                Key::Left => {
                    game.do_command(Command::Walk(Direction::Left));
                },
                Key::Right => {
                    game.do_command(Command::Walk(Direction::Right));
                },
                Key::Escape => {
                    return Some(ScreenChange::AddScreen(screens::PauseScreen::new()));
                },
                _ => {}
            }
        }

        None
    }

    #[allow(unused)]
    fn update(&mut self, game: &mut Game, console: &mut Console) -> Option<ScreenChange> {
        game.step();
        None
    }

    #[allow(unused)]
    fn render(&mut self, game: &mut Game, console: &mut Console) {
        self.draw_borders(game, console);
        self.draw_map(game, console);
        self.draw_player(game, console);
        self.draw_messages(game, console);
    }
}

impl GameScreen {

    #[allow(unused)]
    fn draw_borders(&self, game: &mut Game, console: &mut Console) {
        self.draw_box_with_title(console, "Map", self.map_frame.translate(Offset::new(-1, -1)).resize(Offset::new(1, 1)));
        self.draw_box_with_title(console, "Info", self.info_frame.translate(Offset::new(-1, -1)).resize(Offset::new(1, 1)));
        self.draw_box_with_title(console, "Messages", self.message_frame.translate(Offset::new(-1, -1)).resize(Offset::new(1, 1)));
    }

    fn draw_box_with_title(&self, console: &mut Console, title: &str, rect: Rectangle) {

        let loc = rect.location();
        let width = rect.width();
        let height = rect.height();

        console.put_plain(loc + (0, 0),             chars::NW);
        console.put_plain(loc + (width, 0),         chars::NE);
        console.put_plain(loc + (0, height),        chars::SW);
        console.put_plain(loc + (width, height),    chars::SE);

        for x in 1..width {
            console.put_plain(loc + (x, 0),         chars::HLINE);
            console.put_plain(loc + (x, height),    chars::HLINE);
        }

        for y in 1..height {
            console.put_plain(loc + (0, y),         chars::VLINE);
            console.put_plain(loc + (width, y),     chars::VLINE);
        }

        console.print_plain(rect.location() + (1, 0), title);
    }

    #[allow(unused)]
    fn draw_map(&self, game: &mut Game, console: &mut Console) {
        let map = &game.world.map;

        let (ux, uy) = self.map_view.as_tuple();
        let (width, height) : (usize, usize) = self.map_frame.size().as_tuple();

        for (y, line) in map.tiles[uy .. uy + height].iter().enumerate() {
            for (x, cell) in line[ux .. ux + width].iter().enumerate() {
                let bg_color = match *cell {
                    Tile::Empty => Colors::black,
                    Tile::Wall => Colors::darker_grey,
                    Tile::Floor => Colors::darkest_sepia,
                };

                console.put(self.map_frame.location() + Point::new(x as i32, y as i32), ' ', Colors::white, bg_color);
            }
        }
    }

    #[allow(unused)]
    fn draw_player(&mut self, game: &mut Game, console: &mut Console) {
        let pos = *game.world.player.pos();

        let adjusted_pos = pos + self.map_frame.location() - self.map_view;

        if adjusted_pos.x >= self.map_frame.location().x + self.map_frame.width() - 10 {
            if self.view_can_move_right(game) {
                self.map_view = self.map_view.right(1);
            }
        }

        if adjusted_pos.y >= self.map_frame.location().y + self.map_frame.height() - 5 {
            if self.view_can_move_down(game) {
                self.map_view = self.map_view.down(1);
            }
        }

        if adjusted_pos.y <= self.map_frame.location().y + 5 {
            if self.view_can_move_up(game) {
                self.map_view = self.map_view.up(1);
            }
        }

        if adjusted_pos.x <= self.map_frame.location().x + 10 {
            if self.view_can_move_left(game) {
                self.map_view = self.map_view.left(1);
            }
        }

        if adjusted_pos.x >= self.map_frame.location().x && adjusted_pos.y >= self.map_frame.location().y {
            console.put_plain(self.map_frame.location() - self.map_view + pos, '@');
        }
    }

    #[allow(unused)]
    fn draw_messages(&mut self, game: &mut Game, console: &mut Console) {
        let nmessages = self.message_frame.height() as usize;

        for (i, msg) in game.log.items().take(nmessages).enumerate() {
            let message_color = match *msg.ty() {
                MessageType::Info => Colors::white,
                MessageType::Error => Colors::red,
            };

            console.print(self.message_frame.location().down(i as i32), msg.text(), message_color, Colors::black);
        }
    }

    #[allow(unused)]
    fn view_can_move_up(&self, game: &Game) -> bool {
        self.map_view.y  > 0
    }

    #[allow(unused)]
    fn view_can_move_down(&self, game: &Game) -> bool {
        self.map_view.y + self.map_frame.height() < game.world.map.height()
    }

    #[allow(unused)]
    fn view_can_move_left(&self, game: &Game) -> bool {
        self.map_view.x > 0
    }

    #[allow(unused)]
    fn view_can_move_right(&self, game: &Game) -> bool {
        self.map_view.x + self.map_frame.width() < game.world.map.width()
    }

}
<commit_msg>Move GUI frame locations into a struct<commit_after>use engine::{Game, Command, MessageType, Tile};
use gui::screens::{self, Screen, ScreenChange};
use gui::{Console, Colors, Key};
use gui::chars;
use util::units::{AsTuple, Direction, Offset, Point, Rectangle, Size};

#[allow(missing_copy_implementations)]
pub struct GameScreen {
    frames: Frames,
    map_view: Point,
}

struct Frames {
    map: Rectangle,
    info: Rectangle,
    messages: Rectangle,
}

impl GameScreen {
    pub fn new() -> Box<Screen> {
        let info_frame_location = Point::new(1, 1);
        let map_frame_location = Point::new(16, 1);
        let message_frame_location = Point::new(16, 41);

        let frames = Frames {
            map: Rectangle::new(map_frame_location, Size::new(63, 38)),
            info: Rectangle::new(info_frame_location, Size::new(13, 48)),
            messages: Rectangle::new(message_frame_location, Size::new(63, 8)),
        };

        Box::new(
            GameScreen {
                frames: frames,
                map_view: Point::new(0, 0),
            }
        )
    }
}

impl Screen for GameScreen {

    #[allow(unused)]
    fn input(&mut self, game: &mut Game, console: &mut Console) -> Option<ScreenChange> {
        if let Some(key) = console.check_for_keypress() {
            match key {
                Key::Up => {
                    game.do_command(Command::Walk(Direction::Up));
                },
                Key::Down => {
                    game.do_command(Command::Walk(Direction::Down));
                },
                Key::Left => {
                    game.do_command(Command::Walk(Direction::Left));
                },
                Key::Right => {
                    game.do_command(Command::Walk(Direction::Right));
                },
                Key::Escape => {
                    return Some(ScreenChange::AddScreen(screens::PauseScreen::new()));
                },
                _ => {}
            }
        }

        None
    }

    #[allow(unused)]
    fn update(&mut self, game: &mut Game, console: &mut Console) -> Option<ScreenChange> {
        game.step();
        None
    }

    #[allow(unused)]
    fn render(&mut self, game: &mut Game, console: &mut Console) {
        self.draw_borders(game, console);
        self.draw_map(game, console);
        self.draw_player(game, console);
        self.draw_messages(game, console);
    }
}

impl GameScreen {

    #[allow(unused)]
    fn draw_borders(&self, game: &mut Game, console: &mut Console) {
        self.draw_box_with_title(console, "Map", self.frames.map.translate(Offset::new(-1, -1)).resize(Offset::new(1, 1)));
        self.draw_box_with_title(console, "Info", self.frames.info.translate(Offset::new(-1, -1)).resize(Offset::new(1, 1)));
        self.draw_box_with_title(console, "Messages", self.frames.messages.translate(Offset::new(-1, -1)).resize(Offset::new(1, 1)));
    }

    fn draw_box_with_title(&self, console: &mut Console, title: &str, rect: Rectangle) {

        let loc = rect.location();
        let width = rect.width();
        let height = rect.height();

        console.put_plain(loc + (0, 0),             chars::NW);
        console.put_plain(loc + (width, 0),         chars::NE);
        console.put_plain(loc + (0, height),        chars::SW);
        console.put_plain(loc + (width, height),    chars::SE);

        for x in 1..width {
            console.put_plain(loc + (x, 0),         chars::HLINE);
            console.put_plain(loc + (x, height),    chars::HLINE);
        }

        for y in 1..height {
            console.put_plain(loc + (0, y),         chars::VLINE);
            console.put_plain(loc + (width, y),     chars::VLINE);
        }

        console.print_plain(rect.location() + (1, 0), title);
    }

    #[allow(unused)]
    fn draw_map(&self, game: &mut Game, console: &mut Console) {
        let map = &game.world.map;

        let (ux, uy) = self.map_view.as_tuple();
        let (width, height) : (usize, usize) = self.frames.map.size().as_tuple();

        for (y, line) in map.tiles[uy .. uy + height].iter().enumerate() {
            for (x, cell) in line[ux .. ux + width].iter().enumerate() {
                let bg_color = match *cell {
                    Tile::Empty => Colors::black,
                    Tile::Wall => Colors::darker_grey,
                    Tile::Floor => Colors::darkest_sepia,
                };

                console.put(self.frames.map.location() + Point::new(x as i32, y as i32), ' ', Colors::white, bg_color);
            }
        }
    }

    #[allow(unused)]
    fn draw_player(&mut self, game: &mut Game, console: &mut Console) {
        let pos = *game.world.player.pos();

        let adjusted_pos = pos + self.frames.map.location() - self.map_view;

        if adjusted_pos.x >= self.frames.map.location().x + self.frames.map.width() - 10 {
            if self.view_can_move_right(game) {
                self.map_view = self.map_view.right(1);
            }
        }

        if adjusted_pos.y >= self.frames.map.location().y + self.frames.map.height() - 5 {
            if self.view_can_move_down(game) {
                self.map_view = self.map_view.down(1);
            }
        }

        if adjusted_pos.y <= self.frames.map.location().y + 5 {
            if self.view_can_move_up(game) {
                self.map_view = self.map_view.up(1);
            }
        }

        if adjusted_pos.x <= self.frames.map.location().x + 10 {
            if self.view_can_move_left(game) {
                self.map_view = self.map_view.left(1);
            }
        }

        if adjusted_pos.x >= self.frames.map.location().x && adjusted_pos.y >= self.frames.map.location().y {
            console.put_plain(self.frames.map.location() - self.map_view + pos, '@');
        }
    }

    #[allow(unused)]
    fn draw_messages(&mut self, game: &mut Game, console: &mut Console) {
        let nmessages = self.frames.messages.height() as usize;

        for (i, msg) in game.log.items().take(nmessages).enumerate() {
            let message_color = match *msg.ty() {
                MessageType::Info => Colors::white,
                MessageType::Error => Colors::red,
            };

            console.print(self.frames.messages.location().down(i as i32), msg.text(), message_color, Colors::black);
        }
    }

    #[allow(unused)]
    fn view_can_move_up(&self, game: &Game) -> bool {
        self.map_view.y  > 0
    }

    #[allow(unused)]
    fn view_can_move_down(&self, game: &Game) -> bool {
        self.map_view.y + self.frames.map.height() < game.world.map.height()
    }

    #[allow(unused)]
    fn view_can_move_left(&self, game: &Game) -> bool {
        self.map_view.x > 0
    }

    #[allow(unused)]
    fn view_can_move_right(&self, game: &Game) -> bool {
        self.map_view.x + self.frames.map.width() < game.world.map.width()
    }

}
<|endoftext|>