{-# LANGUAGE OverloadedStrings , FlexibleInstances , GeneralizedNewtypeDeriving , StandaloneDeriving , UndecidableInstances , ViewPatterns , PatternGuards #-} module Clay.Selector where import Control.Applicative import Data.Semigroup import Data.String import Data.Text (Text) import qualified Data.Text as Text -- | The star selector applies to all elements. Maps to @*@ in CSS. star :: Selector star = In (SelectorF (Refinement []) Star) -- | Select elements by name. The preferred syntax is to enable -- @OverloadedStrings@ and actually just use @\"element-name\"@ or use one of -- the predefined elements from "Clay.Elements". element :: Text -> Selector element e = In (SelectorF (Refinement []) (Elem e)) -- | Named alias for `**`. deep :: Selector -> Selector -> Selector deep a b = In (SelectorF (Refinement []) (Deep a b)) -- | The deep selector composer. Maps to @sel1 sel2@ in CSS. (**) :: Selector -> Selector -> Selector (**) = deep -- | Named alias for `|>`. child :: Selector -> Selector -> Selector child a b = In (SelectorF (Refinement []) (Child a b)) -- | The child selector composer. Maps to @sel1 > sel2@ in CSS. (|>) :: Selector -> Selector -> Selector (|>) = child -- | The adjacent selector composer. Maps to @sel1 + sel2@ in CSS. (|+) :: Selector -> Selector -> Selector (|+) a b = In (SelectorF (Refinement []) (Adjacent a b)) -- | Named alias for `#`. with :: Selector -> Refinement -> Selector with (In (SelectorF (Refinement fs) e)) (Refinement ps) = In (SelectorF (Refinement (fs ++ ps)) e) -- | The filter selector composer, adds a filter to a selector. Maps to -- something like @sel#filter@ or @sel.filter@ in CSS, depending on the filter. (#) :: Selector -> Refinement -> Selector (#) = with -- | Filter elements by id. The preferred syntax is to enable -- @OverloadedStrings@ and use @\"#id-name\"@. byId :: Text -> Refinement byId = Refinement . pure . Id -- | Filter elements by class. The preferred syntax is to enable -- @OverloadedStrings@ and use @\".class-name\"@. byClass :: Text -> Refinement byClass = Refinement . pure . Class -- | Filter elements by pseudo selector or pseudo class. The preferred syntax -- is to enable @OverloadedStrings@ and use @\":pseudo-selector\"@ or use one -- of the predefined ones from "Clay.Pseudo". pseudo :: Text -> Refinement pseudo = Refinement . pure . Pseudo -- | Filter elements by pseudo selector functions. The preferred way is to use -- one of the predefined functions from "Clay.Pseudo". func :: Text -> [Text] -> Refinement func f = Refinement . pure . PseudoFunc f -- | Filter elements based on the presence of a certain attribute. The -- preferred syntax is to enable @OverloadedStrings@ and use -- @\"\@attr\"@ or use one of the predefined ones from "Clay.Attributes". attr :: Text -> Refinement attr = Refinement . pure . Attr -- | Filter elements based on the presence of a certain attribute with the -- specified value. (@=) :: Text -> Text -> Refinement (@=) a = Refinement . pure . AttrVal a -- | Filter elements based on the presence of a certain attribute that begins -- with the selected value. (^=) :: Text -> Text -> Refinement (^=) a = Refinement . pure . AttrBegins a -- | Filter elements based on the presence of a certain attribute that ends -- with the specified value. ($=) :: Text -> Text -> Refinement ($=) a = Refinement . pure . AttrEnds a -- | Filter elements based on the presence of a certain attribute that contains -- the specified value as a substring. (*=) :: Text -> Text -> Refinement (*=) a = Refinement . pure . AttrContains a -- | Filter elements based on the presence of a certain attribute that have the -- specified value contained in a space separated list. (~=) :: Text -> Text -> Refinement (~=) a = Refinement . pure . AttrSpace a -- | Filter elements based on the presence of a certain attribute that have the -- specified value contained in a hyphen separated list. (|=) :: Text -> Text -> Refinement (|=) a = Refinement . pure . AttrHyph a ------------------------------------------------------------------------------- data Predicate = Id Text | Class Text | Attr Text | AttrVal Text Text | AttrBegins Text Text | AttrEnds Text Text | AttrContains Text Text | AttrSpace Text Text | AttrHyph Text Text | Pseudo Text | PseudoFunc Text [Text] | PseudoElem Text deriving (Eq, Ord, Show) newtype Refinement = Refinement { unFilter :: [Predicate] } deriving (Show, Semigroup, Monoid) instance IsString Refinement where fromString = refinementFromText . fromString refinementFromText :: Text -> Refinement refinementFromText t = Refinement $ case Text.uncons t of Just ('#', s) -> [Id s] Just ('.', s) -> [Class s] Just (':', s) | Just (':',s') <- Text.uncons s -> [PseudoElem s'] | otherwise -> [Pseudo s] Just ('@', s) -> [Attr s] _ -> [Attr t] ------------------------------------------------------------------------------- data Path f = Star | Elem Text | Child f f | Deep f f | Adjacent f f | Combined f f deriving Show newtype Fix f = In { out :: f (Fix f) } deriving instance Show (f (Fix f)) => Show (Fix f) data SelectorF a = SelectorF Refinement (Path a) deriving Show type Selector = Fix SelectorF instance IsString (Fix SelectorF) where fromString = selectorFromText . fromString selectorFromText :: Text -> Selector selectorFromText t = case Text.uncons t of Just (c, _) | elem c ("#.:@" :: [Char]) -> with star (refinementFromText t) _ -> In $ SelectorF (Refinement []) (Elem t) instance Semigroup (Fix SelectorF) where a <> b = In (SelectorF (Refinement []) (Combined a b)) instance Monoid (Fix SelectorF) where mempty = error "Selector is a semigroup" mappend = (<>)