~alcinnz/haskell-stylist

04976d4d449c66d852580717ce7ca105f61f8360 — Adrian Cochrane 4 years ago c66e930
Unittest conditional styles, add loadImports API to fit Rhapsode's use.
M src/Data/CSS/Preprocessor/Conditions.hs => src/Data/CSS/Preprocessor/Conditions.hs +26 -8
@@ 1,10 1,12 @@
{-# LANGUAGE OverloadedStrings #-}
module Data.CSS.Preprocessor.Conditions(
        ConditionalStyles(..), conditionalStyles,
        extractImports, resolveImports, resolve
        extractImports, resolveImports, loadImports, resolve,
        Datum(..)
    ) where

import qualified Data.CSS.Preprocessor.Conditions.Expr as Query
import Data.CSS.Preprocessor.Conditions.Expr (Datum(..))

import Data.CSS.Syntax.StyleSheet
import Data.CSS.Syntax.Selector


@@ 25,7 27,7 @@ data ConditionalStyles p = ConditionalStyles {
}

conditionalStyles :: PropertyParser p => URI -> String -> ConditionalStyles p
conditionalStyles uri mediaDocument = ConditionalStyles uri mediaDocument [] temp
conditionalStyles uri mediaDocument' = ConditionalStyles uri mediaDocument' [] temp

data ConditionalRule p = Priority Int | StyleRule' StyleRule | AtRule Text [Token] |
    External Query.Expr URI | Internal Query.Expr (ConditionalStyles p)


@@ 106,17 108,33 @@ resolveImports self responses = self {rules = map resolveImport $ rules self}
            Internal cond body
        resolveImport x = x

loadImports :: PropertyParser p => (URI -> IO Text) -> (Text -> Query.Datum) -> (Token -> Query.Datum) ->
        ConditionalStyles p -> [URI] -> IO (ConditionalStyles p)
loadImports loader vars evalToken self blocklist = do
        let imports = extractImports vars evalToken self
        imported <- loadAll [url | url <- imports, url `notElem` blocklist] Nothing
        return $ resolveImports self imported
    where
        loadAll urls Nothing = loadAll urls $ Just urls
        loadAll (url:urls) (Just blocklist') = do
            source <- loader url
            let parsed = parse self {rules = []} source
            styles <- loadImports loader vars evalToken parsed (blocklist ++ blocklist')
            rest <- loadAll urls $ Just blocklist'
            return ((url, styles):rest)
        loadAll [] _ = return []

resolve :: StyleSheet s => (Text -> Query.Datum) -> (Token -> Query.Datum) ->
        s -> ConditionalStyles p -> s
resolve v t styles self = resolve' v t (reverse $ rules self) styles
resolve' :: StyleSheet s => (Text -> Query.Datum) -> (Token -> Query.Datum) ->
        [ConditionalRule p] -> s -> s
resolve' v t (Priority x:rules) styles = resolve' v t rules $ setPriority x styles
resolve' v t (StyleRule' rule:rules) styles = resolve' v t rules $ addRule styles rule
resolve' v t (AtRule name block:rules) styles = resolve' v t rules $ fst $ addAtRule styles name block
resolve' v t (Internal cond block:rules) styles | Query.eval v t cond =
    resolve' v t rules $ resolve v t styles block
resolve' v t (_:rules) styles = resolve' v t rules styles
resolve' v t (Priority x:rules') styles = resolve' v t rules' $ setPriority x styles
resolve' v t (StyleRule' rule:rules') styles = resolve' v t rules' $ addRule styles rule
resolve' v t (AtRule name block:rules') styles = resolve' v t rules' $ fst $ addAtRule styles name block
resolve' v t (Internal cond block:rules') styles | Query.eval v t cond =
    resolve' v t rules' $ resolve v t styles block
resolve' v t (_:rules') styles = resolve' v t rules' styles
resolve' _ _ [] styles = styles

--------

M src/Data/CSS/Preprocessor/Conditions/Expr.hs => src/Data/CSS/Preprocessor/Conditions/Expr.hs +6 -4
@@ 1,6 1,6 @@
{-# LANGUAGE OverloadedStrings #-}
module Data.CSS.Preprocessor.Conditions.Expr(
        Expr, Op(..), parse, eval, Datum(..)
        Expr, Op(..), parse, parse', eval, Datum(..)
    ) where

import Data.CSS.Syntax.Tokens(Token(..))


@@ 9,7 9,7 @@ import Data.Text (stripPrefix)

type Expr = [Op]
data Op = And | Or | Not | Var Text | Tok Token | MkRatio | Func Text [Token]
    | Less | LessEq | Equal | Greater | GreaterEq deriving Eq
    | Less | LessEq | Equal | Greater | GreaterEq deriving (Show, Eq)

parse :: Token -> [Token] -> (Expr, [Token])
parse end toks = let (toks', rest) = break (== end) toks in (parse' toks' [], rest)


@@ 27,7 27,7 @@ parse' (Ident "only":toks) ops = parse' toks ops
parse' (Ident "and":toks) ops = pushOp toks And 30 ops
parse' (Ident "or":toks) ops = pushOp toks Or 30 ops
parse' (Delim '<':Delim '=':toks) ops = pushOp toks LessEq 40 ops
parse' (Delim '<':toks) ops = pushOp toks LessEq 40 ops
parse' (Delim '<':toks) ops = pushOp toks Less 40 ops
parse' (Delim '>':Delim '=':toks) ops = pushOp toks GreaterEq 40 ops
parse' (Delim '>':toks) ops = pushOp toks Greater 40 ops
parse' (Colon:tok:toks) ops = Tok tok : pushOp toks Equal 40 ops


@@ 65,7 65,9 @@ eval' (B y:B x:stack) v t (And:ops) = eval' (B (x && y):stack) v t ops
eval' (B y:B x:stack) v t (Or:ops) = eval' (B (x || y):stack) v t ops
eval' (B x:stack) v t (Not:ops) = eval' (B (not x):stack) v t ops
eval' stack v t (Var name:ops) = eval' (v name:stack) v t ops
eval' stack v t (Tok tok:ops) = eval' (t tok:stack) v t ops
-- Have tokens lower to variables, to make things easier for the callee.
eval' stack v t (Tok tok:ops) | t tok /= B False = eval' (t tok:stack) v t ops
eval' stack v t (Tok (Ident name):ops) = eval' (v name:stack) v t ops
-- TODO: How should I handle ratios?
eval' (N y:N x:stack) v t (MkRatio:ops) = eval' (Ratio x y:stack) v t ops
eval' (N y:N x:stack) v t (Less:ops) = eval' (B (x < y):stack) v t ops

M stylist.cabal => stylist.cabal +2 -1
@@ 85,5 85,6 @@ test-suite test-stylist
  other-modules:        Data.CSS.Syntax.StyleSheet, Data.CSS.Syntax.Selector, Data.CSS.Style
  build-depends:       base >=4.9 && <4.10, css-syntax >=0.1 && <0.2, text,
                        unordered-containers >= 0.2 && <0.3, hashable,
                        network-uri >= 2.6 && <2.7, hspec, QuickCheck
                        network-uri >= 2.6 && <2.7, hspec, QuickCheck,
                        scientific >= 0.3 && <1.0
  ghc-options: -Wall

M test/Test.hs => test/Test.hs +68 -4
@@ 5,9 5,10 @@ import Test.Hspec
import Data.HashMap.Strict
import Data.Maybe (fromJust)
import Network.URI
import Data.Scientific (toRealFloat)

import Data.CSS.Syntax.Tokens
import Data.CSS.Syntax.StyleSheet
import Data.CSS.Syntax.StyleSheet (parse, StyleSheet(..), TrivialStyleSheet(..), scanAtRule, scanValue)
import Data.CSS.Syntax.Selector

import Data.CSS.Style.Common


@@ 16,7 17,7 @@ import Data.CSS.Style.Selector.Interpret
import Data.CSS.Style

import Data.CSS.Preprocessor.Conditions
import Data.CSS.Preprocessor.Conditions.Expr (Datum(..))
import Data.CSS.Preprocessor.Conditions.Expr (Datum(..), Op(..), parse', eval)

main :: IO ()
main = hspec spec


@@ 360,7 361,7 @@ spec = do
            vars `shouldBe` [("--link", [Hash HId "f00"])]
            style ! "color" `shouldBe` [Hash HId "f00"]
    describe "Conditional @rules" $ do
        it "can handle normal rules" $ do
        it "handles normal rules" $ do
            let TrivialStyleSheet styles = resolve' $ parse conditional "a {color: green}"
            styles `shouldBe` [StyleRule (Element [Tag "a"]) [("color", [Ident "green"])] ""]



@@ 375,6 376,60 @@ spec = do

            let TrivialStyleSheet styles = resolve' $ parse conditional "@font {} a {color: green}"
            styles `shouldBe` [StyleRule (Element [Tag "a"]) [("color", [Ident "green"])] ""]
        it "handles @document" $ do
            let TrivialStyleSheet styles = resolve' $ parse conditional "@document url(about:blank) { a {color: green} }"
            styles `shouldBe` [StyleRule (Element []) [] "", StyleRule (Element [Tag "a"]) [("color", [Ident "green"])] ""]
            let TrivialStyleSheet styles = resolve' $ parse conditional "@document url(about:credits) { a {color: red} }"
            styles `shouldBe` []
            let TrivialStyleSheet styles = resolve' $ parse conditional "@document url-prefix('about:') { a {color: green} }"
            styles `shouldBe` [StyleRule (Element []) [] "", StyleRule (Element [Tag "a"]) [("color", [Ident "green"])] ""]
            let TrivialStyleSheet styles = resolve' $ parse conditional "@document url-prefix('https:') { a {color: red} }"
            styles `shouldBe` []
            let TrivialStyleSheet styles = resolve' $ parse conditional "@document media-document('test') { a {color: green} }"
            styles `shouldBe` [StyleRule (Element []) [] "", StyleRule (Element [Tag "a"]) [("color", [Ident "green"])] ""]
            let TrivialStyleSheet styles = resolve' $ parse conditional "@document media-document('other') { a {color: red} }"
            styles `shouldBe` []
        it "handles @media" $ do
            let TrivialStyleSheet styles = resolve' $ parse conditional "@media test { a {color: green} }"
            styles `shouldBe` [StyleRule (Element []) [] "", StyleRule (Element [Tag "a"]) [("color", [Ident "green"])] ""]
            let TrivialStyleSheet styles = resolve' $ parse conditional "@media screen { a {color: red} }"
            styles `shouldBe` []

            let TrivialStyleSheet styles = resolve' $ parse conditional "@media test or screen { a {color: green} }"
            styles `shouldBe` [StyleRule (Element []) [] "", StyleRule (Element [Tag "a"]) [("color", [Ident "green"])] ""]
            let TrivialStyleSheet styles = resolve' $ parse conditional "@media test or test {a {color: green} }"
            styles `shouldBe` [StyleRule (Element []) [] "", StyleRule (Element [Tag "a"]) [("color", [Ident "green"])] ""]
            let TrivialStyleSheet styles = resolve' $ parse conditional "@media screen or screen {a {color: red} }"
            styles `shouldBe` []
            let TrivialStyleSheet styles = resolve' $ parse conditional "@media screen or test {a {color: green} }"
            styles `shouldBe` [StyleRule (Element []) [] "", StyleRule (Element [Tag "a"]) [("color", [Ident "green"])] ""]

            let TrivialStyleSheet styles = resolve' $ parse conditional "@media test and screen { a {color: red} }"
            styles `shouldBe` []
            let TrivialStyleSheet styles = resolve' $ parse conditional "@media test and test { a {color: green} }"
            styles `shouldBe` [StyleRule (Element []) [] "", StyleRule (Element [Tag "a"]) [("color", [Ident "green"])] ""]
            let TrivialStyleSheet styles = resolve' $ parse conditional "@media screen and screen { a {color: red} }"
            styles `shouldBe` []
            let TrivialStyleSheet styles = resolve' $ parse conditional "@media screen and test {a {color: red} }"
            styles `shouldBe` []

            let TrivialStyleSheet styles = resolve' $ parse conditional "@media 2 < 3 { a {color: green} }"
            styles `shouldBe` [StyleRule (Element []) [] "", StyleRule (Element [Tag "a"]) [("color", [Ident "green"])] ""]
            let TrivialStyleSheet styles = resolve' $ parse conditional "@media 2 < 2 { a {color: red} }"
            styles `shouldBe` []
            let TrivialStyleSheet styles = resolve' $ parse conditional "@media 2 < 1 { a {color: red} }"
            styles `shouldBe` []
        it "handles @import" $ do
            let styles = parse conditional "@import url(about:style.css);"
            extractImports' styles `shouldBe` [URI "about:" Nothing "style.css" "" ""]
            let styles = parse conditional "@import 'about:style.css';"
            extractImports' styles `shouldBe` [URI "about:" Nothing "style.css" "" ""]

            let styles = parse conditional "@import url(about:style.css) test;"
            extractImports' styles `shouldBe` [URI "about:" Nothing "style.css" "" ""]
            let styles = parse conditional "@import url(about:style.css) screen;"
            extractImports' styles `shouldBe` []
        -- TODO @supports is harder to test

styleIndex :: StyleIndex
styleIndex = new


@@ 388,4 443,13 @@ linkStyle :: TrivialStyleSheet
linkStyle = TrivialStyleSheet [sampleRule]
sampleRule :: StyleRule
sampleRule = StyleRule (Element [Tag "a"]) [("color", [Ident "green"])] ""
resolve' = resolve (\_ -> B False) (\_ -> B False) emptyStyle
resolve' = resolve (\var -> B (var == "test")) evalToken emptyStyle
    where
        evalToken (Number _ (NVInteger x)) = N $ fromInteger x
        evalToken (Number _ (NVNumber x)) = N $ toRealFloat x
        evalToken _ = B False
extractImports' = extractImports (\var -> B (var == "test")) evalToken
    where
        evalToken (Number _ (NVInteger x)) = N $ fromInteger x
        evalToken (Number _ (NVNumber x)) = N $ toRealFloat x
        evalToken _ = B False