{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE CPP #-} -- for FormatAsHex #if __GLASGOW_HASKELL__ < 710 {-# LANGUAGE OverlappingInstances #-} #endif {- | A module providing access to internals (in case you really need them). Can change at any time, though probably won't. -} module Fmt.Internal ( -- * Classes FromBuilder(..), FormatAsHex(..), FormatAsBase64(..), TupleF(..), -- * Classes used for 'genericF' GBuildable(..), GetFields(..), Buildable'(..), -- * Helpers groupInt, atBase, showSigned', intToDigit', indent', ) where -- Generic useful things import Data.Monoid import Numeric import Data.Char -- Text import qualified Data.Text as T import qualified Data.Text.Encoding as T import qualified Data.Text.Lazy as TL import qualified Data.Text.Lazy.IO as TL import qualified Data.Text.Lazy.Encoding as TL -- 'Buildable' and text-format import Data.Text.Buildable import qualified Data.Text.Format as TF -- Text 'Builder' import Data.Text.Lazy.Builder hiding (fromString) -- Bytestring import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as BSL -- Formatting bytestrings import qualified Data.ByteString.Base16 as B16 import qualified Data.ByteString.Base16.Lazy as B16L import qualified Data.ByteString.Base64 as B64 import qualified Data.ByteString.Base64.Lazy as B64L import qualified Data.ByteString.Base64.URL as B64U import qualified Data.ByteString.Base64.URL.Lazy as B64UL ---------------------------------------------------------------------------- -- FromBuilder ---------------------------------------------------------------------------- class FromBuilder a where -- | Convert a 'Builder' to something else. fromBuilder :: Builder -> a instance FromBuilder Builder where fromBuilder = id {-# INLINE fromBuilder #-} instance (a ~ Char) => FromBuilder [a] where fromBuilder = TL.unpack . toLazyText {-# INLINE fromBuilder #-} instance FromBuilder T.Text where fromBuilder = TL.toStrict . toLazyText {-# INLINE fromBuilder #-} instance FromBuilder TL.Text where fromBuilder = toLazyText {-# INLINE fromBuilder #-} instance (a ~ ()) => FromBuilder (IO a) where fromBuilder = TL.putStr . toLazyText {-# INLINE fromBuilder #-} ---------------------------------------------------------------------------- -- Hex ---------------------------------------------------------------------------- class FormatAsHex a where {- | Format a number or bytestring as hex: >>> hexF 3635 "e33" >>> hexF ("\0\50\63\80" :: BS.ByteString) "00323f50" -} hexF :: a -> Builder instance FormatAsHex BS.ByteString where hexF = fromText . T.decodeLatin1 . B16.encode instance FormatAsHex BSL.ByteString where hexF = fromLazyText . TL.decodeLatin1 . B16L.encode #if __GLASGOW_HASKELL__ >= 710 instance {-# OVERLAPPABLE #-} Integral a => FormatAsHex a where hexF = TF.hex #else instance Integral a => FormatAsHex a where hexF = TF.hex #endif ---------------------------------------------------------------------------- -- Base64 ---------------------------------------------------------------------------- class FormatAsBase64 a where {- | Convert a bytestring to base64: >>> base64F ("\0\50\63\80" :: BS.ByteString) "ADI/UA==" -} base64F :: a -> Builder {- | Convert a bytestring to base64url (a variant of base64 which omits @\/@ and thus can be used in URLs): >>> base64UrlF ("\0\50\63\80" :: BS.ByteString) "ADI_UA==" -} base64UrlF :: a -> Builder instance FormatAsBase64 BS.ByteString where base64F = fromText . T.decodeLatin1 . B64.encode base64UrlF = fromText . T.decodeLatin1 . B64U.encode instance FormatAsBase64 BSL.ByteString where base64F = fromLazyText . TL.decodeLatin1 . B64L.encode base64UrlF = fromLazyText . TL.decodeLatin1 . B64UL.encode ---------------------------------------------------------------------------- -- Tuples ---------------------------------------------------------------------------- class TupleF a where {- | Format a tuple (of up to 8 elements): >>> tupleF (1,2,"hi") "(1, 2, hi)" If any of the elements takes several lines, an alternate format is used: @ >>> __fmt $ tupleF ("test","foo\\nbar","more test")__ ( test , foo bar , more test ) @ -} tupleF :: a -> Builder ---------------------------------------------------------------------------- -- Classes used for 'genericF' ---------------------------------------------------------------------------- class GBuildable f where gbuild :: f a -> Builder class GetFields f where -- | Get fields, together with their names if available getFields :: f a -> [(String, Builder)] -- | A more powerful 'Buildable' used for 'genericF'. Can build functions, -- tuples, lists, maps, etc., as well as combinations thereof. class Buildable' a where build' :: a -> Builder ---------------------------------------------------------------------------- -- Helpers ---------------------------------------------------------------------------- groupInt :: (Buildable a, Integral a) => Int -> Char -> a -> Builder groupInt 0 _ n = build n groupInt i c n = fromLazyText . TL.reverse . foldr merge "" . TL.zip (zeros <> cycle' zeros') . TL.reverse . toLazyText . build $ n where zeros = TL.replicate (fromIntegral i) (TL.singleton '0') zeros' = TL.singleton c <> TL.tail zeros merge (f, c') rest | f == c = TL.singleton c <> TL.singleton c' <> rest | otherwise = TL.singleton c' <> rest cycle' xs = xs <> cycle' xs -- Suppress the warning about redundant Integral constraint _ = toInteger n atBase :: Integral a => Int -> a -> String atBase b _ | b < 2 || b > 36 = error ("baseF: Invalid base " ++ show b) atBase b n = showSigned' (showIntAtBase (toInteger b) intToDigit') (toInteger n) "" {-# INLINE atBase #-} showSigned' :: Real a => (a -> ShowS) -> a -> ShowS showSigned' f n | n < 0 = showChar '-' . f (negate n) | otherwise = f n intToDigit' :: Int -> Char intToDigit' i | i >= 0 && i < 10 = chr (ord '0' + i) | i >= 10 && i < 36 = chr (ord 'a' + i - 10) | otherwise = error ("intToDigit': Invalid int " ++ show i) {- | Add a prefix to the first line, and indent all lines but the first one. The output will always end with a newline, even when the input doesn't. -} indent' :: Int -> T.Text -> Builder -> Builder indent' n pref a = case TL.lines (toLazyText a) of [] -> fromText pref <> "\n" (x:xs) -> fromLazyText $ TL.unlines $ (TL.fromStrict pref <> x) : map (spaces <>) xs where spaces = TL.replicate (fromIntegral n) (TL.singleton ' ')