{-# LANGUAGE CPP #-} {-# LANGUAGE JavaScriptFFI #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PartialTypeSignatures #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeApplications #-} {-# OPTIONS_GHC -ddump-splices #-} module Shpadoinkle.Html.TH.CSS ( extractNamespace , textProperty' ) where import Control.Compactable import Data.ByteString.Lazy as BS (ByteString) import qualified Data.ByteString.Lazy.Char8 as BS import Data.Containers.ListUtils (nubOrd) import qualified Data.Set as Set import Data.String (IsString) import Data.Text (Text) import Language.Haskell.TH.Syntax #ifdef ghcjs_HOST_OS import Data.Text.Encoding import GHCJS.Marshal.Pure import GHCJS.Types as T import Language.Javascript.JSaddle import System.IO.Unsafe (unsafePerformIO) #else import Text.Regex.PCRE #endif import Shpadoinkle (Prop) import Shpadoinkle.Html (ClassList (..)) import Shpadoinkle.Html.Property (textProperty') extractNamespace :: FilePath -> Q [Dec] extractNamespace fp = do css <- runIO $ BS.readFile fp return . split . nubOrd $ getAll css #ifdef ghcjs_HOST_OS foreign import javascript unsafe "Array.from($1.match(new RegExp($2, 'g')))" js_match :: T.JSString -> T.JSString -> IO JSVal notMempty :: (Eq m, Monoid m) => m -> Maybe m notMempty x | x == mempty = Nothing | otherwise = Just x getAll :: ByteString -> [ByteString] getAll css = unsafePerformIO $ do matches <- js_match (pFromJSVal . pToJSVal $ BS.unpack css) selectors maybe [] (fmapMaybe $ notMempty . BS.fromStrict . encodeUtf8) <$> fromJSVal matches #else getAll :: ByteString -> [ByteString] getAll css = getAllTextMatches $ css =~ (selectors @ByteString) #endif split :: [ByteString] -> [Dec] split ss = (toClassDec =<< classes) <> (toIdDec =<< ids) where (classes, ids) = fforEither ss $ \selector -> case BS.uncons selector of Just ('.', class') -> Left class' Just ('#', id') -> Right id' _ -> error "Selector found that is not and id or class" toIdDec :: ByteString -> [Dec] toIdDec "" = [] toIdDec name = let a = VarT $ mkName "a" m = VarT $ mkName "m" l = mkName "textProperty'" name' = case BS.unpack name of '#':rs -> rs rs -> rs n = mkName $ "id'" <> sanitize name' in [ SigD n ((AppT (AppT (TupleT 2) (ConT ''Data.Text.Text)) (AppT (AppT (ConT ''Shpadoinkle.Prop) m) a))) , ValD (VarP n) (NormalB (AppE (AppE (VarE l) (LitE (StringL "id"))) (LitE (StringL name')))) [] ] toClassDec :: ByteString -> [Dec] toClassDec "" = [] toClassDec n' = let n = mkName . sanitize $ case BS.unpack n' of '.':rs -> rs rs -> rs in [ SigD n (ConT ''ClassList) , ValD (VarP n) (NormalB (AppE (ConE 'ClassList) (AppE (VarE 'Set.singleton) (LitE (StringL $ BS.unpack n'))))) [] ] sanitize :: String -> String sanitize = (=<<) $ \case '/' -> "''" '-' -> "_" ':' -> "'" '>' -> "GT" x -> pure x selectors :: IsString s => s selectors = "(#|\\.)-?[_a-zA-Z]+[_a-zA-Z0-9-]*(?=[^}]*\\{)"