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).

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 (Catchable (FileReadError Error)) frontmatter
import BackendTask exposing (BackendTask)
import BackendTask.File as File
import Decode as Decode exposing (Decoder)

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

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 Decode as Decode exposing (Decoder)
import Html exposing (Html)

example :
    BackendTask
        { 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"

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 (Catchable (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)

data : BackendTask String
data =
    bodyWithoutFrontmatter "blog/hello-world.md"

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

Decoder frontmatter
-> String
-> BackendTask (Catchable (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 Decode as Decode exposing (Decoder)

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

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 as 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 (Catchable (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

sourceDirectories : BackendTask (List String)
sourceDirectories =
    File.jsonFile
        (Decode.field
            "source-directories"
            (Decode.list Decode.string)
        )
        "elm.json"
String
-> BackendTask (Catchable (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

elmJsonFile : BackendTask String
elmJsonFile =
    File.rawFile "hello.txt"

Exceptions

type FileReadError decoding
= FileDoesntExist
| FileReadError String
| DecodingError decoding