cli: Command.Balance: support FODS export for multibalance
Data.Amount.showMixedAmountLinesPartsB: new helper function
This commit is contained in:
parent
66a047aade
commit
da61b64f94
@ -155,6 +155,7 @@ module Hledger.Data.Amount (
|
|||||||
showMixedAmountWithZeroCommodity,
|
showMixedAmountWithZeroCommodity,
|
||||||
showMixedAmountB,
|
showMixedAmountB,
|
||||||
showMixedAmountLinesB,
|
showMixedAmountLinesB,
|
||||||
|
showMixedAmountLinesPartsB,
|
||||||
wbToText,
|
wbToText,
|
||||||
wbUnpack,
|
wbUnpack,
|
||||||
mixedAmountSetPrecision,
|
mixedAmountSetPrecision,
|
||||||
@ -1120,10 +1121,17 @@ showMixedAmountB opts ma
|
|||||||
-- This returns the list of WideBuilders: one for each Amount, and padded/elided to the appropriate width.
|
-- This returns the list of WideBuilders: one for each Amount, and padded/elided to the appropriate width.
|
||||||
-- This does not honour displayOneLine; all amounts will be displayed as if displayOneLine were False.
|
-- This does not honour displayOneLine; all amounts will be displayed as if displayOneLine were False.
|
||||||
showMixedAmountLinesB :: AmountFormat -> MixedAmount -> [WideBuilder]
|
showMixedAmountLinesB :: AmountFormat -> MixedAmount -> [WideBuilder]
|
||||||
showMixedAmountLinesB opts@AmountFormat{displayMaxWidth=mmax,displayMinWidth=mmin} ma =
|
showMixedAmountLinesB opts ma =
|
||||||
map (adBuilder . pad) elided
|
map fst $ showMixedAmountLinesPartsB opts ma
|
||||||
|
|
||||||
|
-- | Like 'showMixedAmountLinesB' but also returns
|
||||||
|
-- the amounts associated with each text builder.
|
||||||
|
showMixedAmountLinesPartsB :: AmountFormat -> MixedAmount -> [(WideBuilder, Amount)]
|
||||||
|
showMixedAmountLinesPartsB opts@AmountFormat{displayMaxWidth=mmax,displayMinWidth=mmin} ma =
|
||||||
|
zip (map (adBuilder . pad) elided) amts
|
||||||
where
|
where
|
||||||
astrs = amtDisplayList (wbWidth sep) (showAmountB opts) . orderedAmounts opts $
|
astrs = amtDisplayList (wbWidth sep) (showAmountB opts) amts
|
||||||
|
amts = orderedAmounts opts $
|
||||||
if displayCost opts then ma else mixedAmountStripCosts ma
|
if displayCost opts then ma else mixedAmountStripCosts ma
|
||||||
sep = WideBuilder (TB.singleton '\n') 0
|
sep = WideBuilder (TB.singleton '\n') 0
|
||||||
width = maximum $ map (wbWidth . adBuilder) elided
|
width = maximum $ map (wbWidth . adBuilder) elided
|
||||||
|
|||||||
@ -259,6 +259,7 @@ module Hledger.Cli.Commands.Balance (
|
|||||||
,multiBalanceReportHtmlFootRow
|
,multiBalanceReportHtmlFootRow
|
||||||
,multiBalanceReportAsTable
|
,multiBalanceReportAsTable
|
||||||
,multiBalanceReportTableAsText
|
,multiBalanceReportTableAsText
|
||||||
|
,multiBalanceReportAsSpreadsheet
|
||||||
-- ** HTML output helpers
|
-- ** HTML output helpers
|
||||||
,stylesheet_
|
,stylesheet_
|
||||||
,styles_
|
,styles_
|
||||||
@ -394,6 +395,8 @@ balance opts@CliOpts{reportspec_=rspec} j = case balancecalc_ of
|
|||||||
"tsv" -> printTSV . multiBalanceReportAsCsv ropts
|
"tsv" -> printTSV . multiBalanceReportAsCsv ropts
|
||||||
"html" -> (<>"\n") . L.renderText . multiBalanceReportAsHtml ropts
|
"html" -> (<>"\n") . L.renderText . multiBalanceReportAsHtml ropts
|
||||||
"json" -> (<>"\n") . toJsonText
|
"json" -> (<>"\n") . toJsonText
|
||||||
|
"fods" -> printFods IO.localeEncoding .
|
||||||
|
Map.singleton "Hledger" . multiBalanceReportAsSpreadsheet ropts
|
||||||
_ -> const $ error' $ unsupportedOutputFormatError fmt -- PARTIAL:
|
_ -> const $ error' $ unsupportedOutputFormatError fmt -- PARTIAL:
|
||||||
writeOutputLazyText opts $ render report
|
writeOutputLazyText opts $ render report
|
||||||
|
|
||||||
@ -569,23 +572,38 @@ balanceReportAsSpreadsheet opts (items, total) =
|
|||||||
_ -> [[showName name, renderAmount ma]]
|
_ -> [[showName name, renderAmount ma]]
|
||||||
|
|
||||||
showName = cell . accountNameDrop (drop_ opts)
|
showName = cell . accountNameDrop (drop_ opts)
|
||||||
renderAmount mixedAmt =
|
renderAmount mixedAmt = wbToText <$> cellFromMixedAmount bopts mixedAmt
|
||||||
(cell $ wbToText $ showMixedAmountB bopts mixedAmt) {
|
|
||||||
Ods.cellType =
|
|
||||||
case unifyMixedAmount mixedAmt of
|
|
||||||
Just amt ->
|
|
||||||
Ods.TypeAmount $
|
|
||||||
if showcomm
|
|
||||||
then amt
|
|
||||||
else amt {acommodity = T.empty}
|
|
||||||
Nothing -> Ods.TypeMixedAmount
|
|
||||||
}
|
|
||||||
where
|
where
|
||||||
bopts = machineFmt{displayCommodity=showcomm, displayCommodityOrder = commorder}
|
bopts = machineFmt{displayCommodity=showcomm, displayCommodityOrder = commorder}
|
||||||
(showcomm, commorder)
|
(showcomm, commorder)
|
||||||
| layout_ opts == LayoutBare = (False, Just $ S.toList $ maCommodities mixedAmt)
|
| layout_ opts == LayoutBare = (False, Just $ S.toList $ maCommodities mixedAmt)
|
||||||
| otherwise = (True, Nothing)
|
| otherwise = (True, Nothing)
|
||||||
|
|
||||||
|
cellFromMixedAmount :: AmountFormat -> MixedAmount -> Ods.Cell WideBuilder
|
||||||
|
cellFromMixedAmount bopts mixedAmt =
|
||||||
|
(Ods.defaultCell $ showMixedAmountB bopts mixedAmt) {
|
||||||
|
Ods.cellType =
|
||||||
|
case unifyMixedAmount mixedAmt of
|
||||||
|
Just amt -> amountType bopts amt
|
||||||
|
Nothing -> Ods.TypeMixedAmount
|
||||||
|
}
|
||||||
|
|
||||||
|
cellsFromMixedAmount :: AmountFormat -> MixedAmount -> [Ods.Cell WideBuilder]
|
||||||
|
cellsFromMixedAmount bopts mixedAmt =
|
||||||
|
map
|
||||||
|
(\(str,amt) ->
|
||||||
|
(Ods.defaultCell str) {Ods.cellType = amountType bopts amt})
|
||||||
|
(showMixedAmountLinesPartsB bopts mixedAmt)
|
||||||
|
|
||||||
|
amountType :: AmountFormat -> Amount -> Ods.Type
|
||||||
|
amountType bopts amt =
|
||||||
|
Ods.TypeAmount $
|
||||||
|
if displayCommodity bopts
|
||||||
|
then amt
|
||||||
|
else amt {acommodity = T.empty}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- Multi-column balance reports
|
-- Multi-column balance reports
|
||||||
|
|
||||||
-- | Render a multi-column balance report as CSV.
|
-- | Render a multi-column balance report as CSV.
|
||||||
@ -602,21 +620,35 @@ multiBalanceReportAsCsv opts@ReportOpts{..} report = maybeTranspose allRows
|
|||||||
|
|
||||||
-- Helper for CSV (and HTML) rendering.
|
-- Helper for CSV (and HTML) rendering.
|
||||||
multiBalanceReportAsCsvHelper :: Bool -> ReportOpts -> MultiBalanceReport -> (CSV, CSV)
|
multiBalanceReportAsCsvHelper :: Bool -> ReportOpts -> MultiBalanceReport -> (CSV, CSV)
|
||||||
multiBalanceReportAsCsvHelper ishtml opts@ReportOpts{..} (PeriodicReport colspans items tr) =
|
multiBalanceReportAsCsvHelper ishtml opts =
|
||||||
(headers : concatMap fullRowAsTexts items, totalrows)
|
(map (map Ods.cellContent) *** map (map Ods.cellContent)) .
|
||||||
|
multiBalanceReportAsSpreadsheetHelper ishtml opts
|
||||||
|
|
||||||
|
-- Helper for CSV and ODS and HTML rendering.
|
||||||
|
multiBalanceReportAsSpreadsheetHelper ::
|
||||||
|
Bool -> ReportOpts -> MultiBalanceReport -> ([[Ods.Cell Text]], [[Ods.Cell Text]])
|
||||||
|
multiBalanceReportAsSpreadsheetHelper ishtml opts@ReportOpts{..} (PeriodicReport colspans items tr) =
|
||||||
|
(headers : concatMap fullRowAsTexts items,
|
||||||
|
map (map (\c -> c{Ods.cellStyle = Ods.Body Ods.Total})) totalrows)
|
||||||
where
|
where
|
||||||
headers = "account" : case layout_ of
|
cell = Ods.defaultCell
|
||||||
|
headers =
|
||||||
|
map (\content -> (cell content) {Ods.cellStyle = Ods.Head}) $
|
||||||
|
"account" :
|
||||||
|
case layout_ of
|
||||||
LayoutTidy -> ["period", "start_date", "end_date", "commodity", "value"]
|
LayoutTidy -> ["period", "start_date", "end_date", "commodity", "value"]
|
||||||
LayoutBare -> "commodity" : dateHeaders
|
LayoutBare -> "commodity" : dateHeaders
|
||||||
_ -> dateHeaders
|
_ -> dateHeaders
|
||||||
dateHeaders = map showDateSpan colspans ++ ["total" | row_total_] ++ ["average" | average_]
|
dateHeaders = map showDateSpan colspans ++ ["total" | row_total_] ++ ["average" | average_]
|
||||||
fullRowAsTexts row = map (showName row :) $ rowAsText opts colspans row
|
fullRowAsTexts row = map (cell (showName row) :) $ rowAsText row
|
||||||
where showName = accountNameDrop drop_ . prrFullName
|
where showName = accountNameDrop drop_ . prrFullName
|
||||||
totalrows
|
totalrows
|
||||||
| no_total_ = mempty
|
| no_total_ = []
|
||||||
| ishtml = zipWith (:) (totalRowHeadingHtml : repeat "") $ rowAsText opts colspans tr
|
| ishtml = zipWith (:) (cell totalRowHeadingHtml : repeat Ods.emptyCell) $ rowAsText tr
|
||||||
| otherwise = map (totalRowHeadingCsv :) $ rowAsText opts colspans tr
|
| otherwise = map (cell totalRowHeadingCsv :) $ rowAsText tr
|
||||||
rowAsText = if ishtml then multiBalanceRowAsHtmlText else multiBalanceRowAsCsvText
|
rowAsText =
|
||||||
|
let fmt = if ishtml then oneLineNoCostFmt else machineFmt
|
||||||
|
in map (map (fmap wbToText)) . multiBalanceRowAsCellBuilders fmt opts colspans
|
||||||
|
|
||||||
-- Helpers and CSS styles for HTML output.
|
-- Helpers and CSS styles for HTML output.
|
||||||
|
|
||||||
@ -742,6 +774,17 @@ multiBalanceReportHtmlFootRow ropts isfirstline (hdr:cells) =
|
|||||||
--thRow :: [String] -> Html ()
|
--thRow :: [String] -> Html ()
|
||||||
--thRow = tr_ . mconcat . map (th_ . toHtml)
|
--thRow = tr_ . mconcat . map (th_ . toHtml)
|
||||||
|
|
||||||
|
|
||||||
|
-- | Render the ODS table rows for a MultiBalanceReport.
|
||||||
|
-- Returns the heading row, 0 or more body rows, and the totals row if enabled.
|
||||||
|
multiBalanceReportAsSpreadsheet ::
|
||||||
|
ReportOpts -> MultiBalanceReport -> ((Maybe Int, Maybe Int), [[Ods.Cell Text]])
|
||||||
|
multiBalanceReportAsSpreadsheet ropts mbr =
|
||||||
|
let (upper,lower) = multiBalanceReportAsSpreadsheetHelper True ropts mbr
|
||||||
|
in ((Just 1, case layout_ ropts of LayoutWide _ -> Just 1; _ -> Nothing),
|
||||||
|
upper ++ lower)
|
||||||
|
|
||||||
|
|
||||||
-- | Render a multi-column balance report as plain text suitable for console output.
|
-- | Render a multi-column balance report as plain text suitable for console output.
|
||||||
multiBalanceReportAsText :: ReportOpts -> MultiBalanceReport -> TL.Text
|
multiBalanceReportAsText :: ReportOpts -> MultiBalanceReport -> TL.Text
|
||||||
multiBalanceReportAsText ropts@ReportOpts{..} r = TB.toLazyText $
|
multiBalanceReportAsText ropts@ReportOpts{..} r = TB.toLazyText $
|
||||||
@ -833,31 +876,38 @@ multiBalanceReportAsTable opts@ReportOpts{summary_only_, average_, row_total_, b
|
|||||||
multiColumnTableInterColumnBorder = if pretty_ opts then SingleLine else NoLine
|
multiColumnTableInterColumnBorder = if pretty_ opts then SingleLine else NoLine
|
||||||
|
|
||||||
multiBalanceRowAsTextBuilders :: AmountFormat -> ReportOpts -> [DateSpan] -> PeriodicReportRow a MixedAmount -> [[WideBuilder]]
|
multiBalanceRowAsTextBuilders :: AmountFormat -> ReportOpts -> [DateSpan] -> PeriodicReportRow a MixedAmount -> [[WideBuilder]]
|
||||||
multiBalanceRowAsTextBuilders bopts ReportOpts{..} colspans (PeriodicReportRow _ as rowtot rowavg) =
|
multiBalanceRowAsTextBuilders bopts ropts colspans row =
|
||||||
|
map (map Ods.cellContent) $
|
||||||
|
multiBalanceRowAsCellBuilders bopts ropts colspans row
|
||||||
|
|
||||||
|
multiBalanceRowAsCellBuilders ::
|
||||||
|
AmountFormat -> ReportOpts -> [DateSpan] -> PeriodicReportRow a MixedAmount -> [[Ods.Cell WideBuilder]]
|
||||||
|
multiBalanceRowAsCellBuilders bopts ReportOpts{..} colspans (PeriodicReportRow _ as rowtot rowavg) =
|
||||||
case layout_ of
|
case layout_ of
|
||||||
LayoutWide width -> [fmap (showMixedAmountB bopts{displayMaxWidth=width}) allamts]
|
LayoutWide width -> [fmap (cellFromMixedAmount bopts{displayMaxWidth=width}) allamts]
|
||||||
LayoutTall -> paddedTranspose mempty
|
LayoutTall -> paddedTranspose Ods.emptyCell
|
||||||
. fmap (showMixedAmountLinesB bopts{displayMaxWidth=Nothing})
|
. fmap (cellsFromMixedAmount bopts{displayMaxWidth=Nothing})
|
||||||
$ allamts
|
$ allamts
|
||||||
LayoutBare -> zipWith (:) (fmap wbFromText cs) -- add symbols
|
LayoutBare -> zipWith (:) (map wbCell cs) -- add symbols
|
||||||
. transpose -- each row becomes a list of Text quantities
|
. transpose -- each row becomes a list of Text quantities
|
||||||
. fmap (showMixedAmountLinesB bopts{displayCommodity=False, displayCommodityOrder=Just cs, displayMinWidth=Nothing})
|
. fmap (cellsFromMixedAmount bopts{displayCommodity=False, displayCommodityOrder=Just cs, displayMinWidth=Nothing})
|
||||||
$ allamts
|
$ allamts
|
||||||
LayoutTidy -> concat
|
LayoutTidy -> concat
|
||||||
. zipWith (map . addDateColumns) colspans
|
. zipWith (map . addDateColumns) colspans
|
||||||
. fmap ( zipWith (\c a -> [wbFromText c, a]) cs
|
. fmap ( zipWith (\c a -> [wbCell c, a]) cs
|
||||||
. showMixedAmountLinesB bopts{displayCommodity=False, displayCommodityOrder=Just cs, displayMinWidth=Nothing})
|
. cellsFromMixedAmount bopts{displayCommodity=False, displayCommodityOrder=Just cs, displayMinWidth=Nothing})
|
||||||
$ as -- Do not include totals column or average for tidy output, as this
|
$ as -- Do not include totals column or average for tidy output, as this
|
||||||
-- complicates the data representation and can be easily calculated
|
-- complicates the data representation and can be easily calculated
|
||||||
where
|
where
|
||||||
|
wbCell = Ods.defaultCell . wbFromText
|
||||||
totalscolumn = row_total_ && balanceaccum_ `notElem` [Cumulative, Historical]
|
totalscolumn = row_total_ && balanceaccum_ `notElem` [Cumulative, Historical]
|
||||||
cs = if all mixedAmountLooksZero allamts then [""] else S.toList $ foldMap maCommodities allamts
|
cs = if all mixedAmountLooksZero allamts then [""] else S.toList $ foldMap maCommodities allamts
|
||||||
allamts = (if not summary_only_ then as else []) ++
|
allamts = (if not summary_only_ then as else []) ++
|
||||||
[rowtot | totalscolumn && not (null as)] ++
|
[rowtot | totalscolumn && not (null as)] ++
|
||||||
[rowavg | average_ && not (null as)]
|
[rowavg | average_ && not (null as)]
|
||||||
addDateColumns spn@(DateSpan s e) = (wbFromText (showDateSpan spn) :)
|
addDateColumns spn@(DateSpan s e) = (wbCell (showDateSpan spn) :)
|
||||||
. (wbFromText (maybe "" showEFDate s) :)
|
. (wbCell (maybe "" showEFDate s) :)
|
||||||
. (wbFromText (maybe "" (showEFDate . modifyEFDay (addDays (-1))) e) :)
|
. (wbCell (maybe "" (showEFDate . modifyEFDay (addDays (-1))) e) :)
|
||||||
|
|
||||||
paddedTranspose :: a -> [[a]] -> [[a]]
|
paddedTranspose :: a -> [[a]] -> [[a]]
|
||||||
paddedTranspose _ [] = [[]]
|
paddedTranspose _ [] = [[]]
|
||||||
@ -880,9 +930,6 @@ multiBalanceRowAsText opts = multiBalanceRowAsTextBuilders oneLineNoCostFmt{disp
|
|||||||
multiBalanceRowAsCsvText :: ReportOpts -> [DateSpan] -> PeriodicReportRow a MixedAmount -> [[T.Text]]
|
multiBalanceRowAsCsvText :: ReportOpts -> [DateSpan] -> PeriodicReportRow a MixedAmount -> [[T.Text]]
|
||||||
multiBalanceRowAsCsvText opts colspans = fmap (fmap wbToText) . multiBalanceRowAsTextBuilders machineFmt opts colspans
|
multiBalanceRowAsCsvText opts colspans = fmap (fmap wbToText) . multiBalanceRowAsTextBuilders machineFmt opts colspans
|
||||||
|
|
||||||
multiBalanceRowAsHtmlText :: ReportOpts -> [DateSpan] -> PeriodicReportRow a MixedAmount -> [[T.Text]]
|
|
||||||
multiBalanceRowAsHtmlText opts colspans = fmap (fmap wbToText) . multiBalanceRowAsTextBuilders oneLineNoCostFmt opts colspans
|
|
||||||
|
|
||||||
-- Budget reports
|
-- Budget reports
|
||||||
|
|
||||||
-- A BudgetCell's data values rendered for display - the actual change amount,
|
-- A BudgetCell's data values rendered for display - the actual change amount,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user