percent-format-0.0.1: src/Text/PercentFormat/Spec.hs
-- |
-- Module : Text.PercentFormat
-- Copyright : (c) 2016-2018 Rudy Matela
-- License : 3-Clause BSD (see the file LICENSE)
-- Maintainer : Rudy Matela <rudy@matela.com.br>
--
-- This file is part of PercentFormat a library for printf-style string
-- formatting.
--
-- This module provides the Spec type which represents a @%...@ specification
-- format.
module Text.PercentFormat.Spec where
import Data.Char (isDigit)
data Spec = Spec
{ ty :: SpecType
, width :: Int
, leftAlign :: Bool
, padWith :: Char
, base :: Int -- ^ only for Number types
, precision :: Maybe Int -- ^ only for Number types, Nothing for infinite
, minPrecision :: Int -- ^ minimum precision to show
, positivePrefix :: String
, capitalizeDigits :: Bool -- ^ whether to capitalize (hex) digits
}
deriving (Eq, Show)
data SpecType = NumberSpec
| ReprSpec
| StringSpec
| CharSpec
| Percent
deriving (Eq, Show)
spec :: Spec
spec = Spec
{ ty = error "undefined Spec ty"
, width = 0
, leftAlign = False
, padWith = ' '
, base = 10
, precision = Nothing
, minPrecision = 0
, positivePrefix = ""
, capitalizeDigits = False
}
parseSpec :: String -> (Spec,String)
parseSpec ('%':cs) = (spec {ty = Percent }, cs)
parseSpec ('r':cs) = (spec {ty = ReprSpec }, cs)
parseSpec ('s':cs) = (spec {ty = StringSpec}, cs)
parseSpec ('c':cs) = (spec {ty = CharSpec }, cs)
parseSpec ('i':cs) = (spec {ty = NumberSpec, precision = Just 0}, cs)
parseSpec ('d':cs) = (spec {ty = NumberSpec}, cs)
parseSpec ('x':cs) = (spec {ty = NumberSpec, base = 16}, cs)
parseSpec ('X':cs) = (spec {ty = NumberSpec, base = 16, capitalizeDigits = True}, cs)
parseSpec ('o':cs) = (spec {ty = NumberSpec, base = 8}, cs)
parseSpec ('b':cs) = (spec {ty = NumberSpec, base = 2}, cs)
parseSpec ('f':cs) = (spec {ty = NumberSpec, minPrecision = 1}, cs)
parseSpec ('q':cs) = error $ "`q' format will be implemented in a future version"
parseSpec ('e':cs) = error $ "`e' format will be implemented in a future version"
parseSpec ('E':cs) = error $ "`E' format will be implemented in a future version"
parseSpec ('0':cs) = (s {padWith = '0'}, cs') where (s,cs') = parseSpec cs
parseSpec ( n :cs) | isDigit n = let (w,cs') = span isDigit (n:cs)
(s,cs'') = parseSpec cs'
in (s {width = read w}, cs'')
parseSpec ('.':'*':cs) = let (s,cs') = parseSpec cs
in (s {precision = Nothing}, cs')
parseSpec ('.':cs) = let (w,cs') = span isDigit cs
(s,cs'') = parseSpec cs'
in (s {precision = Just (read ('0':w))}, cs'')
parseSpec ('-':cs) = (s {leftAlign = True}, cs') where (s,cs') = parseSpec cs
parseSpec (' ':cs) = (s {positivePrefix = " "}, cs') where (s,cs') = parseSpec cs
parseSpec ('+':cs) = (s {positivePrefix = "+"}, cs') where (s,cs') = parseSpec cs
parseSpec (c:_) = error $ "unknown format string `" ++ (c:"'")
-- NOTE: for some reason:
-- > reads "4a" :: [(Int,String)]
-- [(4,"a")]
-- > reads "4." :: [(Int,String)]
-- [(4,".")]
-- > reads "4.0" :: [(Int,String)]
-- []
-- > reads "4.1" :: [(Int,String)]
-- []
-- > reads "4..1" :: [(Int,String)]
-- [(4,"..1")]
-- that's why I am using takeWhile above.