@@ -1,3 +1,4 @@
1+{-#LANGUAGE OverloadedStrings #-}
12module Servant.JS.Internal
23 ( JavaScriptGenerator
34 , CommonGeneratorOptions(..)
@@ -34,28 +35,28 @@ module Servant.JS.Internal
3435import Control.Lens ((^.))
3536import qualified Data.CharSet as Set
3637import qualified Data.CharSet.Unicode.Category as Set
37−import Data.List
3838import Data.Monoid
3939import qualified Data.Text as T
40+import Data.Text (Text)
4041import Servant.Foreign
4142
4243type AjaxReq = Req
4344
4445-- A 'JavascriptGenerator' just takes the data found in the API type
45−-- for each endpoint and generates Javascript code in a String. Several
46+-- for each endpoint and generates Javascript code in a Text. Several
4647-- generators are available in this package.
47−type JavaScriptGenerator = [Req] -> String
48+type JavaScriptGenerator = [Req] -> Text
4849
4950-- | This structure is used by specific implementations to let you
5051-- customize the output
5152data CommonGeneratorOptions = CommonGeneratorOptions
5253 {
53− functionNameBuilder :: FunctionName -> String -- ^ function generating function names
54− , requestBody :: String -- ^ name used when a user want to send the request body (to let you redefine it)
55− , successCallback :: String -- ^ name of the callback parameter when the request was successful
56− , errorCallback :: String -- ^ name of the callback parameter when the request reported an error
57− , moduleName :: String -- ^ namespace on which we define the foreign function (empty mean local var)
58− , urlPrefix :: String -- ^ a prefix we should add to the Url in the codegen
54+ functionNameBuilder :: FunctionName -> Text -- ^ function generating function names
55+ , requestBody :: Text -- ^ name used when a user want to send the request body (to let you redefine it)
56+ , successCallback :: Text -- ^ name of the callback parameter when the request was successful
57+ , errorCallback :: Text -- ^ name of the callback parameter when the request reported an error
58+ , moduleName :: Text -- ^ namespace on which we define the foreign function (empty mean local var)
59+ , urlPrefix :: Text -- ^ a prefix we should add to the Url in the codegen
5960 }
6061
6162-- | Default options.
@@ -87,7 +88,34 @@ defCommonGeneratorOptions = CommonGeneratorOptions
8788-- Couldn't work out how to handle zero-width characters.
8889--
8990-- @TODO: specify better default function name, or throw error?
90−toValidFunctionName :: String -> String
91+toValidFunctionName :: Text -> Text
92+-- @TODO: Cons text
93+--
94+
95+toValidFunctionName t =
96+ case T.uncons t of
97+ Just (x,xs) ->
98+ setFirstChar x `T.cons` T.filter remainder xs
99+ Nothing -> "_"
100+ where
101+ setFirstChar c = if firstChar c then c else '_'
102+ firstChar c = prefixOK c || any (Set.member c) firstLetterOK
103+ remainder c = prefixOK c || any (Set.member c) remainderOK
104+ -- Valid prefixes
105+ prefixOK c = c `elem` ['$','_']
106+ -- Unicode character sets
107+ firstLetterOK = [ Set.lowercaseLetter
108+ , Set.uppercaseLetter
109+ , Set.titlecaseLetter
110+ , Set.modifierLetter
111+ , Set.otherLetter
112+ , Set.letterNumber ]
113+ remainderOK = firstLetterOK
114+ <> [ Set.nonSpacingMark
115+ , Set.spacingCombiningMark
116+ , Set.decimalNumber
117+ , Set.connectorPunctuation ]
118+{-
91119toValidFunctionName (x:xs) = [setFirstChar x] <> filter remainder xs
92120 where
93121 setFirstChar c = if firstChar c then c else '_'
@@ -108,59 +136,56 @@ toValidFunctionName (x:xs) = [setFirstChar x] <> filter remainder xs
108136 , Set.decimalNumber
109137 , Set.connectorPunctuation ]
110138toValidFunctionName [] = "_"
139+-}
111140
112−toJSHeader :: HeaderArg -> String
141+toJSHeader :: HeaderArg -> Text
113142toJSHeader (HeaderArg n) = toValidFunctionName ("header" <> n)
114143toJSHeader (ReplaceHeaderArg n p)
115− | pn `isPrefixOf` p = pv <> " + \"" <> rp <> "\""
116− | pn `isSuffixOf` p = "\"" <> rp <> "\" + " <> pv
117− | pn `isInfixOf` p = "\"" <> (replace pn ("\" + " <> pv <> " + \"") p)
144+ | pn `T.isPrefixOf` p = pv <> " + \"" <> rp <> "\""
145+ | pn `T.isSuffixOf` p = "\"" <> rp <> "\" + " <> pv
146+ | pn `T.isInfixOf` p = "\"" <> (T.replace pn ("\" + " <> pv <> " + \"") p)
118147 <> "\""
119148 | otherwise = p
120149 where
121150 pv = toValidFunctionName ("header" <> n)
122151 pn = "{" <> n <> "}"
123− rp = replace pn "" p
124− -- Use replace method from Data.Text
125− replace old new = T.unpack
126− . T.replace (T.pack old) (T.pack new)
127− . T.pack
152+ rp = T.replace pn "" p
128153
129−jsSegments :: [Segment] -> String
154+jsSegments :: [Segment] -> Text
130155jsSegments [] = ""
131−jsSegments [x] = "/" ++ segmentToStr x False
132−jsSegments (x:xs) = "/" ++ segmentToStr x True ++ jsSegments xs
156+jsSegments [x] = "/" <> segmentToStr x False
157+jsSegments (x:xs) = "/" <> segmentToStr x True <> jsSegments xs
133158
134−segmentToStr :: Segment -> Bool -> String
159+segmentToStr :: Segment -> Bool -> Text
135160segmentToStr (Segment st ms) notTheEnd =
136− segmentTypeToStr st ++ jsMParams ms ++ if notTheEnd then "" else "'"
161+ segmentTypeToStr st <> jsMParams ms <> if notTheEnd then "" else "'"
137162
138−segmentTypeToStr :: SegmentType -> String
163+segmentTypeToStr :: SegmentType -> Text
139164segmentTypeToStr (Static s) = s
140−segmentTypeToStr (Cap s) = "' + encodeURIComponent(" ++ s ++ ") + '"
165+segmentTypeToStr (Cap s) = "' + encodeURIComponent(" <> s <> ") + '"
141166
142−jsGParams :: String -> [QueryArg] -> String
167+jsGParams :: Text -> [QueryArg] -> Text
143168jsGParams _ [] = ""
144169jsGParams _ [x] = paramToStr x False
145−jsGParams s (x:xs) = paramToStr x True ++ s ++ jsGParams s xs
170+jsGParams s (x:xs) = paramToStr x True <> s <> jsGParams s xs
146171
147−jsParams :: [QueryArg] -> String
172+jsParams :: [QueryArg] -> Text
148173jsParams = jsGParams "&"
149174
150−jsMParams :: [MatrixArg] -> String
175+jsMParams :: [MatrixArg] -> Text
151176jsMParams [] = ""
152−jsMParams xs = ";" ++ jsGParams ";" xs
177+jsMParams xs = ";" <> jsGParams ";" xs
153178
154−paramToStr :: QueryArg -> Bool -> String
179+paramToStr :: QueryArg -> Bool -> Text
155180paramToStr qarg notTheEnd =
156181 case qarg ^. argType of
157182 Normal -> name
158− ++ "=' + encodeURIComponent("
159− ++ name
160− ++ if notTheEnd then ") + '" else ")"
161− Flag -> name ++ "="
183+ <> "=' + encodeURIComponent("
184+ <> name
185+ <> if notTheEnd then ") + '" else ")"
186+ Flag -> name <> "="
162187 List -> name
163− ++ "[]=' + encodeURIComponent("
164− ++ name
165− ++ if notTheEnd then ") + '" else ")"
188+ <> "[]=' + encodeURIComponent("
189+ <> name
190+ <> if notTheEnd then ") + '" else ")"
166191 where name = qarg ^. argName