elm-pages 2.1.6 → 2.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/generator/review/elm.json +34 -0
- package/generator/review/src/ReviewConfig.elm +10 -0
- package/generator/src/basepath-middleware.js +15 -9
- package/generator/src/build.js +100 -6
- package/generator/src/cli.js +13 -9
- package/generator/src/compile-elm.js +43 -0
- package/generator/src/dev-server.js +63 -11
- package/generator/src/error-formatter.js +62 -9
- package/generator/src/generate-template-module-connector.js +17 -4
- package/generator/src/init.js +4 -0
- package/generator/src/pre-render-html.js +19 -12
- package/generator/src/render-worker.js +0 -1
- package/generator/src/render.js +1 -2
- package/generator/src/seo-renderer.js +21 -2
- package/generator/static-code/hmr.js +43 -6
- package/generator/template/elm-tooling.json +9 -0
- package/generator/template/package.json +5 -1
- package/package.json +16 -9
- package/src/ApiRoute.elm +178 -0
- package/src/AriaLiveAnnouncer.elm +36 -0
- package/src/BuildError.elm +60 -0
- package/src/DataSource/File.elm +288 -0
- package/src/DataSource/Glob.elm +1050 -0
- package/src/DataSource/Http.elm +467 -0
- package/src/DataSource/Internal/Glob.elm +74 -0
- package/src/DataSource/Port.elm +87 -0
- package/src/DataSource/ServerRequest.elm +60 -0
- package/src/DataSource.elm +801 -0
- package/src/Head/Seo.elm +516 -0
- package/src/Head/Twitter.elm +109 -0
- package/src/Head.elm +452 -0
- package/src/HtmlPrinter.elm +27 -0
- package/src/Internal/ApiRoute.elm +89 -0
- package/src/Internal/OptimizedDecoder.elm +18 -0
- package/src/KeepOrDiscard.elm +6 -0
- package/src/OptimizedDecoder/Pipeline.elm +335 -0
- package/src/OptimizedDecoder.elm +818 -0
- package/src/Pages/ContentCache.elm +248 -0
- package/src/Pages/Flags.elm +26 -0
- package/src/Pages/Http.elm +10 -0
- package/src/Pages/Internal/ApplicationType.elm +6 -0
- package/src/Pages/Internal/NotFoundReason.elm +256 -0
- package/src/Pages/Internal/Platform/Cli.elm +1015 -0
- package/src/Pages/Internal/Platform/Effect.elm +14 -0
- package/src/Pages/Internal/Platform/StaticResponses.elm +540 -0
- package/src/Pages/Internal/Platform/ToJsPayload.elm +138 -0
- package/src/Pages/Internal/Platform.elm +745 -0
- package/src/Pages/Internal/RoutePattern.elm +122 -0
- package/src/Pages/Internal/Router.elm +116 -0
- package/src/Pages/Internal/StaticHttpBody.elm +54 -0
- package/src/Pages/Internal/String.elm +39 -0
- package/src/Pages/Manifest/Category.elm +240 -0
- package/src/Pages/Manifest.elm +412 -0
- package/src/Pages/PageUrl.elm +38 -0
- package/src/Pages/ProgramConfig.elm +73 -0
- package/src/Pages/Review/NoContractViolations.elm +397 -0
- package/src/Pages/Secrets.elm +83 -0
- package/src/Pages/SiteConfig.elm +13 -0
- package/src/Pages/StaticHttp/Request.elm +42 -0
- package/src/Pages/StaticHttpRequest.elm +320 -0
- package/src/Pages/Url.elm +60 -0
- package/src/Path.elm +96 -0
- package/src/QueryParams.elm +216 -0
- package/src/RenderRequest.elm +163 -0
- package/src/RequestsAndPending.elm +20 -0
- package/src/Secrets.elm +111 -0
- package/src/SecretsDict.elm +45 -0
- package/src/StructuredData.elm +236 -0
- package/src/TerminalText.elm +242 -0
- package/src/Test/Html/Internal/ElmHtml/Constants.elm +53 -0
- package/src/Test/Html/Internal/ElmHtml/Helpers.elm +17 -0
- package/src/Test/Html/Internal/ElmHtml/InternalTypes.elm +529 -0
- package/src/Test/Html/Internal/ElmHtml/Markdown.elm +56 -0
- package/src/Test/Html/Internal/ElmHtml/ToString.elm +197 -0
- package/src/Test/Internal/KernelConstants.elm +34 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
module Pages.StaticHttpRequest exposing (Error(..), RawRequest(..), Status(..), WhatToDo(..), cacheRequestResolution, merge, resolve, resolveUrls, strippedResponsesEncode, toBuildError)
|
|
2
|
+
|
|
3
|
+
import BuildError exposing (BuildError)
|
|
4
|
+
import Dict exposing (Dict)
|
|
5
|
+
import Internal.OptimizedDecoder
|
|
6
|
+
import Json.Decode.Exploration
|
|
7
|
+
import Json.Encode
|
|
8
|
+
import KeepOrDiscard exposing (KeepOrDiscard)
|
|
9
|
+
import List.Extra
|
|
10
|
+
import OptimizedDecoder
|
|
11
|
+
import Pages.Internal.ApplicationType exposing (ApplicationType)
|
|
12
|
+
import Pages.StaticHttp.Request
|
|
13
|
+
import RequestsAndPending exposing (RequestsAndPending)
|
|
14
|
+
import Secrets
|
|
15
|
+
import TerminalText as Terminal
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
type RawRequest value
|
|
19
|
+
= Request (Dict String WhatToDo) ( List (Secrets.Value Pages.StaticHttp.Request.Request), KeepOrDiscard -> ApplicationType -> RequestsAndPending -> RawRequest value )
|
|
20
|
+
| RequestError Error
|
|
21
|
+
| ApiRoute (Dict String WhatToDo) value
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
type WhatToDo
|
|
25
|
+
= UseRawResponse
|
|
26
|
+
| CliOnly
|
|
27
|
+
| StripResponse (OptimizedDecoder.Decoder ())
|
|
28
|
+
| DistilledResponse Json.Encode.Value
|
|
29
|
+
| Error (List BuildError)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
merge : String -> WhatToDo -> WhatToDo -> WhatToDo
|
|
33
|
+
merge key whatToDo1 whatToDo2 =
|
|
34
|
+
case ( whatToDo1, whatToDo2 ) of
|
|
35
|
+
( Error buildErrors1, Error buildErrors2 ) ->
|
|
36
|
+
Error (buildErrors1 ++ buildErrors2)
|
|
37
|
+
|
|
38
|
+
( Error buildErrors1, _ ) ->
|
|
39
|
+
Error buildErrors1
|
|
40
|
+
|
|
41
|
+
( _, Error buildErrors1 ) ->
|
|
42
|
+
Error buildErrors1
|
|
43
|
+
|
|
44
|
+
( StripResponse strip1, StripResponse strip2 ) ->
|
|
45
|
+
StripResponse (OptimizedDecoder.map2 (\_ _ -> ()) strip1 strip2)
|
|
46
|
+
|
|
47
|
+
( StripResponse strip1, _ ) ->
|
|
48
|
+
StripResponse strip1
|
|
49
|
+
|
|
50
|
+
( _, StripResponse strip1 ) ->
|
|
51
|
+
StripResponse strip1
|
|
52
|
+
|
|
53
|
+
( _, CliOnly ) ->
|
|
54
|
+
whatToDo1
|
|
55
|
+
|
|
56
|
+
( CliOnly, _ ) ->
|
|
57
|
+
whatToDo2
|
|
58
|
+
|
|
59
|
+
( DistilledResponse distilled1, DistilledResponse distilled2 ) ->
|
|
60
|
+
if Json.Encode.encode 0 distilled1 == Json.Encode.encode 0 distilled2 then
|
|
61
|
+
DistilledResponse distilled1
|
|
62
|
+
|
|
63
|
+
else
|
|
64
|
+
Error
|
|
65
|
+
[ { title = "Non-Unique Distill Keys"
|
|
66
|
+
, message =
|
|
67
|
+
[ Terminal.text "I encountered DataSource.distill with two matching keys that had differing encoded values.\n\n"
|
|
68
|
+
, Terminal.text "Look for "
|
|
69
|
+
, Terminal.red <| "DataSource.distill"
|
|
70
|
+
, Terminal.text " with the key "
|
|
71
|
+
, Terminal.red <| ("\"" ++ key ++ "\"")
|
|
72
|
+
, Terminal.text "\n\n"
|
|
73
|
+
, Terminal.yellow <| "The first encoded value was:\n"
|
|
74
|
+
, Terminal.text <| Json.Encode.encode 2 distilled1
|
|
75
|
+
, Terminal.text "\n\n-------------------------------\n\n"
|
|
76
|
+
, Terminal.yellow <| "The second encoded value was:\n"
|
|
77
|
+
, Terminal.text <| Json.Encode.encode 2 distilled2
|
|
78
|
+
]
|
|
79
|
+
, path = "" -- TODO wire in path here?
|
|
80
|
+
, fatal = True
|
|
81
|
+
}
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
( DistilledResponse distilled1, _ ) ->
|
|
85
|
+
DistilledResponse distilled1
|
|
86
|
+
|
|
87
|
+
( _, DistilledResponse distilled1 ) ->
|
|
88
|
+
DistilledResponse distilled1
|
|
89
|
+
|
|
90
|
+
( UseRawResponse, UseRawResponse ) ->
|
|
91
|
+
UseRawResponse
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
strippedResponses : ApplicationType -> RawRequest value -> RequestsAndPending -> Dict String WhatToDo
|
|
95
|
+
strippedResponses =
|
|
96
|
+
strippedResponsesHelp Dict.empty
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
strippedResponsesEncode : ApplicationType -> RawRequest value -> RequestsAndPending -> Result (List BuildError) (Dict String String)
|
|
100
|
+
strippedResponsesEncode appType rawRequest requestsAndPending =
|
|
101
|
+
strippedResponses appType rawRequest requestsAndPending
|
|
102
|
+
|> Dict.toList
|
|
103
|
+
|> List.map
|
|
104
|
+
(\( k, whatToDo ) ->
|
|
105
|
+
(case whatToDo of
|
|
106
|
+
UseRawResponse ->
|
|
107
|
+
Dict.get k requestsAndPending
|
|
108
|
+
|> Maybe.withDefault Nothing
|
|
109
|
+
|> Maybe.withDefault ""
|
|
110
|
+
|> Just
|
|
111
|
+
|> Ok
|
|
112
|
+
|
|
113
|
+
StripResponse decoder ->
|
|
114
|
+
Dict.get k requestsAndPending
|
|
115
|
+
|> Maybe.withDefault Nothing
|
|
116
|
+
|> Maybe.withDefault ""
|
|
117
|
+
|> Json.Decode.Exploration.stripString (Internal.OptimizedDecoder.jde decoder)
|
|
118
|
+
|> Result.withDefault "ERROR"
|
|
119
|
+
|> Just
|
|
120
|
+
|> Ok
|
|
121
|
+
|
|
122
|
+
CliOnly ->
|
|
123
|
+
Nothing
|
|
124
|
+
|> Ok
|
|
125
|
+
|
|
126
|
+
DistilledResponse value ->
|
|
127
|
+
value
|
|
128
|
+
|> Json.Encode.encode 0
|
|
129
|
+
|> Just
|
|
130
|
+
|> Ok
|
|
131
|
+
|
|
132
|
+
Error buildError ->
|
|
133
|
+
Err buildError
|
|
134
|
+
)
|
|
135
|
+
|> Result.map (Maybe.map (Tuple.pair k))
|
|
136
|
+
)
|
|
137
|
+
|> combineMultipleErrors
|
|
138
|
+
|> Result.map (List.filterMap identity)
|
|
139
|
+
|> Result.map Dict.fromList
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
combineMultipleErrors : List (Result (List error) a) -> Result (List error) (List a)
|
|
143
|
+
combineMultipleErrors results =
|
|
144
|
+
List.foldr
|
|
145
|
+
(\result soFarResult ->
|
|
146
|
+
case soFarResult of
|
|
147
|
+
Ok soFarOk ->
|
|
148
|
+
case result of
|
|
149
|
+
Ok value ->
|
|
150
|
+
value :: soFarOk |> Ok
|
|
151
|
+
|
|
152
|
+
Err error_ ->
|
|
153
|
+
Err error_
|
|
154
|
+
|
|
155
|
+
Err errorsSoFar ->
|
|
156
|
+
case result of
|
|
157
|
+
Ok _ ->
|
|
158
|
+
Err errorsSoFar
|
|
159
|
+
|
|
160
|
+
Err error_ ->
|
|
161
|
+
Err <| error_ ++ errorsSoFar
|
|
162
|
+
)
|
|
163
|
+
(Ok [])
|
|
164
|
+
results
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
strippedResponsesHelp : Dict String WhatToDo -> ApplicationType -> RawRequest value -> RequestsAndPending -> Dict String WhatToDo
|
|
168
|
+
strippedResponsesHelp usedSoFar appType request rawResponses =
|
|
169
|
+
case request of
|
|
170
|
+
RequestError _ ->
|
|
171
|
+
usedSoFar
|
|
172
|
+
|
|
173
|
+
Request partiallyStrippedResponses ( _, lookupFn ) ->
|
|
174
|
+
case lookupFn KeepOrDiscard.Keep appType rawResponses of
|
|
175
|
+
followupRequest ->
|
|
176
|
+
strippedResponsesHelp
|
|
177
|
+
(Dict.merge
|
|
178
|
+
(\key a -> Dict.insert key a)
|
|
179
|
+
(\key a b -> Dict.insert key (merge key a b))
|
|
180
|
+
(\key b -> Dict.insert key b)
|
|
181
|
+
usedSoFar
|
|
182
|
+
partiallyStrippedResponses
|
|
183
|
+
Dict.empty
|
|
184
|
+
)
|
|
185
|
+
appType
|
|
186
|
+
followupRequest
|
|
187
|
+
rawResponses
|
|
188
|
+
|
|
189
|
+
ApiRoute partiallyStrippedResponses _ ->
|
|
190
|
+
Dict.merge
|
|
191
|
+
(\key a -> Dict.insert key a)
|
|
192
|
+
(\key a b -> Dict.insert key (merge key a b))
|
|
193
|
+
(\key b -> Dict.insert key b)
|
|
194
|
+
usedSoFar
|
|
195
|
+
partiallyStrippedResponses
|
|
196
|
+
Dict.empty
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
type Error
|
|
200
|
+
= MissingHttpResponse String (List (Secrets.Value Pages.StaticHttp.Request.Request))
|
|
201
|
+
| DecoderError String
|
|
202
|
+
| UserCalledStaticHttpFail String
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
toBuildError : String -> Error -> BuildError
|
|
206
|
+
toBuildError path error =
|
|
207
|
+
case error of
|
|
208
|
+
MissingHttpResponse missingKey _ ->
|
|
209
|
+
{ title = "Missing Http Response"
|
|
210
|
+
, message =
|
|
211
|
+
[ Terminal.text missingKey
|
|
212
|
+
]
|
|
213
|
+
, path = path
|
|
214
|
+
, fatal = True
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
DecoderError decodeErrorMessage ->
|
|
218
|
+
{ title = "Static Http Decoding Error"
|
|
219
|
+
, message =
|
|
220
|
+
[ Terminal.text decodeErrorMessage
|
|
221
|
+
]
|
|
222
|
+
, path = path
|
|
223
|
+
, fatal = True
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
UserCalledStaticHttpFail decodeErrorMessage ->
|
|
227
|
+
{ title = "Called Static Http Fail"
|
|
228
|
+
, message =
|
|
229
|
+
[ Terminal.text <| "I ran into a call to `DataSource.fail` with message: " ++ decodeErrorMessage
|
|
230
|
+
]
|
|
231
|
+
, path = path
|
|
232
|
+
, fatal = True
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
resolve : ApplicationType -> RawRequest value -> RequestsAndPending -> Result Error value
|
|
237
|
+
resolve appType request rawResponses =
|
|
238
|
+
case request of
|
|
239
|
+
RequestError error ->
|
|
240
|
+
Err error
|
|
241
|
+
|
|
242
|
+
Request _ ( _, lookupFn ) ->
|
|
243
|
+
case lookupFn KeepOrDiscard.Keep appType rawResponses of
|
|
244
|
+
nextRequest ->
|
|
245
|
+
resolve appType nextRequest rawResponses
|
|
246
|
+
|
|
247
|
+
ApiRoute _ value ->
|
|
248
|
+
Ok value
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
resolveUrls : ApplicationType -> RawRequest value -> RequestsAndPending -> List (Secrets.Value Pages.StaticHttp.Request.Request)
|
|
252
|
+
resolveUrls appType request rawResponses =
|
|
253
|
+
resolveUrlsHelp appType rawResponses [] request
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
resolveUrlsHelp : ApplicationType -> RequestsAndPending -> List (Secrets.Value Pages.StaticHttp.Request.Request) -> RawRequest value -> List (Secrets.Value Pages.StaticHttp.Request.Request)
|
|
257
|
+
resolveUrlsHelp appType rawResponses soFar request =
|
|
258
|
+
case request of
|
|
259
|
+
RequestError error ->
|
|
260
|
+
case error of
|
|
261
|
+
MissingHttpResponse _ next ->
|
|
262
|
+
(soFar ++ next)
|
|
263
|
+
|> List.Extra.uniqueBy (Secrets.maskedLookup >> Pages.StaticHttp.Request.hash)
|
|
264
|
+
|
|
265
|
+
_ ->
|
|
266
|
+
soFar
|
|
267
|
+
|
|
268
|
+
Request _ ( urlList, lookupFn ) ->
|
|
269
|
+
resolveUrlsHelp appType
|
|
270
|
+
rawResponses
|
|
271
|
+
(soFar ++ urlList)
|
|
272
|
+
(lookupFn KeepOrDiscard.Keep appType rawResponses)
|
|
273
|
+
|
|
274
|
+
ApiRoute _ _ ->
|
|
275
|
+
soFar
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
cacheRequestResolution :
|
|
279
|
+
ApplicationType
|
|
280
|
+
-> RawRequest value
|
|
281
|
+
-> RequestsAndPending
|
|
282
|
+
-> Status value
|
|
283
|
+
cacheRequestResolution appType request rawResponses =
|
|
284
|
+
cacheRequestResolutionHelp [] appType rawResponses request
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
type Status value
|
|
288
|
+
= Incomplete (List (Secrets.Value Pages.StaticHttp.Request.Request))
|
|
289
|
+
| HasPermanentError Error
|
|
290
|
+
| Complete
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
cacheRequestResolutionHelp :
|
|
294
|
+
List (Secrets.Value Pages.StaticHttp.Request.Request)
|
|
295
|
+
-> ApplicationType
|
|
296
|
+
-> RequestsAndPending
|
|
297
|
+
-> RawRequest value
|
|
298
|
+
-> Status value
|
|
299
|
+
cacheRequestResolutionHelp foundUrls appType rawResponses request =
|
|
300
|
+
case request of
|
|
301
|
+
RequestError error ->
|
|
302
|
+
case error of
|
|
303
|
+
MissingHttpResponse _ _ ->
|
|
304
|
+
-- TODO do I need to pass through continuation URLs here? -- Incomplete (urlList ++ foundUrls)
|
|
305
|
+
Incomplete foundUrls
|
|
306
|
+
|
|
307
|
+
DecoderError _ ->
|
|
308
|
+
HasPermanentError error
|
|
309
|
+
|
|
310
|
+
UserCalledStaticHttpFail _ ->
|
|
311
|
+
HasPermanentError error
|
|
312
|
+
|
|
313
|
+
Request _ ( urlList, lookupFn ) ->
|
|
314
|
+
cacheRequestResolutionHelp urlList
|
|
315
|
+
appType
|
|
316
|
+
rawResponses
|
|
317
|
+
(lookupFn KeepOrDiscard.Keep appType rawResponses)
|
|
318
|
+
|
|
319
|
+
ApiRoute _ _ ->
|
|
320
|
+
Complete
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
module Pages.Url exposing (Url, external, fromPath, toAbsoluteUrl, toString)
|
|
2
|
+
|
|
3
|
+
{-| Some of the `elm-pages` APIs will take internal URLs and ensure that they have the `canonicalSiteUrl` prepended.
|
|
4
|
+
|
|
5
|
+
That's the purpose for this type. If you have an external URL, like `Pages.Url.external "https://google.com"`,
|
|
6
|
+
then the canonicalUrl will not be prepended when it is used in a head tag.
|
|
7
|
+
|
|
8
|
+
If you refer to a local page, like `Route.Index |> Route.toPath |> Pages.Url.fromPath`, or `Pages.Url.fromPath`
|
|
9
|
+
|
|
10
|
+
@docs Url, external, fromPath, toAbsoluteUrl, toString
|
|
11
|
+
|
|
12
|
+
-}
|
|
13
|
+
|
|
14
|
+
import Pages.Internal.String as String
|
|
15
|
+
import Path exposing (Path)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
{-| -}
|
|
19
|
+
type Url
|
|
20
|
+
= Internal String
|
|
21
|
+
| External String
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
{-| -}
|
|
25
|
+
fromPath : Path -> Url
|
|
26
|
+
fromPath path =
|
|
27
|
+
path |> Path.toAbsolute |> Internal
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
{-| -}
|
|
31
|
+
external : String -> Url
|
|
32
|
+
external externalUrl =
|
|
33
|
+
External externalUrl
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
{-| -}
|
|
37
|
+
toString : Url -> String
|
|
38
|
+
toString path =
|
|
39
|
+
case path of
|
|
40
|
+
Internal rawPath ->
|
|
41
|
+
rawPath
|
|
42
|
+
|
|
43
|
+
External url ->
|
|
44
|
+
url
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
{-| -}
|
|
48
|
+
toAbsoluteUrl : String -> Url -> String
|
|
49
|
+
toAbsoluteUrl canonicalSiteUrl url =
|
|
50
|
+
case url of
|
|
51
|
+
External externalUrl ->
|
|
52
|
+
externalUrl
|
|
53
|
+
|
|
54
|
+
Internal internalUrl ->
|
|
55
|
+
join canonicalSiteUrl internalUrl
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
join : String -> String -> String
|
|
59
|
+
join base path =
|
|
60
|
+
String.chopEnd "/" base ++ "/" ++ String.chopStart "/" path
|
package/src/Path.elm
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
module Path exposing
|
|
2
|
+
( Path, join, fromString
|
|
3
|
+
, toAbsolute, toRelative, toSegments
|
|
4
|
+
)
|
|
5
|
+
|
|
6
|
+
{-| Represents the path portion of a URL (not query parameters, fragment, protocol, port, etc.).
|
|
7
|
+
|
|
8
|
+
This helper lets you combine together path parts without worrying about having too many or too few slashes.
|
|
9
|
+
These two examples will result in the same URL, even though the first example has trailing and leading slashes, and the
|
|
10
|
+
second does not.
|
|
11
|
+
|
|
12
|
+
Path.join [ "/blog/", "/post-1/" ]
|
|
13
|
+
|> Path.toAbsolute
|
|
14
|
+
--> "/blog/post-1"
|
|
15
|
+
|
|
16
|
+
Path.join [ "blog", "post-1" ]
|
|
17
|
+
|> Path.toAbsolute
|
|
18
|
+
--> "/blog/post-1"
|
|
19
|
+
|
|
20
|
+
We can also safely join Strings that include multiple path parts, a single path part per string, or a mix of the two:
|
|
21
|
+
|
|
22
|
+
Path.join [ "/articles/archive/", "1977", "06", "10", "post-1" ]
|
|
23
|
+
|> Path.toAbsolute
|
|
24
|
+
--> "/articles/archive/1977/06/10/post-1"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
## Creating Paths
|
|
28
|
+
|
|
29
|
+
@docs Path, join, fromString
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
## Turning Paths to String
|
|
33
|
+
|
|
34
|
+
@docs toAbsolute, toRelative, toSegments
|
|
35
|
+
|
|
36
|
+
-}
|
|
37
|
+
|
|
38
|
+
import Pages.Internal.String exposing (chopEnd, chopStart)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
{-| The path portion of the URL, normalized to ensure that path segments are joined with `/`s in the right places (no doubled up or missing slashes).
|
|
42
|
+
-}
|
|
43
|
+
type Path
|
|
44
|
+
= Path String
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
{-| Create a Path from multiple path parts. Each part can either be a single path segment, like `blog`, or a
|
|
48
|
+
multi-part path part, like `blog/post-1`.
|
|
49
|
+
-}
|
|
50
|
+
join : List String -> Path
|
|
51
|
+
join parts =
|
|
52
|
+
parts
|
|
53
|
+
|> List.map normalize
|
|
54
|
+
|> String.join "/"
|
|
55
|
+
|> Path
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
{-| Create a Path from a path String.
|
|
59
|
+
|
|
60
|
+
Path.fromString "blog/post-1/"
|
|
61
|
+
|> Path.toAbsolute
|
|
62
|
+
|> Expect.equal "/blog/post-1"
|
|
63
|
+
|
|
64
|
+
-}
|
|
65
|
+
fromString : String -> Path
|
|
66
|
+
fromString path =
|
|
67
|
+
path
|
|
68
|
+
|> normalize
|
|
69
|
+
|> Path
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
{-| -}
|
|
73
|
+
toSegments : Path -> List String
|
|
74
|
+
toSegments (Path path) =
|
|
75
|
+
path |> String.split "/" |> List.filter ((/=) "")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
{-| Turn a Path to an absolute URL (with no trailing slash).
|
|
79
|
+
-}
|
|
80
|
+
toAbsolute : Path -> String
|
|
81
|
+
toAbsolute (Path path) =
|
|
82
|
+
"/" ++ path
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
{-| Turn a Path to a relative URL.
|
|
86
|
+
-}
|
|
87
|
+
toRelative : Path -> String
|
|
88
|
+
toRelative (Path path) =
|
|
89
|
+
path
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
normalize : String -> String
|
|
93
|
+
normalize pathPart =
|
|
94
|
+
pathPart
|
|
95
|
+
|> chopEnd "/"
|
|
96
|
+
|> chopStart "/"
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
module QueryParams exposing
|
|
2
|
+
( QueryParams
|
|
3
|
+
, Parser
|
|
4
|
+
, andThen, fail, fromResult, fromString, optionalString, parse, string, strings, succeed
|
|
5
|
+
, map2, oneOf
|
|
6
|
+
, toDict, toString
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
{-| Represents the query portion of a URL. You can use `toDict` or `toString` to turn it into basic types, or you can
|
|
10
|
+
parse it into a custom type using the other functions in this module.
|
|
11
|
+
|
|
12
|
+
@docs QueryParams
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
## Parsing
|
|
16
|
+
|
|
17
|
+
@docs Parser
|
|
18
|
+
|
|
19
|
+
@docs andThen, fail, fromResult, fromString, optionalString, parse, string, strings, succeed
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
## Combining
|
|
23
|
+
|
|
24
|
+
@docs map2, oneOf
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
## Accessing as Built-In Types
|
|
28
|
+
|
|
29
|
+
@docs toDict, toString
|
|
30
|
+
|
|
31
|
+
-}
|
|
32
|
+
|
|
33
|
+
import Dict exposing (Dict)
|
|
34
|
+
import Url
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
{-| -}
|
|
38
|
+
type QueryParams
|
|
39
|
+
= QueryParams String
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
{-| -}
|
|
43
|
+
type Parser a
|
|
44
|
+
= Parser (Dict String (List String) -> Result String a)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
{-| -}
|
|
48
|
+
succeed : a -> Parser a
|
|
49
|
+
succeed value =
|
|
50
|
+
Parser (\_ -> Ok value)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
{-| -}
|
|
54
|
+
fail : String -> Parser a
|
|
55
|
+
fail errorMessage =
|
|
56
|
+
Parser (\_ -> Err errorMessage)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
{-| -}
|
|
60
|
+
fromResult : Result String a -> Parser a
|
|
61
|
+
fromResult result =
|
|
62
|
+
Parser (\_ -> result)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
{-| -}
|
|
66
|
+
andThen : (a -> Parser b) -> Parser a -> Parser b
|
|
67
|
+
andThen andThenFn (Parser parser) =
|
|
68
|
+
Parser
|
|
69
|
+
(\dict ->
|
|
70
|
+
case Result.map andThenFn (parser dict) of
|
|
71
|
+
Ok (Parser result) ->
|
|
72
|
+
result dict
|
|
73
|
+
|
|
74
|
+
Err error ->
|
|
75
|
+
Err error
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
{-| -}
|
|
80
|
+
oneOf : List (Parser a) -> Parser a
|
|
81
|
+
oneOf parsers =
|
|
82
|
+
Parser
|
|
83
|
+
(tryParser parsers)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
{-| -}
|
|
87
|
+
tryParser : List (Parser a) -> Dict String (List String) -> Result String a
|
|
88
|
+
tryParser parsers dict =
|
|
89
|
+
case parsers of
|
|
90
|
+
[] ->
|
|
91
|
+
Err ""
|
|
92
|
+
|
|
93
|
+
(Parser nextParser) :: otherParsers ->
|
|
94
|
+
case nextParser dict of
|
|
95
|
+
Ok okValue ->
|
|
96
|
+
Ok okValue
|
|
97
|
+
|
|
98
|
+
Err _ ->
|
|
99
|
+
tryParser otherParsers dict
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
{-| -}
|
|
103
|
+
map2 : (a -> b -> combined) -> Parser a -> Parser b -> Parser combined
|
|
104
|
+
map2 func (Parser a) (Parser b) =
|
|
105
|
+
Parser <|
|
|
106
|
+
\dict ->
|
|
107
|
+
Result.map2 func (a dict) (b dict)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
{-| -}
|
|
111
|
+
optionalString : String -> Parser (Maybe String)
|
|
112
|
+
optionalString key =
|
|
113
|
+
custom key
|
|
114
|
+
(\stringList ->
|
|
115
|
+
case stringList of
|
|
116
|
+
str :: _ ->
|
|
117
|
+
Ok (Just str)
|
|
118
|
+
|
|
119
|
+
_ ->
|
|
120
|
+
Ok Nothing
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
{-| -}
|
|
125
|
+
string : String -> Parser String
|
|
126
|
+
string key =
|
|
127
|
+
custom key
|
|
128
|
+
(\stringList ->
|
|
129
|
+
case stringList of
|
|
130
|
+
[ str ] ->
|
|
131
|
+
Ok str
|
|
132
|
+
|
|
133
|
+
_ ->
|
|
134
|
+
Err ("Missing key " ++ key)
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
{-| -}
|
|
139
|
+
custom : String -> (List String -> Result String a) -> Parser a
|
|
140
|
+
custom key customFn =
|
|
141
|
+
Parser <|
|
|
142
|
+
\dict ->
|
|
143
|
+
customFn (Maybe.withDefault [] (Dict.get key dict))
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
{-| -}
|
|
147
|
+
strings : String -> Parser (List String)
|
|
148
|
+
strings key =
|
|
149
|
+
custom key
|
|
150
|
+
(\stringList -> Ok stringList)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
{-| -}
|
|
154
|
+
fromString : String -> QueryParams
|
|
155
|
+
fromString =
|
|
156
|
+
QueryParams
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
{-| -}
|
|
160
|
+
toString : QueryParams -> String
|
|
161
|
+
toString (QueryParams queryParams) =
|
|
162
|
+
queryParams
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
{-| -}
|
|
166
|
+
parse : Parser a -> QueryParams -> Result String a
|
|
167
|
+
parse (Parser queryParser) queryParams =
|
|
168
|
+
queryParams
|
|
169
|
+
|> toDict
|
|
170
|
+
|> queryParser
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
{-| -}
|
|
174
|
+
toDict : QueryParams -> Dict String (List String)
|
|
175
|
+
toDict (QueryParams queryParams) =
|
|
176
|
+
prepareQuery (Just queryParams)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
prepareQuery : Maybe String -> Dict String (List String)
|
|
180
|
+
prepareQuery maybeQuery =
|
|
181
|
+
case maybeQuery of
|
|
182
|
+
Nothing ->
|
|
183
|
+
Dict.empty
|
|
184
|
+
|
|
185
|
+
Just qry ->
|
|
186
|
+
List.foldr addParam Dict.empty (String.split "&" qry)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
addParam : String -> Dict String (List String) -> Dict String (List String)
|
|
190
|
+
addParam segment dict =
|
|
191
|
+
case String.split "=" segment of
|
|
192
|
+
[ rawKey, rawValue ] ->
|
|
193
|
+
case Url.percentDecode rawKey of
|
|
194
|
+
Nothing ->
|
|
195
|
+
dict
|
|
196
|
+
|
|
197
|
+
Just key ->
|
|
198
|
+
case Url.percentDecode rawValue of
|
|
199
|
+
Nothing ->
|
|
200
|
+
dict
|
|
201
|
+
|
|
202
|
+
Just value ->
|
|
203
|
+
Dict.update key (addToParametersHelp value) dict
|
|
204
|
+
|
|
205
|
+
_ ->
|
|
206
|
+
dict
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
addToParametersHelp : a -> Maybe (List a) -> Maybe (List a)
|
|
210
|
+
addToParametersHelp value maybeList =
|
|
211
|
+
case maybeList of
|
|
212
|
+
Nothing ->
|
|
213
|
+
Just [ value ]
|
|
214
|
+
|
|
215
|
+
Just list ->
|
|
216
|
+
Just (value :: list)
|