<commit_before>use std::default::Default;
use std::path;

use serde_yaml;

use error::*;
use files;
use frontmatter;
use legacy::wildwest;
use sass;
use site;
use slug;
use syntax_highlight::has_syntax_theme;

arg_enum! {
    #[derive(Serialize, Deserialize)]
    #[derive(Debug, PartialEq, Copy, Clone)]
    pub enum Dump {
        DocObject,
        DocTemplate,
        DocLinkObject,
        Document
    }
}

impl Dump {
    pub fn is_doc(&self) -> bool {
        true
    }
}

#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)]
#[derive(Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum SortOrder {
    Asc,
    Desc,
}

impl Default for SortOrder {
    fn default() -> SortOrder {
        SortOrder::Desc
    }
}

#[derive(Debug, PartialEq)]
#[derive(Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SyntaxHighlight {
    pub theme: String,
}

impl Default for SyntaxHighlight {
    fn default() -> SyntaxHighlight {
        SyntaxHighlight { theme: "base16-ocean.dark".to_owned() }
    }
}

#[derive(Debug, PartialEq, Default)]
#[derive(Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PageBuilder {
    pub default: frontmatter::FrontmatterBuilder,
}

#[derive(Debug, PartialEq)]
#[derive(Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PostBuilder {
    pub name: Option<String>,
    pub slug: Option<String>,
    pub description: Option<String>,
    pub dir: String,
    pub drafts_dir: Option<String>,
    pub order: SortOrder,
    pub rss: Option<String>,
    pub jsonfeed: Option<String>,
    pub default: frontmatter::FrontmatterBuilder,
}

impl Default for PostBuilder {
    fn default() -> PostBuilder {
        Self {
            name: None,
            slug: None,
            description: None,
            dir: "posts".to_owned(),
            drafts_dir: None,
            order: SortOrder::default(),
            rss: None,
            jsonfeed: None,
            default: frontmatter::FrontmatterBuilder::new().set_post(true),
        }
    }
}

#[derive(Debug, PartialEq, Default)]
#[derive(Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct AssetsBuilder {
    pub sass: sass::SassOptions,
}

const LAYOUTS_DIR: &'static str = "_layouts";

#[derive(Debug, PartialEq)]
#[derive(Serialize, Deserialize)]
#[serde(deny_unknown_fields, default)]
pub struct ConfigBuilder {
    #[serde(skip)]
    pub root: path::PathBuf,
    pub source: String,
    pub destination: String,
    #[serde(skip)]
    pub abs_dest: Option<String>,
    #[serde(skip)]
    pub include_drafts: bool,
    pub default: frontmatter::FrontmatterBuilder,
    pub pages: PageBuilder,
    pub posts: PostBuilder,
    pub site: site::SiteBuilder,
    pub template_extensions: Vec<String>,
    pub ignore: Vec<String>,
    pub syntax_highlight: SyntaxHighlight,
    pub layouts_dir: &'static str,
    pub assets: AssetsBuilder,
    // This is a debug-only field and should be transient rather than persistently set.
    #[serde(skip)]
    pub dump: Vec<Dump>,
}

impl Default for ConfigBuilder {
    fn default() -> ConfigBuilder {
        ConfigBuilder {
            root: path::PathBuf::new(),
            source: "./".to_owned(),
            destination: "./_site".to_owned(),
            abs_dest: None,
            include_drafts: false,
            default: frontmatter::FrontmatterBuilder::new()
                .set_excerpt_separator("\n\n".to_owned())
                .set_draft(false)
                .set_post(false),
            pages: PageBuilder::default(),
            posts: PostBuilder::default(),
            site: site::SiteBuilder::default(),
            template_extensions: vec!["md".to_owned(), "liquid".to_owned()],
            ignore: vec![],
            syntax_highlight: SyntaxHighlight::default(),
            layouts_dir: LAYOUTS_DIR,
            assets: AssetsBuilder::default(),
            dump: vec![],
        }
    }
}

impl ConfigBuilder {
    pub fn from_file<P: Into<path::PathBuf>>(path: P) -> Result<ConfigBuilder> {
        Self::from_file_internal(path.into())
    }

    fn from_file_internal(path: path::PathBuf) -> Result<ConfigBuilder> {
        let content = files::read_file(&path)?;

        if content.trim().is_empty() {
            return Ok(ConfigBuilder::default());
        }

        let config: wildwest::GlobalConfig = serde_yaml::from_str(&content)?;
        let mut config: ConfigBuilder = config.into();

        let mut root = path;
        root.pop(); // Remove filename
        config.root = root;

        Ok(config)
    }

    pub fn from_cwd<P: Into<path::PathBuf>>(cwd: P) -> Result<ConfigBuilder> {
        Self::from_cwd_internal(cwd.into())
    }

    fn from_cwd_internal(cwd: path::PathBuf) -> Result<ConfigBuilder> {
        let file_path = files::find_project_file(&cwd, ".cobalt.yml");
        let mut config = file_path
            .map(|p| {
                     info!("Using config file {:?}", &p);
                     Self::from_file(&p).chain_err(|| format!("Error reading config file {:?}", p))
                 })
            .unwrap_or_else(|| {
                warn!("No .cobalt.yml file found in current directory, using default config.");
                Ok(ConfigBuilder::default())
            })?;
        config.root = cwd;
        Ok(config)
    }

    pub fn build(self) -> Result<Config> {
        let ConfigBuilder {
            root,
            source,
            destination,
            abs_dest,
            include_drafts,
            default,
            pages,
            posts,
            site,
            template_extensions,
            ignore,
            syntax_highlight,
            layouts_dir,
            assets,
            dump,
        } = self;

        let result: Result<()> = match has_syntax_theme(&syntax_highlight.theme) {
            Ok(true) => Ok(()),
            Ok(false) => {
                Err(format!("Syntax theme '{}' is unsupported", syntax_highlight.theme).into())
            }
            Err(err) => {
                warn!("Syntax theme named '{}' ignored. Reason: {}",
                      syntax_highlight.theme,
                      err);
                Ok(())
            }
        };
        result?;

        let mut pages = pages;
        pages.default = pages.default.merge(default.clone());

        let mut posts = posts;
        posts.default = posts.default.merge(default);
        if posts.dir.starts_with('/') {
            bail!("posts dir {} must be a relative path", posts.dir)
        }
        if let Some(ref drafts_dir) = posts.drafts_dir {
            if drafts_dir.starts_with('/') {
                bail!("posts dir {} must be a relative path", drafts_dir)
            }
        }
        if posts.slug.is_none() {
            if let Some(ref name) = posts.name {
                posts.slug = Some(slug::slugify(name));
            } else {
                posts.slug = Some(posts.dir.clone());
            }
        }

        let source = files::cleanup_path(source);
        let destination = files::cleanup_path(destination);

        let mut ignore = ignore;
        if let Ok(rel_dest) = path::Path::new(&destination).strip_prefix(&source) {
            let rel_dest = rel_dest.to_str().expect("started as a utf-8 string");
            if !rel_dest.is_empty() {
                ignore.push(format!("/{}", rel_dest.to_owned()));
            }
        }

        let source = root.join(source);
        let destination = abs_dest
            .map(|s| s.into())
            .unwrap_or_else(|| root.join(destination));

        let site = site.build(&source)?;

        let config = Config {
            source,
            destination,
            include_drafts,
            pages,
            posts,
            site,
            ignore,
            template_extensions,
            syntax_highlight,
            layouts_dir,
            assets,
            dump,
        };

        Ok(config)
    }
}

#[derive(Debug, PartialEq)]
#[derive(Serialize, Deserialize)]
#[serde(deny_unknown_fields, default)]
pub struct Config {
    pub source: path::PathBuf,
    pub destination: path::PathBuf,
    pub include_drafts: bool,
    pub pages: PageBuilder,
    pub posts: PostBuilder,
    pub site: site::Site,
    pub template_extensions: Vec<String>,
    pub ignore: Vec<String>,
    pub syntax_highlight: SyntaxHighlight,
    pub layouts_dir: &'static str,
    pub assets: AssetsBuilder,
    pub dump: Vec<Dump>,
}

impl Default for Config {
    fn default() -> Config {
        ConfigBuilder::default()
            .build()
            .expect("default config should not fail")
    }
}

#[test]
fn test_from_file_ok() {
    let result = ConfigBuilder::from_file("tests/fixtures/config/.cobalt.yml").unwrap();
    assert_eq!(result,
               ConfigBuilder {
                   root: path::Path::new("tests/fixtures/config").to_path_buf(),
                   destination: "./dest".to_owned(),
                   posts: PostBuilder {
                       dir: "_my_posts".to_owned(),
                       drafts_dir: Some("_drafts".to_owned()),
                       ..Default::default()
                   },
                   ..Default::default()
               });
}

#[test]
fn test_from_file_rss() {
    let result = ConfigBuilder::from_file("tests/fixtures/config/rss.yml").unwrap();
    assert_eq!(result,
               ConfigBuilder {
                   root: path::Path::new("tests/fixtures/config").to_path_buf(),
                   destination: "./".to_owned(),
                   posts: PostBuilder {
                       drafts_dir: Some("_drafts".to_owned()),
                       rss: Some("rss.xml".to_owned()),
                       ..Default::default()
                   },
                   site: site::SiteBuilder {
                       title: Some("My blog!".to_owned()),
                       description: Some("Blog description".to_owned()),
                       base_url: Some("http://example.com".to_owned()),
                       ..Default::default()
                   },
                   ..Default::default()
               });
}

#[test]
fn test_from_file_empty() {
    let result = ConfigBuilder::from_file("tests/fixtures/config/empty.yml").unwrap();
    assert_eq!((result), ConfigBuilder { ..Default::default() });
}

#[test]
fn test_from_file_invalid_syntax() {
    let result = ConfigBuilder::from_file("tests/fixtures/config/invalid_syntax.yml");
    assert!(result.is_err());
}

#[test]
fn test_from_file_not_found() {
    let result = ConfigBuilder::from_file("tests/fixtures/config/config_does_not_exist.yml");
    assert!(result.is_err());
}

#[test]
fn test_from_cwd_ok() {
    let result = ConfigBuilder::from_cwd("tests/fixtures/config").unwrap();
    assert_eq!(result,
               ConfigBuilder {
                   root: path::Path::new("tests/fixtures/config").to_path_buf(),
                   destination: "./dest".to_owned(),
                   posts: PostBuilder {
                       dir: "_my_posts".to_owned(),
                       drafts_dir: Some("_drafts".to_owned()),
                       ..Default::default()
                   },
                   ..Default::default()
               });
}

#[test]
fn test_from_cwd_not_found() {
    let result = ConfigBuilder::from_cwd("tests/fixtures").unwrap();
    assert_eq!(result,
               ConfigBuilder {
                   root: path::Path::new("tests/fixtures").to_path_buf(),
                   ..Default::default()
               });
}

#[test]
fn test_build_default() {
    let config = ConfigBuilder::default();
    config.build().unwrap();
}
#[test]
fn test_build_dest() {
    let result = ConfigBuilder::from_file("tests/fixtures/config/.cobalt.yml").unwrap();
    let result = result.build().unwrap();
    assert_eq!(result,
               Config {
                   source: path::Path::new("tests/fixtures/config").to_path_buf(),
                   destination: path::Path::new("tests/fixtures/config/dest").to_path_buf(),
                   posts: PostBuilder {
                       slug: Some("_my_posts".to_owned()),
                       dir: "_my_posts".to_owned(),
                       drafts_dir: Some("_drafts".to_owned()),
                       default: frontmatter::FrontmatterBuilder::new()
                           .set_excerpt_separator("\n\n".to_owned())
                           .set_draft(false)
                           .set_post(true),
                       ..Default::default()
                   },
                   ignore: ["/dest".to_owned()].to_vec(),
                   ..Default::default()
               });
}

#[test]
fn test_build_abs_dest() {
    let mut result = ConfigBuilder::from_file("tests/fixtures/config/.cobalt.yml").unwrap();
    result.abs_dest = Some("hello/world".to_owned());
    let result = result.build().unwrap();
    assert_eq!(result,
               Config {
                   source: path::Path::new("tests/fixtures/config").to_path_buf(),
                   destination: path::Path::new("hello/world").to_path_buf(),
                   posts: PostBuilder {
                       slug: Some("_my_posts".to_owned()),
                       dir: "_my_posts".to_owned(),
                       drafts_dir: Some("_drafts".to_owned()),
                       default: frontmatter::FrontmatterBuilder::new()
                           .set_excerpt_separator("\n\n".to_owned())
                           .set_draft(false)
                           .set_post(true),
                       ..Default::default()
                   },
                   ignore: ["/dest".to_owned()].to_vec(),
                   ..Default::default()
               });
}

#[test]
fn test_build_posts_rel() {
    let mut config = ConfigBuilder::default();
    config.posts.dir = "rel".into();
    let config = config.build().unwrap();
    assert_eq!(config.posts.dir, "rel");
}

#[test]
fn test_build_posts_abs() {
    let mut config = ConfigBuilder::default();
    config.posts.dir = "/root".into();
    assert!(config.build().is_err());
}

#[test]
fn test_build_drafts_rel() {
    let mut config = ConfigBuilder::default();
    config.posts.drafts_dir = Some("rel".into());
    let config = config.build().unwrap();
    assert_eq!(config.posts.drafts_dir, Some("rel".into()));
}

#[test]
fn test_build_drafts_abs() {
    let mut config = ConfigBuilder::default();
    config.posts.drafts_dir = Some("/root".into());
    assert!(config.build().is_err());
}
<commit_msg>fix(config): Auto-detection of site root<commit_after>use std::default::Default;
use std::path;

use serde_yaml;

use error::*;
use files;
use frontmatter;
use legacy::wildwest;
use sass;
use site;
use slug;
use syntax_highlight::has_syntax_theme;

arg_enum! {
    #[derive(Serialize, Deserialize)]
    #[derive(Debug, PartialEq, Copy, Clone)]
    pub enum Dump {
        DocObject,
        DocTemplate,
        DocLinkObject,
        Document
    }
}

impl Dump {
    pub fn is_doc(&self) -> bool {
        true
    }
}

#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)]
#[derive(Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum SortOrder {
    Asc,
    Desc,
}

impl Default for SortOrder {
    fn default() -> SortOrder {
        SortOrder::Desc
    }
}

#[derive(Debug, PartialEq)]
#[derive(Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SyntaxHighlight {
    pub theme: String,
}

impl Default for SyntaxHighlight {
    fn default() -> SyntaxHighlight {
        SyntaxHighlight { theme: "base16-ocean.dark".to_owned() }
    }
}

#[derive(Debug, PartialEq, Default)]
#[derive(Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PageBuilder {
    pub default: frontmatter::FrontmatterBuilder,
}

#[derive(Debug, PartialEq)]
#[derive(Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PostBuilder {
    pub name: Option<String>,
    pub slug: Option<String>,
    pub description: Option<String>,
    pub dir: String,
    pub drafts_dir: Option<String>,
    pub order: SortOrder,
    pub rss: Option<String>,
    pub jsonfeed: Option<String>,
    pub default: frontmatter::FrontmatterBuilder,
}

impl Default for PostBuilder {
    fn default() -> PostBuilder {
        Self {
            name: None,
            slug: None,
            description: None,
            dir: "posts".to_owned(),
            drafts_dir: None,
            order: SortOrder::default(),
            rss: None,
            jsonfeed: None,
            default: frontmatter::FrontmatterBuilder::new().set_post(true),
        }
    }
}

#[derive(Debug, PartialEq, Default)]
#[derive(Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct AssetsBuilder {
    pub sass: sass::SassOptions,
}

const LAYOUTS_DIR: &'static str = "_layouts";

#[derive(Debug, PartialEq)]
#[derive(Serialize, Deserialize)]
#[serde(deny_unknown_fields, default)]
pub struct ConfigBuilder {
    #[serde(skip)]
    pub root: path::PathBuf,
    pub source: String,
    pub destination: String,
    #[serde(skip)]
    pub abs_dest: Option<String>,
    #[serde(skip)]
    pub include_drafts: bool,
    pub default: frontmatter::FrontmatterBuilder,
    pub pages: PageBuilder,
    pub posts: PostBuilder,
    pub site: site::SiteBuilder,
    pub template_extensions: Vec<String>,
    pub ignore: Vec<String>,
    pub syntax_highlight: SyntaxHighlight,
    pub layouts_dir: &'static str,
    pub assets: AssetsBuilder,
    // This is a debug-only field and should be transient rather than persistently set.
    #[serde(skip)]
    pub dump: Vec<Dump>,
}

impl Default for ConfigBuilder {
    fn default() -> ConfigBuilder {
        ConfigBuilder {
            root: path::PathBuf::new(),
            source: "./".to_owned(),
            destination: "./_site".to_owned(),
            abs_dest: None,
            include_drafts: false,
            default: frontmatter::FrontmatterBuilder::new()
                .set_excerpt_separator("\n\n".to_owned())
                .set_draft(false)
                .set_post(false),
            pages: PageBuilder::default(),
            posts: PostBuilder::default(),
            site: site::SiteBuilder::default(),
            template_extensions: vec!["md".to_owned(), "liquid".to_owned()],
            ignore: vec![],
            syntax_highlight: SyntaxHighlight::default(),
            layouts_dir: LAYOUTS_DIR,
            assets: AssetsBuilder::default(),
            dump: vec![],
        }
    }
}

impl ConfigBuilder {
    pub fn from_file<P: Into<path::PathBuf>>(path: P) -> Result<ConfigBuilder> {
        Self::from_file_internal(path.into())
    }

    fn from_file_internal(path: path::PathBuf) -> Result<ConfigBuilder> {
        let content = files::read_file(&path)?;

        if content.trim().is_empty() {
            return Ok(ConfigBuilder::default());
        }

        let config: wildwest::GlobalConfig = serde_yaml::from_str(&content)?;
        let mut config: ConfigBuilder = config.into();

        let mut root = path;
        root.pop(); // Remove filename
        config.root = root;

        Ok(config)
    }

    pub fn from_cwd<P: Into<path::PathBuf>>(cwd: P) -> Result<ConfigBuilder> {
        Self::from_cwd_internal(cwd.into())
    }

    fn from_cwd_internal(cwd: path::PathBuf) -> Result<ConfigBuilder> {
        let file_path = files::find_project_file(&cwd, ".cobalt.yml");
        let config = file_path
            .map(|p| {
                     info!("Using config file {:?}", &p);
                     Self::from_file(&p).chain_err(|| format!("Error reading config file {:?}", p))
                 })
            .unwrap_or_else(|| {
                warn!("No .cobalt.yml file found in current directory, using default config.");
                let config = ConfigBuilder {
                    root: cwd,
                    ..Default::default()
                };
                Ok(config)
            })?;
        Ok(config)
    }

    pub fn build(self) -> Result<Config> {
        let ConfigBuilder {
            root,
            source,
            destination,
            abs_dest,
            include_drafts,
            default,
            pages,
            posts,
            site,
            template_extensions,
            ignore,
            syntax_highlight,
            layouts_dir,
            assets,
            dump,
        } = self;

        let result: Result<()> = match has_syntax_theme(&syntax_highlight.theme) {
            Ok(true) => Ok(()),
            Ok(false) => {
                Err(format!("Syntax theme '{}' is unsupported", syntax_highlight.theme).into())
            }
            Err(err) => {
                warn!("Syntax theme named '{}' ignored. Reason: {}",
                      syntax_highlight.theme,
                      err);
                Ok(())
            }
        };
        result?;

        let mut pages = pages;
        pages.default = pages.default.merge(default.clone());

        let mut posts = posts;
        posts.default = posts.default.merge(default);
        if posts.dir.starts_with('/') {
            bail!("posts dir {} must be a relative path", posts.dir)
        }
        if let Some(ref drafts_dir) = posts.drafts_dir {
            if drafts_dir.starts_with('/') {
                bail!("posts dir {} must be a relative path", drafts_dir)
            }
        }
        if posts.slug.is_none() {
            if let Some(ref name) = posts.name {
                posts.slug = Some(slug::slugify(name));
            } else {
                posts.slug = Some(posts.dir.clone());
            }
        }

        let source = files::cleanup_path(source);
        let destination = files::cleanup_path(destination);

        let mut ignore = ignore;
        if let Ok(rel_dest) = path::Path::new(&destination).strip_prefix(&source) {
            let rel_dest = rel_dest.to_str().expect("started as a utf-8 string");
            if !rel_dest.is_empty() {
                ignore.push(format!("/{}", rel_dest.to_owned()));
            }
        }

        let source = root.join(source);
        let destination = abs_dest
            .map(|s| s.into())
            .unwrap_or_else(|| root.join(destination));

        let site = site.build(&source)?;

        let config = Config {
            source,
            destination,
            include_drafts,
            pages,
            posts,
            site,
            ignore,
            template_extensions,
            syntax_highlight,
            layouts_dir,
            assets,
            dump,
        };

        Ok(config)
    }
}

#[derive(Debug, PartialEq)]
#[derive(Serialize, Deserialize)]
#[serde(deny_unknown_fields, default)]
pub struct Config {
    pub source: path::PathBuf,
    pub destination: path::PathBuf,
    pub include_drafts: bool,
    pub pages: PageBuilder,
    pub posts: PostBuilder,
    pub site: site::Site,
    pub template_extensions: Vec<String>,
    pub ignore: Vec<String>,
    pub syntax_highlight: SyntaxHighlight,
    pub layouts_dir: &'static str,
    pub assets: AssetsBuilder,
    pub dump: Vec<Dump>,
}

impl Default for Config {
    fn default() -> Config {
        ConfigBuilder::default()
            .build()
            .expect("default config should not fail")
    }
}

#[test]
fn test_from_file_ok() {
    let result = ConfigBuilder::from_file("tests/fixtures/config/.cobalt.yml").unwrap();
    assert_eq!(result,
               ConfigBuilder {
                   root: path::Path::new("tests/fixtures/config").to_path_buf(),
                   destination: "./dest".to_owned(),
                   posts: PostBuilder {
                       dir: "_my_posts".to_owned(),
                       drafts_dir: Some("_drafts".to_owned()),
                       ..Default::default()
                   },
                   ..Default::default()
               });
}

#[test]
fn test_from_file_rss() {
    let result = ConfigBuilder::from_file("tests/fixtures/config/rss.yml").unwrap();
    assert_eq!(result,
               ConfigBuilder {
                   root: path::Path::new("tests/fixtures/config").to_path_buf(),
                   destination: "./".to_owned(),
                   posts: PostBuilder {
                       drafts_dir: Some("_drafts".to_owned()),
                       rss: Some("rss.xml".to_owned()),
                       ..Default::default()
                   },
                   site: site::SiteBuilder {
                       title: Some("My blog!".to_owned()),
                       description: Some("Blog description".to_owned()),
                       base_url: Some("http://example.com".to_owned()),
                       ..Default::default()
                   },
                   ..Default::default()
               });
}

#[test]
fn test_from_file_empty() {
    let result = ConfigBuilder::from_file("tests/fixtures/config/empty.yml").unwrap();
    assert_eq!((result), ConfigBuilder { ..Default::default() });
}

#[test]
fn test_from_file_invalid_syntax() {
    let result = ConfigBuilder::from_file("tests/fixtures/config/invalid_syntax.yml");
    assert!(result.is_err());
}

#[test]
fn test_from_file_not_found() {
    let result = ConfigBuilder::from_file("tests/fixtures/config/config_does_not_exist.yml");
    assert!(result.is_err());
}

#[test]
fn test_from_cwd_ok() {
    let result = ConfigBuilder::from_cwd("tests/fixtures/config/child").unwrap();
    assert_eq!(result,
               ConfigBuilder {
                   root: path::Path::new("tests/fixtures/config").to_path_buf(),
                   destination: "./dest".to_owned(),
                   posts: PostBuilder {
                       dir: "_my_posts".to_owned(),
                       drafts_dir: Some("_drafts".to_owned()),
                       ..Default::default()
                   },
                   ..Default::default()
               });
}

#[test]
fn test_from_cwd_not_found() {
    let result = ConfigBuilder::from_cwd("tests/fixtures").unwrap();
    assert_eq!(result,
               ConfigBuilder {
                   root: path::Path::new("tests/fixtures").to_path_buf(),
                   ..Default::default()
               });
}

#[test]
fn test_build_default() {
    let config = ConfigBuilder::default();
    config.build().unwrap();
}
#[test]
fn test_build_dest() {
    let result = ConfigBuilder::from_file("tests/fixtures/config/.cobalt.yml").unwrap();
    let result = result.build().unwrap();
    assert_eq!(result,
               Config {
                   source: path::Path::new("tests/fixtures/config").to_path_buf(),
                   destination: path::Path::new("tests/fixtures/config/dest").to_path_buf(),
                   posts: PostBuilder {
                       slug: Some("_my_posts".to_owned()),
                       dir: "_my_posts".to_owned(),
                       drafts_dir: Some("_drafts".to_owned()),
                       default: frontmatter::FrontmatterBuilder::new()
                           .set_excerpt_separator("\n\n".to_owned())
                           .set_draft(false)
                           .set_post(true),
                       ..Default::default()
                   },
                   ignore: ["/dest".to_owned()].to_vec(),
                   ..Default::default()
               });
}

#[test]
fn test_build_abs_dest() {
    let mut result = ConfigBuilder::from_file("tests/fixtures/config/.cobalt.yml").unwrap();
    result.abs_dest = Some("hello/world".to_owned());
    let result = result.build().unwrap();
    assert_eq!(result,
               Config {
                   source: path::Path::new("tests/fixtures/config").to_path_buf(),
                   destination: path::Path::new("hello/world").to_path_buf(),
                   posts: PostBuilder {
                       slug: Some("_my_posts".to_owned()),
                       dir: "_my_posts".to_owned(),
                       drafts_dir: Some("_drafts".to_owned()),
                       default: frontmatter::FrontmatterBuilder::new()
                           .set_excerpt_separator("\n\n".to_owned())
                           .set_draft(false)
                           .set_post(true),
                       ..Default::default()
                   },
                   ignore: ["/dest".to_owned()].to_vec(),
                   ..Default::default()
               });
}

#[test]
fn test_build_posts_rel() {
    let mut config = ConfigBuilder::default();
    config.posts.dir = "rel".into();
    let config = config.build().unwrap();
    assert_eq!(config.posts.dir, "rel");
}

#[test]
fn test_build_posts_abs() {
    let mut config = ConfigBuilder::default();
    config.posts.dir = "/root".into();
    assert!(config.build().is_err());
}

#[test]
fn test_build_drafts_rel() {
    let mut config = ConfigBuilder::default();
    config.posts.drafts_dir = Some("rel".into());
    let config = config.build().unwrap();
    assert_eq!(config.posts.drafts_dir, Some("rel".into()));
}

#[test]
fn test_build_drafts_abs() {
    let mut config = ConfigBuilder::default();
    config.posts.drafts_dir = Some("/root".into());
    assert!(config.build().is_err());
}
<|endoftext|>


<commit_before>// External modules
use clap::{Arg, App};
use toml;

// System modules
use std::fs::OpenOptions;
use std::io::BufReader;
use std::io::Read;
//use std::cmp;

#[derive(Clone, Debug, PartialEq)]
pub struct Configuration {
    pub input_path: String,

}

#[derive(Deserialize)]
struct TOMLConfig {
    input_path: Option<String>
}

fn default_config() -> Configuration {
    Configuration {
        input_path: "./".to_string(),
    }
}

pub fn create_config() -> Configuration {
    let version = "0.1";

    let matches = App::new("Panolution")
        .version(version).author("Willi Kappler <grandor@gmx.de>").about("Panorama photo stitcher written in Rust")
        .arg(Arg::with_name("config")
            .short("c")
            .long("config")
            .value_name("FILE")
            .help("Sets a configuration file, command line arguments can overwrite values from the config file")
            .takes_value(true))
        .arg(Arg::with_name("input")
            .help("Sets the input file or folder, default is current folder './'")
            .index(1))
        .arg(Arg::with_name("version")
            .short("v")
            .long("version")
            .help("Shows version number"))
        .after_help(
            "Examples:\n\
             # input path: current folder './'\n\
             panolution\n\n\
            "
        )
        .get_matches();

    // Default values:
    let mut result = default_config();

    if let Some(config_file) = matches.value_of("config") {
        result = load_config(config_file);
    }

    // Command line parameter can overwrite configuration file settings
    if let Some(input_path) = matches.value_of("input") {
        result.input_path = input_path.to_string();
        info!("input path: {}", input_path);
    }

    result
}

fn load_config(filename: &str) -> Configuration {
    info!("Loading configuration file: {}", filename);

    if let Ok(file) = OpenOptions::new().read(true).open(filename) {
        let mut buf_reader = BufReader::new(file);
        let mut content = String::new();

        if let Ok(_) = buf_reader.read_to_string(&mut content) {
            return parse_config_file(&content);
        } else {
            // Content of configuration file could not be read, use default settings instead.
            warn!("Could not read configuration file '{}', using default settings", filename);
        }
    } else {
        // Configuration file could not be opened, use default settings instead.
        warn!("Could not open configuration file '{}', using default settings", filename);
    }
    default_config()
}

fn parse_config_file(content: &str) -> Configuration {
    let mut result = default_config();

    match toml::from_str::<TOMLConfig>(content) {
        Ok(config) => {
            if let Some(input_path) = config.input_path {
                result.input_path = input_path;
            }
        },
        Err(e) => {
            warn!("TOML parse error: {}", e);
        }
    }

    result
}

#[cfg(test)]
mod test {
    use super::{parse_config_file, default_config, Configuration};

    use logger::create_logger;

    #[test]
    fn parse_config_file1() {
        create_logger();

        let input = "";

        let expected_output = default_config();

        assert_eq!(parse_config_file(input), expected_output);
    }

    #[test]
    fn parse_config_file2() {
        create_logger();

        let input =
r#"
input_path = "scans"
"#;

        let expected_output = Configuration{
            input_path: "scans".to_string(),
        };

        assert_eq!(parse_config_file(input), expected_output);
    }
}
<commit_msg>add more configuration options<commit_after>// External modules
use clap::{Arg, App};
use toml;

// System modules
use std::fs::OpenOptions;
use std::io::BufReader;
use std::io::Read;
//use std::cmp;

#[derive(Clone, Debug, PartialEq)]
pub struct Configuration {
    pub input_path: String,
    pub max_iteration: u64,
    pub work_sizes: Vec<u64>,
}

#[derive(Deserialize)]
struct TOMLConfig {
    input_path: Option<String>
}

fn default_config() -> Configuration {
    Configuration {
        input_path: "./".to_string(),
        max_iteration: 10000,
        work_sizes: vec![64, 256],
    }
}

pub fn create_config() -> Configuration {
    let version = "0.1";

    let matches = App::new("Panolution")
        .version(version).author("Willi Kappler <grandor@gmx.de>").about("Panorama photo stitcher written in Rust")
        .arg(Arg::with_name("config")
            .short("c")
            .long("config")
            .value_name("FILE")
            .help("Sets a configuration file, command line arguments can overwrite values from the config file")
            .takes_value(true))
        .arg(Arg::with_name("input")
            .help("Sets the input file or folder, default is current folder './'")
            .index(1))
        .arg(Arg::with_name("version")
            .short("v")
            .long("version")
            .help("Shows version number"))
        .after_help(
            "Examples:\n\
             # input path: current folder './'\n\
             panolution\n\n\
            "
        )
        .get_matches();

    // Default values:
    let mut result = default_config();

    if let Some(config_file) = matches.value_of("config") {
        result = load_config(config_file);
    }

    // Command line parameter can overwrite configuration file settings
    if let Some(input_path) = matches.value_of("input") {
        result.input_path = input_path.to_string();
        info!("input path: {}", input_path);
    }

    result
}

fn load_config(filename: &str) -> Configuration {
    info!("Loading configuration file: {}", filename);

    if let Ok(file) = OpenOptions::new().read(true).open(filename) {
        let mut buf_reader = BufReader::new(file);
        let mut content = String::new();

        if let Ok(_) = buf_reader.read_to_string(&mut content) {
            return parse_config_file(&content);
        } else {
            // Content of configuration file could not be read, use default settings instead.
            warn!("Could not read configuration file '{}', using default settings", filename);
        }
    } else {
        // Configuration file could not be opened, use default settings instead.
        warn!("Could not open configuration file '{}', using default settings", filename);
    }
    default_config()
}

fn parse_config_file(content: &str) -> Configuration {
    let mut result = default_config();

    match toml::from_str::<TOMLConfig>(content) {
        Ok(config) => {
            if let Some(input_path) = config.input_path {
                result.input_path = input_path;
            }
        },
        Err(e) => {
            warn!("TOML parse error: {}", e);
        }
    }

    result
}

#[cfg(test)]
mod test {
    use super::{parse_config_file, default_config, Configuration};

    use logger::create_logger;

    #[test]
    fn parse_config_file1() {
        create_logger();

        let input = "";

        let expected_output = default_config();

        assert_eq!(parse_config_file(input), expected_output);
    }

    #[test]
    fn parse_config_file2() {
        create_logger();

        let input =
r#"
input_path = "scans"
"#;

        let expected_output = Configuration{
            input_path: "scans".to_string(),
        };

        assert_eq!(parse_config_file(input), expected_output);
    }
}
<|endoftext|>


<commit_before>#[cfg(any(target_os = "linux", target_os = "android"))]
use std::os::unix::io::AsRawFd;
use std::{
    io::{self, Error, ErrorKind},
    mem,
    net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},
};
#[cfg(any(target_os = "android"))]
use std::{os::unix::io::RawFd, path::Path};

use cfg_if::cfg_if;
use tokio::net::{TcpSocket, TcpStream, UdpSocket};

use crate::net::{AddrFamily, ConnectOpts};

/// Convert `sockaddr_storage` to `SocketAddr`
#[allow(dead_code)]
pub fn sockaddr_to_std(saddr: &libc::sockaddr_storage) -> io::Result<SocketAddr> {
    match saddr.ss_family as libc::c_int {
        libc::AF_INET => unsafe {
            let addr: SocketAddrV4 = mem::transmute_copy(saddr);
            Ok(SocketAddr::V4(addr))
        },
        libc::AF_INET6 => unsafe {
            let addr: SocketAddrV6 = mem::transmute_copy(saddr);
            Ok(SocketAddr::V6(addr))
        },
        _ => {
            let err = Error::new(ErrorKind::InvalidData, "family must be either AF_INET or AF_INET6");
            Err(err)
        }
    }
}

cfg_if! {
    if #[cfg(target_os = "android")] {
        mod uds;

        /// This is a RPC for Android to `protect()` socket for connecting to remote servers
        ///
        /// https://developer.android.com/reference/android/net/VpnService#protect(java.net.Socket)
        ///
        /// More detail could be found in [shadowsocks-android](https://github.com/shadowsocks/shadowsocks-android) project.
        async fn protect<P: AsRef<Path>>(protect_path: P, fd: RawFd) -> io::Result<()> {
            use tokio::io::AsyncReadExt;

            let mut stream = self::uds::UnixStream::connect(protect_path).await?;

            // send fds
            let dummy: [u8; 1] = [1];
            let fds: [RawFd; 1] = [fd];
            stream.send_with_fd(&dummy, &fds).await?;

            // receive the return value
            let mut response = [0; 1];
            stream.read_exact(&mut response).await?;

            if response[0] == 0xFF {
                return Err(Error::new(ErrorKind::Other, "protect() failed"));
            }

            Ok(())
        }
    }
}

/// create a new TCP stream
#[inline(always)]
#[allow(unused_variables)]
pub async fn tcp_stream_connect(saddr: &SocketAddr, config: &ConnectOpts) -> io::Result<TcpStream> {
    let socket = match *saddr {
        SocketAddr::V4(..) => TcpSocket::new_v4()?,
        SocketAddr::V6(..) => TcpSocket::new_v6()?,
    };

    // Any traffic to localhost should not be protected
    // This is a workaround for VPNService
    #[cfg(target_os = "android")]
    if !saddr.ip().is_loopback() {
        if let Some(ref path) = config.vpn_protect_path {
            protect(path, socket.as_raw_fd()).await?;
        }
    }

    // Set SO_MARK for mark-based routing on Linux (since 2.6.25)
    // NOTE: This will require CAP_NET_ADMIN capability (root in most cases)
    #[cfg(any(target_os = "linux", target_os = "android"))]
    if let Some(mark) = config.fwmark {
        let ret = unsafe {
            libc::setsockopt(
                socket.as_raw_fd(),
                libc::SOL_SOCKET,
                libc::SO_MARK,
                &mark as *const _ as *const _,
                mem::size_of_val(&mark) as libc::socklen_t,
            )
        };
        if ret != 0 {
            return Err(Error::last_os_error());
        }
    }

    // Set SO_BINDTODEVICE for binding to a specific interface
    #[cfg(any(target_os = "linux", target_os = "android"))]
    if let Some(ref iface) = config.bind_interface {
        use nix::sys::socket::{setsockopt, sockopt::BindToDevice};
        if let Err(err) = setsockopt::<BindToDevice>(socket.as_raw_fd(), BindToDevice, iface) {
            return match err.as_errno() {
                Some(errno) => Err(errno.into()),
                None => Err(Error::new(ErrorKind::Other, err)),
            };
        }
    }

    // Binds to IP address
    if let Some(ip) = config.bind_local_addr {
        match (ip, saddr.ip()) {
            (IpAddr::V4(..), IpAddr::V4(..)) => {
                socket.bind(SocketAddr::new(ip, 0))?;
            }
            (IpAddr::V6(..), IpAddr::V6(..)) => {
                socket.bind(SocketAddr::new(ip, 0))?;
            }
            _ => {}
        }
    }

    // Set `SO_SNDBUF`
    if let Some(buf_size) = config.tcp.send_buffer_size {
        socket.set_send_buffer_size(buf_size)?;
    }

    // Set `SO_RCVBUF`
    if let Some(buf_size) = config.tcp.recv_buffer_size {
        socket.set_recv_buffer_size(buf_size)?;
    }

    // it's important that the socket is protected before connecting
    let stream = socket.connect(*saddr).await?;

    if config.tcp.nodelay {
        stream.set_nodelay(true)?;
    }

    Ok(stream)
}

/// Create a `UdpSocket` for connecting to `addr`
#[inline(always)]
#[allow(unused_variables)]
pub async fn create_outbound_udp_socket(af: AddrFamily, config: &ConnectOpts) -> io::Result<UdpSocket> {
    let bind_addr = match (af, config.bind_local_addr) {
        (AddrFamily::Ipv4, Some(IpAddr::V4(ip))) => SocketAddr::new(ip.into(), 0),
        (AddrFamily::Ipv6, Some(IpAddr::V6(ip))) => SocketAddr::new(ip.into(), 0),
        (AddrFamily::Ipv4, ..) => SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 0),
        (AddrFamily::Ipv6, ..) => SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), 0),
    };

    let socket = UdpSocket::bind(bind_addr).await?;

    // Any traffic except localhost should be protected
    // This is a workaround for VPNService
    #[cfg(target_os = "android")]
    {
        if let Some(ref path) = config.vpn_protect_path {
            protect(path, socket.as_raw_fd()).await?;
        }
    }

    // Set SO_MARK for mark-based routing on Linux (since 2.6.25)
    // NOTE: This will require CAP_NET_ADMIN capability (root in most cases)
    #[cfg(any(target_os = "linux", target_os = "android"))]
    if let Some(mark) = config.fwmark {
        let ret = unsafe {
            libc::setsockopt(
                socket.as_raw_fd(),
                libc::SOL_SOCKET,
                libc::SO_MARK,
                &mark as *const _ as *const _,
                mem::size_of_val(&mark) as libc::socklen_t,
            )
        };
        if ret != 0 {
            return Err(Error::last_os_error());
        }
    }

    // Set SO_BINDTODEVICE for binding to a specific interface
    #[cfg(any(target_os = "linux", target_os = "android"))]
    if let Some(ref iface) = config.bind_interface {
        use nix::sys::socket::{setsockopt, sockopt::BindToDevice};
        if let Err(err) = setsockopt::<BindToDevice>(socket.as_raw_fd(), BindToDevice, iface) {
            return match err.as_errno() {
                Some(errno) => Err(errno.into()),
                None => Err(Error::new(ErrorKind::Other, err)),
            };
        }
    }

    Ok(socket)
}

/// Create a `UdpSocket` binded to `addr`
#[inline(always)]
pub async fn create_udp_socket(addr: &SocketAddr) -> io::Result<UdpSocket> {
    UdpSocket::bind(addr).await
}
<commit_msg>set timeout for protect() of Android's VpnService<commit_after>#[cfg(any(target_os = "linux", target_os = "android"))]
use std::os::unix::io::AsRawFd;
use std::{
    io::{self, Error, ErrorKind},
    mem,
    net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},
};
#[cfg(any(target_os = "android"))]
use std::{os::unix::io::RawFd, path::Path};

use cfg_if::cfg_if;
use tokio::net::{TcpSocket, TcpStream, UdpSocket};

use crate::net::{AddrFamily, ConnectOpts};

/// Convert `sockaddr_storage` to `SocketAddr`
#[allow(dead_code)]
pub fn sockaddr_to_std(saddr: &libc::sockaddr_storage) -> io::Result<SocketAddr> {
    match saddr.ss_family as libc::c_int {
        libc::AF_INET => unsafe {
            let addr: SocketAddrV4 = mem::transmute_copy(saddr);
            Ok(SocketAddr::V4(addr))
        },
        libc::AF_INET6 => unsafe {
            let addr: SocketAddrV6 = mem::transmute_copy(saddr);
            Ok(SocketAddr::V6(addr))
        },
        _ => {
            let err = Error::new(ErrorKind::InvalidData, "family must be either AF_INET or AF_INET6");
            Err(err)
        }
    }
}

cfg_if! {
    if #[cfg(target_os = "android")] {
        mod uds;

        /// This is a RPC for Android to `protect()` socket for connecting to remote servers
        ///
        /// https://developer.android.com/reference/android/net/VpnService#protect(java.net.Socket)
        ///
        /// More detail could be found in [shadowsocks-android](https://github.com/shadowsocks/shadowsocks-android) project.
        async fn protect<P: AsRef<Path>>(protect_path: P, fd: RawFd) -> io::Result<()> {
            use tokio::io::AsyncReadExt;

            let mut stream = self::uds::UnixStream::connect(protect_path).await?;

            // send fds
            let dummy: [u8; 1] = [1];
            let fds: [RawFd; 1] = [fd];
            stream.send_with_fd(&dummy, &fds).await?;

            // receive the return value
            let mut response = [0; 1];
            stream.read_exact(&mut response).await?;

            if response[0] == 0xFF {
                return Err(Error::new(ErrorKind::Other, "protect() failed"));
            }

            Ok(())
        }
    }
}

/// create a new TCP stream
#[inline(always)]
#[allow(unused_variables)]
pub async fn tcp_stream_connect(saddr: &SocketAddr, config: &ConnectOpts) -> io::Result<TcpStream> {
    let socket = match *saddr {
        SocketAddr::V4(..) => TcpSocket::new_v4()?,
        SocketAddr::V6(..) => TcpSocket::new_v6()?,
    };

    // Any traffic to localhost should not be protected
    // This is a workaround for VPNService
    #[cfg(target_os = "android")]
    if !saddr.ip().is_loopback() {
        use std::time::Duration;
        use tokio::time;

        if let Some(ref path) = config.vpn_protect_path {
            // RPC calls to `VpnService.protect()`
            // Timeout in 3 seconds like shadowsocks-libev
            match time::timeout(Duration::from_secs(3), protect(path, socket.as_raw_fd())).await {
                Ok(Ok(..)) => {}
                Ok(Err(err)) => return Err(err),
                Err(..) => return Err(ErrorKind::TimedOut.into()),
            }
        }
    }

    // Set SO_MARK for mark-based routing on Linux (since 2.6.25)
    // NOTE: This will require CAP_NET_ADMIN capability (root in most cases)
    #[cfg(any(target_os = "linux", target_os = "android"))]
    if let Some(mark) = config.fwmark {
        let ret = unsafe {
            libc::setsockopt(
                socket.as_raw_fd(),
                libc::SOL_SOCKET,
                libc::SO_MARK,
                &mark as *const _ as *const _,
                mem::size_of_val(&mark) as libc::socklen_t,
            )
        };
        if ret != 0 {
            return Err(Error::last_os_error());
        }
    }

    // Set SO_BINDTODEVICE for binding to a specific interface
    #[cfg(any(target_os = "linux", target_os = "android"))]
    if let Some(ref iface) = config.bind_interface {
        use nix::sys::socket::{setsockopt, sockopt::BindToDevice};
        if let Err(err) = setsockopt::<BindToDevice>(socket.as_raw_fd(), BindToDevice, iface) {
            return match err.as_errno() {
                Some(errno) => Err(errno.into()),
                None => Err(Error::new(ErrorKind::Other, err)),
            };
        }
    }

    // Binds to IP address
    if let Some(ip) = config.bind_local_addr {
        match (ip, saddr.ip()) {
            (IpAddr::V4(..), IpAddr::V4(..)) => {
                socket.bind(SocketAddr::new(ip, 0))?;
            }
            (IpAddr::V6(..), IpAddr::V6(..)) => {
                socket.bind(SocketAddr::new(ip, 0))?;
            }
            _ => {}
        }
    }

    // Set `SO_SNDBUF`
    if let Some(buf_size) = config.tcp.send_buffer_size {
        socket.set_send_buffer_size(buf_size)?;
    }

    // Set `SO_RCVBUF`
    if let Some(buf_size) = config.tcp.recv_buffer_size {
        socket.set_recv_buffer_size(buf_size)?;
    }

    // it's important that the socket is protected before connecting
    let stream = socket.connect(*saddr).await?;

    if config.tcp.nodelay {
        stream.set_nodelay(true)?;
    }

    Ok(stream)
}

/// Create a `UdpSocket` for connecting to `addr`
#[inline(always)]
#[allow(unused_variables)]
pub async fn create_outbound_udp_socket(af: AddrFamily, config: &ConnectOpts) -> io::Result<UdpSocket> {
    let bind_addr = match (af, config.bind_local_addr) {
        (AddrFamily::Ipv4, Some(IpAddr::V4(ip))) => SocketAddr::new(ip.into(), 0),
        (AddrFamily::Ipv6, Some(IpAddr::V6(ip))) => SocketAddr::new(ip.into(), 0),
        (AddrFamily::Ipv4, ..) => SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 0),
        (AddrFamily::Ipv6, ..) => SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), 0),
    };

    let socket = UdpSocket::bind(bind_addr).await?;

    // Any traffic except localhost should be protected
    // This is a workaround for VPNService
    #[cfg(target_os = "android")]
    {
        if let Some(ref path) = config.vpn_protect_path {
            protect(path, socket.as_raw_fd()).await?;
        }
    }

    // Set SO_MARK for mark-based routing on Linux (since 2.6.25)
    // NOTE: This will require CAP_NET_ADMIN capability (root in most cases)
    #[cfg(any(target_os = "linux", target_os = "android"))]
    if let Some(mark) = config.fwmark {
        let ret = unsafe {
            libc::setsockopt(
                socket.as_raw_fd(),
                libc::SOL_SOCKET,
                libc::SO_MARK,
                &mark as *const _ as *const _,
                mem::size_of_val(&mark) as libc::socklen_t,
            )
        };
        if ret != 0 {
            return Err(Error::last_os_error());
        }
    }

    // Set SO_BINDTODEVICE for binding to a specific interface
    #[cfg(any(target_os = "linux", target_os = "android"))]
    if let Some(ref iface) = config.bind_interface {
        use nix::sys::socket::{setsockopt, sockopt::BindToDevice};
        if let Err(err) = setsockopt::<BindToDevice>(socket.as_raw_fd(), BindToDevice, iface) {
            return match err.as_errno() {
                Some(errno) => Err(errno.into()),
                None => Err(Error::new(ErrorKind::Other, err)),
            };
        }
    }

    Ok(socket)
}

/// Create a `UdpSocket` binded to `addr`
#[inline(always)]
pub async fn create_udp_socket(addr: &SocketAddr) -> io::Result<UdpSocket> {
    UdpSocket::bind(addr).await
}
<|endoftext|>


<commit_before>//! Translation of the following Go program into Rust.
//! Source: https://www.nada.kth.se/~snilsson/concurrency/#Match
//!
//! ```go
//! func main() {
//!     people := []string{"Anna", "Bob", "Cody", "Dave", "Eva"}
//!     match := make(chan string, 1) // Make room for one unmatched send.
//!     wg := new(sync.WaitGroup)
//!     for _, name := range people {
//!         wg.Add(1)
//!         go Seek(name, match, wg)
//!     }
//!     wg.Wait()
//!     select {
//!     case name := <-match:
//!         fmt.Printf("No one received %s’s message.\n", name)
//!     default:
//!         // There was no pending send operation.
//!     }
//! }
//!
//! // Seek either sends or receives, whichever possible, a name on the match
//! // channel and notifies the wait group when done.
//! func Seek(name string, match chan string, wg *sync.WaitGroup) {
//!     select {
//!     case peer := <-match:
//!         fmt.Printf("%s received a message from %s.\n", name, peer)
//!     case match <- name:
//!         // Wait for someone to receive my message.
//!     }
//!     wg.Done()
//! }
//! ```

extern crate crossbeam;
#[macro_use]
extern crate crossbeam_channel as channel;

fn main() {
    let people = vec!["Anna", "Bob", "Cody", "Dave", "Eva"];
    let (s, r) = &channel::bounded(1); // Make room for one unmatched send.

    // Either send my name into the channel or receive someone else's, whatever happens first.
    let seek = |name, s: &channel::Sender<&str>, r: &channel::Receiver<&str>| {
        select! {
            recv(r, peer) => println!("{} received a message from {}.", name, peer.unwrap()),
            send(s, name) => {}, // Wait for someone to receive my message.
        }
    };

    crossbeam::scope(|scope| {
        for name in people {
            scope.spawn(move || seek(name, s, r));
        }
    });

    if let Some(name) = r.try_recv() {
        println!("No one received {}’s message.", name);
    }
}
<commit_msg>Simplify<commit_after>//! Translation of the following Go program into Rust.
//! Source: https://www.nada.kth.se/~snilsson/concurrency/#Match
//!
//! ```go
//! func main() {
//!     people := []string{"Anna", "Bob", "Cody", "Dave", "Eva"}
//!     match := make(chan string, 1) // Make room for one unmatched send.
//!     wg := new(sync.WaitGroup)
//!     for _, name := range people {
//!         wg.Add(1)
//!         go Seek(name, match, wg)
//!     }
//!     wg.Wait()
//!     select {
//!     case name := <-match:
//!         fmt.Printf("No one received %s’s message.\n", name)
//!     default:
//!         // There was no pending send operation.
//!     }
//! }
//!
//! // Seek either sends or receives, whichever possible, a name on the match
//! // channel and notifies the wait group when done.
//! func Seek(name string, match chan string, wg *sync.WaitGroup) {
//!     select {
//!     case peer := <-match:
//!         fmt.Printf("%s received a message from %s.\n", name, peer)
//!     case match <- name:
//!         // Wait for someone to receive my message.
//!     }
//!     wg.Done()
//! }
//! ```

extern crate crossbeam;
#[macro_use]
extern crate crossbeam_channel as channel;

fn main() {
    let people = vec!["Anna", "Bob", "Cody", "Dave", "Eva"];
    let (s, r) = &channel::bounded(1); // Make room for one unmatched send.

    // Either send my name into the channel or receive someone else's, whatever happens first.
    let seek = |name, s: &channel::Sender<_>, r: &channel::Receiver<_>| {
        select! {
            recv(r, peer) => println!("{} received a message from {}.", name, peer.unwrap()),
            send(s, name) => {}, // Wait for someone to receive my message.
        }
    };

    crossbeam::scope(|scope| {
        for name in people {
            scope.spawn(move || seek(name, s, r));
        }
    });

    if let Some(name) = r.try_recv() {
        println!("No one received {}’s message.", name);
    }
}
<|endoftext|>


<commit_before>use super::{ReadAt, Size, WriteAt};

use std::io;
use std::io::{Read, Seek, SeekFrom, Write};

/// Adapts a `ReadAt` or `WriteAt` into a `Read` or `Write`.
///
/// This wraps anything that read and write at offsets, turning into an object
/// that can read or write at a file position. This allows you to use those
/// types with all the many functions that expect a `Read` or `Write`.
///
/// Note that seeking on `Cursor` has limited functionality. We don't know how
/// many bytes are available, so we can't use `SeekFrom::End`.
/// See [`SizeCursor`][SizeCursor] for another option.
///
/// [SizeCursor]: struct.SizeCursor.html
///
/// # Examples
///
/// ```no_run
/// # use std::io::{self, Result, Read};
/// # use std::fs::File;
/// use positioned_io::{ReadAt, Cursor};
///
/// struct NetworkStorage {
///     // A remote disk that supports random access.
/// }
/// # impl NetworkStorage {
/// #   fn new(i: i32) -> Self { NetworkStorage { } }
/// # }
///
/// impl ReadAt for NetworkStorage {
///     // ...
/// #   fn read_at(&self, pos: u64, buf: &mut [u8]) -> Result<usize> {
/// #       Ok(0)
/// #   }
/// }
///
/// # const SOME_LOCATION: i32 = 1;
/// # fn foo() -> Result<()> {
/// // Adapt our storage into a Read.
/// let storage = NetworkStorage::new(SOME_LOCATION);
/// let curs = Cursor::new_pos(storage, 1 << 30);
///
/// // Copy a segment to a file.
/// let mut input = curs.take(1 << 20);
/// let mut output = File::create("segment.out")?;
/// io::copy(&mut input, &mut output)?;
/// # Ok(())
/// # }
/// ```
#[derive(Debug, Clone)]
pub struct Cursor<I> {
    io: I,
    pos: u64,
}

impl<I> Cursor<I> {
    /// Create a new `Cursor` which starts reading at a specified offset.
    ///
    /// Pass in a `ReadAt` or `WriteAt` as `io`.
    pub fn new_pos(io: I, pos: u64) -> Self {
        Cursor { io, pos }
    }

    /// Create a new Cursor which starts reading at offset zero.
    ///
    /// Pass in a `ReadAt` or `WriteAt` as `io`.
    pub fn new(io: I) -> Self {
        Self::new_pos(io, 0)
    }

    /// Consume `self` and yield the inner `ReadAt` or `WriteAt`.
    pub fn into_inner(self) -> I {
        self.io
    }

    /// Borrow the inner `ReadAt` or `WriteAt`.
    pub fn get_ref(&self) -> &I {
        &self.io
    }

    /// Borrow the inner `ReadAt` or `WriteAt` mutably.
    pub fn get_mut(&mut self) -> &mut I {
        &mut self.io
    }

    /// Get the current read/write position.
    pub fn position(&self) -> u64 {
        self.pos
    }

    /// Set the current read/write position.
    pub fn set_position(&mut self, pos: u64) {
        self.pos = pos;
    }
}

impl<I> Seek for Cursor<I> {
    fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
        match pos {
            SeekFrom::Start(p) => self.pos = p,
            SeekFrom::Current(p) => {
                let pos = self.pos as i64 + p;
                if pos < 0 {
                    return Err(io::Error::new(io::ErrorKind::InvalidInput, "seek to a negative position"));
                }
                self.pos = pos as u64;
            }
            SeekFrom::End(_) => {
                return Err(io::Error::new(io::ErrorKind::InvalidInput, "seek from unknown end"))
            }
        }
        Ok(self.pos)
    }
}

impl<I: ReadAt> Read for Cursor<I> {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        let bytes = self.get_ref().read_at(self.pos, buf)?;
        self.pos += bytes as u64;
        Ok(bytes)
    }
}

impl<I: WriteAt> Write for Cursor<I> {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        let pos = self.pos;
        let bytes = self.get_mut().write_at(pos, buf)?;
        self.pos += bytes as u64;
        Ok(bytes)
    }

    fn flush(&mut self) -> io::Result<()> {
        WriteAt::flush(self.get_mut())
    }
}

/// Adapts a `ReadAt` or `WriteAt` into a `Read` or `Write`, with better
/// seeking.
///
/// This is just like [`Cursor`][Cursor], except that it requires an object
/// that implements [`Size`][Size], and that it can seek from the end of the
/// I/O object.
///
/// Eventually it will be legal to specialize `Cursor` for types that implement
/// `Size`, see [RFC 1210][RFC].
///
/// [Cursor]: struct.Cursor.html
/// [Size]: trait.Size.html
/// [RFC]: https://github.com/rust-lang/rfcs/pull/1210
#[derive(Debug, Clone)]
pub struct SizeCursor<I: Size> {
    cursor: Cursor<I>,
}

impl<I: Size> SizeCursor<I> {
    /// Create a new `SizeCursor` which starts reading at a specified offset.
    ///
    /// Pass in a `ReadAt` or `WriteAt` as `io`.
    pub fn new_pos(io: I, pos: u64) -> Self {
        SizeCursor {
            cursor: Cursor::new_pos(io, pos)
        }
    }
    /// Create a new `SizeCursor` which starts reading at offset zero.
    ///
    /// Pass in a `ReadAt` or `WriteAt` as `io`.
    pub fn new(io: I) -> Self {
        SizeCursor {
            cursor: Cursor::new(io)
        }
    }

    pub fn as_cursor(&self) -> &Cursor<I> {
        &self.cursor
    }

    pub fn as_cursor_mut(&mut self) -> &mut Cursor<I> {
        &mut self.cursor
    }

    pub fn into_cursor(self) -> Cursor<I> {
        self.cursor
    }

    pub fn into_inner(self) -> I {
        self.cursor.io
    }

    pub fn get_ref(&self) -> &I {
        &self.cursor.io
    }

    pub fn get_mut(&mut self) -> &mut I {
        &mut self.cursor.io
    }

    pub fn position(&self) -> u64 {
        self.cursor.position()
    }

    pub fn set_position(&mut self, pos: u64) {
        self.cursor.set_position(pos)
    }
}

impl<I: Size + ReadAt> Read for SizeCursor<I> {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        self.cursor.read(buf)
    }
}

impl<I: Size + WriteAt> Write for SizeCursor<I> {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        self.cursor.write(buf)
    }

    fn flush(&mut self) -> io::Result<()> {
        self.cursor.flush()
    }
}

// We know how to seek from the end for SizeCursor.
impl<I: Size> Seek for SizeCursor<I> {
    fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
        let pos = match pos {
            SeekFrom::Start(p) => p as i64,
            SeekFrom::Current(p) => self.cursor.pos as i64 + p,
            SeekFrom::End(p) => {
                match self.get_ref().size() {
                    Err(e) => return Err(e),
                    Ok(None) => {
                        return Err(io::Error::new(io::ErrorKind::InvalidData, "seek from unknown end"))
                    }
                    Ok(Some(s)) => s as i64 + p,
                }
            }
        };
        self.cursor.pos = pos as u64;
        Ok(self.cursor.pos)
    }
}
<commit_msg>get_ref -> as_inner<commit_after>use super::{ReadAt, Size, WriteAt};

use std::io;
use std::io::{Read, Seek, SeekFrom, Write};

/// Adapts a `ReadAt` or `WriteAt` into a `Read` or `Write`.
///
/// This wraps anything that read and write at offsets, turning into an object
/// that can read or write at a file position. This allows you to use those
/// types with all the many functions that expect a `Read` or `Write`.
///
/// Note that seeking on `Cursor` has limited functionality. We don't know how
/// many bytes are available, so we can't use `SeekFrom::End`.
/// See [`SizeCursor`][SizeCursor] for another option.
///
/// [SizeCursor]: struct.SizeCursor.html
///
/// # Examples
///
/// ```no_run
/// # use std::io::{self, Result, Read};
/// # use std::fs::File;
/// use positioned_io::{ReadAt, Cursor};
///
/// struct NetworkStorage {
///     // A remote disk that supports random access.
/// }
/// # impl NetworkStorage {
/// #   fn new(i: i32) -> Self { NetworkStorage { } }
/// # }
///
/// impl ReadAt for NetworkStorage {
///     // ...
/// #   fn read_at(&self, pos: u64, buf: &mut [u8]) -> Result<usize> {
/// #       Ok(0)
/// #   }
/// }
///
/// # const SOME_LOCATION: i32 = 1;
/// # fn foo() -> Result<()> {
/// // Adapt our storage into a Read.
/// let storage = NetworkStorage::new(SOME_LOCATION);
/// let curs = Cursor::new_pos(storage, 1 << 30);
///
/// // Copy a segment to a file.
/// let mut input = curs.take(1 << 20);
/// let mut output = File::create("segment.out")?;
/// io::copy(&mut input, &mut output)?;
/// # Ok(())
/// # }
/// ```
#[derive(Debug, Clone)]
pub struct Cursor<I> {
    io: I,
    pos: u64,
}

impl<I> Cursor<I> {
    /// Create a new `Cursor` which starts reading at a specified offset.
    ///
    /// Pass in a `ReadAt` or `WriteAt` as `io`.
    pub fn new_pos(io: I, pos: u64) -> Self {
        Cursor { io, pos }
    }

    /// Create a new Cursor which starts reading at offset zero.
    ///
    /// Pass in a `ReadAt` or `WriteAt` as `io`.
    pub fn new(io: I) -> Self {
        Self::new_pos(io, 0)
    }

    /// Consume `self` and yield the inner `ReadAt` or `WriteAt`.
    pub fn into_inner(self) -> I {
        self.io
    }

    /// Borrow the inner `ReadAt` or `WriteAt`.
    pub fn as_inner(&self) -> &I {
        &self.io
    }

    /// Borrow the inner `ReadAt` or `WriteAt` mutably.
    pub fn as_inner_mut(&mut self) -> &mut I {
        &mut self.io
    }

    /// Get the current read/write position.
    pub fn position(&self) -> u64 {
        self.pos
    }

    /// Set the current read/write position.
    pub fn set_position(&mut self, pos: u64) {
        self.pos = pos;
    }
}

impl<I> Seek for Cursor<I> {
    fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
        match pos {
            SeekFrom::Start(p) => self.pos = p,
            SeekFrom::Current(p) => {
                let pos = self.pos as i64 + p;
                if pos < 0 {
                    return Err(io::Error::new(io::ErrorKind::InvalidInput, "seek to a negative position"));
                }
                self.pos = pos as u64;
            }
            SeekFrom::End(_) => {
                return Err(io::Error::new(io::ErrorKind::InvalidInput, "seek from unknown end"))
            }
        }
        Ok(self.pos)
    }
}

impl<I: ReadAt> Read for Cursor<I> {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        let bytes = self.as_inner().read_at(self.pos, buf)?;
        self.pos += bytes as u64;
        Ok(bytes)
    }
}

impl<I: WriteAt> Write for Cursor<I> {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        let pos = self.pos;
        let bytes = self.as_inner_mut().write_at(pos, buf)?;
        self.pos += bytes as u64;
        Ok(bytes)
    }

    fn flush(&mut self) -> io::Result<()> {
        WriteAt::flush(self.as_inner_mut())
    }
}

/// Adapts a `ReadAt` or `WriteAt` into a `Read` or `Write`, with better
/// seeking.
///
/// This is just like [`Cursor`][Cursor], except that it requires an object
/// that implements [`Size`][Size], and that it can seek from the end of the
/// I/O object.
///
/// Eventually it will be legal to specialize `Cursor` for types that implement
/// `Size`, see [RFC 1210][RFC].
///
/// [Cursor]: struct.Cursor.html
/// [Size]: trait.Size.html
/// [RFC]: https://github.com/rust-lang/rfcs/pull/1210
#[derive(Debug, Clone)]
pub struct SizeCursor<I: Size> {
    cursor: Cursor<I>,
}

impl<I: Size> SizeCursor<I> {
    /// Create a new `SizeCursor` which starts reading at a specified offset.
    ///
    /// Pass in a `ReadAt` or `WriteAt` as `io`.
    pub fn new_pos(io: I, pos: u64) -> Self {
        SizeCursor {
            cursor: Cursor::new_pos(io, pos)
        }
    }
    /// Create a new `SizeCursor` which starts reading at offset zero.
    ///
    /// Pass in a `ReadAt` or `WriteAt` as `io`.
    pub fn new(io: I) -> Self {
        SizeCursor {
            cursor: Cursor::new(io)
        }
    }

    pub fn as_cursor(&self) -> &Cursor<I> {
        &self.cursor
    }

    pub fn as_cursor_mut(&mut self) -> &mut Cursor<I> {
        &mut self.cursor
    }

    pub fn into_cursor(self) -> Cursor<I> {
        self.cursor
    }

    pub fn into_inner(self) -> I {
        self.cursor.io
    }

    pub fn as_inner(&self) -> &I {
        &self.cursor.io
    }

    pub fn as_inner_mut(&mut self) -> &mut I {
        &mut self.cursor.io
    }

    pub fn position(&self) -> u64 {
        self.cursor.position()
    }

    pub fn set_position(&mut self, pos: u64) {
        self.cursor.set_position(pos)
    }
}

impl<I: Size + ReadAt> Read for SizeCursor<I> {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        self.cursor.read(buf)
    }
}

impl<I: Size + WriteAt> Write for SizeCursor<I> {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        self.cursor.write(buf)
    }

    fn flush(&mut self) -> io::Result<()> {
        self.cursor.flush()
    }
}

// We know how to seek from the end for SizeCursor.
impl<I: Size> Seek for SizeCursor<I> {
    fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
        let pos = match pos {
            SeekFrom::Start(p) => p as i64,
            SeekFrom::Current(p) => self.cursor.pos as i64 + p,
            SeekFrom::End(p) => {
                match self.as_inner().size() {
                    Err(e) => return Err(e),
                    Ok(None) => {
                        return Err(io::Error::new(io::ErrorKind::InvalidData, "seek from unknown end"))
                    }
                    Ok(Some(s)) => s as i64 + p,
                }
            }
        };
        self.cursor.pos = pos as u64;
        Ok(self.cursor.pos)
    }
}
<|endoftext|>


<commit_before>//! Deserializing TOML into Rust structures.
//!
//! This module contains all the Serde support for deserializing TOML documents into Rust structures.

use itertools::Itertools;
use serde::de::DeserializeOwned;

mod array;
mod inline_table;
mod item;
mod table;
mod table_enum;
mod value;

use array::*;
use inline_table::*;
use item::*;
use table::*;
use table_enum::*;

/// Errors that can occur when deserializing a type.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Error {
    message: String,
}

impl Error {
    pub(crate) fn custom<T>(msg: T) -> Self
    where
        T: std::fmt::Display,
    {
        Error {
            message: msg.to_string(),
        }
    }
}

impl serde::de::Error for Error {
    fn custom<T>(msg: T) -> Self
    where
        T: std::fmt::Display,
    {
        Error {
            message: msg.to_string(),
        }
    }
}

impl std::fmt::Display for Error {
    fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
        self.message.fmt(formatter)
    }
}

impl From<crate::TomlError> for Error {
    fn from(e: crate::TomlError) -> Error {
        Self::custom(e)
    }
}

impl From<Error> for crate::TomlError {
    fn from(e: Error) -> crate::TomlError {
        Self::custom(e.to_string())
    }
}

impl std::error::Error for Error {}

/// Convert a value into `T`.
pub fn from_str<T>(s: &'_ str) -> Result<T, Error>
where
    T: DeserializeOwned,
{
    let d = s.parse::<crate::Document>()?;
    from_document(d)
}

/// Convert a value into `T`.
pub fn from_slice<T>(s: &'_ [u8]) -> Result<T, Error>
where
    T: DeserializeOwned,
{
    let s = std::str::from_utf8(s).map_err(Error::custom)?;
    from_str(s)
}

/// Convert a document into `T`.
pub fn from_document<T>(d: crate::Document) -> Result<T, Error>
where
    T: DeserializeOwned,
{
    let deserializer = Deserializer::new(d);
    T::deserialize(deserializer)
}

/// Convert an item into `T`.
pub fn from_item<T>(d: crate::Item) -> Result<T, Error>
where
    T: DeserializeOwned,
{
    T::deserialize(d)
}

/// Deserialization implementation for TOML.
pub struct Deserializer {
    input: crate::Document,
}

impl Deserializer {
    /// Deserialization implementation for TOML.
    pub fn new(input: crate::Document) -> Self {
        Self { input }
    }
}

impl<'de, 'a> serde::Deserializer<'de> for Deserializer {
    type Error = Error;

    fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
    where
        V: serde::de::Visitor<'de>,
    {
        self.input.deserialize_any(visitor)
    }

    // `None` is interpreted as a missing field so be sure to implement `Some`
    // as a present field.
    fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Error>
    where
        V: serde::de::Visitor<'de>,
    {
        self.input.deserialize_option(visitor)
    }

    // Called when the type to deserialize is an enum, as opposed to a field in the type.
    fn deserialize_enum<V>(
        self,
        name: &'static str,
        variants: &'static [&'static str],
        visitor: V,
    ) -> Result<V::Value, Error>
    where
        V: serde::de::Visitor<'de>,
    {
        self.input.deserialize_enum(name, variants, visitor)
    }

    serde::forward_to_deserialize_any! {
        bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq
        bytes byte_buf map unit newtype_struct
        ignored_any unit_struct tuple_struct tuple identifier struct
    }
}

impl<'de, 'a> serde::Deserializer<'de> for crate::Document {
    type Error = Error;

    fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
    where
        V: serde::de::Visitor<'de>,
    {
        self.root.deserialize_any(visitor)
    }

    // `None` is interpreted as a missing field so be sure to implement `Some`
    // as a present field.
    fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Error>
    where
        V: serde::de::Visitor<'de>,
    {
        self.root.deserialize_option(visitor)
    }

    // Called when the type to deserialize is an enum, as opposed to a field in the type.
    fn deserialize_enum<V>(
        self,
        name: &'static str,
        variants: &'static [&'static str],
        visitor: V,
    ) -> Result<V::Value, Error>
    where
        V: serde::de::Visitor<'de>,
    {
        self.root.deserialize_enum(name, variants, visitor)
    }

    serde::forward_to_deserialize_any! {
        bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq
        bytes byte_buf map unit newtype_struct
        ignored_any unit_struct tuple_struct tuple identifier struct
    }
}

impl<'de> serde::de::IntoDeserializer<'de, crate::de::Error> for crate::Document {
    type Deserializer = Self;

    fn into_deserializer(self) -> Self {
        self
    }
}

pub(crate) fn validate_struct_keys(
    table: &crate::table::KeyValuePairs,
    fields: &'static [&'static str],
) -> Result<(), Error> {
    let extra_fields = table
        .iter()
        .filter_map(|(key, _val)| {
            if !fields.contains(&key.as_str()) {
                Some(key.clone())
            } else {
                None
            }
        })
        .collect::<Vec<_>>();

    if extra_fields.is_empty() {
        Ok(())
    } else {
        Err(Error::custom(format!(
            "unexpected keys in table: {}, available keys: {}",
            extra_fields.iter().join(", "),
            fields.iter().join(", "),
        )))
    }
}
<commit_msg>perf(de): Open up room for more error context<commit_after>//! Deserializing TOML into Rust structures.
//!
//! This module contains all the Serde support for deserializing TOML documents into Rust structures.

use itertools::Itertools;
use serde::de::DeserializeOwned;

mod array;
mod inline_table;
mod item;
mod table;
mod table_enum;
mod value;

use array::*;
use inline_table::*;
use item::*;
use table::*;
use table_enum::*;

/// Errors that can occur when deserializing a type.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Error {
    inner: Box<ErrorInner>,
}

#[derive(Debug, PartialEq, Eq, Clone)]
struct ErrorInner {
    message: String,
}

impl Error {
    pub(crate) fn custom<T>(msg: T) -> Self
    where
        T: std::fmt::Display,
    {
        Error {
            inner: Box::new(ErrorInner {
                message: msg.to_string(),
            }),
        }
    }
}

impl serde::de::Error for Error {
    fn custom<T>(msg: T) -> Self
    where
        T: std::fmt::Display,
    {
        Error::custom(msg)
    }
}

impl std::fmt::Display for Error {
    fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
        self.inner.message.fmt(formatter)
    }
}

impl From<crate::TomlError> for Error {
    fn from(e: crate::TomlError) -> Error {
        Self::custom(e)
    }
}

impl From<Error> for crate::TomlError {
    fn from(e: Error) -> crate::TomlError {
        Self::custom(e.to_string())
    }
}

impl std::error::Error for Error {}

/// Convert a value into `T`.
pub fn from_str<T>(s: &'_ str) -> Result<T, Error>
where
    T: DeserializeOwned,
{
    let d = s.parse::<crate::Document>()?;
    from_document(d)
}

/// Convert a value into `T`.
pub fn from_slice<T>(s: &'_ [u8]) -> Result<T, Error>
where
    T: DeserializeOwned,
{
    let s = std::str::from_utf8(s).map_err(Error::custom)?;
    from_str(s)
}

/// Convert a document into `T`.
pub fn from_document<T>(d: crate::Document) -> Result<T, Error>
where
    T: DeserializeOwned,
{
    let deserializer = Deserializer::new(d);
    T::deserialize(deserializer)
}

/// Convert an item into `T`.
pub fn from_item<T>(d: crate::Item) -> Result<T, Error>
where
    T: DeserializeOwned,
{
    T::deserialize(d)
}

/// Deserialization implementation for TOML.
pub struct Deserializer {
    input: crate::Document,
}

impl Deserializer {
    /// Deserialization implementation for TOML.
    pub fn new(input: crate::Document) -> Self {
        Self { input }
    }
}

impl<'de, 'a> serde::Deserializer<'de> for Deserializer {
    type Error = Error;

    fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
    where
        V: serde::de::Visitor<'de>,
    {
        self.input.deserialize_any(visitor)
    }

    // `None` is interpreted as a missing field so be sure to implement `Some`
    // as a present field.
    fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Error>
    where
        V: serde::de::Visitor<'de>,
    {
        self.input.deserialize_option(visitor)
    }

    // Called when the type to deserialize is an enum, as opposed to a field in the type.
    fn deserialize_enum<V>(
        self,
        name: &'static str,
        variants: &'static [&'static str],
        visitor: V,
    ) -> Result<V::Value, Error>
    where
        V: serde::de::Visitor<'de>,
    {
        self.input.deserialize_enum(name, variants, visitor)
    }

    serde::forward_to_deserialize_any! {
        bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq
        bytes byte_buf map unit newtype_struct
        ignored_any unit_struct tuple_struct tuple identifier struct
    }
}

impl<'de, 'a> serde::Deserializer<'de> for crate::Document {
    type Error = Error;

    fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
    where
        V: serde::de::Visitor<'de>,
    {
        self.root.deserialize_any(visitor)
    }

    // `None` is interpreted as a missing field so be sure to implement `Some`
    // as a present field.
    fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Error>
    where
        V: serde::de::Visitor<'de>,
    {
        self.root.deserialize_option(visitor)
    }

    // Called when the type to deserialize is an enum, as opposed to a field in the type.
    fn deserialize_enum<V>(
        self,
        name: &'static str,
        variants: &'static [&'static str],
        visitor: V,
    ) -> Result<V::Value, Error>
    where
        V: serde::de::Visitor<'de>,
    {
        self.root.deserialize_enum(name, variants, visitor)
    }

    serde::forward_to_deserialize_any! {
        bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq
        bytes byte_buf map unit newtype_struct
        ignored_any unit_struct tuple_struct tuple identifier struct
    }
}

impl<'de> serde::de::IntoDeserializer<'de, crate::de::Error> for crate::Document {
    type Deserializer = Self;

    fn into_deserializer(self) -> Self {
        self
    }
}

pub(crate) fn validate_struct_keys(
    table: &crate::table::KeyValuePairs,
    fields: &'static [&'static str],
) -> Result<(), Error> {
    let extra_fields = table
        .iter()
        .filter_map(|(key, _val)| {
            if !fields.contains(&key.as_str()) {
                Some(key.clone())
            } else {
                None
            }
        })
        .collect::<Vec<_>>();

    if extra_fields.is_empty() {
        Ok(())
    } else {
        Err(Error::custom(format!(
            "unexpected keys in table: {}, available keys: {}",
            extra_fields.iter().join(", "),
            fields.iter().join(", "),
        )))
    }
}
<|endoftext|>