#![deny(missing_docs)]
use std;
use std::collections::BTreeSet;
use std::fmt;
use std::str::FromStr;
use indexmap::IndexSet;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::error::*;
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Default, Hash)]
pub enum TargetSpec {
#[default]
Wildcard,
Name(String),
All(BTreeSet<TargetSpec>),
Any(BTreeSet<TargetSpec>),
Not(Box<TargetSpec>),
}
impl fmt::Display for TargetSpec {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
TargetSpec::Wildcard => write!(f, "*"),
TargetSpec::Name(ref name) => write!(f, "{}", name),
TargetSpec::All(ref specs) => write!(f, "all({})", SpecsWriter(specs.iter())),
TargetSpec::Any(ref specs) => write!(f, "any({})", SpecsWriter(specs.iter())),
TargetSpec::Not(ref spec) => write!(f, "not({})", spec),
}
}
}
struct SpecsWriter<'a, T: Iterator<Item = &'a TargetSpec> + Clone + 'a>(T);
impl<'a, T> fmt::Display for SpecsWriter<'a, T>
where
T: Iterator<Item = &'a TargetSpec> + Clone + 'a,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use std::iter::{once, repeat};
for (sep, val) in once("").chain(repeat(", ")).zip(self.0.clone()) {
write!(f, "{}{}", sep, val)?;
}
Ok(())
}
}
impl fmt::Debug for TargetSpec {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
impl FromStr for TargetSpec {
type Err = Error;
fn from_str(s: &str) -> std::result::Result<Self, Error> {
let mut iter = s.chars();
let next = iter.next();
let mut lexer = TargetLexer {
inner: iter,
partial: None,
next,
};
parse(&mut lexer).map_err(|cause| {
Error::chain(
format!("Syntax error in target specification `{}`.", s),
cause,
)
})
}
}
impl Serialize for TargetSpec {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
format!("{}", self).serialize(serializer)
}
}
impl<'de> Deserialize<'de> for TargetSpec {
fn deserialize<D>(deserializer: D) -> std::result::Result<TargetSpec, D::Error>
where
D: Deserializer<'de>,
{
use serde::de;
let s = String::deserialize(deserializer)?;
TargetSpec::from_str(&s).map_err(de::Error::custom)
}
}
impl TargetSpec {
pub fn matches(&self, targets: &TargetSet) -> bool {
match *self {
TargetSpec::Wildcard => true,
TargetSpec::Name(ref name) => targets.0.contains(name),
TargetSpec::All(ref specs) => specs.iter().all(|s| s.matches(targets)),
TargetSpec::Any(ref specs) => specs.iter().any(|s| s.matches(targets)),
TargetSpec::Not(ref spec) => !spec.matches(targets),
}
}
pub fn is_wildcard(&self) -> bool {
matches!(*self, TargetSpec::Wildcard)
}
pub fn reduce(&self) -> Self {
match self {
TargetSpec::Wildcard => Self::Wildcard,
TargetSpec::Name(n) => Self::Name(n.clone()),
TargetSpec::All(set) | TargetSpec::Any(set) => {
let set = set
.iter()
.map(|s| s.reduce())
.filter(|s| !matches!(s, Self::Wildcard))
.collect::<BTreeSet<_>>();
match set.len() {
0 => Self::Wildcard,
1 => set.iter().next().unwrap().clone(),
_ => {
if matches!(self, TargetSpec::All(_)) {
Self::All(set)
} else {
Self::Any(set)
}
}
}
}
TargetSpec::Not(t) => Self::Not(Box::new(t.reduce())),
}
}
pub fn get_avail(&self) -> IndexSet<String> {
match *self {
TargetSpec::Wildcard => IndexSet::new(),
TargetSpec::Name(ref name) => IndexSet::from([name.clone()]),
TargetSpec::All(ref specs) | TargetSpec::Any(ref specs) => {
specs.iter().flat_map(TargetSpec::get_avail).collect()
}
TargetSpec::Not(ref spec) => spec.get_avail(),
}
}
}
#[derive(Debug, PartialEq, Eq)]
enum TargetToken {
Ident(String),
LParen,
RParen,
Comma,
Any,
All,
Not,
}
struct TargetLexer<T>
where
T: Iterator<Item = char>,
{
inner: T,
partial: Option<String>,
next: Option<char>,
}
impl<T> Iterator for TargetLexer<T>
where
T: Iterator<Item = char>,
{
type Item = Result<TargetToken>;
fn next(&mut self) -> Option<Result<TargetToken>> {
loop {
let next_is_letter = self
.next
.map(|c| c.is_alphanumeric() || c == '.' || c == '_' || c == '-' || c == ':')
.unwrap_or(false);
if !next_is_letter {
let mut partial = None;
std::mem::swap(&mut self.partial, &mut partial);
if let Some(partial) = partial {
if partial == "all" {
return Some(Ok(TargetToken::All));
} else if partial == "any" {
return Some(Ok(TargetToken::Any));
} else if partial == "not" {
return Some(Ok(TargetToken::Not));
} else {
return Some(Ok(TargetToken::Ident(partial)));
}
}
}
if next_is_letter {
if self.partial.is_none() {
self.partial = Some(String::new());
}
self.partial
.as_mut()
.unwrap()
.extend(self.next.unwrap().to_lowercase());
self.next = self.inner.next();
continue;
}
let next = self.next;
self.next = self.inner.next();
match next {
Some('(') => return Some(Ok(TargetToken::LParen)),
Some(')') => return Some(Ok(TargetToken::RParen)),
Some(',') => return Some(Ok(TargetToken::Comma)),
Some(c) if c.is_whitespace() => (),
Some(c) => return Some(Err(Error::new(format!("Invalid character `{}`.", c)))),
None => return None,
}
}
}
}
fn parse<T>(lexer: &mut TargetLexer<T>) -> Result<TargetSpec>
where
T: Iterator<Item = char>,
{
Ok(match lexer.next() {
Some(Ok(TargetToken::All)) => TargetSpec::All(parse_paren_list(lexer)?),
Some(Ok(TargetToken::Any)) => TargetSpec::Any(parse_paren_list(lexer)?),
Some(Ok(TargetToken::Not)) => {
parse_require(lexer, TargetToken::LParen, "Expected `(`.")?;
let spec = parse(lexer)?;
parse_require(lexer, TargetToken::RParen, "Expected `)`.")?;
TargetSpec::Not(Box::new(spec))
}
Some(Ok(TargetToken::Ident(name))) => {
if name.contains(':') {
return Err(Error::new("Targets names cannot contain colons (`:`)."));
}
if name.starts_with('-') {
return Err(Error::new("Target names cannot start with a hyphen (`-`)."));
}
TargetSpec::Name(name)
}
Some(Ok(TargetToken::LParen)) => {
let spec = parse(lexer)?;
parse_require(lexer, TargetToken::RParen, "Expected `)`.")?;
spec
}
wrong => return parse_wrong(wrong),
})
}
fn parse_paren_list<T>(lexer: &mut TargetLexer<T>) -> Result<BTreeSet<TargetSpec>>
where
T: Iterator<Item = char>,
{
parse_require(lexer, TargetToken::LParen, "Expected `(`.")?;
let mut set = BTreeSet::new();
set.insert(parse(lexer)?);
loop {
match lexer.next() {
Some(Ok(TargetToken::RParen)) => break,
Some(Ok(TargetToken::Comma)) => (),
wrong => return parse_wrong(wrong),
}
set.insert(parse(lexer)?);
}
Ok(set)
}
fn parse_require<T>(lexer: &mut TargetLexer<T>, token: TargetToken, msg: &str) -> Result<()>
where
T: Iterator<Item = char>,
{
match lexer.next() {
Some(Ok(ref tkn)) if tkn == &token => Ok(()),
Some(Err(e)) => Err(e),
_ => Err(Error::new(msg)),
}
}
fn parse_wrong<R>(wrong: Option<Result<TargetToken>>) -> Result<R> {
match wrong {
Some(Ok(TargetToken::All)) => Err(Error::new("Unexpected `all` keyword.")),
Some(Ok(TargetToken::Any)) => Err(Error::new("Unexpected `any` keyword.")),
Some(Ok(TargetToken::Not)) => Err(Error::new("Unexpected `not` keyword.")),
Some(Ok(TargetToken::Ident(name))) => {
Err(Error::new(format!("Unexpected identifier `{}`.", name)))
}
Some(Ok(TargetToken::LParen)) => Err(Error::new("Unexpected `(`.")),
Some(Ok(TargetToken::RParen)) => Err(Error::new("Unexpected `)`.")),
Some(Ok(TargetToken::Comma)) => Err(Error::new("Unexpected `,`.")),
Some(Err(e)) => Err(e),
None => Err(Error::new("Unexpected end of string.")),
}
}
#[derive(Clone, Debug, Serialize, Default)]
pub struct TargetSet(IndexSet<String>);
impl TargetSet {
pub fn empty() -> TargetSet {
TargetSet(Default::default())
}
pub fn new<I>(targets: I) -> TargetSet
where
I: IntoIterator,
I::Item: AsRef<str>,
{
let targets: IndexSet<String> = targets
.into_iter()
.map(|t| t.as_ref().to_lowercase())
.collect();
TargetSet(targets)
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = &String> {
self.0.iter()
}
pub fn insert(&mut self, target: String) {
self.0.insert(target);
}
pub fn reduce_for_dependency(&self, dep_name: &str) -> TargetSet {
let local_targets = TargetSet::new(
self.iter()
.filter_map(|trgt| {
if !trgt.contains(':') {
Some(trgt.clone())
} else {
let parts: Vec<&str> = trgt.splitn(2, ':').collect();
if dep_name == parts[0].to_lowercase().as_str() || parts[0] == "*" {
Some(parts[1].to_string())
} else {
None
}
}
})
.collect::<IndexSet<_>>(),
);
let neg_targets = local_targets
.iter()
.filter_map(|t| t.strip_prefix('-').map(|neg_target| neg_target.to_string()))
.collect::<IndexSet<_>>();
TargetSet::new(
local_targets
.iter()
.filter_map(|t| {
if t.starts_with('-') || neg_targets.contains(t) {
None
} else {
Some(t.clone())
}
})
.collect::<IndexSet<_>>(),
)
}
}
impl<'a> IntoIterator for &'a TargetSet {
type Item = <&'a IndexSet<String> as IntoIterator>::Item;
type IntoIter = <&'a IndexSet<String> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
impl IntoIterator for TargetSet {
type Item = <IndexSet<String> as IntoIterator>::Item;
type IntoIter = <IndexSet<String> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}