<commit_before>use std;
use std::net::{IpAddr};
use std::time::Duration;
use std::marker::PhantomData;
use std::ops::Deref;
use std::sync::Arc;
use std::io;
use std::fs::File;
use std::io::{Read};
use linked_hash_map::Entry as BtEntry;
use std::str::FromStr;

use serde_yaml;
use serde::{Deserialize, Deserializer};
use serde::de::Error as DesError;
use yaml_rust::{Yaml, YamlLoader, YamlEmitter};
use trust_dns::rr::Name;

use utils::IpSet;

pub trait Validator<T> {
    fn validate(val: &T) -> Result<(), String>;
}
impl<T> Validator<T> for () {
    fn validate(_: &T) -> Result<(), String> { Ok(()) }
}
#[derive(Debug)]
pub struct NoRootName;
impl Validator<Name> for NoRootName {
    fn validate(val: &Name) -> Result<(), String> {
        if val.is_root() {
            return Err("Root domain is not allowed".into());
        }
        Ok(())
    }
}
#[derive(Debug)]
pub struct ProxiedValue<TStorage, TValue, TValidator: Validator<TValue> = ()>(TValue, PhantomData<TStorage>, PhantomData<TValidator>);
impl<TStorage, TValue, TValidator: Validator<TValue>> Deref for ProxiedValue<TStorage, TValue, TValidator> {
    type Target = TValue;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}
impl<TStorage, TValue, TValidator: Validator<TValue>> From<TValue> for ProxiedValue<TStorage, TValue, TValidator> {
    fn from(val: TValue) -> Self {
        ProxiedValue(val, PhantomData, PhantomData)
    }
}
pub trait FromStorage<'de, TStorage> {
    fn from_storage<TDes: ?Sized + Deserializer<'de>>(storage: &TStorage) -> Result<Self, TDes::Error> where Self: Sized;
}
impl<'de, TStorage, TValue, TValidator> Deserialize<'de> for ProxiedValue<TStorage, TValue, TValidator>
    where TStorage: Deserialize<'de> + std::fmt::Display,
          TValue: FromStorage<'de, TStorage>,
          TValidator: Validator<TValue>
{
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
        where D: Deserializer<'de>,
    {
        let val = try!(TStorage::deserialize(deserializer));
        let result = try!(TValue::from_storage::<D>(&val));
        TValidator::validate(&result).map_err(|e| D::Error::custom(format!("Invalid value {}: {}", val, e)))?;
        Ok(result.into())
    }
}
impl<'de> FromStorage<'de, u32> for Duration {
    fn from_storage<TDes: ?Sized + Deserializer<'de>>(storage: &u32) -> Result<Self, TDes::Error> {
        Ok(Duration::from_secs(*storage as u64))
    }
}
impl<'de> FromStorage<'de, String> for Arc<IpSet> {
    fn from_storage<TDes: ?Sized + Deserializer<'de>>(storage: &String) -> Result<Self, TDes::Error> {
        IpSet::from_file(&storage)
        .map(Arc::new)
        .map_err(|e| TDes::Error::custom(format!(
            "Failed to load IP list from {}: {}", storage, e,
        )))
    }
}
impl<'de> FromStorage<'de, String> for Name {
    fn from_storage<TDes: ?Sized + Deserializer<'de>>(storage: &String) -> Result<Self, TDes::Error> {
        Name::parse(&storage, Some(&Name::root()))
        .map_err(|e| TDes::Error::custom(format!(
            "Failed to parse domain name {}: {}", storage, e,
        )))
    }
}
macro_attr! {
    #[derive(Clone, Copy, Debug, NewtypeDisplay!, NewtypeFrom!, NewtypeDeref!)]
    pub struct PermissionBits(u32);
}
impl<'de> FromStorage<'de, String> for PermissionBits {
    fn from_storage<TDes: ?Sized + Deserializer<'de>>(storage: &String) -> Result<Self, TDes::Error> {
        u32::from_str_radix(&storage, 8)
        .map(Into::into)
        .map_err(|_| TDes::Error::custom(&format!(
            "Invalid permission bits: {}", storage,
        )))
    }
}

#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct CacheConfig {
    pub min_cache_ttl: u32,
    pub min_response_ttl: u32,
    pub neg_cache_ttl: u32,
    pub cache_retention_time: ProxiedValue<u32, Duration>,
    pub cache_limit: usize,
    pub gc_aggressive_threshold: usize,
    pub gc_interval: ProxiedValue<u32, Duration>,
}
#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct ServeConfig {
    pub ip: IpAddr,
    pub port: u16,
    pub tcp_timeout: ProxiedValue<u32, Duration>,
    pub setuid: Option<String>,
    pub setgid: Option<String>,
}
#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct ForwarderConfig {
    pub servers: Vec<IpAddr>,
    pub accepted_ip_list: Option<ProxiedValue<String, Arc<IpSet>>>,
}
#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct QueryConfig {
    pub timeout: ProxiedValue<u32, Duration>,
    pub trust_cname_hinting: bool,
    pub enable_dnssec: bool,
}
#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct ControlConfig {
    pub sock_path: String,
    pub sock_permission: ProxiedValue<String, PermissionBits>,
}
#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct InternalConfig {
    pub threads: usize,
    pub mio_notify_capacity: usize,
}
#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct ForwardZoneConfig {
    pub zone: ProxiedValue<String, Name, NoRootName>,
    pub server: IpAddr,
}
#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct Config {
    pub serve: ServeConfig,
    pub root_servers: Vec<IpAddr>,
    pub cache: CacheConfig,
    #[serde(default)]
    pub forwarders: Vec<ForwarderConfig>,
    pub query: QueryConfig,
    pub control: ControlConfig,
    pub internal: InternalConfig,
    #[serde(default)]
    pub forward_zones: Vec<ForwardZoneConfig>,
    #[serde(default)]
    include: Option<Vec<String>>, // Placeholder
}

const DEFAULT_CONFIG: &'static str = include_str!("../extra/config-default.yaml");
impl Default for Config {
    fn default() -> Self {
        serde_yaml::from_str(DEFAULT_CONFIG).expect("Default config should never fail")
    }
}
fn merge_yaml(base: Yaml, input: Yaml) -> Yaml {
    if let Yaml::Array(array_input) = input {
        if let Yaml::Array(array_base) = base {
            let mut new_array: Vec<Yaml> = Default::default();
            if array_input[0] == Yaml::String("-- APPEND --".into()) {
                new_array.extend(array_base);
                new_array.extend(array_input.into_iter().skip(1));
            } else {
                new_array.extend(array_input);
            }
            return Yaml::Array(new_array);
        } else {
            return Yaml::Array(array_input);
        }
    }
    let input = if let Yaml::Hash(hash) = input {
        hash
    } else {
        return input
    };
    let mut target = if let Yaml::Hash(base_hash) = base {
        base_hash
    } else {
        return Yaml::Hash(input)
    };
    for (k, v) in input {
        match target.entry(k) {
            BtEntry::Vacant(e) => { e.insert(v); },
            BtEntry::Occupied(mut e) => {
                let old = e.insert(Yaml::Null);
                e.insert(merge_yaml(old, v));
            },
        };
    }
    Yaml::Hash(target)
}
fn merge_config_values(user_values: Yaml) -> Yaml {
    merge_yaml(
        YamlLoader::load_from_str(DEFAULT_CONFIG).unwrap().into_iter().next().unwrap(),
        user_values,
    )
}
fn read_file(path: &str) -> io::Result<String> {
    let mut file = File::open(path)?;
    let mut buffer = String::new();
    file.read_to_string(&mut buffer)?;
    Ok(buffer)
}
fn parse_yaml(content: &str) -> io::Result<Yaml> {
    let parsed_yaml_docs = try!(
        YamlLoader::load_from_str(content)
        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
    );
    if parsed_yaml_docs.len() != 1 {
        return Err(io::Error::new(
            io::ErrorKind::InvalidData,
            "Config file must contain exactly one YAML dictionary",
        ));
    }
    Ok(parsed_yaml_docs[0].clone())
}
impl Config {
    pub fn from_file(path: &str) -> io::Result<Self> {
        Self::from_str(&read_file(path)?)
    }
}
impl FromStr for Config {
    type Err = io::Error;
    fn from_str(content: &str) -> std::result::Result<Self, Self::Err> {
        let parsed_yaml_doc = parse_yaml(content)?;
        let mut merged = merge_config_values(parsed_yaml_doc);
        let err = |msg| (io::Error::new(io::ErrorKind::InvalidData, msg));
        if let Some(vec) = merged["include"].as_vec().map(|x| (*x).clone()) {
            for elem in &vec {
                if let Some(s) = elem.as_str() {
                    let content = read_file(s).map_err(|e| err(format!("Failed to read included file {}: {}", s, e)))?;
                    let doc = parse_yaml(&content).map_err(|e| err(format!("Failed to parse included file {}: {}", s, e)))?;
                    merged = merge_yaml(merged, doc);
                } else {
                    return Err(err(format!("Invalid data in include list: {:?}", elem)));
                }
            }
        }
        let mut out_str = String::new();
        {
            let mut emitter = YamlEmitter::new(&mut out_str);
            emitter.dump(&merged).unwrap(); // dump the YAML object to a String
        }
        serde_yaml::from_str(&out_str)
        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, format!("{}", e)))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::net::IpAddr;
    use std::fs::{File, remove_file};
    use std::io::Write;

    #[test]
    fn test_default_config() {
        Config::default();
    }
    #[test]
    fn test_no_root() {
        let config_file = r#"---
serve:
    ip: "1.1.1.1"

forwarders:
    - servers:
        - "1.2.3.4"

forward_zones:
    - zone: .
      server: 1.2.3.4
    - zone: test
      server: 5.6.7.8
        "#;
        Config::from_str(config_file).unwrap_err();
    }
    #[test]
    fn test_merged_config() {
        let config_file = r#"---
serve:
    ip: "1.1.1.1"

forwarders:
    - servers:
        - "1.2.3.4"

forward_zones:
    - zone: rg
      server: 1.2.3.4
    - zone: test
      server: 5.6.7.8
        "#;
        let config = Config::from_str(config_file).unwrap();
        let default_config = Config::default();
        assert_eq!(config.serve.ip, "1.1.1.1".parse::<IpAddr>().unwrap());
        assert_eq!(config.serve.port, default_config.serve.port);
        assert_eq!(config.forwarders.len(), 1);
        assert_eq!(config.forwarders[0].servers[0], "1.2.3.4".parse::<IpAddr>().unwrap());
    }
    #[test]
    fn test_included_config() {
        const INCLUDE_PATH: &str = "/tmp/7f326c2f-9743-4c77-8c5f-136d8d6738d4";
        let included_file = r#"---
serve:
    ip: "1.1.1.1"

forwarders:
    - servers:
        - "1.2.3.4"
        "#;
        { File::create(INCLUDE_PATH).unwrap().write_all(included_file.as_bytes()).unwrap(); }
        let config = Config::from_str(&format!("---\n\ninclude: [\"{}\"]", INCLUDE_PATH)).unwrap();
        remove_file(INCLUDE_PATH).is_ok();
        let default_config = Config::default();
        assert_eq!(config.serve.ip, "1.1.1.1".parse::<IpAddr>().unwrap());
        assert_eq!(config.serve.port, default_config.serve.port);
        assert_eq!(config.forwarders.len(), 1);
        assert_eq!(config.forwarders[0].servers[0], "1.2.3.4".parse::<IpAddr>().unwrap());
    }
}
<commit_msg>Allow including empty document<commit_after>use std;
use std::net::{IpAddr};
use std::time::Duration;
use std::marker::PhantomData;
use std::ops::Deref;
use std::sync::Arc;
use std::io;
use std::fs::File;
use std::io::{Read};
use linked_hash_map::Entry as BtEntry;
use std::str::FromStr;

use serde_yaml;
use serde::{Deserialize, Deserializer};
use serde::de::Error as DesError;
use yaml_rust::{Yaml, YamlLoader, YamlEmitter};
use trust_dns::rr::Name;

use utils::IpSet;

pub trait Validator<T> {
    fn validate(val: &T) -> Result<(), String>;
}
impl<T> Validator<T> for () {
    fn validate(_: &T) -> Result<(), String> { Ok(()) }
}
#[derive(Debug)]
pub struct NoRootName;
impl Validator<Name> for NoRootName {
    fn validate(val: &Name) -> Result<(), String> {
        if val.is_root() {
            return Err("Root domain is not allowed".into());
        }
        Ok(())
    }
}
#[derive(Debug)]
pub struct ProxiedValue<TStorage, TValue, TValidator: Validator<TValue> = ()>(TValue, PhantomData<TStorage>, PhantomData<TValidator>);
impl<TStorage, TValue, TValidator: Validator<TValue>> Deref for ProxiedValue<TStorage, TValue, TValidator> {
    type Target = TValue;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}
impl<TStorage, TValue, TValidator: Validator<TValue>> From<TValue> for ProxiedValue<TStorage, TValue, TValidator> {
    fn from(val: TValue) -> Self {
        ProxiedValue(val, PhantomData, PhantomData)
    }
}
pub trait FromStorage<'de, TStorage> {
    fn from_storage<TDes: ?Sized + Deserializer<'de>>(storage: &TStorage) -> Result<Self, TDes::Error> where Self: Sized;
}
impl<'de, TStorage, TValue, TValidator> Deserialize<'de> for ProxiedValue<TStorage, TValue, TValidator>
    where TStorage: Deserialize<'de> + std::fmt::Display,
          TValue: FromStorage<'de, TStorage>,
          TValidator: Validator<TValue>
{
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
        where D: Deserializer<'de>,
    {
        let val = try!(TStorage::deserialize(deserializer));
        let result = try!(TValue::from_storage::<D>(&val));
        TValidator::validate(&result).map_err(|e| D::Error::custom(format!("Invalid value {}: {}", val, e)))?;
        Ok(result.into())
    }
}
impl<'de> FromStorage<'de, u32> for Duration {
    fn from_storage<TDes: ?Sized + Deserializer<'de>>(storage: &u32) -> Result<Self, TDes::Error> {
        Ok(Duration::from_secs(*storage as u64))
    }
}
impl<'de> FromStorage<'de, String> for Arc<IpSet> {
    fn from_storage<TDes: ?Sized + Deserializer<'de>>(storage: &String) -> Result<Self, TDes::Error> {
        IpSet::from_file(&storage)
        .map(Arc::new)
        .map_err(|e| TDes::Error::custom(format!(
            "Failed to load IP list from {}: {}", storage, e,
        )))
    }
}
impl<'de> FromStorage<'de, String> for Name {
    fn from_storage<TDes: ?Sized + Deserializer<'de>>(storage: &String) -> Result<Self, TDes::Error> {
        Name::parse(&storage, Some(&Name::root()))
        .map_err(|e| TDes::Error::custom(format!(
            "Failed to parse domain name {}: {}", storage, e,
        )))
    }
}
macro_attr! {
    #[derive(Clone, Copy, Debug, NewtypeDisplay!, NewtypeFrom!, NewtypeDeref!)]
    pub struct PermissionBits(u32);
}
impl<'de> FromStorage<'de, String> for PermissionBits {
    fn from_storage<TDes: ?Sized + Deserializer<'de>>(storage: &String) -> Result<Self, TDes::Error> {
        u32::from_str_radix(&storage, 8)
        .map(Into::into)
        .map_err(|_| TDes::Error::custom(&format!(
            "Invalid permission bits: {}", storage,
        )))
    }
}

#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct CacheConfig {
    pub min_cache_ttl: u32,
    pub min_response_ttl: u32,
    pub neg_cache_ttl: u32,
    pub cache_retention_time: ProxiedValue<u32, Duration>,
    pub cache_limit: usize,
    pub gc_aggressive_threshold: usize,
    pub gc_interval: ProxiedValue<u32, Duration>,
}
#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct ServeConfig {
    pub ip: IpAddr,
    pub port: u16,
    pub tcp_timeout: ProxiedValue<u32, Duration>,
    pub setuid: Option<String>,
    pub setgid: Option<String>,
}
#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct ForwarderConfig {
    pub servers: Vec<IpAddr>,
    pub accepted_ip_list: Option<ProxiedValue<String, Arc<IpSet>>>,
}
#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct QueryConfig {
    pub timeout: ProxiedValue<u32, Duration>,
    pub trust_cname_hinting: bool,
    pub enable_dnssec: bool,
}
#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct ControlConfig {
    pub sock_path: String,
    pub sock_permission: ProxiedValue<String, PermissionBits>,
}
#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct InternalConfig {
    pub threads: usize,
    pub mio_notify_capacity: usize,
}
#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct ForwardZoneConfig {
    pub zone: ProxiedValue<String, Name, NoRootName>,
    pub server: IpAddr,
}
#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct Config {
    pub serve: ServeConfig,
    pub root_servers: Vec<IpAddr>,
    pub cache: CacheConfig,
    #[serde(default)]
    pub forwarders: Vec<ForwarderConfig>,
    pub query: QueryConfig,
    pub control: ControlConfig,
    pub internal: InternalConfig,
    #[serde(default)]
    pub forward_zones: Vec<ForwardZoneConfig>,
    #[serde(default)]
    include: Option<Vec<String>>, // Placeholder
}

const DEFAULT_CONFIG: &'static str = include_str!("../extra/config-default.yaml");
impl Default for Config {
    fn default() -> Self {
        serde_yaml::from_str(DEFAULT_CONFIG).expect("Default config should never fail")
    }
}
fn merge_yaml(base: Yaml, input: Yaml) -> Yaml {
    if input == Yaml::Null {
        return base;
    }
    if let Yaml::Array(array_input) = input {
        if let Yaml::Array(array_base) = base {
            let mut new_array: Vec<Yaml> = Default::default();
            if array_input[0] == Yaml::String("-- APPEND --".into()) {
                new_array.extend(array_base);
                new_array.extend(array_input.into_iter().skip(1));
            } else {
                new_array.extend(array_input);
            }
            return Yaml::Array(new_array);
        } else {
            return Yaml::Array(array_input);
        }
    }
    let input = if let Yaml::Hash(hash) = input {
        hash
    } else {
        return input
    };
    let mut target = if let Yaml::Hash(base_hash) = base {
        base_hash
    } else {
        return Yaml::Hash(input)
    };
    for (k, v) in input {
        match target.entry(k) {
            BtEntry::Vacant(e) => { e.insert(v); },
            BtEntry::Occupied(mut e) => {
                let old = e.insert(Yaml::Null);
                e.insert(merge_yaml(old, v));
            },
        };
    }
    Yaml::Hash(target)
}
fn merge_config_values(user_values: Yaml) -> Yaml {
    merge_yaml(
        YamlLoader::load_from_str(DEFAULT_CONFIG).unwrap().into_iter().next().unwrap(),
        user_values,
    )
}
fn read_file(path: &str) -> io::Result<String> {
    let mut file = File::open(path)?;
    let mut buffer = String::new();
    file.read_to_string(&mut buffer)?;
    Ok(buffer)
}
fn parse_yaml(content: &str) -> io::Result<Yaml> {
    let parsed_yaml_docs = try!(
        YamlLoader::load_from_str(content)
        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
    );
    if parsed_yaml_docs.len() != 1 {
        return Err(io::Error::new(
            io::ErrorKind::InvalidData,
            "Config file must contain exactly one YAML dictionary",
        ));
    }
    Ok(parsed_yaml_docs[0].clone())
}
impl Config {
    pub fn from_file(path: &str) -> io::Result<Self> {
        Self::from_str(&read_file(path)?)
    }
}
impl FromStr for Config {
    type Err = io::Error;
    fn from_str(content: &str) -> std::result::Result<Self, Self::Err> {
        let parsed_yaml_doc = parse_yaml(content)?;
        let mut merged = merge_config_values(parsed_yaml_doc);
        let err = |msg| (io::Error::new(io::ErrorKind::InvalidData, msg));
        if let Some(vec) = merged["include"].as_vec().map(|x| (*x).clone()) {
            for elem in &vec {
                if let Some(s) = elem.as_str() {
                    let content = read_file(s).map_err(|e| err(format!("Failed to read included file {}: {}", s, e)))?;
                    let doc = parse_yaml(&content).map_err(|e| err(format!("Failed to parse included file {}: {}", s, e)))?;
                    merged = merge_yaml(merged, doc);
                } else {
                    return Err(err(format!("Invalid data in include list: {:?}", elem)));
                }
            }
        }
        let mut out_str = String::new();
        {
            let mut emitter = YamlEmitter::new(&mut out_str);
            emitter.dump(&merged).unwrap(); // dump the YAML object to a String
        }
        serde_yaml::from_str(&out_str)
        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, format!("{}", e)))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::net::IpAddr;
    use std::fs::{File, remove_file};
    use std::io::Write;

    #[test]
    fn test_default_config() {
        Config::default();
    }
    #[test]
    fn test_no_root() {
        let config_file = r#"---
serve:
    ip: "1.1.1.1"

forwarders:
    - servers:
        - "1.2.3.4"

forward_zones:
    - zone: .
      server: 1.2.3.4
    - zone: test
      server: 5.6.7.8
        "#;
        Config::from_str(config_file).unwrap_err();
    }
    #[test]
    fn test_merged_config() {
        let config_file = r#"---
serve:
    ip: "1.1.1.1"

forwarders:
    - servers:
        - "1.2.3.4"

forward_zones:
    - zone: rg
      server: 1.2.3.4
    - zone: test
      server: 5.6.7.8
        "#;
        let config = Config::from_str(config_file).unwrap();
        let default_config = Config::default();
        assert_eq!(config.serve.ip, "1.1.1.1".parse::<IpAddr>().unwrap());
        assert_eq!(config.serve.port, default_config.serve.port);
        assert_eq!(config.forwarders.len(), 1);
        assert_eq!(config.forwarders[0].servers[0], "1.2.3.4".parse::<IpAddr>().unwrap());
    }
    #[test]
    fn test_empty_include() {
        const INCLUDE_PATH: &str = "/tmp/c4683e82-9f96-47d3-afaf-c9823c185734";
        let included_file = r#"---

        "#;
        { File::create(INCLUDE_PATH).unwrap().write_all(included_file.as_bytes()).unwrap(); }
        Config::from_str(&format!("---\n\ninclude: [\"{}\"]", INCLUDE_PATH)).unwrap();
        remove_file(INCLUDE_PATH).is_ok();
    }
    #[test]
    fn test_included_config() {
        const INCLUDE_PATH: &str = "/tmp/7f326c2f-9743-4c77-8c5f-136d8d6738d4";
        let included_file = r#"---
serve:
    ip: "1.1.1.1"

forwarders:
    - servers:
        - "1.2.3.4"
        "#;
        { File::create(INCLUDE_PATH).unwrap().write_all(included_file.as_bytes()).unwrap(); }
        let config = Config::from_str(&format!("---\n\ninclude: [\"{}\"]", INCLUDE_PATH)).unwrap();
        remove_file(INCLUDE_PATH).is_ok();
        let default_config = Config::default();
        assert_eq!(config.serve.ip, "1.1.1.1".parse::<IpAddr>().unwrap());
        assert_eq!(config.serve.port, default_config.serve.port);
        assert_eq!(config.forwarders.len(), 1);
        assert_eq!(config.forwarders[0].servers[0], "1.2.3.4".parse::<IpAddr>().unwrap());
    }
}
<|endoftext|>


<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, default)]
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, default)]
pub struct PageBuilder {
    pub default: frontmatter::FrontmatterBuilder,
}

#[derive(Debug, PartialEq)]
#[derive(Serialize, Deserialize)]
#[serde(deny_unknown_fields, default)]
pub struct PostBuilder {
    pub title: 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 {
            title: 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, default)]
pub struct AssetsBuilder {
    pub sass: sass::SassBuilder,
}

impl AssetsBuilder {
    pub fn build(self) -> Assets {
        Assets { sass: self.sass.build() }
    }
}

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

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

#[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>,
    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,
    #[serde(skip)]
    pub layouts_dir: &'static str,
    #[serde(skip)]
    pub includes_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,
            includes_dir: INCLUDES_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: _layouts_dir,
            includes_dir: _includes_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 title) = posts.title {
                posts.slug = Some(slug::slugify(title));
            } 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));

        // HACK for serde #1105
        let layouts_dir = LAYOUTS_DIR;
        let includes_dir = INCLUDES_DIR;

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

        let assets = assets.build();

        let config = Config {
            source,
            destination,
            include_drafts,
            pages,
            posts,
            site,
            ignore,
            template_extensions,
            syntax_highlight,
            layouts_dir,
            includes_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 includes_dir: &'static str,
    pub assets: Assets,
    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());
}
<commit_msg>tests(config): Improve focus of tests<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, default)]
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, default)]
pub struct PageBuilder {
    pub default: frontmatter::FrontmatterBuilder,
}

#[derive(Debug, PartialEq)]
#[derive(Serialize, Deserialize)]
#[serde(deny_unknown_fields, default)]
pub struct PostBuilder {
    pub title: 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 {
            title: 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, default)]
pub struct AssetsBuilder {
    pub sass: sass::SassBuilder,
}

impl AssetsBuilder {
    pub fn build(self) -> Assets {
        Assets { sass: self.sass.build() }
    }
}

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

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

#[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>,
    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,
    #[serde(skip)]
    pub layouts_dir: &'static str,
    #[serde(skip)]
    pub includes_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,
            includes_dir: INCLUDES_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: _layouts_dir,
            includes_dir: _includes_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 title) = posts.title {
                posts.slug = Some(slug::slugify(title));
            } 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));

        // HACK for serde #1105
        let layouts_dir = LAYOUTS_DIR;
        let includes_dir = INCLUDES_DIR;

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

        let assets = assets.build();

        let config = Config {
            source,
            destination,
            include_drafts,
            pages,
            posts,
            site,
            ignore,
            template_extensions,
            syntax_highlight,
            layouts_dir,
            includes_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 includes_dir: &'static str,
    pub assets: Assets,
    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.root,
               path::Path::new("tests/fixtures/config").to_path_buf());
}

#[test]
fn test_from_file_alternate_name() {
    let result = ConfigBuilder::from_file("tests/fixtures/config/rss.yml").unwrap();
    assert_eq!(result.root,
               path::Path::new("tests/fixtures/config").to_path_buf());
}

#[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.root,
               path::Path::new("tests/fixtures/config").to_path_buf());
}

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

#[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.source,
               path::Path::new("tests/fixtures/config").to_path_buf());
    assert_eq!(result.destination,
               path::Path::new("tests/fixtures/config/dest").to_path_buf());
}

#[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.source,
               path::Path::new("tests/fixtures/config").to_path_buf());
    assert_eq!(result.destination,
               path::Path::new("hello/world").to_path_buf());
}

#[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>use cocoa::base::{id, nil};
use cocoa::foundation::{NSString, NSUInteger};
use error::NSError;
use objc::runtime::YES;
use objc_bringup::NSArray;
use sys::{MTLCopyAllDevices, MTLCreateSystemDefaultDevice, MTLDevice};
use std::borrow::Cow;
use std::convert::{AsRef, From};
use std::error::Error;
use std::ffi::CStr;
use std::fmt::{self, Display, Formatter};
use std::path::Path;
use {CommandQueue, CommandQueueError, CompileOptions, FromRaw, FromRawError, Library,
     LibraryError, LibraryErrorType, Size};

pub struct Device(id);

impl Device {
    pub fn system_default_device() -> Result<Self, DeviceError> {
        let device = unsafe { MTLCreateSystemDefaultDevice() };
        if device != nil { Ok(Device(device)) } else { Err(DeviceError::ConstructionFailed) }
    }

    pub fn enumerate_all_system_devices() -> Vec<Result<Self, DeviceError>> {
        let all_devices = unsafe { MTLCopyAllDevices() };
        let mut devices_vec = vec![];

        unsafe {
            for i in 0..all_devices.count() {
                let device = all_devices.objectAtIndex(i);
                if device != nil {
                    devices_vec.push(Ok(Device(device)));
                } else {
                    devices_vec.push(Err(DeviceError::ConstructionFailed));
                }
            }
        }

        devices_vec
    }

    pub fn new_command_queue(&mut self) -> Result<CommandQueue, CommandQueueError> {
        let command_queue = unsafe { self.0.newCommandQueue() };
        Ok(try!(FromRaw::from_raw(command_queue)))
    }

    pub fn new_command_queue_with_max_buffer_count(&mut self, max_command_buffer_count: usize)
                                                   -> Result<CommandQueue, CommandQueueError> {
        let command_queue = unsafe {
            self.0.newCommandQueueWithMaxCommandBufferCount(max_command_buffer_count as NSUInteger)
        };
        Ok(try!(FromRaw::from_raw(command_queue)))
    }

    // TODO(George): This will panic in the metal lib if not called from a binary in a bundle. Need
    // to add proper error checking
    pub fn new_default_library(&mut self) -> Result<Library, LibraryError> {
        unsafe {
            let library = self.0.newDefaultLibrary();
            Ok(try!(FromRaw::from_raw(library)))
        }
    }

    #[allow(unused_variables)]
    pub fn new_library_with_file<P: AsRef<Path>>(&mut self, file_path: P)
                                                 -> Result<Library, LibraryError> {
        unimplemented!();
    }

    pub fn new_library_with_source<S: AsRef<str>>(&mut self, source: S,
                                                  compile_options: &CompileOptions)
                                                  -> Result<Library, LibraryError> {
        unsafe {
            let source = NSString::alloc(nil).init_str(source.as_ref());
            let options = compile_options.mtl_compile_options();
            let mut error = nil;
            let library = self.0.newLibraryWithSource_options_error(source, options, &mut error);
            if library == nil {
                let error = NSError::new(error);
                Err(LibraryError {
                    ns_error: error,
                    error_type: LibraryErrorType::SourceError
                })
            } else {
                Ok(try!(FromRaw::from_raw(library)))
            }
        }
    }

    #[allow(unused_variables)]
    // TODO(George): `data` parameter should be of the correct type
    pub fn new_library_with_data(&mut self, data: ()) -> Result<Library, LibraryError> {
        unimplemented!();
    }

    pub fn is_depth24_stencil8_pixel_format_supported(&self) -> bool {
        unsafe { self.0.depth24Stencil8PixelFormatSupported() == YES }
    }

    pub fn is_headless(&self) -> bool {
        unsafe { self.0.headless() == YES }
    }

    pub fn is_low_power(&self) -> bool {
        unsafe { self.0.lowPower() == YES }
    }

    pub fn max_threads_per_group(&self) -> Size {
        unsafe { self.0.maxThreadsPerGroup().into() }
    }

    pub fn name(&self) -> Cow<str> {
        unsafe { CStr::from_ptr(self.0.name().UTF8String()).to_string_lossy() }
    }

    pub fn supports_texture_sample_count(&self, sample_count: usize) -> bool {
        unsafe { self.0.supportsTextureSampleCount(sample_count as NSUInteger) == YES }
    }
}

impl_from_into_raw!(Device, "MTLDevice");

/// Internal utility function to get a Device's id without consuming it.
/// Not exported publicly from this crate.
#[doc(hidden)]
pub unsafe fn _device_get_raw(device: &Device) -> id {
    device.0
}

#[derive(Clone, Debug)]
pub enum DeviceError {
    ConstructionFailed,
    RawError(FromRawError)
}

impl From<FromRawError> for DeviceError {
    fn from(error: FromRawError) -> Self {
        DeviceError::RawError(error)
    }
}

impl Display for DeviceError {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        let descr = match *self {
            DeviceError::ConstructionFailed => "DeviceError::ConstructionFailed",
            DeviceError::RawError(_) => "DeviceError::ConstructionFailed",
        };
        write!(f, "{}", descr)
    }
}

impl Error for DeviceError {
    fn description(&self) -> &str {
        match *self {
            DeviceError::ConstructionFailed => {
                "Could not create a default device. Please ensure that you are using at least OSX \
                 10.11 or iOS 8.0"
            }
            DeviceError::RawError(_) => "There was an error with the device pointer",
        }
    }

    fn cause(&self) -> Option<&Error> {
        match self {
            &DeviceError::ConstructionFailed => None,
            &DeviceError::RawError(ref raw_err) => Some(raw_err),
        }
    }
}
<commit_msg>Small refactor<commit_after>use cocoa::base::{id, nil};
use cocoa::foundation::{NSString, NSUInteger};
use error::NSError;
use objc::runtime::YES;
use objc_bringup::NSArray;
use sys::{MTLCopyAllDevices, MTLCreateSystemDefaultDevice, MTLDevice};
use std::borrow::Cow;
use std::convert::{AsRef, From};
use std::error::Error;
use std::ffi::CStr;
use std::fmt::{self, Display, Formatter};
use std::path::Path;
use {CommandQueue, CommandQueueError, CompileOptions, FromRaw, FromRawError, Library,
     LibraryError, LibraryErrorType, Size};

pub struct Device(id);

impl Device {
    pub fn system_default_device() -> Result<Self, DeviceError> {
        let device = unsafe { MTLCreateSystemDefaultDevice() };
        if device != nil { Ok(Device(device)) } else { Err(DeviceError::ConstructionFailed) }
    }

    pub fn enumerate_all_system_devices() -> Vec<Result<Self, DeviceError>> {
        let all_devices = unsafe { MTLCopyAllDevices() };
        let mut devices_vec = vec![];

        unsafe {
            for i in 0..all_devices.count() {
                let device = all_devices.objectAtIndex(i);
                if device != nil {
                    devices_vec.push(Ok(Device(device)));
                } else {
                    devices_vec.push(Err(DeviceError::ConstructionFailed));
                }
            }
        }

        devices_vec
    }

    pub fn new_command_queue(&mut self) -> Result<CommandQueue, CommandQueueError> {
        let command_queue = unsafe { self.0.newCommandQueue() };
        Ok(try!(FromRaw::from_raw(command_queue)))
    }

    pub fn new_command_queue_with_max_buffer_count(&mut self, max_command_buffer_count: usize)
                                                   -> Result<CommandQueue, CommandQueueError> {
        let command_queue = unsafe {
            self.0.newCommandQueueWithMaxCommandBufferCount(max_command_buffer_count as NSUInteger)
        };
        Ok(try!(FromRaw::from_raw(command_queue)))
    }

    // TODO(George): This will panic in the metal lib if not called from a binary in a bundle. Need
    // to add proper error checking
    pub fn new_default_library(&mut self) -> Result<Library, LibraryError> {
        unsafe {
            let library = self.0.newDefaultLibrary();
            Ok(try!(FromRaw::from_raw(library)))
        }
    }

    #[allow(unused_variables)]
    pub fn new_library_with_file<P: AsRef<Path>>(&mut self, file_path: P)
                                                 -> Result<Library, LibraryError> {
        unimplemented!();
    }

    pub fn new_library_with_source<S: AsRef<str>>(&mut self, source: S,
                                                  compile_options: &CompileOptions)
                                                  -> Result<Library, LibraryError> {
        unsafe {
            let source = NSString::alloc(nil).init_str(source.as_ref());
            let options = compile_options.mtl_compile_options();
            let mut error = nil;
            let library = self.0.newLibraryWithSource_options_error(source, options, &mut error);
            if library == nil {
                Err(LibraryError {
                    ns_error: NSError::new(error),
                    error_type: LibraryErrorType::SourceError
                })
            } else {
                Ok(try!(FromRaw::from_raw(library)))
            }
        }
    }

    #[allow(unused_variables)]
    // TODO(George): `data` parameter should be of the correct type
    pub fn new_library_with_data(&mut self, data: ()) -> Result<Library, LibraryError> {
        unimplemented!();
    }

    pub fn is_depth24_stencil8_pixel_format_supported(&self) -> bool {
        unsafe { self.0.depth24Stencil8PixelFormatSupported() == YES }
    }

    pub fn is_headless(&self) -> bool {
        unsafe { self.0.headless() == YES }
    }

    pub fn is_low_power(&self) -> bool {
        unsafe { self.0.lowPower() == YES }
    }

    pub fn max_threads_per_group(&self) -> Size {
        unsafe { self.0.maxThreadsPerGroup().into() }
    }

    pub fn name(&self) -> Cow<str> {
        unsafe { CStr::from_ptr(self.0.name().UTF8String()).to_string_lossy() }
    }

    pub fn supports_texture_sample_count(&self, sample_count: usize) -> bool {
        unsafe { self.0.supportsTextureSampleCount(sample_count as NSUInteger) == YES }
    }
}

impl_from_into_raw!(Device, "MTLDevice");

/// Internal utility function to get a Device's id without consuming it.
/// Not exported publicly from this crate.
#[doc(hidden)]
pub unsafe fn _device_get_raw(device: &Device) -> id {
    device.0
}

#[derive(Clone, Debug)]
pub enum DeviceError {
    ConstructionFailed,
    RawError(FromRawError)
}

impl From<FromRawError> for DeviceError {
    fn from(error: FromRawError) -> Self {
        DeviceError::RawError(error)
    }
}

impl Display for DeviceError {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        let descr = match *self {
            DeviceError::ConstructionFailed => "DeviceError::ConstructionFailed",
            DeviceError::RawError(_) => "DeviceError::ConstructionFailed",
        };
        write!(f, "{}", descr)
    }
}

impl Error for DeviceError {
    fn description(&self) -> &str {
        match *self {
            DeviceError::ConstructionFailed => {
                "Could not create a default device. Please ensure that you are using at least OSX \
                 10.11 or iOS 8.0"
            }
            DeviceError::RawError(_) => "There was an error with the device pointer",
        }
    }

    fn cause(&self) -> Option<&Error> {
        match self {
            &DeviceError::ConstructionFailed => None,
            &DeviceError::RawError(ref raw_err) => Some(raw_err),
        }
    }
}
<|endoftext|>


<commit_before>#[doc(
    brief = "The attribute parsing pass",
    desc =
    "Traverses the document tree, pulling relevant documention out of the \
     corresponding AST nodes. The information gathered here is the basis \
     of the natural-language documentation for a crate."
)];

import rustc::syntax::ast;
import rustc::middle::ast_map;

export mk_pass;

fn mk_pass() -> pass {
    run
}

fn run(
    srv: astsrv::srv,
    doc: doc::cratedoc
) -> doc::cratedoc {
    let fold = fold::fold({
        fold_crate: fold_crate,
        fold_mod: fold_mod,
        fold_fn: fold_fn,
        fold_const: fold_const,
        fold_enum: fold_enum,
        fold_res: fold_res
        with *fold::default_seq_fold(srv)
    });
    fold.fold_crate(fold, doc)
}

fn fold_crate(
    fold: fold::fold<astsrv::srv>,
    doc: doc::cratedoc
) -> doc::cratedoc {

    let srv = fold.ctxt;
    let doc = fold::default_seq_fold_crate(fold, doc);

    let attrs = astsrv::exec(srv) {|ctxt|
        let attrs = ctxt.ast.node.attrs;
        attr_parser::parse_crate(attrs)
    };

    {
        topmod: {
            name: option::from_maybe(doc.topmod.name, attrs.name)
            with doc.topmod
        }
    }
}

#[test]
fn should_replace_top_module_name_with_crate_name() {
    let source = "#[link(name = \"bond\")];";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let fold = fold::default_seq_fold(srv);
    let doc = fold_crate(fold, doc);
    assert doc.topmod.name == "bond";
}

fn parse_item_attrs<T>(
    srv: astsrv::srv,
    id: doc::ast_id,
    parse_attrs: fn~([ast::attribute]) -> T) -> T {
    astsrv::exec(srv) {|ctxt|
        let attrs = alt ctxt.ast_map.get(id) {
          ast_map::node_item(item) { item.attrs }
        };
        parse_attrs(attrs)
    }
}

fn fold_mod(fold: fold::fold<astsrv::srv>, doc: doc::moddoc) -> doc::moddoc {
    let srv = fold.ctxt;
    let attrs = if doc.id == ast::crate_node_id {
        // This is the top-level mod, use the crate attributes
        astsrv::exec(srv) {|ctxt|
            attr_parser::parse_mod(ctxt.ast.node.attrs)
        }
    } else {
        parse_item_attrs(srv, doc.id, attr_parser::parse_mod)
    };
    let doc = fold::default_seq_fold_mod(fold, doc);
    ret merge_mod_attrs(doc, attrs);

    fn merge_mod_attrs(
        doc: doc::moddoc,
        attrs: attr_parser::mod_attrs
    ) -> doc::moddoc {
        {
            brief: attrs.brief,
            desc: attrs.desc
            with doc
        }
    }
}

#[test]
fn fold_mod_should_extract_mod_attributes() {
    let source = "#[doc = \"test\"] mod a { }";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let fold = fold::default_seq_fold(srv);
    let doc = fold_mod(fold, doc.topmod.mods()[0]);
    assert doc.desc == some("test");
}

#[test]
fn fold_mod_should_extract_top_mod_attributes() {
    let source = "#[doc = \"test\"];";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let fold = fold::default_seq_fold(srv);
    let doc = fold_mod(fold, doc.topmod);
    assert doc.desc == some("test");
}

fn fold_fn(
    fold: fold::fold<astsrv::srv>,
    doc: doc::fndoc
) -> doc::fndoc {

    let srv = fold.ctxt;

    let attrs = parse_item_attrs(srv, doc.id, attr_parser::parse_fn);
    ret merge_fn_attrs(doc, attrs);

    fn merge_fn_attrs(
        doc: doc::fndoc,
        attrs: attr_parser::fn_attrs
    ) -> doc::fndoc {
        ret {
            brief: attrs.brief,
            desc: attrs.desc,
            args: merge_arg_attrs(doc.args, attrs.args),
            return: merge_ret_attrs(doc.return, attrs.return),
            failure: attrs.failure
            with doc
        };
    }

    fn merge_arg_attrs(
        docs: [doc::argdoc],
        attrs: [attr_parser::arg_attrs]
    ) -> [doc::argdoc] {
        vec::map(docs) {|doc|
            alt vec::find(attrs) {|attr|
                attr.name == doc.name
            } {
                some(attr) {
                    {
                        desc: some(attr.desc)
                        with doc
                    }
                }
                none { doc }
            }
        }
        // FIXME: Warning when documenting a non-existent arg
    }

    fn merge_ret_attrs(
        doc: doc::retdoc,
        attrs: option<str>
    ) -> doc::retdoc {
        {
            desc: attrs
            with doc
        }
    }
}

#[test]
fn fold_fn_should_extract_fn_attributes() {
    let source = "#[doc = \"test\"] fn a() -> int { }";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let fold = fold::default_seq_fold(srv);
    let doc = fold_fn(fold, doc.topmod.fns()[0]);
    assert doc.desc == some("test");
}

#[test]
fn fold_fn_should_extract_arg_attributes() {
    let source = "#[doc(args(a = \"b\"))] fn c(a: bool) { }";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let fold = fold::default_seq_fold(srv);
    let doc = fold_fn(fold, doc.topmod.fns()[0]);
    assert doc.args[0].desc == some("b");
}

#[test]
fn fold_fn_should_extract_return_attributes() {
    let source = "#[doc(return = \"what\")] fn a() -> int { }";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let doc = tystr_pass::mk_pass()(srv, doc);
    let fold = fold::default_seq_fold(srv);
    let doc = fold_fn(fold, doc.topmod.fns()[0]);
    assert doc.return.desc == some("what");
}

#[test]
fn fold_fn_should_preserve_sig() {
    let source = "fn a() -> int { }";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let doc = tystr_pass::mk_pass()(srv, doc);
    let fold = fold::default_seq_fold(srv);
    let doc = fold_fn(fold, doc.topmod.fns()[0]);
    assert doc.sig == some("fn a() -> int");
}

#[test]
fn fold_fn_should_extract_failure_conditions() {
    let source = "#[doc(failure = \"what\")] fn a() { }";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let fold = fold::default_seq_fold(srv);
    let doc = fold_fn(fold, doc.topmod.fns()[0]);
    assert doc.failure == some("what");
}

fn fold_const(
    fold: fold::fold<astsrv::srv>,
    doc: doc::constdoc
) -> doc::constdoc {
    let srv = fold.ctxt;
    let attrs = parse_item_attrs(srv, doc.id, attr_parser::parse_const);

    {
        brief: attrs.brief,
        desc: attrs.desc
        with doc
    }
}

#[test]
fn fold_const_should_extract_docs() {
    let source = "#[doc(brief = \"foo\", desc = \"bar\")]\
                  const a: bool = true;";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let fold = fold::default_seq_fold(srv);
    let doc = fold_const(fold, doc.topmod.consts()[0]);
    assert doc.brief == some("foo");
    assert doc.desc == some("bar");
}

fn fold_enum(
    fold: fold::fold<astsrv::srv>,
    doc: doc::enumdoc
) -> doc::enumdoc {
    let srv = fold.ctxt;
    let attrs = parse_item_attrs(srv, doc.id, attr_parser::parse_enum);

    {
        brief: attrs.brief,
        desc: attrs.desc,
        variants: vec::map(doc.variants) {|variant|
            let attrs = astsrv::exec(srv) {|ctxt|
                alt ctxt.ast_map.get(doc.id) {
                  ast_map::node_item(@{
                    node: ast::item_enum(ast_variants, _), _
                  }) {
                    let ast_variant = option::get(
                        vec::find(ast_variants) {|v|
                            v.node.name == variant.name
                        });

                    attr_parser::parse_variant(ast_variant.node.attrs)
                  }
                }
            };

            {
                desc: attrs.desc
                with variant
            }
        }
        with doc
    }
}

#[test]
fn fold_enum_should_extract_docs() {
    let source = "#[doc(brief = \"a\", desc = \"b\")]\
                  enum a { v }";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let fold = fold::default_seq_fold(srv);
    let doc = fold_enum(fold, doc.topmod.enums()[0]);
    assert doc.brief == some("a");
    assert doc.desc == some("b");
}

#[test]
fn fold_enum_should_extract_variant_docs() {
    let source = "enum a { #[doc = \"c\"] v }";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let fold = fold::default_seq_fold(srv);
    let doc = fold_enum(fold, doc.topmod.enums()[0]);
    assert doc.variants[0].desc == some("c");
}

fn fold_res(
    fold: fold::fold<astsrv::srv>,
    doc: doc::resdoc
) -> doc::resdoc {

    let srv = fold.ctxt;
    let attrs = parse_item_attrs(srv, doc.id, attr_parser::parse_fn);

    {
        brief: attrs.brief,
        desc: attrs.desc,
        args: vec::map(doc.args) {|doc|
            alt vec::find(attrs.args) {|attr|
                attr.name == doc.name
            } {
                some(attr) {
                    {
                        desc: some(attr.desc)
                        with doc
                    }
                }
                none { doc }
            }
        }
        with doc
    }
}

#[test]
fn fold_res_should_extract_docs() {
    let source = "#[doc(brief = \"a\", desc = \"b\")]\
                  resource r(b: bool) { }";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let fold = fold::default_seq_fold(srv);
    let doc = fold_res(fold, doc.topmod.resources()[0]);
    assert doc.brief == some("a");
    assert doc.desc == some("b");
}

#[test]
fn fold_res_should_extract_arg_docs() {
    let source = "#[doc(args(a = \"b\"))]\
                  resource r(a: bool) { }";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let fold = fold::default_seq_fold(srv);
    let doc = fold_res(fold, doc.topmod.resources()[0]);
    assert doc.args[0].name == "a";
    assert doc.args[0].desc == some("b");
}<commit_msg>rustdoc: Add iface attribute docs to the doc tree<commit_after>#[doc(
    brief = "The attribute parsing pass",
    desc =
    "Traverses the document tree, pulling relevant documention out of the \
     corresponding AST nodes. The information gathered here is the basis \
     of the natural-language documentation for a crate."
)];

import rustc::syntax::ast;
import rustc::middle::ast_map;

export mk_pass;

fn mk_pass() -> pass {
    run
}

fn run(
    srv: astsrv::srv,
    doc: doc::cratedoc
) -> doc::cratedoc {
    let fold = fold::fold({
        fold_crate: fold_crate,
        fold_mod: fold_mod,
        fold_fn: fold_fn,
        fold_const: fold_const,
        fold_enum: fold_enum,
        fold_res: fold_res,
        fold_iface: fold_iface
        with *fold::default_seq_fold(srv)
    });
    fold.fold_crate(fold, doc)
}

fn fold_crate(
    fold: fold::fold<astsrv::srv>,
    doc: doc::cratedoc
) -> doc::cratedoc {

    let srv = fold.ctxt;
    let doc = fold::default_seq_fold_crate(fold, doc);

    let attrs = astsrv::exec(srv) {|ctxt|
        let attrs = ctxt.ast.node.attrs;
        attr_parser::parse_crate(attrs)
    };

    {
        topmod: {
            name: option::from_maybe(doc.topmod.name, attrs.name)
            with doc.topmod
        }
    }
}

#[test]
fn should_replace_top_module_name_with_crate_name() {
    let source = "#[link(name = \"bond\")];";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let fold = fold::default_seq_fold(srv);
    let doc = fold_crate(fold, doc);
    assert doc.topmod.name == "bond";
}

fn parse_item_attrs<T>(
    srv: astsrv::srv,
    id: doc::ast_id,
    parse_attrs: fn~([ast::attribute]) -> T) -> T {
    astsrv::exec(srv) {|ctxt|
        let attrs = alt ctxt.ast_map.get(id) {
          ast_map::node_item(item) { item.attrs }
        };
        parse_attrs(attrs)
    }
}

fn fold_mod(fold: fold::fold<astsrv::srv>, doc: doc::moddoc) -> doc::moddoc {
    let srv = fold.ctxt;
    let attrs = if doc.id == ast::crate_node_id {
        // This is the top-level mod, use the crate attributes
        astsrv::exec(srv) {|ctxt|
            attr_parser::parse_mod(ctxt.ast.node.attrs)
        }
    } else {
        parse_item_attrs(srv, doc.id, attr_parser::parse_mod)
    };
    let doc = fold::default_seq_fold_mod(fold, doc);
    ret merge_mod_attrs(doc, attrs);

    fn merge_mod_attrs(
        doc: doc::moddoc,
        attrs: attr_parser::mod_attrs
    ) -> doc::moddoc {
        {
            brief: attrs.brief,
            desc: attrs.desc
            with doc
        }
    }
}

#[test]
fn fold_mod_should_extract_mod_attributes() {
    let source = "#[doc = \"test\"] mod a { }";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let fold = fold::default_seq_fold(srv);
    let doc = fold_mod(fold, doc.topmod.mods()[0]);
    assert doc.desc == some("test");
}

#[test]
fn fold_mod_should_extract_top_mod_attributes() {
    let source = "#[doc = \"test\"];";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let fold = fold::default_seq_fold(srv);
    let doc = fold_mod(fold, doc.topmod);
    assert doc.desc == some("test");
}

fn fold_fn(
    fold: fold::fold<astsrv::srv>,
    doc: doc::fndoc
) -> doc::fndoc {

    let srv = fold.ctxt;

    let attrs = parse_item_attrs(srv, doc.id, attr_parser::parse_fn);
    ret merge_fn_attrs(doc, attrs);

    fn merge_fn_attrs(
        doc: doc::fndoc,
        attrs: attr_parser::fn_attrs
    ) -> doc::fndoc {
        ret {
            brief: attrs.brief,
            desc: attrs.desc,
            args: merge_arg_attrs(doc.args, attrs.args),
            return: merge_ret_attrs(doc.return, attrs.return),
            failure: attrs.failure
            with doc
        };
    }
}

fn merge_arg_attrs(
    docs: [doc::argdoc],
    attrs: [attr_parser::arg_attrs]
) -> [doc::argdoc] {
    vec::map(docs) {|doc|
        alt vec::find(attrs) {|attr|
            attr.name == doc.name
        } {
            some(attr) {
                {
                    desc: some(attr.desc)
                    with doc
                }
            }
            none { doc }
        }
    }
    // FIXME: Warning when documenting a non-existent arg
}

fn merge_ret_attrs(
    doc: doc::retdoc,
    attrs: option<str>
) -> doc::retdoc {
    {
        desc: attrs
        with doc
    }
}

#[test]
fn fold_fn_should_extract_fn_attributes() {
    let source = "#[doc = \"test\"] fn a() -> int { }";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let fold = fold::default_seq_fold(srv);
    let doc = fold_fn(fold, doc.topmod.fns()[0]);
    assert doc.desc == some("test");
}

#[test]
fn fold_fn_should_extract_arg_attributes() {
    let source = "#[doc(args(a = \"b\"))] fn c(a: bool) { }";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let fold = fold::default_seq_fold(srv);
    let doc = fold_fn(fold, doc.topmod.fns()[0]);
    assert doc.args[0].desc == some("b");
}

#[test]
fn fold_fn_should_extract_return_attributes() {
    let source = "#[doc(return = \"what\")] fn a() -> int { }";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let doc = tystr_pass::mk_pass()(srv, doc);
    let fold = fold::default_seq_fold(srv);
    let doc = fold_fn(fold, doc.topmod.fns()[0]);
    assert doc.return.desc == some("what");
}

#[test]
fn fold_fn_should_preserve_sig() {
    let source = "fn a() -> int { }";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let doc = tystr_pass::mk_pass()(srv, doc);
    let fold = fold::default_seq_fold(srv);
    let doc = fold_fn(fold, doc.topmod.fns()[0]);
    assert doc.sig == some("fn a() -> int");
}

#[test]
fn fold_fn_should_extract_failure_conditions() {
    let source = "#[doc(failure = \"what\")] fn a() { }";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let fold = fold::default_seq_fold(srv);
    let doc = fold_fn(fold, doc.topmod.fns()[0]);
    assert doc.failure == some("what");
}

fn fold_const(
    fold: fold::fold<astsrv::srv>,
    doc: doc::constdoc
) -> doc::constdoc {
    let srv = fold.ctxt;
    let attrs = parse_item_attrs(srv, doc.id, attr_parser::parse_const);

    {
        brief: attrs.brief,
        desc: attrs.desc
        with doc
    }
}

#[test]
fn fold_const_should_extract_docs() {
    let source = "#[doc(brief = \"foo\", desc = \"bar\")]\
                  const a: bool = true;";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let fold = fold::default_seq_fold(srv);
    let doc = fold_const(fold, doc.topmod.consts()[0]);
    assert doc.brief == some("foo");
    assert doc.desc == some("bar");
}

fn fold_enum(
    fold: fold::fold<astsrv::srv>,
    doc: doc::enumdoc
) -> doc::enumdoc {
    let srv = fold.ctxt;
    let attrs = parse_item_attrs(srv, doc.id, attr_parser::parse_enum);

    {
        brief: attrs.brief,
        desc: attrs.desc,
        variants: vec::map(doc.variants) {|variant|
            let attrs = astsrv::exec(srv) {|ctxt|
                alt ctxt.ast_map.get(doc.id) {
                  ast_map::node_item(@{
                    node: ast::item_enum(ast_variants, _), _
                  }) {
                    let ast_variant = option::get(
                        vec::find(ast_variants) {|v|
                            v.node.name == variant.name
                        });

                    attr_parser::parse_variant(ast_variant.node.attrs)
                  }
                }
            };

            {
                desc: attrs.desc
                with variant
            }
        }
        with doc
    }
}

#[test]
fn fold_enum_should_extract_docs() {
    let source = "#[doc(brief = \"a\", desc = \"b\")]\
                  enum a { v }";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let fold = fold::default_seq_fold(srv);
    let doc = fold_enum(fold, doc.topmod.enums()[0]);
    assert doc.brief == some("a");
    assert doc.desc == some("b");
}

#[test]
fn fold_enum_should_extract_variant_docs() {
    let source = "enum a { #[doc = \"c\"] v }";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let fold = fold::default_seq_fold(srv);
    let doc = fold_enum(fold, doc.topmod.enums()[0]);
    assert doc.variants[0].desc == some("c");
}

fn fold_res(
    fold: fold::fold<astsrv::srv>,
    doc: doc::resdoc
) -> doc::resdoc {

    let srv = fold.ctxt;
    let attrs = parse_item_attrs(srv, doc.id, attr_parser::parse_fn);

    {
        brief: attrs.brief,
        desc: attrs.desc,
        args: vec::map(doc.args) {|doc|
            alt vec::find(attrs.args) {|attr|
                attr.name == doc.name
            } {
                some(attr) {
                    {
                        desc: some(attr.desc)
                        with doc
                    }
                }
                none { doc }
            }
        }
        with doc
    }
}

#[test]
fn fold_res_should_extract_docs() {
    let source = "#[doc(brief = \"a\", desc = \"b\")]\
                  resource r(b: bool) { }";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let fold = fold::default_seq_fold(srv);
    let doc = fold_res(fold, doc.topmod.resources()[0]);
    assert doc.brief == some("a");
    assert doc.desc == some("b");
}

#[test]
fn fold_res_should_extract_arg_docs() {
    let source = "#[doc(args(a = \"b\"))]\
                  resource r(a: bool) { }";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let fold = fold::default_seq_fold(srv);
    let doc = fold_res(fold, doc.topmod.resources()[0]);
    assert doc.args[0].name == "a";
    assert doc.args[0].desc == some("b");
}

fn fold_iface(
    fold: fold::fold<astsrv::srv>,
    doc: doc::ifacedoc
) -> doc::ifacedoc {
    let srv = fold.ctxt;
    let doc = fold::default_seq_fold_iface(fold, doc);
    let attrs = parse_item_attrs(srv, doc.id, attr_parser::parse_iface);

    {
        brief: attrs.brief,
        desc: attrs.desc,
        methods: merge_method_attrs(srv, doc.id, doc.methods)
        with doc
    }
}

fn merge_method_attrs(
    srv: astsrv::srv,
    item_id: doc::ast_id,
    docs: [doc::methoddoc]
) -> [doc::methoddoc] {
    // Create an assoc list from method name to attributes
    let attrs = astsrv::exec(srv) {|ctxt|
        alt ctxt.ast_map.get(item_id) {
          ast_map::node_item(@{
            node: ast::item_iface(_, methods), _
          }) {
            vec::map(methods) {|method|
                (method.ident, attr_parser::parse_method(method.attrs))
            }
          }
        }
    };

    vec::map2(docs, attrs) {|doc, attrs|
        assert doc.name == tuple::first(attrs);
        let attrs = tuple::second(attrs);

        {
            brief: attrs.brief,
            desc: attrs.desc,
            args: merge_arg_attrs(doc.args, attrs.args),
            return: merge_ret_attrs(doc.return, attrs.return),
            failure: attrs.failure
            with doc
        }
    }
}

#[test]
fn should_extract_iface_docs() {
    let source = "#[doc = \"whatever\"] iface i { fn a(); }";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let doc = run(srv, doc);
    assert doc.topmod.ifaces()[0].desc == some("whatever");
}

#[test]
fn should_extract_iface_method_docs() {
    let source = "iface i {\
                  #[doc(\
                  brief = \"brief\",\
                  desc = \"desc\",\
                  args(a = \"a\"),\
                  return = \"return\",\
                  failure = \"failure\")]\
                  fn f(a: bool) -> bool;\
                  }";
    let srv = astsrv::mk_srv_from_str(source);
    let doc = extract::from_srv(srv, "");
    let doc = run(srv, doc);
    assert doc.topmod.ifaces()[0].methods[0].brief == some("brief");
    assert doc.topmod.ifaces()[0].methods[0].desc == some("desc");
    assert doc.topmod.ifaces()[0].methods[0].args[0].desc == some("a");
    assert doc.topmod.ifaces()[0].methods[0].return.desc == some("return");
    assert doc.topmod.ifaces()[0].methods[0].failure == some("failure");
}<|endoftext|>