-
Notifications
You must be signed in to change notification settings - Fork 0
/
Django.hs
72 lines (58 loc) · 2.88 KB
/
Django.hs
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
-- Copyright 2014 Alvaro J. Genial (http://alva.ro) -- see LICENSE.md for more.
module Text.Neat.Input.Django (input) where
import Control.Applicative ((<$), (<$>), (<*), (<*>), (*>), (<|>), many, some)
import Data.Char (isAlphaNum, isSpace)
import Data.List (intercalate, span)
import Text.Parsec hiding ((<|>), many, optional)
import Text.Neat.Input
import Text.Neat.Template
input :: String -> String -> File
input path string = case runParser (file path) () path string of
Right result -> result
Left failure -> error $ "parsing failure at " ++ show failure
outputMarkers = ("{{", "}}")
commentMarkers = ("{#", "#}")
elementMarkers = ("{%", "%}")
file :: String -> Parsec String () File
file path = File path <$> block <* eof where
block = Block <$> many chunk
chunk = Chunk <$> location <*> element
element = choice $ try <$> [
Output <$> value' `within` outputMarkers,
Comment <$> block `within` commentMarkers,
Define <$> tag "def" function <*> block <* end "enddef",
Filter <$> tag "filter" value <*> block <* end "endfilter",
For <$> tag "for" binding <*> block <*> else' <* end "endfor",
If <$> tag "if" value <*> block <*> else' <* end "endif",
Switch <$> tag "switch" value <*> cases <*> default' <* end "endswitch",
With <$> tag "with" binding <*> block <* end "endwith",
Text <$> some textChar]
value' = value <$> location <*> trimmedText
else' = optionMaybe . try $ end "else" *> block
default' = optionMaybe . try $ end "default" *> block
cases = spaces *> many (try case') where
case' = Case <$> tag "case" Pattern <*> block
tag name f = let t = keyword name *> trimmedText
in f <$> location <*> (t `within` elementMarkers)
end name = keyword name `within` elementMarkers
keyword k = string k <* notFollowedBy nameChar <* spaces
p `within` (left, right) =
between (string left <* spaces) (spaces *> string right) p
trimmedText = spaces *> (trim <$> many textChar)
trim = reverse . dropWhile isSpace . reverse . dropWhile isSpace
value l s = Value l (trim <$> split "|" s)
function l s = Function l n (Pattern l p) where (n, p) = span isName s
binding l s = case split " in " s of
[p, v] -> Binding (Pattern l (trim p)) (value l v)
_ -> case split " as " s of
[v, p] -> Binding (Pattern l (trim p)) (value l v)
_ -> case split "=" s of
[p, v] -> Binding (Pattern l (trim p)) (value l v)
_ -> error $ "invalid for: " ++ s
isName c = isAlphaNum c || c `elem` "'_"
nameChar = alphaNum <|> oneOf "'_"
textChar = noneOf "{#%}" -- TODO: Generate this from markers.
<|> try (char '{' <* notFollowedBy (oneOf "{#%"))
<|> try (oneOf "#%}" <* notFollowedBy (char '}'))
location = f <$> getPosition
where f p = Location (sourceName p) (sourceLine p)