From 69c870c6f08b7d03417db0264159508def41a3b5 Mon Sep 17 00:00:00 2001 From: Simon Michael Date: Wed, 19 Aug 2015 13:16:41 -0700 Subject: [PATCH] balance, lib: make StringFormat singular; cleanup Pass around a StringFormat rather than [StringFormat]. Also more balance report item rendering refactoring. --- hledger-lib/Hledger/Data/StringFormat.hs | 24 ++++---- hledger/Hledger/Cli/Balance.hs | 71 +++++++++++++----------- hledger/Hledger/Cli/Options.hs | 4 +- 3 files changed, 55 insertions(+), 44 deletions(-) diff --git a/hledger-lib/Hledger/Data/StringFormat.hs b/hledger-lib/Hledger/Data/StringFormat.hs index 7e4ac2459..f1482c18b 100644 --- a/hledger-lib/Hledger/Data/StringFormat.hs +++ b/hledger-lib/Hledger/Data/StringFormat.hs @@ -7,7 +7,8 @@ module Hledger.Data.StringFormat ( parseStringFormat , formatString - , StringFormat(..) + , StringFormat + , StringFormatComponent(..) , ReportItemField(..) -- , stringformatp , tests @@ -24,8 +25,10 @@ import Text.Printf (printf) -- | A format specification/template to use when rendering report line items as text. --- These are currently supported by the balance command. -data StringFormat = +-- (Currently supported by the balance command in single-column mode). +type StringFormat = [StringFormatComponent] + +data StringFormatComponent = FormatLiteral String | FormatField Bool -- Left justified ? (Maybe Int) -- Min width @@ -33,6 +36,7 @@ data StringFormat = ReportItemField -- Field name deriving (Show, Eq) + -- | An id identifying which report item field to interpolate. These -- are drawn from several hledger report types, so are not all -- applicable for a given report. @@ -55,7 +59,7 @@ formatString leftJustified min max s = printf fmt s fmt = "%" ++ l ++ min' ++ max' ++ "s" -- | Parse a string format specification, or return a parse error. -parseStringFormat :: String -> Either String [StringFormat] +parseStringFormat :: String -> Either String StringFormat parseStringFormat input = case (runParser (stringformatp <* eof) () "(unknown)") input of Left y -> Left $ show y Right x -> Right x @@ -71,7 +75,7 @@ field = do <|> try (string "total" >> return TotalField) <|> try (many1 digit >>= (\s -> return $ FieldNo $ read s)) -formatField :: Stream [Char] m Char => ParsecT [Char] st m StringFormat +formatField :: Stream [Char] m Char => ParsecT [Char] st m StringFormatComponent formatField = do char '%' leftJustified <- optionMaybe (char '-') @@ -86,7 +90,7 @@ formatField = do Just text -> Just m where ((m,_):_) = readDec text _ -> Nothing -formatLiteral :: Stream [Char] m Char => ParsecT [Char] st m StringFormat +formatLiteral :: Stream [Char] m Char => ParsecT [Char] st m StringFormatComponent formatLiteral = do s <- many1 c return $ FormatLiteral s @@ -95,24 +99,24 @@ formatLiteral = do c = (satisfy isPrintableButNotPercentage "printable character") <|> try (string "%%" >> return '%') -formatp :: Stream [Char] m Char => ParsecT [Char] st m StringFormat +formatp :: Stream [Char] m Char => ParsecT [Char] st m StringFormatComponent formatp = formatField <|> formatLiteral -stringformatp :: Stream [Char] m Char => ParsecT [Char] st m [StringFormat] +stringformatp :: Stream [Char] m Char => ParsecT [Char] st m StringFormat stringformatp = many formatp ---------------------------------------------------------------------- -testFormat :: StringFormat -> String -> String -> Assertion +testFormat :: StringFormatComponent -> String -> String -> Assertion testFormat fs value expected = assertEqual name expected actual where (name, actual) = case fs of FormatLiteral l -> ("literal", formatString False Nothing Nothing l) FormatField leftJustify min max _ -> ("field", formatString leftJustify min max value) -testParser :: String -> [StringFormat] -> Assertion +testParser :: String -> StringFormat -> Assertion testParser s expected = case (parseStringFormat s) of Left error -> assertFailure $ show error Right actual -> assertEqual ("Input: " ++ s) expected actual diff --git a/hledger/Hledger/Cli/Balance.hs b/hledger/Hledger/Cli/Balance.hs index 23e9e6778..7073d85d8 100644 --- a/hledger/Hledger/Cli/Balance.hs +++ b/hledger/Hledger/Cli/Balance.hs @@ -408,40 +408,47 @@ This implementation turned out to be a bit convoluted but implements the followi EUR -1 b USD -1 ; Account 'b' has two amounts. The account name is printed on the last line. -} --- | Render one balance report line item as plain text suitable for console output. -balanceReportItemAsText :: ReportOpts -> [StringFormat] -> BalanceReportItem -> [String] -balanceReportItemAsText opts fmt ((_, accountName, depth), Mixed amounts) = - -- 'amounts' could contain several quantities of the same commodity with different price. - -- In order to combine them into single value (which is expected) we take the first price and - -- use it for the whole mixed amount. This could be suboptimal. XXX - let Mixed normAmounts = normaliseMixedAmountSquashPricesForDisplay (Mixed amounts) in - case normAmounts of - [] -> [] - [a] -> [formatBalanceReportItem fmt ((Just accountName'), depth, a)] - (as) -> multiline as - where +-- | Render one balance report line item as plain text suitable for console output (or +-- whatever string format is specified). +balanceReportItemAsText :: ReportOpts -> StringFormat -> BalanceReportItem -> [String] +balanceReportItemAsText opts fmt ((_, accountName, depth), amt) = + let accountName' = maybeAccountNameDrop opts accountName - multiline :: [Amount] -> [String] - multiline [] = [] - multiline [a] = [formatBalanceReportItem fmt ((Just accountName'), depth, a)] - multiline (a:as) = (formatBalanceReportItem fmt (Nothing, depth, a)) : multiline as + -- 'amounts' could contain several quantities of the same commodity with different price. + -- In order to combine them into single value (which is expected) we take the first price and + -- use it for the whole mixed amount. This could be suboptimal. XXX + amt' = normaliseMixedAmountSquashPricesForDisplay amt + in + formatBalanceReportItem fmt (accountName', depth, amt') -formatBalanceReportItem :: [StringFormat] -> (Maybe AccountName, Int, Amount) -> String -formatBalanceReportItem [] (_, _, _) = "" -formatBalanceReportItem (fmt:fmts) (macctname, depth, amount) = - format fmt (macctname, depth, amount) ++ - formatBalanceReportItem fmts (macctname, depth, amount) - where - format :: StringFormat -> (Maybe AccountName, Int, Amount) -> String - format (FormatLiteral s) _ = s - format (FormatField ljust min max field) (macctname, depth, total) = case field of - DepthSpacerField -> formatString ljust Nothing max $ replicate d ' ' - where d = case min of - Just m -> depth * m - Nothing -> depth - AccountField -> formatString ljust min max $ fromMaybe "" macctname - TotalField -> formatString ljust min max $ showAmountWithoutPrice total - _ -> "" +-- | Render a balance report item using the given StringFormat, generating one or more lines of text. +formatBalanceReportItem :: StringFormat -> (AccountName, Int, MixedAmount) -> [String] +formatBalanceReportItem [] _ = [""] +formatBalanceReportItem fmt (acctname, depth, Mixed amts) = + case amts of + [] -> [] + [a] -> [formatLine fmt (Just acctname, depth, a)] + (a:as) -> [formatLine fmt (Just acctname, depth, a)] ++ + [formatLine fmt (Nothing, depth, a) | a <- as] + +-- | Render one line of a balance report item using the given StringFormat, maybe omitting the account name. +formatLine :: StringFormat -> (Maybe AccountName, Int, Amount) -> String +formatLine [] _ = "" +formatLine (fmt:fmts) (macctname, depth, amount) = + formatComponent fmt (macctname, depth, amount) ++ + formatLine fmts (macctname, depth, amount) + +-- | Render one StringFormat component of one line of a balance report item. +formatComponent :: StringFormatComponent -> (Maybe AccountName, Int, Amount) -> String +formatComponent (FormatLiteral s) _ = s +formatComponent (FormatField ljust min max field) (macctname, depth, total) = case field of + DepthSpacerField -> formatString ljust Nothing max $ replicate d ' ' + where d = case min of + Just m -> depth * m + Nothing -> depth + AccountField -> formatString ljust min max $ fromMaybe "" macctname + TotalField -> formatString ljust min max $ showAmountWithoutPrice total + _ -> "" -- multi-column balance reports diff --git a/hledger/Hledger/Cli/Options.hs b/hledger/Hledger/Cli/Options.hs index ddca7c0e0..df29e954b 100644 --- a/hledger/Hledger/Cli/Options.hs +++ b/hledger/Hledger/Cli/Options.hs @@ -467,11 +467,11 @@ maybeAccountNameDrop opts a | tree_ opts = a -- | Parse the format option if provided, possibly returning an error, -- otherwise get the default value. -lineFormatFromOpts :: ReportOpts -> Either String [StringFormat] +lineFormatFromOpts :: ReportOpts -> Either String StringFormat lineFormatFromOpts = maybe (Right defaultBalanceLineFormat) parseStringFormat . format_ -- | Default line format for balance report: "%20(total) %2(depth_spacer)%-(account)" -defaultBalanceLineFormat :: [StringFormat] +defaultBalanceLineFormat :: StringFormat defaultBalanceLineFormat = [ FormatField False (Just 20) Nothing TotalField , FormatLiteral " "