elm-pages 3.0.0-beta.12 → 3.0.0-beta.14
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/README.md +1 -1
- package/codegen/elm-pages-codegen.js +1496 -1126
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-DeadCodeEliminateData.elmi +0 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-DeadCodeEliminateData.elmo +0 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-DeadCodeEliminateDataTest.elmo +0 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/d.dat +0 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/i.dat +0 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/o.dat +0 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm.json +1 -1
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/Runner.elm.js +151 -39
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/node_runner.js +1 -1
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/node_supervisor.js +1 -1
- package/generator/dead-code-review/elm.json +3 -2
- package/generator/dead-code-review/src/Pages/Review/DeadCodeEliminateData.elm +58 -10
- package/generator/dead-code-review/tests/Pages/Review/DeadCodeEliminateDataTest.elm +45 -29
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/d.dat +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/i.dat +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/o.dat +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm.json +1 -1
- package/generator/review/elm-stuff/tests-0.19.1/js/Runner.elm.js +45 -4
- package/generator/review/elm-stuff/tests-0.19.1/js/node_runner.js +1 -1
- package/generator/review/elm-stuff/tests-0.19.1/js/node_supervisor.js +1 -1
- package/generator/review/elm.json +3 -3
- package/generator/src/RouteBuilder.elm +66 -52
- package/generator/src/SharedTemplate.elm +3 -2
- package/generator/src/SiteConfig.elm +3 -2
- package/generator/src/build.js +6 -6
- package/generator/src/cli.js +12 -7
- package/generator/src/compatibility-key.js +1 -1
- package/generator/src/dev-server.js +7 -7
- package/generator/src/render-test.js +109 -0
- package/generator/src/render.js +77 -51
- package/generator/src/request-cache.js +149 -158
- package/generator/template/app/Api.elm +2 -2
- package/generator/template/app/Route/Index.elm +3 -3
- package/generator/template/app/Shared.elm +3 -3
- package/generator/template/app/Site.elm +3 -3
- package/package.json +11 -11
- package/src/ApiRoute.elm +63 -57
- package/src/BackendTask/Env.elm +87 -0
- package/src/{DataSource → BackendTask}/File.elm +89 -43
- package/src/{DataSource → BackendTask}/Glob.elm +134 -125
- package/src/BackendTask/Http.elm +637 -0
- package/src/{DataSource → BackendTask}/Internal/Glob.elm +1 -1
- package/src/BackendTask/Internal/Request.elm +28 -0
- package/src/BackendTask/Port.elm +202 -0
- package/src/{DataSource.elm → BackendTask.elm} +223 -207
- package/src/Exception.elm +37 -0
- package/src/Form.elm +20 -20
- package/src/Head.elm +7 -7
- package/src/Internal/ApiRoute.elm +7 -5
- package/src/PageServerResponse.elm +6 -1
- package/src/Pages/Generate.elm +35 -63
- package/src/Pages/Internal/Platform/Cli.elm +422 -731
- package/src/Pages/Internal/Platform/Cli.elm.bak +22 -22
- package/src/Pages/Internal/Platform/CompatibilityKey.elm +1 -1
- package/src/Pages/Internal/Platform/Effect.elm +0 -1
- package/src/Pages/Internal/Platform/GeneratorApplication.elm +53 -113
- package/src/Pages/Internal/Platform/StaticResponses.elm +72 -256
- package/src/Pages/Internal/Platform/ToJsPayload.elm +4 -4
- package/src/Pages/Internal/Platform.elm +25 -31
- package/src/Pages/Internal/Script.elm +17 -0
- package/src/Pages/Internal/StaticHttpBody.elm +35 -1
- package/src/Pages/Manifest.elm +5 -4
- package/src/Pages/ProgramConfig.elm +8 -7
- package/src/Pages/Script.elm +34 -25
- package/src/Pages/SiteConfig.elm +3 -2
- package/src/Pages/StaticHttp/Request.elm +2 -2
- package/src/Pages/StaticHttpRequest.elm +37 -90
- package/src/RequestsAndPending.elm +8 -19
- package/src/Server/Request.elm +14 -14
- package/src/Server/Session.elm +34 -34
- package/src/Server/SetCookie.elm +1 -1
- package/src/DataSource/Env.elm +0 -62
- package/src/DataSource/Http.elm +0 -446
- package/src/DataSource/Internal/Request.elm +0 -20
- package/src/DataSource/Port.elm +0 -90
package/src/ApiRoute.elm
CHANGED
|
@@ -5,12 +5,12 @@ module ApiRoute exposing
|
|
|
5
5
|
, ApiRoute, ApiRouteBuilder, Response
|
|
6
6
|
, capture, literal, slash, succeed
|
|
7
7
|
, withGlobalHeadTags
|
|
8
|
-
, toJson, getBuildTimeRoutes,
|
|
8
|
+
, toJson, getBuildTimeRoutes, getGlobalHeadTagsBackendTask
|
|
9
9
|
)
|
|
10
10
|
|
|
11
11
|
{-| ApiRoute's are defined in `src/Api.elm` and are a way to generate files, like RSS feeds, sitemaps, or any text-based file that you output with an Elm function! You get access
|
|
12
|
-
to a
|
|
13
|
-
the
|
|
12
|
+
to a BackendTask so you can pull in HTTP data, etc. Because ApiRoutes don't hydrate into Elm apps (like pages in elm-pages do), you can pull in as much data as you want in
|
|
13
|
+
the BackendTask for your ApiRoutes, and it won't effect the payload size. Instead, the size of an ApiRoute is just the content you output for that route.
|
|
14
14
|
|
|
15
15
|
Similar to your elm-pages Route Modules, ApiRoute's can be either server-rendered or pre-rendered. Let's compare the differences between pre-rendered and server-rendered ApiRoutes, and the different
|
|
16
16
|
use cases they support.
|
|
@@ -25,7 +25,7 @@ A pre-rendered ApiRoute is just a generated file. For example:
|
|
|
25
25
|
- A redirect file for a hosting provider like Netlify
|
|
26
26
|
|
|
27
27
|
You could even generate a JavaScript file, an Elm file, or any file with a String body! It's really just a way to generate files, which are typically used to serve files to a user or Browser, but you execute them, copy them, etc. The only limit is your imagination!
|
|
28
|
-
The beauty is that you have a way to 1) pull in type-safe data using
|
|
28
|
+
The beauty is that you have a way to 1) pull in type-safe data using BackendTask's, and 2) write those files, and all in pure Elm!
|
|
29
29
|
|
|
30
30
|
@docs single, preRender
|
|
31
31
|
|
|
@@ -62,11 +62,11 @@ You define your ApiRoute's in `app/Api.elm`. Here's a simple example:
|
|
|
62
62
|
module Api exposing (routes)
|
|
63
63
|
|
|
64
64
|
import ApiRoute
|
|
65
|
-
import
|
|
65
|
+
import BackendTask exposing (BackendTask)
|
|
66
66
|
import Server.Request
|
|
67
67
|
|
|
68
68
|
routes :
|
|
69
|
-
|
|
69
|
+
BackendTask (List Route)
|
|
70
70
|
-> (Maybe { indent : Int, newLines : Bool } -> Html Never -> String)
|
|
71
71
|
-> List (ApiRoute.ApiRoute ApiRoute.Response)
|
|
72
72
|
routes getStaticRoutes htmlToString =
|
|
@@ -89,7 +89,7 @@ You define your ApiRoute's in `app/Api.elm`. Here's a simple example:
|
|
|
89
89
|
preRenderedExample =
|
|
90
90
|
ApiRoute.succeed
|
|
91
91
|
(\userId ->
|
|
92
|
-
|
|
92
|
+
BackendTask.succeed
|
|
93
93
|
(Json.Encode.object
|
|
94
94
|
[ ( "id", Json.Encode.string userId )
|
|
95
95
|
, ( "name", "Data for user " ++ userId |> Json.Encode.string )
|
|
@@ -103,7 +103,7 @@ You define your ApiRoute's in `app/Api.elm`. Here's a simple example:
|
|
|
103
103
|
|> ApiRoute.literal ".json"
|
|
104
104
|
|> ApiRoute.preRender
|
|
105
105
|
(\route ->
|
|
106
|
-
|
|
106
|
+
BackendTask.succeed
|
|
107
107
|
[ route "1"
|
|
108
108
|
, route "2"
|
|
109
109
|
, route "3"
|
|
@@ -144,7 +144,7 @@ You define your ApiRoute's in `app/Api.elm`. Here's a simple example:
|
|
|
144
144
|
)
|
|
145
145
|
]
|
|
146
146
|
|> Response.json
|
|
147
|
-
|>
|
|
147
|
+
|> BackendTask.succeed
|
|
148
148
|
)
|
|
149
149
|
Server.Request.rawBody
|
|
150
150
|
Server.Request.method
|
|
@@ -168,12 +168,12 @@ You define your ApiRoute's in `app/Api.elm`. Here's a simple example:
|
|
|
168
168
|
|
|
169
169
|
## Internals
|
|
170
170
|
|
|
171
|
-
@docs toJson, getBuildTimeRoutes,
|
|
171
|
+
@docs toJson, getBuildTimeRoutes, getGlobalHeadTagsBackendTask
|
|
172
172
|
|
|
173
173
|
-}
|
|
174
174
|
|
|
175
|
-
import
|
|
176
|
-
import
|
|
175
|
+
import BackendTask exposing (BackendTask)
|
|
176
|
+
import Exception exposing (Throwable)
|
|
177
177
|
import Head
|
|
178
178
|
import Internal.ApiRoute exposing (ApiRoute(..), ApiRouteBuilder(..))
|
|
179
179
|
import Json.Decode as Decode
|
|
@@ -190,53 +190,65 @@ type alias ApiRoute response =
|
|
|
190
190
|
|
|
191
191
|
|
|
192
192
|
{-| Same as [`preRender`](#preRender), but for an ApiRoute that has no dynamic segments. This is just a bit simpler because
|
|
193
|
-
since there are no dynamic segments, you don't need to provide a
|
|
193
|
+
since there are no dynamic segments, you don't need to provide a BackendTask with the list of dynamic segments to pre-render because there is only a single possible route.
|
|
194
194
|
-}
|
|
195
|
-
single : ApiRouteBuilder (
|
|
195
|
+
single : ApiRouteBuilder (BackendTask Throwable String) (List String) -> ApiRoute Response
|
|
196
196
|
single handler =
|
|
197
197
|
handler
|
|
198
|
-
|> preRender (\constructor ->
|
|
198
|
+
|> preRender (\constructor -> BackendTask.succeed [ constructor ])
|
|
199
199
|
|
|
200
200
|
|
|
201
201
|
{-| -}
|
|
202
|
-
serverRender : ApiRouteBuilder (Server.Request.Parser (
|
|
202
|
+
serverRender : ApiRouteBuilder (Server.Request.Parser (BackendTask Never (Server.Response.Response Never Never))) constructor -> ApiRoute Response
|
|
203
203
|
serverRender ((ApiRouteBuilder patterns pattern _ _ _) as fullHandler) =
|
|
204
204
|
ApiRoute
|
|
205
205
|
{ regex = Regex.fromString ("^" ++ pattern ++ "$") |> Maybe.withDefault Regex.never
|
|
206
206
|
, matchesToResponse =
|
|
207
|
-
\path ->
|
|
207
|
+
\serverRequest path ->
|
|
208
208
|
Internal.ApiRoute.tryMatch path fullHandler
|
|
209
209
|
|> Maybe.map
|
|
210
|
-
(\
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
210
|
+
(\toBackendTask ->
|
|
211
|
+
Server.Request.getDecoder toBackendTask
|
|
212
|
+
|> (\decoder ->
|
|
213
|
+
Decode.decodeValue decoder serverRequest
|
|
214
|
+
|> Result.mapError Decode.errorToString
|
|
215
|
+
|> BackendTask.fromResult
|
|
216
|
+
|> BackendTask.map Just
|
|
217
|
+
)
|
|
218
|
+
|> BackendTask.onError
|
|
219
|
+
(\stringError ->
|
|
220
|
+
-- TODO make error with title and better context/formatting
|
|
221
|
+
Exception.fromString stringError |> BackendTask.fail
|
|
222
|
+
)
|
|
223
|
+
|> BackendTask.andThen
|
|
215
224
|
(\rendered ->
|
|
216
225
|
case rendered of
|
|
217
226
|
Just (Ok okRendered) ->
|
|
218
227
|
okRendered
|
|
228
|
+
|> BackendTask.onError never
|
|
219
229
|
|
|
220
230
|
Just (Err errors) ->
|
|
221
231
|
errors
|
|
222
232
|
|> Server.Request.errorsToString
|
|
223
233
|
|> Server.Response.plainText
|
|
224
234
|
|> Server.Response.withStatusCode 400
|
|
225
|
-
|>
|
|
235
|
+
|> BackendTask.succeed
|
|
236
|
+
|> BackendTask.onError never
|
|
226
237
|
|
|
227
238
|
Nothing ->
|
|
228
239
|
Server.Response.plainText "No matching request handler"
|
|
229
240
|
|> Server.Response.withStatusCode 400
|
|
230
|
-
|>
|
|
241
|
+
|> BackendTask.succeed
|
|
242
|
+
|> BackendTask.onError never
|
|
231
243
|
)
|
|
232
244
|
)
|
|
233
|
-
|> Maybe.map (
|
|
245
|
+
|> Maybe.map (BackendTask.map (Server.Response.toJson >> Just))
|
|
234
246
|
|> Maybe.withDefault
|
|
235
|
-
(
|
|
236
|
-
, buildTimeRoutes =
|
|
247
|
+
(BackendTask.succeed Nothing)
|
|
248
|
+
, buildTimeRoutes = BackendTask.succeed []
|
|
237
249
|
, handleRoute =
|
|
238
250
|
\path ->
|
|
239
|
-
|
|
251
|
+
BackendTask.succeed
|
|
240
252
|
(case Internal.ApiRoute.tryMatch path fullHandler of
|
|
241
253
|
Just _ ->
|
|
242
254
|
True
|
|
@@ -251,26 +263,26 @@ serverRender ((ApiRouteBuilder patterns pattern _ _ _) as fullHandler) =
|
|
|
251
263
|
|
|
252
264
|
|
|
253
265
|
{-| -}
|
|
254
|
-
preRenderWithFallback : (constructor ->
|
|
266
|
+
preRenderWithFallback : (constructor -> BackendTask Throwable (List (List String))) -> ApiRouteBuilder (BackendTask Throwable (Server.Response.Response Never Never)) constructor -> ApiRoute Response
|
|
255
267
|
preRenderWithFallback buildUrls ((ApiRouteBuilder patterns pattern _ toString constructor) as fullHandler) =
|
|
256
268
|
let
|
|
257
|
-
buildTimeRoutes__ :
|
|
269
|
+
buildTimeRoutes__ : BackendTask Throwable (List String)
|
|
258
270
|
buildTimeRoutes__ =
|
|
259
271
|
buildUrls (constructor [])
|
|
260
|
-
|>
|
|
272
|
+
|> BackendTask.map (List.map toString)
|
|
261
273
|
in
|
|
262
274
|
ApiRoute
|
|
263
275
|
{ regex = Regex.fromString ("^" ++ pattern ++ "$") |> Maybe.withDefault Regex.never
|
|
264
276
|
, matchesToResponse =
|
|
265
|
-
\path ->
|
|
277
|
+
\_ path ->
|
|
266
278
|
Internal.ApiRoute.tryMatch path fullHandler
|
|
267
|
-
|> Maybe.map (
|
|
279
|
+
|> Maybe.map (BackendTask.map (Server.Response.toJson >> Just))
|
|
268
280
|
|> Maybe.withDefault
|
|
269
|
-
(
|
|
281
|
+
(BackendTask.succeed Nothing)
|
|
270
282
|
, buildTimeRoutes = buildTimeRoutes__
|
|
271
283
|
, handleRoute =
|
|
272
284
|
\path ->
|
|
273
|
-
|
|
285
|
+
BackendTask.succeed
|
|
274
286
|
(case Internal.ApiRoute.tryMatch path fullHandler of
|
|
275
287
|
Just _ ->
|
|
276
288
|
True
|
|
@@ -293,42 +305,42 @@ encodeStaticFileBody fileBody =
|
|
|
293
305
|
|
|
294
306
|
|
|
295
307
|
{-| -}
|
|
296
|
-
preRender : (constructor ->
|
|
308
|
+
preRender : (constructor -> BackendTask Throwable (List (List String))) -> ApiRouteBuilder (BackendTask Throwable String) constructor -> ApiRoute Response
|
|
297
309
|
preRender buildUrls ((ApiRouteBuilder patterns pattern _ toString constructor) as fullHandler) =
|
|
298
310
|
let
|
|
299
|
-
buildTimeRoutes__ :
|
|
311
|
+
buildTimeRoutes__ : BackendTask Throwable (List String)
|
|
300
312
|
buildTimeRoutes__ =
|
|
301
313
|
buildUrls (constructor [])
|
|
302
|
-
|>
|
|
314
|
+
|> BackendTask.map (List.map toString)
|
|
303
315
|
|
|
304
|
-
preBuiltMatches :
|
|
316
|
+
preBuiltMatches : BackendTask Throwable (List (List String))
|
|
305
317
|
preBuiltMatches =
|
|
306
318
|
buildUrls (constructor [])
|
|
307
319
|
in
|
|
308
320
|
ApiRoute
|
|
309
321
|
{ regex = Regex.fromString ("^" ++ pattern ++ "$") |> Maybe.withDefault Regex.never
|
|
310
322
|
, matchesToResponse =
|
|
311
|
-
\path ->
|
|
323
|
+
\_ path ->
|
|
312
324
|
let
|
|
313
325
|
matches : List String
|
|
314
326
|
matches =
|
|
315
327
|
Internal.ApiRoute.pathToMatches path fullHandler
|
|
316
328
|
|
|
317
|
-
routeFound :
|
|
329
|
+
routeFound : BackendTask Throwable Bool
|
|
318
330
|
routeFound =
|
|
319
331
|
preBuiltMatches
|
|
320
|
-
|>
|
|
332
|
+
|> BackendTask.map (List.member matches)
|
|
321
333
|
in
|
|
322
334
|
routeFound
|
|
323
|
-
|>
|
|
335
|
+
|> BackendTask.andThen
|
|
324
336
|
(\found ->
|
|
325
337
|
if found then
|
|
326
338
|
Internal.ApiRoute.tryMatch path fullHandler
|
|
327
|
-
|> Maybe.map (
|
|
328
|
-
|> Maybe.withDefault (
|
|
339
|
+
|> Maybe.map (BackendTask.map (encodeStaticFileBody >> Just))
|
|
340
|
+
|> Maybe.withDefault (BackendTask.succeed Nothing)
|
|
329
341
|
|
|
330
342
|
else
|
|
331
|
-
|
|
343
|
+
BackendTask.succeed Nothing
|
|
332
344
|
)
|
|
333
345
|
, buildTimeRoutes = buildTimeRoutes__
|
|
334
346
|
, handleRoute =
|
|
@@ -339,7 +351,7 @@ preRender buildUrls ((ApiRouteBuilder patterns pattern _ toString constructor) a
|
|
|
339
351
|
Internal.ApiRoute.pathToMatches path fullHandler
|
|
340
352
|
in
|
|
341
353
|
preBuiltMatches
|
|
342
|
-
|>
|
|
354
|
+
|> BackendTask.map (List.member matches)
|
|
343
355
|
, pattern = patterns
|
|
344
356
|
, kind = "prerender"
|
|
345
357
|
, globalHeadTags = Nothing
|
|
@@ -422,25 +434,19 @@ capture (ApiRouteBuilder patterns pattern previousHandler toString constructor)
|
|
|
422
434
|
|
|
423
435
|
{-| For internal use by generated code. Not so useful in user-land.
|
|
424
436
|
-}
|
|
425
|
-
getBuildTimeRoutes : ApiRoute response ->
|
|
437
|
+
getBuildTimeRoutes : ApiRoute response -> BackendTask Throwable (List String)
|
|
426
438
|
getBuildTimeRoutes (ApiRoute handler) =
|
|
427
439
|
handler.buildTimeRoutes
|
|
428
440
|
|
|
429
441
|
|
|
430
442
|
{-| Include head tags on every page's HTML.
|
|
431
443
|
-}
|
|
432
|
-
withGlobalHeadTags :
|
|
444
|
+
withGlobalHeadTags : BackendTask Throwable (List Head.Tag) -> ApiRoute response -> ApiRoute response
|
|
433
445
|
withGlobalHeadTags globalHeadTags (ApiRoute handler) =
|
|
434
446
|
ApiRoute { handler | globalHeadTags = Just globalHeadTags }
|
|
435
447
|
|
|
436
448
|
|
|
437
449
|
{-| -}
|
|
438
|
-
|
|
439
|
-
|
|
450
|
+
getGlobalHeadTagsBackendTask : ApiRoute response -> Maybe (BackendTask Throwable (List Head.Tag))
|
|
451
|
+
getGlobalHeadTagsBackendTask (ApiRoute handler) =
|
|
440
452
|
handler.globalHeadTags
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
--captureRest : ApiRouteBuilder (List String -> a) b -> ApiRouteBuilder a b
|
|
445
|
-
--captureRest previousHandler =
|
|
446
|
-
-- Debug.todo ""
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
module BackendTask.Env exposing
|
|
2
|
+
( get, expect
|
|
3
|
+
, Error(..)
|
|
4
|
+
)
|
|
5
|
+
|
|
6
|
+
{-| Because BackendTask's in `elm-pages` never run in the browser (see [the BackendTask docs](BackendTask)), you can access environment variables securely. As long as the environment variable isn't sent
|
|
7
|
+
down into the final `Data` value, it won't end up in the client!
|
|
8
|
+
|
|
9
|
+
import BackendTask exposing (BackendTask)
|
|
10
|
+
import BackendTask.Env
|
|
11
|
+
|
|
12
|
+
type alias EnvVariables =
|
|
13
|
+
{ sendGridKey : String
|
|
14
|
+
, siteUrl : String
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
sendEmail : Email -> BackendTask ()
|
|
18
|
+
sendEmail email =
|
|
19
|
+
BackendTask.map2 EnvVariables
|
|
20
|
+
(BackendTask.Env.expect "SEND_GRID_KEY")
|
|
21
|
+
(BackendTask.Env.get "BASE_URL"
|
|
22
|
+
|> BackendTask.map (Maybe.withDefault "http://localhost:1234")
|
|
23
|
+
)
|
|
24
|
+
|> BackendTask.andThen (sendEmailBackendTask email)
|
|
25
|
+
|
|
26
|
+
sendEmailBackendTask : Email -> EnvVariables -> BackendTask ()
|
|
27
|
+
sendEmailBackendTask email envVariables =
|
|
28
|
+
Debug.todo "Not defined here"
|
|
29
|
+
|
|
30
|
+
@docs get, expect
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
## Errors
|
|
34
|
+
|
|
35
|
+
@docs Error
|
|
36
|
+
|
|
37
|
+
-}
|
|
38
|
+
|
|
39
|
+
import BackendTask exposing (BackendTask)
|
|
40
|
+
import BackendTask.Http
|
|
41
|
+
import BackendTask.Internal.Request
|
|
42
|
+
import Exception exposing (Catchable)
|
|
43
|
+
import Json.Decode as Decode
|
|
44
|
+
import Json.Encode as Encode
|
|
45
|
+
import TerminalText
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
{-| -}
|
|
49
|
+
type Error
|
|
50
|
+
= MissingEnvVariable String
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
{-| Get an environment variable, or Nothing if there is no environment variable matching that name.
|
|
54
|
+
-}
|
|
55
|
+
get : String -> BackendTask error (Maybe String)
|
|
56
|
+
get envVariableName =
|
|
57
|
+
BackendTask.Internal.Request.request
|
|
58
|
+
{ name = "env"
|
|
59
|
+
, body = BackendTask.Http.jsonBody (Encode.string envVariableName)
|
|
60
|
+
, expect =
|
|
61
|
+
BackendTask.Http.expectJson
|
|
62
|
+
(Decode.nullable Decode.string)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
{-| Get an environment variable, or a BackendTask failure if there is no environment variable matching that name.
|
|
67
|
+
-}
|
|
68
|
+
expect : String -> BackendTask (Catchable Error) String
|
|
69
|
+
expect envVariableName =
|
|
70
|
+
envVariableName
|
|
71
|
+
|> get
|
|
72
|
+
|> BackendTask.andThen
|
|
73
|
+
(\maybeValue ->
|
|
74
|
+
maybeValue
|
|
75
|
+
|> Result.fromMaybe
|
|
76
|
+
(Exception.Catchable (MissingEnvVariable envVariableName)
|
|
77
|
+
{ title = "Missing Env Variable"
|
|
78
|
+
, body =
|
|
79
|
+
[ TerminalText.text "BackendTask.Env.expect was expecting a variable `"
|
|
80
|
+
, TerminalText.yellow envVariableName
|
|
81
|
+
, TerminalText.text "` but couldn't find a variable with that name."
|
|
82
|
+
]
|
|
83
|
+
|> TerminalText.toString
|
|
84
|
+
}
|
|
85
|
+
)
|
|
86
|
+
|> BackendTask.fromResult
|
|
87
|
+
)
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
module
|
|
1
|
+
module BackendTask.File exposing
|
|
2
2
|
( bodyWithFrontmatter, bodyWithoutFrontmatter, onlyFrontmatter
|
|
3
3
|
, jsonFile, rawFile
|
|
4
|
+
, FileReadError(..)
|
|
4
5
|
)
|
|
5
6
|
|
|
6
|
-
{-| This module lets you read files from the local filesystem as a [`
|
|
7
|
+
{-| This module lets you read files from the local filesystem as a [`BackendTask`](BackendTask#BackendTask).
|
|
7
8
|
File paths are relative to the root of your `elm-pages` project (next to the `elm.json` file and `src/` directory).
|
|
8
9
|
|
|
9
10
|
|
|
@@ -40,12 +41,19 @@ plain old JSON in Elm.
|
|
|
40
41
|
|
|
41
42
|
@docs jsonFile, rawFile
|
|
42
43
|
|
|
44
|
+
|
|
45
|
+
## Exceptions
|
|
46
|
+
|
|
47
|
+
@docs FileReadError
|
|
48
|
+
|
|
43
49
|
-}
|
|
44
50
|
|
|
45
|
-
import
|
|
46
|
-
import
|
|
47
|
-
import
|
|
51
|
+
import BackendTask exposing (BackendTask)
|
|
52
|
+
import BackendTask.Http
|
|
53
|
+
import BackendTask.Internal.Request
|
|
54
|
+
import Exception exposing (Catchable)
|
|
48
55
|
import Json.Decode as Decode exposing (Decoder)
|
|
56
|
+
import TerminalText
|
|
49
57
|
|
|
50
58
|
|
|
51
59
|
frontmatter : Decoder frontmatter -> Decoder frontmatter
|
|
@@ -55,11 +63,11 @@ frontmatter frontmatterDecoder =
|
|
|
55
63
|
|
|
56
64
|
{-|
|
|
57
65
|
|
|
58
|
-
import
|
|
59
|
-
import
|
|
66
|
+
import BackendTask exposing (BackendTask)
|
|
67
|
+
import BackendTask.File as File
|
|
60
68
|
import Decode as Decode exposing (Decoder)
|
|
61
69
|
|
|
62
|
-
blogPost :
|
|
70
|
+
blogPost : BackendTask BlogPostMetadata
|
|
63
71
|
blogPost =
|
|
64
72
|
File.bodyWithFrontmatter blogPostDecoder
|
|
65
73
|
"blog/hello-world.md"
|
|
@@ -81,7 +89,7 @@ frontmatter frontmatterDecoder =
|
|
|
81
89
|
Decode.map (String.split " ")
|
|
82
90
|
Decode.string
|
|
83
91
|
|
|
84
|
-
This will give us a
|
|
92
|
+
This will give us a BackendTask that results in the following value:
|
|
85
93
|
|
|
86
94
|
value =
|
|
87
95
|
{ body = "Hey there! This is my first post :)"
|
|
@@ -91,13 +99,13 @@ This will give us a DataSource that results in the following value:
|
|
|
91
99
|
|
|
92
100
|
It's common to parse the body with a markdown parser or other format.
|
|
93
101
|
|
|
94
|
-
import
|
|
95
|
-
import
|
|
102
|
+
import BackendTask exposing (BackendTask)
|
|
103
|
+
import BackendTask.File as File
|
|
96
104
|
import Decode as Decode exposing (Decoder)
|
|
97
105
|
import Html exposing (Html)
|
|
98
106
|
|
|
99
107
|
example :
|
|
100
|
-
|
|
108
|
+
BackendTask
|
|
101
109
|
{ title : String
|
|
102
110
|
, body : List (Html msg)
|
|
103
111
|
}
|
|
@@ -133,7 +141,7 @@ It's common to parse the body with a markdown parser or other format.
|
|
|
133
141
|
)
|
|
134
142
|
|
|
135
143
|
-}
|
|
136
|
-
bodyWithFrontmatter : (String -> Decoder frontmatter) -> String ->
|
|
144
|
+
bodyWithFrontmatter : (String -> Decoder frontmatter) -> String -> BackendTask (Catchable (FileReadError Decode.Error)) frontmatter
|
|
137
145
|
bodyWithFrontmatter frontmatterDecoder filePath =
|
|
138
146
|
read filePath
|
|
139
147
|
(body
|
|
@@ -144,16 +152,23 @@ bodyWithFrontmatter frontmatterDecoder filePath =
|
|
|
144
152
|
)
|
|
145
153
|
|
|
146
154
|
|
|
155
|
+
{-| -}
|
|
156
|
+
type FileReadError decoding
|
|
157
|
+
= FileDoesntExist
|
|
158
|
+
| FileReadError String
|
|
159
|
+
| DecodingError decoding
|
|
160
|
+
|
|
161
|
+
|
|
147
162
|
{-| Same as `bodyWithFrontmatter` except it doesn't include the body.
|
|
148
163
|
|
|
149
164
|
This is often useful when you're aggregating data, for example getting a listing of blog posts and need to extract
|
|
150
165
|
just the metadata.
|
|
151
166
|
|
|
152
|
-
import
|
|
153
|
-
import
|
|
167
|
+
import BackendTask exposing (BackendTask)
|
|
168
|
+
import BackendTask.File as File
|
|
154
169
|
import Decode as Decode exposing (Decoder)
|
|
155
170
|
|
|
156
|
-
blogPost :
|
|
171
|
+
blogPost : BackendTask BlogPostMetadata
|
|
157
172
|
blogPost =
|
|
158
173
|
File.onlyFrontmatter
|
|
159
174
|
blogPostDecoder
|
|
@@ -171,34 +186,34 @@ just the metadata.
|
|
|
171
186
|
(Decode.field "tags" (Decode.list Decode.string))
|
|
172
187
|
|
|
173
188
|
If you wanted to use this to get this metadata for all blog posts in a folder, you could use
|
|
174
|
-
the [`
|
|
189
|
+
the [`BackendTask`](BackendTask) API along with [`BackendTask.Glob`](BackendTask-Glob).
|
|
175
190
|
|
|
176
|
-
import
|
|
177
|
-
import
|
|
191
|
+
import BackendTask exposing (BackendTask)
|
|
192
|
+
import BackendTask.File as File
|
|
178
193
|
import Decode as Decode exposing (Decoder)
|
|
179
194
|
|
|
180
|
-
blogPostFiles :
|
|
195
|
+
blogPostFiles : BackendTask (List String)
|
|
181
196
|
blogPostFiles =
|
|
182
197
|
Glob.succeed identity
|
|
183
198
|
|> Glob.captureFilePath
|
|
184
199
|
|> Glob.match (Glob.literal "content/blog/")
|
|
185
200
|
|> Glob.match Glob.wildcard
|
|
186
201
|
|> Glob.match (Glob.literal ".md")
|
|
187
|
-
|> Glob.
|
|
202
|
+
|> Glob.toBackendTask
|
|
188
203
|
|
|
189
|
-
allMetadata :
|
|
204
|
+
allMetadata : BackendTask (List BlogPostMetadata)
|
|
190
205
|
allMetadata =
|
|
191
206
|
blogPostFiles
|
|
192
|
-
|>
|
|
207
|
+
|> BackendTask.map
|
|
193
208
|
(List.map
|
|
194
209
|
(File.onlyFrontmatter
|
|
195
210
|
blogPostDecoder
|
|
196
211
|
)
|
|
197
212
|
)
|
|
198
|
-
|>
|
|
213
|
+
|> BackendTask.resolve
|
|
199
214
|
|
|
200
215
|
-}
|
|
201
|
-
onlyFrontmatter : Decoder frontmatter -> String ->
|
|
216
|
+
onlyFrontmatter : Decoder frontmatter -> String -> BackendTask (Catchable (FileReadError Decode.Error)) frontmatter
|
|
202
217
|
onlyFrontmatter frontmatterDecoder filePath =
|
|
203
218
|
read filePath
|
|
204
219
|
(frontmatter frontmatterDecoder)
|
|
@@ -216,16 +231,16 @@ tags: elm
|
|
|
216
231
|
Hey there! This is my first post :)
|
|
217
232
|
```
|
|
218
233
|
|
|
219
|
-
import
|
|
234
|
+
import BackendTask exposing (BackendTask)
|
|
220
235
|
|
|
221
|
-
data :
|
|
236
|
+
data : BackendTask String
|
|
222
237
|
data =
|
|
223
238
|
bodyWithoutFrontmatter "blog/hello-world.md"
|
|
224
239
|
|
|
225
240
|
Then data will yield the value `"Hey there! This is my first post :)"`.
|
|
226
241
|
|
|
227
242
|
-}
|
|
228
|
-
bodyWithoutFrontmatter : String ->
|
|
243
|
+
bodyWithoutFrontmatter : String -> BackendTask (Catchable (FileReadError decoderError)) String
|
|
229
244
|
bodyWithoutFrontmatter filePath =
|
|
230
245
|
read filePath
|
|
231
246
|
body
|
|
@@ -241,15 +256,15 @@ use `jsonFile` to get the benefits of the `Decode` here.
|
|
|
241
256
|
|
|
242
257
|
You could read a file called `hello.txt` in your root project directory like this:
|
|
243
258
|
|
|
244
|
-
import
|
|
245
|
-
import
|
|
259
|
+
import BackendTask exposing (BackendTask)
|
|
260
|
+
import BackendTask.File as File
|
|
246
261
|
|
|
247
|
-
elmJsonFile :
|
|
262
|
+
elmJsonFile : BackendTask String
|
|
248
263
|
elmJsonFile =
|
|
249
264
|
File.rawFile "hello.txt"
|
|
250
265
|
|
|
251
266
|
-}
|
|
252
|
-
rawFile : String ->
|
|
267
|
+
rawFile : String -> BackendTask (Catchable (FileReadError decoderError)) String
|
|
253
268
|
rawFile filePath =
|
|
254
269
|
read filePath (Decode.field "rawFile" Decode.string)
|
|
255
270
|
|
|
@@ -258,10 +273,10 @@ rawFile filePath =
|
|
|
258
273
|
|
|
259
274
|
The Decode will strip off any unused JSON data.
|
|
260
275
|
|
|
261
|
-
import
|
|
262
|
-
import
|
|
276
|
+
import BackendTask exposing (BackendTask)
|
|
277
|
+
import BackendTask.File as File
|
|
263
278
|
|
|
264
|
-
sourceDirectories :
|
|
279
|
+
sourceDirectories : BackendTask (List String)
|
|
265
280
|
sourceDirectories =
|
|
266
281
|
File.jsonFile
|
|
267
282
|
(Decode.field
|
|
@@ -271,15 +286,24 @@ The Decode will strip off any unused JSON data.
|
|
|
271
286
|
"elm.json"
|
|
272
287
|
|
|
273
288
|
-}
|
|
274
|
-
jsonFile : Decoder a -> String ->
|
|
289
|
+
jsonFile : Decoder a -> String -> BackendTask (Catchable (FileReadError Decode.Error)) a
|
|
275
290
|
jsonFile jsonFileDecoder filePath =
|
|
276
291
|
rawFile filePath
|
|
277
|
-
|>
|
|
292
|
+
|> BackendTask.andThen
|
|
278
293
|
(\jsonString ->
|
|
279
294
|
jsonString
|
|
280
295
|
|> Decode.decodeString jsonFileDecoder
|
|
281
|
-
|> Result.mapError
|
|
282
|
-
|
|
296
|
+
|> Result.mapError
|
|
297
|
+
(\jsonDecodeError ->
|
|
298
|
+
Exception.Catchable (DecodingError jsonDecodeError)
|
|
299
|
+
{ title = "JSON Decoding Error"
|
|
300
|
+
, body =
|
|
301
|
+
[ TerminalText.text (Decode.errorToString jsonDecodeError)
|
|
302
|
+
]
|
|
303
|
+
|> TerminalText.toString
|
|
304
|
+
}
|
|
305
|
+
)
|
|
306
|
+
|> BackendTask.fromResult
|
|
283
307
|
)
|
|
284
308
|
|
|
285
309
|
|
|
@@ -290,10 +314,32 @@ body =
|
|
|
290
314
|
Decode.field "withoutFrontmatter" Decode.string
|
|
291
315
|
|
|
292
316
|
|
|
293
|
-
read : String -> Decoder a ->
|
|
317
|
+
read : String -> Decoder a -> BackendTask (Catchable (FileReadError error)) a
|
|
294
318
|
read filePath decoder =
|
|
295
|
-
|
|
319
|
+
BackendTask.Internal.Request.request
|
|
296
320
|
{ name = "read-file"
|
|
297
|
-
, body =
|
|
298
|
-
, expect =
|
|
321
|
+
, body = BackendTask.Http.stringBody "" filePath
|
|
322
|
+
, expect =
|
|
323
|
+
Decode.oneOf
|
|
324
|
+
[ Decode.field "errorCode"
|
|
325
|
+
(Decode.map Err (errorDecoder filePath))
|
|
326
|
+
, decoder |> Decode.map Ok
|
|
327
|
+
]
|
|
328
|
+
|> BackendTask.Http.expectJson
|
|
299
329
|
}
|
|
330
|
+
|> BackendTask.andThen BackendTask.fromResult
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
errorDecoder : String -> Decoder (Catchable (FileReadError decoding))
|
|
334
|
+
errorDecoder filePath =
|
|
335
|
+
Decode.succeed
|
|
336
|
+
(Exception.Catchable FileDoesntExist
|
|
337
|
+
{ title = "File Doesn't Exist"
|
|
338
|
+
, body =
|
|
339
|
+
[ TerminalText.text "Couldn't find file at path `"
|
|
340
|
+
, TerminalText.yellow filePath
|
|
341
|
+
, TerminalText.text "`"
|
|
342
|
+
]
|
|
343
|
+
|> TerminalText.toString
|
|
344
|
+
}
|
|
345
|
+
)
|