dillonkearns/elm-pages/10.1.0

NOTE: the latest version is 12.3.0

BackendTask.File

This module lets you read files from the local filesystem as a BackendTask. File paths are relative to the root of your elm-pages project (next to the elm.json file and src/ directory), or you can pass in absolute paths beginning with a /.

Files With Frontmatter

Frontmatter is a convention used to keep metadata at the top of a file between ---'s.

For example, you might have a file called blog/hello-world.md with this content:

---
title: Hello, World!
tags: elm
---
Hey there! This is my first post :)

The frontmatter is in the YAML format here. You can also use JSON in your elm-pages frontmatter.

---
{"title": "Hello, World!", "tags": "elm"}
---
Hey there! This is my first post :)

Whether it's YAML or JSON, you use an Decode to decode your frontmatter, so it feels just like using plain old JSON in Elm.

(String -> Decoder frontmatter)
-> String
-> BackendTask
{ fatal : FatalError
, recoverable : FileReadError Error
}
frontmatter
import BackendTask exposing (BackendTask)
import BackendTask.File as File
import FatalError exposing (FatalError)
import Json.Decode as Decode exposing (Decoder)


blogPost : BackendTask FatalError BlogPostMetadata
blogPost =
    File.bodyWithFrontmatter blogPostDecoder
        "blog/hello-world.md"
        |> BackendTask.allowFatal


type alias BlogPostMetadata =
    { body : String
    , title : String
    , tags : List String
    }


blogPostDecoder : String -> Decoder BlogPostMetadata
blogPostDecoder body =
    Decode.map2 (BlogPostMetadata body)
        (Decode.field "title" Decode.string)
        (Decode.field "tags" tagsDecoder)


tagsDecoder : Decoder (List String)
tagsDecoder =
    Decode.map (String.split " ")
        Decode.string

This will give us a BackendTask that results in the following value:

value =
    { body = "Hey there! This is my first post :)"
    , title = "Hello, World!"
    , tags = [ "elm" ]
    }

It's common to parse the body with a markdown parser or other format.

import BackendTask exposing (BackendTask)
import BackendTask.File as File
import FatalError exposing (FatalError)
import Html exposing (Html)
import Json.Decode as Decode


example :
    BackendTask
        FatalError
        { title : String
        , body : List (Html msg)
        }
example =
    File.bodyWithFrontmatter
        (\markdownString ->
            Decode.map2
                (\title renderedMarkdown ->
                    { title = title
                    , body = renderedMarkdown
                    }
                )
                (Decode.field "title" Decode.string)
                (markdownString
                    |> markdownToView
                    |> Decode.fromResult
                )
        )
        "foo.md"
            |> BackendTask.allowFatal


markdownToView :
    String
    -> Result String (List (Html msg))
markdownToView markdownString =
    markdownString
        |> Markdown.Parser.parse
        |> Result.mapError (\_ -> "Markdown error.")
        |> Result.andThen
            (\blocks ->
                Markdown.Renderer.render
                    Markdown.Renderer.defaultHtmlRenderer
                    blocks
            )
String
-> BackendTask
{ fatal : FatalError
, recoverable : FileReadError decoderError
}
String

Same as bodyWithFrontmatter except it doesn't include the frontmatter.

For example, if you have a file called blog/hello-world.md with

---
title: Hello, World!
tags: elm
---
Hey there! This is my first post :)
import BackendTask exposing (BackendTask)
import BackendTask.File as File
import FatalError exposing (FatalError)


data : BackendTask FatalError String
data =
    File.bodyWithoutFrontmatter "blog/hello-world.md"
        |> BackendTask.allowFatal

Then data will yield the value "Hey there! This is my first post :)".

Decoder frontmatter
-> String
-> BackendTask
{ fatal : FatalError
, recoverable : FileReadError Error
}
frontmatter

Same as bodyWithFrontmatter except it doesn't include the body.

This is often useful when you're aggregating data, for example getting a listing of blog posts and need to extract just the metadata.

import BackendTask exposing (BackendTask)
import BackendTask.File as File
import FatalError exposing (FatalError)
import Json.Decode as Decode exposing (Decoder)


blogPost : BackendTask FatalError BlogPostMetadata
blogPost =
    File.onlyFrontmatter
        blogPostDecoder
        "blog/hello-world.md"
        |> BackendTask.allowFatal


type alias BlogPostMetadata =
    { title : String
    , tags : List String
    }


blogPostDecoder : Decoder BlogPostMetadata
blogPostDecoder =
    Decode.map2 BlogPostMetadata
        (Decode.field "title" Decode.string)
        (Decode.field "tags" (Decode.list Decode.string))

If you wanted to use this to get this metadata for all blog posts in a folder, you could use the BackendTask API along with BackendTask.Glob.

import BackendTask exposing (BackendTask)
import BackendTask.File as File
import Decode exposing (Decoder)


blogPostFiles : BackendTask (List String)
blogPostFiles =
    Glob.succeed identity
        |> Glob.captureFilePath
        |> Glob.match (Glob.literal "content/blog/")
        |> Glob.match Glob.wildcard
        |> Glob.match (Glob.literal ".md")
        |> Glob.toBackendTask


allMetadata : BackendTask (List BlogPostMetadata)
allMetadata =
    blogPostFiles
        |> BackendTask.map
            (List.map
                (File.onlyFrontmatter
                    blogPostDecoder
                )
            )
        |> BackendTask.resolve

Reading Files Without Frontmatter

Decoder a
-> String
-> BackendTask
{ fatal : FatalError
, recoverable : FileReadError Error
}
a

Read a file as JSON.

The Decode will strip off any unused JSON data.

import BackendTask exposing (BackendTask)
import BackendTask.File as File
import FatalError exposing (FatalError)
import Json.Decode as Decode


sourceDirectories : BackendTask FatalError (List String)
sourceDirectories =
    File.jsonFile
        (Decode.field
            "source-directories"
            (Decode.list Decode.string)
        )
        "elm.json"
        |> BackendTask.allowFatal
String
-> BackendTask
{ fatal : FatalError
, recoverable : FileReadError decoderError
}
String

Get the raw file content. Unlike the frontmatter helpers in this module, this function will not strip off frontmatter if there is any.

This is the function you want if you are reading in a file directly. For example, if you read in a CSV file, a raw text file, or any other file that doesn't have frontmatter.

There's a special function for reading in JSON files, jsonFile. If you're reading a JSON file then be sure to use jsonFile to get the benefits of the Decode here.

You could read a file called hello.txt in your root project directory like this:

import BackendTask exposing (BackendTask)
import BackendTask.File as File
import FatalError exposing (FatalError)


elmJsonFile : BackendTask FatalError String
elmJsonFile =
    File.rawFile "hello.txt"
        |> BackendTask.allowFatal

FatalErrors

type FileReadError decoding
= FileDoesntExist
| FileReadError String
| DecodingError decoding