lib: Write.Spreadsheet: support for anchors in HTML and FODS export
cli: Commands.Balance: new option --base-url It adds hledger-web-compatible hyperlinks to account names.
This commit is contained in:
parent
8744a0687c
commit
499c626e48
@ -140,6 +140,7 @@ data ReportOpts = ReportOpts {
|
|||||||
,no_elide_ :: Bool
|
,no_elide_ :: Bool
|
||||||
,real_ :: Bool
|
,real_ :: Bool
|
||||||
,format_ :: StringFormat
|
,format_ :: StringFormat
|
||||||
|
,balance_base_url_ :: Maybe T.Text
|
||||||
,pretty_ :: Bool
|
,pretty_ :: Bool
|
||||||
,querystring_ :: [T.Text]
|
,querystring_ :: [T.Text]
|
||||||
--
|
--
|
||||||
@ -199,6 +200,7 @@ defreportopts = ReportOpts
|
|||||||
, no_elide_ = False
|
, no_elide_ = False
|
||||||
, real_ = False
|
, real_ = False
|
||||||
, format_ = def
|
, format_ = def
|
||||||
|
, balance_base_url_ = Nothing
|
||||||
, pretty_ = False
|
, pretty_ = False
|
||||||
, querystring_ = []
|
, querystring_ = []
|
||||||
, average_ = False
|
, average_ = False
|
||||||
@ -255,6 +257,7 @@ rawOptsToReportOpts d rawopts =
|
|||||||
,no_elide_ = boolopt "no-elide" rawopts
|
,no_elide_ = boolopt "no-elide" rawopts
|
||||||
,real_ = boolopt "real" rawopts
|
,real_ = boolopt "real" rawopts
|
||||||
,format_ = format
|
,format_ = format
|
||||||
|
,balance_base_url_ = T.pack <$> maybestringopt "base-url" rawopts
|
||||||
,querystring_ = querystring
|
,querystring_ = querystring
|
||||||
,average_ = boolopt "average" rawopts
|
,average_ = boolopt "average" rawopts
|
||||||
,related_ = boolopt "related" rawopts
|
,related_ = boolopt "related" rawopts
|
||||||
|
|||||||
@ -37,6 +37,10 @@ formatRow = Lucid.tr_ . traverse_ formatCell
|
|||||||
formatCell :: (Lines border) => Cell border (Lucid.Html ()) -> Lucid.Html ()
|
formatCell :: (Lines border) => Cell border (Lucid.Html ()) -> Lucid.Html ()
|
||||||
formatCell cell =
|
formatCell cell =
|
||||||
let str = cellContent cell in
|
let str = cellContent cell in
|
||||||
|
let content =
|
||||||
|
if Text.null $ cellAnchor cell
|
||||||
|
then str
|
||||||
|
else Lucid.a_ [Lucid.href_ $ cellAnchor cell] str in
|
||||||
let border field access =
|
let border field access =
|
||||||
map (field<>) $ borderLines $ access $ cellBorder cell in
|
map (field<>) $ borderLines $ access $ cellBorder cell in
|
||||||
let leftBorder = border "border-left:" Spr.borderLeft in
|
let leftBorder = border "border-left:" Spr.borderLeft in
|
||||||
@ -51,7 +55,7 @@ formatCell cell =
|
|||||||
map Lucid.class_ $
|
map Lucid.class_ $
|
||||||
filter (not . Text.null) [Spr.textFromClass $ cellClass cell] in
|
filter (not . Text.null) [Spr.textFromClass $ cellClass cell] in
|
||||||
case cellStyle cell of
|
case cellStyle cell of
|
||||||
Head -> Lucid.th_ (style++class_) str
|
Head -> Lucid.th_ (style++class_) content
|
||||||
Body emph ->
|
Body emph ->
|
||||||
let align =
|
let align =
|
||||||
case cellType cell of
|
case cellType cell of
|
||||||
@ -62,7 +66,7 @@ formatCell cell =
|
|||||||
case emph of
|
case emph of
|
||||||
Item -> id
|
Item -> id
|
||||||
Total -> Lucid.b_
|
Total -> Lucid.b_
|
||||||
in Lucid.td_ (style++align++class_) $ withEmph str
|
in Lucid.td_ (style++align++class_) $ withEmph content
|
||||||
|
|
||||||
|
|
||||||
class (Spr.Lines border) => Lines border where
|
class (Spr.Lines border) => Lines border where
|
||||||
|
|||||||
@ -314,9 +314,16 @@ formatCell cell =
|
|||||||
(cellContent cell)
|
(cellContent cell)
|
||||||
_ -> "office:value-type='string'"
|
_ -> "office:value-type='string'"
|
||||||
|
|
||||||
|
anchor text =
|
||||||
|
if T.null $ Spr.cellAnchor cell
|
||||||
|
then text
|
||||||
|
else printf "<text:a xlink:href='%s'>%s</text:a>"
|
||||||
|
(escape $ T.unpack $ Spr.cellAnchor cell) text
|
||||||
|
|
||||||
in
|
in
|
||||||
printf "<table:table-cell%s %s>" style valueType :
|
printf "<table:table-cell%s %s>" style valueType :
|
||||||
printf "<text:p>%s</text:p>" (escape $ T.unpack $ cellContent cell) :
|
printf "<text:p>%s</text:p>"
|
||||||
|
(anchor $ escape $ T.unpack $ cellContent cell) :
|
||||||
"</table:table-cell>" :
|
"</table:table-cell>" :
|
||||||
[]
|
[]
|
||||||
|
|
||||||
|
|||||||
@ -87,13 +87,14 @@ data Cell border text =
|
|||||||
cellType :: Type,
|
cellType :: Type,
|
||||||
cellBorder :: Border border,
|
cellBorder :: Border border,
|
||||||
cellStyle :: Style,
|
cellStyle :: Style,
|
||||||
|
cellAnchor :: Text,
|
||||||
cellClass :: Class,
|
cellClass :: Class,
|
||||||
cellContent :: text
|
cellContent :: text
|
||||||
}
|
}
|
||||||
|
|
||||||
instance Functor (Cell border) where
|
instance Functor (Cell border) where
|
||||||
fmap f (Cell typ border style class_ content) =
|
fmap f (Cell typ border style anchor class_ content) =
|
||||||
Cell typ border style class_ $ f content
|
Cell typ border style anchor class_ $ f content
|
||||||
|
|
||||||
defaultCell :: (Lines border) => text -> Cell border text
|
defaultCell :: (Lines border) => text -> Cell border text
|
||||||
defaultCell text =
|
defaultCell text =
|
||||||
@ -101,6 +102,7 @@ defaultCell text =
|
|||||||
cellType = TypeString,
|
cellType = TypeString,
|
||||||
cellBorder = noBorder,
|
cellBorder = noBorder,
|
||||||
cellStyle = Body Item,
|
cellStyle = Body Item,
|
||||||
|
cellAnchor = mempty,
|
||||||
cellClass = Class mempty,
|
cellClass = Class mempty,
|
||||||
cellContent = text
|
cellContent = text
|
||||||
}
|
}
|
||||||
|
|||||||
@ -265,6 +265,7 @@ module Hledger.Cli.Commands.Balance (
|
|||||||
) where
|
) where
|
||||||
|
|
||||||
import Control.Arrow (second, (***))
|
import Control.Arrow (second, (***))
|
||||||
|
import Control.Monad (guard)
|
||||||
import Data.Decimal (roundTo)
|
import Data.Decimal (roundTo)
|
||||||
import Data.Default (def)
|
import Data.Default (def)
|
||||||
import Data.Function (on)
|
import Data.Function (on)
|
||||||
@ -337,6 +338,7 @@ balancemode = hledgerCommandMode
|
|||||||
,flagNone ["no-total","N"] (setboolopt "no-total") "omit the final total row"
|
,flagNone ["no-total","N"] (setboolopt "no-total") "omit the final total row"
|
||||||
,flagNone ["no-elide"] (setboolopt "no-elide") "don't squash boring parent accounts (in tree mode)"
|
,flagNone ["no-elide"] (setboolopt "no-elide") "don't squash boring parent accounts (in tree mode)"
|
||||||
,flagReq ["format"] (\s opts -> Right $ setopt "format" s opts) "FORMATSTR" "use this custom line format (in simple reports)"
|
,flagReq ["format"] (\s opts -> Right $ setopt "format" s opts) "FORMATSTR" "use this custom line format (in simple reports)"
|
||||||
|
,flagReq ["base-url"] (\s opts -> Right $ setopt "base-url" s opts) "URLPREFIX" "add anchors to table cells with resepct to this base URL"
|
||||||
,flagNone ["sort-amount","S"] (setboolopt "sort-amount") "sort by amount instead of account code/name (in flat mode). With multiple columns, sorts by the row total, or by row average if that is displayed."
|
,flagNone ["sort-amount","S"] (setboolopt "sort-amount") "sort by amount instead of account code/name (in flat mode). With multiple columns, sorts by the row total, or by row average if that is displayed."
|
||||||
,flagNone ["percent", "%"] (setboolopt "percent") "express values in percentage of each column's total"
|
,flagNone ["percent", "%"] (setboolopt "percent") "express values in percentage of each column's total"
|
||||||
,flagNone ["invert"] (setboolopt "invert") "display all amounts with reversed sign"
|
,flagNone ["invert"] (setboolopt "invert") "display all amounts with reversed sign"
|
||||||
@ -593,6 +595,15 @@ addTotalBorders =
|
|||||||
rawTableContent :: [[Ods.Cell border text]] -> [[text]]
|
rawTableContent :: [[Ods.Cell border text]] -> [[text]]
|
||||||
rawTableContent = map (map Ods.cellContent)
|
rawTableContent = map (map Ods.cellContent)
|
||||||
|
|
||||||
|
setAccountAnchor ::
|
||||||
|
Maybe Text -> Text -> Ods.Cell border text -> Ods.Cell border text
|
||||||
|
setAccountAnchor base acct cell =
|
||||||
|
cell
|
||||||
|
{Ods.cellAnchor =
|
||||||
|
foldMap
|
||||||
|
(\url -> url <> "register?q=inacct:" <> quoteIfSpaced acct)
|
||||||
|
base}
|
||||||
|
|
||||||
|
|
||||||
-- | Render a single-column balance report as FODS.
|
-- | Render a single-column balance report as FODS.
|
||||||
balanceReportAsSpreadsheet ::
|
balanceReportAsSpreadsheet ::
|
||||||
@ -613,16 +624,20 @@ balanceReportAsSpreadsheet opts (items, total) =
|
|||||||
rows ::
|
rows ::
|
||||||
RowClass -> AccountName ->
|
RowClass -> AccountName ->
|
||||||
MixedAmount -> [[Ods.Cell Ods.NumLines Text]]
|
MixedAmount -> [[Ods.Cell Ods.NumLines Text]]
|
||||||
rows rc name ma = case layout_ opts of
|
rows rc name ma =
|
||||||
|
let accountCell =
|
||||||
|
setAccountAnchor
|
||||||
|
(guard (rc==Value) >> balance_base_url_ opts) name $
|
||||||
|
cell $ accountNameDrop (drop_ opts) name in
|
||||||
|
case layout_ opts of
|
||||||
LayoutBare ->
|
LayoutBare ->
|
||||||
map (\a ->
|
map (\a ->
|
||||||
[showName name,
|
[accountCell,
|
||||||
cell $ acommodity a,
|
cell $ acommodity a,
|
||||||
renderAmount rc $ mixedAmount a])
|
renderAmount rc $ mixedAmount a])
|
||||||
. amounts $ mixedAmountStripCosts ma
|
. amounts $ mixedAmountStripCosts ma
|
||||||
_ -> [[showName name, renderAmount rc ma]]
|
_ -> [[accountCell, renderAmount rc ma]]
|
||||||
|
|
||||||
showName = cell . accountNameDrop (drop_ opts)
|
|
||||||
renderAmount rc mixedAmt =
|
renderAmount rc mixedAmt =
|
||||||
wbToText <$> cellFromMixedAmount bopts (amountClass rc, mixedAmt)
|
wbToText <$> cellFromMixedAmount bopts (amountClass rc, mixedAmt)
|
||||||
where
|
where
|
||||||
@ -706,9 +721,11 @@ multiBalanceReportAsSpreadsheetHelper ishtml opts@ReportOpts{..} (PeriodicReport
|
|||||||
map (headerCell . showDateSpan) colspans ++
|
map (headerCell . showDateSpan) colspans ++
|
||||||
[hCell "rowtotal" "total" | row_total_] ++
|
[hCell "rowtotal" "total" | row_total_] ++
|
||||||
[hCell "rowaverage" "average" | average_]
|
[hCell "rowaverage" "average" | average_]
|
||||||
fullRowAsTexts row =
|
fullRowAsTexts row = map (anchorCell:) $ rowAsText Value row
|
||||||
map (accountCell (showName row) :) $ rowAsText Value row
|
where anchorCell =
|
||||||
where showName = accountNameDrop drop_ . prrFullName
|
let name = prrFullName row in
|
||||||
|
setAccountAnchor balance_base_url_ name $
|
||||||
|
accountCell $ accountNameDrop drop_ name
|
||||||
totalrows
|
totalrows
|
||||||
| no_total_ = []
|
| no_total_ = []
|
||||||
| ishtml = zipWith (:) (accountCell totalRowHeadingHtml : repeat Ods.emptyCell) $ rowAsText Total tr
|
| ishtml = zipWith (:) (accountCell totalRowHeadingHtml : repeat Ods.emptyCell) $ rowAsText Total tr
|
||||||
@ -1207,7 +1224,7 @@ budgetReportAsSpreadsheet
|
|||||||
-> PeriodicReportRow a BudgetCell
|
-> PeriodicReportRow a BudgetCell
|
||||||
-> [[Ods.Cell Ods.NumLines Text]]
|
-> [[Ods.Cell Ods.NumLines Text]]
|
||||||
rowAsTexts rc render row@(PeriodicReportRow _ as (rowtot,budgettot) (rowavg, budgetavg))
|
rowAsTexts rc render row@(PeriodicReportRow _ as (rowtot,budgettot) (rowavg, budgetavg))
|
||||||
| layout_ /= LayoutBare = [cell (render row) : map showNorm vals]
|
| layout_ /= LayoutBare = [accountCell : map showNorm vals]
|
||||||
| otherwise =
|
| otherwise =
|
||||||
joinNames . zipWith (:) (map cell cs) -- add symbols and names
|
joinNames . zipWith (:) (map cell cs) -- add symbols and names
|
||||||
. transpose -- each row becomes a list of Text quantities
|
. transpose -- each row becomes a list of Text quantities
|
||||||
@ -1224,7 +1241,11 @@ budgetReportAsSpreadsheet
|
|||||||
(budgetAverageClass rc, budgetavg)]
|
(budgetAverageClass rc, budgetavg)]
|
||||||
| average_]
|
| average_]
|
||||||
|
|
||||||
joinNames = map (cell (render row) :)
|
joinNames = map (accountCell :)
|
||||||
|
accountCell =
|
||||||
|
let name = render row in
|
||||||
|
setAccountAnchor (guard (rc==Value) >> balance_base_url_) name $
|
||||||
|
cell name
|
||||||
|
|
||||||
|
|
||||||
-- tests
|
-- tests
|
||||||
|
|||||||
@ -91,6 +91,7 @@ compoundBalanceCommandMode CompoundBalanceCommandSpec{..} =
|
|||||||
,flagNone ["no-total","N"] (setboolopt "no-total") "omit the final total row"
|
,flagNone ["no-total","N"] (setboolopt "no-total") "omit the final total row"
|
||||||
,flagNone ["no-elide"] (setboolopt "no-elide") "don't squash boring parent accounts (in tree mode)"
|
,flagNone ["no-elide"] (setboolopt "no-elide") "don't squash boring parent accounts (in tree mode)"
|
||||||
,flagReq ["format"] (\s opts -> Right $ setopt "format" s opts) "FORMATSTR" "use this custom line format (in simple reports)"
|
,flagReq ["format"] (\s opts -> Right $ setopt "format" s opts) "FORMATSTR" "use this custom line format (in simple reports)"
|
||||||
|
,flagReq ["base-url"] (\s opts -> Right $ setopt "base-url" s opts) "URLPREFIX" "add anchors to table cells with resepct to this base URL"
|
||||||
,flagNone ["sort-amount","S"] (setboolopt "sort-amount") "sort by amount instead of account code/name"
|
,flagNone ["sort-amount","S"] (setboolopt "sort-amount") "sort by amount instead of account code/name"
|
||||||
,flagNone ["percent", "%"] (setboolopt "percent") "express values in percentage of each column's total"
|
,flagNone ["percent", "%"] (setboolopt "percent") "express values in percentage of each column's total"
|
||||||
,flagReq ["layout"] (\s opts -> Right $ setopt "layout" s opts) "ARG"
|
,flagReq ["layout"] (\s opts -> Right $ setopt "layout" s opts) "ARG"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user