cli: CompoundBalanceCommand.compoundBalanceReportAsSpreadsheet: common function for CSV, HTML, FODS export

This commit is contained in:
Henning Thielemann 2024-10-12 21:08:06 +02:00 committed by Simon Michael
parent 71a7879213
commit 9d1ba5c588
3 changed files with 78 additions and 72 deletions

View File

@ -18,6 +18,7 @@ module Hledger.Write.Spreadsheet (
emptyCell, emptyCell,
transposeCell, transposeCell,
transpose, transpose,
horizontalSpan,
addRowSpanHeader, addRowSpanHeader,
rawTableContent, rawTableContent,
) where ) where
@ -171,6 +172,15 @@ transpose :: [[Cell border text]] -> [[Cell border text]]
transpose = List.transpose . map (map transposeCell) transpose = List.transpose . map (map transposeCell)
horizontalSpan ::
(Lines border, Monoid text) =>
[a] -> Cell border text -> [Cell border text]
horizontalSpan subCells cell =
zipWith const
(cell{cellSpan = SpanHorizontal $ length subCells}
: repeat (emptyCell {cellSpan = Covered}))
subCells
addRowSpanHeader :: addRowSpanHeader ::
Cell border text -> Cell border text ->
[[Cell border text]] -> [[Cell border text]] [[Cell border text]] -> [[Cell border text]]

View File

@ -266,6 +266,7 @@ module Hledger.Cli.Commands.Balance (
,multiBalanceHasTotalsColumn ,multiBalanceHasTotalsColumn
,addTotalBorders ,addTotalBorders
,simpleDateSpanCell ,simpleDateSpanCell
,nbsp
,RowClass(..) ,RowClass(..)
-- ** Tests -- ** Tests
,tests_Balance ,tests_Balance

View File

@ -14,19 +14,24 @@ module Hledger.Cli.CompoundBalanceCommand (
,compoundBalanceCommand ,compoundBalanceCommand
) where ) where
import Data.Maybe (fromMaybe, mapMaybe, maybeToList)
import Data.List.NonEmpty (NonEmpty((:|)))
import Data.Bifunctor (second)
import qualified Data.Map as Map
import qualified Data.List as List import qualified Data.List as List
import Data.Maybe (fromMaybe, mapMaybe) import qualified Data.List.NonEmpty as NonEmpty
import qualified Data.Text as T import qualified Data.Text as T
import qualified Data.Text.Lazy as TL import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.Builder as TB import qualified Data.Text.Lazy.Builder as TB
import Data.Time.Calendar (Day, addDays) import Data.Time.Calendar (Day, addDays)
import System.Console.CmdArgs.Explicit as C (Mode, flagNone, flagReq) import System.Console.CmdArgs.Explicit as C (Mode, flagNone, flagReq)
import qualified System.IO as IO
import Hledger.Write.Ods (printFods)
import Hledger.Write.Csv (CSV, printCSV, printTSV) import Hledger.Write.Csv (CSV, printCSV, printTSV)
import Hledger.Write.Html.Attribute (stylesheet, tableStyle, alignleft, alignright) import Hledger.Write.Html.Lucid (printHtml)
import qualified Hledger.Write.Html.Lucid as Html import Hledger.Write.Html.Attribute (stylesheet, tableStyle, alignleft)
import qualified Hledger.Write.Spreadsheet as Spr import qualified Hledger.Write.Spreadsheet as Spr
import Lucid as L hiding (value_) import Lucid as L hiding (value_)
import Safe (tailDef)
import Text.Tabular.AsciiWide as Tabular hiding (render) import Text.Tabular.AsciiWide as Tabular hiding (render)
import Hledger import Hledger
@ -197,6 +202,10 @@ compoundBalanceCommand CompoundBalanceCommandSpec{..} opts@CliOpts{reportspec_=r
"csv" -> printCSV . compoundBalanceReportAsCsv ropts' "csv" -> printCSV . compoundBalanceReportAsCsv ropts'
"tsv" -> printTSV . compoundBalanceReportAsCsv ropts' "tsv" -> printTSV . compoundBalanceReportAsCsv ropts'
"html" -> L.renderText . compoundBalanceReportAsHtml ropts' "html" -> L.renderText . compoundBalanceReportAsHtml ropts'
"fods" -> printFods IO.localeEncoding .
fmap (second NonEmpty.toList) . uncurry Map.singleton .
compoundBalanceReportAsSpreadsheet
oneLineNoCostFmt "Account" (Just "") ropts'
"json" -> toJsonText "json" -> toJsonText
x -> error' $ unsupportedOutputFormatError x x -> error' $ unsupportedOutputFormatError x
@ -302,99 +311,85 @@ compoundBalanceReportAsText ropts (CompoundPeriodicReport title _colspans subrep
-- subreport title row, and an overall title row, one headings row, and an -- subreport title row, and an overall title row, one headings row, and an
-- optional overall totals row is added. -- optional overall totals row is added.
compoundBalanceReportAsCsv :: ReportOpts -> CompoundPeriodicReport DisplayName MixedAmount -> CSV compoundBalanceReportAsCsv :: ReportOpts -> CompoundPeriodicReport DisplayName MixedAmount -> CSV
compoundBalanceReportAsCsv ropts (CompoundPeriodicReport title colspans subreports totalrow) = compoundBalanceReportAsCsv ropts cbr =
addtotals $ let spreadsheet =
padRow title snd $ snd $
: ( "Account" compoundBalanceReportAsSpreadsheet
: ["Commodity" | layout_ ropts == LayoutBare] machineFmt "Account" Nothing ropts cbr
++ map (reportPeriodName (balanceaccum_ ropts) colspans) colspans in Spr.rawTableContent $
++ (if multiBalanceHasTotalsColumn ropts then ["Total"] else []) Spr.horizontalSpan (NonEmpty.head spreadsheet)
++ (if average_ ropts then ["Average"] else []) (Spr.headerCell (cbrTitle cbr)) :
) NonEmpty.toList spreadsheet
: concatMap (subreportAsCsv ropts) subreports
where
-- | Add a subreport title row and drop the heading row.
subreportAsCsv ropts1 (subreporttitle, multibalreport, _) =
padRow subreporttitle :
tailDef [] (multiBalanceReportAsCsv ropts1 multibalreport)
padRow s = take numcols $ s : repeat ""
where
numcols
| null subreports = 1
| otherwise =
(1 +) $ -- account name column
(if layout_ ropts == LayoutBare then (1+) else id) $
(if multiBalanceHasTotalsColumn ropts then (1+) else id) $
(if average_ ropts then (1+) else id) $
maximum $ -- depends on non-null subreports
map (length . prDates . second3) subreports
addtotals
| no_total_ ropts || length subreports == 1 = id
| otherwise = (++ map ("Net:" : ) (multiBalanceRowAsCsvText ropts colspans totalrow))
-- | Render a compound balance report as HTML. -- | Render a compound balance report as HTML.
compoundBalanceReportAsHtml :: ReportOpts -> CompoundPeriodicReport DisplayName MixedAmount -> Html () compoundBalanceReportAsHtml :: ReportOpts -> CompoundPeriodicReport DisplayName MixedAmount -> Html ()
compoundBalanceReportAsHtml ropts cbr = compoundBalanceReportAsHtml ropts cbr =
let (title, (_fixed, cells)) =
compoundBalanceReportAsSpreadsheet
oneLineNoCostFmt "" (Just nbsp) ropts cbr
colspanattr = colspan_ $ T.pack $ show $ length $ NonEmpty.head cells
in do
link_ [rel_ "stylesheet", href_ "hledger.css"]
style_ $ stylesheet $
tableStyle ++ [
("td:nth-child(1)", "white-space:nowrap"),
("tr:nth-child(odd) td", "background-color:#eee")
]
table_ $ do
tr_ $ th_ [colspanattr, style_ alignleft] $ h2_ $ toHtml title
printHtml $ NonEmpty.toList $ fmap (map (fmap L.toHtml)) cells
-- | Render a compound balance report as Spreadsheet.
compoundBalanceReportAsSpreadsheet ::
AmountFormat -> T.Text -> Maybe T.Text ->
ReportOpts -> CompoundPeriodicReport DisplayName MixedAmount ->
(T.Text, ((Maybe Int, Maybe Int), NonEmpty [Spr.Cell Spr.NumLines T.Text]))
compoundBalanceReportAsSpreadsheet fmt accountLabel maybeBlank ropts cbr =
let let
CompoundPeriodicReport title colspans subreports totalrow = cbr CompoundPeriodicReport title colspans subreports totalrow = cbr
headerrow = headerrow =
th_ "" : Spr.headerCell accountLabel :
(guard (layout_ ropts == LayoutBare) >> [th_ "Commodity"]) ++ (guard (layout_ ropts == LayoutBare) >> [Spr.headerCell "Commodity"]) ++
map (th_ [style_ alignright] . toHtml . map (Spr.headerCell . reportPeriodName (balanceaccum_ ropts) colspans)
reportPeriodName (balanceaccum_ ropts) colspans)
colspans ++ colspans ++
(guard (multiBalanceHasTotalsColumn ropts) >> [th_ "Total"]) ++ (guard (multiBalanceHasTotalsColumn ropts) >> [Spr.headerCell "Total"]) ++
(guard (average_ ropts) >> [th_ "Average"]) (guard (average_ ropts) >> [Spr.headerCell "Average"])
colspanattr = colspan_ $ T.pack $ show $ length headerrow blankrow =
leftattr = style_ alignleft fmap (Spr.horizontalSpan headerrow . Spr.defaultCell) maybeBlank
blankrow = tr_ $ td_ [colspanattr] $ toHtmlRaw (" "::String)
titlerows =
[tr_ $ th_ [colspanattr, leftattr] $ h2_ $ toHtml title
,tr_ $ mconcat headerrow
]
-- Make rows for a subreport: its title row, not the headings row, -- Make rows for a subreport: its title row, not the headings row,
-- the data rows, any totals row, and a blank row for whitespace. -- the data rows, any totals row, and a blank row for whitespace.
subreportrows :: (T.Text, MultiBalanceReport, Bool) -> [Html ()] subreportrows ::
(T.Text, MultiBalanceReport, Bool) -> [[Spr.Cell Spr.NumLines T.Text]]
subreportrows (subreporttitle, mbr, _increasestotal) = subreportrows (subreporttitle, mbr, _increasestotal) =
let let
-- TODO: should the commodity_column be displayed as a subaccount in this case as well?
(_, bodyrows, mtotalsrows) = (_, bodyrows, mtotalsrows) =
multiBalanceReportAsSpreadsheetParts oneLineNoCostFmt ropts mbr multiBalanceReportAsSpreadsheetParts fmt ropts mbr
formatRow = Html.formatRow . map (fmap L.toHtml)
in in
[tr_ $ th_ [colspanattr, leftattr, class_ "account"] $ toHtml subreporttitle] Spr.horizontalSpan headerrow
++ map formatRow bodyrows ((Spr.defaultCell subreporttitle){
++ map formatRow mtotalsrows Spr.cellStyle = Spr.Body Spr.Total,
++ [blankrow] Spr.cellClass = Spr.Class "account"
}) :
bodyrows ++
mtotalsrows ++
maybeToList blankrow ++
[]
totalrows = totalrows =
if no_total_ ropts || length subreports == 1 then [] if no_total_ ropts || length subreports == 1 then []
else else
multiBalanceRowAsCellBuilders oneLineNoCostFmt ropts colspans multiBalanceRowAsCellBuilders fmt ropts colspans
Total simpleDateSpanCell totalrow Total simpleDateSpanCell totalrow
-- make a table of rendered lines of the report totals row -- make a table of rendered lines of the report totals row
& map (map (fmap wbToText)) & map (map (fmap wbToText))
& Spr.addRowSpanHeader & Spr.addRowSpanHeader
((Spr.defaultCell "Net:") {Spr.cellClass = Spr.Class "account"}) ((Spr.defaultCell "Net:") {Spr.cellClass = Spr.Class "account"})
-- insert a headings column, with Net: on the first line only -- insert a headings column, with Net: on the first line only
& addTotalBorders -- marking the first for special styling & addTotalBorders -- marking the first row for special styling
& map (Html.formatRow . map (fmap L.toHtml))
-- convert to a list of HTML totals rows
in do
link_ [rel_ "stylesheet", href_ "hledger.css"]
style_ $ stylesheet $
tableStyle ++ [
("td:nth-child(1)", "white-space:nowrap"),
("tr:nth-child(even) td", "background-color:#eee")
]
table_ $ mconcat $
titlerows
++ [blankrow]
++ concatMap subreportrows subreports
++ totalrows
in (title,
((Just 1, Just 1),
headerrow :| concatMap subreportrows subreports ++ totalrows))