realia 0.2.0

Extra conditional compilation macros
Documentation
use crate::dep::{get_dep, get_deps};
use syn::parse::{Parse, ParseStream, Result};
use syn::punctuated::Punctuated;
use syn::{parenthesized, LitStr, Token};
use version_compare::{CompOp, VersionCompare};

pub enum Expr {
    Not(Box<Expr>),
    Any(Vec<Expr>),
    All(Vec<Expr>),
    EnvExists {
        name: String,
    },
    EnvEquals {
        name: String,
        value: String,
    },
    CmdExists {
        name: String,
    },
    DepExists {
        anchor: String,
        name: String,
    },
    DepEquals {
        anchor: String,
        name: String,
        version: String,
    },
    DepSince {
        anchor: String,
        name: String,
        version: String,
    },
    DepBefore {
        anchor: String,
        name: String,
        version: String,
    },
    DepFromRegistry {
        anchor: String,
        name: String,
    },
}

impl Expr {
    pub fn eval(&self) -> bool {
        use self::Expr::*;

        match self {
            Not(expr) => !expr.eval(),
            Any(exprs) => exprs.iter().any(|e| e.eval()),
            All(exprs) => exprs.iter().all(|e| e.eval()),
            EnvExists { name } => std::env::var(name).is_ok(),
            EnvEquals { name, value } => match std::env::var(name) {
                Ok(x) => &x == value,
                _ => false,
            },
            CmdExists { name } => which::which(name).is_ok(),
            DepExists { anchor, name } => get_dep(&anchor, &name).is_ok(),
            DepEquals { anchor, name, version } => get_deps(&anchor).unwrap().iter().any(|dep| {
                &dep.name == name && VersionCompare::compare_to(&dep.version, &version, &CompOp::Eq).unwrap()
            }),
            DepSince { anchor, name, version } => get_deps(&anchor).unwrap().iter().any(|dep| {
                &dep.name == name && VersionCompare::compare_to(&dep.version, &version, &CompOp::Ge).unwrap()
            }),
            DepBefore { anchor, name, version } => get_deps(&anchor).unwrap().iter().any(|dep| {
                &dep.name == name && VersionCompare::compare_to(&dep.version, &version, &CompOp::Lt).unwrap()
            }),
            DepFromRegistry { anchor, name } => get_deps(&anchor).unwrap().iter().any(|dep| {
                &dep.name == name
                    && match &dep.source {
                        Some(source) => source.starts_with("registry+"),
                        _ => false,
                    }
            }),
        }
    }
}

type Exprs = Punctuated<Expr, Token![,]>;

mod keyword {
    syn::custom_keyword!(not);
    syn::custom_keyword!(any);
    syn::custom_keyword!(all);
    syn::custom_keyword!(env);
    syn::custom_keyword!(cmd);
    syn::custom_keyword!(dep);
    syn::custom_keyword!(dep_since);
    syn::custom_keyword!(dep_before);
    syn::custom_keyword!(dep_from_registry);
}

impl Parse for Expr {
    fn parse(input: ParseStream) -> Result<Self> {
        let lookahead = input.lookahead1();
        if lookahead.peek(keyword::not) {
            Self::parse_not(input)
        } else if lookahead.peek(keyword::any) {
            Self::parse_any(input)
        } else if lookahead.peek(keyword::all) {
            Self::parse_all(input)
        } else if lookahead.peek(keyword::env) {
            Self::parse_env(input)
        } else if lookahead.peek(keyword::cmd) {
            Self::parse_cmd(input)
        } else if lookahead.peek(keyword::dep) {
            Self::parse_dep(input)
        } else if lookahead.peek(keyword::dep_since) {
            Self::parse_dep_since(input)
        } else if lookahead.peek(keyword::dep_before) {
            Self::parse_dep_before(input)
        } else if lookahead.peek(keyword::dep_from_registry) {
            Self::parse_dep_from_registry(input)
        } else {
            Err(lookahead.error())
        }
    }
}

impl Expr {
    fn parse_not(input: ParseStream) -> Result<Self> {
        input.parse::<keyword::not>()?;

        let paren;
        parenthesized!(paren in input);
        let expr: Expr = paren.parse()?;
        paren.parse::<Option<Token![,]>>()?;

        Ok(Expr::Not(Box::new(expr)))
    }

    fn parse_any(input: ParseStream) -> Result<Self> {
        input.parse::<keyword::any>()?;

        let paren;
        parenthesized!(paren in input);
        let exprs: Exprs = paren.parse_terminated(Expr::parse)?;

        Ok(Expr::Any(exprs.into_iter().collect()))
    }

    fn parse_all(input: ParseStream) -> Result<Self> {
        input.parse::<keyword::all>()?;

        let paren;
        parenthesized!(paren in input);
        let exprs: Exprs = paren.parse_terminated(Expr::parse)?;

        Ok(Expr::All(exprs.into_iter().collect()))
    }

    fn parse_env(input: ParseStream) -> Result<Self> {
        input.parse::<keyword::env>()?;

        let paren;
        parenthesized!(paren in input);
        let arg1: LitStr = paren.parse()?;
        match paren.parse::<Token![,]>() {
            Ok(_) => {
                let arg2: LitStr = paren.parse()?;
                Ok(Expr::EnvEquals {
                    name: arg1.value(),
                    value: arg2.value(),
                })
            }
            Err(_) => Ok(Expr::EnvExists { name: arg1.value() }),
        }
    }

    fn parse_cmd(input: ParseStream) -> Result<Self> {
        input.parse::<keyword::cmd>()?;

        let paren;
        parenthesized!(paren in input);
        let arg1: LitStr = paren.parse()?;

        Ok(Expr::CmdExists { name: arg1.value() })
    }

    fn parse_dep(input: ParseStream) -> Result<Self> {
        input.parse::<keyword::dep>()?;

        let paren;
        parenthesized!(paren in input);
        let arg1: LitStr = paren.parse()?;
        paren.parse::<Token![,]>()?;
        let arg2: LitStr = paren.parse()?;
        match paren.parse::<Token![,]>() {
            Ok(_) => {
                let arg3: LitStr = paren.parse()?;
                Ok(Expr::DepEquals {
                    anchor: arg1.value(),
                    name: arg2.value(),
                    version: arg3.value(),
                })
            }
            Err(_) => Ok(Expr::DepExists {
                anchor: arg1.value(),
                name: arg2.value(),
            }),
        }
    }

    fn parse_dep_since(input: ParseStream) -> Result<Self> {
        input.parse::<keyword::dep_since>()?;

        let paren;
        parenthesized!(paren in input);
        let arg1: LitStr = paren.parse()?;
        paren.parse::<Token![,]>()?;
        let arg2: LitStr = paren.parse()?;
        paren.parse::<Token![,]>()?;
        let arg3: LitStr = paren.parse()?;
        Ok(Expr::DepSince {
            anchor: arg1.value(),
            name: arg2.value(),
            version: arg3.value(),
        })
    }

    fn parse_dep_before(input: ParseStream) -> Result<Self> {
        input.parse::<keyword::dep_before>()?;

        let paren;
        parenthesized!(paren in input);
        let arg1: LitStr = paren.parse()?;
        paren.parse::<Token![,]>()?;
        let arg2: LitStr = paren.parse()?;
        paren.parse::<Token![,]>()?;
        let arg3: LitStr = paren.parse()?;
        Ok(Expr::DepBefore {
            anchor: arg1.value(),
            name: arg2.value(),
            version: arg3.value(),
        })
    }

    fn parse_dep_from_registry(input: ParseStream) -> Result<Self> {
        input.parse::<keyword::dep_from_registry>()?;

        let paren;
        parenthesized!(paren in input);
        let arg1: LitStr = paren.parse()?;
        paren.parse::<Token![,]>()?;
        let arg2: LitStr = paren.parse()?;
        Ok(Expr::DepFromRegistry {
            anchor: arg1.value(),
            name: arg2.value(),
        })
    }
}