<commit_before>#![cfg_attr(unix, feature(fs_ext, path_ext))]

//! xdg-rs is a utility library to make conforming to the [XDG specification](http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html) easier.
//!
//! Some code borrowed from [rust-xdg](https://github.com/o11c/rust-xdg). ```rust-xdg``` is
//! currently a more complete implementation of the specification. The APIs provided by
//! ```rust-xdg``` and ```xdg-rs``` are different.

use std::env::{self, home_dir, split_paths};
use std::error::Error;
use std::ffi::OsString;
use std::fs::PathExt;
use std::os::unix::fs::PermissionsExt;
use std::path::PathBuf;

/// Get the data home directory given a closure that returns the the value of an environment variable.
/// This method allows having a custom environment.
///
/// If ```$XDG_DATA_HOME``` is not set, it returns ```$HOME/.local/share```.
pub fn get_data_home_from_env<F>(get_env_var: &F) -> PathBuf
    where F: Fn(&str) -> Option<OsString>
{
    getenv_path(get_env_var, "XDG_DATA_HOME")
        .unwrap_or(home_dir().unwrap().join(".local/share"))
}

/// Get the data home directory.
///
/// If ```$XDG_DATA_HOME``` is not set, it returns ```$HOME/.local/share```.
pub fn get_data_home() -> PathBuf {
    get_data_home_from_env(&env::var_os)
}

/// Get the default data directories given a closure that returns the the value of an environment variable.
/// This method allows having a custom environment.
///
/// If ```$XDG_DATA_DIRS``` is not set, it returns ```[/usr/local/share, /usr/share]```.
pub fn get_data_dirs_from_env<F>(get_env_var: &F) -> Vec<PathBuf>
    where F: Fn(&str) -> Option<OsString>
{
    let default_paths = OsString::from("/usr/local/share:/usr/share");

    let paths = match (*get_env_var)("XDG_DATA_DIRS").iter().next() {
        Some(p) if p != "" => p.clone(),
        _ => default_paths
    };

    split_paths(&paths).collect()
}

/// Get the data directories.
///
/// If ```$XDG_DATA_DIRS``` is not set, it returns ```[/usr/local/share, /usr/share]```.
pub fn get_data_dirs() -> Vec<PathBuf> {
    get_data_dirs_from_env(&env::var_os)
}

/// Get the config home directory given a closure that returns the the value of an environment variable.
/// This method allows having a custom environment.
///
/// If ```$XDG_CONFIG_HOME``` is not set, it returns ```$HOME/.config```.
pub fn get_config_home_from_env<F>(get_env_var: &F) -> PathBuf
    where F: Fn(&str) -> Option<OsString>
{
    getenv_path(get_env_var, "XDG_CONFIG_HOME")
        .unwrap_or(home_dir().unwrap().join(".config"))
}
/// Get the config home directory.
///
/// If ```$XDG_CONFIG_HOME``` is not set, it returns ```$HOME/.config```.
pub fn get_config_home() -> PathBuf {
    get_config_home_from_env(&env::var_os)
}

/// Get the default config directories given a closure that returns the the value of an environment variable.
/// This method allows having a custom environment.
///
/// If ```$XDG_CONFIG_DIRS``` is not set, it returns ```[/etc/xdg]```.
pub fn get_config_dirs_from_env<F>(get_env_var: &F) -> Vec<PathBuf>
    where F: Fn(&str) -> Option<OsString>
{
    let default_paths = OsString::from("/etc/xdg");

    let paths = match (*get_env_var)("XDG_CONFIG_DIRS").iter().next() {
        Some(p) if p != "" => p.clone(),
        _ => default_paths
    };

    split_paths(&paths).collect()
}

/// Get the config directories.
///
/// If ```$XDG_CONFIG_DIRS``` is not set, it returns ```[/etc/xdg]```.
pub fn get_config_dirs() -> Vec<PathBuf> {
    get_config_dirs_from_env(&env::var_os)
}

/// Get the cache home directory given a closure that returns the the value of an environment variable.
/// This method allows having a custom environment.
///
/// If ```$XDG_CACHE_HOME``` is not set, it returns ```$HOME/.cache```.
pub fn get_cache_home_from_env<F>(get_env_var: &F) -> PathBuf
    where F: Fn(&str) -> Option<OsString>
{
    getenv_path(get_env_var, "XDG_CACHE_HOME")
        .unwrap_or(home_dir().unwrap().join(".cache"))
}

/// Get the cache home directory.
///
/// If ```$XDG_CACHE_HOME``` is not set, it returns ```$HOME/.cache```.
pub fn get_cache_home() -> PathBuf {
    get_cache_home_from_env(&env::var_os)
}

/// Get $XDG_RUNTIME_DIR if found in the environment.
///
/// Returns None if ```$XDG_RUNTIME_PATH``` is not set, in which case it is up to the application.
/// to fallback to a location that conforms to the specification.
pub fn get_runtime_dir_from_env<F>(get_env_var: &F) -> Option<PathBuf>
    where F: Fn(&str) -> Option<OsString>
{
    getenv_path(get_env_var, "XDG_RUNTIME_DIR")
}

pub fn get_runtime_dir() -> Option<PathBuf> {
    getenv_path(&env::var_os, "XDG_RUNTIME_DIR")
}

/// Check that the value set for ```$XDG_RUNTIME_DIR``` meets the requirements of the specification.
///
/// Returns Ok(true) if permissions are correct, Ok(false) if permissions are incorrect, or propogates any errors that occurred while checking permissions.
///
/// >$XDG_RUNTIME_DIR defines the base directory relative to which user-specific non-essential runtime files and other file objects (such as sockets, named pipes, ...) should be stored. The directory MUST be owned by the user, and he MUST be the only one having read and write access to it. Its Unix access mode MUST be 0700.
/// >
/// >The lifetime of the directory MUST be bound to the user being logged in. It MUST be created when the user first logs in and if the user fully logs out the directory MUST be removed. If the user logs in more than once he should get pointed to the same directory, and it is mandatory that the directory continues to exist from his first login to his last logout on the system, and not removed in between. Files in the directory MUST not survive reboot or a full logout/login cycle.
/// >
/// >The directory MUST be on a local file system and not shared with any other system. The directory MUST by fully-featured by the standards of the operating system. More specifically, on Unix-like operating systems AF_UNIX sockets, symbolic links, hard links, proper permissions, file locking, sparse files, memory mapping, file change notifications, a reliable hard link count must be supported, and no restrictions on the file name character set should be imposed. Files in this directory MAY be subjected to periodic clean-up. To ensure that your files are not removed, they should have their access time timestamp modified at least once every 6 hours of monotonic time or the 'sticky' bit should be set on the file.
#[cfg(unix)]
pub fn test_runtime_dir(path: &PathBuf) -> Result<bool, std::io::Error> {
    path.metadata().map(|stat| (stat.permissions().mode() == 0o700))
}

#[cfg(not(unix))]
pub fn test_runtime_dir(path: &PathBuf) -> Result<bool, std::io::Error> {
    Ok(true)
}

/// Get an environment variable's value as a PathBuf.
fn getenv_path<F>(get_env_var: &F, env_var: &str) -> Option<PathBuf>
    where F: Fn(&str) -> Option<OsString>
{
    let path = (*get_env_var)(env_var);
    let path = path.map(PathBuf::from);
    match path.iter().next() {
        Some(p) if p.is_absolute() => Some(p.clone()),
        _ => None
    }
}

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

    use std::collections::HashMap;
    use std::env::{self, home_dir, join_paths, split_paths};
    use std::ffi::OsString;
    use std::path::PathBuf;

    #[test]
    fn test_env_with_no_xdg_vars() {
        let mut custom_env = HashMap::new();
        custom_env.insert("dummy", "");

        let f = |var: &str| { custom_env.get(var).map(OsString::from) };

        assert!(get_data_home_from_env(&f)   == home_dir().unwrap().join(".local/share"));
        assert!(get_data_dirs_from_env(&f)   == vec![PathBuf::from("/usr/local/share"), PathBuf::from("/usr/share")]);
        assert!(get_config_home_from_env(&f) == home_dir().unwrap().join(".config"));
        assert!(get_config_dirs_from_env(&f) == vec![PathBuf::from("/etc/xdg")]);
        assert!(get_cache_home_from_env(&f)  == home_dir().unwrap().join(".cache"));
        assert!(get_runtime_dir_from_env(&f) == None);
    }

    #[test]
    fn test_env_with_empty_xdg_vars() {
        let mut custom_env = HashMap::new();
        custom_env.insert("XDG_DATA_HOME", "");
        custom_env.insert("XDG_DATA_DIRS", "");
        custom_env.insert("XDG_CONFIG_HOME", "");
        custom_env.insert("XDG_CONFIG_DIRS", "");
        custom_env.insert("XDG_CACHE_HOME", "");

        let f = |var: &str| { custom_env.get(var).map(OsString::from) };

        assert!(get_data_home_from_env(&f)   == home_dir().unwrap().join(".local/share"));
        assert!(get_data_dirs_from_env(&f)   == vec![PathBuf::from("/usr/local/share"), PathBuf::from("/usr/share")]);
        assert!(get_config_home_from_env(&f) == home_dir().unwrap().join(".config"));
        assert!(get_config_dirs_from_env(&f) == vec![PathBuf::from("/etc/xdg")]);
        assert!(get_cache_home_from_env(&f)  == home_dir().unwrap().join(".cache"));
        assert!(get_runtime_dir_from_env(&f) == None);
    }

    #[test]
    fn test_env_with_xdg_vars() {
        let cwd = PathBuf::from(&env::current_dir().unwrap());
        let mut custom_env = HashMap::new();

        custom_env.insert("XDG_DATA_HOME", cwd.join("user/data").into_os_string());
        custom_env.insert("XDG_DATA_DIRS", join_paths(vec![cwd.join("share/data"), cwd.join("local/data")]).unwrap());
        custom_env.insert("XDG_CONFIG_HOME", cwd.join("user/config").into_os_string());
        custom_env.insert("XDG_CONFIG_DIRS", join_paths(vec![cwd.join("config"), cwd.join("local/config")]).unwrap());
        custom_env.insert("XDG_CACHE_HOME", cwd.join("user/cache").into_os_string());

        let f = |var: &str| { custom_env.get(var).map(OsString::from) };

        assert!(get_data_home_from_env(&f)   == custom_env.get("XDG_DATA_HOME").map(PathBuf::from).unwrap());
        assert!(get_data_dirs_from_env(&f)   == split_paths(&custom_env["XDG_DATA_DIRS"]).collect::<Vec<PathBuf>>());
        assert!(get_config_home_from_env(&f) == custom_env.get("XDG_CONFIG_HOME").map(PathBuf::from).unwrap());
        assert!(get_config_dirs_from_env(&f) == split_paths(&custom_env["XDG_CONFIG_DIRS"]).collect::<Vec<PathBuf>>());
        assert!(get_cache_home_from_env(&f)  == custom_env.get("XDG_CACHE_HOME").map(PathBuf::from).unwrap());
    }
}
<commit_msg>[breaking-change] change test_runtime_dir signature to take AsRef<Path><commit_after>#![cfg_attr(unix, feature(fs_ext))]

//! xdg-rs is a utility library to make conforming to the [XDG specification](http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html) easier.
//!
//! Some code borrowed from [rust-xdg](https://github.com/o11c/rust-xdg). ```rust-xdg``` is
//! currently a more complete implementation of the specification. The APIs provided by
//! ```rust-xdg``` and ```xdg-rs``` are different.

use std::env::{self, home_dir, split_paths};
use std::error::Error;
use std::ffi::OsString;
use std::fs;
use std::path::{Path, PathBuf};

#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;

/// Get the data home directory given a closure that returns the the value of an environment variable.
/// This method allows having a custom environment.
///
/// If ```$XDG_DATA_HOME``` is not set, it returns ```$HOME/.local/share```.
pub fn get_data_home_from_env<F>(get_env_var: &F) -> PathBuf
    where F: Fn(&str) -> Option<OsString>
{
    getenv_path(get_env_var, "XDG_DATA_HOME")
        .unwrap_or(home_dir().unwrap().join(".local/share"))
}

/// Get the data home directory.
///
/// If ```$XDG_DATA_HOME``` is not set, it returns ```$HOME/.local/share```.
pub fn get_data_home() -> PathBuf {
    get_data_home_from_env(&env::var_os)
}

/// Get the default data directories given a closure that returns the the value of an environment variable.
/// This method allows having a custom environment.
///
/// If ```$XDG_DATA_DIRS``` is not set, it returns ```[/usr/local/share, /usr/share]```.
pub fn get_data_dirs_from_env<F>(get_env_var: &F) -> Vec<PathBuf>
    where F: Fn(&str) -> Option<OsString>
{
    let default_paths = OsString::from("/usr/local/share:/usr/share");

    let paths = match (*get_env_var)("XDG_DATA_DIRS").iter().next() {
        Some(p) if p != "" => p.clone(),
        _ => default_paths
    };

    split_paths(&paths).collect()
}

/// Get the data directories.
///
/// If ```$XDG_DATA_DIRS``` is not set, it returns ```[/usr/local/share, /usr/share]```.
pub fn get_data_dirs() -> Vec<PathBuf> {
    get_data_dirs_from_env(&env::var_os)
}

/// Get the config home directory given a closure that returns the the value of an environment variable.
/// This method allows having a custom environment.
///
/// If ```$XDG_CONFIG_HOME``` is not set, it returns ```$HOME/.config```.
pub fn get_config_home_from_env<F>(get_env_var: &F) -> PathBuf
    where F: Fn(&str) -> Option<OsString>
{
    getenv_path(get_env_var, "XDG_CONFIG_HOME")
        .unwrap_or(home_dir().unwrap().join(".config"))
}
/// Get the config home directory.
///
/// If ```$XDG_CONFIG_HOME``` is not set, it returns ```$HOME/.config```.
pub fn get_config_home() -> PathBuf {
    get_config_home_from_env(&env::var_os)
}

/// Get the default config directories given a closure that returns the the value of an environment variable.
/// This method allows having a custom environment.
///
/// If ```$XDG_CONFIG_DIRS``` is not set, it returns ```[/etc/xdg]```.
pub fn get_config_dirs_from_env<F>(get_env_var: &F) -> Vec<PathBuf>
    where F: Fn(&str) -> Option<OsString>
{
    let default_paths = OsString::from("/etc/xdg");

    let paths = match (*get_env_var)("XDG_CONFIG_DIRS").iter().next() {
        Some(p) if p != "" => p.clone(),
        _ => default_paths
    };

    split_paths(&paths).collect()
}

/// Get the config directories.
///
/// If ```$XDG_CONFIG_DIRS``` is not set, it returns ```[/etc/xdg]```.
pub fn get_config_dirs() -> Vec<PathBuf> {
    get_config_dirs_from_env(&env::var_os)
}

/// Get the cache home directory given a closure that returns the the value of an environment variable.
/// This method allows having a custom environment.
///
/// If ```$XDG_CACHE_HOME``` is not set, it returns ```$HOME/.cache```.
pub fn get_cache_home_from_env<F>(get_env_var: &F) -> PathBuf
    where F: Fn(&str) -> Option<OsString>
{
    getenv_path(get_env_var, "XDG_CACHE_HOME")
        .unwrap_or(home_dir().unwrap().join(".cache"))
}

/// Get the cache home directory.
///
/// If ```$XDG_CACHE_HOME``` is not set, it returns ```$HOME/.cache```.
pub fn get_cache_home() -> PathBuf {
    get_cache_home_from_env(&env::var_os)
}

/// Get $XDG_RUNTIME_DIR if found in the environment.
///
/// Returns None if ```$XDG_RUNTIME_PATH``` is not set, in which case it is up to the application.
/// to fallback to a location that conforms to the specification.
pub fn get_runtime_dir_from_env<F>(get_env_var: &F) -> Option<PathBuf>
    where F: Fn(&str) -> Option<OsString>
{
    getenv_path(get_env_var, "XDG_RUNTIME_DIR")
}

pub fn get_runtime_dir() -> Option<PathBuf> {
    getenv_path(&env::var_os, "XDG_RUNTIME_DIR")
}

/// Check that the value set for ```$XDG_RUNTIME_DIR``` meets the requirements of the specification.
///
/// Returns Ok(true) if permissions are correct, Ok(false) if permissions are incorrect, or propogates any errors that occurred while checking permissions.
///
/// >$XDG_RUNTIME_DIR defines the base directory relative to which user-specific non-essential runtime files and other file objects (such as sockets, named pipes, ...) should be stored. The directory MUST be owned by the user, and he MUST be the only one having read and write access to it. Its Unix access mode MUST be 0700.
/// >
/// >The lifetime of the directory MUST be bound to the user being logged in. It MUST be created when the user first logs in and if the user fully logs out the directory MUST be removed. If the user logs in more than once he should get pointed to the same directory, and it is mandatory that the directory continues to exist from his first login to his last logout on the system, and not removed in between. Files in the directory MUST not survive reboot or a full logout/login cycle.
/// >
/// >The directory MUST be on a local file system and not shared with any other system. The directory MUST by fully-featured by the standards of the operating system. More specifically, on Unix-like operating systems AF_UNIX sockets, symbolic links, hard links, proper permissions, file locking, sparse files, memory mapping, file change notifications, a reliable hard link count must be supported, and no restrictions on the file name character set should be imposed. Files in this directory MAY be subjected to periodic clean-up. To ensure that your files are not removed, they should have their access time timestamp modified at least once every 6 hours of monotonic time or the 'sticky' bit should be set on the file.
#[cfg(unix)]
pub fn test_runtime_dir<P: AsRef<Path>>(path: P) -> Result<bool, std::io::Error> {
    fs::metadata(path).map(|attr| (attr.permissions().mode() == 0o700))
}

#[cfg(not(unix))]
pub fn test_runtime_dir<P: AsRef<Path>>(path: P) -> Result<bool, std::io::Error> {
    Ok(true)
}

/// Get an environment variable's value as a PathBuf.
fn getenv_path<F>(get_env_var: &F, env_var: &str) -> Option<PathBuf>
    where F: Fn(&str) -> Option<OsString>
{
    let path = (*get_env_var)(env_var);
    let path = path.map(PathBuf::from);
    match path.iter().next() {
        Some(p) if p.is_absolute() => Some(p.clone()),
        _ => None
    }
}

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

    use std::collections::HashMap;
    use std::env::{self, home_dir, join_paths, split_paths};
    use std::ffi::OsString;
    use std::path::PathBuf;

    #[test]
    fn test_env_with_no_xdg_vars() {
        let mut custom_env = HashMap::new();
        custom_env.insert("dummy", "");

        let f = |var: &str| { custom_env.get(var).map(OsString::from) };

        assert!(get_data_home_from_env(&f)   == home_dir().unwrap().join(".local/share"));
        assert!(get_data_dirs_from_env(&f)   == vec![PathBuf::from("/usr/local/share"), PathBuf::from("/usr/share")]);
        assert!(get_config_home_from_env(&f) == home_dir().unwrap().join(".config"));
        assert!(get_config_dirs_from_env(&f) == vec![PathBuf::from("/etc/xdg")]);
        assert!(get_cache_home_from_env(&f)  == home_dir().unwrap().join(".cache"));
        assert!(get_runtime_dir_from_env(&f) == None);
    }

    #[test]
    fn test_env_with_empty_xdg_vars() {
        let mut custom_env = HashMap::new();
        custom_env.insert("XDG_DATA_HOME", "");
        custom_env.insert("XDG_DATA_DIRS", "");
        custom_env.insert("XDG_CONFIG_HOME", "");
        custom_env.insert("XDG_CONFIG_DIRS", "");
        custom_env.insert("XDG_CACHE_HOME", "");

        let f = |var: &str| { custom_env.get(var).map(OsString::from) };

        assert!(get_data_home_from_env(&f)   == home_dir().unwrap().join(".local/share"));
        assert!(get_data_dirs_from_env(&f)   == vec![PathBuf::from("/usr/local/share"), PathBuf::from("/usr/share")]);
        assert!(get_config_home_from_env(&f) == home_dir().unwrap().join(".config"));
        assert!(get_config_dirs_from_env(&f) == vec![PathBuf::from("/etc/xdg")]);
        assert!(get_cache_home_from_env(&f)  == home_dir().unwrap().join(".cache"));
        assert!(get_runtime_dir_from_env(&f) == None);
    }

    #[test]
    fn test_env_with_xdg_vars() {
        let cwd = PathBuf::from(&env::current_dir().unwrap());
        let mut custom_env = HashMap::new();

        custom_env.insert("XDG_DATA_HOME", cwd.join("user/data").into_os_string());
        custom_env.insert("XDG_DATA_DIRS", join_paths(vec![cwd.join("share/data"), cwd.join("local/data")]).unwrap());
        custom_env.insert("XDG_CONFIG_HOME", cwd.join("user/config").into_os_string());
        custom_env.insert("XDG_CONFIG_DIRS", join_paths(vec![cwd.join("config"), cwd.join("local/config")]).unwrap());
        custom_env.insert("XDG_CACHE_HOME", cwd.join("user/cache").into_os_string());

        let f = |var: &str| { custom_env.get(var).map(OsString::from) };

        assert!(get_data_home_from_env(&f)   == custom_env.get("XDG_DATA_HOME").map(PathBuf::from).unwrap());
        assert!(get_data_dirs_from_env(&f)   == split_paths(&custom_env["XDG_DATA_DIRS"]).collect::<Vec<PathBuf>>());
        assert!(get_config_home_from_env(&f) == custom_env.get("XDG_CONFIG_HOME").map(PathBuf::from).unwrap());
        assert!(get_config_dirs_from_env(&f) == split_paths(&custom_env["XDG_CONFIG_DIRS"]).collect::<Vec<PathBuf>>());
        assert!(get_cache_home_from_env(&f)  == custom_env.get("XDG_CACHE_HOME").map(PathBuf::from).unwrap());
    }
}
<|endoftext|>


<commit_before>#![cfg_attr(test, feature(test))]
extern crate piston_meta;

pub mod ast;
pub mod runtime;
pub mod lifetime;

/// Runs a program using a syntax file and the source file.
pub fn run(syntax: &str, source: &str) {
    use std::thread;
    use piston_meta::{json, load_syntax_data};

    let data = load_syntax_data(syntax, source);
    let check_data = data.clone();

    // Do lifetime checking in parallel directly on meta data.
    let handle = thread::spawn(move || {
        let check_data = check_data;
        lifetime::check(&check_data)
    });

    // Convert to AST.
    let mut ignored = vec![];
    let ast = ast::convert(&data, &mut ignored).unwrap();

    // Check that lifetime checking succeeded.
    match handle.join().unwrap() {
        Ok(()) => {}
        Err(msg) => panic!(msg)
    }

    let mut runtime = runtime::Runtime::new();
    if ignored.len() > 0 {
        println!("START IGNORED");
        if ignored.len() > 0 {
            for r in &ignored {
                json::print(&data[r.iter()]);
            }
        }
        println!("END IGNORED");
        panic!("Some meta data was ignored in the syntax");
    }
    runtime.run(&ast);
}

#[cfg(test)]
mod tests {
    extern crate test;

    use super::run;
    use self::test::Bencher;

    #[bench]
    fn bench_add_two(b: &mut Bencher) {
        b.iter(||
            run("assets/syntax.txt", "source/bench/add.rs")
        );
    }

    #[bench]
    fn bench_main(b: &mut Bencher) {
        b.iter(||
            run("assets/syntax.txt", "source/bench/main.rs")
        );
    }

    #[bench]
    fn bench_array(b: &mut Bencher) {
        b.iter(||
            run("assets/syntax.txt", "source/bench/array.rs")
        );
    }

    #[bench]
    fn bench_object(b: &mut Bencher) {
        b.iter(||
            run("assets/syntax.txt", "source/bench/object.rs")
        );
    }

    #[bench]
    fn bench_call(b: &mut Bencher) {
        b.iter(||
            run("assets/syntax.txt", "source/bench/call.rs")
        );
    }
}
<commit_msg>Added "load" function<commit_after>#![cfg_attr(test, feature(test))]
extern crate piston_meta;

pub mod ast;
pub mod runtime;
pub mod lifetime;

/// Runs a program using a syntax file and the source file.
pub fn run(syntax: &str, source: &str) {
    use std::thread;
    use piston_meta::{json, load_syntax_data};

    let data = load_syntax_data(syntax, source);
    let check_data = data.clone();

    // Do lifetime checking in parallel directly on meta data.
    let handle = thread::spawn(move || {
        let check_data = check_data;
        lifetime::check(&check_data)
    });

    // Convert to AST.
    let mut ignored = vec![];
    let ast = ast::convert(&data, &mut ignored).unwrap();

    // Check that lifetime checking succeeded.
    match handle.join().unwrap() {
        Ok(()) => {}
        Err(msg) => panic!(msg)
    }

    let mut runtime = runtime::Runtime::new();
    if ignored.len() > 0 {
        println!("START IGNORED");
        if ignored.len() > 0 {
            for r in &ignored {
                json::print(&data[r.iter()]);
            }
        }
        println!("END IGNORED");
        panic!("Some meta data was ignored in the syntax");
    }
    runtime.run(&ast);
}

/// Loads a source from file.
pub fn load(source: &str) -> Result<Vec<ast::Function>, String> {
    use std::thread;
    use std::fs::File;
    use std::io::Read;
    use piston_meta::{parse_errstr, syntax_errstr};

    let syntax = include_str!("../assets/syntax.txt");
    let syntax_rules = try!(syntax_errstr(syntax));

    let mut data_file = File::open(source).unwrap();
    let mut d = String::new();
    data_file.read_to_string(&mut d).unwrap();

    let mut data = vec![];
    try!(parse_errstr(&syntax_rules, &d, &mut data));
    let check_data = data.clone();

    // Do lifetime checking in parallel directly on meta data.
    let handle = thread::spawn(move || {
        let check_data = check_data;
        lifetime::check(&check_data)
    });

    // Convert to AST.
    let mut ignored = vec![];
    let ast = ast::convert(&data, &mut ignored).unwrap();

    // Check that lifetime checking succeeded.
    match handle.join().unwrap() {
        Ok(()) => {}
        Err(msg) => return Err(msg)
    }

    if ignored.len() > 0 {
        unimplemented!();
    }

    Ok(ast)
}

#[cfg(test)]
mod tests {
    extern crate test;

    use super::run;
    use self::test::Bencher;

    #[bench]
    fn bench_add_two(b: &mut Bencher) {
        b.iter(||
            run("assets/syntax.txt", "source/bench/add.rs")
        );
    }

    #[bench]
    fn bench_main(b: &mut Bencher) {
        b.iter(||
            run("assets/syntax.txt", "source/bench/main.rs")
        );
    }

    #[bench]
    fn bench_array(b: &mut Bencher) {
        b.iter(||
            run("assets/syntax.txt", "source/bench/array.rs")
        );
    }

    #[bench]
    fn bench_object(b: &mut Bencher) {
        b.iter(||
            run("assets/syntax.txt", "source/bench/object.rs")
        );
    }

    #[bench]
    fn bench_call(b: &mut Bencher) {
        b.iter(||
            run("assets/syntax.txt", "source/bench/call.rs")
        );
    }
}
<|endoftext|>


<commit_before>use components::{Attribute, ConstantPoolItem, Field, Method};
use primitives::{PrimitiveIterator, U1, U2, U4};

use std::fs::File;
use std::io::{Error as IoError, Read};
use std::string::FromUtf8Error;

pub mod components;
pub mod primitives;

pub type ParserResult<T> = Result<T, ParserError>;

#[derive(Debug)]
pub enum ParserError {
    UnknownConstantPoolTag(U1),
    UnexpectedConstantPoolItem(&'static str),
    ConstantPoolIndexOutOfBounds(usize),
    InvalidUtf8(FromUtf8Error),
    Io(IoError),
}

impl From<IoError> for ParserError {
    fn from(error: IoError) -> ParserError {
        ParserError::Io(error)
    }
}

impl From<FromUtf8Error> for ParserError {
    fn from(error: FromUtf8Error) -> ParserError {
        ParserError::InvalidUtf8(error)
    }
}

macro_rules! populate_vec {
    ($length:ident, $supplier:expr) => {
        {
            let mut temp_vec = vec![];
            for _ in 0..$length {
                temp_vec.push(try!($supplier));
            }

            temp_vec
        }
    }
}

#[derive(Debug)]
pub struct ClassFile {
    pub magic: U4,
    pub minor_version: U2,
    pub major_version: U2,
    pub constant_pool_count: U2,
    pub constant_pool: Vec<ConstantPoolItem>,
    pub access_flags: U2,
    pub this_class: U2,
    pub super_class: U2,
    pub interfaces_count: U2,
    pub interfaces: Vec<U2>,
    pub fields_count: U2,
    pub fields: Vec<Field>,
    pub methods_count: U2,
    pub methods: Vec<Method>,
    pub attributes_count: U2,
    pub attributes: Vec<Attribute>,
}

impl ClassFile {
    pub fn from(file: File) -> ParserResult<ClassFile> {
        let mut bytes = file.bytes();

        let magic = try!(bytes.next_u4());
        let minor_version = try!(bytes.next_u2());
        let major_version = try!(bytes.next_u2());

        let constant_pool_count = try!(bytes.next_u2());
        let actual_constant_pool_count = constant_pool_count - 1;
        let constant_pool = populate_vec!(actual_constant_pool_count,
                                          ConstantPoolItem::from(&mut bytes));

        let access_flags = try!(bytes.next_u2());
        let this_class = try!(bytes.next_u2());
        let super_class = try!(bytes.next_u2());

        let interfaces_count = try!(bytes.next_u2());
        let interfaces = populate_vec!(interfaces_count, bytes.next_u2());

        let fields_count = try!(bytes.next_u2());
        let fields = populate_vec!(fields_count, Field::from(&mut bytes, &constant_pool));

        let methods_count = try!(bytes.next_u2());
        let methods = populate_vec!(methods_count, Method::from(&mut bytes, &constant_pool));

        let attributes_count = try!(bytes.next_u2());
        let attributes = populate_vec!(attributes_count,
                                       Attribute::from(&mut bytes, &constant_pool));

        Ok(ClassFile {
            magic: magic,
            minor_version: minor_version,
            major_version: major_version,
            constant_pool_count: constant_pool_count,
            constant_pool: constant_pool,
            access_flags: access_flags,
            this_class: this_class,
            super_class: super_class,
            interfaces_count: interfaces_count,
            interfaces: interfaces,
            fields_count: fields_count,
            fields: fields,
            methods_count: methods_count,
            methods: methods,
            attributes_count: attributes_count,
            attributes: attributes,
        })
    }

    fn maybe_resolve_main_method(&self) -> Option<&Method> {
        for method in &self.methods {
            if method.name.eq("main") {
                return Some(method);
            }
        }

        None
    }
}

#[cfg(test)]
mod tests {

    extern crate spectral;

    use self::spectral::prelude::*;

    use super::ClassFile;
    use super::components::{Attribute, AccessFlags, ConstantPoolItem};

    use std::fs::File;
    use std::path::PathBuf;

    const MANIFEST_DIR: &'static str = env!("CARGO_MANIFEST_DIR");

    #[test]
    fn can_successfully_parse_magic() {
        let test_file = open_test_resource("classfile/HelloWorld.class");
        let classfile = ClassFile::from(test_file).unwrap();

        assert_that(&classfile.magic).is_equal_to(&0xCAFEBABE);
    }

    #[test]
    fn can_successfully_parse_version() {
        let test_file = open_test_resource("classfile/HelloWorld.class");
        let classfile = ClassFile::from(test_file).unwrap();

        assert_that(&classfile.minor_version).is_equal_to(&0);
        assert_that(&classfile.major_version).is_equal_to(&52);
    }

    #[test]
    fn can_successfully_parse_constant_pool() {
        let test_file = open_test_resource("classfile/HelloWorld.class");
        let classfile = ClassFile::from(test_file).unwrap();

        assert_that(&classfile.constant_pool_count).is_equal_to(&26);
        assert_that(&classfile.constant_pool).has_length(25);
    }

    #[test]
    fn can_successfully_parse_access_flags() {
        let test_file = open_test_resource("classfile/HelloWorld.class");
        let classfile = ClassFile::from(test_file).unwrap();

        let access_flags = classfile.access_flags;
        asserting("class is public")
            .that(&access_flags)
            .matches(|val| AccessFlags::is_public(*val));
        asserting("class is super").that(&access_flags).matches(|val| AccessFlags::is_super(*val));
    }

    #[test]
    fn can_successfully_parse_class_references() {
        let test_file = open_test_resource("classfile/HelloWorld.class");
        let classfile = ClassFile::from(test_file).unwrap();

        let this_class = classfile.this_class;
        let super_class = classfile.super_class;
        let constant_pool = classfile.constant_pool;

        assert_that(&ConstantPoolItem::retrieve_class_info(this_class as usize, &constant_pool))
            .is_ok();
        assert_that(&ConstantPoolItem::retrieve_class_info(super_class as usize, &constant_pool))
            .is_ok();
    }

    #[test]
    fn can_successfully_parse_interfaces() {
        let test_file = open_test_resource("classfile/HelloWorld.class");
        let classfile = ClassFile::from(test_file).unwrap();

        assert_that(&classfile.interfaces_count).is_equal_to(&0);
        assert_that(&classfile.interfaces).has_length(0);
    }

    #[test]
    fn can_successfully_parse_fields() {
        let test_file = open_test_resource("classfile/HelloWorld.class");
        let classfile = ClassFile::from(test_file).unwrap();

        assert_that(&classfile.fields_count).is_equal_to(&0);
        assert_that(&classfile.fields).has_length(0);
    }

    #[test]
    fn can_successfully_parse_methods() {
        let test_file = open_test_resource("classfile/HelloWorld.class");
        let classfile = ClassFile::from(test_file).unwrap();

        assert_that(&classfile.methods_count).is_equal_to(&3);
        assert_that(&classfile.methods)
            .has_length(3)
            .mapped_contains(|val| &**val.name, &"<init>")
            .mapped_contains(|val| &**val.name, &"main")
            .mapped_contains(|val| &**val.name, &"println");

        let method_attributes: Vec<_> =
            classfile.methods.iter().flat_map(|val| &val.attributes).collect();
        asserting("at least one method has the code attribute")
            .that(&method_attributes)
            .matching_contains(|val| match val {
                &&Attribute::Code(..) => true,
                _ => false,
            });
    }

    #[test]
    fn can_successfully_parse_class_attributes() {
        let test_file = open_test_resource("classfile/HelloWorld.class");
        let classfile = ClassFile::from(test_file).unwrap();

        assert_that(&classfile.attributes_count).is_equal_to(&1);
        assert_that(&classfile.attributes).has_length(1);
    }

    #[test]
    fn can_resolve_main_method_if_present() {
        let test_file = open_test_resource("classfile/HelloWorld.class");
        let classfile = ClassFile::from(test_file).unwrap();

        assert_that(&classfile.maybe_resolve_main_method()).is_some();
    }

    fn open_test_resource(resource_path: &str) -> File {
        let mut file_path = PathBuf::from(MANIFEST_DIR);
        file_path.push("test-resources/");
        file_path.push(resource_path);

        File::open(file_path).unwrap()
    }

}
<commit_msg>Can now retrieve the classname<commit_after>use components::{Attribute, ConstantPoolItem, Field, Method, Utf8Info};
use primitives::{PrimitiveIterator, U1, U2, U4};

use std::fs::File;
use std::io::{Error as IoError, Read};
use std::rc::Rc;
use std::string::FromUtf8Error;

pub mod components;
pub mod primitives;

pub type ParserResult<T> = Result<T, ParserError>;

#[derive(Debug)]
pub enum ParserError {
    UnknownConstantPoolTag(U1),
    UnexpectedConstantPoolItem(&'static str),
    ConstantPoolIndexOutOfBounds(usize),
    InvalidUtf8(FromUtf8Error),
    Io(IoError),
}

impl From<IoError> for ParserError {
    fn from(error: IoError) -> ParserError {
        ParserError::Io(error)
    }
}

impl From<FromUtf8Error> for ParserError {
    fn from(error: FromUtf8Error) -> ParserError {
        ParserError::InvalidUtf8(error)
    }
}

macro_rules! populate_vec {
    ($length:ident, $supplier:expr) => {
        {
            let mut temp_vec = vec![];
            for _ in 0..$length {
                temp_vec.push(try!($supplier));
            }

            temp_vec
        }
    }
}

#[derive(Debug)]
pub struct ClassFile {
    pub magic: U4,
    pub minor_version: U2,
    pub major_version: U2,
    pub constant_pool_count: U2,
    pub constant_pool: Vec<ConstantPoolItem>,
    pub access_flags: U2,
    pub this_class: U2,
    pub super_class: U2,
    pub interfaces_count: U2,
    pub interfaces: Vec<U2>,
    pub fields_count: U2,
    pub fields: Vec<Field>,
    pub methods_count: U2,
    pub methods: Vec<Method>,
    pub attributes_count: U2,
    pub attributes: Vec<Attribute>,
}

impl ClassFile {
    pub fn from(file: File) -> ParserResult<ClassFile> {
        let mut bytes = file.bytes();

        let magic = try!(bytes.next_u4());
        let minor_version = try!(bytes.next_u2());
        let major_version = try!(bytes.next_u2());

        let constant_pool_count = try!(bytes.next_u2());
        let actual_constant_pool_count = constant_pool_count - 1;
        let constant_pool = populate_vec!(actual_constant_pool_count,
                                          ConstantPoolItem::from(&mut bytes));

        let access_flags = try!(bytes.next_u2());
        let this_class = try!(bytes.next_u2());
        let super_class = try!(bytes.next_u2());

        let interfaces_count = try!(bytes.next_u2());
        let interfaces = populate_vec!(interfaces_count, bytes.next_u2());

        let fields_count = try!(bytes.next_u2());
        let fields = populate_vec!(fields_count, Field::from(&mut bytes, &constant_pool));

        let methods_count = try!(bytes.next_u2());
        let methods = populate_vec!(methods_count, Method::from(&mut bytes, &constant_pool));

        let attributes_count = try!(bytes.next_u2());
        let attributes = populate_vec!(attributes_count,
                                       Attribute::from(&mut bytes, &constant_pool));

        Ok(ClassFile {
            magic: magic,
            minor_version: minor_version,
            major_version: major_version,
            constant_pool_count: constant_pool_count,
            constant_pool: constant_pool,
            access_flags: access_flags,
            this_class: this_class,
            super_class: super_class,
            interfaces_count: interfaces_count,
            interfaces: interfaces,
            fields_count: fields_count,
            fields: fields,
            methods_count: methods_count,
            methods: methods,
            attributes_count: attributes_count,
            attributes: attributes,
        })
    }

    pub fn classname(&self) -> ParserResult<Rc<Utf8Info>> {
        let this_class = self.this_class;
        let constant_pool = &self.constant_pool;

        let class_info = try!(ConstantPoolItem::retrieve_class_info(this_class as usize,
                                                                    constant_pool));
        let utf8_info = try!(ConstantPoolItem::retrieve_utf8_info(class_info.name_index as usize,
                                                                  constant_pool));

        Ok(utf8_info)
    }

    pub fn maybe_resolve_main_method(&self) -> Option<&Method> {
        for method in &self.methods {
            if method.name.eq("main") {
                return Some(method);
            }
        }

        None
    }
}

#[cfg(test)]
mod tests {

    extern crate spectral;

    use self::spectral::prelude::*;

    use super::ClassFile;
    use super::components::{Attribute, AccessFlags, ConstantPoolItem};

    use std::fs::File;
    use std::path::PathBuf;

    const MANIFEST_DIR: &'static str = env!("CARGO_MANIFEST_DIR");

    #[test]
    fn can_successfully_parse_magic() {
        let test_file = open_test_resource("classfile/HelloWorld.class");
        let classfile = ClassFile::from(test_file).unwrap();

        assert_that(&classfile.magic).is_equal_to(&0xCAFEBABE);
    }

    #[test]
    fn can_successfully_parse_version() {
        let test_file = open_test_resource("classfile/HelloWorld.class");
        let classfile = ClassFile::from(test_file).unwrap();

        assert_that(&classfile.minor_version).is_equal_to(&0);
        assert_that(&classfile.major_version).is_equal_to(&52);
    }

    #[test]
    fn can_successfully_parse_constant_pool() {
        let test_file = open_test_resource("classfile/HelloWorld.class");
        let classfile = ClassFile::from(test_file).unwrap();

        assert_that(&classfile.constant_pool_count).is_equal_to(&26);
        assert_that(&classfile.constant_pool).has_length(25);
    }

    #[test]
    fn can_successfully_parse_access_flags() {
        let test_file = open_test_resource("classfile/HelloWorld.class");
        let classfile = ClassFile::from(test_file).unwrap();

        let access_flags = classfile.access_flags;
        asserting("class is public")
            .that(&access_flags)
            .matches(|val| AccessFlags::is_public(*val));
        asserting("class is super").that(&access_flags).matches(|val| AccessFlags::is_super(*val));
    }

    #[test]
    fn can_successfully_parse_class_references() {
        let test_file = open_test_resource("classfile/HelloWorld.class");
        let classfile = ClassFile::from(test_file).unwrap();

        let this_class = classfile.this_class;
        let super_class = classfile.super_class;
        let constant_pool = classfile.constant_pool;

        assert_that(&ConstantPoolItem::retrieve_class_info(this_class as usize, &constant_pool))
            .is_ok();
        assert_that(&ConstantPoolItem::retrieve_class_info(super_class as usize, &constant_pool))
            .is_ok();
    }

    #[test]
    fn can_successfully_parse_interfaces() {
        let test_file = open_test_resource("classfile/HelloWorld.class");
        let classfile = ClassFile::from(test_file).unwrap();

        assert_that(&classfile.interfaces_count).is_equal_to(&0);
        assert_that(&classfile.interfaces).has_length(0);
    }

    #[test]
    fn can_successfully_parse_fields() {
        let test_file = open_test_resource("classfile/HelloWorld.class");
        let classfile = ClassFile::from(test_file).unwrap();

        assert_that(&classfile.fields_count).is_equal_to(&0);
        assert_that(&classfile.fields).has_length(0);
    }

    #[test]
    fn can_successfully_parse_methods() {
        let test_file = open_test_resource("classfile/HelloWorld.class");
        let classfile = ClassFile::from(test_file).unwrap();

        assert_that(&classfile.methods_count).is_equal_to(&3);
        assert_that(&classfile.methods)
            .has_length(3)
            .mapped_contains(|val| &**val.name, &"<init>")
            .mapped_contains(|val| &**val.name, &"main")
            .mapped_contains(|val| &**val.name, &"println");

        let method_attributes: Vec<_> =
            classfile.methods.iter().flat_map(|val| &val.attributes).collect();
        asserting("at least one method has the code attribute")
            .that(&method_attributes)
            .matching_contains(|val| match val {
                &&Attribute::Code(..) => true,
                _ => false,
            });
    }

    #[test]
    fn can_successfully_parse_class_attributes() {
        let test_file = open_test_resource("classfile/HelloWorld.class");
        let classfile = ClassFile::from(test_file).unwrap();

        assert_that(&classfile.attributes_count).is_equal_to(&1);
        assert_that(&classfile.attributes).has_length(1);
    }

    #[test]
    fn can_resolve_main_method_if_present() {
        let test_file = open_test_resource("classfile/HelloWorld.class");
        let classfile = ClassFile::from(test_file).unwrap();

        assert_that(&classfile.maybe_resolve_main_method()).is_some();
    }

    #[test]
    fn can_retrieve_classname() {
        let test_file = open_test_resource("classfile/HelloWorld.class");
        let classfile = ClassFile::from(test_file).unwrap();

        let classname = classfile.classname().unwrap();
        assert_that(&classname.to_string()).is_equal_to(&"HelloWorld".to_string());
    }

    fn open_test_resource(resource_path: &str) -> File {
        let mut file_path = PathBuf::from(MANIFEST_DIR);
        file_path.push("test-resources/");
        file_path.push(resource_path);

        File::open(file_path).unwrap()
    }

}
<|endoftext|>


<commit_before>#![doc(html_logo_url = "https://avatars0.githubusercontent.com/u/7853871?s=128", html_favicon_url = "https://avatars0.githubusercontent.com/u/7853871?s=256", html_root_url = "http://ironframework.io/core/iron")]
#![cfg_attr(test, deny(warnings))]
#![deny(missing_docs)]

//! The main crate for Iron.
//!
//! ## Overview
//!
//! Iron is a high level web framework built in and for Rust, built on
//! [hyper](https://github.com/hyperium/hyper). Iron is designed to take advantage
//! of Rust's greatest features - its excellent type system and principled
//! approach to ownership in both single threaded and multi threaded contexts.
//!
//! Iron is highly concurrent and can scale horizontally on more machines behind a
//! load balancer or by running more threads on a more powerful machine. Iron
//! avoids the bottlenecks encountered in highly concurrent code by avoiding shared
//! writes and locking in the core framework.
//!
//! ## Hello World
//!
//! ```no_run
//! extern crate iron;
//!
//! use iron::prelude::*;
//! use iron::status;
//!
//! fn main() {
//!     Iron::new(|_: &mut Request| {
//!         Ok(Response::with((status::Ok, "Hello World!")))
//!     }).http("localhost:3000").unwrap();
//! }
//! ```
//!
//! ## Design Philosophy
//!
//! Iron is meant to be as extensible and pluggable as possible; Iron's core is
//! concentrated and avoids unnecessary features by leaving them to middleware,
//! plugins, and modifiers.
//!
//! Middleware, Plugins, and Modifiers are the main ways to extend Iron with new
//! functionality. Most extensions that would be provided by middleware in other
//! web frameworks are instead addressed by the much simpler Modifier and Plugin
//! systems.
//!
//! Modifiers allow external code to manipulate Requests and Response in an ergonomic
//! fashion, allowing third-party extensions to get the same treatment as modifiers
//! defined in Iron itself. Plugins allow for lazily-evaluated, automatically cached
//! extensions to Requests and Responses, perfect for parsing, accessing, and
//! otherwise lazily manipulating an http connection.
//!
//! Middleware are only used when it is necessary to modify the control flow of a
//! Request flow, hijack the entire handling of a Request, check an incoming
//! Request, or to do final post-processing. This covers areas such as routing,
//! mounting, static asset serving, final template rendering, authentication, and
//! logging.
//!
//! Iron comes with only basic modifiers for setting the status, body, and various
//! headers, and the infrastructure for creating modifiers, plugins, and
//! middleware. No plugins or middleware are bundled with Iron.
//!

// Stdlib dependencies
#[macro_use] extern crate log;

// Third party packages
extern crate hyper;
extern crate typemap as tmap;
extern crate plugin;
extern crate error as err;
extern crate url;
extern crate num_cpus;
extern crate conduit_mime_types as mime_types;
#[macro_use]
extern crate lazy_static;

// Request + Response
pub use request::{Request, Url};
pub use response::Response;

// Middleware system
pub use middleware::{BeforeMiddleware, AfterMiddleware, AroundMiddleware,
                     Handler, Chain};

// Server
pub use iron::{Iron, Protocol};

// Extensions
pub use typemap::TypeMap;

// Headers
pub use hyper::header as headers;
pub use hyper::header::Headers;

// Expose `Pluggable` as `Plugin` so users can do `use iron::Plugin`.
pub use plugin::Pluggable as Plugin;

// Expose modifiers.
pub use modifier::Set;

// Errors
pub use error::Error;
pub use error::IronError;

// Mime types
pub use hyper::mime;

/// Iron's error type and associated utilities.
pub mod error;

/// The Result alias used throughout Iron and in clients of Iron.
pub type IronResult<T> = Result<T, IronError>;

/// A module meant to be glob imported when using Iron.
///
/// For instance:
///
/// ```
/// use iron::prelude::*;
/// ```
///
/// This module contains several important traits that provide many
/// of the convenience methods in Iron, as well as `Request`, `Response`
/// `IronResult`, `IronError` and `Iron`.
pub mod prelude {
    pub use {Set, Plugin, Chain, Request, Response,
             IronResult, IronError, Iron};
}

/// Re-exports from the TypeMap crate.
pub mod typemap {
    pub use tmap::{TypeMap, Key};
}

/// Re-exports from the Modifier crate.
pub mod modifier {
    extern crate modifier as modfier;
    pub use self::modfier::*;
}

/// Status Codes
pub mod status {
    pub use hyper::status::StatusCode as Status;
    pub use hyper::status::StatusCode::*;
    pub use hyper::status::StatusClass;
}

/// HTTP Methods
pub mod method {
    pub use hyper::method::Method;
    pub use hyper::method::Method::*;
}

// Publicized to show the documentation
pub mod middleware;

// Response utilities
pub mod response;

// Request utilities
pub mod request;

// Request and Response Modifiers
pub mod modifiers;

// Helper macros for error handling
mod macros;

mod iron;
<commit_msg>(fix) Publicize the Timeouts type to allow setting timeouts.<commit_after>#![doc(html_logo_url = "https://avatars0.githubusercontent.com/u/7853871?s=128", html_favicon_url = "https://avatars0.githubusercontent.com/u/7853871?s=256", html_root_url = "http://ironframework.io/core/iron")]
#![cfg_attr(test, deny(warnings))]
#![deny(missing_docs)]

//! The main crate for Iron.
//!
//! ## Overview
//!
//! Iron is a high level web framework built in and for Rust, built on
//! [hyper](https://github.com/hyperium/hyper). Iron is designed to take advantage
//! of Rust's greatest features - its excellent type system and principled
//! approach to ownership in both single threaded and multi threaded contexts.
//!
//! Iron is highly concurrent and can scale horizontally on more machines behind a
//! load balancer or by running more threads on a more powerful machine. Iron
//! avoids the bottlenecks encountered in highly concurrent code by avoiding shared
//! writes and locking in the core framework.
//!
//! ## Hello World
//!
//! ```no_run
//! extern crate iron;
//!
//! use iron::prelude::*;
//! use iron::status;
//!
//! fn main() {
//!     Iron::new(|_: &mut Request| {
//!         Ok(Response::with((status::Ok, "Hello World!")))
//!     }).http("localhost:3000").unwrap();
//! }
//! ```
//!
//! ## Design Philosophy
//!
//! Iron is meant to be as extensible and pluggable as possible; Iron's core is
//! concentrated and avoids unnecessary features by leaving them to middleware,
//! plugins, and modifiers.
//!
//! Middleware, Plugins, and Modifiers are the main ways to extend Iron with new
//! functionality. Most extensions that would be provided by middleware in other
//! web frameworks are instead addressed by the much simpler Modifier and Plugin
//! systems.
//!
//! Modifiers allow external code to manipulate Requests and Response in an ergonomic
//! fashion, allowing third-party extensions to get the same treatment as modifiers
//! defined in Iron itself. Plugins allow for lazily-evaluated, automatically cached
//! extensions to Requests and Responses, perfect for parsing, accessing, and
//! otherwise lazily manipulating an http connection.
//!
//! Middleware are only used when it is necessary to modify the control flow of a
//! Request flow, hijack the entire handling of a Request, check an incoming
//! Request, or to do final post-processing. This covers areas such as routing,
//! mounting, static asset serving, final template rendering, authentication, and
//! logging.
//!
//! Iron comes with only basic modifiers for setting the status, body, and various
//! headers, and the infrastructure for creating modifiers, plugins, and
//! middleware. No plugins or middleware are bundled with Iron.
//!

// Stdlib dependencies
#[macro_use] extern crate log;

// Third party packages
extern crate hyper;
extern crate typemap as tmap;
extern crate plugin;
extern crate error as err;
extern crate url;
extern crate num_cpus;
extern crate conduit_mime_types as mime_types;
#[macro_use]
extern crate lazy_static;

// Request + Response
pub use request::{Request, Url};
pub use response::Response;

// Middleware system
pub use middleware::{BeforeMiddleware, AfterMiddleware, AroundMiddleware,
                     Handler, Chain};

// Server
pub use iron::{Iron, Protocol, Timeouts};

// Extensions
pub use typemap::TypeMap;

// Headers
pub use hyper::header as headers;
pub use hyper::header::Headers;

// Expose `Pluggable` as `Plugin` so users can do `use iron::Plugin`.
pub use plugin::Pluggable as Plugin;

// Expose modifiers.
pub use modifier::Set;

// Errors
pub use error::Error;
pub use error::IronError;

// Mime types
pub use hyper::mime;

/// Iron's error type and associated utilities.
pub mod error;

/// The Result alias used throughout Iron and in clients of Iron.
pub type IronResult<T> = Result<T, IronError>;

/// A module meant to be glob imported when using Iron.
///
/// For instance:
///
/// ```
/// use iron::prelude::*;
/// ```
///
/// This module contains several important traits that provide many
/// of the convenience methods in Iron, as well as `Request`, `Response`
/// `IronResult`, `IronError` and `Iron`.
pub mod prelude {
    pub use {Set, Plugin, Chain, Request, Response,
             IronResult, IronError, Iron};
}

/// Re-exports from the TypeMap crate.
pub mod typemap {
    pub use tmap::{TypeMap, Key};
}

/// Re-exports from the Modifier crate.
pub mod modifier {
    extern crate modifier as modfier;
    pub use self::modfier::*;
}

/// Status Codes
pub mod status {
    pub use hyper::status::StatusCode as Status;
    pub use hyper::status::StatusCode::*;
    pub use hyper::status::StatusClass;
}

/// HTTP Methods
pub mod method {
    pub use hyper::method::Method;
    pub use hyper::method::Method::*;
}

// Publicized to show the documentation
pub mod middleware;

// Response utilities
pub mod response;

// Request utilities
pub mod request;

// Request and Response Modifiers
pub mod modifiers;

// Helper macros for error handling
mod macros;

mod iron;
<|endoftext|>


<commit_before>//! The main entry point for the Space API server.
//!
//! Running this code starts a HTTP server instance. The default port is 3000, but you can set your
//! own favorite port by exporting the `PORT` environment variable.

#[macro_use]
extern crate log;
extern crate rustc_serialize;
extern crate iron;
extern crate urlencoded;
extern crate spaceapi;

pub mod datastore;
pub mod sensors;

use std::net::Ipv4Addr;

use rustc_serialize::json::{Json, ToJson};
use iron::prelude::*;
use iron::{status, headers, middleware};
use iron::modifiers::Header;
use urlencoded::UrlEncodedQuery;

pub use spaceapi as api;
use datastore::SafeDataStore;
use spaceapi::optional::Optional;
use spaceapi::SensorTemplate;

/// A Space API server instance.
///
/// You can create a new instance using the ``new`` constructor method by
/// passing it the host, the port, the ``Status`` object and a datastore.
///
/// The ``SpaceapiServer`` includes a web server through
/// [Hyper](http://hyper.rs/hyper/hyper/server/index.html). Simply call the ``serve`` method.
pub struct SpaceapiServer {
    host: Ipv4Addr,
    port: u16,
    status: api::Status,
    datastore: SafeDataStore,
    sensor_specs: Vec<sensors::SensorSpec>,
}

impl SpaceapiServer {

    pub fn new(host: Ipv4Addr, port: u16, status: api::Status, datastore: SafeDataStore) -> SpaceapiServer {
        SpaceapiServer {
            host: host,
            port: port,
            status: status,
            datastore: datastore,
            sensor_specs: vec![],
        }
    }

    /// Update values in the `DataStore`
    fn update_values(&self, map: &urlencoded::QueryMap) -> IronResult<Response> {
        // store data to datastore
        let mut datastore_lock = self.datastore.lock().unwrap();
        println!("{:?}", map);

        for item in map.iter() {
            // TODO: check if key exists and handle errors
            datastore_lock.store(item.0, &item.1[0]);
        }
        let response = Response::with((status::Ok, "updated values"));
        Ok(response)
    }

    /// Start a HTTP server listening on ``self.host:self.port``.
    ///
    /// This call is blocking. It can be interrupted with SIGINT (Ctrl+C).
    pub fn serve(self) {
        let host = self.host;
        let port = self.port;
        println!("Starting HTTP server on http://{}:{}...", host, port);
        Iron::new(self).http((host, port)).unwrap();
    }

    /// Register a new sensor.
    ///
    /// The first argument is a ``api::SensorTemplate`` instance containing all static data.
    /// The second argument specifies how to get the actual sensor value from the datastore.
    /// And the third argument specifies the data type of the value.
    pub fn register_sensor(&mut self, template: Box<api::SensorTemplate>, data_key: String) {
        self.sensor_specs.push(sensors::SensorSpec {
            template: template,
            data_key: data_key,
        });
    }

    fn build_response_json(&self) -> Json {

        // Create a mutable copy of the status struct
        let mut status_copy = self.status.clone();

        // Process registered sensors
        for sensor_spec in &self.sensor_specs {

            sensor_spec.get_sensor_value(&self.datastore).map(|value| {
                if status_copy.sensors.is_absent() {
                    status_copy.sensors = Optional::Value(spaceapi::Sensors {
                        people_now_present: Optional::Absent,
                        temperature: Optional::Absent,
                    });
                }
                sensor_spec.template.to_sensor(&value, &mut status_copy.sensors.as_mut().unwrap());
            });
        }

        status_copy.state.open = status_copy.sensors
                                            .as_ref()
                                            .and_then(|sensors| sensors.people_now_present.as_ref())
                                            .and_then(|people_now_present| {
                                                match people_now_present[0].value {
                                                    0i64 => Optional::Value(false),
                                                    _ => Optional::Value(true),
                                                }
                                            })
                                            .into();

        // Serialize to JSON
        status_copy.to_json()
    }

}

impl middleware::Handler for SpaceapiServer {

    fn handle(&self, req: &mut Request) -> IronResult<Response> {

        match req.get_ref::<UrlEncodedQuery>() {
            Ok(ref hashmap) => return self.update_values(hashmap),
            Err(ref e) => println!("{:?}", e),
        }

        // Get response body
        let body = self.build_response_json().to_string();

        // Create response
        let header_content_type = headers::ContentType("application/json; charset=utf-8".parse().unwrap());
        let header_cache_control = headers::CacheControl(vec![headers::CacheDirective::NoCache]);
        let header_allow_origin = headers::AccessControlAllowOrigin::Any;
        let response = Response::with((status::Ok, body))
                           .set(Header(header_content_type))
                           .set(Header(header_cache_control))
                           .set(Header(header_allow_origin));
        Ok(response)
    }
}

#[cfg(test)]
mod test {
    extern crate spaceapi;
    extern crate rustc_serialize;

    use std::net::Ipv4Addr;
    use std::sync::Mutex;
    use rustc_serialize::json::Json;
    use spaceapi::optional::Optional;
    use super::SpaceapiServer;
    use super::datastore::{DataStore, RedisStore};

    fn get_test_data() -> Json {
        // Create minimal status object
        let status = spaceapi::Status::new(
            "ourspace".to_string(),
            "https://example.com/logo.png".to_string(),
            "https://example.com/".to_string(),
            spaceapi::Location {
                address: Optional::Value("Street 1, Zürich, Switzerland".to_string()),
                lat: 47.123,
                lon: 8.88,
            },
            spaceapi::Contact {
                irc: Optional::Absent,
                twitter: Optional::Absent,
                foursquare: Optional::Absent,
                email: Optional::Value("hi@example.com".to_string()),
            },
            vec![
                "email".to_string(),
                "twitter".to_string(),
            ],
        );

        // Create datastore (TODO: Create dummy store for testing?)
        let datastore = Mutex::new(Box::new(RedisStore::new().unwrap()) as Box<DataStore>);

        // Initialize server
        let server = SpaceapiServer::new(Ipv4Addr::new(127, 0, 0, 1), 3001, status, datastore);

        // Build JSON
        server.build_response_json()
    }

    #[test]
    /// Verify that the result is a JSON object.
    fn verify_json_obj() {
        let json = get_test_data();
        assert!(json.is_object());
    }

    #[test]
    /// Verify that the result has the correct keys.
    fn verify_json_keys() {
        let json = get_test_data();
        let status = json.as_object().unwrap();  // We get back a BTreeMap<String, Json>
        let keys: Vec<String> = status.keys().cloned().collect();  // Collect the keys
        assert_eq!(keys,
                   ["api", "contact", "issue_report_channels", "location", "logo", "space", "state", "url"]);
    }

    #[test]
    /// Verify static data
    fn verify_json_static_data() {
        let json = get_test_data();
        let status = json.as_object().unwrap();  // We get back a BTreeMap<String, Json>

        // Strings
        assert_eq!(status.get("api").unwrap().as_string().unwrap(), "0.13");
        assert_eq!(status.get("space").unwrap().as_string().unwrap(),
                   "ourspace");
        assert_eq!(status.get("url").unwrap().as_string().unwrap(),
                   "https://example.com/");
        assert_eq!(status.get("logo").unwrap().as_string().unwrap(),
                   "https://example.com/logo.png");

        // Channels
        let channels: &Vec<Json> = status.get("issue_report_channels").unwrap().as_array().unwrap();
        let channel_values: Vec<String> = channels.iter()
                                                  .cloned()
                                                  .map(|c| c.as_string().unwrap().to_string())
                                                  .collect();
        assert_eq!(vec!["email", "twitter"], channel_values);
    }

    #[test]
    /// Verify location
    fn verify_json_location() {
        let json = get_test_data();
        let status = json.as_object().unwrap();
        let location = status.get("location").unwrap().as_object().unwrap();

        // Verify data
        let address = location.get("address").unwrap().as_string().unwrap();
        assert_eq!("Street 1, Zürich, Switzerland".to_string(), address);
        assert_eq!(47.123, location.get("lat").unwrap().as_f64().unwrap());
        assert_eq!(8.88, location.get("lon").unwrap().as_f64().unwrap());
    }

    #[test]
    /// Verify contact
    fn verify_json_contact() {
        let json = get_test_data();
        let status = json.as_object().unwrap();
        let contact = status.get("contact").unwrap().as_object().unwrap();

        // Verify data
        let email = contact.get("email").unwrap().as_string().unwrap();
        assert_eq!("hi@example.com".to_string(), email);
    }

    /* TODO: Update
    #[test]
    /// Verify sensor data
    fn verify_json_sensors() {
        let json = get_test_data();
        let status = json.as_object().unwrap();
        let sensors = status.get("sensors").unwrap().as_object().unwrap();
        let temperature_sensor = sensors.get("temperature").unwrap()
                                        .as_array().unwrap()[0]  // List of sensors
                                        .as_object().unwrap();  // Sensor object
        let people_sensor = sensors.get("people_now_present").unwrap()  // List of sensors
                                        .as_array().unwrap()[0]  // Sensor object
                                        .as_object().unwrap();

        // Verify temperature sensor
        let temp_location = temperature_sensor.get("location").unwrap().as_string().unwrap();
        let temp_name = temperature_sensor.get("name").unwrap().as_string().unwrap();
        let temp_unit = temperature_sensor.get("unit").unwrap().as_string().unwrap();
        let temp_value = temperature_sensor.get("value").unwrap().as_f64().unwrap();
        assert_eq!("Basement".to_string(), temp_location);
        assert_eq!("Raspberry CPU".to_string(), temp_name);
        assert_eq!("°C".to_string(), temp_unit);
        assert_eq!(42.5, temp_value);

        // Verify peoplesensor
        let people_location = people_sensor.get("location").unwrap().as_string().unwrap();
        let people_value = people_sensor.get("value").unwrap().as_u64().unwrap();
        assert_eq!("Hackerspace".to_string(), people_location);
        assert_eq!(23, people_value);
    }
    */

}
<commit_msg>Nicer logging<commit_after>//! The main entry point for the Space API server.
//!
//! Running this code starts a HTTP server instance. The default port is 3000, but you can set your
//! own favorite port by exporting the `PORT` environment variable.

#[macro_use]
extern crate log;
extern crate rustc_serialize;
extern crate iron;
extern crate urlencoded;
extern crate spaceapi;

pub mod datastore;
pub mod sensors;

use std::net::Ipv4Addr;

use rustc_serialize::json::{Json, ToJson};
use iron::prelude::*;
use iron::{status, headers, middleware};
use iron::modifiers::Header;
use urlencoded::UrlEncodedQuery;

pub use spaceapi as api;
use datastore::SafeDataStore;
use spaceapi::optional::Optional;
use spaceapi::SensorTemplate;

/// A Space API server instance.
///
/// You can create a new instance using the ``new`` constructor method by
/// passing it the host, the port, the ``Status`` object and a datastore.
///
/// The ``SpaceapiServer`` includes a web server through
/// [Hyper](http://hyper.rs/hyper/hyper/server/index.html). Simply call the ``serve`` method.
pub struct SpaceapiServer {
    host: Ipv4Addr,
    port: u16,
    status: api::Status,
    datastore: SafeDataStore,
    sensor_specs: Vec<sensors::SensorSpec>,
}

impl SpaceapiServer {

    pub fn new(host: Ipv4Addr, port: u16, status: api::Status, datastore: SafeDataStore) -> SpaceapiServer {
        SpaceapiServer {
            host: host,
            port: port,
            status: status,
            datastore: datastore,
            sensor_specs: vec![],
        }
    }

    /// Update values in the `DataStore`
    fn update_values(&self, map: &urlencoded::QueryMap) -> IronResult<Response> {
        // store data to datastore
        let mut datastore_lock = self.datastore.lock().unwrap();
        info!("{:?}", map);

        for item in map.iter() {
            // TODO: check if key exists and handle errors
            datastore_lock.store(item.0, &item.1[0]);
        }
        let response = Response::with((status::Ok, "updated values"));
        Ok(response)
    }

    /// Start a HTTP server listening on ``self.host:self.port``.
    ///
    /// This call is blocking. It can be interrupted with SIGINT (Ctrl+C).
    pub fn serve(self) {
        let host = self.host;
        let port = self.port;
        println!("Starting HTTP server on http://{}:{}...", host, port);
        Iron::new(self).http((host, port)).unwrap();
    }

    /// Register a new sensor.
    ///
    /// The first argument is a ``api::SensorTemplate`` instance containing all static data.
    /// The second argument specifies how to get the actual sensor value from the datastore.
    /// And the third argument specifies the data type of the value.
    pub fn register_sensor(&mut self, template: Box<api::SensorTemplate>, data_key: String) {
        self.sensor_specs.push(sensors::SensorSpec {
            template: template,
            data_key: data_key,
        });
    }

    fn build_response_json(&self) -> Json {

        // Create a mutable copy of the status struct
        let mut status_copy = self.status.clone();

        // Process registered sensors
        for sensor_spec in &self.sensor_specs {

            sensor_spec.get_sensor_value(&self.datastore).map(|value| {
                if status_copy.sensors.is_absent() {
                    status_copy.sensors = Optional::Value(spaceapi::Sensors {
                        people_now_present: Optional::Absent,
                        temperature: Optional::Absent,
                    });
                }
                sensor_spec.template.to_sensor(&value, &mut status_copy.sensors.as_mut().unwrap());
            });
        }

        status_copy.state.open = status_copy.sensors
                                            .as_ref()
                                            .and_then(|sensors| sensors.people_now_present.as_ref())
                                            .and_then(|people_now_present| {
                                                match people_now_present[0].value {
                                                    0i64 => Optional::Value(false),
                                                    _ => Optional::Value(true),
                                                }
                                            })
                                            .into();

        // Serialize to JSON
        status_copy.to_json()
    }

}

impl middleware::Handler for SpaceapiServer {

    fn handle(&self, req: &mut Request) -> IronResult<Response> {

        println!("{} /{} from {}", req.method, req.url.path[0], req.remote_addr);

        match req.get_ref::<UrlEncodedQuery>() {
            Ok(ref hashmap) => return self.update_values(hashmap),
            Err(ref e) => error!("{:?}", e),
        }

        // Get response body
        let body = self.build_response_json().to_string();

        // Create response
        let header_content_type = headers::ContentType("application/json; charset=utf-8".parse().unwrap());
        let header_cache_control = headers::CacheControl(vec![headers::CacheDirective::NoCache]);
        let header_allow_origin = headers::AccessControlAllowOrigin::Any;
        let response = Response::with((status::Ok, body))
                           .set(Header(header_content_type))
                           .set(Header(header_cache_control))
                           .set(Header(header_allow_origin));
        Ok(response)
    }
}

#[cfg(test)]
mod test {
    extern crate spaceapi;
    extern crate rustc_serialize;

    use std::net::Ipv4Addr;
    use std::sync::Mutex;
    use rustc_serialize::json::Json;
    use spaceapi::optional::Optional;
    use super::SpaceapiServer;
    use super::datastore::{DataStore, RedisStore};

    fn get_test_data() -> Json {
        // Create minimal status object
        let status = spaceapi::Status::new(
            "ourspace".to_string(),
            "https://example.com/logo.png".to_string(),
            "https://example.com/".to_string(),
            spaceapi::Location {
                address: Optional::Value("Street 1, Zürich, Switzerland".to_string()),
                lat: 47.123,
                lon: 8.88,
            },
            spaceapi::Contact {
                irc: Optional::Absent,
                twitter: Optional::Absent,
                foursquare: Optional::Absent,
                email: Optional::Value("hi@example.com".to_string()),
            },
            vec![
                "email".to_string(),
                "twitter".to_string(),
            ],
        );

        // Create datastore (TODO: Create dummy store for testing?)
        let datastore = Mutex::new(Box::new(RedisStore::new().unwrap()) as Box<DataStore>);

        // Initialize server
        let server = SpaceapiServer::new(Ipv4Addr::new(127, 0, 0, 1), 3001, status, datastore);

        // Build JSON
        server.build_response_json()
    }

    #[test]
    /// Verify that the result is a JSON object.
    fn verify_json_obj() {
        let json = get_test_data();
        assert!(json.is_object());
    }

    #[test]
    /// Verify that the result has the correct keys.
    fn verify_json_keys() {
        let json = get_test_data();
        let status = json.as_object().unwrap();  // We get back a BTreeMap<String, Json>
        let keys: Vec<String> = status.keys().cloned().collect();  // Collect the keys
        assert_eq!(keys,
                   ["api", "contact", "issue_report_channels", "location", "logo", "space", "state", "url"]);
    }

    #[test]
    /// Verify static data
    fn verify_json_static_data() {
        let json = get_test_data();
        let status = json.as_object().unwrap();  // We get back a BTreeMap<String, Json>

        // Strings
        assert_eq!(status.get("api").unwrap().as_string().unwrap(), "0.13");
        assert_eq!(status.get("space").unwrap().as_string().unwrap(),
                   "ourspace");
        assert_eq!(status.get("url").unwrap().as_string().unwrap(),
                   "https://example.com/");
        assert_eq!(status.get("logo").unwrap().as_string().unwrap(),
                   "https://example.com/logo.png");

        // Channels
        let channels: &Vec<Json> = status.get("issue_report_channels").unwrap().as_array().unwrap();
        let channel_values: Vec<String> = channels.iter()
                                                  .cloned()
                                                  .map(|c| c.as_string().unwrap().to_string())
                                                  .collect();
        assert_eq!(vec!["email", "twitter"], channel_values);
    }

    #[test]
    /// Verify location
    fn verify_json_location() {
        let json = get_test_data();
        let status = json.as_object().unwrap();
        let location = status.get("location").unwrap().as_object().unwrap();

        // Verify data
        let address = location.get("address").unwrap().as_string().unwrap();
        assert_eq!("Street 1, Zürich, Switzerland".to_string(), address);
        assert_eq!(47.123, location.get("lat").unwrap().as_f64().unwrap());
        assert_eq!(8.88, location.get("lon").unwrap().as_f64().unwrap());
    }

    #[test]
    /// Verify contact
    fn verify_json_contact() {
        let json = get_test_data();
        let status = json.as_object().unwrap();
        let contact = status.get("contact").unwrap().as_object().unwrap();

        // Verify data
        let email = contact.get("email").unwrap().as_string().unwrap();
        assert_eq!("hi@example.com".to_string(), email);
    }

    /* TODO: Update
    #[test]
    /// Verify sensor data
    fn verify_json_sensors() {
        let json = get_test_data();
        let status = json.as_object().unwrap();
        let sensors = status.get("sensors").unwrap().as_object().unwrap();
        let temperature_sensor = sensors.get("temperature").unwrap()
                                        .as_array().unwrap()[0]  // List of sensors
                                        .as_object().unwrap();  // Sensor object
        let people_sensor = sensors.get("people_now_present").unwrap()  // List of sensors
                                        .as_array().unwrap()[0]  // Sensor object
                                        .as_object().unwrap();

        // Verify temperature sensor
        let temp_location = temperature_sensor.get("location").unwrap().as_string().unwrap();
        let temp_name = temperature_sensor.get("name").unwrap().as_string().unwrap();
        let temp_unit = temperature_sensor.get("unit").unwrap().as_string().unwrap();
        let temp_value = temperature_sensor.get("value").unwrap().as_f64().unwrap();
        assert_eq!("Basement".to_string(), temp_location);
        assert_eq!("Raspberry CPU".to_string(), temp_name);
        assert_eq!("°C".to_string(), temp_unit);
        assert_eq!(42.5, temp_value);

        // Verify peoplesensor
        let people_location = people_sensor.get("location").unwrap().as_string().unwrap();
        let people_value = people_sensor.get("value").unwrap().as_u64().unwrap();
        assert_eq!("Hackerspace".to_string(), people_location);
        assert_eq!(23, people_value);
    }
    */

}
<|endoftext|>


<commit_before>extern crate num;

pub mod create;
pub use create::*;

pub mod set;
pub use set::*;

pub mod mul;
pub use mul::*;

pub mod sdiv;
pub use sdiv::*;

pub mod misc;
pub use misc::*;

pub mod transform;
pub use transform::*;
<commit_msg>updates<commit_after>#![feature(no_std)]
#![no_std]

extern crate num;

pub mod create;
pub use create::*;

pub mod set;
pub use set::*;

pub mod mul;
pub use mul::*;

pub mod sdiv;
pub use sdiv::*;

pub mod misc;
pub use misc::*;

pub mod transform;
pub use transform::*;
<|endoftext|>


<commit_before>#![cfg_attr(feature = "pattern", feature(pattern))]
#![cfg_attr(feature = "benchmarks", feature(test))]

//! A tiny library to efficiently search strings for sets of ASCII
//! characters or byte slices for sets of bytes.
//!
//! ## Examples
//!
//! ### Searching for a set of ASCII characters
//!
//! ```rust
//! #[macro_use]
//! extern crate jetscii;
//!
//! fn main() {
//!     let part_number = "86-J52:rev1";
//!     let first = ascii_chars!('-', ':').find(part_number);
//!     assert_eq!(first, Some(2));
//! }
//! ```
//!
//! ### Searching for a set of bytes
//!
//! ```rust
//! #[macro_use]
//! extern crate jetscii;
//!
//! fn main() {
//!     let raw_data = [0x00, 0x01, 0x10, 0xFF, 0x42];
//!     let first = bytes!(0x01, 0x10).find(&raw_data);
//!     assert_eq!(first, Some(1));
//! }
//! ```
//!
//! ## Using the pattern API
//!
//! If this crate is compiled with the unstable `pattern` feature
//! flag, [`AsciiChars`] will implement the
//! [`Pattern`][std::str::pattern::Pattern] trait, allowing it to be
//! used with many traditional methods.
//!
//! ```
//! # #[cfg(feature = "pattern")]
//! #[macro_use]
//! extern crate jetscii;
//!
//! fn main() {
//! # #[cfg(feature = "pattern")] {
//!     let part_number = "86-J52:rev1";
//!     let parts: Vec<_> = part_number.split(ascii_chars!('-', ':')).collect();
//!     assert_eq!(&parts, &["86", "J52", "rev1"]);
//! # }
//! }
//! ```
//!
//! ## What's so special about this library?
//!
//! We use a particular set of x86-64 SSE 4.2 instructions (`PCMPESTRI`
//! and `PCMPESTRM`) to gain great speedups. This method stays fast even
//! when searching for a byte in a set of up to 16 choices.
//!
//! When the `PCMPxSTRx` instructions are not available, we fall back to
//! reasonably fast but universally-supported methods.
//!
//! ## Benchmarks
//!
//! ### Single character
//!
//! Searching a 5MiB string of `a`s with a single space at the end for a space:
//!
//! | Method                                                      | Speed          |
//! |-------------------------------------------------------------|----------------|
//! | ascii_chars!(' ').find(s)                      | 5882 MB/s      |
//! | s.as_bytes().iter().position(\|&c\| c == b' ') | 1514 MB/s      |
//! | s.find(" ")                                    | 644 MB/s       |
//! | s.find(&[' '][..])                             | 630 MB/s       |
//! | **s.find(' ')**                                | **10330 MB/s** |
//! | s.find(\|c\| c == ' ')                         | 786 MB/s       |
//!
//! ### Set of 3 characters
//!
//! Searching a 5MiB string of `a`s with a single ampersand at the end for `<`, `>`, and `&`:
//!
//! | Method                                                      | Speed         |
//! |-------------------------------------------------------------|---------------|
//! | **ascii_chars!(/\* ... \*/).find(s)**          | **6238 MB/s** |
//! | s.as_bytes().iter().position(\|&c\| /* ... */) | 1158 MB/s     |
//! | s.find(&[/* ... */][..])                       | 348 MB/s      |
//! | s.find(\|c\| /* ... */))                       | 620 MB/s      |
//!
//! ### Set of 5 characters
//!
//! Searching a 5MiB string of `a`s with a single ampersand at the end for `<`, `>`, `&`, `'`, and `"`:
//!
//! | Method                                                      | Speed         |
//! |-------------------------------------------------------------|---------------|
//! | **ascii_chars!(/\* ... \*/).find(s)**          | **6303 MB/s** |
//! | s.as_bytes().iter().position(\|&c\| /* ... */) | 485 MB/s      |
//! | s.find(&[/* ... */][..]))                      | 282 MB/s      |
//! | s.find(\|c\| /* ... */)                        | 785 MB/s      |

#[cfg(test)]
#[macro_use]
extern crate lazy_static;
#[cfg(test)]
extern crate libc;
#[cfg(test)]
extern crate quickcheck;

use std::marker::PhantomData;

include!(concat!(env!("OUT_DIR"), "/src/macros.rs"));

#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
mod simd;

#[cfg(not(target_feature = "sse4.2"))]
mod fallback;

#[cfg(feature = "pattern")]
mod pattern;

/// Searches a slice for a set of bytes. Up to 16 bytes may be used.
pub struct Bytes<F>
where
    F: Fn(u8) -> bool,
{
    // Include this implementation only when compiling for x86_64 as
    // that's the only platform that we support.
    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
    simd: simd::Bytes,

    // If we are *guaranteed* to have SSE 4.2, then there's no reason
    // to have this implementation.
    #[cfg(not(target_feature = "sse4.2"))]
    fallback: fallback::Bytes<F>,

    // Since we might not use the fallback implementation, we add this
    // to avoid unused type parameters.
    _fallback: PhantomData<F>,
}

impl<F> Bytes<F>
where
    F: Fn(u8) -> bool,
{
    /// Manual constructor; prefer using [`bytes!`] instead.
    ///
    /// Provide an array of bytes to search for, the number of
    /// valid bytes provided, and a closure to use when the SIMD
    /// intrinsics are not available. The closure **must** search for
    /// the same bytes as in the array.
    #[allow(unused_variables)]
    pub /* const */ fn new(bytes: [u8; 16], len: i32, fallback: F) -> Self {
        Bytes {
            #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
            simd: simd::Bytes::new(bytes, len),

            #[cfg(not(target_feature = "sse4.2"))]
            fallback: fallback::Bytes::new(fallback),

            _fallback: PhantomData,
        }
    }

    /// Searches the slice for the first matching byte in the set.
    #[inline]
    pub fn find(&self, haystack: &[u8]) -> Option<usize> {
        // If we can tell at compile time that we have support,
        // call the optimized code directly.
        #[cfg(target_feature = "sse4.2")]
        {
            unsafe { self.simd.find(haystack) }
        }

        // If we can tell at compile time that we will *never* have
        // support, call the fallback directly.
        #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
        {
            self.fallback.find(haystack)
        }

        // Otherwise, we will be run on a machine with or without
        // support, so we perform runtime detection.
        #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"),
                  not(target_feature = "sse4.2")))]
        {
            if is_x86_feature_detected!("sse4.2") {
                unsafe { self.simd.find(haystack) }
            } else {
                self.fallback.find(haystack)
            }
        }
    }
}

/// A convenience type that can be used in a constant or static.
pub type BytesConst = Bytes<fn(u8) -> bool>;

/// Searches a string for a set of ASCII characters. Up to 16
/// characters may be used.
pub struct AsciiChars<F>(Bytes<F>)
where
    F: Fn(u8) -> bool;

impl<F> AsciiChars<F>
where
    F: Fn(u8) -> bool,
{
    /// Manual constructor; prefer using [`ascii_chars!`] instead.
    ///
    /// Provide an array of ASCII bytes to search for, the number of
    /// valid bytes provided, and a closure to use when the SIMD
    /// intrinsics are not available. The closure **must** search for
    /// the same characters as in the array.
    ///
    /// ### Panics
    ///
    /// - If you provide a non-ASCII byte.
    pub /* const */ fn new(chars: [u8; 16], len: i32, fallback: F) -> Self {
        for &b in &chars {
            assert!(b < 128, "Cannot have non-ASCII bytes");
        }
        AsciiChars(Bytes::new(chars, len, fallback))
    }

    /// Searches the string for the first matching ASCII byte in the set.
    #[inline]
    pub fn find(&self, haystack: &str) -> Option<usize> {
        self.0.find(haystack.as_bytes())
    }
}

/// A convenience type that can be used in a constant or static.
pub type AsciiCharsConst = AsciiChars<fn(u8) -> bool>;

#[cfg(all(test, feature = "benchmarks"))]
mod bench {
    extern crate test;

    use super::*;

    lazy_static! {
        static ref SPACE: AsciiCharsConst = ascii_chars!(' ');
        static ref XML_DELIM_3: AsciiCharsConst = ascii_chars!('<', '>', '&');
        static ref XML_DELIM_5: AsciiCharsConst = ascii_chars!('<', '>', '&', '\'', '"');
    }

    fn prefix_string() -> String {
        "a".repeat(5 * 1024 * 1024)
    }

    fn bench_space<F>(b: &mut test::Bencher, f: F)
    where
        F: Fn(&str) -> Option<usize>,
    {
        let mut haystack = prefix_string();
        haystack.push(' ');

        b.iter(|| test::black_box(f(&haystack)));
        b.bytes = haystack.len() as u64;
    }

    #[bench]
    fn space_ascii_chars(b: &mut test::Bencher) {
        bench_space(b, |hs| SPACE.find(hs))
    }

    #[bench]
    fn space_stdlib_find_string(b: &mut test::Bencher) {
        bench_space(b, |hs| hs.find(" "))
    }

    #[bench]
    fn space_stdlib_find_char(b: &mut test::Bencher) {
        bench_space(b, |hs| hs.find(' '))
    }

    #[bench]
    fn space_stdlib_find_char_set(b: &mut test::Bencher) {
        bench_space(b, |hs| hs.find(&[' '][..]))
    }

    #[bench]
    fn space_stdlib_find_closure(b: &mut test::Bencher) {
        bench_space(b, |hs| hs.find(|c| c == ' '))
    }

    #[bench]
    fn space_stdlib_iterator_position(b: &mut test::Bencher) {
        bench_space(b, |hs| hs.as_bytes().iter().position(|&v| v == b' '))
    }

    fn bench_xml_delim_3<F>(b: &mut test::Bencher, f: F)
    where
        F: Fn(&str) -> Option<usize>,
    {
        let mut haystack = prefix_string();
        haystack.push('&');

        b.iter(|| test::black_box(f(&haystack)));
        b.bytes = haystack.len() as u64;
    }

    #[bench]
    fn xml_delim_3_ascii_chars(b: &mut test::Bencher) {
        bench_xml_delim_3(b, |hs| XML_DELIM_3.find(hs))
    }

    #[bench]
    fn xml_delim_3_stdlib_find_char_set(b: &mut test::Bencher) {
        bench_xml_delim_3(b, |hs| hs.find(&['<', '>', '&'][..]))
    }

    #[bench]
    fn xml_delim_3_stdlib_find_char_closure(b: &mut test::Bencher) {
        bench_xml_delim_3(b, |hs| hs.find(|c| c == '<' || c == '>' || c == '&'))
    }

    #[bench]
    fn xml_delim_3_stdlib_iterator_position(b: &mut test::Bencher) {
        bench_xml_delim_3(b, |hs| {
            hs.as_bytes()
                .iter()
                .position(|&c| c == b'<' || c == b'>' || c == b'&')
        })
    }

    fn bench_xml_delim_5<F>(b: &mut test::Bencher, f: F)
    where
        F: Fn(&str) -> Option<usize>,
    {
        let mut haystack = prefix_string();
        haystack.push('"');

        b.iter(|| test::black_box(f(&haystack)));
        b.bytes = haystack.len() as u64;
    }

    #[bench]
    fn xml_delim_5_ascii_chars(b: &mut test::Bencher) {
        bench_xml_delim_5(b, |hs| XML_DELIM_5.find(hs))
    }

    #[bench]
    fn xml_delim_5_stdlib_find_char_set(b: &mut test::Bencher) {
        bench_xml_delim_5(b, |hs| hs.find(&['<', '>', '&', '\'', '"'][..]))
    }

    #[bench]
    fn xml_delim_5_stdlib_find_char_closure(b: &mut test::Bencher) {
        bench_xml_delim_5(b, |hs| {
            hs.find(|c| c == '<' || c == '>' || c == '&' || c == '\'' || c == '"')
        })
    }

    #[bench]
    fn xml_delim_5_stdlib_iterator_position(b: &mut test::Bencher) {
        bench_xml_delim_3(b, |hs| {
            hs.as_bytes()
                .iter()
                .position(|&c| c == b'<' || c == b'>' || c == b'&' || c == b'\'' || c == b'"')
        })
    }
}
<commit_msg>Extract a macro to abstract dispatching to SIMD/fallback code<commit_after>#![cfg_attr(feature = "pattern", feature(pattern))]
#![cfg_attr(feature = "benchmarks", feature(test))]

//! A tiny library to efficiently search strings for sets of ASCII
//! characters or byte slices for sets of bytes.
//!
//! ## Examples
//!
//! ### Searching for a set of ASCII characters
//!
//! ```rust
//! #[macro_use]
//! extern crate jetscii;
//!
//! fn main() {
//!     let part_number = "86-J52:rev1";
//!     let first = ascii_chars!('-', ':').find(part_number);
//!     assert_eq!(first, Some(2));
//! }
//! ```
//!
//! ### Searching for a set of bytes
//!
//! ```rust
//! #[macro_use]
//! extern crate jetscii;
//!
//! fn main() {
//!     let raw_data = [0x00, 0x01, 0x10, 0xFF, 0x42];
//!     let first = bytes!(0x01, 0x10).find(&raw_data);
//!     assert_eq!(first, Some(1));
//! }
//! ```
//!
//! ## Using the pattern API
//!
//! If this crate is compiled with the unstable `pattern` feature
//! flag, [`AsciiChars`] will implement the
//! [`Pattern`][std::str::pattern::Pattern] trait, allowing it to be
//! used with many traditional methods.
//!
//! ```
//! # #[cfg(feature = "pattern")]
//! #[macro_use]
//! extern crate jetscii;
//!
//! fn main() {
//! # #[cfg(feature = "pattern")] {
//!     let part_number = "86-J52:rev1";
//!     let parts: Vec<_> = part_number.split(ascii_chars!('-', ':')).collect();
//!     assert_eq!(&parts, &["86", "J52", "rev1"]);
//! # }
//! }
//! ```
//!
//! ## What's so special about this library?
//!
//! We use a particular set of x86-64 SSE 4.2 instructions (`PCMPESTRI`
//! and `PCMPESTRM`) to gain great speedups. This method stays fast even
//! when searching for a byte in a set of up to 16 choices.
//!
//! When the `PCMPxSTRx` instructions are not available, we fall back to
//! reasonably fast but universally-supported methods.
//!
//! ## Benchmarks
//!
//! ### Single character
//!
//! Searching a 5MiB string of `a`s with a single space at the end for a space:
//!
//! | Method                                                      | Speed          |
//! |-------------------------------------------------------------|----------------|
//! | ascii_chars!(' ').find(s)                      | 5882 MB/s      |
//! | s.as_bytes().iter().position(\|&c\| c == b' ') | 1514 MB/s      |
//! | s.find(" ")                                    | 644 MB/s       |
//! | s.find(&[' '][..])                             | 630 MB/s       |
//! | **s.find(' ')**                                | **10330 MB/s** |
//! | s.find(\|c\| c == ' ')                         | 786 MB/s       |
//!
//! ### Set of 3 characters
//!
//! Searching a 5MiB string of `a`s with a single ampersand at the end for `<`, `>`, and `&`:
//!
//! | Method                                                      | Speed         |
//! |-------------------------------------------------------------|---------------|
//! | **ascii_chars!(/\* ... \*/).find(s)**          | **6238 MB/s** |
//! | s.as_bytes().iter().position(\|&c\| /* ... */) | 1158 MB/s     |
//! | s.find(&[/* ... */][..])                       | 348 MB/s      |
//! | s.find(\|c\| /* ... */))                       | 620 MB/s      |
//!
//! ### Set of 5 characters
//!
//! Searching a 5MiB string of `a`s with a single ampersand at the end for `<`, `>`, `&`, `'`, and `"`:
//!
//! | Method                                                      | Speed         |
//! |-------------------------------------------------------------|---------------|
//! | **ascii_chars!(/\* ... \*/).find(s)**          | **6303 MB/s** |
//! | s.as_bytes().iter().position(\|&c\| /* ... */) | 485 MB/s      |
//! | s.find(&[/* ... */][..]))                      | 282 MB/s      |
//! | s.find(\|c\| /* ... */)                        | 785 MB/s      |

#[cfg(test)]
#[macro_use]
extern crate lazy_static;
#[cfg(test)]
extern crate libc;
#[cfg(test)]
extern crate quickcheck;

use std::marker::PhantomData;

include!(concat!(env!("OUT_DIR"), "/src/macros.rs"));

#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
mod simd;

#[cfg(not(target_feature = "sse4.2"))]
mod fallback;

#[cfg(feature = "pattern")]
mod pattern;

macro_rules! dispatch {
    (simd: $simd:expr,fallback: $fallback:expr,) => {
        // If we can tell at compile time that we have support,
        // call the optimized code directly.
        #[cfg(target_feature = "sse4.2")]
        {
            $simd
        }

        // If we can tell at compile time that we will *never* have
        // support, call the fallback directly.
        #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
        {
            $fallback
        }

        // Otherwise, we will be run on a machine with or without
        // support, so we perform runtime detection.
        #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"),
                  not(target_feature = "sse4.2")))]
        {
            if is_x86_feature_detected!("sse4.2") {
                $simd
            } else {
                $fallback
            }
        }
    };
}

/// Searches a slice for a set of bytes. Up to 16 bytes may be used.
pub struct Bytes<F>
where
    F: Fn(u8) -> bool,
{
    // Include this implementation only when compiling for x86_64 as
    // that's the only platform that we support.
    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
    simd: simd::Bytes,

    // If we are *guaranteed* to have SSE 4.2, then there's no reason
    // to have this implementation.
    #[cfg(not(target_feature = "sse4.2"))]
    fallback: fallback::Bytes<F>,

    // Since we might not use the fallback implementation, we add this
    // to avoid unused type parameters.
    _fallback: PhantomData<F>,
}

impl<F> Bytes<F>
where
    F: Fn(u8) -> bool,
{
    /// Manual constructor; prefer using [`bytes!`] instead.
    ///
    /// Provide an array of bytes to search for, the number of
    /// valid bytes provided, and a closure to use when the SIMD
    /// intrinsics are not available. The closure **must** search for
    /// the same bytes as in the array.
    #[allow(unused_variables)]
    pub /* const */ fn new(bytes: [u8; 16], len: i32, fallback: F) -> Self {
        Bytes {
            #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
            simd: simd::Bytes::new(bytes, len),

            #[cfg(not(target_feature = "sse4.2"))]
            fallback: fallback::Bytes::new(fallback),

            _fallback: PhantomData,
        }
    }

    /// Searches the slice for the first matching byte in the set.
    #[inline]
    pub fn find(&self, haystack: &[u8]) -> Option<usize> {
        dispatch! {
            simd: unsafe { self.simd.find(haystack) },
            fallback: self.fallback.find(haystack),
        }
    }
}

/// A convenience type that can be used in a constant or static.
pub type BytesConst = Bytes<fn(u8) -> bool>;

/// Searches a string for a set of ASCII characters. Up to 16
/// characters may be used.
pub struct AsciiChars<F>(Bytes<F>)
where
    F: Fn(u8) -> bool;

impl<F> AsciiChars<F>
where
    F: Fn(u8) -> bool,
{
    /// Manual constructor; prefer using [`ascii_chars!`] instead.
    ///
    /// Provide an array of ASCII bytes to search for, the number of
    /// valid bytes provided, and a closure to use when the SIMD
    /// intrinsics are not available. The closure **must** search for
    /// the same characters as in the array.
    ///
    /// ### Panics
    ///
    /// - If you provide a non-ASCII byte.
    pub /* const */ fn new(chars: [u8; 16], len: i32, fallback: F) -> Self {
        for &b in &chars {
            assert!(b < 128, "Cannot have non-ASCII bytes");
        }
        AsciiChars(Bytes::new(chars, len, fallback))
    }

    /// Searches the string for the first matching ASCII byte in the set.
    #[inline]
    pub fn find(&self, haystack: &str) -> Option<usize> {
        self.0.find(haystack.as_bytes())
    }
}

/// A convenience type that can be used in a constant or static.
pub type AsciiCharsConst = AsciiChars<fn(u8) -> bool>;

#[cfg(all(test, feature = "benchmarks"))]
mod bench {
    extern crate test;

    use super::*;

    lazy_static! {
        static ref SPACE: AsciiCharsConst = ascii_chars!(' ');
        static ref XML_DELIM_3: AsciiCharsConst = ascii_chars!('<', '>', '&');
        static ref XML_DELIM_5: AsciiCharsConst = ascii_chars!('<', '>', '&', '\'', '"');
    }

    fn prefix_string() -> String {
        "a".repeat(5 * 1024 * 1024)
    }

    fn bench_space<F>(b: &mut test::Bencher, f: F)
    where
        F: Fn(&str) -> Option<usize>,
    {
        let mut haystack = prefix_string();
        haystack.push(' ');

        b.iter(|| test::black_box(f(&haystack)));
        b.bytes = haystack.len() as u64;
    }

    #[bench]
    fn space_ascii_chars(b: &mut test::Bencher) {
        bench_space(b, |hs| SPACE.find(hs))
    }

    #[bench]
    fn space_stdlib_find_string(b: &mut test::Bencher) {
        bench_space(b, |hs| hs.find(" "))
    }

    #[bench]
    fn space_stdlib_find_char(b: &mut test::Bencher) {
        bench_space(b, |hs| hs.find(' '))
    }

    #[bench]
    fn space_stdlib_find_char_set(b: &mut test::Bencher) {
        bench_space(b, |hs| hs.find(&[' '][..]))
    }

    #[bench]
    fn space_stdlib_find_closure(b: &mut test::Bencher) {
        bench_space(b, |hs| hs.find(|c| c == ' '))
    }

    #[bench]
    fn space_stdlib_iterator_position(b: &mut test::Bencher) {
        bench_space(b, |hs| hs.as_bytes().iter().position(|&v| v == b' '))
    }

    fn bench_xml_delim_3<F>(b: &mut test::Bencher, f: F)
    where
        F: Fn(&str) -> Option<usize>,
    {
        let mut haystack = prefix_string();
        haystack.push('&');

        b.iter(|| test::black_box(f(&haystack)));
        b.bytes = haystack.len() as u64;
    }

    #[bench]
    fn xml_delim_3_ascii_chars(b: &mut test::Bencher) {
        bench_xml_delim_3(b, |hs| XML_DELIM_3.find(hs))
    }

    #[bench]
    fn xml_delim_3_stdlib_find_char_set(b: &mut test::Bencher) {
        bench_xml_delim_3(b, |hs| hs.find(&['<', '>', '&'][..]))
    }

    #[bench]
    fn xml_delim_3_stdlib_find_char_closure(b: &mut test::Bencher) {
        bench_xml_delim_3(b, |hs| hs.find(|c| c == '<' || c == '>' || c == '&'))
    }

    #[bench]
    fn xml_delim_3_stdlib_iterator_position(b: &mut test::Bencher) {
        bench_xml_delim_3(b, |hs| {
            hs.as_bytes()
                .iter()
                .position(|&c| c == b'<' || c == b'>' || c == b'&')
        })
    }

    fn bench_xml_delim_5<F>(b: &mut test::Bencher, f: F)
    where
        F: Fn(&str) -> Option<usize>,
    {
        let mut haystack = prefix_string();
        haystack.push('"');

        b.iter(|| test::black_box(f(&haystack)));
        b.bytes = haystack.len() as u64;
    }

    #[bench]
    fn xml_delim_5_ascii_chars(b: &mut test::Bencher) {
        bench_xml_delim_5(b, |hs| XML_DELIM_5.find(hs))
    }

    #[bench]
    fn xml_delim_5_stdlib_find_char_set(b: &mut test::Bencher) {
        bench_xml_delim_5(b, |hs| hs.find(&['<', '>', '&', '\'', '"'][..]))
    }

    #[bench]
    fn xml_delim_5_stdlib_find_char_closure(b: &mut test::Bencher) {
        bench_xml_delim_5(b, |hs| {
            hs.find(|c| c == '<' || c == '>' || c == '&' || c == '\'' || c == '"')
        })
    }

    #[bench]
    fn xml_delim_5_stdlib_iterator_position(b: &mut test::Bencher) {
        bench_xml_delim_3(b, |hs| {
            hs.as_bytes()
                .iter()
                .position(|&c| c == b'<' || c == b'>' || c == b'&' || c == b'\'' || c == b'"')
        })
    }
}
<|endoftext|>