<commit_before>use std::{
    env,
    ffi::OsStr,
    fs::{self, File},
    io::{BufReader, Cursor, Read, Seek},
    path::{Path, PathBuf},
    time::{Duration, SystemTime},
};

use anyhow::{ensure, Context, Result};
use app_dirs::{get_app_root, AppDataType};
use log::debug;
use reqwest::{blocking::Client, Proxy};
use walkdir::{DirEntry, WalkDir};
use zip::ZipArchive;

use crate::types::{PathSource, PlatformType};

static CACHE_DIR_ENV_VAR: &str = "TEALDEER_CACHE_DIR";

pub static TLDR_PAGES_DIR: &str = "tldr-pages";
static TLDR_OLD_PAGES_DIR: &str = "tldr-master";

#[derive(Debug)]
pub struct Cache {
    url: String,
    platform: PlatformType,
}

#[derive(Debug)]
pub struct PageLookupResult {
    pub page_path: PathBuf,
    pub patch_path: Option<PathBuf>,
}

impl PageLookupResult {
    pub fn with_page(page_path: PathBuf) -> Self {
        Self {
            page_path,
            patch_path: None,
        }
    }

    pub fn with_optional_patch(mut self, patch_path: Option<PathBuf>) -> Self {
        self.patch_path = patch_path;
        self
    }

    /// Create a buffered reader that sequentially reads from the page and the
    /// patch, as if they were concatenated.
    ///
    /// This will return an error if either the page file or the patch file
    /// cannot be opened.
    pub fn reader(&self) -> Result<BufReader<Box<dyn Read>>> {
        // Open page file
        let page_file = File::open(&self.page_path)
            .with_context(|| format!("Could not open page file at {:?}", self.page_path))?;

        // Open patch file
        let patch_file_opt = match &self.patch_path {
            Some(path) => Some(
                File::open(path)
                    .with_context(|| format!("Could not open patch file at {:?}", path))?,
            ),
            None => None,
        };

        // Create chained reader from file(s)
        //
        // Note: It might be worthwhile to create our own struct that accepts
        // the page and patch files and that will read them sequentially,
        // because it avoids the boxing below. However, the performance impact
        // would first need to be shown to be significant using a benchmark.
        Ok(BufReader::new(if let Some(patch_file) = patch_file_opt {
            Box::new(page_file.chain(&b"\n"[..]).chain(patch_file)) as Box<dyn Read>
        } else {
            Box::new(page_file) as Box<dyn Read>
        }))
    }
}

pub enum CacheFreshness {
    /// The cache is still fresh (less than MAX_CACHE_AGE old)
    Fresh,
    /// The cache is stale and should be updated
    Stale(Duration),
    /// The cache is missing
    Missing,
}

impl Cache {
    pub fn new<S>(url: S, platform: PlatformType) -> Self
    where
        S: Into<String>,
    {
        Self {
            url: url.into(),
            platform,
        }
    }

    /// Return the path to the cache directory.
    pub fn get_cache_dir() -> Result<(PathBuf, PathSource)> {
        // Allow overriding the cache directory by setting the env variable.
        if let Ok(value) = env::var(CACHE_DIR_ENV_VAR) {
            let path = PathBuf::from(value);
            let (path_exists, path_is_dir) = path
                .metadata()
                .map_or((false, false), |md| (true, md.is_dir()));
            ensure!(
                !path_exists || path_is_dir,
                "Path specified by ${} is not a directory",
                CACHE_DIR_ENV_VAR
            );
            if !path_exists {
                // Try to create the complete directory path.
                fs::create_dir_all(&path).with_context(|| {
                    format!(
                        "Directory path specified by ${} cannot be created",
                        CACHE_DIR_ENV_VAR
                    )
                })?;
                eprintln!(
                    "Successfully created cache directory path `{}`.",
                    path.to_str().unwrap()
                );
            }
            return Ok((path, PathSource::EnvVar));
        };

        // Otherwise, fall back to user cache directory.
        let dirs = get_app_root(AppDataType::UserCache, &crate::APP_INFO)
            .context("Could not determine user cache directory")?;
        Ok((dirs, PathSource::OsConvention))
    }

    /// Download the archive
    fn download(&self) -> Result<Vec<u8>> {
        let mut builder = Client::builder();
        if let Ok(ref host) = env::var("HTTP_PROXY") {
            if let Ok(proxy) = Proxy::http(host) {
                builder = builder.proxy(proxy);
            }
        }
        if let Ok(ref host) = env::var("HTTPS_PROXY") {
            if let Ok(proxy) = Proxy::https(host) {
                builder = builder.proxy(proxy);
            }
        }
        let client = builder
            .build()
            .context("Could not instantiate HTTP client")?;
        let mut resp = client.get(&self.url).send()?;
        let mut buf: Vec<u8> = vec![];
        let bytes_downloaded = resp.copy_to(&mut buf)?;
        debug!("{} bytes downloaded", bytes_downloaded);
        Ok(buf)
    }

    /// Decompress and open the archive
    fn decompress<R: Read + Seek>(reader: R) -> ZipArchive<R> {
        ZipArchive::new(reader).unwrap()
    }

    /// Update the pages cache.
    pub fn update(&self) -> Result<()> {
        // First, download the compressed data
        let bytes: Vec<u8> = self.download()?;

        // Decompress the response body into an `Archive`
        let mut archive = Self::decompress(Cursor::new(bytes));

        // Determine paths
        let (cache_dir, _) = Self::get_cache_dir()?;
        let pages_dir = cache_dir.join(TLDR_PAGES_DIR);

        // Make sure that cache directory exists
        debug!("Ensure cache directory {:?} exists", &cache_dir);
        fs::create_dir_all(&cache_dir).context("Could not create cache directory")?;

        // Clear cache directory
        // Note: This is not the best solution. Ideally we would download the
        // archive to a temporary directory and then swap the two directories.
        // But renaming a directory doesn't work across filesystems and Rust
        // does not yet offer a recursive directory copying function. So for
        // now, we'll use this approach.
        Self::clear().context("Could not clear the cache directory")?;

        // Extract archive
        archive
            .extract(&pages_dir)
            .context("Could not unpack compressed data")?;

        Ok(())
    }

    /// Return the duration since the cache directory was last modified.
    pub fn last_update() -> Option<Duration> {
        if let Ok((cache_dir, _)) = Self::get_cache_dir() {
            if let Ok(metadata) = fs::metadata(cache_dir.join(TLDR_PAGES_DIR)) {
                if let Ok(mtime) = metadata.modified() {
                    let now = SystemTime::now();
                    return now.duration_since(mtime).ok();
                };
            };
        };
        None
    }

    /// Return the freshness of the cache (fresh, stale or missing).
    pub fn freshness() -> CacheFreshness {
        match Cache::last_update() {
            Some(ago) if ago > crate::config::MAX_CACHE_AGE => CacheFreshness::Stale(ago),
            Some(_) => CacheFreshness::Fresh,
            None => CacheFreshness::Missing,
        }
    }

    /// Return the platform directory.
    fn get_platform_dir(&self) -> &'static str {
        match self.platform {
            PlatformType::Linux => "linux",
            PlatformType::OsX => "osx",
            PlatformType::SunOs => "sunos",
            PlatformType::Windows => "windows",
        }
    }

    /// Check for pages for a given platform in one of the given languages.
    fn find_page_for_platform(
        page_name: &str,
        cache_dir: &Path,
        platform: &str,
        language_dirs: &[String],
    ) -> Option<PathBuf> {
        language_dirs
            .iter()
            .map(|lang_dir| cache_dir.join(lang_dir).join(platform).join(page_name))
            .find(|path| path.exists() && path.is_file())
    }

    /// Look up custom patch (<name>.patch). If it exists, store it in a variable.
    fn find_patch(patch_name: &str, custom_pages_dir: Option<&Path>) -> Option<PathBuf> {
        custom_pages_dir
            .map(|custom_dir| custom_dir.join(patch_name))
            .filter(|path| path.exists() && path.is_file())
    }

    /// Search for a page and return the path to it.
    pub fn find_page(
        &self,
        name: &str,
        languages: &[String],
        custom_pages_dir: Option<&Path>,
    ) -> Option<PageLookupResult> {
        let page_filename = format!("{}.md", name);
        let patch_filename = format!("{}.patch", name);
        let custom_filename = format!("{}.page", name);

        // Get cache dir
        let cache_dir = match Self::get_cache_dir() {
            Ok((cache_dir, _)) => cache_dir.join(TLDR_PAGES_DIR),
            Err(e) => {
                log::error!("Could not get cache directory: {}", e);
                return None;
            }
        };

        let lang_dirs: Vec<String> = languages
            .iter()
            .map(|lang| {
                if lang == "en" {
                    String::from("pages")
                } else {
                    format!("pages.{}", lang)
                }
            })
            .collect();

        // Look up custom page (<name>.page). If it exists, return it directly
        if let Some(config_dir) = custom_pages_dir {
            let custom_page = config_dir.join(custom_filename);
            if custom_page.exists() && custom_page.is_file() {
                return Some(PageLookupResult::with_page(custom_page));
            }
        }

        let patch_path = Self::find_patch(&patch_filename, custom_pages_dir);

        // Try to find a platform specific path next, append custom patch to it.
        let platform_dir = self.get_platform_dir();
        if let Some(page) =
            Self::find_page_for_platform(&page_filename, &cache_dir, platform_dir, &lang_dirs)
        {
            return Some(PageLookupResult::with_page(page).with_optional_patch(patch_path));
        }

        // Did not find platform specific results, fall back to "common"
        Self::find_page_for_platform(&page_filename, &cache_dir, "common", &lang_dirs)
            .map(|page| PageLookupResult::with_page(page).with_optional_patch(patch_path))
    }

    /// Return the available pages.
    pub fn list_pages(&self) -> Result<Vec<String>> {
        // Determine platforms directory and platform
        let (cache_dir, _) = Self::get_cache_dir()?;
        let platforms_dir = cache_dir.join(TLDR_PAGES_DIR).join("pages");
        let platform_dir = self.get_platform_dir();

        // Closure that allows the WalkDir instance to traverse platform
        // specific and common page directories, but not others.
        let should_walk = |entry: &DirEntry| -> bool {
            let file_type = entry.file_type();
            let file_name = match entry.file_name().to_str() {
                Some(name) => name,
                None => return false,
            };
            if file_type.is_dir() {
                return file_name == "common" || file_name == platform_dir;
            } else if file_type.is_file() {
                return true;
            }
            false
        };

        // Recursively walk through common and (if applicable) platform specific directory
        let mut pages = WalkDir::new(platforms_dir)
            .min_depth(1) // Skip root directory
            .into_iter()
            .filter_entry(|e| should_walk(e)) // Filter out pages for other architectures
            .filter_map(Result::ok) // Convert results to options, filter out errors
            .filter_map(|e| {
                let path = e.path();
                let extension = &path.extension().and_then(OsStr::to_str).unwrap_or("");
                if e.file_type().is_file() && extension == &"md" {
                    path.file_stem()
                        .and_then(|stem| stem.to_str().map(Into::into))
                } else {
                    None
                }
            })
            .collect::<Vec<String>>();
        pages.sort();
        pages.dedup();
        Ok(pages)
    }

    /// Delete the cache directory.
    pub fn clear() -> Result<()> {
        let (path, _) = Self::get_cache_dir()?;

        // Check preconditions
        ensure!(
            path.exists(),
            "Cache path ({}) does not exist.",
            path.display(),
        );
        ensure!(
            path.is_dir(),
            "Cache path ({}) is not a directory.",
            path.display()
        );

        // Delete old tldr-pages cache location as well if present
        // TODO: To be removed in the future
        for pages_dir_name in [TLDR_PAGES_DIR, TLDR_OLD_PAGES_DIR] {
            let pages_dir = path.join(pages_dir_name);

            if pages_dir.exists() {
                fs::remove_dir_all(&pages_dir).with_context(|| {
                    format!(
                        "Could not remove the cache directory at {}",
                        pages_dir.display()
                    )
                })?;
            }
        }

        Ok(())
    }
}

/// Unit Tests for cache module
#[cfg(test)]
mod tests {
    use super::*;

    use std::{
        fs::File,
        io::{Read, Write},
    };

    #[test]
    fn test_reader_with_patch() {
        // Write test files
        let dir = tempfile::tempdir().unwrap();
        let page_path = dir.path().join("test.page");
        let patch_path = dir.path().join("test.patch");
        {
            let mut f1 = File::create(&page_path).unwrap();
            f1.write_all(b"Hello\n").unwrap();
            let mut f2 = File::create(&patch_path).unwrap();
            f2.write_all(b"World").unwrap();
        }

        // Create chained reader from lookup result
        let lr = PageLookupResult::with_page(page_path).with_optional_patch(Some(patch_path));
        let mut reader = lr.reader().unwrap();

        // Read into a Vec
        let mut buf = Vec::new();
        reader.read_to_end(&mut buf).unwrap();

        assert_eq!(&buf, b"Hello\n\nWorld");
    }

    #[test]
    fn test_reader_without_patch() {
        // Write test file
        let dir = tempfile::tempdir().unwrap();
        let page_path = dir.path().join("test.page");
        {
            let mut f = File::create(&page_path).unwrap();
            f.write_all(b"Hello\n").unwrap();
        }

        // Create chained reader from lookup result
        let lr = PageLookupResult::with_page(page_path);
        let mut reader = lr.reader().unwrap();

        // Read into a Vec
        let mut buf = Vec::new();
        reader.read_to_end(&mut buf).unwrap();

        assert_eq!(&buf, b"Hello\n");
    }
}
<commit_msg>When decompressing ZIP fails, propagate error<commit_after>use std::{
    env,
    ffi::OsStr,
    fs::{self, File},
    io::{BufReader, Cursor, Read},
    path::{Path, PathBuf},
    time::{Duration, SystemTime},
};

use anyhow::{ensure, Context, Result};
use app_dirs::{get_app_root, AppDataType};
use log::debug;
use reqwest::{blocking::Client, Proxy};
use walkdir::{DirEntry, WalkDir};
use zip::ZipArchive;

use crate::types::{PathSource, PlatformType};

static CACHE_DIR_ENV_VAR: &str = "TEALDEER_CACHE_DIR";

pub static TLDR_PAGES_DIR: &str = "tldr-pages";
static TLDR_OLD_PAGES_DIR: &str = "tldr-master";

#[derive(Debug)]
pub struct Cache {
    url: String,
    platform: PlatformType,
}

#[derive(Debug)]
pub struct PageLookupResult {
    pub page_path: PathBuf,
    pub patch_path: Option<PathBuf>,
}

impl PageLookupResult {
    pub fn with_page(page_path: PathBuf) -> Self {
        Self {
            page_path,
            patch_path: None,
        }
    }

    pub fn with_optional_patch(mut self, patch_path: Option<PathBuf>) -> Self {
        self.patch_path = patch_path;
        self
    }

    /// Create a buffered reader that sequentially reads from the page and the
    /// patch, as if they were concatenated.
    ///
    /// This will return an error if either the page file or the patch file
    /// cannot be opened.
    pub fn reader(&self) -> Result<BufReader<Box<dyn Read>>> {
        // Open page file
        let page_file = File::open(&self.page_path)
            .with_context(|| format!("Could not open page file at {:?}", self.page_path))?;

        // Open patch file
        let patch_file_opt = match &self.patch_path {
            Some(path) => Some(
                File::open(path)
                    .with_context(|| format!("Could not open patch file at {:?}", path))?,
            ),
            None => None,
        };

        // Create chained reader from file(s)
        //
        // Note: It might be worthwhile to create our own struct that accepts
        // the page and patch files and that will read them sequentially,
        // because it avoids the boxing below. However, the performance impact
        // would first need to be shown to be significant using a benchmark.
        Ok(BufReader::new(if let Some(patch_file) = patch_file_opt {
            Box::new(page_file.chain(&b"\n"[..]).chain(patch_file)) as Box<dyn Read>
        } else {
            Box::new(page_file) as Box<dyn Read>
        }))
    }
}

pub enum CacheFreshness {
    /// The cache is still fresh (less than MAX_CACHE_AGE old)
    Fresh,
    /// The cache is stale and should be updated
    Stale(Duration),
    /// The cache is missing
    Missing,
}

impl Cache {
    pub fn new<S>(url: S, platform: PlatformType) -> Self
    where
        S: Into<String>,
    {
        Self {
            url: url.into(),
            platform,
        }
    }

    /// Return the path to the cache directory.
    pub fn get_cache_dir() -> Result<(PathBuf, PathSource)> {
        // Allow overriding the cache directory by setting the env variable.
        if let Ok(value) = env::var(CACHE_DIR_ENV_VAR) {
            let path = PathBuf::from(value);
            let (path_exists, path_is_dir) = path
                .metadata()
                .map_or((false, false), |md| (true, md.is_dir()));
            ensure!(
                !path_exists || path_is_dir,
                "Path specified by ${} is not a directory",
                CACHE_DIR_ENV_VAR
            );
            if !path_exists {
                // Try to create the complete directory path.
                fs::create_dir_all(&path).with_context(|| {
                    format!(
                        "Directory path specified by ${} cannot be created",
                        CACHE_DIR_ENV_VAR
                    )
                })?;
                eprintln!(
                    "Successfully created cache directory path `{}`.",
                    path.to_str().unwrap()
                );
            }
            return Ok((path, PathSource::EnvVar));
        };

        // Otherwise, fall back to user cache directory.
        let dirs = get_app_root(AppDataType::UserCache, &crate::APP_INFO)
            .context("Could not determine user cache directory")?;
        Ok((dirs, PathSource::OsConvention))
    }

    /// Download the archive
    fn download(&self) -> Result<Vec<u8>> {
        let mut builder = Client::builder();
        if let Ok(ref host) = env::var("HTTP_PROXY") {
            if let Ok(proxy) = Proxy::http(host) {
                builder = builder.proxy(proxy);
            }
        }
        if let Ok(ref host) = env::var("HTTPS_PROXY") {
            if let Ok(proxy) = Proxy::https(host) {
                builder = builder.proxy(proxy);
            }
        }
        let client = builder
            .build()
            .context("Could not instantiate HTTP client")?;
        let mut resp = client.get(&self.url).send()?;
        let mut buf: Vec<u8> = vec![];
        let bytes_downloaded = resp.copy_to(&mut buf)?;
        debug!("{} bytes downloaded", bytes_downloaded);
        Ok(buf)
    }

    /// Update the pages cache.
    pub fn update(&self) -> Result<()> {
        // First, download the compressed data
        let bytes: Vec<u8> = self.download()?;

        // Decompress the response body into an `Archive`
        let mut archive = ZipArchive::new(Cursor::new(bytes))
            .context("Could not decompress downloaded ZIP archive")?;

        // Determine paths
        let (cache_dir, _) = Self::get_cache_dir()?;
        let pages_dir = cache_dir.join(TLDR_PAGES_DIR);

        // Make sure that cache directory exists
        debug!("Ensure cache directory {:?} exists", &cache_dir);
        fs::create_dir_all(&cache_dir).context("Could not create cache directory")?;

        // Clear cache directory
        // Note: This is not the best solution. Ideally we would download the
        // archive to a temporary directory and then swap the two directories.
        // But renaming a directory doesn't work across filesystems and Rust
        // does not yet offer a recursive directory copying function. So for
        // now, we'll use this approach.
        Self::clear().context("Could not clear the cache directory")?;

        // Extract archive
        archive
            .extract(&pages_dir)
            .context("Could not unpack compressed data")?;

        Ok(())
    }

    /// Return the duration since the cache directory was last modified.
    pub fn last_update() -> Option<Duration> {
        if let Ok((cache_dir, _)) = Self::get_cache_dir() {
            if let Ok(metadata) = fs::metadata(cache_dir.join(TLDR_PAGES_DIR)) {
                if let Ok(mtime) = metadata.modified() {
                    let now = SystemTime::now();
                    return now.duration_since(mtime).ok();
                };
            };
        };
        None
    }

    /// Return the freshness of the cache (fresh, stale or missing).
    pub fn freshness() -> CacheFreshness {
        match Cache::last_update() {
            Some(ago) if ago > crate::config::MAX_CACHE_AGE => CacheFreshness::Stale(ago),
            Some(_) => CacheFreshness::Fresh,
            None => CacheFreshness::Missing,
        }
    }

    /// Return the platform directory.
    fn get_platform_dir(&self) -> &'static str {
        match self.platform {
            PlatformType::Linux => "linux",
            PlatformType::OsX => "osx",
            PlatformType::SunOs => "sunos",
            PlatformType::Windows => "windows",
        }
    }

    /// Check for pages for a given platform in one of the given languages.
    fn find_page_for_platform(
        page_name: &str,
        cache_dir: &Path,
        platform: &str,
        language_dirs: &[String],
    ) -> Option<PathBuf> {
        language_dirs
            .iter()
            .map(|lang_dir| cache_dir.join(lang_dir).join(platform).join(page_name))
            .find(|path| path.exists() && path.is_file())
    }

    /// Look up custom patch (<name>.patch). If it exists, store it in a variable.
    fn find_patch(patch_name: &str, custom_pages_dir: Option<&Path>) -> Option<PathBuf> {
        custom_pages_dir
            .map(|custom_dir| custom_dir.join(patch_name))
            .filter(|path| path.exists() && path.is_file())
    }

    /// Search for a page and return the path to it.
    pub fn find_page(
        &self,
        name: &str,
        languages: &[String],
        custom_pages_dir: Option<&Path>,
    ) -> Option<PageLookupResult> {
        let page_filename = format!("{}.md", name);
        let patch_filename = format!("{}.patch", name);
        let custom_filename = format!("{}.page", name);

        // Get cache dir
        let cache_dir = match Self::get_cache_dir() {
            Ok((cache_dir, _)) => cache_dir.join(TLDR_PAGES_DIR),
            Err(e) => {
                log::error!("Could not get cache directory: {}", e);
                return None;
            }
        };

        let lang_dirs: Vec<String> = languages
            .iter()
            .map(|lang| {
                if lang == "en" {
                    String::from("pages")
                } else {
                    format!("pages.{}", lang)
                }
            })
            .collect();

        // Look up custom page (<name>.page). If it exists, return it directly
        if let Some(config_dir) = custom_pages_dir {
            let custom_page = config_dir.join(custom_filename);
            if custom_page.exists() && custom_page.is_file() {
                return Some(PageLookupResult::with_page(custom_page));
            }
        }

        let patch_path = Self::find_patch(&patch_filename, custom_pages_dir);

        // Try to find a platform specific path next, append custom patch to it.
        let platform_dir = self.get_platform_dir();
        if let Some(page) =
            Self::find_page_for_platform(&page_filename, &cache_dir, platform_dir, &lang_dirs)
        {
            return Some(PageLookupResult::with_page(page).with_optional_patch(patch_path));
        }

        // Did not find platform specific results, fall back to "common"
        Self::find_page_for_platform(&page_filename, &cache_dir, "common", &lang_dirs)
            .map(|page| PageLookupResult::with_page(page).with_optional_patch(patch_path))
    }

    /// Return the available pages.
    pub fn list_pages(&self) -> Result<Vec<String>> {
        // Determine platforms directory and platform
        let (cache_dir, _) = Self::get_cache_dir()?;
        let platforms_dir = cache_dir.join(TLDR_PAGES_DIR).join("pages");
        let platform_dir = self.get_platform_dir();

        // Closure that allows the WalkDir instance to traverse platform
        // specific and common page directories, but not others.
        let should_walk = |entry: &DirEntry| -> bool {
            let file_type = entry.file_type();
            let file_name = match entry.file_name().to_str() {
                Some(name) => name,
                None => return false,
            };
            if file_type.is_dir() {
                return file_name == "common" || file_name == platform_dir;
            } else if file_type.is_file() {
                return true;
            }
            false
        };

        // Recursively walk through common and (if applicable) platform specific directory
        let mut pages = WalkDir::new(platforms_dir)
            .min_depth(1) // Skip root directory
            .into_iter()
            .filter_entry(|e| should_walk(e)) // Filter out pages for other architectures
            .filter_map(Result::ok) // Convert results to options, filter out errors
            .filter_map(|e| {
                let path = e.path();
                let extension = &path.extension().and_then(OsStr::to_str).unwrap_or("");
                if e.file_type().is_file() && extension == &"md" {
                    path.file_stem()
                        .and_then(|stem| stem.to_str().map(Into::into))
                } else {
                    None
                }
            })
            .collect::<Vec<String>>();
        pages.sort();
        pages.dedup();
        Ok(pages)
    }

    /// Delete the cache directory.
    pub fn clear() -> Result<()> {
        let (path, _) = Self::get_cache_dir()?;

        // Check preconditions
        ensure!(
            path.exists(),
            "Cache path ({}) does not exist.",
            path.display(),
        );
        ensure!(
            path.is_dir(),
            "Cache path ({}) is not a directory.",
            path.display()
        );

        // Delete old tldr-pages cache location as well if present
        // TODO: To be removed in the future
        for pages_dir_name in [TLDR_PAGES_DIR, TLDR_OLD_PAGES_DIR] {
            let pages_dir = path.join(pages_dir_name);

            if pages_dir.exists() {
                fs::remove_dir_all(&pages_dir).with_context(|| {
                    format!(
                        "Could not remove the cache directory at {}",
                        pages_dir.display()
                    )
                })?;
            }
        }

        Ok(())
    }
}

/// Unit Tests for cache module
#[cfg(test)]
mod tests {
    use super::*;

    use std::{
        fs::File,
        io::{Read, Write},
    };

    #[test]
    fn test_reader_with_patch() {
        // Write test files
        let dir = tempfile::tempdir().unwrap();
        let page_path = dir.path().join("test.page");
        let patch_path = dir.path().join("test.patch");
        {
            let mut f1 = File::create(&page_path).unwrap();
            f1.write_all(b"Hello\n").unwrap();
            let mut f2 = File::create(&patch_path).unwrap();
            f2.write_all(b"World").unwrap();
        }

        // Create chained reader from lookup result
        let lr = PageLookupResult::with_page(page_path).with_optional_patch(Some(patch_path));
        let mut reader = lr.reader().unwrap();

        // Read into a Vec
        let mut buf = Vec::new();
        reader.read_to_end(&mut buf).unwrap();

        assert_eq!(&buf, b"Hello\n\nWorld");
    }

    #[test]
    fn test_reader_without_patch() {
        // Write test file
        let dir = tempfile::tempdir().unwrap();
        let page_path = dir.path().join("test.page");
        {
            let mut f = File::create(&page_path).unwrap();
            f.write_all(b"Hello\n").unwrap();
        }

        // Create chained reader from lookup result
        let lr = PageLookupResult::with_page(page_path);
        let mut reader = lr.reader().unwrap();

        // Read into a Vec
        let mut buf = Vec::new();
        reader.read_to_end(&mut buf).unwrap();

        assert_eq!(&buf, b"Hello\n");
    }
}
<|endoftext|>


<commit_before>use std::time;
use bytes::Bytes;
use rand::{thread_rng, Rng};
use version_vector::*;
use linear_map::{Entry as LMEntry, LinearMap};
use command::CommandError;
use resp::RespValue;
use bincode;

pub type MutatorFn<'a> = &'a mut FnMut(Id, Version, Cube, &VersionVector)
    -> Result<(Cube, Option<RespValue>), CommandError>;
pub type ResponseFn = fn(Cube) -> RespValue;

#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum Cube {
    // the order is used to merge different types in a deterministic way
    Counter(Counter),
    Value(Value),
    Map(Map),
    Set(Set),
    Void(VersionVector),
}

macro_rules! impl_into{
    ($s:ident, $v:ident) => {
        pub fn $s(self) -> Option<$v>{
            match self {
                Cube::$v(a) => Some(a),
                Cube::Void(a) => Some($v::with(a)),
                _ => None,
            }
        }
    }
}

impl Cube {
    pub fn is_subsumed(&self, bvv: &BitmappedVersionVector) -> bool {
        use self::Cube::*;
        match *self {
            Counter(_) => false, // TODO
            Value(ref a) => a.values.is_empty() && a.vv.contained(bvv),
            Map(ref a) => a.map.is_empty() && a.vv.contained(bvv),
            Set(ref a) => a.values.is_empty() && a.vv.contained(bvv),
            Void(_) => unreachable!(),
        }
    }

    impl_into!(into_value, Value);
    impl_into!(into_counter, Counter);
    impl_into!(into_map, Map);
    impl_into!(into_set, Set);

    // minimum set of dots required to assemble this cube
    pub fn for_each_dot<CB: FnMut(Id, Version)>(&self, mut cb: CB) {
        use self::Cube::*;
        match *self {
            Counter(ref a) => a.for_each_dot(&mut cb),
            Value(ref a) => a.vv.iter().for_each(|(i, v)| cb(i, v)),
            Map(ref a) => a.vv.iter().for_each(|(i, v)| cb(i, v)),
            Set(ref a) => a.vv.iter().for_each(|(i, v)| cb(i, v)),
            Void(_) => unreachable!(),
        }
    }

    pub fn new(bvv: &BitmappedVersionVector) -> Cube {
        let mut vv = VersionVector::new();
        for (&n, bv) in bvv.iter() {
            vv.add(n, bv.base());
        }
        Cube::Void(vv)
    }

    pub fn del(&mut self, id: Id, version: Version, vv: &VersionVector) -> bool {
        use self::Cube::*;
        match *self {
            Counter(_) => unimplemented!(),
            Value(ref mut a) => a.set(id, version, None, vv),
            Map(ref mut a) => a.clear(id, version),
            Set(ref mut a) => a.clear(id, version),
            Void(_) => return false,
        }
        true
    }

    pub fn merge(self, other: Self) -> Self {
        use self::Cube::*;
        match (self, other) {
            (Counter(a), Counter(b)) => Counter(a.merge(b)),
            (Value(a), Value(b)) => Value(a.merge(b)),
            (Map(a), Map(b)) => Map(a.merge(b)),
            (Set(a), Set(b)) => Set(a.merge(b)),
            (Void(vv), a) | (a, Void(vv)) => match a {
                Counter(a) => Counter(a),
                Value(a) => Value(a.merge(self::Value::with(vv))),
                Map(a) => Map(a.merge(self::Map::with(vv))),
                Set(a) => Set(a.merge(self::Set::with(vv))),
                Void(mut o_vv) => {
                    o_vv.merge(&vv);
                    Void(o_vv)
                }
            },
            (a, b) => {
                warn!("Merging Cubes with different types");
                #[allow(unreachable_patterns)]
                match (a, b) {
                    (Counter(a), _) | (_, Counter(a)) => Counter(a),
                    (Value(a), _) | (_, Value(a)) => Value(a),
                    (Map(a), _) | (_, Map(a)) => Map(a),
                    (Set(a), _) | (_, Set(a)) => Set(a),
                    (Void(_), _) | (_, Void(_)) => unreachable!(),
                }
            }
        }
    }
}

// LexCounter
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Counter {
    // I -> (V, C)
    pn: LinearMap<Id, (Version, i64)>,
}

impl Counter {
    fn with(_vv: VersionVector) -> Self {
        Counter {
            pn: Default::default(),
        }
    }

    fn get(&self) -> i64 {
        self.pn.values().map(|&(_, c)| c).sum()
    }

    fn for_each_dot<CB: FnMut(Id, Version)>(&self, cb: &mut CB) {
        self.pn.iter().for_each(|(&i, &(v, _))| cb(i, v));
    }

    fn merge(mut self, other: Self) -> Self {
        for (id, other) in other.pn {
            match self.pn.entry(id) {
                LMEntry::Occupied(mut oc) => if other.0 > oc.get().0 {
                    *oc.get_mut() = other;
                },
                LMEntry::Vacant(va) => {
                    va.insert(other);
                }
            }
        }
        self
    }
}

// MultiRegister
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Value {
    values: DotMap<Bytes>,
    vv: VersionVector,
}

impl Value {
    fn with(vv: VersionVector) -> Self {
        Value {
            values: Default::default(),
            vv,
        }
    }

    pub fn len(&self) -> usize {
        self.values.len()
    }

    pub fn set(&mut self, node: Id, version: Version, value: Option<Bytes>, vv: &VersionVector) {
        self.values.discard(vv);
        if let Some(value) = value {
            self.values.insert(node, version, value);
        }
        self.vv.add(node, version);
    }

    fn merge(mut self, mut other: Self) -> Self {
        self.values.merge(&mut other.values, &self.vv, &other.vv);
        self.vv.merge(&other.vv);
        self
    }
}

/// Actor Observed removal
/// Add wins on conflict
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Set {
    values: CausalMap<Bytes, DotSet>,
    vv: VersionVector,
}

impl Set {
    fn with(vv: VersionVector) -> Self {
        Set {
            values: Default::default(),
            vv,
        }
    }

    pub fn insert(&mut self, node: Id, version: Version, item: Bytes) -> bool {
        self.vv.add(node, version);
        self.values
            .insert(item, DotSet::from_dot((node, version)))
            .is_none()
    }

    pub fn remove(&mut self, node: Id, version: Version, item: &[u8]) -> bool {
        self.vv.add(node, version);
        self.values.remove(item).is_some()
    }

    pub fn pop(&mut self, node: Id, version: Version) -> Option<Bytes> {
        self.vv.add(node, version);
        if self.values.is_empty() {
            None
        } else {
            let i = thread_rng().gen::<usize>() % self.values.len();
            let k = self.values.keys().skip(i).next().unwrap().clone();
            self.values.remove(&k).unwrap();
            Some(k)
        }
    }

    pub fn clear(&mut self, node: Id, version: Version) {
        self.vv.add(node, version);
        self.values.clear();
    }

    fn merge(mut self, mut other: Self) -> Self {
        self.values.merge(&mut other.values, &self.vv, &other.vv);
        self.vv.merge(&other.vv);
        self
    }
}

// Actor Observed removal
// LWW on value conflict (max as tiebreaker)
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Map {
    map: CausalMap<Bytes, MapValue>,
    vv: VersionVector,
}

impl Map {
    fn with(vv: VersionVector) -> Self {
        Map {
            map: Default::default(),
            vv,
        }
    }

    pub fn insert(&mut self, node: Id, version: Version, key: Bytes, value: Bytes) -> bool {
        self.vv.add(node, version);
        self.map
            .insert(key, MapValue::new((node, version), value))
            .is_none()
    }

    pub fn remove(&mut self, node: Id, version: Version, key: &[u8]) -> bool {
        self.vv.add(node, version);
        self.map.remove(key).is_some()
    }

    pub fn clear(&mut self, node: Id, version: Version) {
        self.vv.add(node, version);
        self.map.clear();
    }

    fn merge(mut self, mut other: Self) -> Self {
        self.map.merge(&mut other.map, &self.vv, &other.vv);
        self.vv.merge(&other.vv);
        self
    }
}

#[derive(Clone, Debug, Default, Serialize, Deserialize)]
struct MapValue {
    dots: DotSet,
    value: Bytes,
    timestamp: u64, // millis since epoch
}

impl MapValue {
    fn new(dot: (Id, Version), value: Bytes) -> Self {
        let timestamp = time::UNIX_EPOCH.elapsed().unwrap();
        MapValue {
            dots: DotSet::from_dot(dot),
            value,
            timestamp: timestamp.as_secs() * 1_000 + (timestamp.subsec_nanos() / 1_000_000) as u64,
        }
    }
}

impl CausalValue for MapValue {
    fn merge<VV: AbsVersionVector>(&mut self, other: &mut Self, s_vv: &VV, o_vv: &VV) {
        self.dots.merge(&mut other.dots, s_vv, o_vv);
        // resolve possible value collision
        // if timestamps are equal value becomes max(a, b)
        if self.timestamp > other.timestamp {
            // nothing to do
        } else if other.timestamp > self.timestamp || other.value > self.value {
            self.timestamp = other.timestamp;
            ::std::mem::swap(&mut self.value, &mut other.value);
        }
    }

    fn is_empty(&self) -> bool {
        self.dots.is_empty()
    }
}

pub fn render_dummy(_: Cube) -> RespValue {
    unreachable!()
}

pub fn render_value_or_counter(cube: Cube) -> RespValue {
    match cube {
        Cube::Value(v) => {
            let serialized_vv = bincode::serialize(&v.vv, bincode::Infinite).unwrap();
            let mut values: Vec<_> = v.values
                .into_iter()
                .map(|(_, v)| RespValue::Data(v))
                .collect();
            values.push(RespValue::Data(serialized_vv.into()));
            RespValue::Array(values)
        }
        Cube::Counter(c) => RespValue::Int(c.get()),
        Cube::Void(vv) => {
            let serialized_vv = bincode::serialize(&vv, bincode::Infinite).unwrap();
            RespValue::Array(vec![RespValue::Data(serialized_vv.into())])
        }
        _ => CommandError::TypeError.into(),
    }
}

pub fn render_type(cube: Cube) -> RespValue {
    use self::Cube::*;
    let ty = match cube {
        Counter(_) => "counter", // non-standard
        Value(_) => "string",
        Map(_) => "hash",
        Set(_) => "set",
        Void(_) => "none",
    };
    RespValue::Data(ty.into())
}

pub fn render_map(cube: Cube) -> RespValue {
    match cube {
        Cube::Map(m) => {
            let mut array = Vec::with_capacity(m.map.len() * 2);
            for (k, v) in m.map.into_iter() {
                array.push(RespValue::Data(k));
                array.push(RespValue::Data(v.value));
            }
            RespValue::Array(array)
        }
        Cube::Void(_) => RespValue::Array(vec![]),
        _ => CommandError::TypeError.into(),
    }
}

pub fn render_set(cube: Cube) -> RespValue {
    match cube {
        Cube::Set(s) => {
            let mut array = Vec::with_capacity(s.values.len());
            for (k, _) in s.values.into_iter() {
                array.push(RespValue::Data(k));
            }
            RespValue::Array(array)
        }
        Cube::Void(_) => RespValue::Array(vec![]),
        _ => CommandError::TypeError.into(),
    }
}
<commit_msg>track dots correctly<commit_after>use std::time;
use bytes::Bytes;
use rand::{thread_rng, Rng};
use version_vector::*;
use linear_map::{Entry as LMEntry, LinearMap};
use command::CommandError;
use resp::RespValue;
use bincode;

pub type MutatorFn<'a> = &'a mut FnMut(Id, Version, Cube, &VersionVector)
    -> Result<(Cube, Option<RespValue>), CommandError>;
// TODO: this will eventually become a Box<Fn...>
pub type ResponseFn = fn(Cube) -> RespValue;

#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum Cube {
    // the order is used to merge different types in a deterministic way
    Counter(Counter),
    Value(Value),
    Map(Map),
    Set(Set),
    Void(VersionVector),
}

macro_rules! impl_into{
    ($s:ident, $v:ident) => {
        pub fn $s(self) -> Option<$v>{
            match self {
                Cube::$v(a) => Some(a),
                Cube::Void(a) => Some($v::with(a)),
                _ => None,
            }
        }
    }
}

impl Cube {
    pub fn is_subsumed(&self, bvv: &BitmappedVersionVector) -> bool {
        use self::Cube::*;
        match *self {
            Counter(_) => false, // TODO
            Value(ref a) => a.values.is_empty() && a.vv.contained(bvv),
            Map(ref a) => a.values.is_empty() && a.vv.contained(bvv),
            Set(ref a) => a.values.is_empty() && a.vv.contained(bvv),
            Void(_) => unreachable!(),
        }
    }

    impl_into!(into_value, Value);
    impl_into!(into_counter, Counter);
    impl_into!(into_map, Map);
    impl_into!(into_set, Set);

    // minimum set of dots required to assemble this cube
    // see comment at the bottom
    pub fn for_each_dot<CB: FnMut(Id, Version)>(&self, mut cb: CB) {
        use self::Cube::*;
        match *self {
            Counter(ref a) => a.for_each_dot(&mut cb),
            Value(ref a) => a.values.iter().for_each(|(&(i, v), _)| cb(i, v)),
            Map(ref a) => a.dots.iter().for_each(|(i, v)| cb(i, v)),
            Set(ref a) => a.dots.iter().for_each(|(i, v)| cb(i, v)),
            Void(_) => unreachable!(),
        }
    }

    pub fn new(bvv: &BitmappedVersionVector) -> Cube {
        let mut vv = VersionVector::new();
        for (&n, bv) in bvv.iter() {
            vv.add(n, bv.base());
        }
        Cube::Void(vv)
    }

    pub fn del(&mut self, id: Id, version: Version, vv: &VersionVector) -> bool {
        use self::Cube::*;
        match *self {
            Counter(_) => unimplemented!(),
            Value(ref mut a) => a.set(id, version, None, vv),
            Map(ref mut a) => a.clear(id, version),
            Set(ref mut a) => a.clear(id, version),
            Void(_) => return false,
        }
        true
    }

    pub fn merge(self, other: Self) -> Self {
        use self::Cube::*;
        match (self, other) {
            (Counter(a), Counter(b)) => Counter(a.merge(b)),
            (Value(a), Value(b)) => Value(a.merge(b)),
            (Map(a), Map(b)) => Map(a.merge(b)),
            (Set(a), Set(b)) => Set(a.merge(b)),
            (Void(vv), a) | (a, Void(vv)) => match a {
                Counter(a) => Counter(a),
                Value(a) => Value(a.merge(self::Value::with(vv))),
                Map(a) => Map(a.merge(self::Map::with(vv))),
                Set(a) => Set(a.merge(self::Set::with(vv))),
                Void(mut o_vv) => {
                    o_vv.merge(&vv);
                    Void(o_vv)
                }
            },
            (a, b) => {
                warn!("Merging Cubes with different types");
                #[allow(unreachable_patterns)]
                match (a, b) {
                    (Counter(a), _) | (_, Counter(a)) => Counter(a),
                    (Value(a), _) | (_, Value(a)) => Value(a),
                    (Map(a), _) | (_, Map(a)) => Map(a),
                    (Set(a), _) | (_, Set(a)) => Set(a),
                    (Void(_), _) | (_, Void(_)) => unreachable!(),
                }
            }
        }
    }
}

// LexCounter
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Counter {
    // I -> (V, C)
    pn: LinearMap<Id, (Version, i64)>,
}

impl Counter {
    fn with(_vv: VersionVector) -> Self {
        Counter {
            pn: Default::default(),
        }
    }

    fn get(&self) -> i64 {
        self.pn.values().map(|&(_, c)| c).sum()
    }

    fn for_each_dot<CB: FnMut(Id, Version)>(&self, cb: &mut CB) {
        self.pn.iter().for_each(|(&i, &(v, _))| cb(i, v));
    }

    fn merge(mut self, other: Self) -> Self {
        for (id, other) in other.pn {
            match self.pn.entry(id) {
                LMEntry::Occupied(mut oc) => if other.0 > oc.get().0 {
                    *oc.get_mut() = other;
                },
                LMEntry::Vacant(va) => {
                    va.insert(other);
                }
            }
        }
        self
    }
}

// MultiRegister
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Value {
    values: DotMap<Option<Bytes>>,
    vv: VersionVector,
}

impl Value {
    fn with(vv: VersionVector) -> Self {
        Value {
            values: Default::default(),
            vv,
        }
    }

    pub fn len(&self) -> usize {
        self.values.len()
    }

    pub fn set(&mut self, node: Id, version: Version, value: Option<Bytes>, vv: &VersionVector) {
        self.values.discard(vv);
        self.values.insert(node, version, value);
        self.vv.add(node, version);
    }

    fn merge(mut self, mut other: Self) -> Self {
        self.values.merge(&mut other.values, &self.vv, &other.vv);
        self.vv.merge(&other.vv);
        self
    }
}

/// Actor Observed removal
/// Add wins on conflict
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Set {
    values: CausalMap<Bytes, DotSet>,
    dots: VersionVector,
    vv: VersionVector,
}

impl Set {
    fn with(vv: VersionVector) -> Self {
        Set {
            values: Default::default(),
            dots: Default::default(),
            vv,
        }
    }

    pub fn insert(&mut self, node: Id, version: Version, item: Bytes) -> bool {
        self.vv.add(node, version);
        self.dots.add(node, version);
        self.values
            .insert(item, DotSet::from_dot((node, version)))
            .is_none()
    }

    pub fn remove(&mut self, node: Id, version: Version, item: &[u8]) -> bool {
        self.vv.add(node, version);
        self.dots.add(node, version);
        self.values.remove(item).is_some()
    }

    pub fn pop(&mut self, node: Id, version: Version) -> Option<Bytes> {
        self.vv.add(node, version);
        self.dots.add(node, version);
        if self.values.is_empty() {
            None
        } else {
            let i = thread_rng().gen::<usize>() % self.values.len();
            let k = self.values.keys().skip(i).next().unwrap().clone();
            self.values.remove(&k).unwrap();
            Some(k)
        }
    }

    pub fn clear(&mut self, node: Id, version: Version) {
        self.vv.add(node, version);
        self.dots.add(node, version);
        self.values.clear();
    }

    fn merge(mut self, mut other: Self) -> Self {
        self.values.merge(&mut other.values, &self.vv, &other.vv);
        self.vv.merge(&other.vv);
        self.dots.merge(&other.dots);
        self
    }
}

// Actor Observed removal
// LWW on value conflict (max as tiebreaker)
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Map {
    values: CausalMap<Bytes, MapValue>,
    dots: VersionVector,
    vv: VersionVector,
}

impl Map {
    fn with(vv: VersionVector) -> Self {
        Map {
            values: Default::default(),
            dots: Default::default(),
            vv,
        }
    }

    pub fn insert(&mut self, node: Id, version: Version, key: Bytes, value: Bytes) -> bool {
        self.vv.add(node, version);
        self.dots.add(node, version);
        self.values
            .insert(key, MapValue::new((node, version), value))
            .is_none()
    }

    pub fn remove(&mut self, node: Id, version: Version, key: &[u8]) -> bool {
        self.vv.add(node, version);
        self.dots.add(node, version);
        self.values.remove(key).is_some()
    }

    pub fn clear(&mut self, node: Id, version: Version) {
        self.vv.add(node, version);
        self.dots.add(node, version);
        self.values.clear();
    }

    fn merge(mut self, mut other: Self) -> Self {
        self.values.merge(&mut other.values, &self.vv, &other.vv);
        self.vv.merge(&other.vv);
        self.dots.merge(&other.dots);
        self
    }
}

#[derive(Clone, Debug, Default, Serialize, Deserialize)]
struct MapValue {
    dots: DotSet,
    value: Bytes,
    timestamp: u64, // millis since epoch
}

impl MapValue {
    fn new(dot: (Id, Version), value: Bytes) -> Self {
        let timestamp = time::UNIX_EPOCH.elapsed().unwrap();
        MapValue {
            dots: DotSet::from_dot(dot),
            value,
            timestamp: timestamp.as_secs() * 1_000 + (timestamp.subsec_nanos() / 1_000_000) as u64,
        }
    }
}

impl CausalValue for MapValue {
    fn merge<VV: AbsVersionVector>(&mut self, other: &mut Self, s_vv: &VV, o_vv: &VV) {
        self.dots.merge(&mut other.dots, s_vv, o_vv);
        // resolve possible value collision
        // if timestamps are equal value becomes max(a, b)
        if self.timestamp > other.timestamp {
            // nothing to do
        } else if other.timestamp > self.timestamp || other.value > self.value {
            self.timestamp = other.timestamp;
            ::std::mem::swap(&mut self.value, &mut other.value);
        }
    }

    fn is_empty(&self) -> bool {
        self.dots.is_empty()
    }
}

pub fn render_dummy(_: Cube) -> RespValue {
    unreachable!()
}

pub fn render_value_or_counter(cube: Cube) -> RespValue {
    match cube {
        Cube::Value(v) => {
            let serialized_vv = bincode::serialize(&v.vv, bincode::Infinite).unwrap();
            let mut values: Vec<_> = v.values
                .into_iter()
                .filter_map(|(_, ov)| ov.map(RespValue::Data))
                .collect();
            values.push(RespValue::Data(serialized_vv.into()));
            RespValue::Array(values)
        }
        Cube::Counter(c) => RespValue::Int(c.get()),
        Cube::Void(vv) => {
            let serialized_vv = bincode::serialize(&vv, bincode::Infinite).unwrap();
            RespValue::Array(vec![RespValue::Data(serialized_vv.into())])
        }
        _ => CommandError::TypeError.into(),
    }
}

pub fn render_type(cube: Cube) -> RespValue {
    use self::Cube::*;
    let ty = match cube {
        Counter(_) => "counter", // non-standard
        Value(_) => "string",
        Map(_) => "hash",
        Set(_) => "set",
        Void(_) => "none",
    };
    RespValue::Data(ty.into())
}

pub fn render_map(cube: Cube) -> RespValue {
    match cube {
        Cube::Map(m) => {
            let mut array = Vec::with_capacity(m.values.len() * 2);
            for (k, v) in m.values.into_iter() {
                array.push(RespValue::Data(k));
                array.push(RespValue::Data(v.value));
            }
            RespValue::Array(array)
        }
        Cube::Void(_) => RespValue::Array(vec![]),
        _ => CommandError::TypeError.into(),
    }
}

pub fn render_set(cube: Cube) -> RespValue {
    match cube {
        Cube::Set(s) => {
            let array = s.values.into_iter()
                .map(|(v, _)| RespValue::Data(v))
                .collect();
            RespValue::Array(array)
        }
        Cube::Void(_) => RespValue::Array(vec![]),
        _ => CommandError::TypeError.into(),
    }
}

/*
Using the vv from cubes as dots doesn't work, example bellow

-> n3 is partitioned out
-> n1 "SET a v" gets dot n1-1
n1: a => [n1-1 v][n1 1] log: n1-1 => a
n2: a => [n1-1 v][n1 1] log: n1-1 => a
n3: --

-> n2 "SET a z [n1 1]" dot n2-1
n1: a => [n2-1 z][n1 1, n2 1] log: n1-1 => a, n2-1 => a
n2: a => [n2-1 z][n1 1, n2 1] log: n1-1 => a, n2-1 => a
n3: --

-> n2 "SET b y" gets dot n2-2
n1: b => [n2-2 y][n1 1, n2 2] a => [n2-1 z][n1 1, n2 1] log: n1-1 => a, n2-1 => a, n2-2 => b
n2: b => [n2-2 y][n1 1, n2 2] a => [n2-1 z][n1 1, n2 1] log: n1-1 => a, n2-1 => a, n2-2 => b
n3: --

-> n3 can receive messages
-> n2 "SET c y" gets dot n2-3 (merges with void cube w/ [n1 1, n2 2])
n1: c => [n2-3 y][n1 1, n2 3] b => [n2-2 y][n1 1, n2 2] a => [n2-1 z][n1 1, n2 1] log: n1-1 => a, n2-1 => a, n2-2 => b, n2-3 => c
n2: c => [n2-3 y][n1 1, n2 3] b => [n2-2 y][n1 1, n2 2] a => [n2-1 z][n1 1, n2 1] log: n1-1 => a, n2-1 => a, n2-2 => b, n2-3 => c
n3: c => [n2-3 y][n1 1, n2 3] log: n2-3 => c, n1-1 => c

n3 stores (n1-1 => c) in dot log but that's wrong

*/
<|endoftext|>


<commit_before>//! This module focuses on handling errors generated when using Ardite in a
//! graceful manner.

/// Any error generated by Ardite or it‘s drivers should be output using this
/// type. This allows for a comprehensive display of the error when a service
/// reports it to the user.
pub struct Error {
  /// A specific error code which describes the error.
  code: ErrorCode,
  /// A message providing more detail beyond the error code.
  message: String,
  /// A hint to the user on what to do next to try and avoid the error
  /// happening again. This is optional.
  hint: Option<String>
}

/// The code of an error. Designed to easily map to [HTTP status codes][1].
///
/// [1]: http://www.restapitutorial.com/httpstatuscodes.html
pub enum ErrorCode {
  /// A bad syntax was used. Maps to 400.
  BadRequest = 400,
  /// Permissions do not allow this to happen. Maps to 403.
  Forbidden = 403,
  /// Resource was not found. Maps to 404.
  NotFound = 404,
  /// Present data made the request fail. Maps to 409.
  Conflict = 409,
  /// There was an invalid range. Maps to 416.
  BadRange = 416,
  /// Something bad happened inside a driver. Maps to 500.
  Internal = 500,
  /// The feature has not been implemented. Maps to 501.
  NotImplemented = 501,
}
<commit_msg>Remove comma<commit_after>//! This module focuses on handling errors generated when using Ardite in a
//! graceful manner.

/// Any error generated by Ardite or it‘s drivers should be output using this
/// type. This allows for a comprehensive display of the error when a service
/// reports it to the user.
pub struct Error {
  /// A specific error code which describes the error.
  code: ErrorCode,
  /// A message providing more detail beyond the error code.
  message: String,
  /// A hint to the user on what to do next to try and avoid the error
  /// happening again. This is optional.
  hint: Option<String>
}

/// The code of an error. Designed to easily map to [HTTP status codes][1].
///
/// [1]: http://www.restapitutorial.com/httpstatuscodes.html
pub enum ErrorCode {
  /// A bad syntax was used. Maps to 400.
  BadRequest = 400,
  /// Permissions do not allow this to happen. Maps to 403.
  Forbidden = 403,
  /// Resource was not found. Maps to 404.
  NotFound = 404,
  /// Present data made the request fail. Maps to 409.
  Conflict = 409,
  /// There was an invalid range. Maps to 416.
  BadRange = 416,
  /// Something bad happened inside a driver. Maps to 500.
  Internal = 500,
  /// The feature has not been implemented. Maps to 501.
  NotImplemented = 501
}
<|endoftext|>


<commit_before>use std::io::prelude::*;
use std::io;
use std::fs::File;
use std::str;

use ansi_term::Style;
use ansi_term::Colour::{Red, Yellow};

use parser;
#[derive(Debug)]
pub enum ProcessingError {
    ParseError(parser::ParseError),
    IoError(io::Error),
}

impl From<io::Error> for ProcessingError {
    fn from(from: io::Error) -> Self {
        ProcessingError::IoError(from)
    }
}

impl From<parser::ParseError> for ProcessingError {
    fn from(from: parser::ParseError) -> Self {
        ProcessingError::ParseError(from)
    }
}

pub fn get_error_and_line_for_file(parse_error: &parser::ParseError,
                                   file_name: &str)
                                   -> (parser::ParseError, String) {
    let mut parse_error = parse_error.clone();
    let mut buf_reader = io::BufReader::new(File::open(file_name).unwrap());
    let mut line = buf_reader.by_ref().lines().nth(parse_error.line - 1);

    let line_content;

    // error was in last line which was empty
    if let None = line {
        parse_error.line -= 1;
        buf_reader.seek(io::SeekFrom::Start(0)).unwrap();
        // more helpful to point to end of previous line
        line = buf_reader.lines().nth(parse_error.line - 1);
        line_content = line.unwrap().unwrap();
        parse_error.column = line_content.len() + 1;
    } else {
        line_content = line.unwrap().unwrap();
    }
    (parse_error, line_content)
}

pub fn print_parse_error(file_name: &String,
                         line_content: String,
                         parse_error: parser::ParseError) {
    println!("{}: {}: line {}, col {}: expected one of {:?}",
             Style::new().bold().paint(file_name.clone()),
             Red.bold().paint("parse error"),
             parse_error.line,
             parse_error.column,
             parse_error.expected);
    println!("{}", line_content);
    let mut pointer_string = String::from_utf8(vec![b' '; parse_error.column - 1]).unwrap();
    pointer_string.push('^');
    println!("{}", Style::new().bold().paint(pointer_string));
}

use ast::OffsetSpan;

#[derive(Debug)]
pub struct SourceSpan {
    pub start_line: usize,
    pub start_col: usize,
    pub end_line: usize,
    pub end_col: usize,
}

pub fn offset_span_to_source_span(span: OffsetSpan, input: &String) -> SourceSpan {
    let (start_line, start_col) = offset_to_line_and_col(input, span.0);
    let (end_line, end_col) = offset_to_line_and_col(input, span.1 - 1);
    SourceSpan {
        start_line: start_line,
        start_col: start_col,
        end_line: end_line,
        end_col: end_col,
    }
}

fn offset_to_line_and_col(input: &String, pos: usize) -> (usize, usize) {
    let mut remaining = pos;
    let mut line_num: usize = 1;
    for line in input.lines() {
        let line_length = line.len() + 1;
        if remaining < line_length {
            return (line_num, remaining + 1);
        }
        remaining -= line_length;
        line_num += 1;
    }
    return (line_num, remaining + 1);
}

use interpreter::InterpreterError;
use typechecker::TypeCheckerIssue;

fn adjust_source_span(span: &mut SourceSpan, file_content: &String) {
    if span.end_col == 1 { span.end_col = 0; }
    while (span.end_col == 0) && span.end_line >= span.start_line {
        span.end_line -= 1;
        span.end_col = file_content.lines().nth(span.end_line - 1).unwrap().len();
    }
}

pub fn print_interpreter_error_for_file(err: InterpreterError,
                                        span: SourceSpan,
                                        file_content: &String,
                                        file_name: &String) {
    print_typechecker_error_for_file(TypeCheckerIssue::InterpreterError(err),
                                     span,
                                     file_content,
                                     file_name);
}

pub fn print_typechecker_error_for_file(err: TypeCheckerIssue,
                                        span: SourceSpan,
                                        file_content: &String,
                                        file_name: &String) {
    let mut span = span;
    adjust_source_span(&mut span, file_content);
    if span.start_line == span.end_line {
        println!("in {}, line {}, col {}:",
                 Style::new().bold().paint(file_name.to_string()),
                 span.start_line,
                 span.start_col);
    } else {
        println!("in {}, starting on line {}, col {}:",
                 Style::new().bold().paint(file_name.to_string()),
                 span.start_line,
                 span.start_col);
    }
    match err {
        TypeCheckerIssue::InterpreterError(e) => {
            match e {
                InterpreterError::ReferenceError(id) => {
                    println!("{}: `{}` was not declared",
                             Red.bold().paint("reference error"),
                             id);
                }
                InterpreterError::UndeclaredAssignment(id) => {
                    println!("{}: cannot assign to undeclared `{}`",
                             Red.bold().paint("reference error"),
                             id);
                }
                InterpreterError::BinaryTypeError(binary_op, type1, type2) => {
                    println!("{}: `{}` cannot operate on types {} and {}",
                             Red.bold().paint("type error"),
                             binary_op,
                             type1,
                             type2);
                }
                InterpreterError::UnaryTypeError(unary_op, typ) => {
                    println!("{}: `{}` cannot operate on type {}",
                             Red.bold().paint("type error"),
                             unary_op,
                             typ);
                }
                InterpreterError::NoneError(id) => {
                    println!("{}: tried to use return value of non-returning function `{}`",
                             Red.bold().paint("missing value error"),
                             id);
                }
            }
        }
        TypeCheckerIssue::MultipleTypesFromBranchWarning(id) => {
            println!("{}: `{}` gets different types in branches",
                     Yellow.bold().paint("multiple types from branch"),
                     id);
        }
    }

    if span.start_line == span.end_line {
        let left_padding_size = span.start_line.to_string().len() + 3;
        println!("{} | {}",
                 span.start_line,
                 file_content.lines().nth(span.start_line - 1).unwrap());
        let left_padding = String::from_utf8(vec![b' '; span.start_col - 1 + left_padding_size])
            .unwrap();
        let pointer_string = String::from_utf8(vec![b'^'; span.end_col - span.start_col + 1]).unwrap();
        println!("{}{}", left_padding, Yellow.bold().paint(pointer_string));
    } else {
        let first_line_start_bytes = file_content.lines()
            .nth(span.start_line - 1)
            .unwrap()
            .bytes()
            .take(span.start_col - 1)
            .collect::<Vec<u8>>();
        let first_line_start_string = str::from_utf8(&first_line_start_bytes).unwrap();
        let first_line_rest_bytes = file_content.lines()
            .nth(span.start_line - 1)
            .unwrap()
            .bytes()
            .skip(span.start_col - 1)
            .collect::<Vec<u8>>();
        let first_line_rest_string = str::from_utf8(&first_line_rest_bytes).unwrap();

        let max_idx_width = span.end_line.to_string().len();

        println!("{line_num:width$} |", line_num = "", width = max_idx_width);

        println!("{line_num:width$} | {}{}",
                 first_line_start_string,
                 Red.bold().paint(first_line_rest_string),
                 line_num = span.start_line,
                 width = max_idx_width);

        for line_num in span.start_line..span.end_line - 1 {
            println!("{line_num:width$} | {}",
                     Red.bold().paint(file_content.lines().nth(line_num).unwrap()),
                     line_num = line_num + 1,
                     width = max_idx_width);
        }

        if span.end_col != 0 {
            let last_line_start_bytes = file_content.lines()
                .nth(span.end_line - 1)
                .unwrap()
                .bytes()
                .take(span.end_col)
                .collect::<Vec<u8>>();
            let last_line_start_string = str::from_utf8(&last_line_start_bytes).unwrap();
            let last_line_rest_bytes = file_content.lines()
                .nth(span.end_line - 1)
                .unwrap()
                .bytes()
                .skip(span.end_col)
                .collect::<Vec<u8>>();
            let last_line_rest_string = str::from_utf8(&last_line_rest_bytes).unwrap();
            println!("{line_num:width$} | {}{}",
                     Red.bold().paint(last_line_start_string),
                     last_line_rest_string,
                     line_num = span.end_line,
                     width = max_idx_width);
        }

        println!("{line_num:width$} |", line_num = "", width = max_idx_width);

    }
}
<commit_msg>Fix more off-by-one issues in error printing<commit_after>use std::io::prelude::*;
use std::io;
use std::fs::File;
use std::str;

use ansi_term::Style;
use ansi_term::Colour::{Red, Yellow};

use parser;
#[derive(Debug)]
pub enum ProcessingError {
    ParseError(parser::ParseError),
    IoError(io::Error),
}

impl From<io::Error> for ProcessingError {
    fn from(from: io::Error) -> Self {
        ProcessingError::IoError(from)
    }
}

impl From<parser::ParseError> for ProcessingError {
    fn from(from: parser::ParseError) -> Self {
        ProcessingError::ParseError(from)
    }
}

pub fn get_error_and_line_for_file(parse_error: &parser::ParseError,
                                   file_name: &str)
                                   -> (parser::ParseError, String) {
    let mut parse_error = parse_error.clone();
    let mut buf_reader = io::BufReader::new(File::open(file_name).unwrap());
    let mut line = buf_reader.by_ref().lines().nth(parse_error.line - 1);

    let line_content;

    // error was in last line which was empty
    if let None = line {
        parse_error.line -= 1;
        buf_reader.seek(io::SeekFrom::Start(0)).unwrap();
        // more helpful to point to end of previous line
        line = buf_reader.lines().nth(parse_error.line - 1);
        line_content = line.unwrap().unwrap();
        parse_error.column = line_content.len() + 1;
    } else {
        line_content = line.unwrap().unwrap();
    }
    (parse_error, line_content)
}

pub fn print_parse_error(file_name: &String,
                         line_content: String,
                         parse_error: parser::ParseError) {
    println!("{}: {}: line {}, col {}: expected one of {:?}",
             Style::new().bold().paint(file_name.clone()),
             Red.bold().paint("parse error"),
             parse_error.line,
             parse_error.column,
             parse_error.expected);
    println!("{}", line_content);
    let mut pointer_string = String::from_utf8(vec![b' '; parse_error.column - 1]).unwrap();
    pointer_string.push('^');
    println!("{}", Style::new().bold().paint(pointer_string));
}

use ast::OffsetSpan;

#[derive(Debug)]
pub struct SourceSpan {
    pub start_line: usize,
    pub start_col: usize,
    pub end_line: usize,
    pub end_col: usize,
}

pub fn offset_span_to_source_span(span: OffsetSpan, input: &String) -> SourceSpan {
    let (start_line, start_col) = offset_to_line_and_col(input, span.0);
    let (end_line, end_col) = offset_to_line_and_col(input, span.1 - 1);
    SourceSpan {
        start_line: start_line,
        start_col: start_col,
        end_line: end_line,
        end_col: end_col,
    }
}

fn offset_to_line_and_col(input: &String, pos: usize) -> (usize, usize) {
    let mut remaining = pos;
    let mut line_num: usize = 1;
    for line in input.lines() {
        let line_length = line.len() + 1;
        if remaining < line_length {
            return (line_num, remaining + 1);
        }
        remaining -= line_length;
        line_num += 1;
    }
    return (line_num, remaining + 1);
}

use interpreter::InterpreterError;
use typechecker::TypeCheckerIssue;

fn adjust_source_span(span: &mut SourceSpan, file_content: &String) {
    if (span.start_line != span.end_line) && span.end_col == 1 { span.end_col = 0; }
    while (span.end_col == 0) && span.end_line > span.start_line {
        span.end_line -= 1;
        span.end_col = file_content.lines().nth(span.end_line - 1).unwrap().len();
    }
}

pub fn print_interpreter_error_for_file(err: InterpreterError,
                                        span: SourceSpan,
                                        file_content: &String,
                                        file_name: &String) {
    print_typechecker_error_for_file(TypeCheckerIssue::InterpreterError(err),
                                     span,
                                     file_content,
                                     file_name);
}

pub fn print_typechecker_error_for_file(err: TypeCheckerIssue,
                                        span: SourceSpan,
                                        file_content: &String,
                                        file_name: &String) {
    let mut span = span;
    adjust_source_span(&mut span, file_content);
    if span.start_line == span.end_line {
        println!("in {}, line {}, col {}:",
                 Style::new().bold().paint(file_name.to_string()),
                 span.start_line,
                 span.start_col);
    } else {
        println!("in {}, starting on line {}, col {}:",
                 Style::new().bold().paint(file_name.to_string()),
                 span.start_line,
                 span.start_col);
    }
    match err {
        TypeCheckerIssue::InterpreterError(e) => {
            match e {
                InterpreterError::ReferenceError(id) => {
                    println!("{}: `{}` was not declared",
                             Red.bold().paint("reference error"),
                             id);
                }
                InterpreterError::UndeclaredAssignment(id) => {
                    println!("{}: cannot assign to undeclared `{}`",
                             Red.bold().paint("reference error"),
                             id);
                }
                InterpreterError::BinaryTypeError(binary_op, type1, type2) => {
                    println!("{}: `{}` cannot operate on types {} and {}",
                             Red.bold().paint("type error"),
                             binary_op,
                             type1,
                             type2);
                }
                InterpreterError::UnaryTypeError(unary_op, typ) => {
                    println!("{}: `{}` cannot operate on type {}",
                             Red.bold().paint("type error"),
                             unary_op,
                             typ);
                }
                InterpreterError::NoneError(id) => {
                    println!("{}: tried to use return value of non-returning function `{}`",
                             Red.bold().paint("missing value error"),
                             id);
                }
            }
        }
        TypeCheckerIssue::MultipleTypesFromBranchWarning(id) => {
            println!("{}: `{}` gets different types in branches",
                     Yellow.bold().paint("multiple types from branch"),
                     id);
        }
    }

    if span.start_line == span.end_line {
        let left_padding_size = span.start_line.to_string().len() + 3;
        println!("{} | {}",
                 span.start_line,
                 file_content.lines().nth(span.start_line - 1).unwrap());
        let left_padding = String::from_utf8(vec![b' '; span.start_col + left_padding_size - 1])
            .unwrap();
        let pointer_string = String::from_utf8(vec![b'^'; span.end_col + 1 - span.start_col]).unwrap();
        println!("{}{}", left_padding, Yellow.bold().paint(pointer_string));
    } else {
        let first_line_start_bytes = file_content.lines()
            .nth(span.start_line - 1)
            .unwrap()
            .bytes()
            .take(span.start_col - 1)
            .collect::<Vec<u8>>();
        let first_line_start_string = str::from_utf8(&first_line_start_bytes).unwrap();
        let first_line_rest_bytes = file_content.lines()
            .nth(span.start_line - 1)
            .unwrap()
            .bytes()
            .skip(span.start_col - 1)
            .collect::<Vec<u8>>();
        let first_line_rest_string = str::from_utf8(&first_line_rest_bytes).unwrap();

        let max_idx_width = span.end_line.to_string().len();

        println!("{line_num:width$} |", line_num = "", width = max_idx_width);

        println!("{line_num:width$} | {}{}",
                 first_line_start_string,
                 Red.bold().paint(first_line_rest_string),
                 line_num = span.start_line,
                 width = max_idx_width);

        for line_num in span.start_line..span.end_line - 1 {
            println!("{line_num:width$} | {}",
                     Red.bold().paint(file_content.lines().nth(line_num).unwrap()),
                     line_num = line_num + 1,
                     width = max_idx_width);
        }

        if span.end_col != 0 {
            let last_line_start_bytes = file_content.lines()
                .nth(span.end_line - 1)
                .unwrap()
                .bytes()
                .take(span.end_col)
                .collect::<Vec<u8>>();
            let last_line_start_string = str::from_utf8(&last_line_start_bytes).unwrap();
            let last_line_rest_bytes = file_content.lines()
                .nth(span.end_line - 1)
                .unwrap()
                .bytes()
                .skip(span.end_col)
                .collect::<Vec<u8>>();
            let last_line_rest_string = str::from_utf8(&last_line_rest_bytes).unwrap();
            println!("{line_num:width$} | {}{}",
                     Red.bold().paint(last_line_start_string),
                     last_line_rest_string,
                     line_num = span.end_line,
                     width = max_idx_width);
        }

        println!("{line_num:width$} |", line_num = "", width = max_idx_width);

    }
}
<|endoftext|>


<commit_before>use std::marker::{PhantomData};
use std::collections::{BTreeMap};
use std::default::Default;

/// A backdrop maps all points of a field to some value.
///
/// It is usually a simple constant function.
pub trait Backdrop<D, R> {
    fn get(&self, pos: D) -> R;
}

#[derive(Copy, Clone, RustcEncodable, RustcDecodable)]
/// A backdrop that returns a single value for every field point.
pub struct ConstBackdrop<R>(pub R);

impl<D, R: Copy> Backdrop<D, R> for ConstBackdrop<R> {
    fn get(&self, _key: D) -> R { self.0 }
}

/// A patch dynamically overwrites some values in a field.
pub trait Patch<D, R>: Default {
    fn get<'a>(&'a self, pos: D) -> Option<&'a R>;
    fn set(&mut self, pos: D, val: R);
    fn clear(&mut self, pos: D);
}

impl<D: Ord, R> Patch<D, R> for BTreeMap<D, R> {
    fn get<'a>(&'a self, pos: D) -> Option<&'a R> { self.get(&pos) }
    fn set(&mut self, pos: D, val: R) { self.insert(pos, val); }
    fn clear(&mut self, pos: D) { self.remove(&pos); }
}

#[derive(Clone, RustcEncodable, RustcDecodable)]
/// A generic field structure that overlays a sparse, mutable patch of values
/// on top of a backdrop type that provides default values for the entire
/// query domain.
pub struct Field<D, R, B, P> {
    backdrop: B,
    patch: P,
    _d: PhantomData<D>,
    _r: PhantomData<R>,
}

impl<D: Copy, R: Copy, B, P> Field<D, R, B, P>
    where R: Copy,
          B: Backdrop<D, R>,
          P: Patch<D, R>
{
    pub fn new(b: B) -> Field<D, R, B, P> {
        Field {
            backdrop: b,
            patch: Default::default(),
            _d: PhantomData,
            _r: PhantomData,
        }
    }

    /// Get the value of the field in the given point.
    ///
    /// If the value has been overriden by calling set, that value will be
    /// returned. Otherwise a value from the backdrop structure will be
    /// returned.
    pub fn get(&self, pos: D) -> R {
        match self.patch.get(pos) {
            Some(&v) => v,
            None => self.backdrop.get(pos)
        }
    }

    /// Override the value in a given point.
    pub fn set(&mut self, pos: D, val: R) { self.patch.set(pos, val); }

    /// Clear the value in a given point.
    pub fn clear(&mut self, pos: D) { self.patch.clear(pos); }
}
<commit_msg>HashMap patch impl for Field<commit_after>use std::hash::{Hash};
use std::marker::{PhantomData};
use std::collections::{BTreeMap, HashMap};
use std::default::Default;

/// A backdrop maps all points of a field to some value.
///
/// It is usually a simple constant function.
pub trait Backdrop<D, R> {
    fn get(&self, pos: D) -> R;
}

#[derive(Copy, Clone, RustcEncodable, RustcDecodable)]
/// A backdrop that returns a single value for every field point.
pub struct ConstBackdrop<R>(pub R);

impl<D, R: Copy> Backdrop<D, R> for ConstBackdrop<R> {
    fn get(&self, _key: D) -> R { self.0 }
}

/// A patch dynamically overwrites some values in a field.
pub trait Patch<D, R>: Default {
    fn get<'a>(&'a self, pos: D) -> Option<&'a R>;
    fn set(&mut self, pos: D, val: R);
    fn clear(&mut self, pos: D);
}

impl<D: Ord, R> Patch<D, R> for BTreeMap<D, R> {
    fn get<'a>(&'a self, pos: D) -> Option<&'a R> { self.get(&pos) }
    fn set(&mut self, pos: D, val: R) { self.insert(pos, val); }
    fn clear(&mut self, pos: D) { self.remove(&pos); }
}

impl<D: Eq+Hash, R> Patch<D, R> for HashMap<D, R> {
    fn get<'a>(&'a self, pos: D) -> Option<&'a R> { self.get(&pos) }
    fn set(&mut self, pos: D, val: R) { self.insert(pos, val); }
    fn clear(&mut self, pos: D) { self.remove(&pos); }
}

#[derive(Clone, RustcEncodable, RustcDecodable)]
/// A generic field structure that overlays a sparse, mutable patch of values
/// on top of a backdrop type that provides default values for the entire
/// query domain.
pub struct Field<D, R, B, P> {
    backdrop: B,
    patch: P,
    _d: PhantomData<D>,
    _r: PhantomData<R>,
}

impl<D: Copy, R: Copy, B, P> Field<D, R, B, P>
    where R: Copy,
          B: Backdrop<D, R>,
          P: Patch<D, R>
{
    pub fn new(b: B) -> Field<D, R, B, P> {
        Field {
            backdrop: b,
            patch: Default::default(),
            _d: PhantomData,
            _r: PhantomData,
        }
    }

    /// Get the value of the field in the given point.
    ///
    /// If the value has been overriden by calling set, that value will be
    /// returned. Otherwise a value from the backdrop structure will be
    /// returned.
    pub fn get(&self, pos: D) -> R {
        match self.patch.get(pos) {
            Some(&v) => v,
            None => self.backdrop.get(pos)
        }
    }

    /// Override the value in a given point.
    pub fn set(&mut self, pos: D, val: R) { self.patch.set(pos, val); }

    /// Clear the value in a given point.
    pub fn clear(&mut self, pos: D) { self.patch.clear(pos); }
}
<|endoftext|>