Browse code
Init
fiddlerwoaroof authored on 07/02/2017 09:25:44
Showing 12 changed files
Showing 12 changed files
- .gitignore
- COPYING
- LICENSE
- README.md
- Setup.hs
- app/Main.hs
- cssparser.cabal
- src/CssParser.hs
- stack.yaml
- style.css
- test/CssParserTest.hs
- tmp.css
0 | 2 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,29 @@ |
1 |
+Copyright (c) 2015 Edward Langley |
|
2 |
+All rights reserved. |
|
3 |
+ |
|
4 |
+Redistribution and use in source and binary forms, with or without |
|
5 |
+modification, are permitted provided that the following conditions |
|
6 |
+are met: |
|
7 |
+ |
|
8 |
+Redistributions of source code must retain the above copyright notice, |
|
9 |
+this list of conditions and the following disclaimer. |
|
10 |
+ |
|
11 |
+Redistributions in binary form must reproduce the above copyright |
|
12 |
+notice, this list of conditions and the following disclaimer in the |
|
13 |
+documentation and/or other materials provided with the distribution. |
|
14 |
+ |
|
15 |
+Neither the name of the project's author nor the names of its |
|
16 |
+contributors may be used to endorse or promote products derived from |
|
17 |
+this software without specific prior written permission. |
|
18 |
+ |
|
19 |
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
20 |
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
21 |
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
|
22 |
+FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
23 |
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
24 |
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED |
|
25 |
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
|
26 |
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
|
27 |
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
|
28 |
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
|
29 |
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
0 | 30 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,30 @@ |
1 |
+Copyright Author name here (c) 2017 |
|
2 |
+ |
|
3 |
+All rights reserved. |
|
4 |
+ |
|
5 |
+Redistribution and use in source and binary forms, with or without |
|
6 |
+modification, are permitted provided that the following conditions are met: |
|
7 |
+ |
|
8 |
+ * Redistributions of source code must retain the above copyright |
|
9 |
+ notice, this list of conditions and the following disclaimer. |
|
10 |
+ |
|
11 |
+ * Redistributions in binary form must reproduce the above |
|
12 |
+ copyright notice, this list of conditions and the following |
|
13 |
+ disclaimer in the documentation and/or other materials provided |
|
14 |
+ with the distribution. |
|
15 |
+ |
|
16 |
+ * Neither the name of Author name here nor the names of other |
|
17 |
+ contributors may be used to endorse or promote products derived |
|
18 |
+ from this software without specific prior written permission. |
|
19 |
+ |
|
20 |
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
21 |
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
22 |
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
23 |
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
24 |
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
25 |
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
26 |
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
27 |
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
28 |
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
29 |
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
30 |
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
0 | 31 |
\ No newline at end of file |
0 | 3 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,42 @@ |
1 |
+{- |
|
2 |
+ -Copyright (c) 2015 Edward Langley |
|
3 |
+ -All rights reserved. |
|
4 |
+ - |
|
5 |
+ -Redistribution and use in source and binary forms, with or without |
|
6 |
+ -modification, are permitted provided that the following conditions |
|
7 |
+ -are met: |
|
8 |
+ - |
|
9 |
+ -Redistributions of source code must retain the above copyright notice, |
|
10 |
+ -this list of conditions and the following disclaimer. |
|
11 |
+ - |
|
12 |
+ -Redistributions in binary form must reproduce the above copyright |
|
13 |
+ -notice, this list of conditions and the following disclaimer in the |
|
14 |
+ -documentation and/or other materials provided with the distribution. |
|
15 |
+ - |
|
16 |
+ -Neither the name of the project's author nor the names of its |
|
17 |
+ -contributors may be used to endorse or promote products derived from |
|
18 |
+ -this software without specific prior written permission. |
|
19 |
+ - |
|
20 |
+ -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
21 |
+ -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
22 |
+ -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
|
23 |
+ -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
24 |
+ -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
25 |
+ -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED |
|
26 |
+ -TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
|
27 |
+ -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
|
28 |
+ -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
|
29 |
+ -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
|
30 |
+ -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
31 |
+ -} |
|
32 |
+module Main where |
|
33 |
+ |
|
34 |
+import Text.Parsec |
|
35 |
+import System.IO |
|
36 |
+import CssParser |
|
37 |
+import System.Environment |
|
38 |
+ |
|
39 |
+main = do |
|
40 |
+ args <- getArgs |
|
41 |
+ dat <- readFile (args !! 0) |
|
42 |
+ putStrLn $ show $ parseCSS dat |
0 | 43 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,47 @@ |
1 |
+name: cssparser |
|
2 |
+version: 0.1.0.0 |
|
3 |
+-- synopsis: |
|
4 |
+-- description: |
|
5 |
+homepage: https://github.com/githubuser/cssparser#readme |
|
6 |
+license: BSD3 |
|
7 |
+license-file: LICENSE |
|
8 |
+author: Author name here |
|
9 |
+maintainer: example@example.com |
|
10 |
+copyright: 2017 Author name here |
|
11 |
+category: Web |
|
12 |
+build-type: Simple |
|
13 |
+extra-source-files: README.md |
|
14 |
+cabal-version: >=1.10 |
|
15 |
+ |
|
16 |
+library |
|
17 |
+ hs-source-dirs: src |
|
18 |
+ exposed-modules: CssParser |
|
19 |
+ build-depends: base >= 4.7 && < 5 |
|
20 |
+ , containers >= 0.5.7 |
|
21 |
+ , parsec == 3.1.11 |
|
22 |
+ default-language: Haskell2010 |
|
23 |
+ |
|
24 |
+executable cssparser-exe |
|
25 |
+ hs-source-dirs: app |
|
26 |
+ main-is: Main.hs |
|
27 |
+ ghc-options: -threaded -rtsopts -with-rtsopts=-N |
|
28 |
+ build-depends: base |
|
29 |
+ , parsec == 3.1.11 |
|
30 |
+ , cssparser |
|
31 |
+ default-language: Haskell2010 |
|
32 |
+ |
|
33 |
+test-suite cssparser-test |
|
34 |
+ type: exitcode-stdio-1.0 |
|
35 |
+ hs-source-dirs: test |
|
36 |
+ main-is: CssParserTest.hs |
|
37 |
+ build-depends: base |
|
38 |
+ , parsec == 3.1.11 |
|
39 |
+ , containers >= 0.5.7 |
|
40 |
+ , HUnit >= 1.3 |
|
41 |
+ , cssparser |
|
42 |
+ ghc-options: -threaded -rtsopts -with-rtsopts=-N |
|
43 |
+ default-language: Haskell2010 |
|
44 |
+ |
|
45 |
+source-repository head |
|
46 |
+ type: git |
|
47 |
+ location: https://github.com/fiddlerwoaroof/cssparser |
0 | 48 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,475 @@ |
1 |
+{- |
|
2 |
+ -Copyright (c) 2015 Edward Langley |
|
3 |
+ -All rights reserved. |
|
4 |
+ - |
|
5 |
+ -Redistribution and use in source and binary forms, with or without |
|
6 |
+ -modification, are permitted provided that the following conditions |
|
7 |
+ -are met: |
|
8 |
+ - |
|
9 |
+ -Redistributions of source code must retain the above copyright notice, |
|
10 |
+ -this list of conditions and the following disclaimer. |
|
11 |
+ - |
|
12 |
+ -Redistributions in binary form must reproduce the above copyright |
|
13 |
+ -notice, this list of conditions and the following disclaimer in the |
|
14 |
+ -documentation and/or other materials provided with the distribution. |
|
15 |
+ - |
|
16 |
+ -Neither the name of the project's author nor the names of its |
|
17 |
+ -contributors may be used to endorse or promote products derived from |
|
18 |
+ -this software without specific prior written permission. |
|
19 |
+ - |
|
20 |
+ -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
21 |
+ -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
22 |
+ -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
|
23 |
+ -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
24 |
+ -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
25 |
+ -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED |
|
26 |
+ -TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
|
27 |
+ -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
|
28 |
+ -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
|
29 |
+ -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
|
30 |
+ -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
31 |
+ -} |
|
32 |
+ |
|
33 |
+{-# LANGUAGE RankNTypes,KindSignatures #-} |
|
34 |
+ |
|
35 |
+module CssParser |
|
36 |
+ where |
|
37 |
+ |
|
38 |
+import Prelude |
|
39 |
+import Control.Monad |
|
40 |
+import qualified Text.Parsec as NP |
|
41 |
+import Text.ParserCombinators.Parsec |
|
42 |
+import Data.Functor.Identity() |
|
43 |
+import Text.Parsec.Prim() |
|
44 |
+import Data.Map hiding (foldr,null,map) |
|
45 |
+import Data.Maybe |
|
46 |
+ |
|
47 |
+data URL = URLFunc String | |
|
48 |
+ URLString String | |
|
49 |
+ URLMedia [String] URL |
|
50 |
+ deriving (Show, Eq) |
|
51 |
+ |
|
52 |
+ |
|
53 |
+type Query = [String] |
|
54 |
+data CSSDeclaration = CSSDeclaration CSSSelector CSSBody | |
|
55 |
+ PageDeclaration (Maybe String) CSSBody | |
|
56 |
+ Charset String | |
|
57 |
+ Import URL | |
|
58 |
+ MediaQuery Query CSSDeclaration | |
|
59 |
+ DeclarationList [CSSDeclaration] |
|
60 |
+ deriving (Show,Eq) |
|
61 |
+ |
|
62 |
+ |
|
63 |
+type CSSBody = Map String String |
|
64 |
+ |
|
65 |
+data CSSSelector = |
|
66 |
+ Ident String | |
|
67 |
+ TagName String | |
|
68 |
+ Pseudo String | |
|
69 |
+ Attribute String String | |
|
70 |
+ Id String | |
|
71 |
+ Class String | |
|
72 |
+ Sibling CSSSelector CSSSelector | -- These are all XRelation Head Tail |
|
73 |
+ DirectChild CSSSelector CSSSelector | |
|
74 |
+ Child CSSSelector CSSSelector | |
|
75 |
+ SelectorList [CSSSelector] |
|
76 |
+ deriving (Show,Eq) |
|
77 |
+ |
|
78 |
+matchUpToX :: (Eq a,Num a) => GenParser Char st Char -> a -> GenParser Char st String |
|
79 |
+matchUpToX p n = scan id n |
|
80 |
+ where |
|
81 |
+ scan f x = if x == 0 |
|
82 |
+ then return (f []) |
|
83 |
+ else do { |
|
84 |
+ nxt <- p; |
|
85 |
+ scan (\tail_ -> f (nxt:tail_)) (x-1) |
|
86 |
+ } <|> return (f []) |
|
87 |
+ |
|
88 |
+match1ToX :: (Eq a,Num a) => GenParser Char st Char -> a -> GenParser Char st String |
|
89 |
+match1ToX p n = do |
|
90 |
+ result <- p |
|
91 |
+ tail_ <- matchUpToX p (n-1) |
|
92 |
+ return (result:tail_) |
|
93 |
+ |
|
94 |
+-- Char classes |
|
95 |
+h :: GenParser Char st Char |
|
96 |
+h = hexDigit |
|
97 |
+ |
|
98 |
+nonascii :: GenParser Char st Char |
|
99 |
+nonascii = satisfy isNonAscii |
|
100 |
+ where |
|
101 |
+ isNonAscii a = '\240' <= a && a <= '\1114111' |
|
102 |
+ |
|
103 |
+unicode :: GenParser Char st String |
|
104 |
+unicode = do |
|
105 |
+ char '\\' |
|
106 |
+ result <- match1ToX h (6 :: Integer) |
|
107 |
+ spaces |
|
108 |
+ return result |
|
109 |
+ |
|
110 |
+escape :: GenParser Char st String |
|
111 |
+escape = (liftM ('\\':) $ try unicode) <|> do |
|
112 |
+ char '\\' <:> (many1 $ noneOf "\r\n\f0123456789abcdef") |
|
113 |
+ |
|
114 |
+nmstart :: GenParser Char st Char |
|
115 |
+nmstart = oneOf "-" <|> letter |
|
116 |
+ |
|
117 |
+nmchar :: GenParser Char st Char |
|
118 |
+nmchar = oneOf "_-" <|> alphaNum |
|
119 |
+ |
|
120 |
+ |
|
121 |
+space_ :: GenParser Char st Char |
|
122 |
+space_ = oneOf " \t\r\n\f" |
|
123 |
+ |
|
124 |
+s :: GenParser Char st String |
|
125 |
+s = many1 space_ |
|
126 |
+ |
|
127 |
+w :: GenParser Char st String |
|
128 |
+w = many space_ |
|
129 |
+ |
|
130 |
+nl :: GenParser Char st String |
|
131 |
+nl = (string "\n" <|> (try $ string "\r\n") <|> string "\r" <|> string "\f") >> return "\n" |
|
132 |
+ |
|
133 |
+genBadString :: Char -> GenParser Char st String |
|
134 |
+genBadString c = do |
|
135 |
+ char c |
|
136 |
+ result <- liftM (foldr (++) []) $ |
|
137 |
+ many $ (many1 $ noneOf (c:"\n\r\f")) <|> (char '\\' >> nl) <|> escape |
|
138 |
+ char '\\' |
|
139 |
+ anyChar |
|
140 |
+ return result |
|
141 |
+ |
|
142 |
+badstring :: GenParser Char st String |
|
143 |
+badstring = genBadString '"' <|> genBadString '\'' |
|
144 |
+ |
|
145 |
+string_ :: GenParser Char st String |
|
146 |
+string_ = do |
|
147 |
+ a <- char '"' <|> char '\'' |
|
148 |
+ result <- liftM (foldr (++) []) $ many $ do |
|
149 |
+ res <- (try $ char '\\' <:> nl) <|> (try escape) <|> (liftM (:[]) $ noneOf (a:"\n\r\f")) |
|
150 |
+ return res |
|
151 |
+ char a |
|
152 |
+ return result |
|
153 |
+ |
|
154 |
+-- badcomment, baduri, comment undefined |
|
155 |
+ |
|
156 |
+-- tested |
|
157 |
+ident :: GenParser Char st String |
|
158 |
+ident = do |
|
159 |
+ head_ <- optionMaybe $ string "-" |
|
160 |
+ result <- nmstart |
|
161 |
+ tail_ <- many nmchar |
|
162 |
+ return $ (fromMaybe "" head_) ++ (result:tail_) |
|
163 |
+ |
|
164 |
+--tested |
|
165 |
+name :: GenParser Char st String |
|
166 |
+name = many1 nmchar |
|
167 |
+ |
|
168 |
+-- tested |
|
169 |
+num :: GenParser Char st String |
|
170 |
+num = floating <|> many1 digit |
|
171 |
+ where |
|
172 |
+ floating = try $ do |
|
173 |
+ radix <- many digit |
|
174 |
+ mantissa <- char '.' <:> many1 digit |
|
175 |
+ return $ radix ++ mantissa |
|
176 |
+ |
|
177 |
+cdo :: GenParser Char st String |
|
178 |
+cdo = string "<!--" |
|
179 |
+ |
|
180 |
+cdc :: GenParser Char st String |
|
181 |
+cdc = string "-->" |
|
182 |
+ |
|
183 |
+-- TODO: figger out if / should be here |
|
184 |
+url :: GenParser Char st String |
|
185 |
+url = liftM (foldr (++) []) $ many $ many1 (oneOf "!#$%&*-~" <|> nonascii) <|> escape |
|
186 |
+ |
|
187 |
+atEnd :: forall b s u (m :: * -> *) t. (Show t, NP.Stream s m t) => NP.ParsecT s u m b -> NP.ParsecT s u m b |
|
188 |
+atEnd p = p >>= ((eof >>) . return) |
|
189 |
+ |
|
190 |
+-- partially tested |
|
191 |
+urlParse :: GenParser Char st URL |
|
192 |
+urlParse = do |
|
193 |
+ result <- between (string "url(" >> spaces) (char ')') |
|
194 |
+ (withSpace $ string_ <|> (many1 $ oneOf "=?.,:/" <|> alphaNum) <|> url) |
|
195 |
+ |
|
196 |
+ return $ URLFunc result |
|
197 |
+ |
|
198 |
+stylesheet :: GenParser Char st CSSDeclaration |
|
199 |
+stylesheet = do -- TODO: allow the letters to be escaped in these strings |
|
200 |
+ chrset <- charset |
|
201 |
+ let cset = case chrset of |
|
202 |
+ Nothing -> []; |
|
203 |
+ Just x -> [x] |
|
204 |
+ many $ s <|> cdo <|> cdc |
|
205 |
+ |
|
206 |
+ imports <- liftM (foldr (++) []) $ many $ do |
|
207 |
+ imports <- many1 $ import_ |
|
208 |
+ many ((cdo >> spaces) <|> (cdc >> spaces)) |
|
209 |
+ return $ imports |
|
210 |
+ |
|
211 |
+ result <- many $ ruleset <|> media <|> page |
|
212 |
+ many $ (cdo >> many s) <|> (cdc >> many s) |
|
213 |
+ |
|
214 |
+ return $ DeclarationList $ cset ++ imports ++ result |
|
215 |
+ |
|
216 |
+page :: GenParser Char st CSSDeclaration |
|
217 |
+page = do |
|
218 |
+ string "@page" |
|
219 |
+ spaces |
|
220 |
+ pseudo_ <- optionMaybe pseudo_page |
|
221 |
+ |
|
222 |
+ body <- rulebody |
|
223 |
+ return $ PageDeclaration pseudo_ $ fromList body |
|
224 |
+ |
|
225 |
+pseudo_page :: GenParser Char st String |
|
226 |
+pseudo_page = char ':' >> ident >>= (spaces >>) . return |
|
227 |
+ |
|
228 |
+ |
|
229 |
+charset :: GenParser Char st (Maybe CSSDeclaration) |
|
230 |
+charset = optionMaybe $ do |
|
231 |
+ try $ string "@charset " |
|
232 |
+ result <- string_ |
|
233 |
+ char ';' |
|
234 |
+ return $ Charset result |
|
235 |
+ |
|
236 |
+medium :: GenParser Char st String |
|
237 |
+medium = ident >>= (\q -> spaces >> return q) |
|
238 |
+ |
|
239 |
+mediaList :: GenParser Char st [String] |
|
240 |
+mediaList = sepBy medium (char ',' >> spaces) |
|
241 |
+ |
|
242 |
+media :: GenParser Char st CSSDeclaration |
|
243 |
+media = do |
|
244 |
+ try $ string "@media" |
|
245 |
+ spaces |
|
246 |
+ query <- mediaList |
|
247 |
+ body <- between (char '{') (char '}') $ do |
|
248 |
+ spaces |
|
249 |
+ rules <- many ruleset |
|
250 |
+ spaces |
|
251 |
+ return rules |
|
252 |
+ return $ MediaQuery query $ DeclarationList body |
|
253 |
+ |
|
254 |
+import_ :: GenParser Char st CSSDeclaration |
|
255 |
+import_ = do |
|
256 |
+ try $ string "@import " |
|
257 |
+ spaces |
|
258 |
+ result <- (liftM URLString string_) <|> urlParse |
|
259 |
+ spaces |
|
260 |
+ tail_ <- optionMaybe $ mediaList |
|
261 |
+ char ';' |
|
262 |
+ spaces |
|
263 |
+ return $ Import $ case tail_ of |
|
264 |
+ Nothing -> result |
|
265 |
+ Just x -> URLMedia x result |
|
266 |
+ |
|
267 |
+ |
|
268 |
+ |
|
269 |
+cssRule :: GenParser Char st CSSSelector |
|
270 |
+cssRule = (sepBy1 selector $ char ',' >> spaces) >>= (return . SelectorList) |
|
271 |
+ |
|
272 |
+selector :: GenParser Char st CSSSelector |
|
273 |
+selector = do |
|
274 |
+ result <- simpleSelector |
|
275 |
+ tail_ <- optionMaybe $ parseCombinator result <|> try (parseOCombinator result) |
|
276 |
+ spaces |
|
277 |
+ return $ fromMaybe result tail_ |
|
278 |
+ |
|
279 |
+-- This returns the value of a combinator, ignoring trailing spaces |
|
280 |
+withSpace :: GenParser Char u b -> GenParser Char u b |
|
281 |
+withSpace = (>>= ((spaces >>) . return)) |
|
282 |
+ |
|
283 |
+oneOfWithSpace :: String -> GenParser Char st Char |
|
284 |
+oneOfWithSpace ss = foldr1 (<|>) parsers |
|
285 |
+ where |
|
286 |
+ parsers = map (withSpace . char) ss |
|
287 |
+ |
|
288 |
+operator :: GenParser Char st Char |
|
289 |
+operator = oneOfWithSpace "/," |
|
290 |
+ |
|
291 |
+combinator :: GenParser Char st Char |
|
292 |
+combinator = oneOfWithSpace "+>" |
|
293 |
+ |
|
294 |
+unary_operator :: GenParser Char st Char |
|
295 |
+unary_operator = oneOf "-+" |
|
296 |
+ |
|
297 |
+-- parse an optional combinator |
|
298 |
+parseOCombinator :: CSSSelector -> GenParser Char st CSSSelector |
|
299 |
+parseOCombinator head_ = do |
|
300 |
+ spaces |
|
301 |
+ combine <- optionMaybe $ combinator |
|
302 |
+ sel <- selector |
|
303 |
+ return $ case combine of |
|
304 |
+ Nothing -> Child head_ sel |
|
305 |
+ (Just x) -> case x of |
|
306 |
+ '+' -> Sibling head_ sel |
|
307 |
+ '>' -> DirectChild head_ sel |
|
308 |
+ |
|
309 |
+-- parse a combinator |
|
310 |
+parseCombinator :: CSSSelector -> GenParser Char st CSSSelector |
|
311 |
+parseCombinator head_ = do |
|
312 |
+ combine <- combinator |
|
313 |
+ sel <- selector |
|
314 |
+ return $ case combine of |
|
315 |
+ '+' -> Sibling head_ sel |
|
316 |
+ '>' -> DirectChild head_ sel |
|
317 |
+ |
|
318 |
+ |
|
319 |
+ |
|
320 |
+simpleSelector :: GenParser Char st CSSSelector |
|
321 |
+simpleSelector = do |
|
322 |
+ result <- (try elementName <:> many modifier) <|> many1 modifier |
|
323 |
+ return $ if null $ tail result |
|
324 |
+ then head result |
|
325 |
+ else SelectorList result |
|
326 |
+ |
|
327 |
+modifier :: GenParser Char st CSSSelector |
|
328 |
+modifier = hash <|> class_ <|> attrib <|> pseudo |
|
329 |
+ |
|
330 |
+elementName :: GenParser Char st CSSSelector |
|
331 |
+elementName = liftM TagName $ ident <|> string "*" |
|
332 |
+ |
|
333 |
+hash :: GenParser Char st CSSSelector |
|
334 |
+hash = char '#' >> liftM Id ident |
|
335 |
+ |
|
336 |
+class_ :: GenParser Char st CSSSelector |
|
337 |
+class_ = char '.' >> liftM Class ident |
|
338 |
+ |
|
339 |
+attrib :: GenParser Char st CSSSelector |
|
340 |
+attrib = between (char '[') (char ']') $ do |
|
341 |
+ spaces |
|
342 |
+ key <- ident |
|
343 |
+ spaces |
|
344 |
+ char '=' |
|
345 |
+ value <- ident |
|
346 |
+ spaces |
|
347 |
+ return $ Attribute key value |
|
348 |
+ |
|
349 |
+pseudo :: GenParser Char st CSSSelector |
|
350 |
+pseudo = char ':' >> liftM Pseudo selectorIdent |
|
351 |
+ where |
|
352 |
+ selectorIdent = do |
|
353 |
+ init <- optionMaybe $ string ":" |
|
354 |
+ idnt <- ident |
|
355 |
+ return $ (fromMaybe "" init) ++ idnt |
|
356 |
+ |
|
357 |
+ |
|
358 |
+{-# ANN (<++>) "HLint: ignore" #-} |
|
359 |
+(<++>) :: Monad m => m [a] -> m [a] -> m [a] |
|
360 |
+a <++> b = liftM2 (++) a b |
|
361 |
+ |
|
362 |
+(<:>) :: Monad m => m a -> m [a] -> m [a] |
|
363 |
+a <:> b = liftM2 (:) a b |
|
364 |
+ |
|
365 |
+ |
|
366 |
+ruleset :: GenParser Char st CSSDeclaration |
|
367 |
+ruleset = do |
|
368 |
+ selector_ <- cssRule |
|
369 |
+ spaces |
|
370 |
+ result <- rulebody |
|
371 |
+ return $ CSSDeclaration selector_ $ fromList result |
|
372 |
+ |
|
373 |
+rulebody :: GenParser Char st [(String, String)] |
|
374 |
+rulebody = between (withSpace $ char '{') (withSpace $ char '}') $ do |
|
375 |
+ head_ <- declaration |
|
376 |
+ tail_ <- many $ do |
|
377 |
+ char ';' |
|
378 |
+ spaces |
|
379 |
+ optionMaybe declaration |
|
380 |
+ spaces |
|
381 |
+ return $ (head_:catMaybes tail_) |
|
382 |
+ |
|
383 |
+declaration :: GenParser Char st (String,String) |
|
384 |
+declaration = do |
|
385 |
+ prop <- property |
|
386 |
+ withSpace $ char ':' |
|
387 |
+ value <- expr |
|
388 |
+ optional prio |
|
389 |
+ return (prop,value) |
|
390 |
+ |
|
391 |
+prio :: GenParser Char st String |
|
392 |
+prio = char '!' >> spaces >> string "important" |
|
393 |
+ |
|
394 |
+-- TODO: Still need to implement comments . . . |
|
395 |
+ |
|
396 |
+expr :: GenParser Char st String |
|
397 |
+expr = do |
|
398 |
+ fst <- term |
|
399 |
+ rst <- many $ do |
|
400 |
+ op <- optionMaybe $ liftM (:[]) operator |
|
401 |
+ trm <- term |
|
402 |
+ return $ case op of |
|
403 |
+ Nothing -> trm |
|
404 |
+ Just x -> x ++ (' ':trm) |
|
405 |
+ return $ fst ++ foldr (\x y -> (' ':x++(' ':y))) [] rst |
|
406 |
+ |
|
407 |
+term :: GenParser Char st String |
|
408 |
+term = do |
|
409 |
+ withSpace numerical <|> withSpace uri <|> withSpace string_ <|> withSpace ident <|> hexcolor <|> function |
|
410 |
+ |
|
411 |
+numerical :: GenParser Char st String |
|
412 |
+numerical = do |
|
413 |
+ unop <- optionMaybe unary_operator |
|
414 |
+ value <- withSpace (try percentage) <|> withSpace (try length) <|> withSpace (try ems) <|> withSpace (try exs) |
|
415 |
+ <|> withSpace (try angle) <|> withSpace (try time) <|> withSpace (try freq) <|> withSpace number |
|
416 |
+ return $ case unop of |
|
417 |
+ Just '-' -> '-':value |
|
418 |
+ _ -> value |
|
419 |
+ where |
|
420 |
+ number :: GenParser Char st String |
|
421 |
+ number = num |
|
422 |
+ |
|
423 |
+ percentage :: GenParser Char st String |
|
424 |
+ percentage = num <++> string "%" |
|
425 |
+ |
|
426 |
+ length :: GenParser Char st String |
|
427 |
+ length = num <++> (foldr1 (<|>) $ map string ["px", "cm", "mm", "in", "pt", "pc", "rem", "vw", "vh"]) |
|
428 |
+ |
|
429 |
+ ems :: GenParser Char st String |
|
430 |
+ ems = num <++> string "em" |
|
431 |
+ |
|
432 |
+ exs :: GenParser Char st String |
|
433 |
+ exs = num <++> string "ex" |
|
434 |
+ |
|
435 |
+ angle :: GenParser Char st String |
|
436 |
+ angle = num <++> (string "deg" <|> string "rad" <|> string "grad") |
|
437 |
+ |
|
438 |
+ time :: GenParser Char st String |
|
439 |
+ time = num <++> (string "ms" <|> string "s") |
|
440 |
+ |
|
441 |
+ freq :: GenParser Char st String |
|
442 |
+ freq = num <++> (string "hz" <|> string "khz") |
|
443 |
+ |
|
444 |
+ |
|
445 |
+function :: GenParser Char st String |
|
446 |
+function = do |
|
447 |
+ _ <- ident |
|
448 |
+ _ <- string "(" |
|
449 |
+ spaces |
|
450 |
+ d <- expr |
|
451 |
+ _ <- string ")" |
|
452 |
+ spaces |
|
453 |
+ return $ d |
|
454 |
+ |
|
455 |
+hexcolor :: GenParser Char st String |
|
456 |
+hexcolor = char '#' <:> many1 hexDigit |
|
457 |
+ |
|
458 |
+uri :: GenParser Char st String |
|
459 |
+uri = do |
|
460 |
+ a <- string "url(" |
|
461 |
+ w |
|
462 |
+ result <- try string_ <|> url |
|
463 |
+ w |
|
464 |
+ d <- string ")" |
|
465 |
+ return $ a ++ result ++ d |
|
466 |
+ |
|
467 |
+ |
|
468 |
+property :: GenParser Char st String |
|
469 |
+property = do |
|
470 |
+ result <- ident |
|
471 |
+ spaces |
|
472 |
+ return result |
|
473 |
+ |
|
474 |
+parseCSS :: String -> Either ParseError CSSDeclaration |
|
475 |
+parseCSS = parse stylesheet "(unknown)" |
0 | 476 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,66 @@ |
1 |
+# This file was automatically generated by 'stack init' |
|
2 |
+# |
|
3 |
+# Some commonly used options have been documented as comments in this file. |
|
4 |
+# For advanced use and comprehensive documentation of the format, please see: |
|
5 |
+# http://docs.haskellstack.org/en/stable/yaml_configuration/ |
|
6 |
+ |
|
7 |
+# Resolver to choose a 'specific' stackage snapshot or a compiler version. |
|
8 |
+# A snapshot resolver dictates the compiler version and the set of packages |
|
9 |
+# to be used for project dependencies. For example: |
|
10 |
+# |
|
11 |
+# resolver: lts-3.5 |
|
12 |
+# resolver: nightly-2015-09-21 |
|
13 |
+# resolver: ghc-7.10.2 |
|
14 |
+# resolver: ghcjs-0.1.0_ghc-7.10.2 |
|
15 |
+# resolver: |
|
16 |
+# name: custom-snapshot |
|
17 |
+# location: "./custom-snapshot.yaml" |
|
18 |
+resolver: lts-7.19 |
|
19 |
+ |
|
20 |
+# User packages to be built. |
|
21 |
+# Various formats can be used as shown in the example below. |
|
22 |
+# |
|
23 |
+# packages: |
|
24 |
+# - some-directory |
|
25 |
+# - https://example.com/foo/bar/baz-0.0.2.tar.gz |
|
26 |
+# - location: |
|
27 |
+# git: https://github.com/commercialhaskell/stack.git |
|
28 |
+# commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a |
|
29 |
+# - location: https://github.com/commercialhaskell/stack/commit/e7b331f14bcffb8367cd58fbfc8b40ec7642100a |
|
30 |
+# extra-dep: true |
|
31 |
+# subdirs: |
|
32 |
+# - auto-update |
|
33 |
+# - wai |
|
34 |
+# |
|
35 |
+# A package marked 'extra-dep: true' will only be built if demanded by a |
|
36 |
+# non-dependency (i.e. a user package), and its test suites and benchmarks |
|
37 |
+# will not be run. This is useful for tweaking upstream packages. |
|
38 |
+packages: |
|
39 |
+- '.' |
|
40 |
+# Dependency packages to be pulled from upstream that are not in the resolver |
|
41 |
+# (e.g., acme-missiles-0.3) |
|
42 |
+extra-deps: [] |
|
43 |
+ |
|
44 |
+# Override default flag values for local packages and extra-deps |
|
45 |
+flags: {} |
|
46 |
+ |
|
47 |
+# Extra package databases containing global packages |
|
48 |
+extra-package-dbs: [] |
|
49 |
+ |
|
50 |
+# Control whether we use the GHC we find on the path |
|
51 |
+# system-ghc: true |
|
52 |
+# |
|
53 |
+# Require a specific version of stack, using version ranges |
|
54 |
+# require-stack-version: -any # Default |
|
55 |
+# require-stack-version: ">=1.3" |
|
56 |
+# |
|
57 |
+# Override the architecture used by stack, especially useful on Windows |
|
58 |
+# arch: i386 |
|
59 |
+# arch: x86_64 |
|
60 |
+# |
|
61 |
+# Extra directories used by stack for building |
|
62 |
+# extra-include-dirs: [/path/to/dir] |
|
63 |
+# extra-lib-dirs: [/path/to/dir] |
|
64 |
+# |
|
65 |
+# Allow a newer minor version of GHC than the snapshot specifies |
|
66 |
+# compiler-check: newer-minor |
|
0 | 67 |
\ No newline at end of file |
1 | 68 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,48 @@ |
1 |
+*{ |
|
2 |
+ box-sizing: border-box; |
|
3 |
+} |
|
4 |
+ |
|
5 |
+article::after{ |
|
6 |
+ clear: both; |
|
7 |
+ float: none; |
|
8 |
+ display: block; |
|
9 |
+ content: " "; |
|
10 |
+} |
|
11 |
+ |
|
12 |
+h1, |
|
13 |
+ h2{ |
|
14 |
+ margin-left: -3em; |
|
15 |
+} |
|
16 |
+ |
|
17 |
+h3{ |
|
18 |
+ text-align: center; |
|
19 |
+ border-bottom: thin solid black; |
|
20 |
+} |
|
21 |
+ |
|
22 |
+body{ |
|
23 |
+ margin-left: 12.5em; |
|
24 |
+} |
|
25 |
+ |
|
26 |
+header{ |
|
27 |
+ max-width: 30em; |
|
28 |
+ width: 75vw; |
|
29 |
+} |
|
30 |
+ |
|
31 |
+section#ingredients{ |
|
32 |
+ float: left; |
|
33 |
+ max-width: 10em; |
|
34 |
+ width: 24vw; |
|
35 |
+} |
|
36 |
+ |
|
37 |
+section#instructions{ |
|
38 |
+ float: left; |
|
39 |
+ max-width: 20em; |
|
40 |
+ margin-left: 2vw; |
|
41 |
+ width: 49vw; |
|
42 |
+} |
|
43 |
+ |
|
44 |
+article + article h2{ |
|
45 |
+ margin-top: 1em; |
|
46 |
+ padding-top: 1em; |
|
47 |
+ border-top: thin solid black; |
|
48 |
+} |
|
0 | 49 |
\ No newline at end of file |
1 | 50 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,176 @@ |
1 |
+{- |
|
2 |
+ -Copyright (c) 2015 Edward Langley |
|
3 |
+ -All rights reserved. |
|
4 |
+ - |
|
5 |
+ -Redistribution and use in source and binary forms, with or without |
|
6 |
+ -modification, are permitted provided that the following conditions |
|
7 |
+ -are met: |
|
8 |
+ - |
|
9 |
+ -Redistributions of source code must retain the above copyright notice, |
|
10 |
+ -this list of conditions and the following disclaimer. |
|
11 |
+ - |
|
12 |
+ -Redistributions in binary form must reproduce the above copyright |
|
13 |
+ -notice, this list of conditions and the following disclaimer in the |
|
14 |
+ -documentation and/or other materials provided with the distribution. |
|
15 |
+ - |
|
16 |
+ -Neither the name of the project's author nor the names of its |
|
17 |
+ -contributors may be used to endorse or promote products derived from |
|
18 |
+ -this software without specific prior written permission. |
|
19 |
+ - |
|
20 |
+ -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
21 |
+ -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
22 |
+ -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
|
23 |
+ -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
24 |
+ -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
25 |
+ -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED |
|
26 |
+ -TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
|
27 |
+ -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
|
28 |
+ -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
|
29 |
+ -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
|
30 |
+ -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
31 |
+ -} |
|
32 |
+ |
|
33 |
+{-# LANGUAGE RankNTypes,KindSignatures #-} |
|
34 |
+ |
|
35 |
+module Main |
|
36 |
+ where |
|
37 |
+ |
|
38 |
+import Test.HUnit (Assertion, (@=?), runTestTT, Test(..), Counts(..)) |
|
39 |
+import Data.Functor.Identity(Identity) |
|
40 |
+import Text.Parsec hiding (runParser) |
|
41 |
+import qualified CssParser as C |
|
42 |
+import System.Exit (ExitCode(..), exitWith) |
|
43 |
+import Data.Map hiding (foldr,null,map) |
|
44 |
+{-import Data.Char (toUpper)-} |
|
45 |
+ |
|
46 |
+exitProperly :: IO Counts -> IO () |
|
47 |
+exitProperly m = do |
|
48 |
+ counts <- m |
|
49 |
+ exitWith $ if failures counts /= 0 || errors counts /= 0 then ExitFailure 1 else ExitSuccess |
|
50 |
+ |
|
51 |
+testCase :: String -> Assertion -> Test |
|
52 |
+testCase label_ assertion = TestLabel label_ (TestCase assertion) |
|
53 |
+ |
|
54 |
+main :: IO () |
|
55 |
+main = exitProperly $ runTestTT $ TestList [ TestList parserTests ] |
|
56 |
+ |
|
57 |
+runParser :: ParsecT String () Identity a -> String -> Either ParseError a |
|
58 |
+runParser parser = flip parse "" $ atEnd parser |
|
59 |
+ |
|
60 |
+testSuccParser :: forall b. (Eq b, Show b) => ParsecT String () Identity b -> String -> b -> Assertion |
|
61 |
+testSuccParser parser in_ out = (Right out) @=? (runParser parser in_) |
|
62 |
+ |
|
63 |
+testFailParser :: forall a. (Eq a) => ParsecT String () Identity a -> String -> Assertion |
|
64 |
+testFailParser parser in_ = |
|
65 |
+ case (runParser parser in_) of |
|
66 |
+ Left _ -> 1 @=? 1 |
|
67 |
+ Right _ -> 1 @=? 2 |
|
68 |
+ |
|
69 |
+parserSuccEqualsTestCase :: forall a. (Eq a, Show a) => String -> ParsecT String () Identity a -> String -> a -> Test |
|
70 |
+parserSuccEqualsTestCase name = ((testCase name .) .) . testSuccParser |
|
71 |
+ |
|
72 |
+parserFailsTestCase :: forall a. Eq a => String -> ParsecT String () Identity a -> String -> Test |
|
73 |
+parserFailsTestCase name parser in_ = testCase name $ testFailParser parser in_ |
|
74 |
+ |
|
75 |
+atEnd :: forall b s u (m :: * -> *) t. (Show t, Stream s m t) => ParsecT s u m b -> ParsecT s u m b |
|
76 |
+atEnd p = p >>= ((eof >>) . return) |
|
77 |
+ |
|
78 |
+parserTests :: [Test] |
|
79 |
+parserTests = |
|
80 |
+ [ parserSuccEqualsTestCase "matchUpToX" (C.matchUpToX digit 2) "" "", |
|
81 |
+ parserSuccEqualsTestCase "matchUpToX" (C.matchUpToX digit 2) "1" "1", |
|
82 |
+ parserSuccEqualsTestCase "matchUpToX" (C.matchUpToX digit 2) "11" "11", |
|
83 |
+ parserFailsTestCase "matchUpToX" (C.matchUpToX digit 2) "111", -- Here, it should complain |
|
84 |
+ |
|
85 |
+ parserFailsTestCase "match1ToX" (C.match1ToX digit 2) "", |
|
86 |
+ parserSuccEqualsTestCase "match1ToX" (C.match1ToX digit 2) "1" "1", |
|
87 |
+ parserSuccEqualsTestCase "match1ToX" (C.match1ToX digit 2) "11" "11", |
|
88 |
+ parserFailsTestCase "match1ToX" (C.match1ToX digit 2) "111", -- Here, it should complain |
|
89 |
+ |
|
90 |
+ parserSuccEqualsTestCase "nonascii" C.nonascii "\240" '\240', |
|
91 |
+ parserSuccEqualsTestCase "nonascii" C.nonascii "\241" '\241', |
|
92 |
+ parserSuccEqualsTestCase "nonascii" C.nonascii "\1114110" '\1114110', |
|
93 |
+ parserSuccEqualsTestCase "nonascii" C.nonascii "\1114111" '\1114111', |
|
94 |
+ parserFailsTestCase "nonascii" C.nonascii "\230", |
|
95 |
+ |
|
96 |
+ parserSuccEqualsTestCase "unicode" C.unicode "\\222230 " "222230", |
|
97 |
+ parserFailsTestCase "unicode" C.unicode "\\2222301", |
|
98 |
+ parserFailsTestCase "unicode" C.unicode "\\", |
|
99 |
+ |
|
100 |
+ parserSuccEqualsTestCase "lone \\n" C.nl "\n" "\n", |
|
101 |
+ parserSuccEqualsTestCase "lone \\r" C.nl "\r" "\n", |
|
102 |
+ parserSuccEqualsTestCase "lone \\f" C.nl "\f" "\n", |
|
103 |
+ parserSuccEqualsTestCase "\\r\\n" C.nl "\r\n" "\n", |
|
104 |
+ parserFailsTestCase "nl" C.nl "a", |
|
105 |
+ |
|
106 |
+ parserSuccEqualsTestCase "test double quote" C.string_ "\"aaa\"" "aaa", |
|
107 |
+ parserSuccEqualsTestCase "test double quote" C.string_ "\"aa\\\"a\"" "aa\\\"a", |
|
108 |
+ parserSuccEqualsTestCase "test single quote" C.string_ "'aaa'" "aaa", |
|
109 |
+ parserSuccEqualsTestCase "test escaped quote" C.string_ "'aa\\'a'" "aa\\'a", |
|
110 |
+ parserSuccEqualsTestCase "test escaped quote" C.string_ "'aa\\\na'" "aa\\\na", |
|
111 |
+ parserFailsTestCase "unended string" C.string_ "'aaa", |
|
112 |
+ parserFailsTestCase "unended string" C.string_ "\"aaa", |
|
113 |
+ parserFailsTestCase "no string" C.string_ "aaa", |
|
114 |
+ |
|
115 |
+ parserSuccEqualsTestCase "escape" C.escape "\\j" "\\j", |
|
116 |
+ parserSuccEqualsTestCase "escape" C.escape "\\00" "\\00", |
|
117 |
+ parserSuccEqualsTestCase "escape" C.escape "\\'" "\\'", |
|
118 |
+ parserFailsTestCase "no escaped letter should fail" C.escape "\\", |
|
119 |
+ parserFailsTestCase "escaped newline fails" C.escape "\\\n", |
|
120 |
+ |
|
121 |
+ parserSuccEqualsTestCase "test simple ident" C.ident "ident" "ident", |
|
122 |
+ parserSuccEqualsTestCase "test alnum ident" C.ident "ident0" "ident0", |
|
123 |
+ parserSuccEqualsTestCase "test alnum ident" C.ident "-ident0" "-ident0", |
|
124 |
+ parserFailsTestCase "ident can't begin with num" C.ident "1dent", |
|
125 |
+ parserFailsTestCase "ident can't contain space" C.ident "dent ", |
|
126 |
+ |
|
127 |
+ parserSuccEqualsTestCase "test simple name" C.name "name" "name", |
|
128 |
+ parserSuccEqualsTestCase "test alnum name" C.name "name0" "name0", |
|
129 |
+ parserSuccEqualsTestCase "test alnum name" C.name "-name0" "-name0", |
|
130 |
+ parserSuccEqualsTestCase "name begins with num" C.name "1dent" "1dent", |
|
131 |
+ parserFailsTestCase "name can't contain space" C.name "dent ", |
|
132 |
+ |
|
133 |
+ parserSuccEqualsTestCase "test num" C.num "123" "123", |
|
134 |
+ parserSuccEqualsTestCase "test floating num" C.num "1.23" "1.23", |
|
135 |
+ parserSuccEqualsTestCase "test floating num" C.num ".23" ".23", |
|
136 |
+ |
|
137 |
+ parserSuccEqualsTestCase "url with string" C.urlParse "url('something')" $ C.URLFunc "something", |
|
138 |
+ parserSuccEqualsTestCase "url with string" C.urlParse "url(\"something\")" $ C.URLFunc "something", |
|
139 |
+ parserSuccEqualsTestCase "url with string and spaces" C.urlParse "url( \"something\" )" $ C.URLFunc "something", |
|
140 |
+ parserSuccEqualsTestCase "url without string" C.urlParse "url(/a/b/c)" $ C.URLFunc "/a/b/c", |
|
141 |
+ parserSuccEqualsTestCase "url without string" C.urlParse "url(a/b/c)" $ C.URLFunc "a/b/c", |
|
142 |
+ |
|
143 |
+ parserSuccEqualsTestCase "page rule" C.page "@page{a:b}" $ C.PageDeclaration Nothing (fromList [("a","b")]), |
|
144 |
+ parserSuccEqualsTestCase "page whitespace" C.page "@page { a : b ; }" $ C.PageDeclaration Nothing (fromList [("a","b")]), |
|
145 |
+ parserSuccEqualsTestCase "pseudo-page" C.page "@page:first{a:b;}" $ C.PageDeclaration (Just "first") (fromList [("a","b")]), |
|
146 |
+ |
|
147 |
+ parserSuccEqualsTestCase "charset" C.charset "@charset \"UTF-8\";" $ (Just . C.Charset) "UTF-8", |
|
148 |
+ |
|
149 |
+ parserSuccEqualsTestCase "Medium identifier" C.medium "print" $ "print", |
|
150 |
+ parserSuccEqualsTestCase "Medium identifier" C.medium "print " $ "print", |
|
151 |
+ |
|
152 |
+ parserSuccEqualsTestCase "Media Declarations" C.mediaList "print, screen" $ ["print", "screen"], |
|
153 |
+ |
|
154 |
+ parserSuccEqualsTestCase "Media Declarations" C.media "@media screen {}" $ C.MediaQuery ["screen"] $ C.DeclarationList [], |
|
155 |
+ parserSuccEqualsTestCase "Media Declarations" C.media "@media screen { a {b:c;} }" $ C.MediaQuery ["screen"] $ C.DeclarationList [C.CSSDeclaration (C.SelectorList [C.TagName "a"]) (fromList [("b", "c")])] |
|
156 |
+ ] |
|
157 |
+ {- |
|
158 |
+ -[ testCase "empty accumulation" $ |
|
159 |
+ - [] @=? accumulate square [] |
|
160 |
+ -, testCase "accumulate squares" $ |
|
161 |
+ - [1, 4, 9] @=? accumulate square [1, 2, 3] |
|
162 |
+ -, testCase "accumulate upcases" $ |
|
163 |
+ - ["HELLO", "WORLD"] @=? accumulate (map toUpper) ["hello", "world"] |
|
164 |
+ -, testCase "accumulate reversed strings" $ |
|
165 |
+ - ["eht", "kciuq", "nworb", "xof", "cte"] @=? |
|
166 |
+ - accumulate reverse ["the", "quick", "brown", "fox", "etc"] |
|
167 |
+ -, testCase "accumulate recursively" $ |
|
168 |
+ - [["a1", "a2", "a3"], ["b1", "b2", "b3"], ["c1", "c2", "c3"]] @=? |
|
169 |
+ - accumulate (\c -> accumulate ((c:) . show) ([1, 2, 3] :: [Int])) "abc" |
|
170 |
+ -, testCase "accumulate non-strict" $ |
|
171 |
+ - ["nice work!"] @=? |
|
172 |
+ - take 1 (accumulate id |
|
173 |
+ - ("nice work!" : |
|
174 |
+ - error "accumulate should be even lazier, don't use reverse!")) |
|
175 |
+ -] |
|
176 |
+ -} |