<commit_before>// Copyright (c) 2016-2018 Chef Software Inc. and/or applicable contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use std::str::FromStr;

use super::metadata::{read_metafile, MetaFile};
use super::{PackageIdent, PackageTarget};

use crate::error::{Error, Result};

use tempfile::Builder;
use tempfile::TempDir;

pub const INSTALL_TMP_PREFIX: &str = ".hab-pkg-install";

/// Return a directory which can be used as a temp dir during package install/
/// uninstall
///
/// It returns a path which is in the same parent directory as `path`
/// but with TempDir style randomization
pub fn temp_package_directory(path: &Path) -> Result<TempDir> {
    let base = path.parent().ok_or_else(|| {
        Error::PackageUnpackFailed(
            "Could not determine parent directory for temporary package directory".to_owned(),
        )
    })?;
    fs::create_dir_all(base)?;
    let temp_install_prefix = path
        .file_name()
        .and_then(|f| f.to_str())
        .and_then(|dirname| Some(format!("{}-{}", INSTALL_TMP_PREFIX, dirname)))
        .ok_or_else(|| {
            Error::PackageUnpackFailed(
                "Could not generate prefix for temporary package directory".to_owned(),
            )
        })?;
    Ok(Builder::new()
        .prefix(&temp_install_prefix)
        .tempdir_in(base)?)
}

/// Returns a list of package structs built from the contents of the given directory.
pub fn all_packages(path: &Path) -> Result<Vec<PackageIdent>> {
    let mut package_list: Vec<PackageIdent> = vec![];
    if fs::metadata(path)?.is_dir() {
        walk_origins(&path, &mut package_list)?;
    }
    Ok(package_list)
}

/// Returns a vector of package idents built from the contents of
/// the given directory, using the given origin to restrict the
/// search.
///
/// The search is restricted by assuming the package directory
/// structure is:
///
///    /base/ORIGIN/NAME/VERSION/RELEASE/
///
pub fn package_list_for_origin(base_pkg_path: &Path, origin: &String) -> Result<Vec<PackageIdent>> {
    let mut package_list: Vec<PackageIdent> = vec![];
    let mut package_path = PathBuf::from(base_pkg_path);
    package_path.push(&origin);

    if !is_existing_dir(&package_path)? {
        return Ok(package_list);
    };

    walk_names(&origin, &package_path, &mut package_list)?;
    Ok(package_list)
}

/// Returns a vector of package structs built from the contents of
/// the given directory, using the given ident to restrict the
/// search.
///
/// The search is restricted by assuming the package directory
/// structure is:
///
///    /base/ORIGIN/NAME/VERSION/RELEASE/
///
pub fn package_list_for_ident(
    base_pkg_path: &Path,
    ident: &PackageIdent,
) -> Result<Vec<PackageIdent>> {
    let mut package_list: Vec<PackageIdent> = vec![];
    let mut package_path = PathBuf::from(base_pkg_path);
    package_path.push(&ident.origin);
    package_path.push(&ident.name);

    if !is_existing_dir(&package_path)? {
        return Ok(package_list);
    }

    match (&ident.version, &ident.release) {
        // origin/name
        (None, _) => walk_versions(&ident.origin, &ident.name, &package_path, &mut package_list)?,
        // origin/name/version
        (Some(version), None) => {
            package_path.push(version);
            if !is_existing_dir(&package_path)? {
                return Ok(package_list);
            }
            walk_releases(
                &ident.origin,
                &ident.name,
                &version,
                &package_path,
                &mut package_list,
            )?
        }
        // origin/name/version/release
        (Some(version), Some(release)) => {
            package_path.push(version);
            package_path.push(release);
            if !is_existing_dir(&package_path)? {
                return Ok(package_list);
            }

            let active_target = PackageTarget::active_target();
            if let Some(new_ident) = package_ident_from_dir(
                &ident.origin,
                &ident.name,
                &version,
                active_target,
                &package_path,
            ) {
                package_list.push(new_ident.clone())
            }
        }
    }
    Ok(package_list)
}

/// Helper function for all_packages. Walks the directory at the given
/// Path for origin directories and builds on the given package list
/// by recursing into name, version, and release directories.
fn walk_origins(path: &Path, packages: &mut Vec<PackageIdent>) -> Result<()> {
    for entry in fs::read_dir(path)? {
        let origin_dir = entry?;
        let origin_path = origin_dir.path();
        if fs::metadata(&origin_path)?.is_dir() {
            let origin = filename_from_entry(origin_dir);
            walk_names(&origin, &origin_path, packages)?;
        }
    }
    Ok(())
}

/// Helper function for walk_origins. Walks the direcotry at the given
/// Path for name directories and recurses into them to find version
/// and release directories.
fn walk_names(origin: &String, dir: &Path, packages: &mut Vec<PackageIdent>) -> Result<()> {
    for entry in fs::read_dir(dir)? {
        let name_dir = entry?;
        let name_path = name_dir.path();
        if fs::metadata(&name_path)?.is_dir() {
            let name = filename_from_entry(name_dir);
            walk_versions(&origin, &name, &name_path, packages)?;
        }
    }
    Ok(())
}

/// Helper function for walk_names. Walks the directory at the given
/// Path and recurses into them to find release directories.
fn walk_versions(
    origin: &String,
    name: &String,
    dir: &Path,
    packages: &mut Vec<PackageIdent>,
) -> Result<()> {
    for entry in fs::read_dir(dir)? {
        let version_dir = entry?;
        let version_path = version_dir.path();
        if fs::metadata(&version_path)?.is_dir() {
            let version = filename_from_entry(version_dir);
            walk_releases(origin, name, &version, &version_path, packages)?;
        }
    }
    Ok(())
}

/// Helper function for walk_versions. Walks the directory at the
/// given Path and constructs a Package struct if the directory is a
/// valid package directory. Any resulting packages are pushed onto
/// the given packages vector, assuming the given origin, name, and
/// version.
fn walk_releases(
    origin: &String,
    name: &String,
    version: &String,
    dir: &Path,
    packages: &mut Vec<PackageIdent>,
) -> Result<()> {
    let active_target = PackageTarget::active_target();
    for entry in fs::read_dir(dir)? {
        let release_dir = entry?;
        let release_path = release_dir.path();
        if fs::metadata(&release_path)?.is_dir() {
            if let Some(ident) =
                package_ident_from_dir(origin, name, version, active_target, &release_path)
            {
                packages.push(ident)
            }
        }
    }
    Ok(())
}

/// package_ident_from_dir returns a PackageIdent if the given
/// path contains a valid package for the given active_target.
///
/// Returns None when
///    - The directory is a temporary install directroy
///    - An error occurs reading the package metadata
///    - An error occurs reading the package target
///    - The package target doesn't match the given active target
fn package_ident_from_dir(
    origin: &String,
    name: &String,
    version: &String,
    active_target: &PackageTarget,
    dir: &Path,
) -> Option<PackageIdent> {
    let release = if let Some(rel) = dir.file_name().and_then(|f| f.to_str()) {
        rel
    } else {
        return None;
    };

    if release.starts_with(INSTALL_TMP_PREFIX) {
        debug!(
            "PackageInstall::package_ident_from_dir(): rejected PackageInstall candidate \
             because it matches installation temporary directory prefix: {}",
            dir.display()
        );
        return None;
    }

    let metafile_content = read_metafile(dir, &MetaFile::Target);
    // If there is an error reading the target metafile, then skip the candidate
    if let Err(e) = metafile_content {
        debug!(
            "PackageInstall::package_ident_from_dir(): rejected PackageInstall candidate \
             due to error reading TARGET metafile, found={}, reason={:?}",
            dir.display(),
            e,
        );
        return None;
    }

    // Any errors have been cleared, so unwrap is safe
    let metafile_content = metafile_content.unwrap();
    let install_target = PackageTarget::from_str(&metafile_content);
    // If there is an error parsing the target as a valid PackageTarget, then skip the
    // candidate
    if let Err(e) = install_target {
        debug!(
            "PackageInstall::package_ident_from_dir(): rejected PackageInstall candidate \
             due to error parsing TARGET metafile as a valid PackageTarget, \
             found={}, reason={:?}",
            dir.display(),
            e,
        );
        return None;
    }
    // Any errors have been cleared, so unwrap is safe
    let install_target = install_target.unwrap();

    // Ensure that the installed package's target matches the active `PackageTarget`,
    // otherwise skip the candidate
    if active_target == &install_target {
        return Some(PackageIdent::new(
            origin.clone(),
            name.clone(),
            Some(version.clone()),
            Some(release.to_owned()),
        ));
    } else {
        debug!(
            "PackageInstall::package_ident_from_dir(): rejected PackageInstall candidate, \
             found={}, installed_target={}, active_target={}",
            dir.display(),
            install_target,
            active_target,
        );
        return None;
    }
}

fn filename_from_entry(entry: fs::DirEntry) -> String {
    return entry.file_name().to_string_lossy().into_owned().to_string();
}

fn is_existing_dir(path: &Path) -> Result<bool> {
    match fs::metadata(&path) {
        Err(err) => {
            if err.kind() == io::ErrorKind::NotFound {
                return Ok(false);
            }
            return Err(Error::from(err));
        }
        Ok(metadata) => return Ok(metadata.is_dir()),
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::package::test_support::testing_package_install;

    use crate::fs;
    use std::fs::File;
    use tempfile::Builder;

    #[test]
    fn empty_dir_gives_empty_list() {
        let package_root = Builder::new().prefix("fs-root").tempdir().unwrap();
        let packages = all_packages(&package_root.path()).unwrap();

        assert_eq!(0, packages.len());
    }

    #[test]
    fn not_a_dir_gives_empty_list() {
        let fs_root = Builder::new().prefix("fs-root").tempdir().unwrap();
        let file_path = fs_root.path().join("not-a-dir");
        let _tmp_file = File::create(&file_path).unwrap();

        let packages = all_packages(&file_path).unwrap();

        assert_eq!(0, packages.len());
    }
    #[test]
    fn can_read_single_package() {
        let fs_root = Builder::new().prefix("fs-root").tempdir().unwrap();
        let package_root = fs::pkg_root_path(Some(fs_root.path()));
        let package_install = testing_package_install("core/redis", fs_root.path());

        let packages = all_packages(&package_root).unwrap();

        assert_eq!(1, packages.len());
        assert_eq!(package_install.ident, packages[0]);
    }

    #[test]
    fn can_read_multiple_packages() {
        let fs_root = Builder::new().prefix("fs-root").tempdir().unwrap();
        let package_root = fs::pkg_root_path(Some(fs_root.path()));
        let expected = vec![
            testing_package_install("core/redis/1.0.0", fs_root.path()),
            testing_package_install("test/foobar", fs_root.path()),
            testing_package_install("core/redis/1.1.0", fs_root.path()),
        ];

        let packages = all_packages(&package_root).unwrap();

        assert_eq!(3, packages.len());
        for p in &expected {
            assert!(packages.contains(&p.ident));
        }
    }

    #[test]
    fn create_temp_package_directory_in_same_parentdir() {
        let p = Path::new("/tmp/foo");
        let temp_dir = temp_package_directory(&p).unwrap();
        let temp_path = temp_dir.path();

        assert_eq!(&p.parent(), &temp_path.parent());
    }

    #[test]
    fn temp_package_directory_starts_with_prefix() {
        let p = Path::new("/tmp/foo");
        let temp_dir = temp_package_directory(&p).unwrap();
        let temp_filename = temp_dir.path().file_name().unwrap().to_str().unwrap();

        assert!(&temp_filename.starts_with(INSTALL_TMP_PREFIX));
    }

    #[test]
    fn temp_package_directory_changes() {
        let p = Path::new("/tmp/foo");
        let temp_dir1 = temp_package_directory(&p).unwrap();
        let temp_dir2 = temp_package_directory(&p).unwrap();

        assert_ne!(&temp_dir1.path(), &temp_dir2.path());
    }

    #[test]
    fn list_for_origin_skips_non_origin_packages() {
        let fs_root = Builder::new().prefix("fs-root").tempdir().unwrap();
        let package_root = fs::pkg_root_path(Some(fs_root.path()));
        let test_origin = vec![testing_package_install("test/foobar", fs_root.path())];
        let core_origin = vec![
            testing_package_install("core/redis/1.0.0", fs_root.path()),
            testing_package_install("core/redis/1.1.0", fs_root.path()),
        ];

        let packages = package_list_for_origin(&package_root, &String::from("core")).unwrap();

        assert_eq!(2, packages.len());
        for p in &core_origin {
            assert!(packages.contains(&p.ident));
        }

        for p in &test_origin {
            assert!(!packages.contains(&p.ident));
        }
    }

    #[test]
    fn list_for_origin_no_packages() {
        let fs_root = Builder::new().prefix("fs-root").tempdir().unwrap();
        let package_root = fs::pkg_root_path(Some(fs_root.path()));

        let packages = package_list_for_origin(&package_root, &String::from("core")).unwrap();

        assert_eq!(0, packages.len());
    }
}
<commit_msg>Examine clippy::ptr_arg<commit_after>// Copyright (c) 2016-2018 Chef Software Inc. and/or applicable contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use std::str::FromStr;

use super::metadata::{read_metafile, MetaFile};
use super::{PackageIdent, PackageTarget};

use crate::error::{Error, Result};

use tempfile::Builder;
use tempfile::TempDir;

pub const INSTALL_TMP_PREFIX: &str = ".hab-pkg-install";

/// Return a directory which can be used as a temp dir during package install/
/// uninstall
///
/// It returns a path which is in the same parent directory as `path`
/// but with TempDir style randomization
pub fn temp_package_directory(path: &Path) -> Result<TempDir> {
    let base = path.parent().ok_or_else(|| {
        Error::PackageUnpackFailed(
            "Could not determine parent directory for temporary package directory".to_owned(),
        )
    })?;
    fs::create_dir_all(base)?;
    let temp_install_prefix = path
        .file_name()
        .and_then(|f| f.to_str())
        .and_then(|dirname| Some(format!("{}-{}", INSTALL_TMP_PREFIX, dirname)))
        .ok_or_else(|| {
            Error::PackageUnpackFailed(
                "Could not generate prefix for temporary package directory".to_owned(),
            )
        })?;
    Ok(Builder::new()
        .prefix(&temp_install_prefix)
        .tempdir_in(base)?)
}

/// Returns a list of package structs built from the contents of the given directory.
pub fn all_packages(path: &Path) -> Result<Vec<PackageIdent>> {
    let mut package_list: Vec<PackageIdent> = vec![];
    if fs::metadata(path)?.is_dir() {
        walk_origins(&path, &mut package_list)?;
    }
    Ok(package_list)
}

/// Returns a vector of package idents built from the contents of
/// the given directory, using the given origin to restrict the
/// search.
///
/// The search is restricted by assuming the package directory
/// structure is:
///
///    /base/ORIGIN/NAME/VERSION/RELEASE/
///
pub fn package_list_for_origin(base_pkg_path: &Path, origin: &str) -> Result<Vec<PackageIdent>> {
    let mut package_list: Vec<PackageIdent> = vec![];
    let mut package_path = PathBuf::from(base_pkg_path);
    package_path.push(&origin);

    if !is_existing_dir(&package_path)? {
        return Ok(package_list);
    };

    walk_names(&origin, &package_path, &mut package_list)?;
    Ok(package_list)
}

/// Returns a vector of package structs built from the contents of
/// the given directory, using the given ident to restrict the
/// search.
///
/// The search is restricted by assuming the package directory
/// structure is:
///
///    /base/ORIGIN/NAME/VERSION/RELEASE/
///
pub fn package_list_for_ident(
    base_pkg_path: &Path,
    ident: &PackageIdent,
) -> Result<Vec<PackageIdent>> {
    let mut package_list: Vec<PackageIdent> = vec![];
    let mut package_path = PathBuf::from(base_pkg_path);
    package_path.push(&ident.origin);
    package_path.push(&ident.name);

    if !is_existing_dir(&package_path)? {
        return Ok(package_list);
    }

    match (&ident.version, &ident.release) {
        // origin/name
        (None, _) => walk_versions(&ident.origin, &ident.name, &package_path, &mut package_list)?,
        // origin/name/version
        (Some(version), None) => {
            package_path.push(version);
            if !is_existing_dir(&package_path)? {
                return Ok(package_list);
            }
            walk_releases(
                &ident.origin,
                &ident.name,
                &version,
                &package_path,
                &mut package_list,
            )?
        }
        // origin/name/version/release
        (Some(version), Some(release)) => {
            package_path.push(version);
            package_path.push(release);
            if !is_existing_dir(&package_path)? {
                return Ok(package_list);
            }

            let active_target = PackageTarget::active_target();
            if let Some(new_ident) = package_ident_from_dir(
                &ident.origin,
                &ident.name,
                &version,
                active_target,
                &package_path,
            ) {
                package_list.push(new_ident.clone())
            }
        }
    }
    Ok(package_list)
}

/// Helper function for all_packages. Walks the directory at the given
/// Path for origin directories and builds on the given package list
/// by recursing into name, version, and release directories.
fn walk_origins(path: &Path, packages: &mut Vec<PackageIdent>) -> Result<()> {
    for entry in fs::read_dir(path)? {
        let origin_dir = entry?;
        let origin_path = origin_dir.path();
        if fs::metadata(&origin_path)?.is_dir() {
            let origin = filename_from_entry(origin_dir);
            walk_names(&origin, &origin_path, packages)?;
        }
    }
    Ok(())
}

/// Helper function for walk_origins. Walks the direcotry at the given
/// Path for name directories and recurses into them to find version
/// and release directories.
fn walk_names(origin: &str, dir: &Path, packages: &mut Vec<PackageIdent>) -> Result<()> {
    for entry in fs::read_dir(dir)? {
        let name_dir = entry?;
        let name_path = name_dir.path();
        if fs::metadata(&name_path)?.is_dir() {
            let name = filename_from_entry(name_dir);
            walk_versions(&origin, &name, &name_path, packages)?;
        }
    }
    Ok(())
}

/// Helper function for walk_names. Walks the directory at the given
/// Path and recurses into them to find release directories.
fn walk_versions(
    origin: &str,
    name: &str,
    dir: &Path,
    packages: &mut Vec<PackageIdent>,
) -> Result<()> {
    for entry in fs::read_dir(dir)? {
        let version_dir = entry?;
        let version_path = version_dir.path();
        if fs::metadata(&version_path)?.is_dir() {
            let version = filename_from_entry(version_dir);
            walk_releases(origin, name, &version, &version_path, packages)?;
        }
    }
    Ok(())
}

/// Helper function for walk_versions. Walks the directory at the
/// given Path and constructs a Package struct if the directory is a
/// valid package directory. Any resulting packages are pushed onto
/// the given packages vector, assuming the given origin, name, and
/// version.
fn walk_releases(
    origin: &str,
    name: &str,
    version: &str,
    dir: &Path,
    packages: &mut Vec<PackageIdent>,
) -> Result<()> {
    let active_target = PackageTarget::active_target();
    for entry in fs::read_dir(dir)? {
        let release_dir = entry?;
        let release_path = release_dir.path();
        if fs::metadata(&release_path)?.is_dir() {
            if let Some(ident) =
                package_ident_from_dir(origin, name, version, active_target, &release_path)
            {
                packages.push(ident)
            }
        }
    }
    Ok(())
}

/// package_ident_from_dir returns a PackageIdent if the given
/// path contains a valid package for the given active_target.
///
/// Returns None when
///    - The directory is a temporary install directroy
///    - An error occurs reading the package metadata
///    - An error occurs reading the package target
///    - The package target doesn't match the given active target
fn package_ident_from_dir(
    origin: &str,
    name: &str,
    version: &str,
    active_target: &PackageTarget,
    dir: &Path,
) -> Option<PackageIdent> {
    let release = if let Some(rel) = dir.file_name().and_then(|f| f.to_str()) {
        rel
    } else {
        return None;
    };

    if release.starts_with(INSTALL_TMP_PREFIX) {
        debug!(
            "PackageInstall::package_ident_from_dir(): rejected PackageInstall candidate \
             because it matches installation temporary directory prefix: {}",
            dir.display()
        );
        return None;
    }

    let metafile_content = read_metafile(dir, &MetaFile::Target);
    // If there is an error reading the target metafile, then skip the candidate
    if let Err(e) = metafile_content {
        debug!(
            "PackageInstall::package_ident_from_dir(): rejected PackageInstall candidate \
             due to error reading TARGET metafile, found={}, reason={:?}",
            dir.display(),
            e,
        );
        return None;
    }

    // Any errors have been cleared, so unwrap is safe
    let metafile_content = metafile_content.unwrap();
    let install_target = PackageTarget::from_str(&metafile_content);
    // If there is an error parsing the target as a valid PackageTarget, then skip the
    // candidate
    if let Err(e) = install_target {
        debug!(
            "PackageInstall::package_ident_from_dir(): rejected PackageInstall candidate \
             due to error parsing TARGET metafile as a valid PackageTarget, \
             found={}, reason={:?}",
            dir.display(),
            e,
        );
        return None;
    }
    // Any errors have been cleared, so unwrap is safe
    let install_target = install_target.unwrap();

    // Ensure that the installed package's target matches the active `PackageTarget`,
    // otherwise skip the candidate
    if active_target == &install_target {
        return Some(PackageIdent::new(
            origin.to_string(),
            name.to_string(),
            Some(version.to_string()),
            Some(release.to_owned()),
        ));
    } else {
        debug!(
            "PackageInstall::package_ident_from_dir(): rejected PackageInstall candidate, \
             found={}, installed_target={}, active_target={}",
            dir.display(),
            install_target,
            active_target,
        );
        return None;
    }
}

fn filename_from_entry(entry: fs::DirEntry) -> String {
    return entry.file_name().to_string_lossy().into_owned().to_string();
}

fn is_existing_dir(path: &Path) -> Result<bool> {
    match fs::metadata(&path) {
        Err(err) => {
            if err.kind() == io::ErrorKind::NotFound {
                return Ok(false);
            }
            return Err(Error::from(err));
        }
        Ok(metadata) => return Ok(metadata.is_dir()),
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::package::test_support::testing_package_install;

    use crate::fs;
    use std::fs::File;
    use tempfile::Builder;

    #[test]
    fn empty_dir_gives_empty_list() {
        let package_root = Builder::new().prefix("fs-root").tempdir().unwrap();
        let packages = all_packages(&package_root.path()).unwrap();

        assert_eq!(0, packages.len());
    }

    #[test]
    fn not_a_dir_gives_empty_list() {
        let fs_root = Builder::new().prefix("fs-root").tempdir().unwrap();
        let file_path = fs_root.path().join("not-a-dir");
        let _tmp_file = File::create(&file_path).unwrap();

        let packages = all_packages(&file_path).unwrap();

        assert_eq!(0, packages.len());
    }
    #[test]
    fn can_read_single_package() {
        let fs_root = Builder::new().prefix("fs-root").tempdir().unwrap();
        let package_root = fs::pkg_root_path(Some(fs_root.path()));
        let package_install = testing_package_install("core/redis", fs_root.path());

        let packages = all_packages(&package_root).unwrap();

        assert_eq!(1, packages.len());
        assert_eq!(package_install.ident, packages[0]);
    }

    #[test]
    fn can_read_multiple_packages() {
        let fs_root = Builder::new().prefix("fs-root").tempdir().unwrap();
        let package_root = fs::pkg_root_path(Some(fs_root.path()));
        let expected = vec![
            testing_package_install("core/redis/1.0.0", fs_root.path()),
            testing_package_install("test/foobar", fs_root.path()),
            testing_package_install("core/redis/1.1.0", fs_root.path()),
        ];

        let packages = all_packages(&package_root).unwrap();

        assert_eq!(3, packages.len());
        for p in &expected {
            assert!(packages.contains(&p.ident));
        }
    }

    #[test]
    fn create_temp_package_directory_in_same_parentdir() {
        let p = Path::new("/tmp/foo");
        let temp_dir = temp_package_directory(&p).unwrap();
        let temp_path = temp_dir.path();

        assert_eq!(&p.parent(), &temp_path.parent());
    }

    #[test]
    fn temp_package_directory_starts_with_prefix() {
        let p = Path::new("/tmp/foo");
        let temp_dir = temp_package_directory(&p).unwrap();
        let temp_filename = temp_dir.path().file_name().unwrap().to_str().unwrap();

        assert!(&temp_filename.starts_with(INSTALL_TMP_PREFIX));
    }

    #[test]
    fn temp_package_directory_changes() {
        let p = Path::new("/tmp/foo");
        let temp_dir1 = temp_package_directory(&p).unwrap();
        let temp_dir2 = temp_package_directory(&p).unwrap();

        assert_ne!(&temp_dir1.path(), &temp_dir2.path());
    }

    #[test]
    fn list_for_origin_skips_non_origin_packages() {
        let fs_root = Builder::new().prefix("fs-root").tempdir().unwrap();
        let package_root = fs::pkg_root_path(Some(fs_root.path()));
        let test_origin = vec![testing_package_install("test/foobar", fs_root.path())];
        let core_origin = vec![
            testing_package_install("core/redis/1.0.0", fs_root.path()),
            testing_package_install("core/redis/1.1.0", fs_root.path()),
        ];

        let packages = package_list_for_origin(&package_root, &String::from("core")).unwrap();

        assert_eq!(2, packages.len());
        for p in &core_origin {
            assert!(packages.contains(&p.ident));
        }

        for p in &test_origin {
            assert!(!packages.contains(&p.ident));
        }
    }

    #[test]
    fn list_for_origin_no_packages() {
        let fs_root = Builder::new().prefix("fs-root").tempdir().unwrap();
        let package_root = fs::pkg_root_path(Some(fs_root.path()));

        let packages = package_list_for_origin(&package_root, &String::from("core")).unwrap();

        assert_eq!(0, packages.len());
    }
}
<|endoftext|>


<commit_before>extern crate tokio_process;

use futures;
use std;
use std::io::Result as IoResult;
use std::io::{Read, Write};
use tokio_core::reactor::Handle;
use tokio_io::{AsyncRead, AsyncWrite};

use std::cell::RefCell;
use std::rc::Rc;

use std::process::Command;

use self::tokio_process::{Child, CommandExt};

use super::{once, ConstructParams, PeerConstructor, Specifier};
use super::{BoxedNewPeerFuture, Peer};
use std::process::Stdio;

#[derive(Debug, Clone)]
pub struct Cmd(pub String);
impl Specifier for Cmd {
    fn construct(&self, p: ConstructParams) -> PeerConstructor {
        let args = if cfg!(target_os = "windows") {
            let mut args = Command::new("cmd");
            args.arg("/C").arg(self.0.clone());
            args
        } else {
            let mut args = Command::new("sh");
            args.arg("-c").arg(self.0.clone());
            args
        };
        let h = &p.tokio_handle;
        once(Box::new(futures::future::result(process_connect_peer(h, args))) as BoxedNewPeerFuture)
    }
    specifier_boilerplate!(noglobalstate singleconnect no_subspec typ=Other);
}
specifier_class!(
    name = CmdClass,
    target = Cmd,
    prefixes = ["cmd:"],
    arg_handling = into,
    overlay = false,
    StreamOriented,
    SingleConnect,
    help = r#"
Start specified command line using `sh -c` (even on Windows)

Example: serve a counter

    websocat -U ws-l:127.0.0.1:8008 sh-c:'for i in 0 1 2 3 4 5 6 7 8 9 10; do echo $i; sleep 1; done'

Example: unauthenticated shell

    websocat --exit-on-eof ws-l:127.0.0.1:5667 sh-c:'bash -i 2>&1'

"#
);
// TODO: client and example output for each server example
// TODO: chromium-based examples

#[derive(Debug, Clone)]
pub struct ShC(pub String);
impl Specifier for ShC {
    fn construct(&self, p: ConstructParams) -> PeerConstructor {
        let mut args = Command::new("sh");
        args.arg("-c").arg(self.0.clone());
        let h = &p.tokio_handle;
        once(Box::new(futures::future::result(process_connect_peer(h, args))) as BoxedNewPeerFuture)
    }
    specifier_boilerplate!(noglobalstate singleconnect no_subspec typ=Other);
}
specifier_class!(
    name = ShCClass,
    target = ShC,
    prefixes = ["sh-c:"],
    arg_handling = into,
    overlay = false,
    StreamOriented,
    SingleConnect,
    help = r#"
Start specified command line using `sh -c` or `cmd /C` (depending on platform)

Otherwise should be the the same as `sh-c:` (see examples from there).
"#
);

#[derive(Debug, Clone)]
pub struct Exec(pub String);
impl Specifier for Exec {
    fn construct(&self, p: ConstructParams) -> PeerConstructor {
        let mut args = Command::new(self.0.clone());
        args.args(p.program_options.exec_args.clone());
        let h = &p.tokio_handle;
        once(Box::new(futures::future::result(process_connect_peer(h, args))) as BoxedNewPeerFuture)
    }
    specifier_boilerplate!(noglobalstate singleconnect no_subspec typ=Other);
}
specifier_class!(
    name = ExecClass,
    target = Exec,
    prefixes = ["exec:"],
    arg_handling = into,
    overlay = false,
    StreamOriented,
    SingleConnect,
    help = r#"
Execute a program directly (without a subshell), providing array of arguments on Unix [A]

Example: Serve current date

  websocat -U ws-l:127.0.0.1:5667 exec:date

Example: pinger

  websocat -U ws-l:127.0.0.1:5667 exec:ping --exec-args 127.0.0.1 -c 1

"#
);

fn process_connect_peer(h: &Handle, mut cmd: Command) -> Result<Peer, Box<std::error::Error>> {
    cmd.stdin(Stdio::piped()).stdout(Stdio::piped());
    let child = cmd.spawn_async(h)?;
    let ph = ProcessPeer(Rc::new(RefCell::new(child)));
    Ok(Peer::new(ph.clone(), ph))
}

#[derive(Clone)]
struct ProcessPeer(Rc<RefCell<Child>>);

impl Read for ProcessPeer {
    fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> {
        self.0
            .borrow_mut()
            .stdout()
            .as_mut()
            .expect("assertion failed 1425")
            .read(buf)
    }
}

impl Write for ProcessPeer {
    fn write(&mut self, buf: &[u8]) -> IoResult<usize> {
        self.0
            .borrow_mut()
            .stdin()
            .as_mut()
            .expect("assertion failed 1425")
            .write(buf)
    }

    fn flush(&mut self) -> IoResult<()> {
        self.0
            .borrow_mut()
            .stdin()
            .as_mut()
            .expect("assertion failed 1425")
            .flush()
    }
}

impl AsyncRead for ProcessPeer {}

impl AsyncWrite for ProcessPeer {
    fn shutdown(&mut self) -> futures::Poll<(), std::io::Error> {
        let mut c: tokio_process::ChildStdin = self
            .0
            .borrow_mut()
            .stdin()
            .take()
            .expect("assertion failed 1425");
        c.shutdown()
    }
}
<commit_msg>Fix confused error messages for cmd: and sh-c:<commit_after>extern crate tokio_process;

use futures;
use std;
use std::io::Result as IoResult;
use std::io::{Read, Write};
use tokio_core::reactor::Handle;
use tokio_io::{AsyncRead, AsyncWrite};

use std::cell::RefCell;
use std::rc::Rc;

use std::process::Command;

use self::tokio_process::{Child, CommandExt};

use super::{once, ConstructParams, PeerConstructor, Specifier};
use super::{BoxedNewPeerFuture, Peer};
use std::process::Stdio;

#[derive(Debug, Clone)]
pub struct Cmd(pub String);
impl Specifier for Cmd {
    fn construct(&self, p: ConstructParams) -> PeerConstructor {
        let args = if cfg!(target_os = "windows") {
            let mut args = Command::new("cmd");
            args.arg("/C").arg(self.0.clone());
            args
        } else {
            let mut args = Command::new("sh");
            args.arg("-c").arg(self.0.clone());
            args
        };
        let h = &p.tokio_handle;
        once(Box::new(futures::future::result(process_connect_peer(h, args))) as BoxedNewPeerFuture)
    }
    specifier_boilerplate!(noglobalstate singleconnect no_subspec typ=Other);
}
specifier_class!(
    name = CmdClass,
    target = Cmd,
    prefixes = ["cmd:"],
    arg_handling = into,
    overlay = false,
    StreamOriented,
    SingleConnect,
    help = r#"
Start specified command line using `sh -c` or `cmd /C` (depending on platform)

Otherwise should be the the same as `sh-c:` (see examples from there).
"#
);
// TODO: client and example output for each server example
// TODO: chromium-based examples

#[derive(Debug, Clone)]
pub struct ShC(pub String);
impl Specifier for ShC {
    fn construct(&self, p: ConstructParams) -> PeerConstructor {
        let mut args = Command::new("sh");
        args.arg("-c").arg(self.0.clone());
        let h = &p.tokio_handle;
        once(Box::new(futures::future::result(process_connect_peer(h, args))) as BoxedNewPeerFuture)
    }
    specifier_boilerplate!(noglobalstate singleconnect no_subspec typ=Other);
}
specifier_class!(
    name = ShCClass,
    target = ShC,
    prefixes = ["sh-c:"],
    arg_handling = into,
    overlay = false,
    StreamOriented,
    SingleConnect,
    help = r#"
Start specified command line using `sh -c` (even on Windows)

Example: serve a counter

    websocat -U ws-l:127.0.0.1:8008 sh-c:'for i in 0 1 2 3 4 5 6 7 8 9 10; do echo $i; sleep 1; done'

Example: unauthenticated shell

    websocat --exit-on-eof ws-l:127.0.0.1:5667 sh-c:'bash -i 2>&1'
"#
);

#[derive(Debug, Clone)]
pub struct Exec(pub String);
impl Specifier for Exec {
    fn construct(&self, p: ConstructParams) -> PeerConstructor {
        let mut args = Command::new(self.0.clone());
        args.args(p.program_options.exec_args.clone());
        let h = &p.tokio_handle;
        once(Box::new(futures::future::result(process_connect_peer(h, args))) as BoxedNewPeerFuture)
    }
    specifier_boilerplate!(noglobalstate singleconnect no_subspec typ=Other);
}
specifier_class!(
    name = ExecClass,
    target = Exec,
    prefixes = ["exec:"],
    arg_handling = into,
    overlay = false,
    StreamOriented,
    SingleConnect,
    help = r#"
Execute a program directly (without a subshell), providing array of arguments on Unix [A]

Example: Serve current date

  websocat -U ws-l:127.0.0.1:5667 exec:date

Example: pinger

  websocat -U ws-l:127.0.0.1:5667 exec:ping --exec-args 127.0.0.1 -c 1

"#
);

fn process_connect_peer(h: &Handle, mut cmd: Command) -> Result<Peer, Box<std::error::Error>> {
    cmd.stdin(Stdio::piped()).stdout(Stdio::piped());
    let child = cmd.spawn_async(h)?;
    let ph = ProcessPeer(Rc::new(RefCell::new(child)));
    Ok(Peer::new(ph.clone(), ph))
}

#[derive(Clone)]
struct ProcessPeer(Rc<RefCell<Child>>);

impl Read for ProcessPeer {
    fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> {
        self.0
            .borrow_mut()
            .stdout()
            .as_mut()
            .expect("assertion failed 1425")
            .read(buf)
    }
}

impl Write for ProcessPeer {
    fn write(&mut self, buf: &[u8]) -> IoResult<usize> {
        self.0
            .borrow_mut()
            .stdin()
            .as_mut()
            .expect("assertion failed 1425")
            .write(buf)
    }

    fn flush(&mut self) -> IoResult<()> {
        self.0
            .borrow_mut()
            .stdin()
            .as_mut()
            .expect("assertion failed 1425")
            .flush()
    }
}

impl AsyncRead for ProcessPeer {}

impl AsyncWrite for ProcessPeer {
    fn shutdown(&mut self) -> futures::Poll<(), std::io::Error> {
        let mut c: tokio_process::ChildStdin = self
            .0
            .borrow_mut()
            .stdin()
            .take()
            .expect("assertion failed 1425");
        c.shutdown()
    }
}
<|endoftext|>


<commit_before>//! Support for archive files.

use core::convert::TryInto;

use crate::archive;
use crate::read::{self, Error, ReadError, ReadRef};

/// The kind of archive format.
// TODO: Gnu64 and Darwin64 (and Darwin for writing)
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ArchiveKind {
    /// There are no special files that indicate the archive format.
    Unknown,
    /// The GNU (or System V) archive format.
    Gnu,
    /// The BSD archive format.
    Bsd,
    /// The Windows COFF archive format.
    Coff,
}

/// A partially parsed archive file.
#[derive(Debug)]
pub struct ArchiveFile<'data, R: ReadRef<'data> = &'data [u8]> {
    data: R,
    len: u64,
    offset: u64,
    kind: ArchiveKind,
    symbols: &'data [u8],
    names: &'data [u8],
}

impl<'data, R: ReadRef<'data>> ArchiveFile<'data, R> {
    /// Parse the archive header and special members.
    pub fn parse(data: R) -> read::Result<Self> {
        let len = data.len().read_error("Unknown archive length")?;
        let mut tail = 0;
        let magic = data
            .read_bytes(&mut tail, archive::MAGIC.len() as u64)
            .read_error("Invalid archive size")?;
        if magic != &archive::MAGIC[..] {
            return Err(Error("Unsupported archive identifier"));
        }

        let mut file = ArchiveFile {
            data,
            offset: tail,
            len,
            kind: ArchiveKind::Unknown,
            symbols: &[],
            names: &[],
        };

        // The first few members may be special, so parse them.
        // GNU has:
        // - "/": symbol table (optional)
        // - "//": names table (optional)
        // COFF has:
        // - "/": first linker member
        // - "/": second linker member
        // - "//": names table
        // BSD has:
        // - "__.SYMDEF" or "__.SYMDEF SORTED": symbol table (optional)
        if tail < len {
            let member = ArchiveMember::parse(data, &mut tail, &[])?;
            if member.name == b"/" {
                // GNU symbol table (unless we later determine this is COFF).
                file.kind = ArchiveKind::Gnu;
                file.symbols = member.data(data)?;
                file.offset = tail;

                if tail < len {
                    let member = ArchiveMember::parse(data, &mut tail, &[])?;
                    if member.name == b"/" {
                        // COFF linker member.
                        file.kind = ArchiveKind::Coff;
                        file.symbols = member.data(data)?;
                        file.offset = tail;

                        if tail < len {
                            let member = ArchiveMember::parse(data, &mut tail, &[])?;
                            if member.name == b"//" {
                                // COFF names table.
                                file.names = member.data(data)?;
                                file.offset = tail;
                            }
                        }
                    } else if member.name == b"//" {
                        // GNU names table.
                        file.names = member.data(data)?;
                        file.offset = tail;
                    }
                }
            } else if member.name == b"//" {
                // GNU names table.
                file.kind = ArchiveKind::Gnu;
                file.names = member.data(data)?;
                file.offset = tail;
            } else if member.name == b"__.SYMDEF" || member.name == b"__.SYMDEF SORTED" {
                // BSD symbol table.
                file.kind = ArchiveKind::Bsd;
                file.symbols = member.data(data)?;
                file.offset = tail;
            } else {
                // TODO: This could still be a BSD file. We leave this as unknown for now.
            }
        }
        Ok(file)
    }

    /// Return the archive format.
    #[inline]
    pub fn kind(&self) -> ArchiveKind {
        self.kind
    }

    /// Iterate over the members of the archive.
    ///
    /// This does not return special members.
    #[inline]
    pub fn members(&self) -> ArchiveMemberIterator<'data, R> {
        ArchiveMemberIterator {
            data: self.data,
            offset: self.offset,
            len: self.len,
            names: self.names,
        }
    }
}

/// An iterator over the members of an archive.
#[derive(Debug)]
pub struct ArchiveMemberIterator<'data, R: ReadRef<'data> = &'data [u8]> {
    data: R,
    offset: u64,
    len: u64,
    names: &'data [u8],
}

impl<'data, R: ReadRef<'data>> Iterator for ArchiveMemberIterator<'data, R> {
    type Item = read::Result<ArchiveMember<'data>>;

    fn next(&mut self) -> Option<Self::Item> {
        if self.offset >= self.len {
            return None;
        }
        let member = ArchiveMember::parse(self.data, &mut self.offset, self.names);
        if member.is_err() {
            self.offset = self.len;
        }
        Some(member)
    }
}

/// A partially parsed archive member.
#[derive(Debug)]
pub struct ArchiveMember<'data> {
    header: &'data archive::Header,
    name: &'data [u8],
    offset: u64,
    size: u64,
}

impl<'data> ArchiveMember<'data> {
    /// Parse the archive member header, name, and file data.
    ///
    /// This reads the extended name (if any) and adjusts the file size.
    fn parse<R: ReadRef<'data>>(
        data: R,
        offset: &mut u64,
        names: &'data [u8],
    ) -> read::Result<Self> {
        let header = data
            .read::<archive::Header>(offset)
            .read_error("Invalid archive member header")?;
        if header.terminator != archive::TERMINATOR {
            return Err(Error("Invalid archive terminator"));
        }

        let mut file_offset = *offset;
        let mut file_size =
            parse_u64_digits(&header.size, 10).read_error("Invalid archive member size")?;
        *offset = offset
            .checked_add(file_size)
            .read_error("Archive member size is too large")?;
        // Entries are padded to an even number of bytes.
        if (file_size & 1) != 0 {
            *offset = offset.saturating_add(1);
        }

        let name = if header.name[0] == b'/' && (header.name[1] as char).is_digit(10) {
            // Read file name from the names table.
            parse_sysv_extended_name(&header.name[1..], names)
                .read_error("Invalid archive extended name offset")?
        } else if &header.name[..3] == b"#1/" && (header.name[3] as char).is_digit(10) {
            // Read file name from the start of the file data.
            parse_bsd_extended_name(&header.name[3..], data, &mut file_offset, &mut file_size)
                .read_error("Invalid archive extended name length")?
        } else if header.name[0] == b'/' {
            let name_len = memchr::memchr(b' ', &header.name).unwrap_or(header.name.len());
            &header.name[..name_len]
        } else {
            let name_len = memchr::memchr(b'/', &header.name)
                .or_else(|| memchr::memchr(b' ', &header.name))
                .unwrap_or(header.name.len());
            &header.name[..name_len]
        };

        Ok(ArchiveMember {
            header,
            name,
            offset: file_offset,
            size: file_size,
        })
    }

    /// Return the raw header.
    #[inline]
    pub fn header(&self) -> &'data archive::Header {
        self.header
    }

    /// Return the parsed file name.
    ///
    /// This may be an extended file name.
    #[inline]
    pub fn name(&self) -> &'data [u8] {
        self.name
    }

    /// Parse the file modification timestamp from the header.
    #[inline]
    pub fn date(&self) -> Option<u64> {
        parse_u64_digits(&self.header.date, 10)
    }

    /// Parse the user ID from the header.
    #[inline]
    pub fn uid(&self) -> Option<u64> {
        parse_u64_digits(&self.header.uid, 10)
    }

    /// Parse the group ID from the header.
    #[inline]
    pub fn gid(&self) -> Option<u64> {
        parse_u64_digits(&self.header.gid, 10)
    }

    /// Parse the file mode from the header.
    #[inline]
    pub fn mode(&self) -> Option<u64> {
        parse_u64_digits(&self.header.mode, 8)
    }

    /// Return the offset and size of the file data.
    pub fn file_range(&self) -> (u64, u64) {
        (self.offset, self.size)
    }

    /// Return the file data.
    #[inline]
    pub fn data<R: ReadRef<'data>>(&self, data: R) -> read::Result<&'data [u8]> {
        data.read_bytes_at(self.offset, self.size)
            .read_error("Archive member size is too large")
    }
}

// Ignores bytes starting from the first space.
fn parse_u64_digits(digits: &[u8], radix: u32) -> Option<u64> {
    let len = memchr::memchr(b' ', digits).unwrap_or(digits.len());
    let digits = &digits[..len];
    if digits.is_empty() {
        return None;
    }
    let mut result: u64 = 0;
    for &c in digits {
        let x = (c as char).to_digit(radix)?;
        result = result
            .checked_mul(u64::from(radix))?
            .checked_add(u64::from(x))?;
    }
    Some(result)
}

fn parse_sysv_extended_name<'data>(digits: &[u8], names: &'data [u8]) -> Result<&'data [u8], ()> {
    let offset = parse_u64_digits(digits, 10).ok_or(())?;
    let offset = offset.try_into().map_err(|_| ())?;
    let name_data = names.get(offset..).ok_or(())?;
    let name = match memchr::memchr2(b'/', 0, name_data) {
        Some(len) => &name_data[..len],
        None => name_data,
    };
    Ok(name)
}

/// Modifies `data` to start after the extended name.
fn parse_bsd_extended_name<'data, R: ReadRef<'data>>(
    digits: &[u8],
    data: R,
    offset: &mut u64,
    size: &mut u64,
) -> Result<&'data [u8], ()> {
    let len = parse_u64_digits(digits, 10).ok_or(())?;
    *size = size.checked_sub(len).ok_or(())?;
    let name_data = data.read_bytes(offset, len)?;
    let name = match memchr::memchr(0, name_data) {
        Some(len) => &name_data[..len],
        None => name_data,
    };
    Ok(name)
}

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

    #[test]
    fn kind() {
        let data = b"!<arch>\n";
        let archive = ArchiveFile::parse(&data[..]).unwrap();
        assert_eq!(archive.kind(), ArchiveKind::Unknown);

        let data = b"\
            !<arch>\n\
            /                                               4         `\n\
            0000";
        let archive = ArchiveFile::parse(&data[..]).unwrap();
        assert_eq!(archive.kind(), ArchiveKind::Gnu);

        let data = b"\
            !<arch>\n\
            //                                              4         `\n\
            0000";
        let archive = ArchiveFile::parse(&data[..]).unwrap();
        assert_eq!(archive.kind(), ArchiveKind::Gnu);

        let data = b"\
            !<arch>\n\
            /                                               4         `\n\
            0000\
            //                                              4         `\n\
            0000";
        let archive = ArchiveFile::parse(&data[..]).unwrap();
        assert_eq!(archive.kind(), ArchiveKind::Gnu);

        let data = b"\
            !<arch>\n\
            __.SYMDEF                                       4         `\n\
            0000";
        let archive = ArchiveFile::parse(&data[..]).unwrap();
        assert_eq!(archive.kind(), ArchiveKind::Bsd);

        let data = b"\
            !<arch>\n\
            #1/9                                            13        `\n\
            __.SYMDEF0000";
        let archive = ArchiveFile::parse(&data[..]).unwrap();
        assert_eq!(archive.kind(), ArchiveKind::Bsd);

        let data = b"\
            !<arch>\n\
            #1/16                                           20        `\n\
            __.SYMDEF SORTED0000";
        let archive = ArchiveFile::parse(&data[..]).unwrap();
        assert_eq!(archive.kind(), ArchiveKind::Bsd);

        let data = b"\
            !<arch>\n\
            /                                               4         `\n\
            0000\
            /                                               4         `\n\
            0000\
            //                                              4         `\n\
            0000";
        let archive = ArchiveFile::parse(&data[..]).unwrap();
        assert_eq!(archive.kind(), ArchiveKind::Coff);
    }

    #[test]
    fn gnu_names() {
        let data = b"\
            !<arch>\n\
            //                                              18        `\n\
            0123456789abcdef/\n\
            0123456789abcde/0           0     0     644     3         `\n\
            odd\n\
            /0              0           0     0     644     4         `\n\
            even";
        let data = &data[..];
        let archive = ArchiveFile::parse(data).unwrap();
        assert_eq!(archive.kind(), ArchiveKind::Gnu);
        let mut members = archive.members();

        let member = members.next().unwrap().unwrap();
        assert_eq!(member.name(), b"0123456789abcde");
        assert_eq!(member.data(data).unwrap(), &b"odd"[..]);

        let member = members.next().unwrap().unwrap();
        assert_eq!(member.name(), b"0123456789abcdef");
        assert_eq!(member.data(data).unwrap(), &b"even"[..]);

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

    #[test]
    fn bsd_names() {
        let data = b"\
            !<arch>\n\
            0123456789abcde 0           0     0     644     3         `\n\
            odd\n\
            #1/16           0           0     0     644     20        `\n\
            0123456789abcdefeven";
        let data = &data[..];
        let archive = ArchiveFile::parse(data).unwrap();
        assert_eq!(archive.kind(), ArchiveKind::Unknown);
        let mut members = archive.members();

        let member = members.next().unwrap().unwrap();
        assert_eq!(member.name(), b"0123456789abcde");
        assert_eq!(member.data(data).unwrap(), &b"odd"[..]);

        let member = members.next().unwrap().unwrap();
        assert_eq!(member.name(), b"0123456789abcdef");
        assert_eq!(member.data(data).unwrap(), &b"even"[..]);

        assert!(members.next().is_none());
    }
}
<commit_msg>Further reduce the runtime of parse_u64_digits<commit_after>//! Support for archive files.

use core::convert::TryInto;

use crate::archive;
use crate::read::{self, Error, ReadError, ReadRef};

/// The kind of archive format.
// TODO: Gnu64 and Darwin64 (and Darwin for writing)
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ArchiveKind {
    /// There are no special files that indicate the archive format.
    Unknown,
    /// The GNU (or System V) archive format.
    Gnu,
    /// The BSD archive format.
    Bsd,
    /// The Windows COFF archive format.
    Coff,
}

/// A partially parsed archive file.
#[derive(Debug)]
pub struct ArchiveFile<'data, R: ReadRef<'data> = &'data [u8]> {
    data: R,
    len: u64,
    offset: u64,
    kind: ArchiveKind,
    symbols: &'data [u8],
    names: &'data [u8],
}

impl<'data, R: ReadRef<'data>> ArchiveFile<'data, R> {
    /// Parse the archive header and special members.
    pub fn parse(data: R) -> read::Result<Self> {
        let len = data.len().read_error("Unknown archive length")?;
        let mut tail = 0;
        let magic = data
            .read_bytes(&mut tail, archive::MAGIC.len() as u64)
            .read_error("Invalid archive size")?;
        if magic != &archive::MAGIC[..] {
            return Err(Error("Unsupported archive identifier"));
        }

        let mut file = ArchiveFile {
            data,
            offset: tail,
            len,
            kind: ArchiveKind::Unknown,
            symbols: &[],
            names: &[],
        };

        // The first few members may be special, so parse them.
        // GNU has:
        // - "/": symbol table (optional)
        // - "//": names table (optional)
        // COFF has:
        // - "/": first linker member
        // - "/": second linker member
        // - "//": names table
        // BSD has:
        // - "__.SYMDEF" or "__.SYMDEF SORTED": symbol table (optional)
        if tail < len {
            let member = ArchiveMember::parse(data, &mut tail, &[])?;
            if member.name == b"/" {
                // GNU symbol table (unless we later determine this is COFF).
                file.kind = ArchiveKind::Gnu;
                file.symbols = member.data(data)?;
                file.offset = tail;

                if tail < len {
                    let member = ArchiveMember::parse(data, &mut tail, &[])?;
                    if member.name == b"/" {
                        // COFF linker member.
                        file.kind = ArchiveKind::Coff;
                        file.symbols = member.data(data)?;
                        file.offset = tail;

                        if tail < len {
                            let member = ArchiveMember::parse(data, &mut tail, &[])?;
                            if member.name == b"//" {
                                // COFF names table.
                                file.names = member.data(data)?;
                                file.offset = tail;
                            }
                        }
                    } else if member.name == b"//" {
                        // GNU names table.
                        file.names = member.data(data)?;
                        file.offset = tail;
                    }
                }
            } else if member.name == b"//" {
                // GNU names table.
                file.kind = ArchiveKind::Gnu;
                file.names = member.data(data)?;
                file.offset = tail;
            } else if member.name == b"__.SYMDEF" || member.name == b"__.SYMDEF SORTED" {
                // BSD symbol table.
                file.kind = ArchiveKind::Bsd;
                file.symbols = member.data(data)?;
                file.offset = tail;
            } else {
                // TODO: This could still be a BSD file. We leave this as unknown for now.
            }
        }
        Ok(file)
    }

    /// Return the archive format.
    #[inline]
    pub fn kind(&self) -> ArchiveKind {
        self.kind
    }

    /// Iterate over the members of the archive.
    ///
    /// This does not return special members.
    #[inline]
    pub fn members(&self) -> ArchiveMemberIterator<'data, R> {
        ArchiveMemberIterator {
            data: self.data,
            offset: self.offset,
            len: self.len,
            names: self.names,
        }
    }
}

/// An iterator over the members of an archive.
#[derive(Debug)]
pub struct ArchiveMemberIterator<'data, R: ReadRef<'data> = &'data [u8]> {
    data: R,
    offset: u64,
    len: u64,
    names: &'data [u8],
}

impl<'data, R: ReadRef<'data>> Iterator for ArchiveMemberIterator<'data, R> {
    type Item = read::Result<ArchiveMember<'data>>;

    fn next(&mut self) -> Option<Self::Item> {
        if self.offset >= self.len {
            return None;
        }
        let member = ArchiveMember::parse(self.data, &mut self.offset, self.names);
        if member.is_err() {
            self.offset = self.len;
        }
        Some(member)
    }
}

/// A partially parsed archive member.
#[derive(Debug)]
pub struct ArchiveMember<'data> {
    header: &'data archive::Header,
    name: &'data [u8],
    offset: u64,
    size: u64,
}

impl<'data> ArchiveMember<'data> {
    /// Parse the archive member header, name, and file data.
    ///
    /// This reads the extended name (if any) and adjusts the file size.
    fn parse<R: ReadRef<'data>>(
        data: R,
        offset: &mut u64,
        names: &'data [u8],
    ) -> read::Result<Self> {
        let header = data
            .read::<archive::Header>(offset)
            .read_error("Invalid archive member header")?;
        if header.terminator != archive::TERMINATOR {
            return Err(Error("Invalid archive terminator"));
        }

        let mut file_offset = *offset;
        let mut file_size =
            parse_u64_digits(&header.size, 10).read_error("Invalid archive member size")?;
        *offset = offset
            .checked_add(file_size)
            .read_error("Archive member size is too large")?;
        // Entries are padded to an even number of bytes.
        if (file_size & 1) != 0 {
            *offset = offset.saturating_add(1);
        }

        let name = if header.name[0] == b'/' && (header.name[1] as char).is_digit(10) {
            // Read file name from the names table.
            parse_sysv_extended_name(&header.name[1..], names)
                .read_error("Invalid archive extended name offset")?
        } else if &header.name[..3] == b"#1/" && (header.name[3] as char).is_digit(10) {
            // Read file name from the start of the file data.
            parse_bsd_extended_name(&header.name[3..], data, &mut file_offset, &mut file_size)
                .read_error("Invalid archive extended name length")?
        } else if header.name[0] == b'/' {
            let name_len = memchr::memchr(b' ', &header.name).unwrap_or(header.name.len());
            &header.name[..name_len]
        } else {
            let name_len = memchr::memchr(b'/', &header.name)
                .or_else(|| memchr::memchr(b' ', &header.name))
                .unwrap_or(header.name.len());
            &header.name[..name_len]
        };

        Ok(ArchiveMember {
            header,
            name,
            offset: file_offset,
            size: file_size,
        })
    }

    /// Return the raw header.
    #[inline]
    pub fn header(&self) -> &'data archive::Header {
        self.header
    }

    /// Return the parsed file name.
    ///
    /// This may be an extended file name.
    #[inline]
    pub fn name(&self) -> &'data [u8] {
        self.name
    }

    /// Parse the file modification timestamp from the header.
    #[inline]
    pub fn date(&self) -> Option<u64> {
        parse_u64_digits(&self.header.date, 10)
    }

    /// Parse the user ID from the header.
    #[inline]
    pub fn uid(&self) -> Option<u64> {
        parse_u64_digits(&self.header.uid, 10)
    }

    /// Parse the group ID from the header.
    #[inline]
    pub fn gid(&self) -> Option<u64> {
        parse_u64_digits(&self.header.gid, 10)
    }

    /// Parse the file mode from the header.
    #[inline]
    pub fn mode(&self) -> Option<u64> {
        parse_u64_digits(&self.header.mode, 8)
    }

    /// Return the offset and size of the file data.
    pub fn file_range(&self) -> (u64, u64) {
        (self.offset, self.size)
    }

    /// Return the file data.
    #[inline]
    pub fn data<R: ReadRef<'data>>(&self, data: R) -> read::Result<&'data [u8]> {
        data.read_bytes_at(self.offset, self.size)
            .read_error("Archive member size is too large")
    }
}

// Ignores bytes starting from the first space.
fn parse_u64_digits(digits: &[u8], radix: u32) -> Option<u64> {
    if let [b' ', ..] = digits {
        return None;
    }
    let mut result: u64 = 0;
    for &c in digits {
        if c == b' ' {
            return Some(result);
        } else {
            let x = (c as char).to_digit(radix)?;
            result = result
                .checked_mul(u64::from(radix))?
                .checked_add(u64::from(x))?;
        }
    }
    Some(result)
}

fn parse_sysv_extended_name<'data>(digits: &[u8], names: &'data [u8]) -> Result<&'data [u8], ()> {
    let offset = parse_u64_digits(digits, 10).ok_or(())?;
    let offset = offset.try_into().map_err(|_| ())?;
    let name_data = names.get(offset..).ok_or(())?;
    let name = match memchr::memchr2(b'/', b'\0', name_data) {
        Some(len) => &name_data[..len],
        None => name_data,
    };
    Ok(name)
}

/// Modifies `data` to start after the extended name.
fn parse_bsd_extended_name<'data, R: ReadRef<'data>>(
    digits: &[u8],
    data: R,
    offset: &mut u64,
    size: &mut u64,
) -> Result<&'data [u8], ()> {
    let len = parse_u64_digits(digits, 10).ok_or(())?;
    *size = size.checked_sub(len).ok_or(())?;
    let name_data = data.read_bytes(offset, len)?;
    let name = match memchr::memchr(b'\0', name_data) {
        Some(len) => &name_data[..len],
        None => name_data,
    };
    Ok(name)
}

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

    #[test]
    fn kind() {
        let data = b"!<arch>\n";
        let archive = ArchiveFile::parse(&data[..]).unwrap();
        assert_eq!(archive.kind(), ArchiveKind::Unknown);

        let data = b"\
            !<arch>\n\
            /                                               4         `\n\
            0000";
        let archive = ArchiveFile::parse(&data[..]).unwrap();
        assert_eq!(archive.kind(), ArchiveKind::Gnu);

        let data = b"\
            !<arch>\n\
            //                                              4         `\n\
            0000";
        let archive = ArchiveFile::parse(&data[..]).unwrap();
        assert_eq!(archive.kind(), ArchiveKind::Gnu);

        let data = b"\
            !<arch>\n\
            /                                               4         `\n\
            0000\
            //                                              4         `\n\
            0000";
        let archive = ArchiveFile::parse(&data[..]).unwrap();
        assert_eq!(archive.kind(), ArchiveKind::Gnu);

        let data = b"\
            !<arch>\n\
            __.SYMDEF                                       4         `\n\
            0000";
        let archive = ArchiveFile::parse(&data[..]).unwrap();
        assert_eq!(archive.kind(), ArchiveKind::Bsd);

        let data = b"\
            !<arch>\n\
            #1/9                                            13        `\n\
            __.SYMDEF0000";
        let archive = ArchiveFile::parse(&data[..]).unwrap();
        assert_eq!(archive.kind(), ArchiveKind::Bsd);

        let data = b"\
            !<arch>\n\
            #1/16                                           20        `\n\
            __.SYMDEF SORTED0000";
        let archive = ArchiveFile::parse(&data[..]).unwrap();
        assert_eq!(archive.kind(), ArchiveKind::Bsd);

        let data = b"\
            !<arch>\n\
            /                                               4         `\n\
            0000\
            /                                               4         `\n\
            0000\
            //                                              4         `\n\
            0000";
        let archive = ArchiveFile::parse(&data[..]).unwrap();
        assert_eq!(archive.kind(), ArchiveKind::Coff);
    }

    #[test]
    fn gnu_names() {
        let data = b"\
            !<arch>\n\
            //                                              18        `\n\
            0123456789abcdef/\n\
            0123456789abcde/0           0     0     644     3         `\n\
            odd\n\
            /0              0           0     0     644     4         `\n\
            even";
        let data = &data[..];
        let archive = ArchiveFile::parse(data).unwrap();
        assert_eq!(archive.kind(), ArchiveKind::Gnu);
        let mut members = archive.members();

        let member = members.next().unwrap().unwrap();
        assert_eq!(member.name(), b"0123456789abcde");
        assert_eq!(member.data(data).unwrap(), &b"odd"[..]);

        let member = members.next().unwrap().unwrap();
        assert_eq!(member.name(), b"0123456789abcdef");
        assert_eq!(member.data(data).unwrap(), &b"even"[..]);

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

    #[test]
    fn bsd_names() {
        let data = b"\
            !<arch>\n\
            0123456789abcde 0           0     0     644     3         `\n\
            odd\n\
            #1/16           0           0     0     644     20        `\n\
            0123456789abcdefeven";
        let data = &data[..];
        let archive = ArchiveFile::parse(data).unwrap();
        assert_eq!(archive.kind(), ArchiveKind::Unknown);
        let mut members = archive.members();

        let member = members.next().unwrap().unwrap();
        assert_eq!(member.name(), b"0123456789abcde");
        assert_eq!(member.data(data).unwrap(), &b"odd"[..]);

        let member = members.next().unwrap().unwrap();
        assert_eq!(member.name(), b"0123456789abcdef");
        assert_eq!(member.data(data).unwrap(), &b"even"[..]);

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


<commit_before>//! Render combinatory blocks.
//!
//! Render blocks are logical rendering units that have inputs and outputs. Inputs can represent
//! vertex attributes, user-specified values, built-ins or previous blocks’ outputs.

use glsl::syntax::{ExternalDeclaration, TranslationUnit};
use std::iter::once;

use crate::render::input::{Input, inputs_to_struct_decl};
use crate::render::output::{Output, outputs_to_struct_decl};

/// A render block, allowing for combining blocks in order to create more complex rendering
/// computations.
#[derive(Clone, Debug)]
struct Block {
  /// Unique identifier of the block. Used to get mangled names for structures and functions.
  id: String,
  inputs: Vec<Input>,
  outputs: Vec<Output>,
  code: Vec<ExternalDeclaration>
}

impl Block {
  /// Create a new block out of inputs, outputs and GLSL code.
  pub fn new<S, I, O, C>(id: S, inputs: I, outputs: O, code: C) -> Self
  where S: Into<String>,
        I: IntoIterator<Item = Input>,
        O: IntoIterator<Item = Output>,
        C: IntoIterator<Item = ExternalDeclaration> {
    Block {
      id: id.into(),
      inputs: inputs.into_iter().collect(),
      outputs: outputs.into_iter().collect(),
      code: code.into_iter().collect()
    }
  }

  /// Turn a block into its GLSL header representation.
  ///
  /// The GLSL header contains the inputs and the outputs struct definitions.
  fn to_glsl_header(&self) -> Option<TranslationUnit> {
    // get the input and output GLSL representations
    let input_struct = inputs_to_struct_decl("In", &self.inputs);
    let output_struct = outputs_to_struct_decl("Out", &self.outputs);

    TranslationUnit::from_iter(once(input_struct).chain(once(output_struct)).flatten())
  }

  /// Turn a block into its GLSL full representation.
  ///
  /// The full GLSL representation includes:
  ///
  ///   - All intermediates structures, constants and functions.
  ///   - The *input* and *output* structures, renamed to:
  ///     - `In` becomes `In_<blockid>`. For instance, the block with ID `"Blur"` has its input type
  ///        going from `In` to `In_Blur`.
  ///     - `Out` becomes `Out_<blockid>`. For instance, the block with ID `"Blur"` has its output
  ///        type going from `Out` to `Out_Blur`.
  ///     - The function named `call` is searched for in the *code* part of the block and renamed
  ///       `call_<blockid>`. For instance, the block with ID `"Blur"` has its `call` function
  ///       renamed `call_Blur`.
  ///   - All references to `In`, `Out` and `call` are replaced with the appropriate new name.
  fn to_glsl(&self) -> Vec<ExternalDeclaration> {
    unimplemented!()
  }
}

#[cfg(test)]
mod tests {
  use glsl_quasiquote::glsl;

  use crate::render::types::*;
  use super::*;

  #[test]
  fn block_to_glsl_header() {
    use crate::render::input::Input;
    use crate::render::output::Output;

    let inputs = vec![Input::new::<Float, _>("time"), Input::new::<RGBAU, _>("bias")];
    let outputs = vec![Output::new::<RGBF, _>("color")];
    let code = vec![];
    let block = Block::new("simple", inputs, outputs, code);
    let expected = glsl!{
      struct In {
        float time;
        uvec4 bias;
      };

      struct Out {
        vec3 color;
      };
    };

    assert_eq!(block.to_glsl_header(), Some(expected));
  }
}
<commit_msg>Fully implement Block::to_glsl.<commit_after>//! Render combinatory blocks.
//!
//! Render blocks are logical rendering units that have inputs and outputs. Inputs can represent
//! vertex attributes, user-specified values, built-ins or previous blocks’ outputs.

use glsl::syntax::{ExternalDeclaration, Identifier, TranslationUnit, TypeName};
use glsl::visitor::{Host, Visit, Visitor};
use std::iter::once;

use crate::render::input::{Input, inputs_to_struct_decl};
use crate::render::output::{Output, outputs_to_struct_decl};

/// A render block, allowing for combining blocks in order to create more complex rendering
/// computations.
#[derive(Clone, Debug)]
struct Block {
  /// Unique identifier of the block. Used to get mangled names for structures and functions.
  id: String,
  /// List of inputs that compose the public interface of the block.
  inputs: Vec<Input>,
  /// List of outputs that compose the public interface of the block.
  outputs: Vec<Output>,
  /// Actual transformation code of the block.
  code: TranslationUnit
}

impl Block {
  /// Create a new block out of inputs, outputs and GLSL code.
  pub fn new<S, I, O, C>(id: S, inputs: I, outputs: O, code: C) -> Self
  where S: Into<String>,
        I: IntoIterator<Item = Input>,
        O: IntoIterator<Item = Output>,
        C: Into<TranslationUnit> {
    Block {
      id: id.into(),
      inputs: inputs.into_iter().collect(),
      outputs: outputs.into_iter().collect(),
      code: code.into()
    }
  }

  /// Turn a block into its GLSL header representation.
  ///
  /// The GLSL header contains the inputs and the outputs struct definitions.
  fn to_glsl_header(&self) -> Option<TranslationUnit> {
    // get the input and output GLSL representations
    let input_struct = inputs_to_struct_decl("In", &self.inputs);
    let output_struct = outputs_to_struct_decl("Out", &self.outputs);

    TranslationUnit::from_iter(once(input_struct).chain(once(output_struct)).flatten())
  }

  /// Turn a block into its GLSL full representation.
  ///
  /// The full GLSL representation includes:
  ///
  ///   - All intermediates structures, constants and functions.
  ///   - The *input* and *output* structures, renamed to:
  ///     - `In` becomes `In_<blockid>`. For instance, the block with ID `"Blur"` has its input type
  ///        going from `In` to `In_Blur`.
  ///     - `Out` becomes `Out_<blockid>`. For instance, the block with ID `"Blur"` has its output
  ///        type going from `Out` to `Out_Blur`.
  ///     - The function named `call` is searched for in the *code* part of the block and renamed
  ///       `call_<blockid>`. For instance, the block with ID `"Blur"` has its `call` function
  ///       renamed `call_Blur`.
  ///   - All references to `In`, `Out` and `call` are replaced with the appropriate new name.
  fn to_glsl(&self) -> Option<TranslationUnit> {
    // generate the header
    let mut ast = self.to_glsl_header()?;

    // append the actual code
    ast.extend((&self.code).into_iter().cloned());

    // transform the AST to mangle In, Out and call()
    let mut mangler = Mangler::new(self.id.as_str());
    ast.visit(&mut mangler);

    Some(ast)
  }
}

/// AST visitor that mangles type names and function calls.
///
/// This works but appending a unique ID to names to replace. Replacements occur for:
///
///   - All intermediates structures, constants and functions, that reference `In`, `Out` or `call`.
///   - The *input* and *output* structures, renamed as:
///     - `In` becomes `In_<blockid>`. For instance, the block with ID `"Blur"` has its input type
///        going from `In` to `In_Blur`.
///     - `Out` becomes `Out_<blockid>`. For instance, the block with ID `"Blur"` has its output
///        type going from `Out` to `Out_Blur`.
///     - The function named `call` is searched for in the *code* part of the block and renamed
///       `call_<blockid>`. For instance, the block with ID `"Blur"` has its `call` function
///       renamed `call_Blur`.
///   - All references to `In`, `Out` and `call` are replaced with the appropriate new name.
struct Mangler<'a> {
  id: &'a str
}

impl<'a> Mangler<'a> {
  fn new(id: &'a str) -> Self {
    Mangler { id }
  }
}

impl<'a> Visitor for Mangler<'a> {
  fn visit_type_name(&mut self, type_name: &mut TypeName) -> Visit {
    match type_name.as_str() {
      "In" => *type_name = TypeName::new(format!("In_{}", self.id)).unwrap(),
      "Out" => *type_name = TypeName::new(format!("Out_{}", self.id)).unwrap(),
      _ => ()
    }

    Visit::Parent
  }

  fn visit_identifier(&mut self, identifier: &mut Identifier) -> Visit {
    match identifier.as_str() {
      "In" => *identifier = Identifier::new(format!("In_{}", self.id)).unwrap(),
      "Out" => *identifier = Identifier::new(format!("Out_{}", self.id)).unwrap(),
      "call" => *identifier = Identifier::new(format!("call_{}", self.id)).unwrap(),
      _ => ()
    }

    Visit::Parent
  }
}

#[cfg(test)]
mod tests {
  use glsl_quasiquote::glsl;

  use crate::render::types::*;
  use super::*;

  #[test]
  fn block_to_glsl_header() {
    use crate::render::input::Input;
    use crate::render::output::Output;

    let inputs = vec![Input::new::<Float, _>("time"), Input::new::<RGBAU, _>("bias")];
    let outputs = vec![Output::new::<RGBF, _>("color")];
    let code = glsl!{ void main() {} };
    let block = Block::new("simple", inputs, outputs, code);
    let expected = glsl!{
      struct In {
        float time;
        uvec4 bias;
      };

      struct Out {
        vec3 color;
      };
    };

    assert_eq!(block.to_glsl_header(), Some(expected));
  }

  #[test]
  fn block_to_glsl() {
    use crate::render::input::Input;
    use crate::render::output::Output;

    let inputs = vec![Input::new::<Float, _>("time"), Input::new::<RGBAF, _>("bias")];
    let outputs = vec![Output::new::<RGBF, _>("color")];

    let code = glsl!{
      Out call(In x) {
        return Out(x.bias.xyz * x.time);
      }
    };

    let block = Block::new("simple", inputs, outputs, code);

    let expected = glsl!{
      struct In_simple {
        float time;
        vec4 bias;
      };

      struct Out_simple {
        vec3 color;
      };

      Out_simple call_simple(In_simple x) {
        return Out_simple(x.bias.xyz * x.time);
      }
    };

    assert_eq!(block.to_glsl(), Some(expected));
  }
}
<|endoftext|>


<commit_before>
use token::{Token, TokenKind};
use error::{NcclError, ErrorKind};

use std::error::Error;

// ranked worst to best
enum Indent {
    Neither,
    Tabs,
    Spaces(u8),
}

pub struct Scanner {
    source: Vec<u8>,
    tokens: Vec<Token>,
    start: usize,
    current: usize,
    line: u64,
    indent: Indent,
}

impl Scanner {
    pub fn new(source: String) -> Self {
        Scanner {
            source: source.into_bytes(),
            tokens: Vec::new(),
            start: 0,
            current: 0,
            line: 1,
            indent: Indent::Neither,
        }
    }

    pub fn scan_tokens(&mut self) -> Result<Vec<Token>, Vec<Box<Error>>> {
        let mut err: Vec<Box<Error>> = vec![];

        while !self.is_at_end() {
            self.start = self.current;
            let e = self.scan_token();
            if e.is_err() {
                err.push(Box::new(e.err().unwrap()));
            }
        }

        self.tokens.push(Token::new(TokenKind::EOF, "".into(), self.line));

        if !err.is_empty() {
            Err(err)
        } else {
            Ok(self.tokens.clone())
        }
    }

    fn scan_token(&mut self) -> Result<(), NcclError> {
        let mut error = Ok(());
        match self.advance() {
            b'#' => {
                while self.peek() != b'\n' && !self.is_at_end() {
                    self.advance();
                }
            },

            b' ' => {
                match self.indent {
                    Indent::Neither => {
                        let mut spaces = 0;
                        while self.peek() == b' ' && !self.is_at_end() {
                            self.advance();
                            spaces += 1;
                        }
                        if self.is_at_end() {
                            error = Err(NcclError::new(ErrorKind::ParseError, "Expected value, found EOF", self.line));
                        }
                        self.indent = Indent::Spaces(spaces);
                        self.add_token(TokenKind::Indent);
                    }
                    Indent::Spaces(s) => {
                        let mut spaces = 0;
                        while spaces < s  && !self.is_at_end() {
                            if self.peek() != b' ' {
                                error = Err(NcclError::new(ErrorKind::IndentationError, &format!("Incorrect number of spaces: found {}, expected {}", spaces, s), self.line));
                            }
                            self.advance();
                            spaces += 1;
                        }
                        if self.is_at_end() {
                            error = Err(NcclError::new(ErrorKind::ParseError, "Expected value, found EOF", self.line));
                        }
                        self.add_token(TokenKind::Indent);
                    },
                    Indent::Tabs => { error = Err(NcclError::new(ErrorKind::IndentationError, "Expected tabs, found spaces", self.line)); }
                }
            },

            b'\t' => {
                match self.indent {
                    Indent::Neither => {
                        self.add_token(TokenKind::Indent);
                        self.indent = Indent::Tabs;
                    },
                    Indent::Tabs => {
                        self.add_token(TokenKind::Indent);
                    },
                    Indent::Spaces(_) => { error = Err(NcclError::new(ErrorKind::IndentationError, "Expected spaces, found tabs", self.line)); }
                }
            },

            b'\n' => {
                self.add_token(TokenKind::Newline);
                self.line += 1;
            },

            b'\r' => {},

            b'"' => if let Err(e) = self.string() { error = Err(e); },

            _ => if let Err(e) = self.identifier() { error = Err(e); },
        }

        error
    }

    fn identifier(&mut self) -> Result<(), NcclError> {
        while self.peek() != b'\n' && self.peek() != b'\r' && !self.is_at_end() {
            self.advance();
        }

        let value = String::from_utf8(self.source[self.start..self.current].to_vec()).unwrap();
        self.add_token_string(TokenKind::Value, value);

        Ok(())
    }

    fn string(&mut self) -> Result<(), NcclError> {
        let mut value = String::new();
        while self.peek() != b'"' && !self.is_at_end() {
            if self.peek() == b'\n' {
                self.line += 1;
            }

            if self.peek() == b'\\' {
                self.advance();
                match self.peek() {
                    b'n' => {
                        value.push('\n');
                    },
                    b'r' => {
                        value.push('\r');
                    }
                    b'\\' => {
                        value.push('\\');
                    },
                    b'"' => {
                        value.push('"');
                    },
                    _ => return Err(NcclError::new(ErrorKind::ParseError, &format!("Unknown format code: {}", self.peek()), self.line))
                }
            } else {
                value.push(self.source[self.current] as char);
            }

            self.advance();
        }

        if self.is_at_end() {
            return Err(NcclError::new(ErrorKind::ParseError, "Unterminated string", self.line));
        }

        self.advance();

        self.add_token_string(TokenKind::Value, value);

        Ok(())
    }

    fn is_at_end(&mut self) -> bool {
        self.current >= self.source.len()
    }

    fn advance(&mut self) -> u8 {
        self.current += 1;
        self.source[self.current - 1]
    }

    fn add_token(&mut self, kind: TokenKind) {
        let text = String::from_utf8(self.source[self.start..self.current].to_vec()).unwrap();
        self.tokens.push(Token::new(kind, text, self.line));
    }

    fn add_token_string(&mut self, kind: TokenKind, value: String) {
        self.tokens.push(Token::new(kind, value, self.line));
    }

    fn peek(&mut self) -> u8 {
        if self.current >= self.source.len() {
            b'\0'
        } else {
            self.source[self.current]
        }
    }
}

<commit_msg>allow different indent types for separate top-level keys<commit_after>
use token::{Token, TokenKind};
use error::{NcclError, ErrorKind};

use std::error::Error;

// ranked worst to best
enum Indent {
    Neither,
    Tabs,
    Spaces(u8),
}

pub struct Scanner {
    source: Vec<u8>,
    tokens: Vec<Token>,
    start: usize,
    current: usize,
    line: u64,
    indent: Indent,
}

impl Scanner {
    pub fn new(source: String) -> Self {
        Scanner {
            source: source.into_bytes(),
            tokens: Vec::new(),
            start: 0,
            current: 0,
            line: 1,
            indent: Indent::Neither,
        }
    }

    pub fn scan_tokens(&mut self) -> Result<Vec<Token>, Vec<Box<Error>>> {
        let mut err: Vec<Box<Error>> = vec![];

        while !self.is_at_end() {
            self.start = self.current;
            let e = self.scan_token();
            if e.is_err() {
                err.push(Box::new(e.err().unwrap()));
            }
        }

        self.tokens.push(Token::new(TokenKind::EOF, "".into(), self.line));

        if !err.is_empty() {
            Err(err)
        } else {
            Ok(self.tokens.clone())
        }
    }

    fn scan_token(&mut self) -> Result<(), NcclError> {
        let mut error = Ok(());
        match self.advance() {
            b'#' => {
                while self.peek() != b'\n' && !self.is_at_end() {
                    self.advance();
                }
            },

            b' ' => {
                match self.indent {
                    Indent::Neither => {
                        let mut spaces = 0;
                        while self.peek() == b' ' && !self.is_at_end() {
                            self.advance();
                            spaces += 1;
                        }
                        if self.is_at_end() {
                            error = Err(NcclError::new(ErrorKind::ParseError, "Expected value, found EOF", self.line));
                        }
                        self.indent = Indent::Spaces(spaces);
                        self.add_token(TokenKind::Indent);
                    }
                    Indent::Spaces(s) => {
                        let mut spaces = 0;
                        while spaces < s  && !self.is_at_end() {
                            if self.peek() != b' ' {
                                error = Err(NcclError::new(ErrorKind::IndentationError, &format!("Incorrect number of spaces: found {}, expected {}", spaces, s), self.line));
                            }
                            self.advance();
                            spaces += 1;
                        }
                        if self.is_at_end() {
                            error = Err(NcclError::new(ErrorKind::ParseError, "Expected value, found EOF", self.line));
                        }
                        self.add_token(TokenKind::Indent);
                    },
                    Indent::Tabs => { error = Err(NcclError::new(ErrorKind::IndentationError, "Expected tabs, found spaces", self.line)); }
                }
            },

            b'\t' => {
                match self.indent {
                    Indent::Neither => {
                        self.add_token(TokenKind::Indent);
                        self.indent = Indent::Tabs;
                    },
                    Indent::Tabs => {
                        self.add_token(TokenKind::Indent);
                    },
                    Indent::Spaces(_) => { error = Err(NcclError::new(ErrorKind::IndentationError, "Expected spaces, found tabs", self.line)); }
                }
            },

            b'\n' => {
                self.add_token(TokenKind::Newline);
                self.line += 1;
                if self.peek() != b' ' && self.peek() != b'\t' && self.peek() != b'#' {
                    self.indent = Indent::Neither;
                }
            },

            b'\r' => {},

            b'"' => if let Err(e) = self.string() { error = Err(e); },

            _ => if let Err(e) = self.identifier() { error = Err(e); },
        }

        error
    }

    fn identifier(&mut self) -> Result<(), NcclError> {
        while self.peek() != b'\n' && self.peek() != b'\r' && !self.is_at_end() {
            self.advance();
        }

        let value = String::from_utf8(self.source[self.start..self.current].to_vec()).unwrap();
        self.add_token_string(TokenKind::Value, value);

        Ok(())
    }

    fn string(&mut self) -> Result<(), NcclError> {
        let mut value = String::new();
        while self.peek() != b'"' && !self.is_at_end() {
            if self.peek() == b'\n' {
                self.line += 1;
            }

            if self.peek() == b'\\' {
                self.advance();
                match self.peek() {
                    b'n' => {
                        value.push('\n');
                    },
                    b'r' => {
                        value.push('\r');
                    }
                    b'\\' => {
                        value.push('\\');
                    },
                    b'"' => {
                        value.push('"');
                    },
                    _ => return Err(NcclError::new(ErrorKind::ParseError, &format!("Unknown format code: {}", self.peek()), self.line))
                }
            } else {
                value.push(self.source[self.current] as char);
            }

            self.advance();
        }

        if self.is_at_end() {
            return Err(NcclError::new(ErrorKind::ParseError, "Unterminated string", self.line));
        }

        self.advance();

        self.add_token_string(TokenKind::Value, value);

        Ok(())
    }

    fn is_at_end(&mut self) -> bool {
        self.current >= self.source.len()
    }

    fn advance(&mut self) -> u8 {
        self.current += 1;
        self.source[self.current - 1]
    }

    fn add_token(&mut self, kind: TokenKind) {
        let text = String::from_utf8(self.source[self.start..self.current].to_vec()).unwrap();
        self.tokens.push(Token::new(kind, text, self.line));
    }

    fn add_token_string(&mut self, kind: TokenKind, value: String) {
        self.tokens.push(Token::new(kind, value, self.line));
    }

    fn peek(&mut self) -> u8 {
        if self.current >= self.source.len() {
            b'\0'
        } else {
            self.source[self.current]
        }
    }
}

<|endoftext|>