//! Platform definition used by Cargo.
//!
//! This defines a [`Platform`] type which is used in Cargo to specify a target platform.
//! There are two kinds, a named target like `x86_64-apple-darwin`, and a "cfg expression"
//! like `cfg(any(target_os = "macos", target_os = "ios"))`.
//!
//! See `examples/matches.rs` for an example of how to match against a `Platform`.
//!
//! [`Platform`]: enum.Platform.html

use std::fmt;
use std::str::FromStr;

mod cfg;
mod error;

pub use cfg::{Cfg, CfgExpr};
pub use error::{ParseError, ParseErrorKind};

/// Platform definition.
#[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Clone, Debug)]
pub enum Platform {
    /// A named platform, like `x86_64-apple-darwin`.
    Name(String),
    /// A cfg expression, like `cfg(windows)`.
    Cfg(CfgExpr),
}

impl Platform {
    /// Returns whether the Platform matches the given target and cfg.
    ///
    /// The named target and cfg values should be obtained from `rustc`.
    pub fn matches(&self, name: &str, cfg: &[Cfg]) -> bool {
        match *self {
            Platform::Name(ref p) => p == name,
            Platform::Cfg(ref p) => p.matches(cfg),
        }
    }

    fn validate_named_platform(name: &str) -> Result<(), ParseError> {
        if let Some(ch) = name
            .chars()
            .find(|&c| !(c.is_alphanumeric() || c == '_' || c == '-' || c == '.'))
        {
            if name.chars().any(|c| c == '(') {
                return Err(ParseError::new(
                    name,
                    ParseErrorKind::InvalidTarget(
                        "unexpected `(` character, cfg expressions must start with `cfg(`"
                            .to_string(),
                    ),
                ));
            }
            return Err(ParseError::new(
                name,
                ParseErrorKind::InvalidTarget(format!(
                    "unexpected character {} in target name",
                    ch
                )),
            ));
        }
        Ok(())
    }
}

impl serde::Serialize for Platform {
    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        self.to_string().serialize(s)
    }
}

impl<'de> serde::Deserialize<'de> for Platform {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        FromStr::from_str(&s).map_err(serde::de::Error::custom)
    }
}

impl FromStr for Platform {
    type Err = ParseError;

    fn from_str(s: &str) -> Result<Platform, ParseError> {
        if s.starts_with("cfg(") && s.ends_with(')') {
            let s = &s[4..s.len() - 1];
            s.parse().map(Platform::Cfg)
        } else {
            Platform::validate_named_platform(s)?;
            Ok(Platform::Name(s.to_string()))
        }
    }
}

impl fmt::Display for Platform {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
            Platform::Name(ref n) => n.fmt(f),
            Platform::Cfg(ref e) => write!(f, "cfg({})", e),
        }
    }
}
