119 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Haskell
		
	
	
	
	
	
			
		
		
	
	
			119 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Haskell
		
	
	
	
	
	
module Hledger.Data.FormatStrings (
 | 
						|
          parseFormatString
 | 
						|
        , formatStrings
 | 
						|
        , formatValue
 | 
						|
        , FormatString(..)
 | 
						|
        , HledgerFormatField(..)
 | 
						|
        , tests
 | 
						|
        ) where
 | 
						|
 | 
						|
import Numeric
 | 
						|
import Data.Char (isPrint)
 | 
						|
import Data.Maybe
 | 
						|
import Test.HUnit
 | 
						|
import Text.ParserCombinators.Parsec
 | 
						|
import Text.Printf
 | 
						|
 | 
						|
import Hledger.Data.Types
 | 
						|
 | 
						|
 | 
						|
formatValue :: Bool -> Maybe Int -> Maybe Int -> String -> String
 | 
						|
formatValue leftJustified min max value = printf formatS value
 | 
						|
    where
 | 
						|
      l = if leftJustified then "-" else ""
 | 
						|
      min' = maybe "" show min
 | 
						|
      max' = maybe "" (\i -> "." ++ (show i)) max
 | 
						|
      formatS = "%" ++ l ++ min' ++ max' ++ "s"
 | 
						|
 | 
						|
parseFormatString :: String -> Either String [FormatString]
 | 
						|
parseFormatString input = case (runParser formatStrings () "(unknown)") input of
 | 
						|
    Left y -> Left $ show y
 | 
						|
    Right x -> Right x
 | 
						|
 | 
						|
{-
 | 
						|
Parsers
 | 
						|
-}
 | 
						|
 | 
						|
field :: GenParser Char st HledgerFormatField
 | 
						|
field = do
 | 
						|
        try (string "account" >> return AccountField)
 | 
						|
    <|> try (string "depth_spacer" >> return DepthSpacerField)
 | 
						|
    <|> try (string "date" >> return DescriptionField)
 | 
						|
    <|> try (string "description" >> return DescriptionField)
 | 
						|
    <|> try (string "total" >> return TotalField)
 | 
						|
    <|> try (many1 digit >>= (\s -> return $ FieldNo $ read s))
 | 
						|
 | 
						|
formatField :: GenParser Char st FormatString
 | 
						|
formatField = do
 | 
						|
    char '%'
 | 
						|
    leftJustified <- optionMaybe (char '-')
 | 
						|
    minWidth <- optionMaybe (many1 $ digit)
 | 
						|
    maxWidth <- optionMaybe (do char '.'; many1 $ digit) -- TODO: Can this be (char '1') *> (many1 digit)
 | 
						|
    char '('
 | 
						|
    f <- field
 | 
						|
    char ')'
 | 
						|
    return $ FormatField (isJust leftJustified) (parseDec minWidth) (parseDec maxWidth) f
 | 
						|
    where
 | 
						|
      parseDec s = case s of
 | 
						|
        Just text -> Just m where ((m,_):_) = readDec text
 | 
						|
        _ -> Nothing
 | 
						|
 | 
						|
formatLiteral :: GenParser Char st FormatString
 | 
						|
formatLiteral = do
 | 
						|
    s <- many1 c
 | 
						|
    return $ FormatLiteral s
 | 
						|
    where
 | 
						|
      isPrintableButNotPercentage x = isPrint x && (not $ x == '%')
 | 
						|
      c =     (satisfy isPrintableButNotPercentage <?> "printable character")
 | 
						|
          <|> try (string "%%" >> return '%')
 | 
						|
 | 
						|
formatStr :: GenParser Char st FormatString
 | 
						|
formatStr =
 | 
						|
        formatField
 | 
						|
    <|> formatLiteral
 | 
						|
 | 
						|
formatStrings :: GenParser Char st [FormatString]
 | 
						|
formatStrings = many formatStr
 | 
						|
 | 
						|
testFormat :: FormatString -> String -> String -> Assertion
 | 
						|
testFormat fs value expected = assertEqual name expected actual
 | 
						|
    where
 | 
						|
        (name, actual) = case fs of
 | 
						|
            FormatLiteral l -> ("literal", formatValue False Nothing Nothing l)
 | 
						|
            FormatField leftJustify min max _ -> ("field", formatValue leftJustify min max value)
 | 
						|
 | 
						|
testParser :: String -> [FormatString] -> Assertion
 | 
						|
testParser s expected = case (parseFormatString s) of
 | 
						|
    Left  error -> assertFailure $ show error
 | 
						|
    Right actual -> assertEqual ("Input: " ++ s) expected actual
 | 
						|
 | 
						|
tests = test [ formattingTests ++ parserTests ]
 | 
						|
 | 
						|
formattingTests = [
 | 
						|
      testFormat (FormatLiteral " ")                                ""            " "
 | 
						|
    , testFormat (FormatField False Nothing Nothing DescriptionField)    "description" "description"
 | 
						|
    , testFormat (FormatField False (Just 20) Nothing DescriptionField)  "description" "         description"
 | 
						|
    , testFormat (FormatField False Nothing (Just 20) DescriptionField)  "description" "description"
 | 
						|
    , testFormat (FormatField True Nothing (Just 20) DescriptionField)   "description" "description"
 | 
						|
    , testFormat (FormatField True (Just 20) Nothing DescriptionField)   "description" "description         "
 | 
						|
    , testFormat (FormatField True (Just 20) (Just 20) DescriptionField) "description" "description         "
 | 
						|
    , testFormat (FormatField True Nothing (Just 3) DescriptionField)    "description" "des"
 | 
						|
    ]
 | 
						|
 | 
						|
parserTests = [
 | 
						|
      testParser ""                             []
 | 
						|
    , testParser "D"                            [FormatLiteral "D"]
 | 
						|
    , testParser "%(date)"                      [FormatField False Nothing Nothing DescriptionField]
 | 
						|
    , testParser "%(total)"                     [FormatField False Nothing Nothing TotalField]
 | 
						|
    , testParser "Hello %(date)!"               [FormatLiteral "Hello ", FormatField False Nothing Nothing DescriptionField, FormatLiteral "!"]
 | 
						|
    , testParser "%-(date)"                     [FormatField True Nothing Nothing DescriptionField]
 | 
						|
    , testParser "%20(date)"                    [FormatField False (Just 20) Nothing DescriptionField]
 | 
						|
    , testParser "%.10(date)"                   [FormatField False Nothing (Just 10) DescriptionField]
 | 
						|
    , testParser "%20.10(date)"                 [FormatField False (Just 20) (Just 10) DescriptionField]
 | 
						|
    , testParser "%20(account) %.10(total)\n"   [ FormatField False (Just 20) Nothing AccountField
 | 
						|
                                                , FormatLiteral " "
 | 
						|
                                                , FormatField False Nothing (Just 10) TotalField
 | 
						|
                                                , FormatLiteral "\n"
 | 
						|
                                                ]
 | 
						|
  ]
 |