bwq 0.3.1

A linter and parser for Brandwatch query files (.bwq)
Documentation
use crate::error::Span;

#[derive(Debug, Clone, PartialEq)]
pub struct Query {
    pub expression: Expression,
    pub span: Span,
}

#[derive(Debug, Clone, PartialEq)]
pub enum Expression {
    BooleanOp {
        operator: BooleanOperator,
        left: Box<Expression>,
        right: Option<Box<Expression>>,
        span: Span,
    },

    Group {
        expression: Box<Expression>,
        span: Span,
    },

    Proximity {
        operator: ProximityOperator,
        terms: Vec<Expression>,
        span: Span,
    },

    Field {
        field: FieldType,
        value: Box<Expression>,
        span: Span,
    },

    Range {
        field: Option<FieldType>,
        start: String,
        end: String,
        span: Span,
    },

    Term {
        term: Term,
        span: Span,
    },
}

#[derive(Debug, Clone, PartialEq)]
pub enum BooleanOperator {
    And,
    Or,
    Not,
}

impl BooleanOperator {
    pub fn parse(s: &str) -> Option<Self> {
        match s {
            "AND" => Some(Self::And),
            "OR" => Some(Self::Or),
            "NOT" => Some(Self::Not),
            _ => None,
        }
    }

    pub fn as_str(&self) -> &'static str {
        match self {
            Self::And => "AND",
            Self::Or => "OR",
            Self::Not => "NOT",
        }
    }
}

#[derive(Debug, Clone, PartialEq)]
pub enum ProximityOperator {
    Proximity { distance: Option<u32> },
    Near { distance: u32 },
    NearForward { distance: u32 },
}

#[derive(Debug, Clone, PartialEq)]
pub enum FieldType {
    Title,
    Site,
    Url,
    Author,
    Links,

    Continent,
    Country,
    Region,
    City,
    Latitude,
    Longitude,

    Language,
    ChannelId,

    AuthorGender,
    AuthorVerified,
    AuthorVerifiedType,
    AuthorFollowers,
    BlogName,
    ParentBlogName,
    RootBlogName,
    ParentPostId,
    RootPostId,
    Tags,
    BrandIds,
    Objects,
    EngagementType,
    EngagingWith,
    EngagingWithGuid,
    Guid,
    ImageType,
    ItemReview,
    Rating,
    MinuteOfDay,
    PubType,
    PublisherSubType,
    Publication,
    RedditAuthorFlair,
    RedditPostFlair,
    RedditSpoiler,
    SensitiveContent,
    Subreddit,
    SubredditNSFW,
    SubredditTopics,
    TopLevelDomain,
    WeblogTitle,
    Sentiment,
}

impl FieldType {
    pub fn parse(s: &str) -> Option<Self> {
        match s.to_lowercase().as_str() {
            "title" => Some(Self::Title),
            "site" => Some(Self::Site),
            "url" => Some(Self::Url),
            "author" => Some(Self::Author),
            "links" => Some(Self::Links),
            "continent" => Some(Self::Continent),
            "country" => Some(Self::Country),
            "region" => Some(Self::Region),
            "city" => Some(Self::City),
            "latitude" => Some(Self::Latitude),
            "longitude" => Some(Self::Longitude),
            "language" => Some(Self::Language),
            "channelid" => Some(Self::ChannelId),
            "authorgender" => Some(Self::AuthorGender),
            "authorverified" => Some(Self::AuthorVerified),
            "authorverifiedtype" => Some(Self::AuthorVerifiedType),
            "authorfollowers" => Some(Self::AuthorFollowers),
            "blogname" => Some(Self::BlogName),
            "parentblogname" => Some(Self::ParentBlogName),
            "rootblogname" => Some(Self::RootBlogName),
            "parentpostid" => Some(Self::ParentPostId),
            "rootpostid" => Some(Self::RootPostId),
            "tags" => Some(Self::Tags),
            "brandids" => Some(Self::BrandIds),
            "objects" => Some(Self::Objects),
            "engagementtype" => Some(Self::EngagementType),
            "engagingwith" => Some(Self::EngagingWith),
            "engagingwithguid" => Some(Self::EngagingWithGuid),
            "guid" => Some(Self::Guid),
            "imagetype" => Some(Self::ImageType),
            "itemreview" => Some(Self::ItemReview),
            "rating" => Some(Self::Rating),
            "minuteofday" => Some(Self::MinuteOfDay),
            "pubtype" => Some(Self::PubType),
            "publishersubtype" => Some(Self::PublisherSubType),
            "publication" => Some(Self::Publication),
            "redditauthorflair" => Some(Self::RedditAuthorFlair),
            "redditpostflair" => Some(Self::RedditPostFlair),
            "redditspoiler" => Some(Self::RedditSpoiler),
            "sensitivecontent" => Some(Self::SensitiveContent),
            "subreddit" => Some(Self::Subreddit),
            "subredditnsfw" => Some(Self::SubredditNSFW),
            "subreddittopics" => Some(Self::SubredditTopics),
            "topleveldomain" => Some(Self::TopLevelDomain),
            "weblogtitle" => Some(Self::WeblogTitle),
            "sentiment" => Some(Self::Sentiment),
            _ => None,
        }
    }

    pub fn as_str(&self) -> &'static str {
        match self {
            Self::Title => "title",
            Self::Site => "site",
            Self::Url => "url",
            Self::Author => "author",
            Self::Links => "links",
            Self::Continent => "continent",
            Self::Country => "country",
            Self::Region => "region",
            Self::City => "city",
            Self::Latitude => "latitude",
            Self::Longitude => "longitude",
            Self::Language => "language",
            Self::ChannelId => "channelId",
            Self::AuthorGender => "authorGender",
            Self::AuthorVerified => "authorVerified",
            Self::AuthorVerifiedType => "authorVerifiedType",
            Self::AuthorFollowers => "authorFollowers",
            Self::BlogName => "blogName",
            Self::ParentBlogName => "parentBlogName",
            Self::RootBlogName => "rootBlogName",
            Self::ParentPostId => "parentPostId",
            Self::RootPostId => "rootPostId",
            Self::Tags => "tags",
            Self::BrandIds => "brandIds",
            Self::Objects => "objects",
            Self::EngagementType => "engagementType",
            Self::EngagingWith => "engagingWith",
            Self::EngagingWithGuid => "engagingWithGuid",
            Self::Guid => "guid",
            Self::ImageType => "imageType",
            Self::ItemReview => "itemReview",
            Self::Rating => "rating",
            Self::MinuteOfDay => "minuteOfDay",
            Self::PubType => "pubType",
            Self::PublisherSubType => "publisherSubType",
            Self::Publication => "publication",
            Self::RedditAuthorFlair => "redditAuthorFlair",
            Self::RedditPostFlair => "redditPostFlair",
            Self::RedditSpoiler => "redditSpoiler",
            Self::SensitiveContent => "sensitiveContent",
            Self::Subreddit => "subreddit",
            Self::SubredditNSFW => "subredditNSFW",
            Self::SubredditTopics => "subredditTopics",
            Self::TopLevelDomain => "topLevelDomain",
            Self::WeblogTitle => "weblogTitle",
            Self::Sentiment => "sentiment",
        }
    }
}

#[derive(Debug, Clone, PartialEq)]
pub enum Term {
    Word { value: String },
    Phrase { value: String },
    Wildcard { value: String },
    Replacement { value: String },
    CaseSensitive { value: String },
    Hashtag { value: String },
    Mention { value: String },
    Emoji { value: String },
}