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

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

pub struct PythonBuildpack;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

struct PoetryLayer;

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

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

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

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

        poetry.run_sync(&["install"])?;

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

struct PipLayer;

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

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

    fn create(
        &self,
        context: &BuildContext<Self::Buildpack>,
        layer_path: &Path,
    ) -> Result<LayerResult<Self::Metadata>, eyre::Report> {
        // Reuse or create a virtualenv.
        // The name `.venv` is most commonly used for this (e.g. Poetry uses that) but
        // we also look for `venv` and `env`.
        const VENV: &str = ".venv";
        let mut virtualenv = PathBuf::from(VENV);
        if !virtualenv.exists() {
            let venv = PathBuf::from("venv");
            let env = PathBuf::from("env");
            if venv.join("bin").join("python").exists() {
                virtualenv = venv;
            } else if env.join("bin").join("python").exists() {
                virtualenv = env;
            } else {
                // Require Python >=3.4 because that is when `ensurepip` was added (although we don't
                // need to run that module explicitly)
                // TODO: the version of Python that is created should be the one installed by the `PythonLayer`.
                let python = PythonBinary {}.require_sync(Some(">=3.4,<4".to_string()), true)?;
                python.run_sync(&["-m", "venv", "--clear", VENV])?;
            }
        }
        let virtualenv = virtualenv.canonicalize()?;

        // Use the Python in the virtualenv, assume it is `python3` it has `pip`
        let mut python = BinaryInstallation {
            name: "python3".into(),
            path: virtualenv.join("bin").join("python3"),
            ..Default::default()
        };

        // Make the layer the Poetry cache (instead of a system level one)
        if !platform_is_stencila(&context.platform) {
            python.envs(&[("PIP_CACHE_DIR", layer_path.as_os_str())]);
        }

        python.run_sync(&["-m", "pip", "install", "-r", REQUIREMENTS_TXT])?;

        LayerResultBuilder::new(GenericMetadata::default()).build()
    }
}
<commit_msg>fix(Python buildpack): Install a `.venv` if needed; add logs<commit_after>use std::{
    ffi::OsString,
    fs,
    path::{Path, PathBuf},
};

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

pub struct PythonBuildpack;

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

const INSTALLED: &str = "<installed>";
const POETRY_LOCK: &str = "poetry.lock";
const PYPROJECT_TOML: &str = "pyproject.toml";
const REQUIREMENTS_TXT: &str = "requirements.txt";
const RUNTIME_TXT: &str = "runtime.txt";
const TOOL_VERSIONS: &str = ".tool-versions";
const VENV: &str = ".venv";

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

struct PoetryLayer;

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

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

    fn create(
        &self,
        context: &BuildContext<Self::Buildpack>,
        layer_path: &Path,
    ) -> Result<LayerResult<Self::Metadata>, eyre::Report> {
        // Although the `POETRY_VIRTUALENVS_IN_PROJECT = true` setting below should make
        // Potry create a `.vevn` if one does not exist - it doesn't. So do it here.
        if !PathBuf::from(VENV).exists() {
            tracing::info!("Creating virtual environment '{}'", VENV);
            create_venv(layer_path)?;
        }

        tracing::info!("Installing packages using Poetry");

        let mut poetry = PoetryBinary {}.require_sync(Some(">=1,<2".to_string()), true)?;

        // Ensure that a `.venv` folder in the working directory is used (instead of a system level one)
        let mut envs: Vec<(OsString, OsString)> =
            vec![("POETRY_VIRTUALENVS_IN_PROJECT".into(), "true".into())];

        // If Stencila is not the platform make use the layer as the Poetry cache
        if !platform_is_stencila(&context.platform) {
            envs.push(("POETRY_CACHE_DIR".into(), layer_path.canonicalize()?.into()));
        }

        // Protip: use `println!("{}", poetry.run_sync(&["config", "--list"])?);` to check
        // config set by env vars
        poetry.envs(&envs);

        poetry.run_sync(&["install"])?;

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

struct PipLayer;

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

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

    fn create(
        &self,
        context: &BuildContext<Self::Buildpack>,
        layer_path: &Path,
    ) -> Result<LayerResult<Self::Metadata>, eyre::Report> {
        // Reuse or create a virtualenv.
        // The name `.venv` is most commonly used for this (e.g. Poetry uses that) but
        // we also look for `venv` and `env`.
        let mut virtualenv = PathBuf::from(VENV);
        if !virtualenv.exists() {
            let venv = PathBuf::from("venv");
            let env = PathBuf::from("env");
            if venv.join("bin").join("python").exists() {
                virtualenv = venv;
            } else if env.join("bin").join("python").exists() {
                virtualenv = env;
            } else {
                tracing::info!("Creating virtual environment '{}'", VENV);
                create_venv(layer_path)?;
            }
        }
        let virtualenv = virtualenv.canonicalize()?;

        tracing::info!("Installing packages using Pip");

        // Use the Python in the virtualenv, assume it is `python3` and has `pip`
        let mut python = BinaryInstallation {
            name: "python3".into(),
            path: virtualenv.join("bin").join("python3"),
            ..Default::default()
        };

        // If Stencila is not the platform use the layer as the Pip cache
        if !platform_is_stencila(&context.platform) {
            python.envs(&[("PIP_CACHE_DIR", layer_path.as_os_str())]);
        }

        python.run_sync(&["-m", "pip", "install", "-r", REQUIREMENTS_TXT])?;

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

/// Create a `.venv` virtual environment using the installed version of Python
fn create_venv(layer_path: &Path) -> Result<(), eyre::Report> {
    // Get the `python` installed by `PythonLayer`.
    // This is important because if affects the binary in the `venv`
    let python = BinaryInstallation {
        name: "python".into(),
        path: layer_path
            .canonicalize()?
            .parent()
            .expect("Should have parent")
            .join("python")
            .join("bin")
            .join("python"),
        ..Default::default()
    };

    python.run_sync(&["-m", "venv", "--clear", VENV])?;

    Ok(())
}
<|endoftext|>


<commit_before>use specs::{Entities, LazyUpdate, Read, ReadExpect, ReadStorage, System, WriteStorage};

use crate::animation::SpriteData;
use crate::combat::components::{Action, Draw, Position, State, UnitType, Velocity, Weapon};

pub struct StateUpdater;

impl<'a> System<'a> for StateUpdater {
    type SystemData = (
        WriteStorage<'a, State>,
        ReadStorage<'a, Position>,
        Entities<'a>,
        Read<'a, LazyUpdate>,
        ReadExpect<'a, SpriteData>,
    );

    fn run(&mut self, (mut state, position, entities, updater, sprite_data): Self::SystemData) {
        use specs::Join;
        let sprites = &sprite_data.sprites;

        for (state, entity) in (&mut state, &*entities).join() {
            // Handle dagger throwing.
            if state.action.is_throw_dagger() && state.ticks == 4 {
                let position: Option<&Position> = position.get(entity);
                if let Some(position) = position {
                    let dagger = entities.create();
                    updater.insert(
                        dagger,
                        Position {
                            x: position.x + 5 * state.direction as i32,
                            y: position.y,
                        },
                    );
                    updater.insert(
                        dagger,
                        Velocity {
                            x: 20 * state.direction as i32, //TODO: change hardcoded dagger vel.
                            y: 0,
                        },
                    );
                    let frame = sprites
                        .get("dagger")
                        .and_then(|s| s.animations.get("fly"))
                        .and_then(|a| a.frames.get(0))
                        .expect("dagger entity yaml not loaded");
                    updater.insert(
                        dagger,
                        UnitType {
                            name: "dagger".to_string(),
                        },
                    );
                    updater.insert(
                        dagger,
                        Draw {
                            frame: frame.clone(),
                            animation: "fly".to_string(),
                            direction: state.direction,
                        },
                    );
                    updater.insert(
                        dagger,
                        Weapon {
                            ..Default::default()
                        },
                    )
                }
            }

            match state.action {
                Action::Attack(..)
                | Action::Hit(..)
                | Action::AttackRecovery
                | Action::Entrance => {
                    state.ticks += 1;
                    if state.ticks == state.length && state.length != 0 {
                        // assuming: that all states that match this arm have a
                        // length > 1 means we don't need to update state.length
                        // in the ResolveCollisions with the animation length
                        // whenever that system changes state of an entity.
                        if state.ticks > 1 {
                            state.action = Action::Idle;
                        }
                    }
                }
                Action::Defend(..) => {
                    // defend actions are 1 frame long, so we switch to idle
                    // immediately
                    state.action = Action::Idle;
                }
                Action::Death(_) => {
                    state.ticks += 1;
                    if state.ticks == state.length && state.length != 0 && state.ticks > 1 {
                        state.action = Action::Dead;
                    }
                }
                _ => (),
            }
        }
    }
}
<commit_msg>Add RenderOrder when inserting a dagger.<commit_after>use specs::{Entities, LazyUpdate, Read, ReadExpect, ReadStorage, System, WriteStorage};

use crate::animation::SpriteData;
use crate::combat::components::{Action, Draw, Position, State, UnitType, Velocity, Weapon};
use crate::components::RenderOrder;

pub struct StateUpdater;

impl<'a> System<'a> for StateUpdater {
    type SystemData = (
        WriteStorage<'a, State>,
        ReadStorage<'a, Position>,
        Entities<'a>,
        Read<'a, LazyUpdate>,
        ReadExpect<'a, SpriteData>,
    );

    fn run(&mut self, (mut state, position, entities, updater, sprite_data): Self::SystemData) {
        use specs::Join;
        let sprites = &sprite_data.sprites;

        for (state, entity) in (&mut state, &*entities).join() {
            // Handle dagger throwing.
            if state.action.is_throw_dagger() && state.ticks == 4 {
                let position: Option<&Position> = position.get(entity);
                if let Some(position) = position {
                    let dagger = entities.create();
                    updater.insert(
                        dagger,
                        Position {
                            x: position.x + 5 * state.direction as i32,
                            y: position.y,
                        },
                    );
                    updater.insert(
                        dagger,
                        RenderOrder {
                            ..Default::default()
                        },
                    );
                    updater.insert(
                        dagger,
                        Velocity {
                            x: 20 * state.direction as i32, //TODO: change hardcoded dagger vel.
                            y: 0,
                        },
                    );
                    let frame = sprites
                        .get("dagger")
                        .and_then(|s| s.animations.get("fly"))
                        .and_then(|a| a.frames.get(0))
                        .expect("dagger entity yaml not loaded");
                    updater.insert(
                        dagger,
                        UnitType {
                            name: "dagger".to_string(),
                        },
                    );
                    updater.insert(
                        dagger,
                        Draw {
                            frame: frame.clone(),
                            animation: "fly".to_string(),
                            direction: state.direction,
                        },
                    );
                    updater.insert(
                        dagger,
                        Weapon {
                            ..Default::default()
                        },
                    )
                }
            }

            match state.action {
                Action::Attack(..)
                | Action::Hit(..)
                | Action::AttackRecovery
                | Action::Entrance => {
                    state.ticks += 1;
                    if state.ticks == state.length && state.length != 0 {
                        // assuming: that all states that match this arm have a
                        // length > 1 means we don't need to update state.length
                        // in the ResolveCollisions with the animation length
                        // whenever that system changes state of an entity.
                        if state.ticks > 1 {
                            state.action = Action::Idle;
                        }
                    }
                }
                Action::Defend(..) => {
                    // defend actions are 1 frame long, so we switch to idle
                    // immediately
                    state.action = Action::Idle;
                }
                Action::Death(_) => {
                    state.ticks += 1;
                    if state.ticks == state.length && state.length != 0 && state.ticks > 1 {
                        state.action = Action::Dead;
                    }
                }
                _ => (),
            }
        }
    }
}
<|endoftext|>


<commit_before>#![deny(bare_trait_objects)]

mod bindings;
pub mod c_api;
pub mod wasi;

pub use bindings::bindings;
pub use wasi::{export_wasi_funcs, WasiCtx, WasiCtxBuilder};

// Exporting this type alias as a stop-gap: wasi-common should really provide a pub Rust enum type
// for the return values from _start, but it does not yet. We're going to pursue that path via witx
// and friends rather than export the type definition and constants from wasi-common right now.
//
// In the meantime, we're providing this alias because it is more descriptive to use (compared to
// `u32` in our code that uses the exitcode.
#[allow(non_camel_case_types)]
pub type __wasi_exitcode_t = u32;
<commit_msg>lucet-wasi: re-export wasi-common's error<commit_after>#![deny(bare_trait_objects)]

mod bindings;
pub mod c_api;
pub mod wasi;

pub use bindings::bindings;
pub use wasi::{export_wasi_funcs, WasiCtx, WasiCtxBuilder};

// Exporting this type alias as a stop-gap: wasi-common should really provide a pub Rust enum type
// for the return values from _start, but it does not yet. We're going to pursue that path via witx
// and friends rather than export the type definition and constants from wasi-common right now.
//
// In the meantime, we're providing this alias because it is more descriptive to use (compared to
// `u32` in our code that uses the exitcode.
#[allow(non_camel_case_types)]
pub type __wasi_exitcode_t = u32;

// Re-export wasi-common's error type
pub use wasi_common::Error;
<|endoftext|>


<commit_before>/// A TOML [1] configuration file parser
///
/// [1]: https://github.com/mojombo/toml

use std::io::mem::MemReader;
use std::io::File;
use std::hashmap::HashMap;

fn read_char(rd: &mut MemReader) -> char {
  match rd.read_byte() {
    Some(b) => b as char,
    None => fail!()
  }
}

fn read_char_opt(rd: &mut MemReader) -> Option<char> {
  match rd.read_byte() {
    Some(b) => Some(b as char),
    None => None
  }
}

#[deriving(ToStr)]
enum Value {
    True,
    False,
    Unsigned(u64),
    Integer(i64),
    Float(f64),
    String(~str),
    Array(~[Value]),
    Datetime, // XXX
    Map(HashMap<~str, Value>) // XXX: This is no value
}

// parse values recursivly
fn parse_value(rd: &mut MemReader) -> Value {
    enum State {
        st_value,
        st_number,
        st_got_number
    }

    let mut state: State = st_value;
    let mut val: ~str = ~"";

    loop {
        match state {
            st_value => {
                if rd.eof() { fail!() }
                let ch = read_char(rd);
                match ch {
                    '\r' | '\n' | ' ' | '\t' => { }
                    '0' .. '9' => {
                        val.push_char(ch);
                        state = st_number;
                    }
                    't' => {
                        assert!(read_char(rd) == 'r');
                        assert!(read_char(rd) == 'u');
                        assert!(read_char(rd) == 'e');
                        return True
                    }
                    'f' => {
                        assert!(read_char(rd) == 'a');
                        assert!(read_char(rd) == 'l');
                        assert!(read_char(rd) == 's');
                        assert!(read_char(rd) == 'e');
                        return False
                    }
                    '"' => {
                        loop {
                            let ch = read_char(rd);
                            if ch == '"' {
                                break;
                            }
                            val.push_char(ch);
                        }
                        return String(val)
                    }
                    _ => { fail!() }
                }
            }
            st_got_number => {
                return Unsigned(from_str(val).unwrap())
            }
            st_number => {
                if rd.eof() {
                    state = st_got_number;
                } else {
                    let ch = read_char(rd);
                    match ch {
                        '\r' | '\n' | ' ' | '\t' => { state = st_got_number }
                        '#' => { skip_comment(rd); state = st_got_number }
                        '0' .. '9' => {
                            val.push_char(ch);
                        }
                        _ => { fail!() }
                    }
                }
            }
        }
    }
}

// We must be already within the '#"
fn skip_comment(rd: &mut MemReader) {
    loop {
        if rd.eof() { return }
        match read_char(rd) {
            '\n' => { return }
            _ => { }
        }
    }
}

trait Visitor {
    fn section(&mut self, name: &str, is_array: bool) -> bool;
    fn pair(&mut self, key: &str, val: Value) -> bool;
}

struct TOMLVisitor {
    root: HashMap<~str, Value>,
    current_path: ~str,
    is_array: bool
}

impl TOMLVisitor {
    fn new() -> TOMLVisitor {
        TOMLVisitor { root: HashMap::new(), current_path: ~"", is_array: false }
    }
    fn get_root<'a>(&'a self) -> &'a HashMap<~str, Value> {
        return &self.root;
    }
}

impl Visitor for TOMLVisitor {
    fn section(&mut self, name: &str, is_array: bool) -> bool {
        debug!("Section: {} (is_array={})", name, is_array);
        self.is_array = is_array;
        self.current_path = name.to_owned();
        return true
    }
    fn pair(&mut self, key: &str, val: Value) -> bool {
        debug!("Pair: {} {:s}", key, val.to_str());
        let mut m = self.root.find_or_insert(self.current_path.clone(), Map(HashMap::new())); // XXX: remove clone
        match *m {
            Map(ref mut map) => {
                let ok = map.insert(key.to_owned(), val);
                return ok
            }
            _ => { return false }
        }
    }
}

fn parse<V: Visitor>(rd: &mut MemReader, visitor: &mut V) -> bool {
    enum State {
        st_toplevel,
        st_section,
        st_section_or_double_section,
        st_double_section,
        st_ident,
        st_assign_wanted,
        st_value_wanted
    }

    let mut state = st_toplevel;
    let mut buf: ~str = ~"";

    let mut current_char: Option<char> = read_char_opt(rd);

    loop {
        match state {
            st_toplevel => {
                if current_char.is_none() { return true }
                let ch = current_char.unwrap();
                match ch {
                    // ignore whitespace
                    '\r' | '\n' | ' ' | '\t' => { }

                    // comment
                    '#' => {
                        // skip to end of line
                        loop {
                            current_char = read_char_opt(rd);
                            match current_char {
                                Some('\n') => { break }
                                None => { return true }
                                _ => { /* skip */ }
                            }
                        }
                    }

                    // section
                    '[' => { state = st_section_or_double_section }

                    // identifier
                    'a' .. 'z' | 'A' .. 'Z' | '_' => {
                        state = st_ident;
                        buf.push_char(ch)
                    }

                    _ => { return false }
                }
                current_char = read_char_opt(rd); // advance
            }
            st_section_or_double_section => {
                if current_char.is_none() { return false }
                let ch = current_char.unwrap();
                match ch {
                    'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '.' | '_'=> {
                        buf.push_char(ch);
                        state = st_section;
                    }
                    '[' => {
                        state = st_double_section;
                    }
                    ']' => {
                        // empty section
                        assert!(buf.len() == 0);
                        visitor.section(buf, false);
                        buf.truncate(0);
                        state = st_toplevel;
                    }
                    _ => { return false }
                }
                current_char = read_char_opt(rd); // advance
            }
            st_double_section => {
                if current_char.is_none() { return false }
                let ch = current_char.unwrap();
                match ch {
                    'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '.' | '_'=> {
                        buf.push_char(ch);
                    }
                    ']' => {
                        current_char = read_char_opt(rd); // advance
                        match current_char {
                            Some(']') => {}
                            _         => {return false} // error
                        }
                        visitor.section(buf, true);
                        buf.truncate(0);
                        state = st_toplevel;
                    }
                    _ => { return false }
                }
                current_char = read_char_opt(rd); // advance
            }
            st_section => {
                if current_char.is_none() { return false }
                let ch = current_char.unwrap();
                match ch {
                    'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '.' | '_'=> {
                        buf.push_char(ch);
                    }
                    ']' => {
                        visitor.section(buf, false);
                        buf.truncate(0);
                        state = st_toplevel;
                    }
                    _ => { return false }
                }
                current_char = read_char_opt(rd); // advance
            }
            st_ident => {
                if current_char.is_none() { return false }
                let ch = current_char.unwrap();
                match ch {
                    'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '_'=> {
                        buf.push_char(ch);
                    }

                    '\r' | '\n' | ' ' | '\t' => { state = st_assign_wanted }

                    '=' => { state = st_value_wanted }

                    _ => { return false }
                }
                current_char = read_char_opt(rd); // advance
            }
            st_assign_wanted => {
                if current_char.is_none() { return false }
                match current_char.unwrap() {
                    '\r' | '\n' | ' ' | '\t' => { }
                    '=' => { state = st_value_wanted }
                    _ => { return false }
                }
                current_char = read_char_opt(rd); // advance
            }
            st_value_wanted => {
                visitor.pair(buf, parse_value(rd));
                buf.truncate(0);
                state = st_toplevel;
                current_char = read_char_opt(rd); // advance
            }
        }
    }

    assert!(false);
}

fn main() {
  let contents = File::open(&Path::new(std::os::args()[1])).read_to_end();
  let mut rd = MemReader::new(contents);
  let mut visitor = TOMLVisitor::new();
  parse(&mut rd, &mut visitor);
  println!("{:s}", visitor.get_root().to_str());
}
<commit_msg>Greatly simplify state machine by using peeking<commit_after>/// A TOML [1] configuration file parser
///
/// [1]: https://github.com/mojombo/toml

use std::io::mem::MemReader;
use std::io::File;
use std::hashmap::HashMap;

fn read_char(rd: &mut MemReader) -> char {
  match rd.read_byte() {
    Some(b) => b as char,
    None => fail!()
  }
}

fn read_char_opt(rd: &mut MemReader) -> Option<char> {
  match rd.read_byte() {
    Some(b) => Some(b as char),
    None => None
  }
}

#[deriving(ToStr)]
enum Value {
    True,
    False,
    Unsigned(u64),
    Integer(i64),
    Float(f64),
    String(~str),
    Array(~[Value]),
    Datetime, // XXX
    Map(HashMap<~str, Value>) // XXX: This is no value
}

// parse values recursivly
fn parse_value(rd: &mut MemReader) -> Value {
    enum State {
        st_value,
        st_number,
        st_got_number
    }

    let mut state: State = st_value;
    let mut val: ~str = ~"";

    loop {
        match state {
            st_value => {
                if rd.eof() { fail!() }
                let ch = read_char(rd);
                match ch {
                    '\r' | '\n' | ' ' | '\t' => { }
                    '0' .. '9' => {
                        val.push_char(ch);
                        state = st_number;
                    }
                    't' => {
                        assert!(read_char(rd) == 'r');
                        assert!(read_char(rd) == 'u');
                        assert!(read_char(rd) == 'e');
                        return True
                    }
                    'f' => {
                        assert!(read_char(rd) == 'a');
                        assert!(read_char(rd) == 'l');
                        assert!(read_char(rd) == 's');
                        assert!(read_char(rd) == 'e');
                        return False
                    }
                    '"' => {
                        loop {
                            let ch = read_char(rd);
                            if ch == '"' {
                                break;
                            }
                            val.push_char(ch);
                        }
                        return String(val)
                    }
                    _ => { fail!() }
                }
            }
            st_got_number => {
                return Unsigned(from_str(val).unwrap())
            }
            st_number => {
                if rd.eof() {
                    state = st_got_number;
                } else {
                    let ch = read_char(rd);
                    match ch {
                        '\r' | '\n' | ' ' | '\t' => { state = st_got_number }
                        '#' => { skip_comment(rd); state = st_got_number }
                        '0' .. '9' => {
                            val.push_char(ch);
                        }
                        _ => { fail!() }
                    }
                }
            }
        }
    }
}

// We must be already within the '#"
fn skip_comment(rd: &mut MemReader) {
    loop {
        if rd.eof() { return }
        match read_char(rd) {
            '\n' => { return }
            _ => { }
        }
    }
}

trait Visitor {
    fn section(&mut self, name: ~str, is_array: bool) -> bool;
    fn pair(&mut self, key: &str, val: Value) -> bool;
}

struct TOMLVisitor {
    root: HashMap<~str, Value>,
    current_path: ~str,
    is_array: bool
}

impl TOMLVisitor {
    fn new() -> TOMLVisitor {
        TOMLVisitor { root: HashMap::new(), current_path: ~"", is_array: false }
    }
    fn get_root<'a>(&'a self) -> &'a HashMap<~str, Value> {
        return &self.root;
    }
}

impl Visitor for TOMLVisitor {
    fn section(&mut self, name: ~str, is_array: bool) -> bool {
        debug!("Section: {} (is_array={})", name, is_array);
        self.is_array = is_array;
        self.current_path = name;
        return true
    }
    fn pair(&mut self, key: &str, val: Value) -> bool {
        debug!("Pair: {} {:s}", key, val.to_str());
        let mut m = self.root.find_or_insert(self.current_path.clone(), Map(HashMap::new())); // XXX: remove clone
        match *m {
            Map(ref mut map) => {
                let ok = map.insert(key.to_owned(), val);
                return ok
            }
            _ => { return false }
        }
    }
}

fn parse<V: Visitor>(rd: &mut MemReader, visitor: &mut V) -> bool {
    enum State {
        st_toplevel,
        st_ident,
        st_assign_wanted,
        st_value_wanted
    }

    let mut state = st_toplevel;
    let mut buf: ~str = ~"";

    let mut current_char: Option<char> = read_char_opt(rd);

    loop {
        match state {
            st_toplevel => {
                if current_char.is_none() { return true }
                let ch = current_char.unwrap();
                match ch {
                    // ignore whitespace
                    '\r' | '\n' | ' ' | '\t' => { }

                    // comment
                    '#' => {
                        // skip to end of line
                        loop {
                            current_char = read_char_opt(rd);
                            match current_char {
                                Some('\n') => { break }
                                None => { return true }
                                _ => { /* skip */ }
                            }
                        }
                    }

                    // section
                    '[' => {
                        current_char = read_char_opt(rd); // advance
                        let mut double_section = false;
                        match current_char {
                            Some('[') => {
                                double_section = true;
                                current_char = read_char_opt(rd); // advance
                            }
                            _ => {}
                        }
                        let mut section = ~"";
                        loop {
                            if current_char.is_none() { return false }
                            let ch = current_char.unwrap();
                            match ch {
                                'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '.' | '_'=> {
                                    section.push_char(ch);
                                }
                                ']' => {
                                    break
                                }
                                _ => { return false }
                            }
                            current_char = read_char_opt(rd); // advance
                        }

                        assert!(current_char == Some(']'));

                        if double_section {
                            current_char = read_char_opt(rd); // advance
                            if current_char != Some(']') { return false }
                        }

                        visitor.section(section, double_section);
                    }

                    // identifier
                    'a' .. 'z' | 'A' .. 'Z' | '_' => {
                        state = st_ident;
                        buf.push_char(ch)
                    }

                    _ => { return false }
                }
                current_char = read_char_opt(rd); // advance
            }
            st_ident => {
                if current_char.is_none() { return false }
                let ch = current_char.unwrap();
                match ch {
                    'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '_'=> {
                        buf.push_char(ch);
                    }

                    '\r' | '\n' | ' ' | '\t' => { state = st_assign_wanted }

                    '=' => { state = st_value_wanted }

                    _ => { return false }
                }
                current_char = read_char_opt(rd); // advance
            }
            st_assign_wanted => {
                if current_char.is_none() { return false }
                match current_char.unwrap() {
                    '\r' | '\n' | ' ' | '\t' => { }
                    '=' => { state = st_value_wanted }
                    _ => { return false }
                }
                current_char = read_char_opt(rd); // advance
            }
            st_value_wanted => {
                visitor.pair(buf, parse_value(rd));
                buf.truncate(0);
                state = st_toplevel;
                current_char = read_char_opt(rd); // advance
            }
        }
    }

    assert!(false);
}

fn main() {
  let contents = File::open(&Path::new(std::os::args()[1])).read_to_end();
  let mut rd = MemReader::new(contents);
  let mut visitor = TOMLVisitor::new();
  parse(&mut rd, &mut visitor);
  println!("{:s}", visitor.get_root().to_str());
}
<|endoftext|>


<commit_before>use std::mem;

#[derive(PartialEq, Debug)]
pub enum MqttType {
    Reserved = 0,
    Connect = 1,
    // ConnAck = 2,
    // Publish = 3,
    // PubAck = 4,
    // PubRec = 5,
    // PubRel = 6,
    // PubComp = 7,
    // Subscribe = 8,
    // SubAck = 9,
    // Unsubscribe = 0xa,
    // UnsubAck = 0xb,
    PingReq = 0xc,
    // PingResp = 0xd,
    // Disconnect = 0xe,
}

pub fn message_type(bytes: &[u8]) -> MqttType {
    unsafe { mem::transmute((bytes[0] & 0xf0) >> 4) }
}

fn connect_bytes() -> Vec<u8> {
    vec!(
        0x10u8, 0x2a, // fixed header
        0x00, 0x06, 'M' as u8, 'Q' as u8, 'I' as u8, 's' as u8, 'd' as u8, 'p' as u8,
        0x03, // protocol version
        0xcc, // connection flags 1100111x user, pw, !wr, w(01), w, !c, x
        0x00, 0x0a, // keepalive of 100
        0x00, 0x03, 'c' as u8, 'i' as u8, 'd' as u8, // client ID
        0x00, 0x04, 'w' as u8, 'i' as u8, 'l' as u8, 'l' as u8, // will topic
        0x00, 0x04, 'w' as u8, 'm' as u8, 's' as u8, 'g' as u8, // will msg
        0x00, 0x07, 'g' as u8, 'l' as u8, 'i' as u8, 'f' as u8, 't' as u8, 'e' as u8, 'l' as u8, // username
        0x00, 0x02, 'p' as u8, 'w' as u8, // password
        )
}

#[test]
fn connect_type() {
    let connect_bytes = connect_bytes();
    assert_eq!(message_type(&connect_bytes), MqttType::Connect);
}

#[test]
fn ping_type() {
    let ping_bytes = &[0xc0u8, 0][0..];
    assert_eq!(message_type(ping_bytes), MqttType::PingReq);
}

pub fn remaining_length(bytes: &[u8]) -> i32 {
    42
}

#[test]
fn connect_len() {
    assert_eq!(remaining_length(&connect_bytes()), 42);
}

#[test]
fn ping_len() {
    let ping_bytes = &[0xc0u8, 0][0..];
    assert_eq!(remaining_length(&ping_bytes), 0);
}
<commit_msg>Passing UTs for get remaining length<commit_after>use std::mem;

#[derive(PartialEq, Debug)]
pub enum MqttType {
    Reserved = 0,
    Connect = 1,
    // ConnAck = 2,
    // Publish = 3,
    // PubAck = 4,
    // PubRec = 5,
    // PubRel = 6,
    // PubComp = 7,
    // Subscribe = 8,
    // SubAck = 9,
    // Unsubscribe = 0xa,
    // UnsubAck = 0xb,
    PingReq = 0xc,
    // PingResp = 0xd,
    // Disconnect = 0xe,
}

pub fn message_type(bytes: &[u8]) -> MqttType {
    unsafe { mem::transmute((bytes[0] & 0xf0) >> 4) }
}

fn connect_bytes() -> Vec<u8> {
    vec!(
        0x10u8, 0x2a, // fixed header
        0x00, 0x06, 'M' as u8, 'Q' as u8, 'I' as u8, 's' as u8, 'd' as u8, 'p' as u8,
        0x03, // protocol version
        0xcc, // connection flags 1100111x user, pw, !wr, w(01), w, !c, x
        0x00, 0x0a, // keepalive of 100
        0x00, 0x03, 'c' as u8, 'i' as u8, 'd' as u8, // client ID
        0x00, 0x04, 'w' as u8, 'i' as u8, 'l' as u8, 'l' as u8, // will topic
        0x00, 0x04, 'w' as u8, 'm' as u8, 's' as u8, 'g' as u8, // will msg
        0x00, 0x07, 'g' as u8, 'l' as u8, 'i' as u8, 'f' as u8, 't' as u8, 'e' as u8, 'l' as u8, // username
        0x00, 0x02, 'p' as u8, 'w' as u8, // password
        )
}

#[test]
fn connect_type() {
    let connect_bytes = connect_bytes();
    assert_eq!(message_type(&connect_bytes), MqttType::Connect);
}

#[test]
fn ping_type() {
    let ping_bytes = &[0xc0u8, 0][0..];
    assert_eq!(message_type(ping_bytes), MqttType::PingReq);
}

pub fn remaining_length(bytes: &[u8]) -> i32 {
    if bytes.len() < 2 {
        return 0;
    }

    let bytes = &bytes[1..]; //skip half of the fixed header

    //algorithm straight from the MQTT spec
    let mut multiplier: i32 = 1;
    let mut value: i32 = 0;
    let mut digit: i32 = bytes[0] as i32;

    loop { // do-while would be cleaner...
        value += (digit & 127) * multiplier;
        multiplier *= 128;

        if (digit & 128) == 0 {
            break;
        }

        if bytes.len() > 1 {
            digit = bytes[1..][0] as i32;
        }
    }

    value
}

#[test]
fn connect_len() {
    assert_eq!(remaining_length(&connect_bytes()), 42);
}

#[test]
fn ping_len() {
    let ping_bytes = &[0xc0u8, 0][0..];
    assert_eq!(remaining_length(&ping_bytes), 0);
}

#[test]
fn msg_lens() {
    assert_eq!(remaining_length(&[]), 0);
    assert_eq!(remaining_length(&[0x15, 5]), 5);
    assert_eq!(remaining_length(&[0x27, 7]), 7);
    assert_eq!(remaining_length(&[0x12, 0xc1, 0x02]), 321);
    assert_eq!(remaining_length(&[0x12, 0x83, 0x02]), 259);
    //assert_eq!(remaining_length(&[0x12, 0x85, 0x80, 0x01]), 2097157);
}
<|endoftext|>


<commit_before>//! The main event loop which performs I/O on the pseudoterminal
use std::borrow::Cow;
use std::collections::VecDeque;
use std::io::{self, ErrorKind, Write};
use std::fs::File;
use std::os::unix::io::AsRawFd;
use std::sync::Arc;

use mio::{self, Events, PollOpt, Ready};
use mio::unix::EventedFd;

use ansi;
use display;
use event;
use term::Term;
use util::thread;
use sync::FairMutex;

/// Messages that may be sent to the `EventLoop`
#[derive(Debug)]
pub enum Msg {
    /// Data that should be written to the pty
    Input(Cow<'static, [u8]>),

    /// Indicates that the `EventLoop` should shut down, as Alacritty is shutting down
    Shutdown
}

/// The main event!.. loop.
///
/// Handles all the pty I/O and runs the pty parser which updates terminal
/// state.
pub struct EventLoop<Io> {
    poll: mio::Poll,
    pty: Io,
    rx: mio::channel::Receiver<Msg>,
    tx: mio::channel::Sender<Msg>,
    terminal: Arc<FairMutex<Term>>,
    display: display::Notifier,
    ref_test: bool,
}

/// Helper type which tracks how much of a buffer has been written.
struct Writing {
    source: Cow<'static, [u8]>,
    written: usize,
}

/// Indicates the result of draining the mio channel
#[derive(Debug)]
enum DrainResult {
    /// At least one new item was received
    ReceivedItem,
    /// Nothing was available to receive
    Empty,
    /// A shutdown message was received
    Shutdown
}

impl DrainResult {

    pub fn is_shutdown(&self) -> bool {
        match *self {
            DrainResult::Shutdown => true,
            _ => false
        }
    }

    #[allow(dead_code)]
    pub fn is_empty(&self) -> bool {
        match *self {
            DrainResult::Empty => true,
            _ => false
        }
    }

    #[allow(dead_code)]
    pub fn is_item(&self) -> bool {
        match *self {
            DrainResult::ReceivedItem => true,
            _ => false
        }
    }

}

/// All of the mutable state needed to run the event loop
///
/// Contains list of items to write, current write state, etc. Anything that
/// would otherwise be mutated on the `EventLoop` goes here.
pub struct State {
    write_list: VecDeque<Cow<'static, [u8]>>,
    writing: Option<Writing>,
    parser: ansi::Processor,
}

pub struct Notifier(pub ::mio::channel::Sender<Msg>);

impl event::Notify for Notifier {
    fn notify<B>(&mut self, bytes: B)
        where B: Into<Cow<'static, [u8]>>
    {
        let bytes = bytes.into();
        // terminal hangs if we send 0 bytes through.
        if bytes.len() == 0 {
            return
        }
        match self.0.send(Msg::Input(bytes)) {
            Ok(_) => (),
            Err(_) => panic!("expected send event loop msg"),
        }
    }
}

impl Default for State {
    fn default() -> State {
        State {
            write_list: VecDeque::new(),
            parser: ansi::Processor::new(),
            writing: None,
        }
    }
}

impl State {
    #[inline]
    fn ensure_next(&mut self) {
        if self.writing.is_none() {
            self.goto_next();
        }
    }

    #[inline]
    fn goto_next(&mut self) {
        self.writing = self.write_list
            .pop_front()
            .map(Writing::new);
    }

    #[inline]
    fn take_current(&mut self) -> Option<Writing> {
        self.writing.take()
    }

    #[inline]
    fn needs_write(&self) -> bool {
        self.writing.is_some() || !self.write_list.is_empty()
    }

    #[inline]
    fn set_current(&mut self, new: Option<Writing>) {
        self.writing = new;
    }
}

impl Writing {
    #[inline]
    fn new(c: Cow<'static, [u8]>) -> Writing {
        Writing { source: c, written: 0 }
    }

    #[inline]
    fn advance(&mut self, n: usize) {
        self.written += n;
    }

    #[inline]
    fn remaining_bytes(&self) -> &[u8] {
        &self.source[self.written..]
    }

    #[inline]
    fn finished(&self) -> bool {
        self.written >= self.source.len()
    }
}

/// `mio::Token` for the event loop channel
const CHANNEL: mio::Token = mio::Token(0);

/// `mio::Token` for the pty file descriptor
const PTY: mio::Token = mio::Token(1);

impl<Io> EventLoop<Io>
    where Io: io::Read + io::Write + Send + AsRawFd + 'static
{
    /// Create a new event loop
    pub fn new(
        terminal: Arc<FairMutex<Term>>,
        display: display::Notifier,
        pty: Io,
        ref_test: bool,
    ) -> EventLoop<Io> {
        let (tx, rx) = ::mio::channel::channel();
        EventLoop {
            poll: mio::Poll::new().expect("create mio Poll"),
            pty: pty,
            tx: tx,
            rx: rx,
            terminal: terminal,
            display: display,
            ref_test: ref_test,
        }
    }

    pub fn channel(&self) -> mio::channel::Sender<Msg> {
        self.tx.clone()
    }

    // Drain the channel
    //
    // Returns a `DrainResult` indicating the result of receiving from the channe;
    //
    fn drain_recv_channel(&self, state: &mut State) -> DrainResult {
        let mut received_item = false;
        while let Ok(msg) = self.rx.try_recv() {
            received_item = true;
            match msg {
                Msg::Input(input) => {
                    state.write_list.push_back(input);
                },
                Msg::Shutdown => {
                    return DrainResult::Shutdown;
                }
            }
        }

        return if received_item { DrainResult::ReceivedItem } else { DrainResult::Empty };
    }

    // Returns a `bool` indicating whether or not the event loop should continue running
    #[inline]
    fn channel_event(&mut self, state: &mut State) -> bool {
        if self.drain_recv_channel(state).is_shutdown() {
            return false;
        }

        self.poll.reregister(
            &self.rx, CHANNEL,
            Ready::readable(),
            PollOpt::edge() | PollOpt::oneshot()
        ).expect("reregister channel");

        if state.needs_write() {
            self.poll.reregister(
                &EventedFd(&self.pty.as_raw_fd()),
                PTY,
                Ready::readable() | Ready::writable(),
                PollOpt::edge() | PollOpt::oneshot()
            ).expect("reregister fd after channel recv");
        }
        true
    }

    #[inline]
    fn pty_read<W>(
        &mut self,
        state: &mut State,
        buf: &mut [u8],
        mut writer: Option<&mut W>
    ) -> io::Result<()>
        where W: Write
    {
        loop {
            match self.pty.read(&mut buf[..]) {
                Ok(0) => break,
                Ok(got) => {
                    writer = writer.map(|w| {
                        w.write_all(&buf[..got]).unwrap(); w
                    });

                    let mut terminal = self.terminal.lock();
                    for byte in &buf[..got] {
                        state.parser.advance(&mut *terminal, *byte, &mut self.pty);
                    }

                    // Only request a draw if one hasn't already been requested.
                    //
                    // This is a performance optimization even if only for X11
                    // which is very expensive to hammer on the even loop wakeup
                    if !terminal.dirty {
                        self.display.notify();
                        terminal.dirty = true;

                        // Break for writing
                        //
                        // Want to prevent case where reading always returns
                        // data and sequences like `C-c` cannot be sent.
                        //
                        // Doing this check in !terminal.dirty will prevent the
                        // condition from being checked overzealously.
                        //
                        // We want to break if `drain_recv_channel` returns either `Ok(true)`
                        // (new items came in for writing) or `Err` (we need to shut down)
                        if state.writing.is_some()
                            || !state.write_list.is_empty()
                            || match self.drain_recv_channel(state) {
                                DrainResult::Shutdown | DrainResult::ReceivedItem => true,
                                _ => false
                            }
                        {
                            break;
                        }
                    }
                },
                Err(err) => {
                    match err.kind() {
                        ErrorKind::Interrupted |
                        ErrorKind::WouldBlock => break,
                        _ => return Err(err),
                    }
                }
            }
        }

        Ok(())
    }

    #[inline]
    fn pty_write(&mut self, state: &mut State) -> io::Result<()> {
        state.ensure_next();

        'write_many: while let Some(mut current) = state.take_current() {
            'write_one: loop {
                match self.pty.write(current.remaining_bytes()) {
                    Ok(0) => {
                        state.set_current(Some(current));
                        break 'write_many;
                    },
                    Ok(n) => {
                        current.advance(n);
                        if current.finished() {
                            state.goto_next();
                            break 'write_one;
                        }
                    },
                    Err(err) => {
                        state.set_current(Some(current));
                        match err.kind() {
                            ErrorKind::Interrupted |
                            ErrorKind::WouldBlock => break 'write_many,
                            _ => return Err(err),
                        }
                    }
                }

            }
        }

        Ok(())
    }

    pub fn spawn(
        mut self,
        state: Option<State>
    ) -> thread::JoinHandle<(EventLoop<Io>, State)> {
        thread::spawn_named("pty reader", move || {
            let mut state = state.unwrap_or_else(Default::default);
            let mut buf = [0u8; 4096];

            let fd = self.pty.as_raw_fd();
            let fd = EventedFd(&fd);

            let poll_opts = PollOpt::edge() | PollOpt::oneshot();

            self.poll.register(&self.rx, CHANNEL, Ready::readable(), poll_opts).unwrap();
            self.poll.register(&fd, PTY, Ready::readable(), poll_opts).unwrap();

            let mut events = Events::with_capacity(1024);

            let mut pipe = if self.ref_test {
                let file = File::create("./alacritty.recording")
                    .expect("create alacritty recording");
                Some(file)
            } else {
                None
            };

            'event_loop: loop {
                if let Err(err) = self.poll.poll(&mut events, None) {
                    match err.kind() {
                        ErrorKind::Interrupted => continue,
                        _ => panic!("EventLoop polling error: {:?}", err)
                    }
                }

                for event in events.iter() {
                    match event.token() {
                        CHANNEL =>  {
                            if !self.channel_event(&mut state) {
                                break 'event_loop;
                            }
                        },
                        PTY => {
                            let kind = event.kind();

                            if kind.is_hup() {
                                break 'event_loop;
                            }

                            if kind.is_readable() {
                                if let Err(err) = self.pty_read(&mut state, &mut buf, pipe.as_mut()) {
                                    error!("Event loop exitting due to error: {} [{}:{}]",
                                           err, file!(), line!());
                                    break 'event_loop;
                                }

                                if ::tty::process_should_exit() {
                                    break 'event_loop;
                                }
                            }

                            if kind.is_writable() {
                                if let Err(err) = self.pty_write(&mut state) {
                                    error!("Event loop exitting due to error: {} [{}:{}]",
                                           err, file!(), line!());
                                    break 'event_loop;
                                }
                            }

                            // Figure out pty interest
                            let mut interest = Ready::readable();
                            if state.needs_write() {
                                interest.insert(Ready::writable());
                            }

                            // Reregister pty
                            self.poll
                                .reregister(&fd, PTY, interest, poll_opts)
                                .expect("register fd after read/write");
                        },
                        _ => (),
                    }
                }
            }

            let _ = self.poll.deregister(&self.rx);
            let _ = self.poll.deregister(&fd);

            (self, state)
        })
    }
}
<commit_msg>Minor cleanup, style fix, dead code removal<commit_after>//! The main event loop which performs I/O on the pseudoterminal
use std::borrow::Cow;
use std::collections::VecDeque;
use std::io::{self, ErrorKind, Write};
use std::fs::File;
use std::os::unix::io::AsRawFd;
use std::sync::Arc;

use mio::{self, Events, PollOpt, Ready};
use mio::unix::EventedFd;

use ansi;
use display;
use event;
use term::Term;
use util::thread;
use sync::FairMutex;

/// Messages that may be sent to the `EventLoop`
#[derive(Debug)]
pub enum Msg {
    /// Data that should be written to the pty
    Input(Cow<'static, [u8]>),

    /// Indicates that the `EventLoop` should shut down, as Alacritty is shutting down
    Shutdown
}

/// The main event!.. loop.
///
/// Handles all the pty I/O and runs the pty parser which updates terminal
/// state.
pub struct EventLoop<Io> {
    poll: mio::Poll,
    pty: Io,
    rx: mio::channel::Receiver<Msg>,
    tx: mio::channel::Sender<Msg>,
    terminal: Arc<FairMutex<Term>>,
    display: display::Notifier,
    ref_test: bool,
}

/// Helper type which tracks how much of a buffer has been written.
struct Writing {
    source: Cow<'static, [u8]>,
    written: usize,
}

/// Indicates the result of draining the mio channel
#[derive(Debug)]
enum DrainResult {
    /// At least one new item was received
    ReceivedItem,
    /// Nothing was available to receive
    Empty,
    /// A shutdown message was received
    Shutdown
}

impl DrainResult {
    pub fn is_shutdown(&self) -> bool {
        match *self {
            DrainResult::Shutdown => true,
            _ => false
        }
    }

    pub fn is_empty(&self) -> bool {
        match *self {
            DrainResult::Empty => true,
            _ => false
        }
    }
}

/// All of the mutable state needed to run the event loop
///
/// Contains list of items to write, current write state, etc. Anything that
/// would otherwise be mutated on the `EventLoop` goes here.
pub struct State {
    write_list: VecDeque<Cow<'static, [u8]>>,
    writing: Option<Writing>,
    parser: ansi::Processor,
}

pub struct Notifier(pub ::mio::channel::Sender<Msg>);

impl event::Notify for Notifier {
    fn notify<B>(&mut self, bytes: B)
        where B: Into<Cow<'static, [u8]>>
    {
        let bytes = bytes.into();
        // terminal hangs if we send 0 bytes through.
        if bytes.len() == 0 {
            return
        }
        match self.0.send(Msg::Input(bytes)) {
            Ok(_) => (),
            Err(_) => panic!("expected send event loop msg"),
        }
    }
}

impl Default for State {
    fn default() -> State {
        State {
            write_list: VecDeque::new(),
            parser: ansi::Processor::new(),
            writing: None,
        }
    }
}

impl State {
    #[inline]
    fn ensure_next(&mut self) {
        if self.writing.is_none() {
            self.goto_next();
        }
    }

    #[inline]
    fn goto_next(&mut self) {
        self.writing = self.write_list
            .pop_front()
            .map(Writing::new);
    }

    #[inline]
    fn take_current(&mut self) -> Option<Writing> {
        self.writing.take()
    }

    #[inline]
    fn needs_write(&self) -> bool {
        self.writing.is_some() || !self.write_list.is_empty()
    }

    #[inline]
    fn set_current(&mut self, new: Option<Writing>) {
        self.writing = new;
    }
}

impl Writing {
    #[inline]
    fn new(c: Cow<'static, [u8]>) -> Writing {
        Writing { source: c, written: 0 }
    }

    #[inline]
    fn advance(&mut self, n: usize) {
        self.written += n;
    }

    #[inline]
    fn remaining_bytes(&self) -> &[u8] {
        &self.source[self.written..]
    }

    #[inline]
    fn finished(&self) -> bool {
        self.written >= self.source.len()
    }
}

/// `mio::Token` for the event loop channel
const CHANNEL: mio::Token = mio::Token(0);

/// `mio::Token` for the pty file descriptor
const PTY: mio::Token = mio::Token(1);

impl<Io> EventLoop<Io>
    where Io: io::Read + io::Write + Send + AsRawFd + 'static
{
    /// Create a new event loop
    pub fn new(
        terminal: Arc<FairMutex<Term>>,
        display: display::Notifier,
        pty: Io,
        ref_test: bool,
    ) -> EventLoop<Io> {
        let (tx, rx) = ::mio::channel::channel();
        EventLoop {
            poll: mio::Poll::new().expect("create mio Poll"),
            pty: pty,
            tx: tx,
            rx: rx,
            terminal: terminal,
            display: display,
            ref_test: ref_test,
        }
    }

    pub fn channel(&self) -> mio::channel::Sender<Msg> {
        self.tx.clone()
    }

    // Drain the channel
    //
    // Returns a `DrainResult` indicating the result of receiving from the channe;
    //
    fn drain_recv_channel(&self, state: &mut State) -> DrainResult {
        let mut received_item = false;
        while let Ok(msg) = self.rx.try_recv() {
            received_item = true;
            match msg {
                Msg::Input(input) => {
                    state.write_list.push_back(input);
                },
                Msg::Shutdown => {
                    return DrainResult::Shutdown;
                }
            }
        }

        if received_item {
            DrainResult::ReceivedItem
        } else {
            DrainResult::Empty
        }
    }

    // Returns a `bool` indicating whether or not the event loop should continue running
    #[inline]
    fn channel_event(&mut self, state: &mut State) -> bool {
        if self.drain_recv_channel(state).is_shutdown() {
            return false;
        }

        self.poll.reregister(
            &self.rx, CHANNEL,
            Ready::readable(),
            PollOpt::edge() | PollOpt::oneshot()
        ).expect("reregister channel");

        if state.needs_write() {
            self.poll.reregister(
                &EventedFd(&self.pty.as_raw_fd()),
                PTY,
                Ready::readable() | Ready::writable(),
                PollOpt::edge() | PollOpt::oneshot()
            ).expect("reregister fd after channel recv");
        }

        true
    }

    #[inline]
    fn pty_read<W>(
        &mut self,
        state: &mut State,
        buf: &mut [u8],
        mut writer: Option<&mut W>
    ) -> io::Result<()>
        where W: Write
    {
        loop {
            match self.pty.read(&mut buf[..]) {
                Ok(0) => break,
                Ok(got) => {
                    writer = writer.map(|w| {
                        w.write_all(&buf[..got]).unwrap(); w
                    });

                    let mut terminal = self.terminal.lock();
                    for byte in &buf[..got] {
                        state.parser.advance(&mut *terminal, *byte, &mut self.pty);
                    }

                    // Only request a draw if one hasn't already been requested.
                    //
                    // This is a performance optimization even if only for X11
                    // which is very expensive to hammer on the even loop wakeup
                    if !terminal.dirty {
                        self.display.notify();
                        terminal.dirty = true;

                        // Break for writing
                        //
                        // Want to prevent case where reading always returns
                        // data and sequences like `C-c` cannot be sent.
                        //
                        // Doing this check in !terminal.dirty will prevent the
                        // condition from being checked overzealously.
                        //
                        // Break if `drain_recv_channel` signals there is work to do or
                        // shutting down.
                        if state.writing.is_some()
                            || !state.write_list.is_empty()
                            || !self.drain_recv_channel(state).is_empty()
                        {
                            break;
                        }
                    }
                },
                Err(err) => {
                    match err.kind() {
                        ErrorKind::Interrupted |
                        ErrorKind::WouldBlock => break,
                        _ => return Err(err),
                    }
                }
            }
        }

        Ok(())
    }

    #[inline]
    fn pty_write(&mut self, state: &mut State) -> io::Result<()> {
        state.ensure_next();

        'write_many: while let Some(mut current) = state.take_current() {
            'write_one: loop {
                match self.pty.write(current.remaining_bytes()) {
                    Ok(0) => {
                        state.set_current(Some(current));
                        break 'write_many;
                    },
                    Ok(n) => {
                        current.advance(n);
                        if current.finished() {
                            state.goto_next();
                            break 'write_one;
                        }
                    },
                    Err(err) => {
                        state.set_current(Some(current));
                        match err.kind() {
                            ErrorKind::Interrupted |
                            ErrorKind::WouldBlock => break 'write_many,
                            _ => return Err(err),
                        }
                    }
                }

            }
        }

        Ok(())
    }

    pub fn spawn(
        mut self,
        state: Option<State>
    ) -> thread::JoinHandle<(EventLoop<Io>, State)> {
        thread::spawn_named("pty reader", move || {
            let mut state = state.unwrap_or_else(Default::default);
            let mut buf = [0u8; 4096];

            let fd = self.pty.as_raw_fd();
            let fd = EventedFd(&fd);

            let poll_opts = PollOpt::edge() | PollOpt::oneshot();

            self.poll.register(&self.rx, CHANNEL, Ready::readable(), poll_opts).unwrap();
            self.poll.register(&fd, PTY, Ready::readable(), poll_opts).unwrap();

            let mut events = Events::with_capacity(1024);

            let mut pipe = if self.ref_test {
                let file = File::create("./alacritty.recording")
                    .expect("create alacritty recording");
                Some(file)
            } else {
                None
            };

            'event_loop: loop {
                if let Err(err) = self.poll.poll(&mut events, None) {
                    match err.kind() {
                        ErrorKind::Interrupted => continue,
                        _ => panic!("EventLoop polling error: {:?}", err)
                    }
                }

                for event in events.iter() {
                    match event.token() {
                        CHANNEL =>  {
                            if !self.channel_event(&mut state) {
                                break 'event_loop;
                            }
                        },
                        PTY => {
                            let kind = event.kind();

                            if kind.is_hup() {
                                break 'event_loop;
                            }

                            if kind.is_readable() {
                                if let Err(err) = self.pty_read(&mut state, &mut buf, pipe.as_mut()) {
                                    error!("Event loop exitting due to error: {} [{}:{}]",
                                           err, file!(), line!());
                                    break 'event_loop;
                                }

                                if ::tty::process_should_exit() {
                                    break 'event_loop;
                                }
                            }

                            if kind.is_writable() {
                                if let Err(err) = self.pty_write(&mut state) {
                                    error!("Event loop exitting due to error: {} [{}:{}]",
                                           err, file!(), line!());
                                    break 'event_loop;
                                }
                            }

                            // Figure out pty interest
                            let mut interest = Ready::readable();
                            if state.needs_write() {
                                interest.insert(Ready::writable());
                            }

                            // Reregister pty
                            self.poll
                                .reregister(&fd, PTY, interest, poll_opts)
                                .expect("register fd after read/write");
                        },
                        _ => (),
                    }
                }
            }

            let _ = self.poll.deregister(&self.rx);
            let _ = self.poll.deregister(&fd);

            (self, state)
        })
    }
}
<|endoftext|>


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

use log::warn;

pub fn walk<P: AsRef<Path>>(root: P) -> std::io::Result<Walk> {
    let iter = std::fs::read_dir(root)?;
    Ok(Walk {
        pending: vec!(WalkDir::from_read_dir(iter)),
    })
}

pub struct Walk {
    // TODO: Add support for stopping at device boundaries.
    pending: Vec<WalkDir>,
}

pub struct WalkEntry {
    path: PathBuf,
    metadata: Metadata,
}

impl Walk {

    fn push(&mut self, entry: &WalkEntry) {
        match std::fs::read_dir(&entry.path) {
            Ok(iter) => {
                self.pending.push(WalkDir::from_read_dir(iter));
            },
            Err(error) => {
                warn!("failed to read '{}': {}", entry.path.display(), error);
            },
        }
    }

    fn pop(&mut self) -> Option<WalkEntry> {
        while let Some(iter) = self.pending.last_mut() {
            for entry in iter {
                return Some(entry);
            }

            self.pending.pop();
        }

        None
    }
}

impl std::iter::Iterator for Walk {

    type Item = WalkEntry;

    fn next(&mut self) -> Option<WalkEntry> {
        let entry = self.pop()?;

        if entry.metadata.is_dir() {
            self.push(&entry);
        }

        Some(entry)
    }
}

/// Iterator over the entries in a directory.
///
/// This iterator is very similar to the standard `ReadDir` iterator, except
/// that it is forgetful and only yields entries that did not cause any errors.
///
/// Unlike the `ReadDir` iterator entries, `WalkDir` entries are guaranteed to
/// have valid metadata objects attached.
struct WalkDir {
    iter: std::fs::ReadDir,
}

impl WalkDir {

    /// Converts a standard `ReadDir` iterator.
    fn from_read_dir(iter: std::fs::ReadDir) -> WalkDir {
        WalkDir {
            iter: iter,
        }
    }
}

impl std::iter::Iterator for WalkDir {

    type Item = WalkEntry;

    fn next(&mut self) -> Option<WalkEntry> {
        for entry in &mut self.iter {
            let entry = match entry {
                Ok(entry) => entry,
                Err(error) => {
                    warn!("directory iteration error: {}", error);
                    continue
                },
            };

            let path = entry.path();
            let metadata = match entry.metadata() {
                Ok(metadata) => metadata,
                Err(error) => {
                    warn!("failed to stat '{}': {}", path.display(), error);
                    continue
                },
            };

            return Some(WalkEntry {
                path: path,
                metadata: metadata,
            });
        }

        None
    }
}

#[cfg(test)]
mod tests {

    use std::fs::File;

    use super::*;

    #[test]
    fn test_walk_dir_empty() {
        let tempdir = tempfile::tempdir().unwrap();

        let iter = std::fs::read_dir(&tempdir).unwrap();
        let mut iter = WalkDir::from_read_dir(iter);

        assert!(iter.next().is_none());
    }

    #[test]
    fn test_walk_dir_with_files() {
        let tempdir = tempfile::tempdir().unwrap();
        File::create(tempdir.path().join("abc")).unwrap();
        File::create(tempdir.path().join("def")).unwrap();
        File::create(tempdir.path().join("ghi")).unwrap();

        let iter = std::fs::read_dir(&tempdir).unwrap();

        let mut results = WalkDir::from_read_dir(iter).collect::<Vec<_>>();
        results.sort_by_key(|entry| entry.path.clone());

        assert_eq!(results.len(), 3);

        assert_eq!(results[0].path, tempdir.path().join("abc"));
        assert!(results[0].metadata.is_file());

        assert_eq!(results[1].path, tempdir.path().join("def"));
        assert!(results[1].metadata.is_file());

        assert_eq!(results[2].path, tempdir.path().join("ghi"));
        assert!(results[2].metadata.is_file());
    }

    #[test]
    fn test_walk_dir_with_dirs() {
        let tempdir = tempfile::tempdir().unwrap();
        std::fs::create_dir(tempdir.path().join("abc")).unwrap();
        std::fs::create_dir(tempdir.path().join("def")).unwrap();

        let iter = std::fs::read_dir(&tempdir).unwrap();

        let mut results = WalkDir::from_read_dir(iter).collect::<Vec<_>>();
        results.sort_by_key(|entry| entry.path.clone());

        assert_eq!(results.len(), 2);

        assert_eq!(results[0].path, tempdir.path().join("abc"));
        assert!(results[0].metadata.is_dir());

        assert_eq!(results[1].path, tempdir.path().join("def"));
        assert!(results[1].metadata.is_dir());
    }

    #[cfg(target_family = "unix")]
    #[test]
    fn test_walk_dir_with_links() {
        let tempdir = tempfile::tempdir().unwrap();
        let source = tempdir.path().join("abc");
        let target = tempdir.path().join("def");

        File::create(&source).unwrap();
        std::os::unix::fs::symlink(&source, &target).unwrap();

        let iter = std::fs::read_dir(&tempdir).unwrap();

        let mut results = WalkDir::from_read_dir(iter).collect::<Vec<_>>();
        results.sort_by_key(|entry| entry.path.clone());

        assert_eq!(results.len(), 2);

        assert_eq!(results[0].path, source);
        assert!(results[0].metadata.file_type().is_file());

        assert_eq!(results[1].path, target);
        assert!(results[1].metadata.file_type().is_symlink());
    }
}
<commit_msg>Write tests for the filesystem walk iterator.<commit_after>use std::fs::Metadata;
use std::path::{Path, PathBuf};

use log::warn;

pub fn walk<P: AsRef<Path>>(root: P) -> std::io::Result<Walk> {
    let iter = std::fs::read_dir(root)?;
    Ok(Walk {
        pending: vec!(WalkDir::from_read_dir(iter)),
    })
}

pub struct Walk {
    // TODO: Add support for stopping at device boundaries.
    pending: Vec<WalkDir>,
}

pub struct WalkEntry {
    path: PathBuf,
    metadata: Metadata,
}

impl Walk {

    fn push(&mut self, entry: &WalkEntry) {
        match std::fs::read_dir(&entry.path) {
            Ok(iter) => {
                self.pending.push(WalkDir::from_read_dir(iter));
            },
            Err(error) => {
                warn!("failed to read '{}': {}", entry.path.display(), error);
            },
        }
    }

    fn pop(&mut self) -> Option<WalkEntry> {
        while let Some(iter) = self.pending.last_mut() {
            for entry in iter {
                return Some(entry);
            }

            self.pending.pop();
        }

        None
    }
}

impl std::iter::Iterator for Walk {

    type Item = WalkEntry;

    fn next(&mut self) -> Option<WalkEntry> {
        let entry = self.pop()?;

        if entry.metadata.is_dir() {
            self.push(&entry);
        }

        Some(entry)
    }
}

/// Iterator over the entries in a directory.
///
/// This iterator is very similar to the standard `ReadDir` iterator, except
/// that it is forgetful and only yields entries that did not cause any errors.
///
/// Unlike the `ReadDir` iterator entries, `WalkDir` entries are guaranteed to
/// have valid metadata objects attached.
struct WalkDir {
    iter: std::fs::ReadDir,
}

impl WalkDir {

    /// Converts a standard `ReadDir` iterator.
    fn from_read_dir(iter: std::fs::ReadDir) -> WalkDir {
        WalkDir {
            iter: iter,
        }
    }
}

impl std::iter::Iterator for WalkDir {

    type Item = WalkEntry;

    fn next(&mut self) -> Option<WalkEntry> {
        for entry in &mut self.iter {
            let entry = match entry {
                Ok(entry) => entry,
                Err(error) => {
                    warn!("directory iteration error: {}", error);
                    continue
                },
            };

            let path = entry.path();
            let metadata = match entry.metadata() {
                Ok(metadata) => metadata,
                Err(error) => {
                    warn!("failed to stat '{}': {}", path.display(), error);
                    continue
                },
            };

            return Some(WalkEntry {
                path: path,
                metadata: metadata,
            });
        }

        None
    }
}

#[cfg(test)]
mod tests {

    use std::fs::File;

    use super::*;

    #[test]
    fn test_walk_dir_empty() {
        let tempdir = tempfile::tempdir().unwrap();

        let iter = std::fs::read_dir(&tempdir).unwrap();
        let mut iter = WalkDir::from_read_dir(iter);

        assert!(iter.next().is_none());
    }

    #[test]
    fn test_walk_dir_with_files() {
        let tempdir = tempfile::tempdir().unwrap();
        File::create(tempdir.path().join("abc")).unwrap();
        File::create(tempdir.path().join("def")).unwrap();
        File::create(tempdir.path().join("ghi")).unwrap();

        let iter = std::fs::read_dir(&tempdir).unwrap();

        let mut results = WalkDir::from_read_dir(iter).collect::<Vec<_>>();
        results.sort_by_key(|entry| entry.path.clone());

        assert_eq!(results.len(), 3);

        assert_eq!(results[0].path, tempdir.path().join("abc"));
        assert!(results[0].metadata.is_file());

        assert_eq!(results[1].path, tempdir.path().join("def"));
        assert!(results[1].metadata.is_file());

        assert_eq!(results[2].path, tempdir.path().join("ghi"));
        assert!(results[2].metadata.is_file());
    }

    #[test]
    fn test_walk_dir_with_dirs() {
        let tempdir = tempfile::tempdir().unwrap();
        std::fs::create_dir(tempdir.path().join("abc")).unwrap();
        std::fs::create_dir(tempdir.path().join("def")).unwrap();

        let iter = std::fs::read_dir(&tempdir).unwrap();

        let mut results = WalkDir::from_read_dir(iter).collect::<Vec<_>>();
        results.sort_by_key(|entry| entry.path.clone());

        assert_eq!(results.len(), 2);

        assert_eq!(results[0].path, tempdir.path().join("abc"));
        assert!(results[0].metadata.is_dir());

        assert_eq!(results[1].path, tempdir.path().join("def"));
        assert!(results[1].metadata.is_dir());
    }

    #[cfg(target_family = "unix")]
    #[test]
    fn test_walk_dir_with_links() {
        let tempdir = tempfile::tempdir().unwrap();
        let source = tempdir.path().join("abc");
        let target = tempdir.path().join("def");

        File::create(&source).unwrap();
        std::os::unix::fs::symlink(&source, &target).unwrap();

        let iter = std::fs::read_dir(&tempdir).unwrap();

        let mut results = WalkDir::from_read_dir(iter).collect::<Vec<_>>();
        results.sort_by_key(|entry| entry.path.clone());

        assert_eq!(results.len(), 2);

        assert_eq!(results[0].path, source);
        assert!(results[0].metadata.file_type().is_file());

        assert_eq!(results[1].path, target);
        assert!(results[1].metadata.file_type().is_symlink());
    }

    // TODO: The iterator should yield information about the root folder on its
    // first tick.

    #[test]
    fn test_walk_empty() {
        let tempdir = tempfile::tempdir().unwrap();

        let mut iter = walk(&tempdir).unwrap();
        assert!(iter.next().is_none());
    }

    #[test]
    fn test_walk_with_flat_files() {
        let tempdir = tempfile::tempdir().unwrap();
        File::create(tempdir.path().join("abc")).unwrap();
        File::create(tempdir.path().join("def")).unwrap();
        File::create(tempdir.path().join("ghi")).unwrap();

        let mut results = walk(&tempdir).unwrap().collect::<Vec<_>>();
        results.sort_by_key(|entry| entry.path.clone());

        assert_eq!(results.len(), 3);

        assert_eq!(results[0].path, tempdir.path().join("abc"));
        assert!(results[0].metadata.is_file());

        assert_eq!(results[1].path, tempdir.path().join("def"));
        assert!(results[1].metadata.is_file());

        assert_eq!(results[2].path, tempdir.path().join("ghi"));
        assert!(results[2].metadata.is_file());
    }

    #[test]
    fn test_walk_with_flat_dirs() {
        let tempdir = tempfile::tempdir().unwrap();
        std::fs::create_dir(tempdir.path().join("abc")).unwrap();
        std::fs::create_dir(tempdir.path().join("def")).unwrap();

        let mut results = walk(&tempdir).unwrap().collect::<Vec<_>>();
        results.sort_by_key(|entry| entry.path.clone());

        assert_eq!(results.len(), 2);

        assert_eq!(results[0].path, tempdir.path().join("abc"));
        assert!(results[0].metadata.is_dir());

        assert_eq!(results[1].path, tempdir.path().join("def"));
        assert!(results[1].metadata.is_dir());
    }

    #[test]
    fn test_walk_with_nested_dirs() {
        let tempdir = tempfile::tempdir().unwrap();
        std::fs::create_dir(tempdir.path().join("abc")).unwrap();
        std::fs::create_dir(tempdir.path().join("abc").join("def")).unwrap();

        let mut results = walk(&tempdir).unwrap().collect::<Vec<_>>();
        results.sort_by_key(|entry| entry.path.clone());

        assert_eq!(results.len(), 2);

        assert_eq!(results[0].path, tempdir.path().join("abc"));
        assert!(results[0].metadata.is_dir());

        assert_eq!(results[1].path, tempdir.path().join("abc").join("def"));
        assert!(results[1].metadata.is_dir());
    }

    #[test]
    fn test_walk_with_deeply_nested_dirs() {
        let tempdir = tempfile::tempdir().unwrap();

        let mut dir = tempdir.path().to_path_buf();
        for _ in 0..512 {
            dir.push("foo");
            std::fs::create_dir(&dir).unwrap();
        }

        let mut results = walk(&tempdir).unwrap().collect::<Vec<_>>();
        results.sort_by_key(|entry| entry.path.clone());

        assert_eq!(results.len(), 512);

        for entry in results {
            assert!(entry.path.starts_with(&tempdir));
            assert!(entry.path.ends_with("foo"));
            assert!(entry.metadata.is_dir());
        }
    }

    #[test]
    fn test_walk_with_files_inside_dir() {
        let tempdir = tempfile::tempdir().unwrap();
        std::fs::create_dir(tempdir.path().join("foo")).unwrap();
        File::create(tempdir.path().join("foo").join("abc")).unwrap();
        File::create(tempdir.path().join("foo").join("def")).unwrap();

        let mut results = walk(&tempdir).unwrap().collect::<Vec<_>>();
        results.sort_by_key(|entry| entry.path.clone());

        assert_eq!(results.len(), 3);

        assert_eq!(results[0].path, tempdir.path().join("foo"));
        assert!(results[0].metadata.is_dir());

        assert_eq!(results[1].path, tempdir.path().join("foo").join("abc"));
        assert!(results[1].metadata.is_file());

        assert_eq!(results[2].path, tempdir.path().join("foo").join("def"));
        assert!(results[1].metadata.is_file());
    }

    #[cfg(target_family = "unix")]
    #[test]
    fn test_walk_with_dir_symlinks() {
        let tempdir = tempfile::tempdir().unwrap();
        let dir = tempdir.path().join("abc");
        let file = dir.join("def");
        let symlink = tempdir.path().join("ghi");

        std::fs::create_dir(&dir).unwrap();
        File::create(&file).unwrap();
        std::os::unix::fs::symlink(&dir, &symlink).unwrap();

        let mut results = walk(&tempdir).unwrap().collect::<Vec<_>>();
        results.sort_by_key(|entry| entry.path.clone());

        assert_eq!(results.len(), 3);

        assert_eq!(results[0].path, dir);
        assert!(results[0].metadata.file_type().is_dir());

        assert_eq!(results[1].path, file);
        assert!(results[1].metadata.file_type().is_file());

        assert_eq!(results[2].path, symlink);
        assert!(results[2].metadata.file_type().is_symlink());
    }

    #[cfg(target_family = "unix")]
    #[test]
    fn test_walk_with_circular_symlinks() {
        let tempdir = tempfile::tempdir().unwrap();
        let dir = tempdir.path().join("foo");
        let symlink = tempdir.path().join("foo").join("bar");

        std::fs::create_dir(&dir).unwrap();
        std::os::unix::fs::symlink(&dir, &symlink).unwrap();

        let mut results = walk(&tempdir).unwrap().collect::<Vec<_>>();
        results.sort_by_key(|entry| entry.path.clone());

        assert_eq!(results.len(), 2);

        assert_eq!(results[0].path, dir);
        assert!(results[0].metadata.file_type().is_dir());

        assert_eq!(results[1].path, symlink);
        assert!(results[1].metadata.file_type().is_symlink());
    }

    #[test]
    fn test_walk_with_unicode_names() {
        let tempdir = tempfile::tempdir().unwrap();
        File::create(tempdir.path().join("zażółć gęślą jaźń")).unwrap();

        let mut results = walk(&tempdir).unwrap().collect::<Vec<_>>();
        results.sort_by_key(|entry| entry.path.clone());

        assert_eq!(results.len(), 1);
        assert_eq!(results[0].path, tempdir.path().join("zażółć gęślą jaźń"));
    }

    #[test]
    fn test_walk_metadata_size() {
        let tempdir = tempfile::tempdir().unwrap();
        std::fs::write(tempdir.path().join("foo"), b"123456789").unwrap();

        let mut iter = walk(&tempdir).unwrap();

        let entry = iter.next().unwrap();
        assert_eq!(entry.metadata.len(), 9);

        assert!(iter.next().is_none());
    }
}
<|endoftext|>