mdgriffith/elm-markup/2.0.0

NOTE: the latest version is 3.0.1

Mark

elm-markup is about defining what you're expecting in a markup document.

The elm-markup language relies heavily on indentation, which always some multiple of 4 spaces.

Document result
-> String
-> Result (List (DeadEnd Context Problem)) result

Blocks

type Document result
document : (child -> result) -> Block child -> Document result

Create a markup Document. You're first goal is to describe a document in terms of the blocks you're expecting.

Here's a brief example of a document that

document : Mark.Document (List String)
document =
    Mark.document
        identity
        (Mark.manyOf
            [ Mark.block "MyBlock"
                identity
                Mark.string
            , Mark.string
            ]
        )


parse : String -> Result (DeadEnd Context Problem) (List String)
parse source =
    Mark.parse document source

Which will parse a document with many of either raw strings orMyBlock blocks. The idenity function usage above means that this will result in a List String, but you could change that function to make this parser result in anything you want!

| MyBlock
    Here's text captured in a MyBlock


This text is captured at the top level.


| MyBlock
    Here's some more text in a MyBlock.
type Block data

A Block data is just a parser that results in data.

You'll be building up your Document in terms of the Blocks.

block : String -> (child -> result) -> Block child -> Block result

A named block.

Mark.block "MyBlock"
    Html.text
    Mark.string

Will parse the following and render it as Html.text

| MyBlock
    Here is an unformatted string!

Note block names should be capitalized. In the future this may be enforced.

bool : Block Bool

Parse either True or False.

int : Block Int
float : Block Float
string : Block String

Parse a single line and return it as a string.

multiline : Block String

Parse multiple lines at the current indentation level.

For example:

Mark.block "Poem"
    (\str -> str)
    Mark.multiline

Will parse the following:

| Poem
    Whose woods these are I think I know.
    His house is in the village though;
    He will not see me stopping here
    To watch his woods fill up with snow.

Where str in the above function will be

"""Whose woods these are I think I know.
His house is in the village though;
He will not see me stopping here
To watch his woods fill up with snow."""
exactly : String -> value -> Block value

Parse an exact string. This can be useful to parse custom types if you pair it with Mark.oneOf.

type Plant = IsWatered | IsDying




Mark.oneOf
    [ Mark.exactly "IsWatered" IsWatered
    , Mark.exactly "IsDying" IsDying
    ]
stub : String -> result -> Block result

An empty block. This could be useful for inserting something that doesn't need parameters.

| Logo
map : (a -> b) -> Block a -> Block b

Change the result of a block by applying a function to it.

andThen : (a -> Block b) -> Block a -> Block b

Expect a different block based on the result of a previous one.

Text

{ view : Text -> rendered
, inlines : List (Inline rendered)
, replacements : List Replacement
}
-> Block (List rendered)

Handling formatted text is a little more involved than may be initially apparent.

Text styling can be overlapped such as

/My italicized sentence can have *bold*/

In order to render this, the above sentence is chopped up into Text fragments that can have multiple styles active.

  • view is the function to render an individual fragment.
  • inlines are custom inline blocks. These are how links are implemented in Mark.Default!
  • replacements will replace characters before rendering. For example, we can replace ... with the real ellipses unicode character, ….

Note check out Mark.Default.text to see an example.

type Text
= Text (List Style) String

A text fragment with some styling.

type Style
= Bold
| Italic
| Strike
type Inline result
inline : String -> result -> Inline result

Create a custom inline element.

For example, here is how you could parse a link:

Mark.inline "Link"
    (\txt url ->
        Html.a [ Html.Attriutes.href url ] (List.map renderText txt)
    )
    |> Mark.inlineText
    |> Mark.inlineString "url"

Here's an example of a sentence with the above link:

Here is my sentence {link| with a link to a web-comic. |url=http://www.poorlydrawnlines.com/comic/website-bird/} You're welcome.

inlineString : String -> Inline (String -> result) -> Inline result

Parse an inline String field

inlineText : Inline (List Text -> result) -> Inline result
replacement : String -> String -> Replacement

Replace a string with another string. This can be useful to have shortcuts to unicode characters.

For example, in Mark.Default, this is used to replace ... with the unicode ellipses character: ….

{ start : ( String, String )
, end : ( String, String )
}

A balanced replacement. This is used in Mark.Default to do auto-curly quotes.

Mark.balanced
    { start = ( "\"", "“" )
    , end = ( "\"", "”" )
    }

Blocks

oneOf : List (Block a) -> Block a
manyOf : List (Block a) -> Block (List a)

Many blocks that are all at the same indentation level.

startWith : (start -> rest -> result) -> Block start -> Block rest -> Block result

Parse two blocks in sequence.

This can be useful to do things like require that a specific block comes first.

So, the classic case of a blog post that has some meta data at the start could be done like this

Mark.startWith
    (\metadata article ->
        { meta = metadata
        , article = article
        }
    )
    (Mark.record3 "Article"
        (\author title published ->
            { author = author
            , title = title
            , published = published
            }
        )
        (Mark.field "author" Mark.string)
        (Mark.field "title" Mark.string)
        (Mark.field "published" Mark.bool)
    )
    (Mark.manyOf [ Mark.text ])

Which would parse the following doc:

| Article
    author = Mortimer, the Stylish Elephant
    published = False
    title = How to use CSS to rearrange your living room.


First off, thank you for using my library.


Secondly, who knew that flex-box could wrap a sofa?
{ item : Block item
, start : Block icon
}
-> Block (List (Nested ( icon, List item )))

It can be useful to parse a tree structure. For example, here's a nested list.

| List
    - item one
    - item two
        - nested item two
        additional text for nested item two
    - item three
        - nested item three

In order to parse the above, you could define a block as

Mark.block "List"
    (\(Nested nested) ->
        -- Do something with nested.content and nested.children
    )
    (Mark.nested
        { item = text
        , start = Mark.exactly "-" ()
        }
    )

Note the indentation is always a multiple of 4. Another Note text in the above code is defined elsewhere.

type Nested item
= Nested
{ content : item
, children : List (Nested item)
}

Records

Embed a data record.

Here's an implementation to render an <img />

Mark.record2 "Image"
    (\src description ->
        Html.img [ Html.Attribute.src src, Html.Attribute.alt description ] []
    )
    (Mark.field "src" Mark.string)
    (Mark.field "description" Mark.string)

Which would parse

| Image
    src = http://placekitten/200/500
    description = A super cute kitten.

Note The order of the fields in the document does not matter, so this will parse correctly as well:

| Image
    description = A super cute kitten.
    src = http://placekitten/200/500
type Field value
field : String -> Block value -> Field value
record2 : String -> (one -> two -> data) -> Field one -> Field two -> Block data

A record with two fields.

Note there's no record1, because that's basically just a block.

String
-> (one -> two -> three -> data)
-> Field one
-> Field two
-> Field three
-> Block data
String
-> (one -> two -> three -> four -> data)
-> Field one
-> Field two
-> Field three
-> Field four
-> Block data
String
-> (one -> two -> three -> four -> five -> data)
-> Field one
-> Field two
-> Field three
-> Field four
-> Field five
-> Block data
String
-> (one -> two -> three -> four -> five -> six -> data)
-> Field one
-> Field two
-> Field three
-> Field four
-> Field five
-> Field six
-> Block data
String
-> (one -> two -> three -> four -> five -> six -> seven -> data)
-> Field one
-> Field two
-> Field three
-> Field four
-> Field five
-> Field six
-> Field seven
-> Block data
String
-> (one -> two -> three -> four -> five -> six -> seven -> eight -> data)
-> Field one
-> Field two
-> Field three
-> Field four
-> Field five
-> Field six
-> Field seven
-> Field eight
-> Block data
String
-> (one -> two -> three -> four -> five -> six -> seven -> eight -> nine -> data)
-> Field one
-> Field two
-> Field three
-> Field four
-> Field five
-> Field six
-> Field seven
-> Field eight
-> Field nine
-> Block data
String
-> ( one
-> two
-> three
-> four
-> five
-> six
-> seven
-> eight
-> nine
-> ten
-> data
)
-> Field one
-> Field two
-> Field three
-> Field four
-> Field five
-> Field six
-> Field seven
-> Field eight
-> Field nine
-> Field ten
-> Block data

Errors

type Context
= InBlock String
| InInline String
| InRecordField String
type Problem
= ExpectingIndent Int
| InlineStart
| InlineEnd
| BlockStart
| Expecting String
| ExpectingBlockName String
| ExpectingInlineName String
| ExpectingFieldName String
| NonMatchingFields
{ expecting : List String
, found : List String
}
| MissingField String
| RecordError
| Escape
| EscapedChar
| Newline
| Space
| End
| Integer
| FloatingPoint
| InvalidNumber
| UnexpectedEnd
| CantStartTextWithSpace
| UnclosedStyles (List Style)
| UnexpectedField
{ found : String
, options : List String
, recordName : String
}

Advanced

advanced : Parser Context Problem result -> Block result

Define your own parser using `elm/parser'.

This would be the place to start if you wanted to create a parser for syntax highlighting for an Elm block.

Warning This is the only place where you can break the rules/conventions of elm-markup.

Note You can get the current expected indentation using Parser.Advanced.getIndent.