{-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} module Language.Google.Search.Simple where import Prelude import Control.Monad.Free import Data.Char (isSpace) import Data.Foldable (fold) import Data.List (intersperse) import Data.Monoid import Data.String (IsString (..)) import Data.Text (Text) import qualified Data.Text as T import Data.Text.Lazy.Builder (Builder) import qualified Data.Text.Lazy.Builder as B -- * Units data Duration = Days | Months | Years deriving (Show) data Size = Bytes | KBytes | MBytes deriving (Show) -- | Render a search expression using Google search syntax. class SearchBuilder e where searchBuilder :: e -> Builder -- | 'SearchBuilder' for 'Free'! instance (Functor f, SearchBuilder a, SearchBuilder (f Builder)) => SearchBuilder (Free f a) where searchBuilder = iter searchBuilder . fmap searchBuilder ------------------------------------------------------------------------ -- * Primitive Terms -- | 'Fuzzy' terms are grouped with parentheses (if necessary), while -- 'Exact' terms are always “double-quoted”. The 'IsString' instance -- defaults to 'Fuzzy', so a @\"literal string\" ∷ 'Term'@ may be used. data Term = Fuzzy Text | Exact Text deriving (Show) instance IsString Term where fromString = Fuzzy . T.pack instance SearchBuilder Term where searchBuilder term = case term of Fuzzy s -> ($ B.fromText s) $ if T.any isSpace s then ("(" <>) . (<> ")") else id Exact s -> ($ B.fromText s) $ (B.singleton '"' <>) . (<> B.singleton '"') ------------------------------------------------------------------------ -- | The shape of Boolean expressions. data BooleanF e = Not e | And [e] | Or [e] deriving (Functor, Show) instance SearchBuilder (BooleanF Builder) where searchBuilder b = case b of Not e -> "-" <> e And es -> ("(" <>) . (<> ")") . fold $ intersperse " " es Or es -> ("(" <>) . (<> ")") . fold $ intersperse " OR " es -- | The free Boolean-shaped monad: comes with an 'IsString' instance -- so we can write @\"simple queries\" :: 'Simple'@. No refunds. type BooleanM = Free BooleanF instance (IsString a) => IsString (BooleanM a) where fromString = return . fromString -- | Simple Boolean combinations of 'Term's. type Simple = BooleanM Term ------------------------------------------------------------------------ -- | Conjunction on a list of 'BooleanM' expressions. fAnd :: [BooleanM a] -> BooleanM a fAnd = Free . And -- | Disjunction on a list of 'BooleanM' expressions. fOr :: [BooleanM a] -> BooleanM a fOr = Free . Or -- | Negation of a 'BooleanM' expression. fNot :: BooleanM a -> BooleanM a fNot = Free . Not