thomasin / elm-frontmatter / Content.Decode

This is the main module used when writing decoders, and covers decoding basic Elm types like string, int, list

Declarations ⸺ From the decoder function in your Content.elm file, declare either success or failure finding a decoder.

Attributes ⸺ Describe a YAML key/value pair

Decoders ⸺ Decode YAML values into Elm types

Declarations


type alias QueryResult =
Internal.DeclarationResult

This type is returned from the main decoder function in your Content.elm file. It is the result of trying to find a decoder for an input file. Use frontmatter, frontmatterWithoutBody to return a successfully found decoder, or throw, ignore if you can't match a decoder to the input.

frontmatter : Decoder value -> List Attribute -> QueryResult

Decode a frontmatter file. This will include the file body as a body field in the generated record. The first argument is the type that the body will be decoded as. Common options would be Content.Decode.string or Content.Decode.Markdown.decode

decoder : Content.Type.Path -> Content.Decode.QueryResult
decoder typePath =
    case typePath of
        Content.Type.Single [ "Content", "Index" ] ->
            Content.Decode.frontmatter Content.Decode.string
                [ Content.Decode.attribute "title" Content.Decode.string
                , Content.Decode.attribute "description" Content.Decode.string
                ]

        _ ->
            Content.Decode.throw

{- =>
   type alias Content =
       { title : String
       , description : String
       , body : String
       }

   content : Content
   content =
       { title = "Today's newspaper"
       , description = "A pleasant walk"
       , body = "Main content"
       }
-}

frontmatterWithoutBody : List Attribute -> QueryResult

Decode a frontmatter file. This will ignore the file body.

decoder : Content.Type.Path -> Content.Decode.QueryResult
decoder typePath =
    case typePath of
        Content.Type.Single [ "Content", "Index" ] ->
            Content.Decode.frontmatterWithoutBody
                [ Content.Decode.attribute "title" Content.Decode.string
                , Content.Decode.attribute "description" Content.Decode.string
                ]

        _ ->
            Content.Decode.throw

{- =>
   type alias Content =
       { title : String
       , description : String
       }

   content : Content
   content =
       { title = "Today's newspaper"
       , description = "A pleasant walk"
       }
-}

throw : QueryResult

If this is returned from the main decoder function it will throw an error. Useful when you want to ensure that all markdown files are handled.

decoder : Content.Type.Path -> Content.Decode.QueryResult
decoder typePath =
    case typePath of
        Content.Type.Single [ "Content", "Index" ] ->
            Content.Decode.frontmatterWithoutBody
                [ Content.Decode.attribute "title" Content.Decode.string
                , Content.Decode.attribute "description" Content.Decode.string
                ]

        _ ->
            Content.Decode.throw

ignore : QueryResult

If this is returned from the main decoder function it won't do anything. Useful when you want to allow markdown files to be created without having a matching decoder yet.

decoder : Content.Type.Path -> Content.Decode.Declaration
decoder typePath =
    case typePath of
        Content.Type.Single [ "Content", "Index" ] ->
            Content.Decode.frontmatterWithoutBody
                [ Content.Decode.attribute "title" Content.Decode.string
                , Content.Decode.attribute "description" Content.Decode.string
                ]

        _ ->
            Content.Decode.ignore

Attributes


type alias Attribute =
Internal.Attribute

Represents a YAML key and value e.g. title: Both the 'title' key and this string are part of the attribute Can be used in the frontmatter, frontmatterWithoutBody, or anonymousRecord functions.


type alias DecodedAttribute =
Internal.DecodedAttribute

The result of attribute's json decoder. It is an opaque type that the anonymousRecord decodes and uses to construct the record expression.

attribute : String -> Decoder a -> Attribute

attribute is how you decode named YAML fields. They map 1-1 to the generated Elm. The fields are generated in the order that they appear in the list.

{- YAML:
title: "Today's newspaper"
description: "A pleasant walk"

---

Tea
-}

Content.Decode.frontmatter Content.Decode.string
    [ Content.Decode.attribute "title" Content.Decode.string
    , Content.Decode.attribute "description" Content.Decode.string
    ]

{- =>
type alias Content =
    { title : String
    , description : String
    , body : String
    }

content : Content
content =
    { title = "Today's newspaper"
    , description = "A pleasant walk"
    , body = "Tea"
    }
-}

renameTo : String -> Attribute -> Attribute

Rename an attribute! This means you can parse the same frontmatter field into multiple Elm attributes.

Content.Decode.frontmatter Content.Decode.string
    [ Content.Decode.attribute "title" Content.Decode.string
    , Content.Decode.renameTo "slug" (Content.Decode.attribute "title" slugDecoder)
    ]

Decoders


type alias Decoder a =
Internal.Decoder a

Decoders turn JSON frontmatter data into Elm types and records


type alias Context =
Internal.DecoderContext

Decoder context passed down. Contains the file path and module directory of the file currently being decoded. This is currently opaque, but if needed I can add a function to get the current file path.

fromSyntax : Syntax Context a -> (a -> List { with : String, args : Json.Encode.Value }) -> (Context -> Json.Decode.Decoder a) -> Decoder a

Create a decoder from a Syntax object. This lets you use custom JSON decoders to ensure the content you are receiving is valid. The Syntax object passed should be the Syntax object matching the output type of your JSON decoder.

Content.Decode.fromSyntax Content.Decode.Syntax.int
    (always [])
    (Json.Decode.int
        |> Json.Decode.andThen
            (\number ->
                if number > 0 then
                    Json.Decode.succeed number

                else
                    Json.Decode.fail "Only positive numbers supported"
            )
    )

string : Decoder String

Decode strings

Content.Decode.frontmatter Content.Decode.frontmatter
    [ Content.Decode.attribute "title" Content.Decode.string
    , Content.Decode.attribute "description" Content.Decode.string
    ]

int : Decoder Basics.Int

Decode ints

Content.Decode.frontmatter Content.Decode.frontmatter
    [ Content.Decode.attribute "title" Content.Decode.string
    , Content.Decode.attribute "daysTillFullMoon" Content.Decode.int
    ]

float : Decoder Basics.Float

Decode floats

Content.Decode.frontmatter Content.Decode.frontmatter
    [ Content.Decode.attribute "title" Content.Decode.string
    , Content.Decode.attribute "bankAccountDollars" Content.Decode.float
    ]

datetime : Decoder Time.Posix

Decode Iso8601 formatted date strings. elm/time must be installed for the output to compile.

Given a markdown file index.md containing

---
title: A list of people
tomorrow: 2016-08-04T18:53:38.297Z
---

body text

And a decoder

Content.Decode.frontmatter Content.Decode.string
    [ Content.Decode.attribute "tomorrow" Content.Decode.datetime
    ]

This will generate the Content/Index.elm file

import Time

type alias Content =
    { tomorrow : Time.Posix
    , body : String
    }

content : Content
content =
    { tomorrow = Time.millisToPosix 1470336818297
    , body = "body text"
    }

anonymousRecord : List Attribute -> Decoder (List DecodedAttribute)

Decode an anonymous record (We don't have typed records). You have to create anonymous records with a list of attributes.

Content.Decode.frontmatter Content.Decode.string
    [ Content.Decode.attribute "title" Content.Decode.string
    , Content.Decode.attribute "recordtest"
        (Content.Decode.anonymousRecord
            [ Content.Decode.attribute "field1" Content.Decode.string
            , Content.Decode.attribute "field2" Content.Decode.string
            ]
        )
    ]

list : Decoder a -> Decoder (List a)

Decode a list of items. Given a markdown file index.md containing

---
title: A list of people
strings:
    - string1
    - string2
people:
    - about/people/[person1].md
    - about/people/[person2].md
---

body text

And a decoder

Content.Decode.frontmatter Content.Decode.string
    [ Content.Decode.attribute "strings" (Content.Decode.list Content.Decode.string)
    , Content.Decode.attribute "people"
        (Content.Decode.list (Content.Decode.reference (Content.Type.Collection [ "Content", "About", "People" ]))
    ]

This will generate the Content/Index.elm file

type alias Content =
    { strings : List String
    , people : List Content.About.People.CollectionItem
    , body : String
    }

content : Content
content =
    { strings = [ "string1", "string2" ]
    , people = [ Content.About.People.person1, Content.About.People.person2 ]
    , body = "body text"
    }

reference : Content.Type.Path -> Decoder ( Elm.Syntax.ModuleName.ModuleName, String )

References another content record. Given a markdown file index.md containing

---
title: Index
about: about.md
person1: about/people/[person1].md
---

body text

And a decoder

Content.Decode.frontmatter Content.Decode.string
    [ Content.Decode.attribute "about" (Content.Decode.reference (Content.Type.Single [ "Content", "About" ]))
    , Content.Decode.attribute "person1" (Content.Decode.reference (Content.Type.Collection [ "Content", "About", "People" ]))
    ]

This will generate the Content/Index.elm file

import Content.About.People

type alias Content =
    { about : Content.About.Content
    , person1 : Content.About.People.CollectionItem
    , body : String
    }

content : Content
content =
    { about = Content.About.content
    , person1 = Content.About.People.person1
    , body = "body text"
    }