{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS -Wall #-}

-- |
-- Module      : Text.Format
-- Copyright   : (c) 2013 Chris Done, 2013 Shachaf Ben-Kiki
-- License     : BSD3
-- Maintainer  : chrisdone@gmail.com
-- Stability   : experimental
-- Portability : GHC
--
-- Copy of the holey monoids library but with constructor exported.

module Text.Format.Holey
  (
  -- * Formatting library
  Format,
  Holey (..),
  (%),
  now,
  bind,
  later,
  hmap
  ) where

import Data.Monoid
import Data.String
import Data.Text.Lazy.Builder (Builder)

-- | A formatter.
type Format a = forall r. Holey Builder r (a -> r)

-- | The type of a monoid with holes. The underlying monoid is
-- represented by type parameter @m@. The @r@ is the result type and
-- stays polymorphic until the very last moment when 'run' is
-- called. The last argument @a@ is always a function with zero or
-- more arguments, finally resulting in @r@. Ordering the arguments in
-- this order allows holey monoids to be composed using `.`, stacking
-- the expected arguments. Note that the `Monoid` constraint is only
-- used in the identity 'Holey' and in composing two 'Holey's.
newtype Holey m r a = Holey { runHM :: (m -> r) -> a }

-- | Very useful instance for writing format string.
instance (IsString m, a ~ r) => IsString (Holey m r a) where
  fromString = now . fromString

-- | Composition operator. The same as category composition.
(%) :: Monoid n => Holey n b c -> Holey n b1 b -> Holey n b1 c
f % g = f `bind` \a -> g `bind` \b -> now (a `mappend` b)

-- | Insert a constant monoidal value.
now :: m -> Holey m r r
now a = Holey ($ a)

-- | Monadic indexed bind for holey monoids.
bind :: Holey m b c -> (m -> Holey n a b) -> Holey n a c
m `bind` f = Holey $ \k -> runHM m (\a -> runHM (f a) k)

-- | Insert a monoidal value that is not specified until the
-- computation is 'run'. The argument that is expected later is
-- converted to the monoid type using the given conversion function.
later :: (a -> m) -> Holey m r (a -> r)
later f = Holey (. f)

-- | Convert between underlying 'Monoid' types.
hmap :: (m -> n) -> Holey m r a -> Holey n r a
hmap g m = Holey (\k -> runHM m (k . g))