@@ -1,3 +1,4 @@
1+{-#LANGUAGE OverloadedStrings #-}
1module Servant.JS.Internal
2module Servant.JS.Internal
2 ( JavaScriptGenerator
3 ( JavaScriptGenerator
3 , CommonGeneratorOptions(..)
4 , CommonGeneratorOptions(..)
@@ -34,28 +35,28 @@ module Servant.JS.Internal
34import Control.Lens ((^.))
35import Control.Lens ((^.))
35import qualified Data.CharSet as Set
36import qualified Data.CharSet as Set
36import qualified Data.CharSet.Unicode.Category as Set
37import qualified Data.CharSet.Unicode.Category as Set
38import Data.Monoid
38import Data.Monoid
39import qualified Data.Text as T
39import qualified Data.Text as T
40+import Data.Text (Text)
40import Servant.Foreign
41import Servant.Foreign
42type AjaxReq = Req
43type AjaxReq = Req
44-- A 'JavascriptGenerator' just takes the data found in the API type
45-- 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
46-- generators are available in this package.
47-- generators are available in this package.
47−type JavaScriptGenerator = [Req] -> String
48+type JavaScriptGenerator = [Req] -> Text
49-- | This structure is used by specific implementations to let you
50-- | This structure is used by specific implementations to let you
50-- customize the output
51-- customize the output
51data CommonGeneratorOptions = CommonGeneratorOptions
52data CommonGeneratorOptions = CommonGeneratorOptions
53− functionNameBuilder :: FunctionName -> String -- ^ function generating function names
54+ functionNameBuilder :: FunctionName -> Text -- ^ function generating function names
54− , requestBody :: String -- ^ name used when a user want to send the request body (to let you redefine it)
55+ , requestBody :: Text -- ^ 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+ , successCallback :: Text -- ^ 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+ , errorCallback :: Text -- ^ 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+ , moduleName :: Text -- ^ 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
59+ , urlPrefix :: Text -- ^ a prefix we should add to the Url in the codegen
61-- | Default options.
62-- | Default options.
@@ -87,7 +88,34 @@ defCommonGeneratorOptions = CommonGeneratorOptions
87-- Couldn't work out how to handle zero-width characters.
88-- Couldn't work out how to handle zero-width characters.
89-- @TODO: specify better default function name, or throw error?
90-- @TODO: specify better default function name, or throw error?
90−toValidFunctionName :: String -> String
91+toValidFunctionName :: Text -> Text
95+toValidFunctionName t =
98+ setFirstChar x `T.cons` T.filter remainder xs
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
105+ prefixOK c = c `elem` ['$','_']
106+ -- Unicode character sets
107+ firstLetterOK = [ Set.lowercaseLetter
108+ , Set.uppercaseLetter
109+ , Set.titlecaseLetter
110+ , Set.modifierLetter
112+ , Set.letterNumber ]
113+ remainderOK = firstLetterOK
114+ <> [ Set.nonSpacingMark
115+ , Set.spacingCombiningMark
117+ , Set.connectorPunctuation ]
91toValidFunctionName (x:xs) = [setFirstChar x] <> filter remainder xs
119toValidFunctionName (x:xs) = [setFirstChar x] <> filter remainder xs
93 setFirstChar c = if firstChar c then c else '_'
121 setFirstChar c = if firstChar c then c else '_'
@@ -108,59 +136,56 @@ toValidFunctionName (x:xs) = [setFirstChar x] <> filter remainder xs
108 , Set.decimalNumber
136 , Set.decimalNumber
109 , Set.connectorPunctuation ]
137 , Set.connectorPunctuation ]
110toValidFunctionName [] = "_"
138toValidFunctionName [] = "_"
112−toJSHeader :: HeaderArg -> String
141+toJSHeader :: HeaderArg -> Text
113toJSHeader (HeaderArg n) = toValidFunctionName ("header" <> n)
142toJSHeader (HeaderArg n) = toValidFunctionName ("header" <> n)
114toJSHeader (ReplaceHeaderArg n p)
143toJSHeader (ReplaceHeaderArg n p)
115− | pn `isPrefixOf` p = pv <> " + \"" <> rp <> "\""
144+ | pn `T.isPrefixOf` p = pv <> " + \"" <> rp <> "\""
116− | pn `isSuffixOf` p = "\"" <> rp <> "\" + " <> pv
145+ | pn `T.isSuffixOf` p = "\"" <> rp <> "\" + " <> pv
117− | pn `isInfixOf` p = "\"" <> (replace pn ("\" + " <> pv <> " + \"") p)
146+ | pn `T.isInfixOf` p = "\"" <> (T.replace pn ("\" + " <> pv <> " + \"") p)
119 | otherwise = p
148 | otherwise = p
121 pv = toValidFunctionName ("header" <> n)
150 pv = toValidFunctionName ("header" <> n)
122 pn = "{" <> n <> "}"
151 pn = "{" <> n <> "}"
123− rp = replace pn "" p
152+ rp = T.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)
129−jsSegments :: [Segment] -> String
154+jsSegments :: [Segment] -> Text
130jsSegments [] = ""
155jsSegments [] = ""
131−jsSegments [x] = "/" ++ segmentToStr x False
156+jsSegments [x] = "/" <> segmentToStr x False
132−jsSegments (x:xs) = "/" ++ segmentToStr x True ++ jsSegments xs
157+jsSegments (x:xs) = "/" <> segmentToStr x True <> jsSegments xs
134−segmentToStr :: Segment -> Bool -> String
159+segmentToStr :: Segment -> Bool -> Text
135segmentToStr (Segment st ms) notTheEnd =
160segmentToStr (Segment st ms) notTheEnd =
136− segmentTypeToStr st ++ jsMParams ms ++ if notTheEnd then "" else "'"
161+ segmentTypeToStr st <> jsMParams ms <> if notTheEnd then "" else "'"
138−segmentTypeToStr :: SegmentType -> String
163+segmentTypeToStr :: SegmentType -> Text
139segmentTypeToStr (Static s) = s
164segmentTypeToStr (Static s) = s
140−segmentTypeToStr (Cap s) = "' + encodeURIComponent(" ++ s ++ ") + '"
165+segmentTypeToStr (Cap s) = "' + encodeURIComponent(" <> s <> ") + '"
142−jsGParams :: String -> [QueryArg] -> String
167+jsGParams :: Text -> [QueryArg] -> Text
143jsGParams _ [] = ""
168jsGParams _ [] = ""
144jsGParams _ [x] = paramToStr x False
169jsGParams _ [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
147−jsParams :: [QueryArg] -> String
172+jsParams :: [QueryArg] -> Text
148jsParams = jsGParams "&"
173jsParams = jsGParams "&"
150−jsMParams :: [MatrixArg] -> String
175+jsMParams :: [MatrixArg] -> Text
151jsMParams [] = ""
176jsMParams [] = ""
152−jsMParams xs = ";" ++ jsGParams ";" xs
177+jsMParams xs = ";" <> jsGParams ";" xs
154−paramToStr :: QueryArg -> Bool -> String
179+paramToStr :: QueryArg -> Bool -> Text
155paramToStr qarg notTheEnd =
180paramToStr qarg notTheEnd =
156 case qarg ^. argType of
181 case qarg ^. argType of
157 Normal -> name
182 Normal -> name
158− ++ "=' + encodeURIComponent("
183+ <> "=' + encodeURIComponent("
160− ++ if notTheEnd then ") + '" else ")"
185+ <> if notTheEnd then ") + '" else ")"
161− Flag -> name ++ "="
186+ Flag -> name <> "="
162 List -> name
187 List -> name
163− ++ "[]=' + encodeURIComponent("
188+ <> "[]=' + encodeURIComponent("
165− ++ if notTheEnd then ") + '" else ")"
190+ <> if notTheEnd then ") + '" else ")"
166 where name = qarg ^. argName
191 where name = qarg ^. argName