6 -- |Utility functions used internally in this package.
7 module Network.HTTP.Lucu.Utils
14 , parseWWWFormURLEncoded
27 import Control.Applicative hiding (empty)
28 import Control.Monad hiding (mapM)
29 import Data.Ascii (Ascii, AsciiBuilder)
30 import qualified Data.Ascii as A
31 import Data.ByteString (ByteString)
32 import qualified Data.ByteString.Char8 as BS
33 import Data.CaseInsensitive (CI)
34 import qualified Data.CaseInsensitive as CI
36 import Data.Collections
37 import Data.Collections.BaseInstances ()
38 import Data.Convertible.Base
39 import Data.Convertible.Instances.Time ()
41 import Data.Monoid.Unicode
42 import Data.Text (Text)
43 import qualified Data.Text as T
46 import Prelude hiding (last, mapM, null, reverse)
47 import Prelude.Unicode
48 import System.Directory
50 -- |'Host' represents an IP address or a host name in an URI
54 -- |'PathSegment' represents an URI path segment, split by slashes and
56 type PathSegment = ByteString
58 -- |'Path' is a list of URI path segments.
59 type Path = [PathSegment]
61 -- |>>> splitBy (== ':') "ab:c:def"
63 splitBy ∷ (a → Bool) → [a] → [[a]]
64 {-# INLINEABLE splitBy #-}
66 = case break isSep src of
68 (first, _sep:rest) → first : splitBy isSep rest
70 -- |>>> quoteStr "abc"
73 -- >>> quoteStr "ab\"c"
75 quoteStr ∷ Ascii → AsciiBuilder
76 quoteStr str = A.toAsciiBuilder "\"" ⊕
77 go (A.toByteString str) (∅) ⊕
80 go ∷ ByteString → AsciiBuilder → AsciiBuilder
82 = case BS.break (≡ '"') bs of
88 (ab ⊕ b2ab x ⊕ A.toAsciiBuilder "\\\"")
90 b2ab ∷ ByteString → AsciiBuilder
91 b2ab = A.toAsciiBuilder ∘ A.unsafeFromByteString
93 -- |>>> parseWWWFormURLEncoded "aaa=bbb&ccc=ddd"
94 -- [("aaa", "bbb"), ("ccc", "ddd")]
95 parseWWWFormURLEncoded ∷ Ascii → [(ByteString, ByteString)]
96 parseWWWFormURLEncoded src
97 -- THINKME: We could gain some performance by using attoparsec
100 | otherwise = do pairStr ← splitBy (\ c → c ≡ ';' ∨ c ≡ '&') (A.toString src)
101 let (key, value) = break (≡ '=') pairStr
102 return ( unescape key
103 , unescape $ case value of
108 unescape ∷ String → ByteString
109 unescape = BS.pack ∘ unEscapeString ∘ (plusToSpace <$>)
111 plusToSpace ∷ Char → Char
112 plusToSpace '+' = ' '
115 -- |>>> uriHost "http://example.com/foo/bar"
118 {-# INLINE uriHost #-}
119 uriHost = CI.mk ∘ T.pack ∘ uriRegName ∘ fromJust ∘ uriAuthority
121 -- |>>> uriPathSegments "http://example.com/foo/bar"
123 uriPathSegments ∷ URI → Path
125 = let reqPathStr = uriPath uri
126 reqPath = [ unEscapeString x
127 | x ← splitBy (≡ '/') reqPathStr, (¬) (null x) ]
131 -- |>>> trim " ab c d "
133 trim ∷ String → String
134 trim = reverse ∘ f ∘ reverse ∘ f
136 f = dropWhile isSpace
139 -- | (⊲) = ('<|')
141 -- U+22B2, NORMAL SUBGROUP OF
142 (⊲) ∷ Sequence α a ⇒ a → α → α
146 -- | (⊳) = ('|>')
148 -- U+22B3, CONTAINS AS NORMAL SUBGROUP
149 (⊳) ∷ Sequence α a ⇒ α → a → α
153 -- | (⋈) = ('><')
156 (⋈) ∷ Sequence α a ⇒ α → α → α
159 -- |Generalised @mapM@ from any 'Foldable' to 'Unfoldable'. Why isn't
160 -- this in the @collections-api@?
161 mapM ∷ (Foldable α a, Unfoldable β b, Functor m, Monad m)
162 ⇒ (a → m b) → α → m β
164 mapM = flip foldrM empty ∘ (flip ((<$>) ∘ flip insert) ∘)
166 -- |Get the modification time of a given file.
167 getLastModified ∷ FilePath → IO UTCTime
168 getLastModified = (cs <$>) ∘ getModificationTime