krisajenkins/formatting/4.0.5

NOTE: this package is not compatible with Elm 0.19.1

Formatting

A type-safe string formatting library. It fulfils the need for string-interpolation or a printf function, without sacrificing Elm's runtime guarantees or requiring any language-level changes. It also composes well, to make building up complex formatters easy.

type Format r a
= Format ((String -> r) -> a)

A formatter. This type holds all the information we need to create a formatting function, wrapped up in a way that makes it easy to compose.

Build one of these up with primitives like s, string and int, join them together with <>, and when you're done, generate the final printing function with print.

Example

import Formatting exposing (..)


greeting =
    s "Hello " <> string <> s "!"


print greeting "Kris"


--> "Hello Kris!"

Creating Custom Formatters

Imagine you have an existing formatting rule you'd like to turn into a formatter:

tweetSummary : Int -> String -> String
tweetSummary starCount body =
    "(" ++ toString starCount ++ ") " ++ body

First, wrap the type signature in brackets:

tweetSummary : (Int -> String -> String)

Then change the result type to a variable. (That's where the magic begins - the Formatting library gets control of the final result type.):

tweetSummary : (Int -> String -> r)

Now add Format r to the start.

tweetSummary : Format r (Int -> String -> r)

All very mechanical. Now for the function body. Let's recall what it looked like at the start:

tweetSummary starCount body =
    "(" ++ toString starCount ++ ") " ++ body

Change that into an anonymous function:

tweetSummary =
    (\starCount body ->
        "(" ++ toString starCount ++ ") " ++ body
    )

Now add in a callback function as the first argument:

tweetSummary =
    (\callback starCount body ->
        "(" ++ toString starCount ++ ") " ++ body
    )

Pass your function's result to that callback (using <| is the easy way):

tweetSummary =
    (\callback starCount body ->
        callback <| "(" ++ toString starCount ++ ") " ++ body
    )

Finally, wrap that all up in a Format constructor:

tweetSummary =
    Format
        (\callback starCount body ->
            callback <| "(" ++ toString starCount ++ ") " ++ body
        )

And you're done. You have a composable formatting function. It's a mechanical process that's probably a bit weird at first, but easy to get used to.

(<>) : Format b a -> Format c b -> Format c a

Compose two formatters together.

map : (String -> String) -> Format r a -> Format r a

Create a new formatter by applying a function to the output of this formatter.

For example:

import String exposing (toUpper)


format = s "Name: " <> map toUpper string

...produces a formatter that uppercases the name:

print format "Kris"


--> "Name: KRIS"
premap : (a -> b) -> Format r (b -> v) -> Format r (a -> v)

Create a new formatter by applying a function to the input of this formatter. The dual of map.

For example:

format = s "Height: " <> premap .height float

...produces a formatter that accesses a .height record field:

print format { height: 1.72 }


--> "Height: 1.72"
apply : Format r (a -> b -> r) -> a -> Format r (b -> r)

Apply an argument to a Formatter. Useful when you want to supply an argument, but don't yet want to convert your formatter to a plain ol' function (with print).

print : Format String a -> a

Turn your formatter into a function that's just waiting for its arguments.

Given this format:

orderFormat =
    s "FREE: " <> int <> s " x " <> string  <> s "!"

...we can either use it immediately:

order : String
order = print orderFormat 2 "Ice Cream"


--> "FREE: 2 x Ice Cream!"

...or turn it into an ordinary function to be used later:

orderFormatter : Int -> String -> String
orderFormatter =
    print orderFormat




...elsewhere...




order : String
order = orderFormatter 2 "Ice Cream"


--> "FREE: 2 x Ice Cream!"
html : Format (Html msg) a -> a

Convenience function. Like print, but returns an Html.text node as its final result, instead of a String.

Hint: If you're using any formatters where whitespace is sigificant, you might well need one or both of these CSS rules:

font-family: monospace;
white-space: pre;
s : String -> Format r r

A boilerplate string.

string : Format r (String -> r)

A placeholder for a String argument.

int : Format r (Int -> r)

A placeholder for an Int argument.

bool : Format r (Bool -> r)

A placeholder for an Bool argument.

float : Format r (Float -> r)

A placeholder for a Float argument.

any : Format r (a -> r)

A placeholder for any value that we can call toString on.

Eagle-eyed readers of the source will notice that we use this same function to define int and float, since toString gives us the right result for both of those types.

The sole difference is, int and float have more restrictive type signatures.

wrap : String -> Format r a -> Format r a

wrap one string with another. It's convenient for building strings like `"Invalid key '<keyname>'." For example:

print (wrap "'" string) "tester"


--> "'tester'"
pad : Int -> Char -> Format r a -> Format r a

String.pad lifted into the world of Formatters.

For example:

print (pad 10 '-' string) "KRIS"


--> "---KRIS---"
padLeft : Int -> Char -> Format r a -> Format r a

String.padLeft lifted into the world of Formatters.

For example:

print (padLeft 10 '_' float) 1.72


--> "______1.72"
padRight : Int -> Char -> Format r a -> Format r a

String.padRight lifted into the world of Formatters.

For example:

print (padRight 10 '.' int) 789


--> "789......."
dp : Int -> Format r (Float -> r)

DEPRECATED: Use roundTo instead.

roundTo : Int -> Format r (Float -> r)

A float rounded to n decimal places.

uriFragment : Format r (String -> r)

Format a URI fragment.

For example:

print uriFragment "this string"


--> "this%20string"