~alcinnz/haskell-stylist

ref: e4585f4a4b180fe20d62e804b35b100398e717ee haskell-stylist/src/Data/CSS/Preprocessor/PsuedoClasses.hs -rw-r--r-- 6.6 KiB
e4585f4a — Adrian Cochrane Add more version bounds! 1 year, 6 months ago
                                                                                
3df8d43c Adrian Cochrane
86efaacd Adrian Cochrane
cae4f2e0 Adrian Cochrane
b63c1787 Adrian Cochrane
3df8d43c Adrian Cochrane
c33014ef Adrian Cochrane
15b2412e Adrian Cochrane
3df8d43c Adrian Cochrane
86efaacd Adrian Cochrane
b63c1787 Adrian Cochrane
3df8d43c Adrian Cochrane
86efaacd Adrian Cochrane
51a99e75 Adrian Cochrane
3df8d43c Adrian Cochrane
51a99e75 Adrian Cochrane
cae4f2e0 Adrian Cochrane
3df8d43c Adrian Cochrane
cae4f2e0 Adrian Cochrane
3df8d43c Adrian Cochrane
86efaacd Adrian Cochrane
cae4f2e0 Adrian Cochrane
3df8d43c Adrian Cochrane
86efaacd Adrian Cochrane
15b2412e Adrian Cochrane
d2460bd3 Adrian Cochrane
86efaacd Adrian Cochrane
51a99e75 Adrian Cochrane
cae4f2e0 Adrian Cochrane
ed0c2ba1 Adrian Cochrane
3df8d43c Adrian Cochrane
51a99e75 Adrian Cochrane
cae4f2e0 Adrian Cochrane
51a99e75 Adrian Cochrane
cae4f2e0 Adrian Cochrane
86efaacd Adrian Cochrane
cae4f2e0 Adrian Cochrane
86efaacd Adrian Cochrane
51a99e75 Adrian Cochrane
b63c1787 Adrian Cochrane
51a99e75 Adrian Cochrane
86efaacd Adrian Cochrane
cae4f2e0 Adrian Cochrane
86efaacd Adrian Cochrane
b63c1787 Adrian Cochrane
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
{-# LANGUAGE OverloadedStrings #-}
-- | Lowers psuedoclasses to rawer syntactic forms.
module Data.CSS.Preprocessor.PsuedoClasses(LowerPsuedoClasses(..),
    psuedoClassesFilter, htmlPsuedoFilter,
    addRewrite, addRewrite', addTest, addContains, PropertyTest,
    addPsuedoEl, addNamespace) where

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

import Data.Text as Txt
import Data.Maybe (fromMaybe, listToMaybe)
import Data.HashMap.Lazy as HM
import Data.Function ((&))
import Data.List as L (intercalate)

--------
---- core
--------
type RewriteMap = HashMap Text ([Token] -> [SimpleSelector])
data LowerPsuedoClasses s = LowerPsuedoClasses {
    inner :: s,
    rewriteRules :: RewriteMap,
    psuedoEls :: [Text],
    namespaces :: HashMap Text Text
}

instance StyleSheet s => StyleSheet (LowerPsuedoClasses s) where
    setPriority p self = self { inner = setPriority p $ inner self }
    addRule self rule = self { inner = addRule (inner self) $ lowerRule self rule }

    addAtRule self "namespace" (Ident ns:toks) | (Url url:toks') <- skipSpace toks =
        (addNamespace ns url self, toks')
    addAtRule self name toks = let (inner', toks') = addAtRule (inner self) name toks
        in (self { inner = inner' }, toks')

lowerRule :: LowerPsuedoClasses t -> StyleRule -> StyleRule
lowerRule self@(LowerPsuedoClasses { psuedoEls = psuedos }) (StyleRule sel props "")
    | Just pseudo <- extractPseudoEl psuedos sel =
        lowerRule (addRewrite' pseudo [] self) $ StyleRule sel props pseudo
lowerRule LowerPsuedoClasses { namespaces = ns, rewriteRules = rewrites } (StyleRule sel props pseudoel) =
    StyleRule (lowerSelector ns rewrites sel) props pseudoel

extractPseudoEl :: [Text] -> Selector -> Maybe Text
extractPseudoEl ps (Element sel) = extractPseudoEl' ps sel
extractPseudoEl ps (Child _ sel) = extractPseudoEl' ps sel
extractPseudoEl ps (Descendant _ sel) = extractPseudoEl' ps sel
extractPseudoEl ps (Adjacent _ sel) = extractPseudoEl' ps sel
extractPseudoEl ps (Sibling _ sel) = extractPseudoEl' ps sel
extractPseudoEl' :: [Text] -> [SimpleSelector] -> Maybe Text
extractPseudoEl' ps sel = listToMaybe [p | Psuedoclass p [] <- sel, p `Prelude.elem` ps]

lowerSelector :: HashMap Text Text -> RewriteMap -> Selector -> Selector
lowerSelector ns rewrites (Element sel') = Element $ lowerSelector' ns rewrites sel'
lowerSelector ns rewrites (Child p sel') =
    Child (lowerSelector ns rewrites p) $ lowerSelector' ns rewrites sel'
lowerSelector ns rewrites (Descendant p sel') =
    Descendant (lowerSelector ns rewrites p) $ lowerSelector' ns rewrites sel'
lowerSelector ns rewrites (Adjacent sib sel') =
    Adjacent (lowerSelector ns rewrites sib) $ lowerSelector' ns rewrites sel'
lowerSelector ns rewrites (Sibling sib sel') =
    Sibling (lowerSelector ns rewrites sib) $ lowerSelector' ns rewrites sel'

lowerSelector' :: HashMap Text Text -> RewriteMap -> [SimpleSelector] -> [SimpleSelector]
lowerSelector' namespaces' rewrites (Namespace ns:sels) =
    Namespace (fromMaybe "about:invalid" $ HM.lookup ns namespaces') : lowerSelector' namespaces' rewrites sels
lowerSelector' ns rewrites (Psuedoclass name args:sels)
    | Just value <- name `HM.lookup` rewrites = value args ++ lowerSelector' ns rewrites sels
lowerSelector' ns rewrites (sel:sels) = sel : lowerSelector' ns rewrites sels
lowerSelector' _ _ [] = []

--------
---- constructors
--------
psuedoClassesFilter :: StyleSheet s => s -> LowerPsuedoClasses s
psuedoClassesFilter s = LowerPsuedoClasses s HM.empty ["before", "after"] HM.empty

addPsuedoEl :: Text -> LowerPsuedoClasses s -> LowerPsuedoClasses s
addPsuedoEl ps self = self { psuedoEls = psuedoEls self ++ Txt.words ps }

addRewrite :: Text -> Text -> LowerPsuedoClasses s -> LowerPsuedoClasses s
addRewrite name sel self = addRewrite' name (tokenize sel) self
addRewrite' :: Text -> [Token] -> LowerPsuedoClasses s -> LowerPsuedoClasses s
addRewrite' name sel self =
    addTest' name (\args -> [Psuedoclass "where" $ spliceArgs sel args]) self
  where
    spliceArgs [] [] = []
    spliceArgs (Ident "_":toks) (arg:args) = arg : spliceArgs toks args
    spliceArgs (tok:toks) args = tok : spliceArgs toks args
    spliceArgs _ _ = [Ident "\tfail"]

addContains :: Text -> [Int] -> LowerPsuedoClasses s -> LowerPsuedoClasses s
addContains name path self =
    addRewrite' name (L.intercalate [Comma] $ buildSelector path [Colon, Ident "root"]) self
  where
    buildSelector (p:ps) prefix =
        let prefix' = prefix ++ [Delim '>', Colon, Function "nth-child", num p, RightParen]
        in prefix' : buildSelector ps prefix'
    buildSelector [] _ = []
    num x = Number (Txt.pack $ show x) $ NVInteger (toInteger x)

addTest :: Text -> Maybe Text -> Text -> PropertyFunc -> LowerPsuedoClasses s -> LowerPsuedoClasses s
addTest name ns attr test self = addTest' name (noArg [Property ns attr $ Callback test]) self
    where
        noArg sel [] = sel
        noArg _ _ = [Psuedoclass " fail" []]
addTest' :: Text -> ([Token] -> [SimpleSelector]) -> LowerPsuedoClasses s -> LowerPsuedoClasses s
addTest' name sel self = self {rewriteRules = insert name sel $ rewriteRules self }

addNamespace :: Text -> Text -> LowerPsuedoClasses s -> LowerPsuedoClasses s
addNamespace ns uri self = self { namespaces = insert ns uri $ namespaces self }

htmlPsuedoFilter :: StyleSheet s => s -> LowerPsuedoClasses s
htmlPsuedoFilter s = psuedoClassesFilter s &
    addRewrite "link" "[href]" &
    addRewrite "any-link" "[href]" &
    addRewrite "blank" "[value=''], :not([value])" &
    addRewrite "checked" "[checked]" &
    addRewrite "dir" "[dir=_], [dir=_] *" &
    addRewrite "disabled" "[disabled]" &
    addRewrite "enabled" ":not([disabled])" &
    addRewrite "first-child" ":nth-child(1)" &
    addRewrite "first-of-type" ":nth-of-type(1)" &
    addRewrite "indeterminate" "[indeterminate]" &
    addRewrite "lang" "[lang|=_], [lang|=_] *" &
    -- Not sure if I ever really want to support these, but might as well list them.
    -- Requires more data to be fed to the core CSS engine.
    addRewrite "last-child" ":nth-last-child(1)" &
    addRewrite "last-of-type" ":nth-last-of-type(1)" &
    addRewrite "only-child" ":nth-child(1):nth-last-child(1)" &
    addRewrite "only-of-type" ":nth-of-type(1):nth-last-of-type(1)" &
    -- No issue with remainder.
    addRewrite "optional" ":not([required])" &
    addRewrite "placeholder-shown" "[value=''][placeholder], [placeholder]:not([value])" &
    addRewrite "readonly" "[readonly], [disabled]" &
    addRewrite "read-write" ":not([readonly]):not([disabled])" &
    addRewrite "required" "[required]" &
    addRewrite "scope" ":root" &
    addRewrite "root" "html"