format-heavy

Full-weight string formatting library, analog of Python's string.format

https://github.com/lbobylev/format-heavy#format-heavy

Stackage Nightly 2026-09-18:0.1.0.2
Latest on Hackage:0.1.0.2

See all snapshots format-heavy appears in

BSD-3-Clause licensed by Ilya Portnov
Maintained by Leonid Bobylev
This version can be pinned in stack with:format-heavy-0.1.0.2@sha256:6b7579d9775d4e14f490ee687f5a3cc9c8a5ba6f18003dc3251c2ed7b1998433,2860

format-heavy

format-heavy is a continuation of text-format-heavy, originally created by Ilya Portnov, and provides Haskell string formatting inspired by Python’s str.format() syntax.

It supports positional and named placeholders, per-value format specs, custom variable containers, and custom Formatable instances.

Quick Start

{-# LANGUAGE OverloadedStrings #-}

import Data.Text.Format.Heavy
import qualified Data.Text.Lazy as TL

hello :: TL.Text
hello = format "Hello, {name}!" [("name", "world" :: TL.Text)]

Placeholder Syntax

format "{}" (Single ("hello" :: TL.Text))
-- "hello"

format "{0} {1}" ("hello" :: TL.Text, "world" :: TL.Text)
-- "hello world"

format "{name}" [("name", "world" :: TL.Text)]
-- "world"

format "{{name}}" ()
-- "{name}"

The default syntax uses braces. Literal braces can be escaped as {{ and }}.

Passing Variables

Use Single for one value:

format "value: {}" (Single (42 :: Int))
-- "value: 42"

Use tuples or lists for positional placeholders:

format "{0}, {1}" ("hello" :: TL.Text, "world" :: TL.Text)
-- "hello, world"

format "{0}, {2}" (Several ["zero", "one", "two" :: TL.Text])
-- "zero, two"

Use association lists or maps for named placeholders:

format "Hello, {name}!" [("name", "Alice" :: TL.Text)]
-- "Hello, Alice!"

For heterogeneous named values, wrap each value in Variable:

let vars =
      [ ("name", Variable ("Alice" :: TL.Text))
      , ("count", Variable (3 :: Int))
      ]

format "{name} has {count} messages" vars
-- "Alice has 3 messages"

Format Specs

Format specs are written after : and interpreted by the value being formatted:

format "hex: {:#x}" (Single (427 :: Int))
-- "hex: 0x1ab"

format "float: {:+6.4}" (Single (2.718281828 :: Double))
-- "float: +2.7183"

format "center: <{:^10}>" (Single ("hello" :: String))
-- "center: <   hello  >"

format "upper: {:~u}" (Single ("hello" :: TL.Text))
-- "upper: HELLO"

format "bool: {:yes:no}" (Single False)
-- "bool: no"

Maybe values can specify a fallback after |:

format "value: {:.3|<missing>}" (Single (Nothing :: Maybe Float))
-- "value: <missing>"

Any value with a Show instance can be formatted through Shown:

format "debug: {}" (Single (Shown (Just True)))
-- "debug: Just True"

Error Handling

format is convenient and throws an error if formatting fails.

Use formatEither when errors should be handled explicitly:

import Data.Text.Format.Heavy.Build (formatEither)

formatEither "missing: {1}" (Single ("value" :: TL.Text))
-- Left "Parameter not found: 1"

Parsing And Introspection

Use Data.Text.Format.Heavy.Parse.parse to inspect a format string without formatting it:

import qualified Data.Text.Format.Heavy.Parse as Format

Format.parse "{name}"
-- Right [FormatReplacementField "name" Nothing]

Format.parse "{name:.2}"
-- Right [FormatReplacementField "name" (Just ".2")]

Format.parse "{{name}} {name}"
-- Right [FormatString "{name} ", FormatReplacementField "name" Nothing]

Format.parse "{value:{width}}"
-- Right [FormatReplacementField "value" (Just "{width}")]

This API exposes the field name and the raw format spec parsed from the default braces syntax. It does not format values.

Shell-Like Syntax

The default syntax uses braces. A shell-like syntax is also available:

import Data.Text.Format.Heavy
import Data.Text.Format.Heavy.Parse.Shell
import qualified Data.Text.Lazy as TL

format (parseShellFormat' "Hello, $name!") [("name", "world" :: TL.Text)]
-- "Hello, world!"

Braced shell-style placeholders can also carry format specs:

format (parseShellFormat' "hex: ${:#x}") (Single (427 :: Int))
-- "hex: 0x1ab"

Extending

Applications can extend the library by defining:

  • Formatable instances for custom value types.
  • VarContainer instances for custom variable sources.
  • Custom parsers that produce Format.

License

BSD-3-Clause. See LICENSE.

Changes

Revision history for format-heavy

0.1.0.2 – 2026-09-11

  • Switch development and CI tooling to Cabal only.

0.1.0.1 – 2026-09-01

  • Add CI coverage for GHC 9.2 through 9.10.

0.1.0.0 – 2026-08-31

  • Initial release of format-heavy.