-- | -- Module : Text.PercentFormat -- Copyright : (c) 2016-2018 Rudy Matela -- License : 3-Clause BSD (see the file LICENSE) -- Maintainer : Rudy Matela -- -- 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.