mirror of
https://github.com/simonmichael/hledger.git
synced 2024-09-20 02:37:12 +03:00
lib,cli,ui: Use WideBuilder for Tabular.AsciiWide.
Move WideBuilder to Text.WideString.
This commit is contained in:
parent
b9c00dce61
commit
13c111da73
@ -259,11 +259,14 @@ budgetReportAsText ropts@ReportOpts{..} budgetr = TB.toLazyText $
|
|||||||
-- XXX lay out actual, percentage and/or goal in the single table cell for now, should probably use separate cells
|
-- XXX lay out actual, percentage and/or goal in the single table cell for now, should probably use separate cells
|
||||||
showcell :: (Int, Int, Int) -> BudgetDisplayCell -> Cell
|
showcell :: (Int, Int, Int) -> BudgetDisplayCell -> Cell
|
||||||
showcell (actualwidth, budgetwidth, percentwidth) ((actual,wa), mbudget) =
|
showcell (actualwidth, budgetwidth, percentwidth) ((actual,wa), mbudget) =
|
||||||
Cell TopRight [(T.replicate (actualwidth - wa) " " <> actual <> budgetstr, actualwidth + totalbudgetwidth)]
|
Cell TopRight [WideBuilder ( TB.fromText (T.replicate (actualwidth - wa) " ")
|
||||||
|
<> TB.fromText actual
|
||||||
|
<> budgetstr
|
||||||
|
) (actualwidth + totalbudgetwidth)]
|
||||||
where
|
where
|
||||||
totalpercentwidth = if percentwidth == 0 then 0 else percentwidth + 5
|
totalpercentwidth = if percentwidth == 0 then 0 else percentwidth + 5
|
||||||
totalbudgetwidth = if budgetwidth == 0 then 0 else budgetwidth + totalpercentwidth + 3
|
totalbudgetwidth = if budgetwidth == 0 then 0 else budgetwidth + totalpercentwidth + 3
|
||||||
budgetstr = case mbudget of
|
budgetstr = TB.fromText $ case mbudget of
|
||||||
Nothing -> T.replicate totalbudgetwidth " "
|
Nothing -> T.replicate totalbudgetwidth " "
|
||||||
Just ((budget, wb), Nothing) -> " [" <> T.replicate totalpercentwidth " " <> T.replicate (budgetwidth - wb) " " <> budget <> "]"
|
Just ((budget, wb), Nothing) -> " [" <> T.replicate totalpercentwidth " " <> T.replicate (budgetwidth - wb) " " <> budget <> "]"
|
||||||
Just ((budget, wb), Just (pct, wp)) -> " [" <> T.replicate (percentwidth - wp) " " <> pct <> "% of " <> T.replicate (budgetwidth - wb) " " <> budget <> "]"
|
Just ((budget, wb), Just (pct, wp)) -> " [" <> T.replicate (percentwidth - wp) " " <> pct <> "% of " <> T.replicate (budgetwidth - wb) " " <> budget <> "]"
|
||||||
|
@ -64,8 +64,8 @@ import Text.Printf (printf)
|
|||||||
import Hledger.Utils.Parse
|
import Hledger.Utils.Parse
|
||||||
import Hledger.Utils.Regex (toRegex', regexReplace)
|
import Hledger.Utils.Regex (toRegex', regexReplace)
|
||||||
import Text.Tabular (Header(..), Properties(..))
|
import Text.Tabular (Header(..), Properties(..))
|
||||||
import Text.Tabular.AsciiWide (Align(..), Cell(..), TableOpts(..), renderRow)
|
import Text.Tabular.AsciiWide (Align(..), TableOpts(..), alignCell, renderRow)
|
||||||
import Text.WideString (charWidth, strWidth, textWidth)
|
import Text.WideString (charWidth, strWidth)
|
||||||
|
|
||||||
|
|
||||||
-- | Take elements from the end of a list.
|
-- | Take elements from the end of a list.
|
||||||
@ -188,14 +188,14 @@ unbracket s
|
|||||||
concatTopPadded :: [String] -> String
|
concatTopPadded :: [String] -> String
|
||||||
concatTopPadded = TL.unpack . renderRow def{tableBorders=False, borderSpaces=False}
|
concatTopPadded = TL.unpack . renderRow def{tableBorders=False, borderSpaces=False}
|
||||||
. Group NoLine . map (Header . cell)
|
. Group NoLine . map (Header . cell)
|
||||||
where cell = Cell BottomLeft . map (\x -> (x, textWidth x)) . T.lines . T.pack
|
where cell = alignCell BottomLeft . T.pack
|
||||||
|
|
||||||
-- | Join several multi-line strings as side-by-side rectangular strings of the same height, bottom-padded.
|
-- | Join several multi-line strings as side-by-side rectangular strings of the same height, bottom-padded.
|
||||||
-- Treats wide characters as double width.
|
-- Treats wide characters as double width.
|
||||||
concatBottomPadded :: [String] -> String
|
concatBottomPadded :: [String] -> String
|
||||||
concatBottomPadded = TL.unpack . renderRow def{tableBorders=False, borderSpaces=False}
|
concatBottomPadded = TL.unpack . renderRow def{tableBorders=False, borderSpaces=False}
|
||||||
. Group NoLine . map (Header . cell)
|
. Group NoLine . map (Header . cell)
|
||||||
where cell = Cell TopLeft . map (\x -> (x, textWidth x)) . T.lines . T.pack
|
where cell = alignCell TopLeft . T.pack
|
||||||
|
|
||||||
|
|
||||||
-- | Join multi-line strings horizontally, after compressing each of
|
-- | Join multi-line strings horizontally, after compressing each of
|
||||||
|
@ -47,6 +47,7 @@ module Hledger.Utils.Text
|
|||||||
fitText,
|
fitText,
|
||||||
-- -- * wide-character-aware layout
|
-- -- * wide-character-aware layout
|
||||||
WideBuilder(..),
|
WideBuilder(..),
|
||||||
|
wbToText,
|
||||||
wbUnpack,
|
wbUnpack,
|
||||||
textWidth,
|
textWidth,
|
||||||
textTakeWidth,
|
textTakeWidth,
|
||||||
@ -68,32 +69,13 @@ import Data.Monoid
|
|||||||
#endif
|
#endif
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import qualified Data.Text.Lazy as TL
|
|
||||||
import qualified Data.Text.Lazy.Builder as TB
|
|
||||||
-- import Text.Parsec
|
-- import Text.Parsec
|
||||||
-- import Text.Printf (printf)
|
-- import Text.Printf (printf)
|
||||||
|
|
||||||
-- import Hledger.Utils.Parse
|
-- import Hledger.Utils.Parse
|
||||||
-- import Hledger.Utils.Regex
|
-- import Hledger.Utils.Regex
|
||||||
import Hledger.Utils.Test
|
import Hledger.Utils.Test
|
||||||
import Text.WideString (charWidth, textWidth)
|
import Text.WideString (WideBuilder(..), wbToText, wbUnpack, charWidth, textWidth)
|
||||||
|
|
||||||
|
|
||||||
-- | Helper for constructing Builders while keeping track of text width.
|
|
||||||
data WideBuilder = WideBuilder
|
|
||||||
{ wbBuilder :: !TB.Builder
|
|
||||||
, wbWidth :: !Int
|
|
||||||
}
|
|
||||||
|
|
||||||
instance Semigroup WideBuilder where
|
|
||||||
WideBuilder x i <> WideBuilder y j = WideBuilder (x <> y) (i + j)
|
|
||||||
|
|
||||||
instance Monoid WideBuilder where
|
|
||||||
mempty = WideBuilder mempty 0
|
|
||||||
|
|
||||||
-- | Unpack a WideBuilder to a String.
|
|
||||||
wbUnpack :: WideBuilder -> String
|
|
||||||
wbUnpack = TL.unpack . TB.toLazyText . wbBuilder
|
|
||||||
|
|
||||||
|
|
||||||
-- lowercase, uppercase :: String -> String
|
-- lowercase, uppercase :: String -> String
|
||||||
|
@ -15,7 +15,7 @@ import qualified Data.Text.Lazy as TL
|
|||||||
import Data.Text.Lazy.Builder (Builder, fromString, fromText, singleton, toLazyText)
|
import Data.Text.Lazy.Builder (Builder, fromString, fromText, singleton, toLazyText)
|
||||||
import Safe (maximumMay)
|
import Safe (maximumMay)
|
||||||
import Text.Tabular
|
import Text.Tabular
|
||||||
import Text.WideString (textWidth)
|
import Text.WideString (WideBuilder(..), textWidth)
|
||||||
|
|
||||||
|
|
||||||
-- | The options to use for rendering a table.
|
-- | The options to use for rendering a table.
|
||||||
@ -32,8 +32,7 @@ instance Default TableOpts where
|
|||||||
}
|
}
|
||||||
|
|
||||||
-- | Cell contents along an alignment
|
-- | Cell contents along an alignment
|
||||||
data Cell = Cell Align [(Text, Int)]
|
data Cell = Cell Align [WideBuilder]
|
||||||
deriving (Show)
|
|
||||||
|
|
||||||
-- | How to align text in a cell
|
-- | How to align text in a cell
|
||||||
data Align = TopRight | BottomRight | BottomLeft | TopLeft
|
data Align = TopRight | BottomRight | BottomLeft | TopLeft
|
||||||
@ -44,11 +43,11 @@ emptyCell = Cell TopRight []
|
|||||||
|
|
||||||
-- | Create a single-line cell from the given contents with its natural width.
|
-- | Create a single-line cell from the given contents with its natural width.
|
||||||
alignCell :: Align -> Text -> Cell
|
alignCell :: Align -> Text -> Cell
|
||||||
alignCell a x = Cell a [(x, textWidth x)]
|
alignCell a x = Cell a . map (\x -> WideBuilder (fromText x) (textWidth x)) $ if T.null x then [""] else T.lines x
|
||||||
|
|
||||||
-- | Return the width of a Cell.
|
-- | Return the width of a Cell.
|
||||||
cellWidth :: Cell -> Int
|
cellWidth :: Cell -> Int
|
||||||
cellWidth (Cell _ xs) = fromMaybe 0 . maximumMay $ map snd xs
|
cellWidth (Cell _ xs) = fromMaybe 0 . maximumMay $ map wbWidth xs
|
||||||
|
|
||||||
|
|
||||||
-- | Render a table according to common options, for backwards compatibility
|
-- | Render a table according to common options, for backwards compatibility
|
||||||
@ -57,7 +56,7 @@ render pretty fr fc f = renderTable def{prettyTable=pretty} (cell . fr) (cell .
|
|||||||
where cell = alignCell TopRight
|
where cell = alignCell TopRight
|
||||||
|
|
||||||
-- | Render a table according to various cell specifications>
|
-- | Render a table according to various cell specifications>
|
||||||
renderTable :: TableOpts -- ^ Options controlling Table rendering
|
renderTable :: TableOpts -- ^ Options controlling Table rendering
|
||||||
-> (rh -> Cell) -- ^ Rendering function for row headers
|
-> (rh -> Cell) -- ^ Rendering function for row headers
|
||||||
-> (ch -> Cell) -- ^ Rendering function for column headers
|
-> (ch -> Cell) -- ^ Rendering function for column headers
|
||||||
-> (a -> Cell) -- ^ Function determining the string and width of a cell
|
-> (a -> Cell) -- ^ Function determining the string and width of a cell
|
||||||
@ -66,7 +65,7 @@ renderTable :: TableOpts -- ^ Options controlling Table rendering
|
|||||||
renderTable topts fr fc f = toLazyText . renderTableB topts fr fc f
|
renderTable topts fr fc f = toLazyText . renderTableB topts fr fc f
|
||||||
|
|
||||||
-- | A version of renderTable which returns the underlying Builder.
|
-- | A version of renderTable which returns the underlying Builder.
|
||||||
renderTableB :: TableOpts -- ^ Options controlling Table rendering
|
renderTableB :: TableOpts -- ^ Options controlling Table rendering
|
||||||
-> (rh -> Cell) -- ^ Rendering function for row headers
|
-> (rh -> Cell) -- ^ Rendering function for row headers
|
||||||
-> (ch -> Cell) -- ^ Rendering function for column headers
|
-> (ch -> Cell) -- ^ Rendering function for column headers
|
||||||
-> (a -> Cell) -- ^ Function determining the string and width of a cell
|
-> (a -> Cell) -- ^ Function determining the string and width of a cell
|
||||||
@ -109,7 +108,7 @@ renderRow topts = toLazyText . renderRowB topts
|
|||||||
-- | A version of renderRow which returns the underlying Builder.
|
-- | A version of renderRow which returns the underlying Builder.
|
||||||
renderRowB:: TableOpts -> Header Cell -> Builder
|
renderRowB:: TableOpts -> Header Cell -> Builder
|
||||||
renderRowB topts h = renderColumns topts is h
|
renderRowB topts h = renderColumns topts is h
|
||||||
where is = map (\(Cell _ xs) -> fromMaybe 0 . maximumMay $ map snd xs) $ headerContents h
|
where is = map cellWidth $ headerContents h
|
||||||
|
|
||||||
|
|
||||||
verticalBar :: Bool -> Char
|
verticalBar :: Bool -> Char
|
||||||
@ -143,16 +142,16 @@ renderColumns TableOpts{prettyTable=pretty, tableBorders=borders, borderSpaces=s
|
|||||||
. zipHeader 0 is $ padRow <$> h -- Pad cell height and add width marker
|
. zipHeader 0 is $ padRow <$> h -- Pad cell height and add width marker
|
||||||
where
|
where
|
||||||
-- Pad each cell to have the appropriate width
|
-- Pad each cell to have the appropriate width
|
||||||
padCell (w, Cell TopLeft ls) = map (\(x,xw) -> fromText x <> fromText (T.replicate (w - xw) " ")) ls
|
padCell (w, Cell TopLeft ls) = map (\x -> wbBuilder x <> fromText (T.replicate (w - wbWidth x) " ")) ls
|
||||||
padCell (w, Cell BottomLeft ls) = map (\(x,xw) -> fromText x <> fromText (T.replicate (w - xw) " ")) ls
|
padCell (w, Cell BottomLeft ls) = map (\x -> wbBuilder x <> fromText (T.replicate (w - wbWidth x) " ")) ls
|
||||||
padCell (w, Cell TopRight ls) = map (\(x,xw) -> fromText (T.replicate (w - xw) " ") <> fromText x) ls
|
padCell (w, Cell TopRight ls) = map (\x -> fromText (T.replicate (w - wbWidth x) " ") <> wbBuilder x) ls
|
||||||
padCell (w, Cell BottomRight ls) = map (\(x,xw) -> fromText (T.replicate (w - xw) " ") <> fromText x) ls
|
padCell (w, Cell BottomRight ls) = map (\x -> fromText (T.replicate (w - wbWidth x) " ") <> wbBuilder x) ls
|
||||||
|
|
||||||
-- Pad each cell to have the same number of lines
|
-- Pad each cell to have the same number of lines
|
||||||
padRow (Cell TopLeft ls) = Cell TopLeft $ ls ++ replicate (nLines - length ls) ("",0)
|
padRow (Cell TopLeft ls) = Cell TopLeft $ ls ++ replicate (nLines - length ls) mempty
|
||||||
padRow (Cell TopRight ls) = Cell TopRight $ ls ++ replicate (nLines - length ls) ("",0)
|
padRow (Cell TopRight ls) = Cell TopRight $ ls ++ replicate (nLines - length ls) mempty
|
||||||
padRow (Cell BottomLeft ls) = Cell BottomLeft $ replicate (nLines - length ls) ("",0) ++ ls
|
padRow (Cell BottomLeft ls) = Cell BottomLeft $ replicate (nLines - length ls) mempty ++ ls
|
||||||
padRow (Cell BottomRight ls) = Cell BottomRight $ replicate (nLines - length ls) ("",0) ++ ls
|
padRow (Cell BottomRight ls) = Cell BottomRight $ replicate (nLines - length ls) mempty ++ ls
|
||||||
|
|
||||||
hsep :: Properties -> [Builder]
|
hsep :: Properties -> [Builder]
|
||||||
hsep NoLine = replicate nLines $ if spaces then " " else ""
|
hsep NoLine = replicate nLines $ if spaces then " " else ""
|
||||||
|
@ -4,11 +4,38 @@ module Text.WideString (
|
|||||||
-- * wide-character-aware layout
|
-- * wide-character-aware layout
|
||||||
strWidth,
|
strWidth,
|
||||||
textWidth,
|
textWidth,
|
||||||
charWidth
|
charWidth,
|
||||||
|
-- * Text Builders which keep track of length
|
||||||
|
WideBuilder(..),
|
||||||
|
wbUnpack,
|
||||||
|
wbToText
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
|
import qualified Data.Text.Lazy as TL
|
||||||
|
import qualified Data.Text.Lazy.Builder as TB
|
||||||
|
|
||||||
|
|
||||||
|
-- | Helper for constructing Builders while keeping track of text width.
|
||||||
|
data WideBuilder = WideBuilder
|
||||||
|
{ wbBuilder :: !TB.Builder
|
||||||
|
, wbWidth :: !Int
|
||||||
|
}
|
||||||
|
|
||||||
|
instance Semigroup WideBuilder where
|
||||||
|
WideBuilder x i <> WideBuilder y j = WideBuilder (x <> y) (i + j)
|
||||||
|
|
||||||
|
instance Monoid WideBuilder where
|
||||||
|
mempty = WideBuilder mempty 0
|
||||||
|
|
||||||
|
-- | Convert a WideBuilder to a strict Text.
|
||||||
|
wbToText :: WideBuilder -> Text
|
||||||
|
wbToText = TL.toStrict . TB.toLazyText . wbBuilder
|
||||||
|
|
||||||
|
-- | Convert a WideBuilder to a String.
|
||||||
|
wbUnpack :: WideBuilder -> String
|
||||||
|
wbUnpack = TL.unpack . TB.toLazyText . wbBuilder
|
||||||
|
|
||||||
|
|
||||||
-- | Calculate the render width of a string, considering
|
-- | Calculate the render width of a string, considering
|
||||||
|
@ -97,7 +97,7 @@ rsInit d reset ui@UIState{aopts=_uopts@UIOpts{cliopts_=CliOpts{reportspec_=rspec
|
|||||||
,rsItemBalanceAmount = showamt bal
|
,rsItemBalanceAmount = showamt bal
|
||||||
,rsItemTransaction = t
|
,rsItemTransaction = t
|
||||||
}
|
}
|
||||||
where showamt = \((WideBuilder b w) -> (TL.toStrict $ TB.toLazyText b, w))
|
where showamt = (\wb -> (wbUnpack wb, wbWidth wb))
|
||||||
. showMixed oneLine{displayMaxWidth=Just 32}
|
. showMixed oneLine{displayMaxWidth=Just 32}
|
||||||
-- blank items are added to allow more control of scroll position; we won't allow movement over these.
|
-- blank items are added to allow more control of scroll position; we won't allow movement over these.
|
||||||
-- XXX Ugly. Changing to 0 helps when debugging.
|
-- XXX Ugly. Changing to 0 helps when debugging.
|
||||||
|
@ -420,27 +420,25 @@ renderBalanceReportItem opts (acctname, depth, total) =
|
|||||||
, map cellWidth is )
|
, map cellWidth is )
|
||||||
|
|
||||||
render topaligned oneline = map (maybeConcat . renderComponent topaligned opts (acctname, depth, total))
|
render topaligned oneline = map (maybeConcat . renderComponent topaligned opts (acctname, depth, total))
|
||||||
where maybeConcat (Cell a xs) = if oneline then Cell a [(T.intercalate ", " strs, width)]
|
where maybeConcat (Cell a xs) =
|
||||||
else Cell a xs
|
if oneline then Cell a [WideBuilder (mconcat . intersperse (TB.fromText ", ") $ map wbBuilder xs) width]
|
||||||
where
|
else Cell a xs
|
||||||
(strs, ws) = unzip xs
|
where width = sumStrict (map ((+2) . wbWidth) xs) -2
|
||||||
width = sumStrict (map (+2) ws) -2
|
|
||||||
|
|
||||||
|
|
||||||
-- | Render one StringFormat component for a balance report item.
|
-- | Render one StringFormat component for a balance report item.
|
||||||
renderComponent :: Bool -> ReportOpts -> (AccountName, Int, MixedAmount) -> StringFormatComponent -> Cell
|
renderComponent :: Bool -> ReportOpts -> (AccountName, Int, MixedAmount) -> StringFormatComponent -> Cell
|
||||||
renderComponent _ _ _ (FormatLiteral s) = Cell TopLeft . map (\x -> (x, textWidth x)) $ T.lines s
|
renderComponent _ _ _ (FormatLiteral s) = alignCell TopLeft s
|
||||||
renderComponent topaligned opts (acctname, depth, total) (FormatField ljust mmin mmax field) = case field of
|
renderComponent topaligned opts (acctname, depth, total) (FormatField ljust mmin mmax field) = case field of
|
||||||
DepthSpacerField -> Cell align [(T.replicate d " ", d)]
|
DepthSpacerField -> Cell align [WideBuilder (TB.fromText $ T.replicate d " ") d]
|
||||||
where d = maybe id min mmax $ depth * fromMaybe 1 mmin
|
where d = maybe id min mmax $ depth * fromMaybe 1 mmin
|
||||||
AccountField -> Cell align [(t, textWidth t)] where t = formatText ljust mmin mmax acctname
|
AccountField -> alignCell align $ formatText ljust mmin mmax acctname
|
||||||
TotalField -> Cell align . pure $ showamt total
|
TotalField -> Cell align . pure $ showamt total
|
||||||
_ -> Cell align [("", 0)]
|
_ -> Cell align [mempty]
|
||||||
where
|
where
|
||||||
align = if topaligned then (if ljust then TopLeft else TopRight)
|
align = if topaligned then (if ljust then TopLeft else TopRight)
|
||||||
else (if ljust then BottomLeft else BottomRight)
|
else (if ljust then BottomLeft else BottomRight)
|
||||||
showamt = (\(WideBuilder b w) -> (TL.toStrict $ TB.toLazyText b, w))
|
showamt = showMixed noPrice{displayColour=color_ opts, displayMinWidth=mmin, displayMaxWidth=mmax}
|
||||||
. showMixed noPrice{displayColour=color_ opts, displayMinWidth=mmin, displayMaxWidth=mmax}
|
|
||||||
|
|
||||||
-- rendering multi-column balance reports
|
-- rendering multi-column balance reports
|
||||||
|
|
||||||
@ -629,7 +627,7 @@ balanceReportTableAsText ReportOpts{..} =
|
|||||||
Tab.renderTableB def{tableBorders=False, prettyTable=pretty_tables_}
|
Tab.renderTableB def{tableBorders=False, prettyTable=pretty_tables_}
|
||||||
(Tab.alignCell TopLeft) (Tab.alignCell TopRight) showamt
|
(Tab.alignCell TopLeft) (Tab.alignCell TopRight) showamt
|
||||||
where
|
where
|
||||||
showamt = Cell TopRight . (\(WideBuilder b w) -> [(TL.toStrict $ TB.toLazyText b, w)]) . showMixed oneLine{displayColour=color_, displayMaxWidth=mmax}
|
showamt = Cell TopRight . pure . showMixed oneLine{displayColour=color_, displayMaxWidth=mmax}
|
||||||
mmax = if no_elide_ then Nothing else Just 32
|
mmax = if no_elide_ then Nothing else Just 32
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user