<commit_before>extern crate libc;
extern crate notify;
extern crate libloading;
extern crate tempdir;

use libloading::Library;
use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::sync::mpsc::{channel, Receiver, Sender};
use notify::{RecommendedWatcher, Watcher, Event};
use tempdir::TempDir;
use std::time::Duration;
use std::thread;
use std::fs;
use std::env;
use std::fmt::Write;

/// Formats dll name on Windows ("test_foo" -> "test_foo.dll")
#[cfg(target_os="windows")]
fn get_dynamiclib_name(name: &str) -> String {
    format!("{}.dll", name)
}

/// Formats dll name on Mac ("test_foo" -> "libtest_foo.dylib")
#[cfg(target_os="macos")]
fn get_dynamiclib_name(name: &str) -> String {
    format!("lib{}.dylib", name)
}

/// Formats dll name on *nix ("test_foo" -> "libtest_foo.so")
#[cfg(any(target_os="linux",
          target_os="freebsd",
          target_os="dragonfly",
          target_os="netbsd",
          target_os="openbsd"))]
fn get_dynamiclib_name(name: &str) -> String {
    format!("lib{}.so", name)
}

pub struct Lib {
    pub lib: Library,
    pub loaded_path: PathBuf,
    pub original_path: Option<PathBuf>,
}

pub struct DynamicReload {
    libs: Vec<Rc<Lib>>,
    watcher: Option<RecommendedWatcher>,
    shadow_dir: Option<TempDir>,
    search_paths: Vec<&'static str>,
    watch_recv: Receiver<Event>,
}

pub enum Search {
    Default,
    Backwards,
}

#[derive(PartialEq)]
pub enum UsePlatformName {
    No,
    Yes
}

// Test

impl DynamicReload {

    ///
    ///
    pub fn new(search_paths: Option<Vec<&'static str>>, shadow_dir: Option<&'static str>, _search: Search) -> DynamicReload {
        let (tx, rx) = channel();
        DynamicReload {
            libs: Vec::new(),
            watcher: Self::get_watcher(tx),
            shadow_dir: Self::get_temp_dir(shadow_dir),
            watch_recv: rx,
            search_paths: Self::get_search_paths(search_paths),
        }
    }

    ///
    /// Add a library to be loaded and to be reloaded once updated.
    /// If UsePlatformName is set to Yes the input name will be formatted according
    /// to the standard way libraries looks on that platform examples:
    ///
    /// ```ignore
    /// Windows: foobar -> foobar.dll
    /// Linux:   foobar -> libfoobar.so
    /// Mac:     foobar -> libfoobar.dylib
    /// ````
    ///
    /// If set to no the given inputname will be used as is. This function
    /// will also search for the file in this priority order
    ///
    ///
    /// ```ignore
    /// 1. Current directory
    /// 2. In the search paths (relative to currect directory)
    /// 3. Currect directory of the executable
    /// 4. Serach backwards from executable if Backwards has been set in [new](struct.DynamicReload.html#method.new)
    /// ```
    ///
    pub fn add_library(&mut self, name: &str, name_format: UsePlatformName) -> Result<Rc<Lib>, String> {
        match Self::try_load_library(self, name, name_format) {
            Ok(lib) => {
                if let Some(w) = self.watcher.as_mut() {
                    if let Some(path) = lib.original_path.as_ref() {
                        let _ = w.watch(path);
                    }
                }
                // Bump the ref here as we keep one around to keep track of files that needs to be reloaded
                self.libs.push(lib.clone());
                Ok(lib)
            }
            Err(e) => Err(e),
        }
    }

    ///
    /// Update the will check if a dynamic library needs to be reloaded and if that is the case
    /// then the supplied callback functions will be called
    ///
    /// First the before_reload code. This allows the calling application to performe actions
    /// before the dynamic library is unloaded. This can for example be to save some internal
    /// state that needs to be restorted when relodaded
    ///
    /// After the reloading is comple the post_reload function will be called giving the host
    /// application the possibility to restore any potentially saved state
    ///
    /// If no callbacks are needed use the regular [update](struct.DynamicReload.html#method.update) call instead
    ///
    pub fn update_with_callback<F, T>(&mut self, before_update: F, after_update: F, data: T) where F : Fn(T, &Rc<Lib>) {
        match self.watch_recv.try_recv() {
            Ok(file) => Self::reload_libs(self, file.path.as_ref().unwrap(), before_update, after_update, data),
            _ => (),
        }
    }

    ///
    /// Updates the DynamicReload handler and reloads the dynamic libraries if needed
    ///
    ///
    pub fn update(&self) {
    }

    fn reload_libs<F, T>(&mut self, file_path: &PathBuf, before_update: F, after_update: F, data: T) where F : Fn(T, &Rc<Lib>) {
        let len = self.libs.len();
        for i in (0..len).rev() {
            if Self::should_reload(file_path, &self.libs[i]) {
                Self::reload_lib(self, i, file_path, before_update, after_update, data);
            }
        }
    }

    fn reload_lib<F, T>(&mut self, index: usize, file_path: &PathBuf, before_update: F, after_update: F, data: T)
        where F : Fn(T, &Rc<Lib>) {
        before_update(data, &self.libs[index]);
        self.libs.swap_remove(index);

        match Self::load_library(self, file_path) {
            Ok(lib) => {
                self.libs.push(lib.clone());
                after_update(data, &lib);
            }

            // What should we really do here?
            Err(err) => {
                println!("Unable to reload lib {:?} err {:?}", file_path, err);
            }
        }
    }

    fn try_load_library(&self, name: &str, name_format: UsePlatformName) -> Result<Rc<Lib>, String> {
        if let Some(path) = Self::search_dirs(self, name, name_format) {
            Self::load_library(self, &path)
        } else {
            let mut t = "Unable to find ".to_string();
            t.push_str(name);
            Err(t)
        }
    }

    fn load_library(&self, full_path: &PathBuf) -> Result<Rc<Lib>, String> {
        let path;
        let original_path;

        if let Some(sd) = self.shadow_dir.as_ref() {
            path = sd.path().join(full_path.file_name().unwrap());
            if !Self::try_copy(&full_path, &path) {
                let mut error = "".to_string();
                write!(error, "Unable to copy {:?} to {:?}", full_path, path).unwrap();
                return Err(error);
            }
            original_path = Some(full_path.clone());
        } else {
            original_path = None;
            path = full_path.clone();
        }

        Self::init_library(original_path, path)
    }

    fn init_library(org_path: Option<PathBuf>, path: PathBuf) -> Result<Rc<Lib>, String> {
        match Library::new(&path) {
            Ok(l) => Ok(Rc::new(Lib {
                original_path: org_path,
                loaded_path: path,
                lib: l
            })),
            Err(e) => {
                let mut error = "".to_string();
                write!(error, "Unable to load library {:?}", e).unwrap();
                Err(error)
            }
        }
    }

    fn should_reload(reload_path: &PathBuf, lib: &Lib) -> bool {
        if let Some(p) = lib.original_path.as_ref() {
            if reload_path.to_str().unwrap().contains(p.to_str().unwrap()) {
                return true;
            }
        }

        false
    }

    fn remove_library(&mut self, path: &PathBuf) {

    }

    fn search_dirs(&self, name: &str, name_format: UsePlatformName) -> Option<PathBuf> {
        let lib_name = Self::get_library_name(name, name_format);

        // 1. Serach the current directory
        if let Some(path) = Self::search_current_dir(&lib_name) {
            return Some(path);
        }

        // 2. search the relative paths
        if let Some(path) = Self::search_relative_paths(self, &lib_name) {
            return Some(path);
        }

        // 3. searches in the executable dir and then backwards
        Self::search_backwards_from_exe(&lib_name)
    }

    fn search_current_dir(name: &String) -> Option<PathBuf> {
        Self::is_file(&Path::new(name).to_path_buf())
    }

    fn search_relative_paths(&self, name: &String) -> Option<PathBuf> {
        for p in self.search_paths.iter() {
            let path = Path::new(p).join(name);
            if let Some(file) = Self::is_file(&path) {
                return Some(file);
            }
        }

        None
    }

    fn get_parent_dir(path: &PathBuf) -> Option<PathBuf> {
        match path.parent() {
            Some(p) => Some(p.to_path_buf()),
            _ => None,
        }
    }

    fn search_backwards_from_file(path: &PathBuf, lib_name: &String) -> Option<PathBuf> {
        match Self::get_parent_dir(path) {
            Some(p) => {
                let new_path = Path::new(&p).join(lib_name);
                if Self::is_file(&new_path).is_some() {
                    return Some(new_path);
                }
                Self::search_backwards_from_file(&p, lib_name)
            },
            _ => None,
        }
    }

    fn search_backwards_from_exe(lib_name: &String) -> Option<PathBuf> {
        let exe_path = env::current_exe().unwrap_or(PathBuf::new());
        Self::search_backwards_from_file(&exe_path, lib_name)
    }

    fn get_temp_dir(shadow_dir: Option<&str>) -> Option<TempDir> {
        match shadow_dir {
            Some(dir) => {
                match TempDir::new_in(dir, "shadow_libs") {
                    Ok(td) => Some(td),
                    Err(er) => {
                        println!("Unable to create tempdir {}", er);
                        None
                    }
                }
            }
            _ => None,
        }
    }

    fn is_file(path: &PathBuf) -> Option<PathBuf> {
        match fs::metadata(path) {
            Ok(md) => {
                if md.is_file() {
                    Some(path.clone())
                } else {
                    None
                }
            }
            _ => None,
        }
    }

    //
    // In some cases when a file has been set that it's reloaded it's actually not possible
    // to read from it directly so this code does some testing first to ensure we
    // can actually read from it (by using metadata which does a stat on the file)
    // If we can't read from it we wait for 100 ms before we try again, if we can't
    // do it with in 1 sec we give up
    //
    fn try_copy(src: &Path, dest: &Path) -> bool {
        for _ in 0..10 {
            match fs::metadata(src) {
                Ok(file) => {
                    let len = file.len();
                    if len > 0 {
                        fs::copy(&src, &dest).unwrap();
                        //println!("Copy from {} {}", src.to_str().unwrap(), dest.to_str().unwrap());
                        return true;
                    }
                }
                _ => (),
            }

            thread::sleep(Duration::from_millis(100));
        }

        false
    }

    fn get_watcher(tx: Sender<Event>) -> Option<RecommendedWatcher> {
        match Watcher::new(tx) {
            Ok(watcher) => Some(watcher),
            Err(e) => {
                println!("Unable to create file watcher, no dynamic reloading will be done, error: {:?}", e);
                None
            }
        }
    }

    pub fn get_search_paths(search_paths: Option<Vec<&'static str>>) -> Vec<&'static str> {
        match search_paths {
            Some(paths) => {
                paths.clone()
            },
            None => Vec::new(),
        }
    }

    fn get_library_name(name: &str, name_format: UsePlatformName) -> String {
        if name_format == UsePlatformName::Yes  {
            get_dynamiclib_name(name)
        } else {
            name.to_string()
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::sync::mpsc::channel;
    use std::path::Path;
    use std::env;

    #[test]
    fn test_search_paths_none() {
        assert_eq!(DynamicReload::get_search_paths(None).len(), 0);
    }

    #[test]
    fn test_search_paths_some() {
        assert_eq!(DynamicReload::get_search_paths(Some(vec!["test", "test"])).len(), 2);
    }

    #[test]
    fn test_get_watcher() {
        let (tx, _) = channel();
        // We expect this to always work
        assert!(DynamicReload::get_watcher(tx).is_some());
    }

    #[test]
    fn test_get_temp_dir_fail() {
        assert!(DynamicReload::get_temp_dir(Some("_no_such_dir")).is_none());
    }

    #[test]
    fn test_get_temp_dir_none() {
        assert!(DynamicReload::get_temp_dir(None).is_none());
    }

    #[test]
    fn test_get_temp_dir_ok() {
        assert!(DynamicReload::get_temp_dir(Some("")).is_some());
    }

    #[test]
    fn test_is_file_fail() {
        assert!(DynamicReload::is_file(&Path::new("haz_no_file_with_this_name").to_path_buf()).is_none());
    }

    #[test]
    fn test_is_file_ok() {
        assert!(DynamicReload::is_file(&env::current_exe().unwrap()).is_some());
    }

    #[test]
    #[cfg(target_os="macos")]
    fn test_get_library_name_mac() {
        assert_eq!(DynamicReload::get_library_name("foobar", UsePlatformName::Yes), "libfoobar.dylib");
    }

    #[test]
    fn test_get_library_name() {
        assert_eq!(DynamicReload::get_library_name("foobar", UsePlatformName::No), "foobar");
    }

    #[test]
    fn test_search_backwards_from_file_ok() {
        // While this rely on that we have a Cargo project it should be fine
        assert!(DynamicReload::search_backwards_from_exe(&"Cargo.toml".to_string()).is_some());
    }

    #[test]
    fn test_search_backwards_from_file_fail() {
        assert!(DynamicReload::search_backwards_from_exe(&"_no_such_file".to_string()).is_none());
    }
}
<commit_msg>Fixed compile errors<commit_after>extern crate libc;
extern crate notify;
extern crate libloading;
extern crate tempdir;

use libloading::Library;
use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::sync::mpsc::{channel, Receiver, Sender};
use notify::{RecommendedWatcher, Watcher, Event};
use tempdir::TempDir;
use std::time::Duration;
use std::thread;
use std::fs;
use std::env;
use std::fmt::Write;

/// Formats dll name on Windows ("test_foo" -> "test_foo.dll")
#[cfg(target_os="windows")]
fn get_dynamiclib_name(name: &str) -> String {
    format!("{}.dll", name)
}

/// Formats dll name on Mac ("test_foo" -> "libtest_foo.dylib")
#[cfg(target_os="macos")]
fn get_dynamiclib_name(name: &str) -> String {
    format!("lib{}.dylib", name)
}

/// Formats dll name on *nix ("test_foo" -> "libtest_foo.so")
#[cfg(any(target_os="linux",
          target_os="freebsd",
          target_os="dragonfly",
          target_os="netbsd",
          target_os="openbsd"))]
fn get_dynamiclib_name(name: &str) -> String {
    format!("lib{}.so", name)
}

pub struct Lib {
    pub lib: Library,
    pub loaded_path: PathBuf,
    pub original_path: Option<PathBuf>,
}

pub struct DynamicReload {
    libs: Vec<Rc<Lib>>,
    watcher: Option<RecommendedWatcher>,
    shadow_dir: Option<TempDir>,
    search_paths: Vec<&'static str>,
    watch_recv: Receiver<Event>,
}

pub enum Search {
    Default,
    Backwards,
}

#[derive(PartialEq)]
pub enum UsePlatformName {
    No,
    Yes
}

// Test

impl DynamicReload {

    ///
    ///
    pub fn new(search_paths: Option<Vec<&'static str>>, shadow_dir: Option<&'static str>, _search: Search) -> DynamicReload {
        let (tx, rx) = channel();
        DynamicReload {
            libs: Vec::new(),
            watcher: Self::get_watcher(tx),
            shadow_dir: Self::get_temp_dir(shadow_dir),
            watch_recv: rx,
            search_paths: Self::get_search_paths(search_paths),
        }
    }

    ///
    /// Add a library to be loaded and to be reloaded once updated.
    /// If UsePlatformName is set to Yes the input name will be formatted according
    /// to the standard way libraries looks on that platform examples:
    ///
    /// ```ignore
    /// Windows: foobar -> foobar.dll
    /// Linux:   foobar -> libfoobar.so
    /// Mac:     foobar -> libfoobar.dylib
    /// ````
    ///
    /// If set to no the given inputname will be used as is. This function
    /// will also search for the file in this priority order
    ///
    ///
    /// ```ignore
    /// 1. Current directory
    /// 2. In the search paths (relative to currect directory)
    /// 3. Currect directory of the executable
    /// 4. Serach backwards from executable if Backwards has been set in [new](struct.DynamicReload.html#method.new)
    /// ```
    ///
    pub fn add_library(&mut self, name: &str, name_format: UsePlatformName) -> Result<Rc<Lib>, String> {
        match Self::try_load_library(self, name, name_format) {
            Ok(lib) => {
                if let Some(w) = self.watcher.as_mut() {
                    if let Some(path) = lib.original_path.as_ref() {
                        let _ = w.watch(path);
                    }
                }
                // Bump the ref here as we keep one around to keep track of files that needs to be reloaded
                self.libs.push(lib.clone());
                Ok(lib)
            }
            Err(e) => Err(e),
        }
    }

    ///
    /// Update the will check if a dynamic library needs to be reloaded and if that is the case
    /// then the supplied callback functions will be called
    ///
    /// First the before_reload code. This allows the calling application to performe actions
    /// before the dynamic library is unloaded. This can for example be to save some internal
    /// state that needs to be restorted when relodaded
    ///
    /// After the reloading is comple the post_reload function will be called giving the host
    /// application the possibility to restore any potentially saved state
    ///
    /// If no callbacks are needed use the regular [update](struct.DynamicReload.html#method.update) call instead
    ///
    pub fn update_with_callback<F, T>(&mut self, ref before_update: F, ref after_update: F, data: &mut T) where F : Fn(&mut T, &Rc<Lib>) {
        match self.watch_recv.try_recv() {
            Ok(file) => Self::reload_libs(self, file.path.as_ref().unwrap(), before_update, after_update, data),
            _ => (),
        }
    }

    ///
    /// Updates the DynamicReload handler and reloads the dynamic libraries if needed
    ///
    ///
    pub fn update(&self) {
    }

    fn reload_libs<F, T>(&mut self, file_path: &PathBuf, ref before_update: F, ref after_update: F, data: &mut T) where F : Fn(&mut T, &Rc<Lib>) {
        let len = self.libs.len();
        for i in (0..len).rev() {
            if Self::should_reload(file_path, &self.libs[i]) {
                Self::reload_lib(self, i, file_path, before_update, after_update, data);
            }
        }
    }

    fn reload_lib<F, T>(&mut self, index: usize, file_path: &PathBuf, ref before_update: F, ref after_update: F, data: &mut T) where F : Fn(&mut T, &Rc<Lib>) {
        before_update(data, &self.libs[index]);
        self.libs.swap_remove(index);

        match Self::load_library(self, file_path) {
            Ok(lib) => {
                self.libs.push(lib.clone());
                after_update(data, &lib);
            }

            // What should we really do here?
            Err(err) => {
                println!("Unable to reload lib {:?} err {:?}", file_path, err);
            }
        }
    }

    fn try_load_library(&self, name: &str, name_format: UsePlatformName) -> Result<Rc<Lib>, String> {
        if let Some(path) = Self::search_dirs(self, name, name_format) {
            Self::load_library(self, &path)
        } else {
            let mut t = "Unable to find ".to_string();
            t.push_str(name);
            Err(t)
        }
    }

    fn load_library(&self, full_path: &PathBuf) -> Result<Rc<Lib>, String> {
        let path;
        let original_path;

        if let Some(sd) = self.shadow_dir.as_ref() {
            path = sd.path().join(full_path.file_name().unwrap());
            if !Self::try_copy(&full_path, &path) {
                let mut error = "".to_string();
                write!(error, "Unable to copy {:?} to {:?}", full_path, path).unwrap();
                return Err(error);
            }
            original_path = Some(full_path.clone());
        } else {
            original_path = None;
            path = full_path.clone();
        }

        Self::init_library(original_path, path)
    }

    fn init_library(org_path: Option<PathBuf>, path: PathBuf) -> Result<Rc<Lib>, String> {
        match Library::new(&path) {
            Ok(l) => Ok(Rc::new(Lib {
                original_path: org_path,
                loaded_path: path,
            lib: l
            })),
            Err(e) => {
                let mut error = "".to_string();
                write!(error, "Unable to load library {:?}", e).unwrap();
                Err(error)
            }
        }
    }

    fn should_reload(reload_path: &PathBuf, lib: &Lib) -> bool {
        if let Some(p) = lib.original_path.as_ref() {
            if reload_path.to_str().unwrap().contains(p.to_str().unwrap()) {
                return true;
            }
        }

        false
    }

    fn search_dirs(&self, name: &str, name_format: UsePlatformName) -> Option<PathBuf> {
        let lib_name = Self::get_library_name(name, name_format);

        // 1. Serach the current directory
        if let Some(path) = Self::search_current_dir(&lib_name) {
            return Some(path);
        }

        // 2. search the relative paths
        if let Some(path) = Self::search_relative_paths(self, &lib_name) {
            return Some(path);
        }

        // 3. searches in the executable dir and then backwards
        Self::search_backwards_from_exe(&lib_name)
    }

    fn search_current_dir(name: &String) -> Option<PathBuf> {
        Self::is_file(&Path::new(name).to_path_buf())
    }

    fn search_relative_paths(&self, name: &String) -> Option<PathBuf> {
        for p in self.search_paths.iter() {
            let path = Path::new(p).join(name);
            if let Some(file) = Self::is_file(&path) {
                return Some(file);
            }
        }

        None
    }

    fn get_parent_dir(path: &PathBuf) -> Option<PathBuf> {
        match path.parent() {
            Some(p) => Some(p.to_path_buf()),
            _ => None,
        }
    }

    fn search_backwards_from_file(path: &PathBuf, lib_name: &String) -> Option<PathBuf> {
        match Self::get_parent_dir(path) {
            Some(p) => {
                let new_path = Path::new(&p).join(lib_name);
                if Self::is_file(&new_path).is_some() {
                    return Some(new_path);
                }
                Self::search_backwards_from_file(&p, lib_name)
            },
            _ => None,
        }
    }

    fn search_backwards_from_exe(lib_name: &String) -> Option<PathBuf> {
        let exe_path = env::current_exe().unwrap_or(PathBuf::new());
        Self::search_backwards_from_file(&exe_path, lib_name)
    }

    fn get_temp_dir(shadow_dir: Option<&str>) -> Option<TempDir> {
        match shadow_dir {
            Some(dir) => {
                match TempDir::new_in(dir, "shadow_libs") {
                    Ok(td) => Some(td),
                    Err(er) => {
                        println!("Unable to create tempdir {}", er);
                        None
                    }
                }
            }
            _ => None,
        }
    }

    fn is_file(path: &PathBuf) -> Option<PathBuf> {
        match fs::metadata(path) {
            Ok(md) => {
                if md.is_file() {
                    Some(path.clone())
                } else {
                    None
                }
            }
            _ => None,
        }
    }

    //
    // In some cases when a file has been set that it's reloaded it's actually not possible
    // to read from it directly so this code does some testing first to ensure we
    // can actually read from it (by using metadata which does a stat on the file)
    // If we can't read from it we wait for 100 ms before we try again, if we can't
    // do it with in 1 sec we give up
    //
    fn try_copy(src: &Path, dest: &Path) -> bool {
        for _ in 0..10 {
            match fs::metadata(src) {
                Ok(file) => {
                    let len = file.len();
                    if len > 0 {
                        fs::copy(&src, &dest).unwrap();
                        //println!("Copy from {} {}", src.to_str().unwrap(), dest.to_str().unwrap());
                        return true;
                    }
                }
                _ => (),
            }

            thread::sleep(Duration::from_millis(100));
        }

        false
    }

    fn get_watcher(tx: Sender<Event>) -> Option<RecommendedWatcher> {
        match Watcher::new(tx) {
            Ok(watcher) => Some(watcher),
            Err(e) => {
                println!("Unable to create file watcher, no dynamic reloading will be done, error: {:?}", e);
                None
            }
        }
    }

    pub fn get_search_paths(search_paths: Option<Vec<&'static str>>) -> Vec<&'static str> {
        match search_paths {
            Some(paths) => {
                paths.clone()
            },
            None => Vec::new(),
        }
    }

    fn get_library_name(name: &str, name_format: UsePlatformName) -> String {
        if name_format == UsePlatformName::Yes  {
            get_dynamiclib_name(name)
        } else {
            name.to_string()
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::sync::mpsc::channel;
    use std::path::Path;
    use std::env;

    #[test]
    fn test_search_paths_none() {
        assert_eq!(DynamicReload::get_search_paths(None).len(), 0);
    }

    #[test]
    fn test_search_paths_some() {
        assert_eq!(DynamicReload::get_search_paths(Some(vec!["test", "test"])).len(), 2);
    }

    #[test]
    fn test_get_watcher() {
        let (tx, _) = channel();
        // We expect this to always work
        assert!(DynamicReload::get_watcher(tx).is_some());
    }

    #[test]
    fn test_get_temp_dir_fail() {
        assert!(DynamicReload::get_temp_dir(Some("_no_such_dir")).is_none());
    }

    #[test]
    fn test_get_temp_dir_none() {
        assert!(DynamicReload::get_temp_dir(None).is_none());
    }

    #[test]
    fn test_get_temp_dir_ok() {
        assert!(DynamicReload::get_temp_dir(Some("")).is_some());
    }

    #[test]
    fn test_is_file_fail() {
        assert!(DynamicReload::is_file(&Path::new("haz_no_file_with_this_name").to_path_buf()).is_none());
    }

    #[test]
    fn test_is_file_ok() {
        assert!(DynamicReload::is_file(&env::current_exe().unwrap()).is_some());
    }

    #[test]
    #[cfg(target_os="macos")]
    fn test_get_library_name_mac() {
        assert_eq!(DynamicReload::get_library_name("foobar", UsePlatformName::Yes), "libfoobar.dylib");
    }

    #[test]
    fn test_get_library_name() {
        assert_eq!(DynamicReload::get_library_name("foobar", UsePlatformName::No), "foobar");
    }

    #[test]
    fn test_search_backwards_from_file_ok() {
        // While this rely on that we have a Cargo project it should be fine
        assert!(DynamicReload::search_backwards_from_exe(&"Cargo.toml".to_string()).is_some());
    }

    #[test]
    fn test_search_backwards_from_file_fail() {
        assert!(DynamicReload::search_backwards_from_exe(&"_no_such_file".to_string()).is_none());
    }
}
<|endoftext|>


<commit_before>pub mod stm {
    use std::result::Result as Either;
    use std::mem::{transmute, forget, uninitialized};
    use std::ptr::swap;
    /// We cannot make a real monad, so this is going to be a fake linear type a la Clean.
    pub struct Result(Either);

    impl Result {
        pub fn ret(a: A) -> Result {
            Result(Either::Ok(a))
        }

        pub fn abort() -> Result {
            Result(Either::Err(()))
        }

        /// just don't look
        fn spec_move(mut self) -> Either {
            unsafe {
                let mut x = uninitialized();
                swap(&mut x, transmute(&mut self));
                forget(self);
                x
            }
        }

        pub fn bind(self, f : F) -> Result where F : FnOnce(A) -> Result {
            Result(self.spec_move().and_then(|x| f(x).spec_move()))
        }
    }

    impl Drop for Result {
        // Best we can do absent a true linear type
        fn drop(&mut self) {
            panic!()
        }
    }

    pub trait GlobalState {
        /// A hook for transaction-local state
        type LocalState;

        fn run(&self, F) -> A
            where F: FnMut(&mut Self::LocalState) -> Result;

        fn or_else(&self, F, G) -> Result
            where F: FnMut(&mut Self::LocalState) -> Result,
                  G: FnMut(&mut Self::LocalState) -> Result;
    }

    pub trait TVar {
        /// A wrapper around an object whose access is mediated by the STM implementation
        type LocalState;

        fn read(&mut self, Self::LocalState) -> Result;

        fn write(&mut self, Self::LocalState, A) -> Result<()>;
    }
}
pub mod no_rec {
    use std::sync::atomic::{AtomicUsize, Ordering};
    use std::vec::Vec;
    use std::collections::HashMap;
    use std::cell::UnsafeCell;
    use std::marker::PhantomData;
    use stm::{self, Result};

    pub struct GlobalState {
        version : AtomicUsize
    }

    #[derive(Hash, Eq, PartialEq, Debug, Copy, Clone)]
    /// Lies... Lies
    struct TVarAddr<'a, 'b : 'a>(*mut TVar<'b, ()>, PhantomData<&'a TVar<'b, ()>>);

    impl<'a, 'b : 'a> TVarAddr<'a, 'b> {
        fn version(self) -> &'a AtomicUsize {
            unsafe { &(*self.0).version }
        }
    }

    pub struct LocalState<'v, 'g : 'v> {
        /// the norec paper has the reads stored as a list of <address, value> pairs. For us it
        /// would probably be better to have versioned TVars. That way we always have a fast
        /// comparison operation and avoid doing extra dynamic dispatch when validating reads.
        reads : Vec<(TVarAddr<'v, 'g>, usize)>,

        /// for writes we will probably have to cast unsafely to get this to work. TVars just hold
        /// arbitrary types and we only have one table.  XXX: gross
        writes : HashMap<TVarAddr<'v, 'g>, Box<[u8]>>,

        /// STM environment we are using
        global : &'g GlobalState,

        /// last version at which our view of the global state was valid
        snapshot : usize
    }

    impl<'v, 'g : 'v> LocalState<'v, 'g> {
        /*
         * while (true)
         * time = global lock
         * if ((time&1)!=0)
         *  continue
         * for each (addr, val) in reads
         *      if (∗addr != val)
         *          TXAbort() // abort will longjmp if (time == global lock)
         * return time
         */
        fn validate(&mut self) -> Result<usize> {
            loop {
                let time = self.global.version.load(Ordering::SeqCst);
                if (time & 1) == 0 {
                    continue;
                }
                for &(addr, version) in &self.reads {
                    unsafe {
                        if addr.version().load(Ordering::SeqCst) != version {
                            return Result::abort();
                        }
                    }
                }
                return Result::ret(time);
            }
        }
    }

    pub struct TVar<'a, A> {
        version : AtomicUsize,
        val : UnsafeCell,
        global : &'a GlobalState
    }

    impl<'a, A> TVar<'a, A> {
        unsafe fn get_val(&self) -> &A {
            &*self.val.get()
        }

        unsafe fn get_val_mut(&self) -> &mut A {
            &mut *self.val.get()
        }
    }

    //impl<'a, A> stm::TVar for TVar<'a, A> {
    //}
}

#[test]
fn it_works() {
}
<commit_msg>cleanup<commit_after>#![allow(dead_code)]
pub mod stm {
    use std::result::Result as Either;
    use std::mem::{transmute, forget, uninitialized};
    use std::ptr::swap;
    /// We cannot make a real monad, so this is going to be a fake linear type a la Clean.
    pub struct Result(Either);

    impl Result {
        pub fn ret(a: A) -> Result {
            Result(Either::Ok(a))
        }

        pub fn abort() -> Result {
            Result(Either::Err(()))
        }

        /// just don't look
        fn spec_move(mut self) -> Either {
            unsafe {
                let mut x = uninitialized();
                swap(&mut x, transmute(&mut self));
                forget(self);
                x
            }
        }

        pub fn bind(self, f : F) -> Result where F : FnOnce(A) -> Result {
            Result(self.spec_move().and_then(|x| f(x).spec_move()))
        }
    }

    impl Drop for Result {
        // Best we can do absent a true linear type
        fn drop(&mut self) {
            panic!()
        }
    }

    pub trait GlobalState {
        /// A hook for transaction-local state
        type LocalState;

        fn run(&self, F) -> A
            where F: FnMut(&mut Self::LocalState) -> Result;

        fn or_else(&self, F, G) -> Result
            where F: FnMut(&mut Self::LocalState) -> Result,
                  G: FnMut(&mut Self::LocalState) -> Result;
    }

    pub trait TVar {
        /// A wrapper around an object whose access is mediated by the STM implementation
        type LocalState;

        fn read(&mut self, Self::LocalState) -> Result;

        fn write(&mut self, Self::LocalState, A) -> Result<()>;
    }
}
pub mod no_rec {
    use std::sync::atomic::{AtomicUsize, Ordering};
    use std::vec::Vec;
    use std::collections::HashMap;
    use std::cell::UnsafeCell;
    use std::marker::PhantomData;
    use stm::{Result};

    pub struct GlobalState {
        version : AtomicUsize
    }

    #[allow(raw_pointer_derive)]
    #[derive(Hash, Eq, PartialEq, Debug, Copy, Clone)]
    /// Lies... Lies
    struct TVarAddr<'a, 'b : 'a>(*mut TVar<'b, ()>, PhantomData<&'a TVar<'b, ()>>);

    impl<'a, 'b : 'a> TVarAddr<'a, 'b> {
        fn version(self) -> &'a AtomicUsize {
            unsafe { &(*self.0).version }
        }
    }

    pub struct LocalState<'v, 'g : 'v> {
        /// the norec paper has the reads stored as a list of <address, value> pairs. For us it
        /// would probably be better to have versioned TVars. That way we always have a fast
        /// comparison operation and avoid doing extra dynamic dispatch when validating reads.
        reads : Vec<(TVarAddr<'v, 'g>, usize)>,

        /// for writes we will probably have to cast unsafely to get this to work. TVars just hold
        /// arbitrary types and we only have one table.  XXX: gross
        writes : HashMap<TVarAddr<'v, 'g>, Box<[u8]>>,

        /// STM environment we are using
        global : &'g GlobalState,

        /// last version at which our view of the global state was valid
        snapshot : usize
    }

    impl<'v, 'g : 'v> LocalState<'v, 'g> {
        /*
         * while (true)
         * time = global lock
         * if ((time&1)!=0)
         *  continue
         * for each (addr, val) in reads
         *      if (∗addr != val)
         *          TXAbort() // abort will longjmp if (time == global lock)
         * return time
         */
        fn validate(&mut self) -> Result<usize> {
            loop {
                let time = self.global.version.load(Ordering::SeqCst);
                if (time & 1) == 0 {
                    continue;
                }
                for &(addr, version) in &self.reads {
                    if addr.version().load(Ordering::SeqCst) != version {
                        return Result::abort();
                    }
                }
                return Result::ret(time);
            }
        }
    }

    pub struct TVar<'a, A> {
        version : AtomicUsize,
        val : UnsafeCell,
        global : &'a GlobalState
    }

    impl<'a, A> TVar<'a, A> {
        unsafe fn get_val(&self) -> &A {
            &*self.val.get()
        }

        unsafe fn get_val_mut(&self) -> &mut A {
            &mut *self.val.get()
        }
    }

    //impl<'a, A> stm::TVar for TVar<'a, A> {
    //}
}

#[test]
fn it_works() {
}
<|endoftext|>


<commit_before>#[derive(Debug, PartialEq, Eq)]
pub struct GitStatus {
    oid: String,
    branch: String,
    upstream: Option<String>,
    ahead: bool,
    behind: bool,
    staged: bool,
    unstaged: bool,
    untracked: bool,
    ignored: bool,
}

impl GitStatus {
    pub fn new(status_txt: &str) -> Result<GitStatus, &'static str> {
        let mut s = GitStatus {
            oid: String::new(),
            branch: String::new(),
            upstream: None,
            ahead: false,
            behind: false,
            staged: false,
            unstaged: false,
            untracked: false,
            ignored: false,
        };
        for line in status_txt.lines() {
            let mut words = line.split(' ');
            match words.next() {
                Some("#") => {
                    match words.next() {
                        Some("branch.oid") => {
                            match words.next() {
                                Some(oid) => s.oid = String::from(oid),
                                None => return Err("Bad input"),
                            }
                        },
                        Some("branch.head") => {
                            match words.next() {
                                Some(branch) => s.branch = String::from(branch),
                                None => return Err("Bad input"),
                            }
                        }
                        Some("branch.upstream") => {
                            match words.next() {
                                Some(upstream) => s.upstream = Some(String::from(upstream)),
                                None => return Err("Bad input"),
                            }
                        },
                        Some("branch.ab") => {
                            match words.next() {
                                Some("+0") => {},
                                Some(_) => s.ahead = true,
                                None => return Err("Bad input"),
                            }
                            match words.next() {
                                Some("-0") => {},
                                Some(_) => s.behind = true,
                                None => return Err("Bad input"),
                            }
                        },
                        _ => return Err("Bad input"),
                    }
                },
                Some("1") | Some("2") => {
                    if let Some(changes) = words.next() {
                        let mut changes = changes.chars();
                        match changes.next() {
                            Some('.') => {},
                            Some(_) => s.staged = true,
                            None => return Err("Bad input"),
                        }
                        match changes.next() {
                            Some('.') => {},
                            Some(_) => s.unstaged = true,
                            None => return Err("Bad input"),
                        }
                    }
                },
                Some("?") => s.untracked = true,
                Some("!") => s.ignored = true,
                Some("") | None => {},
                _ => return Err("Bad input"),
            }
        }
        Ok(s)
    }

    pub fn to_line(&self) -> String {
        let mut symbols = String::new();
        if self.ahead {
            symbols.push('A');
        }
        if self.behind {
            symbols.push('B');
        }
        if self.staged {
            symbols.push('+');
        }
        if self.unstaged {
            symbols.push('*');
        }
        if self.untracked {
            symbols.push('?');
        }
        if self.ignored {
            symbols.push('!');
        }
        let short_oid = if self.oid == "(initial)" {
            &self.oid
        } else {
            &self.oid[..7]
        };
        if symbols.is_empty() {
            format!("[{} {}]", self.branch, short_oid)
        } else {
            format!("[{} {} {}]", self.branch, short_oid, symbols)
        }
    }
}

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

    #[test]
    fn parse_clean() {
        let test_status = "\
# branch.oid 3845e7a3c3aadaaebb2d1b261bf07a9357d35a79
# branch.head master
";
        assert_eq!(
            GitStatus::new(test_status).unwrap(),
            GitStatus {
                oid: String::from("3845e7a3c3aadaaebb2d1b261bf07a9357d35a79"),
                branch: String::from("master"),
                upstream: None,
                ahead: false,
                behind: false,
                staged: false,
                unstaged: false,
                untracked: false,
                ignored: false,
            }
        );
    }

    #[test]
    fn parse_clean_upstream() {
        let test_status = "\
# branch.oid 3845e7a3c3aadaaebb2d1b261bf07a9357d35a79
# branch.head master
# branch.upstream origin/master
# branch.ab +1 -1
";
        assert_eq!(
            GitStatus::new(test_status).unwrap(),
            GitStatus {
                oid: String::from("3845e7a3c3aadaaebb2d1b261bf07a9357d35a79"),
                branch: String::from("master"),
                upstream: Some(String::from("origin/master")),
                ahead: true,
                behind: true,
                staged: false,
                unstaged: false,
                untracked: false,
                ignored: false,
            }
        );
    }

    #[test]
    fn parse_staged_only() {
        let test_status = "\
# branch.oid 3845e7a3c3aadaaebb2d1b261bf07a9357d35a79
# branch.head master
1 M. N... 100644 100644 100644 1290f45e7ad7575848a436d8febbd6c4ba07f1f3 311c77295c5b6056f4599c2b8d0a019d4c76746a README.md
";
        let s = GitStatus::new(test_status).unwrap();
        assert!(s.staged);
        assert!(!s.unstaged);
    }

    #[test]
    fn parse_unstaged_only() {
        let test_status = "\
# branch.oid 3845e7a3c3aadaaebb2d1b261bf07a9357d35a79
# branch.head master
1 .M N... 100644 100644 100644 1290f45e7ad7575848a436d8febbd6c4ba07f1f3 1290f45e7ad7575848a436d8febbd6c4ba07f1f3 README.md
";
        let s = GitStatus::new(test_status).unwrap();
        assert!(!s.staged);
        assert!(s.unstaged);
    }

    #[test]
    fn parse_both_staged_and_unstaged() {
        let test_status = "\
# branch.oid 3845e7a3c3aadaaebb2d1b261bf07a9357d35a79
# branch.head master
1 MM N... 100644 100644 100644 1290f45e7ad7575848a436d8febbd6c4ba07f1f3 311c77295c5b6056f4599c2b8d0a019d4c76746a README.md
";
        let s = GitStatus::new(test_status).unwrap();
        assert!(s.staged);
        assert!(s.unstaged);
    }

    #[test]
    fn parse_deleted() {
        let test_status = "\
# branch.oid 3845e7a3c3aadaaebb2d1b261bf07a9357d35a79
# branch.head master
1 D. N... 100644 000000 000000 1290f45e7ad7575848a436d8febbd6c4ba07f1f3 0000000000000000000000000000000000000000 README.md
";
        let s = GitStatus::new(test_status).unwrap();
        assert!(s.staged);
        assert!(!s.unstaged);
    }

    #[test]
    fn parse_renamed() {
        let test_status = "\
# branch.oid 3845e7a3c3aadaaebb2d1b261bf07a9357d35a79
# branch.head master
2 R. N... 100644 100644 100644 1290f45e7ad7575848a436d8febbd6c4ba07f1f3 1290f45e7ad7575848a436d8febbd6c4ba07f1f3 R100 README	README.md
";
        let s = GitStatus::new(test_status).unwrap();
        assert!(s.staged);
        assert!(!s.unstaged);
    }

    #[test]
    fn parse_untracked() {
        let test_status = "\
# branch.oid 3845e7a3c3aadaaebb2d1b261bf07a9357d35a79
# branch.head master
? foo.txt
";
        assert!(GitStatus::new(test_status).unwrap().untracked);
    }

    #[test]
    fn parse_ignored() {
        let test_status = "\
# branch.oid 3845e7a3c3aadaaebb2d1b261bf07a9357d35a79
# branch.head master
! node_modules/
";
        assert!(GitStatus::new(test_status).unwrap().ignored);
    }

    #[test]
    fn parse_very_dirty() {
        let test_status = "\
# branch.oid 3845e7a3c3aadaaebb2d1b261bf07a9357d35a79
# branch.head master
# branch.upstream origin/master
# branch.ab +1 -1
1 D. N... 100644 000000 000000 1290f45e7ad7575848a436d8febbd6c4ba07f1f3 0000000000000000000000000000000000000000 README.md
1 .M N... 100644 100644 100644 5e8a8090976077ddf16252a560460a20dbbdd6a5 5e8a8090976077ddf16252a560460a20dbbdd6a5 gh-pages.sh
? foo.txt
! ignored.txt
";
        assert_eq!(
            GitStatus::new(test_status).unwrap(),
            GitStatus {
                oid: String::from("3845e7a3c3aadaaebb2d1b261bf07a9357d35a79"),
                branch: String::from("master"),
                upstream: Some(String::from("origin/master")),
                ahead: true,
                behind: true,
                staged: true,
                unstaged: true,
                untracked: true,
                ignored: true,
            }
        );
    }

    #[test]
    fn parse_bad_input() {
        assert_eq!(GitStatus::new("foo"), Err("Bad input"));
    }

    #[test]
    fn test_to_line_clean() {
        let s = GitStatus {
            oid: String::from("3845e7a3c3aadaaebb2d1b261bf07a9357d35a79"),
            branch: String::from("master"),
            upstream: None,
            ahead: false,
            behind: false,
            staged: false,
            unstaged: false,
            untracked: false,
            ignored: false,
        };
        assert_eq!(s.to_line(), String::from("[master 3845e7a]"));
    }

    #[test]
    fn test_to_line_dirty() {
        let s = GitStatus {
            oid: String::from("3845e7a3c3aadaaebb2d1b261bf07a9357d35a79"),
            branch: String::from("master"),
            upstream: Some(String::from("origin/master")),
            ahead: true,
            behind: true,
            staged: true,
            unstaged: true,
            untracked: true,
            ignored: true,
        };
        assert_eq!(s.to_line(), String::from("[master 3845e7a AB+*?!]"));
    }

    #[test]
    fn test_to_line_initial_commit() {
        let s = GitStatus {
            oid: String::from("(initial)"),
            branch: String::from("master"),
            upstream: None,
            ahead: false,
            behind: false,
            staged: false,
            unstaged: false,
            untracked: false,
            ignored: false,
        };
        assert_eq!(s.to_line(), String::from("[master (initial)]"));
    }
}
<commit_msg>Use 'ok_or' and '?' operator to reduce boilerplate<commit_after>#[derive(Debug, PartialEq, Eq)]
pub struct GitStatus {
    oid: String,
    branch: String,
    upstream: Option<String>,
    ahead: bool,
    behind: bool,
    staged: bool,
    unstaged: bool,
    untracked: bool,
    ignored: bool,
}

impl GitStatus {
    pub fn new(status_txt: &str) -> Result<GitStatus, &'static str> {
        let mut s = GitStatus {
            oid: String::new(),
            branch: String::new(),
            upstream: None,
            ahead: false,
            behind: false,
            staged: false,
            unstaged: false,
            untracked: false,
            ignored: false,
        };
        for line in status_txt.lines() {
            let mut words = line.split(' ');
            match words.next() {
                Some("#") => {
                    match words.next() {
                        Some("branch.oid") => {
                            s.oid = String::from(words.next().ok_or("Bad input")?);
                        },
                        Some("branch.head") => {
                            s.branch = String::from(words.next().ok_or("Bad input")?);
                        }
                        Some("branch.upstream") => {
                            s.upstream = Some(String::from(words.next().ok_or("Bad input")?));
                        },
                        Some("branch.ab") => {
                            match words.next() {
                                Some("+0") => {},
                                Some(_) => s.ahead = true,
                                None => return Err("Bad input"),
                            }
                            match words.next() {
                                Some("-0") => {},
                                Some(_) => s.behind = true,
                                None => return Err("Bad input"),
                            }
                        },
                        _ => return Err("Bad input"),
                    }
                },
                Some("1") | Some("2") => {
                    if let Some(changes) = words.next() {
                        let mut changes = changes.chars();
                        match changes.next() {
                            Some('.') => {},
                            Some(_) => s.staged = true,
                            None => return Err("Bad input"),
                        }
                        match changes.next() {
                            Some('.') => {},
                            Some(_) => s.unstaged = true,
                            None => return Err("Bad input"),
                        }
                    }
                },
                Some("?") => s.untracked = true,
                Some("!") => s.ignored = true,
                Some("") | None => {},
                _ => return Err("Bad input"),
            }
        }
        Ok(s)
    }

    pub fn to_line(&self) -> String {
        let mut symbols = String::new();
        if self.ahead {
            symbols.push('A');
        }
        if self.behind {
            symbols.push('B');
        }
        if self.staged {
            symbols.push('+');
        }
        if self.unstaged {
            symbols.push('*');
        }
        if self.untracked {
            symbols.push('?');
        }
        if self.ignored {
            symbols.push('!');
        }
        let short_oid = if self.oid == "(initial)" {
            &self.oid
        } else {
            &self.oid[..7]
        };
        if symbols.is_empty() {
            format!("[{} {}]", self.branch, short_oid)
        } else {
            format!("[{} {} {}]", self.branch, short_oid, symbols)
        }
    }
}

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

    #[test]
    fn parse_clean() {
        let test_status = "\
# branch.oid 3845e7a3c3aadaaebb2d1b261bf07a9357d35a79
# branch.head master
";
        assert_eq!(
            GitStatus::new(test_status).unwrap(),
            GitStatus {
                oid: String::from("3845e7a3c3aadaaebb2d1b261bf07a9357d35a79"),
                branch: String::from("master"),
                upstream: None,
                ahead: false,
                behind: false,
                staged: false,
                unstaged: false,
                untracked: false,
                ignored: false,
            }
        );
    }

    #[test]
    fn parse_clean_upstream() {
        let test_status = "\
# branch.oid 3845e7a3c3aadaaebb2d1b261bf07a9357d35a79
# branch.head master
# branch.upstream origin/master
# branch.ab +1 -1
";
        assert_eq!(
            GitStatus::new(test_status).unwrap(),
            GitStatus {
                oid: String::from("3845e7a3c3aadaaebb2d1b261bf07a9357d35a79"),
                branch: String::from("master"),
                upstream: Some(String::from("origin/master")),
                ahead: true,
                behind: true,
                staged: false,
                unstaged: false,
                untracked: false,
                ignored: false,
            }
        );
    }

    #[test]
    fn parse_staged_only() {
        let test_status = "\
# branch.oid 3845e7a3c3aadaaebb2d1b261bf07a9357d35a79
# branch.head master
1 M. N... 100644 100644 100644 1290f45e7ad7575848a436d8febbd6c4ba07f1f3 311c77295c5b6056f4599c2b8d0a019d4c76746a README.md
";
        let s = GitStatus::new(test_status).unwrap();
        assert!(s.staged);
        assert!(!s.unstaged);
    }

    #[test]
    fn parse_unstaged_only() {
        let test_status = "\
# branch.oid 3845e7a3c3aadaaebb2d1b261bf07a9357d35a79
# branch.head master
1 .M N... 100644 100644 100644 1290f45e7ad7575848a436d8febbd6c4ba07f1f3 1290f45e7ad7575848a436d8febbd6c4ba07f1f3 README.md
";
        let s = GitStatus::new(test_status).unwrap();
        assert!(!s.staged);
        assert!(s.unstaged);
    }

    #[test]
    fn parse_both_staged_and_unstaged() {
        let test_status = "\
# branch.oid 3845e7a3c3aadaaebb2d1b261bf07a9357d35a79
# branch.head master
1 MM N... 100644 100644 100644 1290f45e7ad7575848a436d8febbd6c4ba07f1f3 311c77295c5b6056f4599c2b8d0a019d4c76746a README.md
";
        let s = GitStatus::new(test_status).unwrap();
        assert!(s.staged);
        assert!(s.unstaged);
    }

    #[test]
    fn parse_deleted() {
        let test_status = "\
# branch.oid 3845e7a3c3aadaaebb2d1b261bf07a9357d35a79
# branch.head master
1 D. N... 100644 000000 000000 1290f45e7ad7575848a436d8febbd6c4ba07f1f3 0000000000000000000000000000000000000000 README.md
";
        let s = GitStatus::new(test_status).unwrap();
        assert!(s.staged);
        assert!(!s.unstaged);
    }

    #[test]
    fn parse_renamed() {
        let test_status = "\
# branch.oid 3845e7a3c3aadaaebb2d1b261bf07a9357d35a79
# branch.head master
2 R. N... 100644 100644 100644 1290f45e7ad7575848a436d8febbd6c4ba07f1f3 1290f45e7ad7575848a436d8febbd6c4ba07f1f3 R100 README	README.md
";
        let s = GitStatus::new(test_status).unwrap();
        assert!(s.staged);
        assert!(!s.unstaged);
    }

    #[test]
    fn parse_untracked() {
        let test_status = "\
# branch.oid 3845e7a3c3aadaaebb2d1b261bf07a9357d35a79
# branch.head master
? foo.txt
";
        assert!(GitStatus::new(test_status).unwrap().untracked);
    }

    #[test]
    fn parse_ignored() {
        let test_status = "\
# branch.oid 3845e7a3c3aadaaebb2d1b261bf07a9357d35a79
# branch.head master
! node_modules/
";
        assert!(GitStatus::new(test_status).unwrap().ignored);
    }

    #[test]
    fn parse_very_dirty() {
        let test_status = "\
# branch.oid 3845e7a3c3aadaaebb2d1b261bf07a9357d35a79
# branch.head master
# branch.upstream origin/master
# branch.ab +1 -1
1 D. N... 100644 000000 000000 1290f45e7ad7575848a436d8febbd6c4ba07f1f3 0000000000000000000000000000000000000000 README.md
1 .M N... 100644 100644 100644 5e8a8090976077ddf16252a560460a20dbbdd6a5 5e8a8090976077ddf16252a560460a20dbbdd6a5 gh-pages.sh
? foo.txt
! ignored.txt
";
        assert_eq!(
            GitStatus::new(test_status).unwrap(),
            GitStatus {
                oid: String::from("3845e7a3c3aadaaebb2d1b261bf07a9357d35a79"),
                branch: String::from("master"),
                upstream: Some(String::from("origin/master")),
                ahead: true,
                behind: true,
                staged: true,
                unstaged: true,
                untracked: true,
                ignored: true,
            }
        );
    }

    #[test]
    fn parse_bad_input() {
        assert_eq!(GitStatus::new("foo"), Err("Bad input"));
    }

    #[test]
    fn test_to_line_clean() {
        let s = GitStatus {
            oid: String::from("3845e7a3c3aadaaebb2d1b261bf07a9357d35a79"),
            branch: String::from("master"),
            upstream: None,
            ahead: false,
            behind: false,
            staged: false,
            unstaged: false,
            untracked: false,
            ignored: false,
        };
        assert_eq!(s.to_line(), String::from("[master 3845e7a]"));
    }

    #[test]
    fn test_to_line_dirty() {
        let s = GitStatus {
            oid: String::from("3845e7a3c3aadaaebb2d1b261bf07a9357d35a79"),
            branch: String::from("master"),
            upstream: Some(String::from("origin/master")),
            ahead: true,
            behind: true,
            staged: true,
            unstaged: true,
            untracked: true,
            ignored: true,
        };
        assert_eq!(s.to_line(), String::from("[master 3845e7a AB+*?!]"));
    }

    #[test]
    fn test_to_line_initial_commit() {
        let s = GitStatus {
            oid: String::from("(initial)"),
            branch: String::from("master"),
            upstream: None,
            ahead: false,
            behind: false,
            staged: false,
            unstaged: false,
            untracked: false,
            ignored: false,
        };
        assert_eq!(s.to_line(), String::from("[master (initial)]"));
    }
}
<|endoftext|>


<commit_before>extern crate base64;
extern crate file;
extern crate ring;
extern crate rpassword;
extern crate rprompt;
#[macro_use] extern crate serde_derive;
extern crate serde_json;
extern crate username;
extern crate xdg;

use ring::{aead, digest, pbkdf2, rand};
use ring::rand::SecureRandom;
use std::str::FromStr;
use std::path::PathBuf;
use xdg::BaseDirectories;

const SERIALIZATION_API:    usize = 1;
const SERIALIZATION_FIELDS: usize = 4;

#[derive(Debug,PartialEq,Deserialize,Serialize)]
#[serde(tag = "type")]
pub enum Entity {
    Credentials(Credentials),
}

#[derive(Debug,PartialEq,Deserialize,Serialize)]
pub struct Credentials {
    pub url:         String,
    pub login:       String,
    pub password:    String,
    pub totp_secret: Option<String>
}

pub struct MnemesisUtils {
    base_dirs: BaseDirectories,
    config:    MnemesisConfig,
    crypt:     Crypt,
}

#[derive(Debug,PartialEq,Deserialize,Serialize)]
struct MnemesisConfig {
    secret: String,
}

impl MnemesisUtils {
    pub fn new() -> MnemesisUtils {
        let base_dirs  = BaseDirectories::with_prefix("mnemesis").expect("Failed getting base directories");
        let passphrase = MnemesisUtils::prompt_for_password("Passphrase: ");
        let config     = MnemesisConfig::load(&base_dirs, &Crypt::new(&passphrase, Algorithm::ChaCha20Poly1305));
        let crypt      = Crypt::new(&config.secret, Algorithm::ChaCha20Poly1305);

        MnemesisUtils {
            base_dirs,
            config,
            crypt,
        }
    }

    pub fn prompt_for_input(msg: &str) -> String {
        rprompt::prompt_reply_stdout(msg).expect("Failed reading from stdin")
    }

    pub fn prompt_for_password(msg: &str) -> String {
        rpassword::prompt_password_stdout(msg).expect("Failed reading password")
    }

    fn credentials_directory(&self, path: &str) -> PathBuf {
        self.base_dirs.place_data_file(path).expect("Failed computing credentials directory")
    }

    pub fn read_entities(&self, path: &str) -> Vec<Entity> {
        let full_path = self.credentials_directory(path);

        if full_path.exists() {
            if full_path.is_file() {
                let encrypted_data = file::get_text(full_path.to_str().expect(&format!("{:?} is not valid UTF-8", full_path))).expect(&format!("Failed to read {:?}", full_path));
                let decrypted_data = self.crypt.decrypt(encrypted_data);
                serde_json::from_str::<Vec<Entity>>(&decrypted_data).expect(&format!("Found garbage in {:?}", full_path))
            } else {
                panic!("{:?} exists and is not a file", full_path);
            }
        } else {
            Vec::new()
        }
    }

    pub fn write_entities(&self, path: &str, entities: Vec<Entity>) {
        let full_path      = self.credentials_directory(path);
        let decrypted_data = serde_json::to_string(&entities).expect("Failed to serialize entities");
        let encrypted_data = self.crypt.encrypt(decrypted_data);

        file::put(full_path.to_str().expect(&format!("{:?} is not valid UTF-8", full_path)), &encrypted_data).expect(&format!("Failed to write {:?}", full_path));
    }
}

impl MnemesisConfig {
    pub fn load(base_dirs: &BaseDirectories, crypt: &Crypt) -> MnemesisConfig {
        base_dirs.find_config_file("mnemesis-config.json").and_then(|conf_file| {
            conf_file.to_str().and_then(|conf_file| file::get_text(conf_file).ok()).map(|encrypted_conf| crypt.decrypt(encrypted_conf))
        }).and_then(|conf| {
            serde_json::from_str::<MnemesisConfig>(&conf).ok()
        }).unwrap_or_else(|| {
            let config = MnemesisConfig {
                secret: crypt.generate_passphrase(),
            };
            let decrypted_data = serde_json::to_string(&config).expect("Failed to serialize config");
            let encrypted_data = crypt.encrypt(decrypted_data);
            let full_path      = base_dirs.place_config_file("mnemesis-config.json").expect("Failed to create config file");

            file::put(full_path.to_str().expect(&format!("{:?} is not valid UTF-8", full_path)), &encrypted_data).expect(&format!("Failed to write {:?}", full_path));
            config
        })
    }
}

struct Crypt {
    algorithm: Algorithm,
    key:       [u8; 32],
    rand:      rand::SystemRandom,
}

#[derive(Clone, Debug, PartialEq)]
enum Algorithm {
    ChaCha20Poly1305,
}

impl Algorithm {
    fn aead_algorithm(&self) -> &'static aead::Algorithm {
        match *self {
            Algorithm::ChaCha20Poly1305 => &aead::CHACHA20_POLY1305
        }
    }
}

impl ToString for Algorithm {
    fn to_string(&self) -> String {
        format!("{:?}", self)
    }
}

impl FromStr for Algorithm {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "ChaCha20Poly1305" => Ok(Algorithm::ChaCha20Poly1305),
            _                   => Err(format!("Unknown algorithm: {}", s)),
        }
    }
}

impl Crypt {
    fn new(passphrase: &str, algorithm: Algorithm) -> Crypt {
        let key = Self::derive(passphrase.as_bytes());

        Crypt {
            algorithm,
            key,
            rand: rand::SystemRandom::new(),
        }
    }

    fn derive(bytes: &[u8]) -> [u8; 32] {
        let salt    = username::get_user_name().expect("Failed to query username");
        let mut key = [0; 32];
        pbkdf2::derive(&digest::SHA512, 100, salt.as_bytes(), bytes, &mut key);
        key
    }

    fn random(&self, bytes: usize) -> Vec<u8> {
        let mut rand = vec![0; bytes];
        self.rand.fill(&mut rand).expect("Failed generating random bytes");
        rand
    }

    fn nonce(&self) -> Vec<u8> {
        self.random(12)
    }

    fn encrypt(&self, data: String) -> String {
        let algorithm                = self.algorithm.aead_algorithm();
        let sealing_key              = aead::SealingKey::new(&algorithm, &self.key).expect("Failed creating sealing key");
        let nonce                    = self.nonce();
        let additional_data: [u8; 0] = [];
        let mut data                 = data.into_bytes();

        for _ in 0..algorithm.tag_len() {
            data.push(0);
        }

        let encrypted_data = aead::seal_in_place(&sealing_key, &nonce, &additional_data, &mut data, algorithm.tag_len()).map(|len| data[..len].to_vec()).expect("Failed sealing data");

        format!("{}:{}:{}:{}", SERIALIZATION_API, self.algorithm.to_string(), base64::encode(&nonce), base64::encode(&encrypted_data))
    }

    fn decrypt(&self, data: String) -> String {
        let components: Vec<_> = data.splitn(SERIALIZATION_FIELDS, ':').collect();

        assert_eq!(components.len(),      SERIALIZATION_FIELDS);
        assert_eq!(components[0].parse(), Ok(SERIALIZATION_API));
        assert_eq!(components[1].parse(), Ok(self.algorithm.clone())); // TODO: move me to param and use the read one

        let algorithm                = self.algorithm.aead_algorithm();
        let opening_key              = aead::OpeningKey::new(&algorithm, &self.key).expect("Failed creating opening key");
        let nonce                    = base64::decode(components[2]).expect("Failed to decode nonce");
        let mut data                 = base64::decode(components[3]).expect("Failed to decode data");
        let additional_data: [u8; 0] = [];
        let decrypted_data           = aead::open_in_place(&opening_key, &nonce, &additional_data, 0, &mut data).expect("Failed opening data");

        String::from_utf8(decrypted_data.to_vec()).expect("Failed decoding data")
    }

    fn generate_passphrase(&self) -> String {
        base64::encode(&Self::derive(self.random(64).as_ref()))
    }
}

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

    #[test]
    fn test_encrypt_decrypt() {
        let pass  = "This is a nice passphrase.";
        let c     = Crypt::new(pass, Algorithm::ChaCha20Poly1305);
        let data  = "Very secret data";
        let e     = c.encrypt(data.to_string());
        let e2    = e.clone();

        assert_eq!(c.decrypt(e), data);
        assert_eq!(Crypt::new(pass, Algorithm::ChaCha20Poly1305).decrypt(e2), data);
    }
}
<commit_msg>crypt: simplify<commit_after>extern crate base64;
extern crate file;
extern crate ring;
extern crate rpassword;
extern crate rprompt;
#[macro_use] extern crate serde_derive;
extern crate serde_json;
extern crate username;
extern crate xdg;

use ring::{aead, digest, pbkdf2, rand};
use ring::rand::SecureRandom;
use std::str::FromStr;
use std::path::PathBuf;
use xdg::BaseDirectories;

const SERIALIZATION_API:    usize = 1;
const SERIALIZATION_FIELDS: usize = 4;

#[derive(Debug,PartialEq,Deserialize,Serialize)]
#[serde(tag = "type")]
pub enum Entity {
    Credentials(Credentials),
}

#[derive(Debug,PartialEq,Deserialize,Serialize)]
pub struct Credentials {
    pub url:         String,
    pub login:       String,
    pub password:    String,
    pub totp_secret: Option<String>
}

pub struct MnemesisUtils {
    base_dirs: BaseDirectories,
    crypt:     Crypt,
}

#[derive(Debug,PartialEq,Deserialize,Serialize)]
struct MnemesisConfig {
    secret: String,
}

impl MnemesisUtils {
    pub fn new() -> MnemesisUtils {
        let base_dirs  = BaseDirectories::with_prefix("mnemesis").expect("Failed getting base directories");
        let passphrase = MnemesisUtils::prompt_for_password("Passphrase: ");
        let config     = MnemesisConfig::load(&base_dirs, &Crypt::new(&passphrase));
        let crypt      = Crypt::new(&config.secret);

        MnemesisUtils {
            base_dirs,
            crypt,
        }
    }

    pub fn prompt_for_input(msg: &str) -> String {
        rprompt::prompt_reply_stdout(msg).expect("Failed reading from stdin")
    }

    pub fn prompt_for_password(msg: &str) -> String {
        rpassword::prompt_password_stdout(msg).expect("Failed reading password")
    }

    fn credentials_directory(&self, path: &str) -> PathBuf {
        self.base_dirs.place_data_file(path).expect("Failed computing credentials directory")
    }

    pub fn read_entities(&self, path: &str) -> Vec<Entity> {
        let full_path = self.credentials_directory(path);

        if full_path.exists() {
            if full_path.is_file() {
                let encrypted_data = file::get_text(full_path.to_str().expect(&format!("{:?} is not valid UTF-8", full_path))).expect(&format!("Failed to read {:?}", full_path));
                let decrypted_data = self.crypt.decrypt(encrypted_data);
                serde_json::from_str::<Vec<Entity>>(&decrypted_data).expect(&format!("Found garbage in {:?}", full_path))
            } else {
                panic!("{:?} exists and is not a file", full_path);
            }
        } else {
            Vec::new()
        }
    }

    pub fn write_entities(&self, path: &str, entities: Vec<Entity>) {
        let full_path      = self.credentials_directory(path);
        let decrypted_data = serde_json::to_string(&entities).expect("Failed to serialize entities");
        let encrypted_data = self.crypt.encrypt(decrypted_data, Algorithm::ChaCha20Poly1305);

        file::put(full_path.to_str().expect(&format!("{:?} is not valid UTF-8", full_path)), &encrypted_data).expect(&format!("Failed to write {:?}", full_path));
    }
}

impl MnemesisConfig {
    pub fn load(base_dirs: &BaseDirectories, crypt: &Crypt) -> MnemesisConfig {
        base_dirs.find_config_file("mnemesis-config.json").and_then(|conf_file| {
            conf_file.to_str().and_then(|conf_file| file::get_text(conf_file).ok()).map(|encrypted_conf| crypt.decrypt(encrypted_conf))
        }).and_then(|conf| {
            serde_json::from_str::<MnemesisConfig>(&conf).ok()
        }).unwrap_or_else(|| {
            let config = MnemesisConfig {
                secret: crypt.generate_passphrase(),
            };
            let decrypted_data = serde_json::to_string(&config).expect("Failed to serialize config");
            let encrypted_data = crypt.encrypt(decrypted_data, Algorithm::ChaCha20Poly1305);
            let full_path      = base_dirs.place_config_file("mnemesis-config.json").expect("Failed to create config file");

            file::put(full_path.to_str().expect(&format!("{:?} is not valid UTF-8", full_path)), &encrypted_data).expect(&format!("Failed to write {:?}", full_path));
            config
        })
    }
}

struct Crypt {
    key:  [u8; 32],
    rand: rand::SystemRandom,
}

#[derive(Clone, Debug, PartialEq)]
enum Algorithm {
    ChaCha20Poly1305,
}

impl Algorithm {
    fn aead_algorithm(&self) -> &'static aead::Algorithm {
        match *self {
            Algorithm::ChaCha20Poly1305 => &aead::CHACHA20_POLY1305
        }
    }
}

impl ToString for Algorithm {
    fn to_string(&self) -> String {
        format!("{:?}", self)
    }
}

impl FromStr for Algorithm {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "ChaCha20Poly1305" => Ok(Algorithm::ChaCha20Poly1305),
            _                  => Err(format!("Unknown algorithm: {}", s)),
        }
    }
}

impl Crypt {
    fn new(passphrase: &str) -> Crypt {
        let key = Self::derive(passphrase.as_bytes());

        Crypt {
            key,
            rand: rand::SystemRandom::new(),
        }
    }

    fn derive(bytes: &[u8]) -> [u8; 32] {
        let salt    = username::get_user_name().expect("Failed to query username");
        let mut key = [0; 32];
        pbkdf2::derive(&digest::SHA512, 100, salt.as_bytes(), bytes, &mut key);
        key
    }

    fn random(&self, bytes: usize) -> Vec<u8> {
        let mut rand = vec![0; bytes];
        self.rand.fill(&mut rand).expect("Failed generating random bytes");
        rand
    }

    fn nonce(&self) -> Vec<u8> {
        self.random(12)
    }

    fn encrypt(&self, data: String, algo: Algorithm) -> String {
        let algorithm                = algo.aead_algorithm();
        let sealing_key              = aead::SealingKey::new(&algorithm, &self.key).expect("Failed creating sealing key");
        let nonce                    = self.nonce();
        let additional_data: [u8; 0] = [];
        let mut data                 = data.into_bytes();

        for _ in 0..algorithm.tag_len() {
            data.push(0);
        }

        let encrypted_data = aead::seal_in_place(&sealing_key, &nonce, &additional_data, &mut data, algorithm.tag_len()).map(|len| data[..len].to_vec()).expect("Failed sealing data");

        format!("{}:{}:{}:{}", SERIALIZATION_API, algo.to_string(), base64::encode(&nonce), base64::encode(&encrypted_data))
    }

    fn decrypt(&self, data: String) -> String {
        let components: Vec<_> = data.splitn(SERIALIZATION_FIELDS, ':').collect();

        assert_eq!(components.len(),      SERIALIZATION_FIELDS);
        assert_eq!(components[0].parse(), Ok(SERIALIZATION_API));

        let algorithm                = components[1].parse::<Algorithm>().expect("Unknown algorithm").aead_algorithm();
        let opening_key              = aead::OpeningKey::new(&algorithm, &self.key).expect("Failed creating opening key");
        let nonce                    = base64::decode(components[2]).expect("Failed to decode nonce");
        let mut data                 = base64::decode(components[3]).expect("Failed to decode data");
        let additional_data: [u8; 0] = [];
        let decrypted_data           = aead::open_in_place(&opening_key, &nonce, &additional_data, 0, &mut data).expect("Failed opening data");

        String::from_utf8(decrypted_data.to_vec()).expect("Failed decoding data")
    }

    fn generate_passphrase(&self) -> String {
        base64::encode(&Self::derive(self.random(64).as_ref()))
    }
}

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

    #[test]
    fn test_encrypt_decrypt() {
        let pass  = "This is a nice passphrase.";
        let c     = Crypt::new(pass);
        let data  = "Very secret data";
        let e     = c.encrypt(data.to_string(), Algorithm::ChaCha20Poly1305);
        let e2    = e.clone();

        assert_eq!(c.decrypt(e), data);
        assert_eq!(Crypt::new(pass).decrypt(e2), data);
    }
}
<|endoftext|>


<commit_before>extern crate itertools;

use itertools::Itertools;

use std::collections::HashMap;
use std::fmt;
use std::str::FromStr;
use std::vec;

#[derive(Debug, Clone)]
pub enum EvalError {
    StackUnderflow,
    NameNotFound(String),
    TypeMismatch,
}

impl fmt::Display for EvalError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            EvalError::StackUnderflow => write!(f, "stack underflow"),
            EvalError::NameNotFound(ref name) => write!(f, "could not find name: {}", name),
            EvalError::TypeMismatch => write!(f, "type mismatch"),
        }
    }
}

pub type EvalResult<T> = Result<T, EvalError>;

// This wrapper struct is needed to allow for the derivation of traits on `Term`
#[derive(Copy)]
pub struct Prim {
    pub f: fn(&mut Evaluator) -> EvalResult<()>,
}

impl fmt::Debug for Prim {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Prim(<prim>)")
    }
}

impl Clone for Prim {
    fn clone(&self) -> Prim { *self }
}

impl PartialEq for Prim {
    fn eq(&self, _: &Prim) -> bool {
        unimplemented!() // TODO: figure out function equality
    }
}

#[derive(Clone, Debug, PartialEq)]
pub enum Term {
    Number(i32),
    Quote(Stack),
    Name(String),
    Prim(Prim),
}

impl Term {
    pub fn prim(f: fn(&mut Evaluator) -> EvalResult<()>) -> Term {
        Term::Prim(Prim { f: f })
    }
}

impl fmt::Display for Term {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Term::Number(x) => write!(f, "{}", x),
            Term::Quote(ref stack) => write!(f, "[ {} ]", stack),
            Term::Name(ref name) => write!(f, "{}", name),
            Term::Prim(_) => write!(f, "<prim>"),
        }
    }
}

#[derive(Clone, Debug)]
pub struct Words {
    defs: HashMap<String, Term>,
}

impl Words {
    pub fn empty() -> Words {
        Words {
            defs: HashMap::new(),
        }
    }

    pub fn standard() -> Words {
        let mut words = Words::empty();

        words.define("dup", Term::prim(prim::dup)); // (A b → A b b)
        words.define("pop", Term::prim(prim::pop)); // (A b → A)
        words.define("swap", Term::prim(prim::swap)); // (A b c → A c b)
        words.define("apply", Term::prim(prim::apply)); // (A (A → B) → B)
        words.define("quote", Term::prim(prim::quote)); // (A b → A (C → C b))
        words.define("compose", Term::prim(prim::compose)); // (A (B → C) (C → D) → A (B → D)))

        words.define("+", Term::prim(prim::add)); // (A num num → A num)
        words.define("-", Term::prim(prim::sub)); // (A num num → A num)
        words.define("*", Term::prim(prim::mul)); // (A num num → A num)
        words.define("/", Term::prim(prim::div)); // (A num num → A num)
        words.define("%", Term::prim(prim::rem)); // (A num num → A num)

        words
    }

    pub fn define<S: Into<String>>(&mut self, name: S, x: Term) {
        self.defs.insert(name.into(), x);
    }

    pub fn lookup(&self, name: &str) -> Option<&Term> {
        self.defs.get(name)
    }
}

#[derive(Clone, Debug, PartialEq)]
pub struct Stack {
    terms: Vec<Term>,
}

impl Stack {
    pub fn new(terms: Vec<Term>) -> Stack {
        Stack { terms: terms }
    }

    fn push(&mut self, term: Term) {
        self.terms.push(term);
    }

    fn pop(&mut self) -> EvalResult<Term> {
        match self.terms.pop() {
            Some(term) => Ok(term),
            None => Err(EvalError::StackUnderflow),
        }
    }

    fn pop_number(&mut self) -> EvalResult<i32> {
        match try!(self.pop()) {
            Term::Number(x) => Ok(x),
            _ => Err(EvalError::TypeMismatch),
        }
    }

    fn pop_quote(&mut self) -> EvalResult<Stack> {
        match try!(self.pop()) {
            Term::Quote(stack) => Ok(stack),
            _ => Err(EvalError::TypeMismatch),
        }
    }

    fn peek(&mut self) -> EvalResult<&Term> {
        match self.terms.last() {
            Some(term) => Ok(term),
            None => Err(EvalError::StackUnderflow),
        }
    }

    pub fn quote(&mut self) -> EvalResult<()> {
        let term = try!(self.pop());
        self.push(Term::Quote(Stack::new(vec![term])));
        Ok(())
    }

    fn compose(&mut self, other: Stack) {
        self.terms.extend(other.terms);
    }

    fn dup(&mut self) -> EvalResult<()> {
        let term = try!(self.peek()).clone();
        self.push(term);
        Ok(())
    }

    fn swap(&mut self) -> EvalResult<()> {
        let term_a = try!(self.pop());
        let term_b = try!(self.pop());

        self.push(term_a);
        self.push(term_b);

        Ok(())
    }

    fn apply_binop<F: Fn(i32, i32) -> i32>(&mut self, f: F) -> EvalResult<()> {
        let x = try!(self.pop_number());
        let y = try!(self.pop_number());

        self.push(Term::Number(f(x, y)));

        Ok(())
    }
}

impl FromStr for Stack {
    type Err = String;

    fn from_str(src: &str) -> Result<Stack, String> {
        fn parse_tokens<'a, I>(tokens: &mut I, in_quote: bool) -> Result<Stack, String> where
            I: Iterator<Item = &'a str>,
        {
            let mut terms = vec![];

            while let Some(token) = tokens.next() {
                match token {
                    "[" => {
                        terms.push(Term::Quote(
                            try!(parse_tokens(tokens, true))
                        ))
                    },
                    "]" => {
                        if in_quote {
                            return Ok(Stack::new(terms));
                        } else {
                            return Err("found unexpected `]`".to_string());
                        }
                    },
                    token => match token.parse() {
                        Ok(x) => terms.push(Term::Number(x)),
                        Err(_) => terms.push(Term::Name(token.to_string())),
                    },
                }
            }
            if in_quote {
                Err("expected closing `]`".to_string())
            } else {
                Ok(Stack::new(terms))
            }
        }

        parse_tokens(&mut src.split_whitespace(), false)
    }
}

pub type Terms = vec::IntoIter<Term>;

impl IntoIterator for Stack {
    type Item = Term;
    type IntoIter = Terms;

    fn into_iter(self) -> Terms {
        self.terms.into_iter()
    }
}

impl fmt::Display for Stack {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.terms.iter().format(" ", |t, f| f(t)))
    }
}

pub struct Evaluator<'a> {
    words: &'a Words,
    stack: Stack,
    quote: Terms,
}

impl<'a> Evaluator<'a> {
    pub fn new(words: &Words, quote: Stack) -> Evaluator {
        Evaluator {
            words: words,
            stack: Stack::new(vec![]),
            quote: quote.into_iter(),

        }
    }

    fn eval_name(&mut self, name: String) -> EvalResult<()> {
        match self.words.lookup(&name) {
            Some(term) => self.eval_term(term.clone()),
            None => Err(EvalError::NameNotFound(name)),
        }
    }

    fn eval_term(&mut self, term: Term) -> EvalResult<()> {
        match term {
            Term::Number(x) => { self.stack.push(Term::Number(x)); Ok(()) },
            Term::Quote(stack) => { self.stack.push(Term::Quote(stack)); Ok(()) },
            Term::Name(name) => self.eval_name(name),
            Term::Prim(prim) => (prim.f)(self),
        }
    }

    fn eval_quote(&mut self, quote: Stack) -> EvalResult<()> {
        let mut quote = quote.into_iter();
        while let Some(term) = quote.next() {
            try!(self.eval_term(term))
        }
        Ok(())
    }

    fn eval_stack(&mut self) -> EvalResult<()> {
        while let Some(term) = self.quote.next() {
            try!(self.eval_term(term))
        }
        Ok(())
    }

    pub fn eval(mut self) -> EvalResult<Stack> {
        try!(self.eval_stack());
        Ok(self.stack)
    }
}

mod prim {
    use std::ops::*;
    use super::*;

    // dup : (A b → A b b)
    pub fn dup(ev: &mut Evaluator) -> EvalResult<()> {
        ev.stack.dup()
    }

    // pop : (A b → A)
    pub fn pop(ev: &mut Evaluator) -> EvalResult<()> {
        ev.stack.pop().map(|_| ())
    }

    // swap : (A b c → A c b)
    pub fn swap(ev: &mut Evaluator) -> EvalResult<()> {
        ev.stack.swap()
    }

    // apply : (A (A → B) → B)
    pub fn apply(ev: &mut Evaluator) -> EvalResult<()> {
        let quote = try!(ev.stack.pop_quote());

        ev.eval_quote(quote)
    }

    // quote : (A b → A (C → C b))
    pub fn quote(ev: &mut Evaluator) -> EvalResult<()> {
        let term = try!(ev.stack.pop());
        let quoted = Term::Quote(Stack::new(vec![term]));

        ev.stack.push(quoted);

        Ok(())
    }

    // compose : (A (B → C) (C → D) → A (B → D)))
    pub fn compose(ev: &mut Evaluator) -> EvalResult<()> {
        let stack_a = try!(ev.stack.pop_quote());
        let mut stack_b = try!(ev.stack.pop_quote());

        stack_b.compose(stack_a);
        ev.stack.push(Term::Quote(stack_b));

        Ok(())
    }

    // add : (A num num → A num)
    pub fn add(ev: &mut Evaluator) -> EvalResult<()> {
        ev.stack.apply_binop(i32::add)
    }

    // sub : (A num num → A num)
    pub fn sub(ev: &mut Evaluator) -> EvalResult<()> {
        ev.stack.apply_binop(i32::sub)
    }

    // mul : (A num num → A num)
    pub fn mul(ev: &mut Evaluator) -> EvalResult<()> {
        ev.stack.apply_binop(i32::mul)
    }

    // div : (A num num → A num)
    pub fn div(ev: &mut Evaluator) -> EvalResult<()> {
        ev.stack.apply_binop(i32::div)
    }

    // rem : (A num num → A num)
    pub fn rem(ev: &mut Evaluator) -> EvalResult<()> {
        ev.stack.apply_binop(i32::rem)
    }
}
<commit_msg>Add words primitive for printing the environment<commit_after>extern crate itertools;

use itertools::Itertools;

use std::collections::HashMap;
use std::fmt;
use std::str::FromStr;
use std::vec;

#[derive(Debug, Clone)]
pub enum EvalError {
    StackUnderflow,
    NameNotFound(String),
    TypeMismatch,
}

impl fmt::Display for EvalError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            EvalError::StackUnderflow => write!(f, "stack underflow"),
            EvalError::NameNotFound(ref name) => write!(f, "could not find name: {}", name),
            EvalError::TypeMismatch => write!(f, "type mismatch"),
        }
    }
}

pub type EvalResult<T> = Result<T, EvalError>;

// This wrapper struct is needed to allow for the derivation of traits on `Term`
#[derive(Copy)]
pub struct Prim {
    pub f: fn(&mut Evaluator) -> EvalResult<()>,
}

impl fmt::Debug for Prim {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Prim(<prim>)")
    }
}

impl Clone for Prim {
    fn clone(&self) -> Prim { *self }
}

impl PartialEq for Prim {
    fn eq(&self, _: &Prim) -> bool {
        unimplemented!() // TODO: figure out function equality
    }
}

#[derive(Clone, Debug, PartialEq)]
pub enum Term {
    Number(i32),
    Quote(Stack),
    Name(String),
    Prim(Prim),
}

impl Term {
    pub fn prim(f: fn(&mut Evaluator) -> EvalResult<()>) -> Term {
        Term::Prim(Prim { f: f })
    }
}

impl fmt::Display for Term {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Term::Number(x) => write!(f, "{}", x),
            Term::Quote(ref stack) => write!(f, "[ {} ]", stack),
            Term::Name(ref name) => write!(f, "{}", name),
            Term::Prim(_) => write!(f, "<prim>"),
        }
    }
}

#[derive(Clone, Debug)]
pub struct Words {
    defs: HashMap<String, Term>,
}

impl Words {
    pub fn empty() -> Words {
        Words {
            defs: HashMap::new(),
        }
    }

    pub fn standard() -> Words {
        let mut words = Words::empty();

        words.define("dup", Term::prim(prim::dup)); // (A b -> A b b)
        words.define("pop", Term::prim(prim::pop)); // (A b -> A)
        words.define("swap", Term::prim(prim::swap)); // (A b c -> A c b)
        words.define("apply", Term::prim(prim::apply)); // (A (A -> B) -> B)
        words.define("quote", Term::prim(prim::quote)); // (A b -> A (C -> C b))
        words.define("compose", Term::prim(prim::compose)); // (A (B -> C) (C -> D) -> A (B -> D)))

        words.define("words", Term::prim(prim::words)); // (A ~> A)

        words.define("+", Term::prim(prim::add)); // (A num num -> A num)
        words.define("-", Term::prim(prim::sub)); // (A num num -> A num)
        words.define("*", Term::prim(prim::mul)); // (A num num -> A num)
        words.define("/", Term::prim(prim::div)); // (A num num -> A num)
        words.define("%", Term::prim(prim::rem)); // (A num num -> A num)

        words
    }

    pub fn define<S: Into<String>>(&mut self, name: S, x: Term) {
        self.defs.insert(name.into(), x);
    }

    pub fn lookup(&self, name: &str) -> Option<&Term> {
        self.defs.get(name)
    }
}

#[derive(Clone, Debug, PartialEq)]
pub struct Stack {
    terms: Vec<Term>,
}

impl Stack {
    pub fn new(terms: Vec<Term>) -> Stack {
        Stack { terms: terms }
    }

    fn push(&mut self, term: Term) {
        self.terms.push(term);
    }

    fn pop(&mut self) -> EvalResult<Term> {
        match self.terms.pop() {
            Some(term) => Ok(term),
            None => Err(EvalError::StackUnderflow),
        }
    }

    fn pop_number(&mut self) -> EvalResult<i32> {
        match try!(self.pop()) {
            Term::Number(x) => Ok(x),
            _ => Err(EvalError::TypeMismatch),
        }
    }

    fn pop_quote(&mut self) -> EvalResult<Stack> {
        match try!(self.pop()) {
            Term::Quote(stack) => Ok(stack),
            _ => Err(EvalError::TypeMismatch),
        }
    }

    fn peek(&mut self) -> EvalResult<&Term> {
        match self.terms.last() {
            Some(term) => Ok(term),
            None => Err(EvalError::StackUnderflow),
        }
    }

    pub fn quote(&mut self) -> EvalResult<()> {
        let term = try!(self.pop());
        self.push(Term::Quote(Stack::new(vec![term])));
        Ok(())
    }

    fn compose(&mut self, other: Stack) {
        self.terms.extend(other.terms);
    }

    fn dup(&mut self) -> EvalResult<()> {
        let term = try!(self.peek()).clone();
        self.push(term);
        Ok(())
    }

    fn swap(&mut self) -> EvalResult<()> {
        let term_a = try!(self.pop());
        let term_b = try!(self.pop());

        self.push(term_a);
        self.push(term_b);

        Ok(())
    }

    fn apply_binop<F: Fn(i32, i32) -> i32>(&mut self, f: F) -> EvalResult<()> {
        let x = try!(self.pop_number());
        let y = try!(self.pop_number());

        self.push(Term::Number(f(x, y)));

        Ok(())
    }
}

impl FromStr for Stack {
    type Err = String;

    fn from_str(src: &str) -> Result<Stack, String> {
        fn parse_tokens<'a, I>(tokens: &mut I, in_quote: bool) -> Result<Stack, String> where
            I: Iterator<Item = &'a str>,
        {
            let mut terms = vec![];

            while let Some(token) = tokens.next() {
                match token {
                    "[" => {
                        terms.push(Term::Quote(
                            try!(parse_tokens(tokens, true))
                        ))
                    },
                    "]" => {
                        if in_quote {
                            return Ok(Stack::new(terms));
                        } else {
                            return Err("found unexpected `]`".to_string());
                        }
                    },
                    token => match token.parse() {
                        Ok(x) => terms.push(Term::Number(x)),
                        Err(_) => terms.push(Term::Name(token.to_string())),
                    },
                }
            }
            if in_quote {
                Err("expected closing `]`".to_string())
            } else {
                Ok(Stack::new(terms))
            }
        }

        parse_tokens(&mut src.split_whitespace(), false)
    }
}

pub type Terms = vec::IntoIter<Term>;

impl IntoIterator for Stack {
    type Item = Term;
    type IntoIter = Terms;

    fn into_iter(self) -> Terms {
        self.terms.into_iter()
    }
}

impl fmt::Display for Stack {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.terms.iter().format(" ", |t, f| f(t)))
    }
}

pub struct Evaluator<'a> {
    words: &'a Words,
    stack: Stack,
    quote: Terms,
}

impl<'a> Evaluator<'a> {
    pub fn new(words: &Words, quote: Stack) -> Evaluator {
        Evaluator {
            words: words,
            stack: Stack::new(vec![]),
            quote: quote.into_iter(),

        }
    }

    fn eval_name(&mut self, name: String) -> EvalResult<()> {
        match self.words.lookup(&name) {
            Some(term) => self.eval_term(term.clone()),
            None => Err(EvalError::NameNotFound(name)),
        }
    }

    fn eval_term(&mut self, term: Term) -> EvalResult<()> {
        match term {
            Term::Number(x) => { self.stack.push(Term::Number(x)); Ok(()) },
            Term::Quote(stack) => { self.stack.push(Term::Quote(stack)); Ok(()) },
            Term::Name(name) => self.eval_name(name),
            Term::Prim(prim) => (prim.f)(self),
        }
    }

    fn eval_quote(&mut self, quote: Stack) -> EvalResult<()> {
        let mut quote = quote.into_iter();
        while let Some(term) = quote.next() {
            try!(self.eval_term(term))
        }
        Ok(())
    }

    fn eval_stack(&mut self) -> EvalResult<()> {
        while let Some(term) = self.quote.next() {
            try!(self.eval_term(term))
        }
        Ok(())
    }

    pub fn eval(mut self) -> EvalResult<Stack> {
        try!(self.eval_stack());
        Ok(self.stack)
    }
}

mod prim {
    use std::ops::*;
    use super::*;

    // dup : (A b -> A b b)
    pub fn dup(ev: &mut Evaluator) -> EvalResult<()> {
        ev.stack.dup()
    }

    // pop : (A b -> A)
    pub fn pop(ev: &mut Evaluator) -> EvalResult<()> {
        ev.stack.pop().map(|_| ())
    }

    // swap : (A b c -> A c b)
    pub fn swap(ev: &mut Evaluator) -> EvalResult<()> {
        ev.stack.swap()
    }

    // apply : (A (A -> B) -> B)
    pub fn apply(ev: &mut Evaluator) -> EvalResult<()> {
        let quote = try!(ev.stack.pop_quote());

        ev.eval_quote(quote)
    }

    // quote : (A b -> A (C -> C b))
    pub fn quote(ev: &mut Evaluator) -> EvalResult<()> {
        let term = try!(ev.stack.pop());
        let quoted = Term::Quote(Stack::new(vec![term]));

        ev.stack.push(quoted);

        Ok(())
    }

    // compose : (A (B -> C) (C -> D) -> A (B -> D)))
    pub fn compose(ev: &mut Evaluator) -> EvalResult<()> {
        let stack_a = try!(ev.stack.pop_quote());
        let mut stack_b = try!(ev.stack.pop_quote());

        stack_b.compose(stack_a);
        ev.stack.push(Term::Quote(stack_b));

        Ok(())
    }

    // words : (A ~> A)
    pub fn words(ev: &mut Evaluator) -> EvalResult<()> {
        for name in ev.words.defs.keys() {
            println!("{}", name);
        }

        Ok(())
    }

    // add : (A num num -> A num)
    pub fn add(ev: &mut Evaluator) -> EvalResult<()> {
        ev.stack.apply_binop(i32::add)
    }

    // sub : (A num num -> A num)
    pub fn sub(ev: &mut Evaluator) -> EvalResult<()> {
        ev.stack.apply_binop(i32::sub)
    }

    // mul : (A num num -> A num)
    pub fn mul(ev: &mut Evaluator) -> EvalResult<()> {
        ev.stack.apply_binop(i32::mul)
    }

    // div : (A num num -> A num)
    pub fn div(ev: &mut Evaluator) -> EvalResult<()> {
        ev.stack.apply_binop(i32::div)
    }

    // rem : (A num num -> A num)
    pub fn rem(ev: &mut Evaluator) -> EvalResult<()> {
        ev.stack.apply_binop(i32::rem)
    }
}
<|endoftext|>