packages feed

penny-lib 0.20.0.0 → 0.21.0.0

raw patch · 43 files changed

+1484/−930 lines, 43 filesdep +randomdep ~multiargPVP ok

version bump matches the API change (PVP)

Dependencies added: random

Dependency ranges changed: multiarg

API changes (from Hackage documentation)

- Penny: balanceAsIs :: a -> Qty -> Text
- Penny: balanceFormat :: Defaults -> Commodity -> Qty -> Text
- Penny: convertFormat :: Defaults -> Commodity -> Qty -> Text
- Penny: defaultQtyFormat :: Qty -> Text
- Penny: postingsBalanceFormat :: Defaults -> Commodity -> Qty -> Text
- Penny: postingsQtyFormat :: Defaults -> (PostMeta, Posting) -> Text
- Penny: qtyAsIs :: (PostMeta, Posting) -> Text
- Penny.Brenner: GroupAll :: GroupSpec
- Penny.Brenner: GroupLarge :: GroupSpec
- Penny.Brenner: GroupSpecs :: GroupSpec -> GroupSpec -> GroupSpecs
- Penny.Brenner: NoGrouping :: GroupSpec
- Penny.Brenner: data GroupSpec
- Penny.Brenner: data GroupSpecs
- Penny.Brenner: groupSpecs :: FitAcct -> GroupSpecs
- Penny.Brenner: left :: GroupSpecs -> GroupSpec
- Penny.Brenner: right :: GroupSpecs -> GroupSpec
- Penny.Brenner.Types: ConfigLocation :: Text -> ConfigLocation
- Penny.Brenner.Types: groupSpecs :: FitAcct -> GroupSpecs
- Penny.Brenner.Types: instance Eq ConfigLocation
- Penny.Brenner.Types: instance HasText ConfigLocation
- Penny.Brenner.Types: instance Show ConfigLocation
- Penny.Brenner.Types: newtype ConfigLocation
- Penny.Brenner.Types: unConfigLocation :: ConfigLocation -> Text
- Penny.Cabin.Balance.Convert.Options: format :: DefaultOpts -> Commodity -> Qty -> Text
- Penny.Cabin.Balance.MultiCommodity: defaultFormat :: a -> Qty -> Text
- Penny.Cabin.Posts: balanceAsIs :: a -> Qty -> Text
- Penny.Cabin.Posts: balanceFormat :: ZincOpts -> Commodity -> Qty -> Text
- Penny.Cabin.Posts: qtyAsIs :: (PostMeta, Posting) -> Text
- Penny.Cabin.Posts: qtyFormat :: ZincOpts -> (PostMeta, Posting) -> Text
- Penny.Cabin.Posts.Chunk: balanceFormat :: ChunkOpts -> Commodity -> Qty -> Text
- Penny.Cabin.Posts.Growers: balanceFormat :: GrowOpts -> Commodity -> Qty -> Text
- Penny.Copper: GroupAll :: GroupSpec
- Penny.Copper: GroupLarge :: GroupSpec
- Penny.Copper: GroupSpecs :: GroupSpec -> GroupSpec -> GroupSpecs
- Penny.Copper: NoGrouping :: GroupSpec
- Penny.Copper: data GroupSpec
- Penny.Copper: data GroupSpecs
- Penny.Copper: left :: GroupSpecs -> GroupSpec
- Penny.Copper: right :: GroupSpecs -> GroupSpec
- Penny.Copper.Interface: instance Binary BlankLine
- Penny.Copper.Interface: instance Binary Comment
- Penny.Copper.Interface: instance Binary ParsedTopLine
- Penny.Copper.Interface: instance Constructor C1_0BlankLine
- Penny.Copper.Interface: instance Constructor C1_0ParsedTopLine
- Penny.Copper.Interface: instance Datatype D1BlankLine
- Penny.Copper.Interface: instance Datatype D1ParsedTopLine
- Penny.Copper.Interface: instance Generic BlankLine
- Penny.Copper.Interface: instance Generic ParsedTopLine
- Penny.Copper.Interface: instance Selector S1_0_0ParsedTopLine
- Penny.Copper.Interface: instance Selector S1_0_1ParsedTopLine
- Penny.Copper.Interface: instance Selector S1_0_2ParsedTopLine
- Penny.Copper.Interface: instance Selector S1_0_3ParsedTopLine
- Penny.Copper.Interface: instance Selector S1_0_4ParsedTopLine
- Penny.Copper.Interface: instance Selector S1_0_5ParsedTopLine
- Penny.Copper.Parsec: digitGroup :: Parser [Char]
- Penny.Copper.Parsec: digitPostSequence :: Parser (Maybe [Char])
- Penny.Copper.Parsec: digitSequence :: Parser [Char]
- Penny.Copper.Parsec: quantity :: Parser Qty
- Penny.Copper.Render: GroupAll :: GroupSpec
- Penny.Copper.Render: GroupLarge :: GroupSpec
- Penny.Copper.Render: GroupSpecs :: GroupSpec -> GroupSpec -> GroupSpecs
- Penny.Copper.Render: NoGrouping :: GroupSpec
- Penny.Copper.Render: data GroupSpec
- Penny.Copper.Render: data GroupSpecs
- Penny.Copper.Render: groupDecimal :: GroupSpec -> String -> String
- Penny.Copper.Render: groupWhole :: GroupSpec -> String -> String
- Penny.Copper.Render: grouper :: String
- Penny.Copper.Render: instance Eq GroupSpec
- Penny.Copper.Render: instance Show GroupSpec
- Penny.Copper.Render: instance Show GroupSpecs
- Penny.Copper.Render: left :: GroupSpecs -> GroupSpec
- Penny.Copper.Render: quantity :: GroupSpecs -> Qty -> Text
- Penny.Copper.Render: radix :: String
- Penny.Copper.Render: right :: GroupSpecs -> GroupSpec
- Penny.Lincoln.Bits: instance Binary PostingCore
- Penny.Lincoln.Bits: instance Binary PostingData
- Penny.Lincoln.Bits: instance Binary PostingFileMeta
- Penny.Lincoln.Bits: instance Binary PricePoint
- Penny.Lincoln.Bits: instance Binary TopLineCore
- Penny.Lincoln.Bits: instance Binary TopLineData
- Penny.Lincoln.Bits: instance Binary TopLineFileMeta
- Penny.Lincoln.Bits: instance Constructor C1_0PostingCore
- Penny.Lincoln.Bits: instance Constructor C1_0PostingData
- Penny.Lincoln.Bits: instance Constructor C1_0PostingFileMeta
- Penny.Lincoln.Bits: instance Constructor C1_0PricePoint
- Penny.Lincoln.Bits: instance Constructor C1_0TopLineCore
- Penny.Lincoln.Bits: instance Constructor C1_0TopLineData
- Penny.Lincoln.Bits: instance Constructor C1_0TopLineFileMeta
- Penny.Lincoln.Bits: instance Datatype D1PostingCore
- Penny.Lincoln.Bits: instance Datatype D1PostingData
- Penny.Lincoln.Bits: instance Datatype D1PostingFileMeta
- Penny.Lincoln.Bits: instance Datatype D1PricePoint
- Penny.Lincoln.Bits: instance Datatype D1TopLineCore
- Penny.Lincoln.Bits: instance Datatype D1TopLineData
- Penny.Lincoln.Bits: instance Datatype D1TopLineFileMeta
- Penny.Lincoln.Bits: instance Generic PostingCore
- Penny.Lincoln.Bits: instance Generic PostingData
- Penny.Lincoln.Bits: instance Generic PostingFileMeta
- Penny.Lincoln.Bits: instance Generic PricePoint
- Penny.Lincoln.Bits: instance Generic TopLineCore
- Penny.Lincoln.Bits: instance Generic TopLineData
- Penny.Lincoln.Bits: instance Generic TopLineFileMeta
- Penny.Lincoln.Bits: instance Selector S1_0_0PostingCore
- Penny.Lincoln.Bits: instance Selector S1_0_0PostingData
- Penny.Lincoln.Bits: instance Selector S1_0_0PostingFileMeta
- Penny.Lincoln.Bits: instance Selector S1_0_0PricePoint
- Penny.Lincoln.Bits: instance Selector S1_0_0TopLineCore
- Penny.Lincoln.Bits: instance Selector S1_0_0TopLineData
- Penny.Lincoln.Bits: instance Selector S1_0_0TopLineFileMeta
- Penny.Lincoln.Bits: instance Selector S1_0_1PostingCore
- Penny.Lincoln.Bits: instance Selector S1_0_1PostingData
- Penny.Lincoln.Bits: instance Selector S1_0_1PostingFileMeta
- Penny.Lincoln.Bits: instance Selector S1_0_1PricePoint
- Penny.Lincoln.Bits: instance Selector S1_0_1TopLineCore
- Penny.Lincoln.Bits: instance Selector S1_0_1TopLineData
- Penny.Lincoln.Bits: instance Selector S1_0_1TopLineFileMeta
- Penny.Lincoln.Bits: instance Selector S1_0_2PostingCore
- Penny.Lincoln.Bits: instance Selector S1_0_2PostingData
- Penny.Lincoln.Bits: instance Selector S1_0_2PricePoint
- Penny.Lincoln.Bits: instance Selector S1_0_2TopLineCore
- Penny.Lincoln.Bits: instance Selector S1_0_2TopLineData
- Penny.Lincoln.Bits: instance Selector S1_0_2TopLineFileMeta
- Penny.Lincoln.Bits: instance Selector S1_0_3PostingCore
- Penny.Lincoln.Bits: instance Selector S1_0_3PricePoint
- Penny.Lincoln.Bits: instance Selector S1_0_3TopLineCore
- Penny.Lincoln.Bits: instance Selector S1_0_3TopLineFileMeta
- Penny.Lincoln.Bits: instance Selector S1_0_4PostingCore
- Penny.Lincoln.Bits: instance Selector S1_0_4PricePoint
- Penny.Lincoln.Bits: instance Selector S1_0_4TopLineCore
- Penny.Lincoln.Bits: instance Selector S1_0_5PostingCore
- Penny.Lincoln.Bits: instance Selector S1_0_6PostingCore
- Penny.Lincoln.Bits: instance Selector S1_0_7PostingCore
- Penny.Lincoln.Bits.DateTime: instance Binary DateTime
- Penny.Lincoln.Bits.DateTime: instance Binary Hours
- Penny.Lincoln.Bits.DateTime: instance Binary Minutes
- Penny.Lincoln.Bits.DateTime: instance Binary Seconds
- Penny.Lincoln.Bits.DateTime: instance Binary TimeZoneOffset
- Penny.Lincoln.Bits.DateTime: instance Constructor C1_0Hours
- Penny.Lincoln.Bits.DateTime: instance Constructor C1_0Minutes
- Penny.Lincoln.Bits.DateTime: instance Constructor C1_0Seconds
- Penny.Lincoln.Bits.DateTime: instance Constructor C1_0TimeZoneOffset
- Penny.Lincoln.Bits.DateTime: instance Datatype D1Hours
- Penny.Lincoln.Bits.DateTime: instance Datatype D1Minutes
- Penny.Lincoln.Bits.DateTime: instance Datatype D1Seconds
- Penny.Lincoln.Bits.DateTime: instance Datatype D1TimeZoneOffset
- Penny.Lincoln.Bits.DateTime: instance Generic Hours
- Penny.Lincoln.Bits.DateTime: instance Generic Minutes
- Penny.Lincoln.Bits.DateTime: instance Generic Seconds
- Penny.Lincoln.Bits.DateTime: instance Generic TimeZoneOffset
- Penny.Lincoln.Bits.DateTime: instance Selector S1_0_0Hours
- Penny.Lincoln.Bits.DateTime: instance Selector S1_0_0Minutes
- Penny.Lincoln.Bits.DateTime: instance Selector S1_0_0Seconds
- Penny.Lincoln.Bits.DateTime: instance Selector S1_0_0TimeZoneOffset
- Penny.Lincoln.Bits.Open: instance Binary Account
- Penny.Lincoln.Bits.Open: instance Binary Amount
- Penny.Lincoln.Bits.Open: instance Binary Commodity
- Penny.Lincoln.Bits.Open: instance Binary DrCr
- Penny.Lincoln.Bits.Open: instance Binary Entry
- Penny.Lincoln.Bits.Open: instance Binary FilePosting
- Penny.Lincoln.Bits.Open: instance Binary FileTransaction
- Penny.Lincoln.Bits.Open: instance Binary Filename
- Penny.Lincoln.Bits.Open: instance Binary Flag
- Penny.Lincoln.Bits.Open: instance Binary GlobalPosting
- Penny.Lincoln.Bits.Open: instance Binary GlobalTransaction
- Penny.Lincoln.Bits.Open: instance Binary Memo
- Penny.Lincoln.Bits.Open: instance Binary Number
- Penny.Lincoln.Bits.Open: instance Binary Payee
- Penny.Lincoln.Bits.Open: instance Binary PostingLine
- Penny.Lincoln.Bits.Open: instance Binary PriceLine
- Penny.Lincoln.Bits.Open: instance Binary Side
- Penny.Lincoln.Bits.Open: instance Binary SpaceBetween
- Penny.Lincoln.Bits.Open: instance Binary SubAccount
- Penny.Lincoln.Bits.Open: instance Binary Tag
- Penny.Lincoln.Bits.Open: instance Binary Tags
- Penny.Lincoln.Bits.Open: instance Binary TopLineLine
- Penny.Lincoln.Bits.Open: instance Binary TopMemoLine
- Penny.Lincoln.Bits.Open: instance Constructor C1_0Account
- Penny.Lincoln.Bits.Open: instance Constructor C1_0Amount
- Penny.Lincoln.Bits.Open: instance Constructor C1_0DrCr
- Penny.Lincoln.Bits.Open: instance Constructor C1_0Entry
- Penny.Lincoln.Bits.Open: instance Constructor C1_0FilePosting
- Penny.Lincoln.Bits.Open: instance Constructor C1_0FileTransaction
- Penny.Lincoln.Bits.Open: instance Constructor C1_0GlobalPosting
- Penny.Lincoln.Bits.Open: instance Constructor C1_0GlobalTransaction
- Penny.Lincoln.Bits.Open: instance Constructor C1_0PostingLine
- Penny.Lincoln.Bits.Open: instance Constructor C1_0PriceLine
- Penny.Lincoln.Bits.Open: instance Constructor C1_0Side
- Penny.Lincoln.Bits.Open: instance Constructor C1_0SpaceBetween
- Penny.Lincoln.Bits.Open: instance Constructor C1_0Tags
- Penny.Lincoln.Bits.Open: instance Constructor C1_0TopLineLine
- Penny.Lincoln.Bits.Open: instance Constructor C1_0TopMemoLine
- Penny.Lincoln.Bits.Open: instance Constructor C1_1DrCr
- Penny.Lincoln.Bits.Open: instance Constructor C1_1Side
- Penny.Lincoln.Bits.Open: instance Constructor C1_1SpaceBetween
- Penny.Lincoln.Bits.Open: instance Datatype D1Account
- Penny.Lincoln.Bits.Open: instance Datatype D1Amount
- Penny.Lincoln.Bits.Open: instance Datatype D1DrCr
- Penny.Lincoln.Bits.Open: instance Datatype D1Entry
- Penny.Lincoln.Bits.Open: instance Datatype D1FilePosting
- Penny.Lincoln.Bits.Open: instance Datatype D1FileTransaction
- Penny.Lincoln.Bits.Open: instance Datatype D1GlobalPosting
- Penny.Lincoln.Bits.Open: instance Datatype D1GlobalTransaction
- Penny.Lincoln.Bits.Open: instance Datatype D1PostingLine
- Penny.Lincoln.Bits.Open: instance Datatype D1PriceLine
- Penny.Lincoln.Bits.Open: instance Datatype D1Side
- Penny.Lincoln.Bits.Open: instance Datatype D1SpaceBetween
- Penny.Lincoln.Bits.Open: instance Datatype D1Tags
- Penny.Lincoln.Bits.Open: instance Datatype D1TopLineLine
- Penny.Lincoln.Bits.Open: instance Datatype D1TopMemoLine
- Penny.Lincoln.Bits.Open: instance Eq Amount
- Penny.Lincoln.Bits.Open: instance Eq Entry
- Penny.Lincoln.Bits.Open: instance Equivalent Amount
- Penny.Lincoln.Bits.Open: instance Equivalent Entry
- Penny.Lincoln.Bits.Open: instance Generic Account
- Penny.Lincoln.Bits.Open: instance Generic Amount
- Penny.Lincoln.Bits.Open: instance Generic DrCr
- Penny.Lincoln.Bits.Open: instance Generic Entry
- Penny.Lincoln.Bits.Open: instance Generic FilePosting
- Penny.Lincoln.Bits.Open: instance Generic FileTransaction
- Penny.Lincoln.Bits.Open: instance Generic GlobalPosting
- Penny.Lincoln.Bits.Open: instance Generic GlobalTransaction
- Penny.Lincoln.Bits.Open: instance Generic PostingLine
- Penny.Lincoln.Bits.Open: instance Generic PriceLine
- Penny.Lincoln.Bits.Open: instance Generic Side
- Penny.Lincoln.Bits.Open: instance Generic SpaceBetween
- Penny.Lincoln.Bits.Open: instance Generic Tags
- Penny.Lincoln.Bits.Open: instance Generic TopLineLine
- Penny.Lincoln.Bits.Open: instance Generic TopMemoLine
- Penny.Lincoln.Bits.Open: instance Ord Amount
- Penny.Lincoln.Bits.Open: instance Ord Entry
- Penny.Lincoln.Bits.Open: instance Selector S1_0_0Account
- Penny.Lincoln.Bits.Open: instance Selector S1_0_0Amount
- Penny.Lincoln.Bits.Open: instance Selector S1_0_0Entry
- Penny.Lincoln.Bits.Open: instance Selector S1_0_0FilePosting
- Penny.Lincoln.Bits.Open: instance Selector S1_0_0FileTransaction
- Penny.Lincoln.Bits.Open: instance Selector S1_0_0GlobalPosting
- Penny.Lincoln.Bits.Open: instance Selector S1_0_0GlobalTransaction
- Penny.Lincoln.Bits.Open: instance Selector S1_0_0PostingLine
- Penny.Lincoln.Bits.Open: instance Selector S1_0_0PriceLine
- Penny.Lincoln.Bits.Open: instance Selector S1_0_0Tags
- Penny.Lincoln.Bits.Open: instance Selector S1_0_0TopLineLine
- Penny.Lincoln.Bits.Open: instance Selector S1_0_0TopMemoLine
- Penny.Lincoln.Bits.Open: instance Selector S1_0_1Amount
- Penny.Lincoln.Bits.Open: instance Selector S1_0_1Entry
- Penny.Lincoln.Bits.Open: instance Show Amount
- Penny.Lincoln.Bits.Open: instance Show Entry
- Penny.Lincoln.Bits.Price: instance Binary CountPerUnit
- Penny.Lincoln.Bits.Price: instance Binary From
- Penny.Lincoln.Bits.Price: instance Binary Price
- Penny.Lincoln.Bits.Price: instance Binary To
- Penny.Lincoln.Bits.Price: instance Constructor C1_0CountPerUnit
- Penny.Lincoln.Bits.Price: instance Constructor C1_0From
- Penny.Lincoln.Bits.Price: instance Constructor C1_0Price
- Penny.Lincoln.Bits.Price: instance Constructor C1_0To
- Penny.Lincoln.Bits.Price: instance Datatype D1CountPerUnit
- Penny.Lincoln.Bits.Price: instance Datatype D1From
- Penny.Lincoln.Bits.Price: instance Datatype D1Price
- Penny.Lincoln.Bits.Price: instance Datatype D1To
- Penny.Lincoln.Bits.Price: instance Generic CountPerUnit
- Penny.Lincoln.Bits.Price: instance Generic From
- Penny.Lincoln.Bits.Price: instance Generic Price
- Penny.Lincoln.Bits.Price: instance Generic To
- Penny.Lincoln.Bits.Price: instance Selector S1_0_0CountPerUnit
- Penny.Lincoln.Bits.Price: instance Selector S1_0_0From
- Penny.Lincoln.Bits.Price: instance Selector S1_0_0Price
- Penny.Lincoln.Bits.Price: instance Selector S1_0_0To
- Penny.Lincoln.Bits.Price: instance Selector S1_0_1Price
- Penny.Lincoln.Bits.Price: instance Selector S1_0_2Price
- Penny.Lincoln.Bits.Qty: RadFrac :: String -> NumberStr
- Penny.Lincoln.Bits.Qty: Whole :: String -> NumberStr
- Penny.Lincoln.Bits.Qty: WholeRad :: String -> NumberStr
- Penny.Lincoln.Bits.Qty: WholeRadFrac :: String -> String -> NumberStr
- Penny.Lincoln.Bits.Qty: data NumberStr
- Penny.Lincoln.Bits.Qty: instance Binary Qty
- Penny.Lincoln.Bits.Qty: instance Constructor C1_0Qty
- Penny.Lincoln.Bits.Qty: instance Datatype D1Qty
- Penny.Lincoln.Bits.Qty: instance Generic Qty
- Penny.Lincoln.Bits.Qty: instance Selector S1_0_0Qty
- Penny.Lincoln.Bits.Qty: instance Selector S1_0_1Qty
- Penny.Lincoln.Bits.Qty: instance Show NumberStr
- Penny.Lincoln.Bits.Qty: mantissa :: Qty -> Integer
- Penny.Lincoln.Bits.Qty: prettyShowQty :: Qty -> String
- Penny.Lincoln.Bits.Qty: type Mantissa = Integer
- Penny.Lincoln.Ents: Inferred :: Inferred
- Penny.Lincoln.Ents: NotInferred :: Inferred
- Penny.Lincoln.Ents: data Inferred
- Penny.Lincoln.Ents: instance Binary Inferred
- Penny.Lincoln.Ents: instance Binary m => Binary (Ent m)
- Penny.Lincoln.Ents: instance Binary m => Binary (Ents m)
- Penny.Lincoln.Ents: instance Constructor C1_0Ent
- Penny.Lincoln.Ents: instance Constructor C1_0Ents
- Penny.Lincoln.Ents: instance Constructor C1_0Inferred
- Penny.Lincoln.Ents: instance Constructor C1_1Inferred
- Penny.Lincoln.Ents: instance Datatype D1Ent
- Penny.Lincoln.Ents: instance Datatype D1Ents
- Penny.Lincoln.Ents: instance Datatype D1Inferred
- Penny.Lincoln.Ents: instance Eq Inferred
- Penny.Lincoln.Ents: instance Generic (Ent m)
- Penny.Lincoln.Ents: instance Generic (Ents m)
- Penny.Lincoln.Ents: instance Generic Inferred
- Penny.Lincoln.Ents: instance Ord Inferred
- Penny.Lincoln.Ents: instance Selector S1_0_0Ent
- Penny.Lincoln.Ents: instance Selector S1_0_0Ents
- Penny.Lincoln.Ents: instance Selector S1_0_1Ent
- Penny.Lincoln.Ents: instance Selector S1_0_2Ent
- Penny.Lincoln.Ents: instance Show Inferred
+ Penny: CGPeriod :: CommaGrp
+ Penny: CGSpace :: CommaGrp
+ Penny: CGThinSpace :: CommaGrp
+ Penny: Comma :: Radix
+ Penny: PGComma :: PeriodGrp
+ Penny: PGSpace :: PeriodGrp
+ Penny: PGThinSpace :: PeriodGrp
+ Penny: Period :: Radix
+ Penny: S3a :: a -> S3 a b c
+ Penny: S3b :: b -> S3 a b c
+ Penny: S3c :: c -> S3 a b c
+ Penny: data CommaGrp
+ Penny: data PeriodGrp
+ Penny: data Radix
+ Penny: data S3 a b c
+ Penny: formatQty :: Defaults -> FormatQty
+ Penny: getQtyFormat :: S3 Radix PeriodGrp CommaGrp -> [LedgerItem] -> Amount Qty -> S3 Radix PeriodGrp CommaGrp
+ Penny: qtyFormatter :: S3 Radix PeriodGrp CommaGrp -> FormatQty
+ Penny.Brenner: CGPeriod :: CommaGrp
+ Penny.Brenner: CGSpace :: CommaGrp
+ Penny.Brenner: CGThinSpace :: CommaGrp
+ Penny.Brenner: Comma :: Radix
+ Penny.Brenner: PGComma :: PeriodGrp
+ Penny.Brenner: PGSpace :: PeriodGrp
+ Penny.Brenner: PGThinSpace :: PeriodGrp
+ Penny.Brenner: Period :: Radix
+ Penny.Brenner: S3a :: a -> S3 a b c
+ Penny.Brenner: S3b :: b -> S3 a b c
+ Penny.Brenner: S3c :: c -> S3 a b c
+ Penny.Brenner: data CommaGrp
+ Penny.Brenner: data PeriodGrp
+ Penny.Brenner: data Radix
+ Penny.Brenner: data S3 a b c
+ Penny.Brenner: qtySpec :: FitAcct -> S3 Radix PeriodGrp CommaGrp
+ Penny.Brenner.Types: qtySpec :: FitAcct -> S3 Radix PeriodGrp CommaGrp
+ Penny.Brenner.Types: type Mode = Maybe FitAcct -> Mode (ProgName -> String) (IO ())
+ Penny.Copper.Parsec: digit :: Parser Digit
+ Penny.Copper.Parsec: digitList :: Parser DigitList
+ Penny.Copper.Parsec: digitsRadDigits :: Parser a -> Parser void -> Parser (GroupedDigits a, Maybe (GroupedDigits a))
+ Penny.Copper.Parsec: groupPart :: Parser a -> Parser (a, DigitList)
+ Penny.Copper.Parsec: groupedDigits :: Parser a -> Parser (GroupedDigits a)
+ Penny.Copper.Parsec: nonEmpty :: Parser a -> Parser (NonEmpty a)
+ Penny.Copper.Parsec: qtyRep :: Parser QtyRep
+ Penny.Copper.Parsec: quotedCommaQtyRep :: Parser QtyRep
+ Penny.Copper.Parsec: quotedPeriodQtyRep :: Parser QtyRep
+ Penny.Copper.Parsec: unquotedQtyRep :: Parser QtyRep
+ Penny.Copper.Parsec: unquotedQtyRepWithSpaces :: Parser QtyRep
+ Penny.Copper.Render: hasSpace :: WholeOrFrac (GroupedDigits PeriodGrp) -> Bool
+ Penny.Copper.Render: qtyRep :: QtyRep -> Text
+ Penny.Copper.Render: quoteQtyRep :: QtyRep -> (Text, Text)
+ Penny.Lincoln.Bits.Open: instance Eq q => Eq (Amount q)
+ Penny.Lincoln.Bits.Open: instance Eq q => Eq (Entry q)
+ Penny.Lincoln.Bits.Open: instance Equivalent Side
+ Penny.Lincoln.Bits.Open: instance Equivalent SpaceBetween
+ Penny.Lincoln.Bits.Open: instance Equivalent q => Equivalent (Amount q)
+ Penny.Lincoln.Bits.Open: instance Equivalent q => Equivalent (Entry q)
+ Penny.Lincoln.Bits.Open: instance Functor Amount
+ Penny.Lincoln.Bits.Open: instance Functor Entry
+ Penny.Lincoln.Bits.Open: instance Ord q => Ord (Amount q)
+ Penny.Lincoln.Bits.Open: instance Ord q => Ord (Entry q)
+ Penny.Lincoln.Bits.Open: instance Show q => Show (Amount q)
+ Penny.Lincoln.Bits.Open: instance Show q => Show (Entry q)
+ Penny.Lincoln.Bits.Qty: CGPeriod :: CommaGrp
+ Penny.Lincoln.Bits.Qty: CGSpace :: CommaGrp
+ Penny.Lincoln.Bits.Qty: CGThinSpace :: CommaGrp
+ Penny.Lincoln.Bits.Qty: Comma :: Radix
+ Penny.Lincoln.Bits.Qty: D0 :: Digit
+ Penny.Lincoln.Bits.Qty: D1 :: Digit
+ Penny.Lincoln.Bits.Qty: D2 :: Digit
+ Penny.Lincoln.Bits.Qty: D3 :: Digit
+ Penny.Lincoln.Bits.Qty: D4 :: Digit
+ Penny.Lincoln.Bits.Qty: D5 :: Digit
+ Penny.Lincoln.Bits.Qty: D6 :: Digit
+ Penny.Lincoln.Bits.Qty: D7 :: Digit
+ Penny.Lincoln.Bits.Qty: D8 :: Digit
+ Penny.Lincoln.Bits.Qty: D9 :: Digit
+ Penny.Lincoln.Bits.Qty: DigitList :: NonEmpty Digit -> DigitList
+ Penny.Lincoln.Bits.Qty: GroupedDigits :: DigitList -> [(a, DigitList)] -> GroupedDigits a
+ Penny.Lincoln.Bits.Qty: PGComma :: PeriodGrp
+ Penny.Lincoln.Bits.Qty: PGSpace :: PeriodGrp
+ Penny.Lincoln.Bits.Qty: PGThinSpace :: PeriodGrp
+ Penny.Lincoln.Bits.Qty: Period :: Radix
+ Penny.Lincoln.Bits.Qty: QGrouped :: (Either (WholeOrFrac (GroupedDigits PeriodGrp)) (WholeOrFrac (GroupedDigits CommaGrp))) -> QtyRep
+ Penny.Lincoln.Bits.Qty: QNoGrouping :: (WholeOrFrac DigitList) -> Radix -> QtyRep
+ Penny.Lincoln.Bits.Qty: WholeOrFrac :: Either (WholeOnly a) (WholeFrac a) -> WholeOrFrac a
+ Penny.Lincoln.Bits.Qty: bestRadGroup :: [QtyRep] -> Maybe (S3 Radix PeriodGrp CommaGrp)
+ Penny.Lincoln.Bits.Qty: class Digits a
+ Penny.Lincoln.Bits.Qty: class Grouper a
+ Penny.Lincoln.Bits.Qty: class HasQty a
+ Penny.Lincoln.Bits.Qty: data CommaGrp
+ Penny.Lincoln.Bits.Qty: data Digit
+ Penny.Lincoln.Bits.Qty: data GroupedDigits a
+ Penny.Lincoln.Bits.Qty: data PeriodGrp
+ Penny.Lincoln.Bits.Qty: data QtyRep
+ Penny.Lincoln.Bits.Qty: data Radix
+ Penny.Lincoln.Bits.Qty: data WholeFrac a
+ Penny.Lincoln.Bits.Qty: data WholeOnly a
+ Penny.Lincoln.Bits.Qty: digits :: Digits a => a -> DigitList
+ Penny.Lincoln.Bits.Qty: dsFirstPart :: GroupedDigits a -> DigitList
+ Penny.Lincoln.Bits.Qty: dsNextParts :: GroupedDigits a -> [(a, DigitList)]
+ Penny.Lincoln.Bits.Qty: frac :: WholeFrac a -> a
+ Penny.Lincoln.Bits.Qty: groupChar :: Grouper a => a -> Char
+ Penny.Lincoln.Bits.Qty: instance Bounded CommaGrp
+ Penny.Lincoln.Bits.Qty: instance Bounded Digit
+ Penny.Lincoln.Bits.Qty: instance Bounded PeriodGrp
+ Penny.Lincoln.Bits.Qty: instance Bounded Radix
+ Penny.Lincoln.Bits.Qty: instance Digits (GroupedDigits a)
+ Penny.Lincoln.Bits.Qty: instance Digits DigitList
+ Penny.Lincoln.Bits.Qty: instance Enum CommaGrp
+ Penny.Lincoln.Bits.Qty: instance Enum Digit
+ Penny.Lincoln.Bits.Qty: instance Enum PeriodGrp
+ Penny.Lincoln.Bits.Qty: instance Enum Radix
+ Penny.Lincoln.Bits.Qty: instance Eq CommaGrp
+ Penny.Lincoln.Bits.Qty: instance Eq Digit
+ Penny.Lincoln.Bits.Qty: instance Eq DigitList
+ Penny.Lincoln.Bits.Qty: instance Eq PeriodGrp
+ Penny.Lincoln.Bits.Qty: instance Eq QtyRep
+ Penny.Lincoln.Bits.Qty: instance Eq Radix
+ Penny.Lincoln.Bits.Qty: instance Eq a => Eq (GroupedDigits a)
+ Penny.Lincoln.Bits.Qty: instance Eq a => Eq (WholeFrac a)
+ Penny.Lincoln.Bits.Qty: instance Eq a => Eq (WholeOnly a)
+ Penny.Lincoln.Bits.Qty: instance Eq a => Eq (WholeOrFrac a)
+ Penny.Lincoln.Bits.Qty: instance Equivalent QtyRep
+ Penny.Lincoln.Bits.Qty: instance Grouper CommaGrp
+ Penny.Lincoln.Bits.Qty: instance Grouper PeriodGrp
+ Penny.Lincoln.Bits.Qty: instance HasQty Qty
+ Penny.Lincoln.Bits.Qty: instance HasQty QtyRep
+ Penny.Lincoln.Bits.Qty: instance Ord CommaGrp
+ Penny.Lincoln.Bits.Qty: instance Ord Digit
+ Penny.Lincoln.Bits.Qty: instance Ord DigitList
+ Penny.Lincoln.Bits.Qty: instance Ord PeriodGrp
+ Penny.Lincoln.Bits.Qty: instance Ord QtyRep
+ Penny.Lincoln.Bits.Qty: instance Ord Radix
+ Penny.Lincoln.Bits.Qty: instance Ord a => Ord (GroupedDigits a)
+ Penny.Lincoln.Bits.Qty: instance Ord a => Ord (WholeFrac a)
+ Penny.Lincoln.Bits.Qty: instance Ord a => Ord (WholeOnly a)
+ Penny.Lincoln.Bits.Qty: instance Ord a => Ord (WholeOrFrac a)
+ Penny.Lincoln.Bits.Qty: instance Semigroup DigitList
+ Penny.Lincoln.Bits.Qty: instance Show CommaGrp
+ Penny.Lincoln.Bits.Qty: instance Show Digit
+ Penny.Lincoln.Bits.Qty: instance Show DigitList
+ Penny.Lincoln.Bits.Qty: instance Show PeriodGrp
+ Penny.Lincoln.Bits.Qty: instance Show QtyRep
+ Penny.Lincoln.Bits.Qty: instance Show Radix
+ Penny.Lincoln.Bits.Qty: instance Show a => Show (GroupedDigits a)
+ Penny.Lincoln.Bits.Qty: instance Show a => Show (WholeFrac a)
+ Penny.Lincoln.Bits.Qty: instance Show a => Show (WholeOnly a)
+ Penny.Lincoln.Bits.Qty: instance Show a => Show (WholeOrFrac a)
+ Penny.Lincoln.Bits.Qty: newtype DigitList
+ Penny.Lincoln.Bits.Qty: newtype WholeOrFrac a
+ Penny.Lincoln.Bits.Qty: qtyToRep :: S3 Radix PeriodGrp CommaGrp -> Qty -> QtyRep
+ Penny.Lincoln.Bits.Qty: qtyToRepGrouped :: g -> Qty -> WholeOrFrac (GroupedDigits g)
+ Penny.Lincoln.Bits.Qty: qtyToRepNoGrouping :: Qty -> WholeOrFrac DigitList
+ Penny.Lincoln.Bits.Qty: showQtyRep :: QtyRep -> Text
+ Penny.Lincoln.Bits.Qty: showRadix :: Radix -> Text
+ Penny.Lincoln.Bits.Qty: signif :: Qty -> Integer
+ Penny.Lincoln.Bits.Qty: type Signif = Integer
+ Penny.Lincoln.Bits.Qty: type WholeOrFracResult a = Either (WholeOrFrac DigitList) (WholeOrFrac (GroupedDigits a))
+ Penny.Lincoln.Bits.Qty: unDigitList :: DigitList -> NonEmpty Digit
+ Penny.Lincoln.Bits.Qty: unWholeOnly :: WholeOnly a -> a
+ Penny.Lincoln.Bits.Qty: unWholeOrFrac :: WholeOrFrac a -> Either (WholeOnly a) (WholeFrac a)
+ Penny.Lincoln.Bits.Qty: whole :: WholeFrac a -> a
+ Penny.Lincoln.Bits.Qty: wholeFrac :: Digits a => a -> a -> Maybe (WholeFrac a)
+ Penny.Lincoln.Bits.Qty: wholeOnly :: Digits a => a -> Maybe (WholeOnly a)
+ Penny.Lincoln.Bits.Qty: wholeOrFrac :: GroupedDigits a -> Maybe (GroupedDigits a) -> Maybe (Either (WholeOrFrac DigitList) (WholeOrFrac (GroupedDigits a)))
+ Penny.Lincoln.Bits.Qty: wholeOrFracToQtyRep :: Either (WholeOrFracResult PeriodGrp) (WholeOrFracResult CommaGrp) -> QtyRep
+ Penny.Lincoln.Equivalent: instance (Equivalent a, Equivalent b) => Equivalent (Either a b)
+ Penny.Lincoln.Equivalent: instance (Equivalent a, Equivalent b, Equivalent c) => Equivalent (a, b, c)
+ Penny.Lincoln.Equivalent: instance Equivalent a => Equivalent (Maybe a)
+ Penny.Lincoln.Queries: eiQty :: Posting -> Either QtyRep Qty
+ Penny.Wheat: formatQty :: WheatConf -> [LedgerItem] -> Amount Qty -> Text
+ Penny.Zinc: formatQty :: Defaults -> [LedgerItem] -> Amount Qty -> Text
- Penny: Defaults :: Bool -> Matcher -> Bool -> ExprDesc -> Maybe Scheme -> [Scheme] -> [(SortField, SortOrder)] -> (Commodity -> Qty -> Text) -> Bool -> SortOrder -> Bool -> Target -> SortOrder -> SortBy -> (Commodity -> Qty -> Text) -> Fields Bool -> Int -> Bool -> ((PostMeta, Posting) -> Text) -> ((PostMeta, Posting) -> Text) -> (Commodity -> Qty -> Text) -> Int -> Int -> Int -> Spacers Int -> Defaults
+ Penny: Defaults :: Bool -> Matcher -> Bool -> ExprDesc -> Maybe Scheme -> [Scheme] -> [(SortField, SortOrder)] -> FormatQty -> Bool -> SortOrder -> Bool -> Target -> SortOrder -> SortBy -> Fields Bool -> Int -> Bool -> ((PostMeta, Posting) -> Text) -> Int -> Int -> Int -> Spacers Int -> Defaults
- Penny.Brenner: FitAcct :: String -> String -> String -> String -> String -> String -> GroupSpecs -> Translator -> Side -> SpaceBetween -> (ParserDesc, FitFileLocation -> IO (Exceptional String [Posting])) -> (Desc -> Payee -> Payee) -> FitAcct
+ Penny.Brenner: FitAcct :: String -> String -> String -> String -> String -> String -> S3 Radix PeriodGrp CommaGrp -> Translator -> Side -> SpaceBetween -> (ParserDesc, FitFileLocation -> IO (Exceptional String [Posting])) -> (Desc -> Payee -> Payee) -> FitAcct
- Penny.Brenner.Clear: mode :: Mode (Maybe FitAcct -> IO ())
+ Penny.Brenner.Clear: mode :: Mode
- Penny.Brenner.Database: mode :: Mode (Maybe FitAcct -> IO ())
+ Penny.Brenner.Database: mode :: Mode
- Penny.Brenner.Import: mode :: Mode (Maybe FitAcct -> IO ())
+ Penny.Brenner.Import: mode :: Mode
- Penny.Brenner.Info: mode :: Mode (Maybe ConfigLocation -> Config -> IO ())
+ Penny.Brenner.Info: mode :: Config -> Mode (ProgName -> String) (IO ())
- Penny.Brenner.Merge: mode :: Mode (Maybe FitAcct -> IO ())
+ Penny.Brenner.Merge: mode :: Mode
- Penny.Brenner.Print: mode :: Mode (Maybe FitAcct -> IO ())
+ Penny.Brenner.Print: mode :: Mode
- Penny.Brenner.Types: FitAcct :: FitAcctName -> FitAcctDesc -> DbLocation -> PennyAcct -> DefaultAcct -> Currency -> GroupSpecs -> Translator -> Side -> SpaceBetween -> (ParserDesc, FitFileLocation -> IO (Exceptional String [Posting])) -> (Desc -> Payee -> Payee) -> FitAcct
+ Penny.Brenner.Types: FitAcct :: FitAcctName -> FitAcctDesc -> DbLocation -> PennyAcct -> DefaultAcct -> Currency -> S3 Radix PeriodGrp CommaGrp -> Translator -> Side -> SpaceBetween -> (ParserDesc, FitFileLocation -> IO (Exceptional String [Posting])) -> (Desc -> Payee -> Payee) -> FitAcct
- Penny.Cabin.Balance.Convert: Opts :: (Commodity -> Qty -> Text) -> ShowZeroBalances -> Sorter -> To -> DateTime -> Changers -> Opts
+ Penny.Cabin.Balance.Convert: Opts :: (Amount Qty -> Text) -> ShowZeroBalances -> Sorter -> To -> DateTime -> Changers -> Opts
- Penny.Cabin.Balance.Convert: balanceFormat :: Opts -> Commodity -> Qty -> Text
+ Penny.Cabin.Balance.Convert: balanceFormat :: Opts -> Amount Qty -> Text
- Penny.Cabin.Balance.Convert.Options: DefaultOpts :: ShowZeroBalances -> Target -> SortOrder -> SortBy -> (Commodity -> Qty -> Text) -> DefaultOpts
+ Penny.Cabin.Balance.Convert.Options: DefaultOpts :: ShowZeroBalances -> Target -> SortOrder -> SortBy -> DefaultOpts
- Penny.Cabin.Balance.MultiCommodity: Opts :: (Commodity -> Qty -> Text) -> ShowZeroBalances -> (SubAccount -> SubAccount -> Ordering) -> Changers -> Opts
+ Penny.Cabin.Balance.MultiCommodity: Opts :: (Amount Qty -> Text) -> ShowZeroBalances -> (SubAccount -> SubAccount -> Ordering) -> Changers -> Opts
- Penny.Cabin.Balance.MultiCommodity: balanceFormat :: Opts -> Commodity -> Qty -> Text
+ Penny.Cabin.Balance.MultiCommodity: balanceFormat :: Opts -> Amount Qty -> Text
- Penny.Cabin.Balance.MultiCommodity: defaultOpts :: Opts
+ Penny.Cabin.Balance.MultiCommodity: defaultOpts :: (Amount Qty -> Text) -> Opts
- Penny.Cabin.Balance.MultiCommodity: parseReport :: (Commodity -> Qty -> Text) -> ParseOpts -> Report
+ Penny.Cabin.Balance.MultiCommodity: parseReport :: ParseOpts -> Report
- Penny.Cabin.Balance.MultiCommodity.Chunker: rowsToChunks :: Changers -> (Commodity -> Qty -> Text) -> [Row] -> [Chunk]
+ Penny.Cabin.Balance.MultiCommodity.Chunker: rowsToChunks :: Changers -> (Amount Qty -> Text) -> [Row] -> [Chunk]
- Penny.Cabin.Interface: type MkReport = CaseSensitive -> (CaseSensitive -> Text -> Exceptional Text Matcher) -> Changers -> ExprDesc -> ([Transaction] -> [(LibertyMeta, Posting)]) -> Mode ParseResult
+ Penny.Cabin.Interface: type MkReport = CaseSensitive -> (CaseSensitive -> Text -> Exceptional Text Matcher) -> Changers -> ExprDesc -> ([Transaction] -> [(LibertyMeta, Posting)]) -> Mode (ProgName -> String) ParseResult
- Penny.Cabin.Interface: type PrintReport = [Transaction] -> [PricePoint] -> Exceptional Text [Chunk]
+ Penny.Cabin.Interface: type PrintReport = (Amount Qty -> Text) -> [Transaction] -> [PricePoint] -> Exceptional Text [Chunk]
- Penny.Cabin.Posts: ZincOpts :: Fields Bool -> ReportWidth -> ShowZeroBalances -> ((PostMeta, Posting) -> Text) -> ((PostMeta, Posting) -> Text) -> (Commodity -> Qty -> Text) -> SubAccountLength -> Alloc -> Alloc -> Spacers Int -> ZincOpts
+ Penny.Cabin.Posts: ZincOpts :: Fields Bool -> ReportWidth -> ShowZeroBalances -> ((PostMeta, Posting) -> Text) -> SubAccountLength -> Alloc -> Alloc -> Spacers Int -> ZincOpts
- Penny.Cabin.Posts.Chunk: ChunkOpts :: ((PostMeta, Posting) -> Text) -> ((PostMeta, Posting) -> Text) -> (Commodity -> Qty -> Text) -> Fields Bool -> SubAccountLength -> Alloc -> Alloc -> Spacers Int -> ReportWidth -> ChunkOpts
+ Penny.Cabin.Posts.Chunk: ChunkOpts :: ((PostMeta, Posting) -> Text) -> (Amount Qty -> Text) -> Fields Bool -> SubAccountLength -> Alloc -> Alloc -> Spacers Int -> ReportWidth -> ChunkOpts
- Penny.Cabin.Posts.Chunk: qtyFormat :: ChunkOpts -> (PostMeta, Posting) -> Text
+ Penny.Cabin.Posts.Chunk: qtyFormat :: ChunkOpts -> Amount Qty -> Text
- Penny.Cabin.Posts.Growers: GrowOpts :: ((PostMeta, Posting) -> Text) -> ((PostMeta, Posting) -> Text) -> (Commodity -> Qty -> Text) -> Fields Bool -> GrowOpts
+ Penny.Cabin.Posts.Growers: GrowOpts :: ((PostMeta, Posting) -> Text) -> (Amount Qty -> Text) -> Fields Bool -> GrowOpts
- Penny.Cabin.Posts.Growers: qtyFormat :: GrowOpts -> (PostMeta, Posting) -> Text
+ Penny.Cabin.Posts.Growers: qtyFormat :: GrowOpts -> Amount Qty -> Text
- Penny.Cabin.Scheme: balanceToQtys :: Changers -> (Commodity -> Qty -> Text) -> EvenOdd -> [(Commodity, BottomLine)] -> [Chunk]
+ Penny.Cabin.Scheme: balanceToQtys :: Changers -> (Amount Qty -> Text) -> EvenOdd -> [(Commodity, BottomLine)] -> [Chunk]
- Penny.Cabin.Scheme: bottomLineToQty :: Changers -> (Commodity -> Qty -> Text) -> EvenOdd -> (Commodity, BottomLine) -> Chunk
+ Penny.Cabin.Scheme: bottomLineToQty :: Changers -> (Amount Qty -> Text) -> EvenOdd -> (Commodity, BottomLine) -> Chunk
- Penny.Copper: item :: GroupSpecs -> S4 (TopLineCore, Ents PostingCore) PricePoint Comment BlankLine -> Maybe Text
+ Penny.Copper: item :: Maybe (Amount Qty -> S3 Radix PeriodGrp CommaGrp) -> S4 (TopLineCore, Ents PostingCore) PricePoint Comment BlankLine -> Maybe Text
- Penny.Copper.Parsec: amount :: Parser (Amount, Side, SpaceBetween)
+ Penny.Copper.Parsec: amount :: Parser (Amount QtyRep, Side, SpaceBetween)
- Penny.Copper.Parsec: entry :: Parser (Entry, Side, SpaceBetween)
+ Penny.Copper.Parsec: entry :: Parser (Entry QtyRep, Side, SpaceBetween)
- Penny.Copper.Parsec: leftCmdtyLvl1Amt :: Parser (Amount, Side, SpaceBetween)
+ Penny.Copper.Parsec: leftCmdtyLvl1Amt :: Parser (Amount QtyRep, Side, SpaceBetween)
- Penny.Copper.Parsec: leftCmdtyLvl3Amt :: Parser (Amount, Side, SpaceBetween)
+ Penny.Copper.Parsec: leftCmdtyLvl3Amt :: Parser (Amount QtyRep, Side, SpaceBetween)
- Penny.Copper.Parsec: leftSideCmdtyAmt :: Parser (Amount, Side, SpaceBetween)
+ Penny.Copper.Parsec: leftSideCmdtyAmt :: Parser (Amount QtyRep, Side, SpaceBetween)
- Penny.Copper.Parsec: posting :: Parser (PostingCore, PostingLine, Maybe Entry)
+ Penny.Copper.Parsec: posting :: Parser (PostingCore, PostingLine, Maybe (Entry QtyRep))
- Penny.Copper.Parsec: rightSideCmdtyAmt :: Parser (Amount, Side, SpaceBetween)
+ Penny.Copper.Parsec: rightSideCmdtyAmt :: Parser (Amount QtyRep, Side, SpaceBetween)
- Penny.Copper.Render: amount :: GroupSpecs -> Maybe Side -> Maybe SpaceBetween -> Amount -> Maybe Text
+ Penny.Copper.Render: amount :: Maybe (Amount Qty -> S3 Radix PeriodGrp CommaGrp) -> Maybe Side -> Maybe SpaceBetween -> Either (Amount QtyRep) (Amount Qty) -> Maybe Text
- Penny.Copper.Render: entry :: GroupSpecs -> Maybe Side -> Maybe SpaceBetween -> Entry -> Maybe Text
+ Penny.Copper.Render: entry :: Maybe (Amount Qty -> S3 Radix PeriodGrp CommaGrp) -> Maybe Side -> Maybe SpaceBetween -> Either (Entry QtyRep) (Entry Qty) -> Maybe Text
- Penny.Copper.Render: item :: GroupSpecs -> S4 (TopLineCore, Ents PostingCore) PricePoint Comment BlankLine -> Maybe Text
+ Penny.Copper.Render: item :: Maybe (Amount Qty -> S3 Radix PeriodGrp CommaGrp) -> S4 (TopLineCore, Ents PostingCore) PricePoint Comment BlankLine -> Maybe Text
- Penny.Copper.Render: posting :: GroupSpecs -> Bool -> Ent PostingCore -> Maybe Text
+ Penny.Copper.Render: posting :: Maybe (Amount Qty -> S3 Radix PeriodGrp CommaGrp) -> Bool -> Ent PostingCore -> Maybe Text
- Penny.Copper.Render: price :: GroupSpecs -> PricePoint -> Maybe Text
+ Penny.Copper.Render: price :: PricePoint -> Maybe Text
- Penny.Copper.Render: transaction :: GroupSpecs -> (TopLineCore, Ents PostingCore) -> Maybe Text
+ Penny.Copper.Render: transaction :: Maybe (Amount Qty -> S3 Radix PeriodGrp CommaGrp) -> (TopLineCore, Ents PostingCore) -> Maybe Text
- Penny.Liberty: version :: Version -> OptSpec (IO a)
+ Penny.Liberty: version :: Version -> String -> String
- Penny.Liberty: xactionsToFiltered :: LPdct -> [PostFilterFn] -> (Posting -> Posting -> Ordering) -> [Transaction] -> ([Chunk], [(LibertyMeta, Posting)])
+ Penny.Liberty: xactionsToFiltered :: LPdct -> [PostFilterFn] -> (Posting -> Posting -> Ordering) -> [Transaction] -> ((Amount Qty -> Text) -> [Chunk], [(LibertyMeta, Posting)])
- Penny.Lincoln: display :: Posting -> Text
+ Penny.Lincoln: display :: (Amount Qty -> Text) -> Posting -> Text
- Penny.Lincoln.Balance: Inferable :: Entry -> Balanced
+ Penny.Lincoln.Balance: Inferable :: (Entry Qty) -> Balanced
- Penny.Lincoln.Balance: entriesToBalanced :: [Entry] -> Balanced
+ Penny.Lincoln.Balance: entriesToBalanced :: HasQty q => [Entry q] -> Balanced
- Penny.Lincoln.Balance: entryToBalance :: Entry -> Balance
+ Penny.Lincoln.Balance: entryToBalance :: HasQty q => Entry q -> Balance
- Penny.Lincoln.Bits.Open: Amount :: Qty -> Commodity -> Amount
+ Penny.Lincoln.Bits.Open: Amount :: q -> Commodity -> Amount q
- Penny.Lincoln.Bits.Open: Entry :: DrCr -> Amount -> Entry
+ Penny.Lincoln.Bits.Open: Entry :: DrCr -> Amount q -> Entry q
- Penny.Lincoln.Bits.Open: amount :: Entry -> Amount
+ Penny.Lincoln.Bits.Open: amount :: Entry q -> Amount q
- Penny.Lincoln.Bits.Open: commodity :: Amount -> Commodity
+ Penny.Lincoln.Bits.Open: commodity :: Amount q -> Commodity
- Penny.Lincoln.Bits.Open: data Amount
+ Penny.Lincoln.Bits.Open: data Amount q
- Penny.Lincoln.Bits.Open: data Entry
+ Penny.Lincoln.Bits.Open: data Entry q
- Penny.Lincoln.Bits.Open: drCr :: Entry -> DrCr
+ Penny.Lincoln.Bits.Open: drCr :: Entry q -> DrCr
- Penny.Lincoln.Bits.Open: qty :: Amount -> Qty
+ Penny.Lincoln.Bits.Open: qty :: Amount q -> q
- Penny.Lincoln.Bits.Price: CountPerUnit :: Qty -> CountPerUnit
+ Penny.Lincoln.Bits.Price: CountPerUnit :: QtyRep -> CountPerUnit
- Penny.Lincoln.Bits.Price: unCountPerUnit :: CountPerUnit -> Qty
+ Penny.Lincoln.Bits.Price: unCountPerUnit :: CountPerUnit -> QtyRep
- Penny.Lincoln.Bits.Qty: newQty :: Mantissa -> Places -> Maybe Qty
+ Penny.Lincoln.Bits.Qty: newQty :: Signif -> Places -> Maybe Qty
- Penny.Lincoln.Bits.Qty: toQty :: NumberStr -> Maybe Qty
+ Penny.Lincoln.Bits.Qty: toQty :: HasQty a => a -> Qty
- Penny.Lincoln.Ents: entry :: Ent m -> Entry
+ Penny.Lincoln.Ents: entry :: Ent m -> Either (Entry QtyRep) (Entry Qty)
- Penny.Lincoln.Ents: ents :: [(Maybe Entry, m)] -> Maybe (Ents m)
+ Penny.Lincoln.Ents: ents :: [(Maybe (Either (Entry QtyRep) (Entry Qty)), m)] -> Maybe (Ents m)
- Penny.Lincoln.Ents: inferred :: Ent m -> Inferred
+ Penny.Lincoln.Ents: inferred :: Ent m -> Bool
- Penny.Lincoln.Ents: rEnts :: Commodity -> DrCr -> (Qty, m) -> [(Qty, m)] -> m -> Ents m
+ Penny.Lincoln.Ents: rEnts :: Commodity -> DrCr -> (Either QtyRep Qty, m) -> [(Either QtyRep Qty, m)] -> m -> Ents m
- Penny.Lincoln.PriceDb: convertAsOf :: PriceDb -> DateTime -> To -> Amount -> Exceptional PriceDbError Qty
+ Penny.Lincoln.PriceDb: convertAsOf :: HasQty q => PriceDb -> DateTime -> To -> Amount q -> Exceptional PriceDbError Qty
- Penny.Lincoln.Queries: amount :: Posting -> Amount
+ Penny.Lincoln.Queries: amount :: Posting -> Either (Amount QtyRep) (Amount Qty)
- Penny.Lincoln.Queries: entry :: Posting -> Entry
+ Penny.Lincoln.Queries: entry :: Posting -> Either (Entry QtyRep) (Entry Qty)
- Penny.Lincoln.Queries.Siblings: amount :: Posting -> [Amount]
+ Penny.Lincoln.Queries.Siblings: amount :: Posting -> [Either (Amount QtyRep) (Amount Qty)]
- Penny.Lincoln.Queries.Siblings: entry :: Posting -> [Entry]
+ Penny.Lincoln.Queries.Siblings: entry :: Posting -> [Either (Entry QtyRep) (Entry Qty)]
- Penny.Wheat: WheatConf :: String -> [String] -> [UTCTime -> Test Posting] -> IndentAmt -> Maybe TestVerbosity -> (Name -> Bool) -> Bool -> Bool -> UTCTime -> WheatConf
+ Penny.Wheat: WheatConf :: String -> [String] -> [UTCTime -> Test Posting] -> IndentAmt -> Maybe TestVerbosity -> (Name -> Bool) -> Bool -> Bool -> UTCTime -> ([LedgerItem] -> Amount Qty -> Text) -> WheatConf
- Penny.Zinc: Defaults :: CaseSensitive -> Matcher -> ColorToFile -> Maybe Scheme -> [Scheme] -> [(SortField, SortOrder)] -> ExprDesc -> Defaults
+ Penny.Zinc: Defaults :: CaseSensitive -> Matcher -> ColorToFile -> Maybe Scheme -> [Scheme] -> [(SortField, SortOrder)] -> ExprDesc -> ([LedgerItem] -> Amount Qty -> Text) -> Defaults

Files

lib/Penny.hs view
@@ -23,9 +23,6 @@   -- ** Expression type   , Exp.ExprDesc(..) -  -- ** Formatting quantities-  , defaultQtyFormat-   -- ** Convert report options   , Target(..)   , CP.SortBy(..)@@ -35,9 +32,15 @@   , Spacers(..)   , widthFromRuntime   , Ps.yearMonthDay-  , Ps.qtyAsIs-  , Ps.balanceAsIs +  -- ** Formatting quantities+  , S3(..)+  , qtyFormatter+  , getQtyFormat+  , L.Radix(..)+  , L.PeriodGrp(..)+  , L.CommaGrp(..)+   -- ** Runtime   , S.Runtime   , S.environment@@ -84,7 +87,11 @@     -- bin/doc/dependencies.dot in the Penny git repository.   ) where +import Data.Ord (comparing)+import Data.List (sortBy, groupBy)+import Data.Maybe (mapMaybe, fromMaybe) import qualified Data.Text as X+import qualified Data.Map as Map import Data.Version (Version(..)) import qualified Penny.Cabin.Balance.Convert as Conv import qualified Penny.Cabin.Balance.Convert.Parser as CP@@ -100,12 +107,23 @@ import qualified Penny.Cabin.Posts.Spacers as PS import qualified Penny.Cabin.Posts.Meta as M import qualified Penny.Cabin.Scheme as E+import qualified Penny.Copper as Cop+import Penny.Steel.Sums+import qualified Penny.Steel.Sums as Su import qualified Penny.Lincoln as L import qualified Data.Prednote.Expressions as Exp import qualified Penny.Zinc as Z import qualified Penny.Shield as S import qualified Text.Matchers as Mr +-- | A function used to format quantities.+type FormatQty+  = [Cop.LedgerItem]+  -- ^ All parsed items++  -> L.Amount L.Qty+  -> X.Text+ -- | This type contains settings for all the reports, as well as -- default settings for the global options. Some of these can be -- overridden on the command line.@@ -142,9 +160,10 @@     -- If this list is empty, then by default postings are left in the     -- same order as they appear in the ledger files. -  , balanceFormat :: L.Commodity -> L.Qty -> X.Text-    -- ^ How to format balances in the balance report. Change this-    -- function if, for example, you want to allow for digit grouping.+  , formatQty :: FormatQty+    -- ^ How to format quantities. This affects only quantities that+    -- are not parsed from the ledger.  Examples include calculated+    -- totals and inferred quantities.  Affects all reports.    , balanceShowZeroBalances :: Bool     -- ^ Show zero balances in the balance report? If True, show them;@@ -168,10 +187,6 @@   , convertSortBy :: CP.SortBy     -- ^ Sort by account or by quantity in the convert report. -  , convertFormat :: L.Commodity -> L.Qty -> X.Text-    -- ^ How to format balances in the convert report. For instance,-    -- this function might perform digit grouping.-   , postingsFields :: Fields Bool     -- ^ Fields to show by default in the postings report. @@ -186,16 +201,6 @@   , postingsDateFormat :: (M.PostMeta, L.Posting) -> X.Text     -- ^ How to format dates in the postings report. -  , postingsQtyFormat :: (M.PostMeta, L.Posting) -> X.Text-    -- ^ How to format quantities in the balance report. This function-    -- is used when showing the quantity for the posting itself, and-    -- not the quantity for the totals columns (for that, see-    -- postingsBalanceFormat.) For example this function might perform-    -- digit grouping.--  , postingsBalanceFormat :: L.Commodity -> L.Qty -> X.Text-    -- ^ How to format balance totals in the postings report.-   , postingsSubAccountLength :: Int     -- ^ Account names in the postings report are shortened if     -- necessary in order to help the report fit within the allotted@@ -230,6 +235,119 @@     -- the absolute value of the field is taken.)   } +-- | Provides a function to use in the 'formatQty' field. This formats+-- quantities that were not parsed in the ledger.  It first consults a+-- list of all items that were parsed from the ledger.  It examines+-- these items to determine if another item with the same commodity+-- already exists in the ledger.+--+-- If other items with the same commodity exist in the ledger, the+-- radix point most frequently occurring amongst those items is+-- used. If at least one of these items (with this radix point) also+-- has grouped digits, then the quantity will be formatted with+-- grouped digits; otherwise, no digit grouping is performed. If digit+-- grouping is performed, it is done according to the following rules:+--+-- * only digits to the left of the radix point are grouped+--+-- * grouping is performed only if the number has at least five+-- digits. Therefore, 1234 is not grouped, but 1,234.5 is grouped, as+-- is 12,345+--+-- * the character most frequently appearing as a grouping character+-- (for this particular commodity and radix point) is used to perform+-- grouping+--+-- * digits are grouped into groups of 3 digits+--+-- If a radix point cannot be determined from the quantities for a+-- given commodity, then the radix point appearing most frequently for+-- all commodities is used.  If it's impossible to determine a radix+-- point from all commodities, then the given default radix point and+-- digit grouping (if desired) is used.+--+-- This function builds a map internally which holds all the+-- formatting information; it might be expensive to build, so the+-- function is written to be partially applied.++qtyFormatter+  :: Su.S3 L.Radix L.PeriodGrp L.CommaGrp+  -- ^ What to do if no radix or grouping information can be+  -- determined from the ledger.  Pass Radix if you want to use a+  -- radix point but no grouping; a PeriodGrp if you want to use a+  -- period for a radix point and the given grouping character, or a+  -- CommaGrp if you want to use a comma for a radix point and the+  -- given grouping character.++  -> FormatQty+qtyFormatter df ls =+  let getFmt = getQtyFormat df ls+  in \a -> L.showQtyRep . L.qtyToRep (getFmt a) . L.qty $ a++-- | Obtains radix and grouping information for a particular commodity+-- and quantity, but does not actually perform the formatting.+getQtyFormat+  :: Su.S3 L.Radix L.PeriodGrp L.CommaGrp+  -- ^ What to do if no radix or grouping information can be+  -- determined from the ledger.  Pass Radix if you want to use a+  -- radix point but no grouping; a PeriodGrp if you want to use a+  -- period for a radix point and the given grouping character, or a+  -- CommaGrp if you want to use a comma for a radix point and the+  -- given grouping character.++  -> [Cop.LedgerItem]+  -> L.Amount L.Qty+  -> Su.S3 L.Radix L.PeriodGrp L.CommaGrp+getQtyFormat df ls =+  let m = formattingMap ls+  in \a -> fromMaybe df (Map.lookup (L.commodity a) m)+++-- | Returns a map of each commodity in the ledger and the grouping to+-- use for it.+formattingMap+  :: [Cop.LedgerItem]+  -> Map.Map L.Commodity (Su.S3 L.Radix L.PeriodGrp L.CommaGrp)+formattingMap+  = Map.fromList+  . mapMaybe formatCmdty+  . groupBy (\x y -> fst x == fst y)+  . sortBy (comparing fst)+  . allQtyRep++-- | Given a list of (Commodity, QtyRep) pairs, get a single pair of+-- the commodity and the grouping that should be used for this+-- commodity. The input list must not be empty. Returns Nothing if no+-- group data can be ascertained.+formatCmdty+  :: [(L.Commodity, L.QtyRep)]+  -> Maybe (L.Commodity, Su.S3 L.Radix L.PeriodGrp L.CommaGrp)+formatCmdty ls = case L.bestRadGroup . map snd $ ls of+  Nothing -> Nothing+  Just r -> Just (fst . head $ ls, r)++-- | Given a list of LedgerItem, create a list of pairs of commodities+-- and QtyRep.+allQtyRep :: [Cop.LedgerItem] -> [(L.Commodity, L.QtyRep)]+allQtyRep = concatMap toPairs+  where+    toPairs i = case i of+      Su.S4a t ->+        mapMaybe toEntPair+        . L.unEnts+        . snd+        . L.unTransaction+        $ t+      Su.S4b p ->+        [( L.unTo . L.to . L.price $ p+        , L.unCountPerUnit . L.countPerUnit . L.price $ p)]+      _ -> []++toEntPair :: L.Ent m -> Maybe (L.Commodity, L.QtyRep)+toEntPair e = case L.entry e of+  Left en -> Just (L.commodity . L.amount $ en, L.qty . L.amount $ en)+  Right _ -> Nothing+ -- | Creates an IO action that you can use for the main function. runPenny   :: Version@@ -278,7 +396,7 @@       cd = toConvertDefaults df       pd = toPostingsDefaults df   in [ Ps.zincReport pd-     , MC.parseReport (balanceFormat df) bd+     , MC.parseReport bd      , Conv.cmdLineReport cd      ] @@ -292,6 +410,7 @@   , Z.moreSchemes = additionalSchemes d   , Z.sorter = sorter d   , Z.exprDesc = expressionType d+  , Z.formatQty = formatQty d   }  toBalanceDefaults :: Defaults -> MP.ParseOpts@@ -308,7 +427,6 @@   , ConvOpts.target = convTarget . convertTarget $ d   , ConvOpts.sortOrder = convertOrder d   , ConvOpts.sortBy = convertSortBy d-  , ConvOpts.format = convertFormat d   }  toPostingsDefaults :: Defaults -> Ps.ZincOpts@@ -318,8 +436,6 @@   , Ps.showZeroBalances =       CO.ShowZeroBalances . postingsShowZeroBalances $ d   , Ps.dateFormat = postingsDateFormat d-  , Ps.qtyFormat = postingsQtyFormat d-  , Ps.balanceFormat = postingsBalanceFormat d   , Ps.subAccountLength =       Ps.SubAccountLength . postingsSubAccountLength $ d   , Ps.payeeAllocation =@@ -328,9 +444,6 @@       Ps.alloc . postingsAccountAllocation $ d   , Ps.spacers = convSpacers . postingsSpacers $ d   }--defaultQtyFormat :: L.Qty -> X.Text-defaultQtyFormat = X.pack . L.prettyShowQty  data Spacers a = Spacers   { sGlobalTransaction :: a
lib/Penny/Brenner.hs view
@@ -8,8 +8,10 @@ module Penny.Brenner   ( FitAcct(..)   , Config(..)-  , R.GroupSpecs(..)-  , R.GroupSpec(..)+  , Su.S3(..)+  , L.Radix(..)+  , L.PeriodGrp(..)+  , L.CommaGrp(..)   , Y.Translator(..)   , L.Side(..)   , L.SpaceBetween(..)@@ -19,14 +21,11 @@   ) where  import qualified Penny.Brenner.Types as Y-import Control.Monad (join)-import Data.Either (partitionEithers) import qualified Data.Text as X import qualified Data.Version as V import qualified Penny.Liberty as Ly import qualified Penny.Lincoln as L import qualified Penny.Lincoln.Builders as Bd-import qualified Penny.Copper.Render as R import qualified Penny.Brenner.Clear as C import qualified Penny.Brenner.Database as D import qualified Penny.Brenner.Import as I@@ -34,8 +33,10 @@ import qualified Penny.Brenner.Merge as M import qualified Penny.Brenner.OFX as O import qualified Penny.Brenner.Print as P-import qualified Penny.Brenner.Util as U+import qualified Penny.Steel.Sums as Su import qualified System.Console.MultiArg as MA+import System.Environment (getProgName)+import qualified System.Exit as Exit import qualified Control.Monad.Exception.Synchronous as Ex  -- | Brenner, with a pre-compiled configuration.@@ -46,44 +47,37 @@   -> IO () brennerMain v cf = do   let cf' = convertConfig cf-  join $ MA.modesWithHelp (help False) (globalOpts v)-                          (preProcessor cf')+  pn <- getProgName+  ei <- MA.modesIO (globalOpts v) (preProcessor cf')+  case ei of+    Left showHelp -> putStr (showHelp pn) >> Exit.exitSuccess+    Right g -> g +type GetHelp = MA.ProgName -> String+ -- | Parses global options for a pre-compiled configuration. globalOpts   :: V.Version   -- ^ Binary version-  -> [MA.OptSpec (Either (IO ()) Y.FitAcctName)]-globalOpts v =+  -> MA.Opts GetHelp Y.FitAcctName+globalOpts v = MA.optsHelpVersion help (Ly.version v)   [ MA.OptSpec ["fit-account"] "f"-  (MA.OneArg (Right . Y.FitAcctName . X.pack))-  , fmap Left (Ly.version v)+               (MA.OneArg (Y.FitAcctName . X.pack))   ]  -- | Pre-processes global options for a pre-compiled configuration. preProcessor   :: Y.Config-  -> [Either (IO ()) Y.FitAcctName]-  -> Either (a -> IO ()) [MA.Mode (IO ())]-preProcessor cf args =-  let (vers, as) = partitionEithers args-  in case vers of-      [] -> makeModes Nothing cf as-      x:_ -> Left (const x)---- | Makes modes for a pre-compiled configuration.-makeModes-  :: Maybe Y.ConfigLocation-  -> Y.Config   -> [Y.FitAcctName]-  -- ^ Names of financial institutions given on command line-  -> Either (a -> IO ()) [MA.Mode (IO ())]-makeModes cl cf as = Ex.toEither . Ex.mapException (const . U.errExit) $ do-  mayFi <- case as of+  -> Ex.Exceptional String+      (Either (IO ())+              [MA.Mode (MA.ProgName -> String) (IO ())])+preProcessor cf args = do+  mayFi <- case args of     [] -> return $ Y.defaultFitAcct cf     _ ->       let pdct a = Y.fitAcctName a == s-          s = last as+          s = last args           toFilter = case Y.defaultFitAcct cf of             Nothing -> Y.moreFitAccts cf             Just d -> d : Y.moreFitAccts cf@@ -96,34 +90,33 @@               "more than one financial institution account "               ++ "named " ++ (X.unpack . Y.unFitAcctName $ s)               ++ " configured."-  return . map (fmap (\f -> f cl cf mayFi)) $ allModes+  return . Right $ allModes cf mayFi  -- | Each mode takes a Maybe FitAcct. Even if every mode needs a -- FitAcct to function, they take a Maybe FitAcct because otherwise -- the user will not even get online help if a FitAcct is not -- supplied. Each mode must fail on its own if it actually needs a -- FitAcct.-type ModeFunc-  = Maybe Y.ConfigLocation-  -> Y.Config-  -> Maybe Y.FitAcct-  -> IO () -allModes :: [MA.Mode ModeFunc]-allModes =-  fmap (\f cl cf _ -> f cl cf) Info.mode-  : map (fmap (const . const))-        [C.mode, I.mode, M.mode, P.mode, D.mode]+allModes+  :: Y.Config+  -> Maybe Y.FitAcct+  -> [MA.Mode (MA.ProgName -> String) (IO ())]+allModes c a =+  [ C.mode a+  , I.mode a+  , Info.mode c+  , M.mode a+  , P.mode a+  , D.mode a ]  -- | Help for a pre-compiled configuration. help-  :: Bool-  -- ^ True if running under a dynamic configuration-  -> String+  :: String   -- ^ Program name    -> String-help dyn n = unlines ls+help n = unlines ls   where     ls = [ "usage: " ++ n ++ " [global-options]"             ++ " COMMAND [local-options]"@@ -142,11 +135,7 @@          , "  (use the \"info\" command to see which are available)."          , "  If this option does not appear,"          , "  the default account is used if there is one."-         ] ++ if not dyn then [] else-                  [ ""-                  , "-c, --config-file FILENAME"-                  , "  Specify configuration file location"-                  ]+         ]  -- | Information to configure a single financial institution account. data FitAcct = FitAcct@@ -177,10 +166,9 @@   , currency :: String     -- ^ The commodity for the currency of your card (e.g. @$@). -  , groupSpecs :: R.GroupSpecs-    -- ^ How to group digits when printing the resulting ledger. All-    -- quantities (not just those affected by this program) will be-    -- formatted using this specification.+  , qtySpec :: Su.S3 L.Radix L.PeriodGrp L.CommaGrp+    -- ^ How to group digits when printing the resulting ledger.+    -- This affects only the postings created from the statement.    , translator :: Y.Translator     -- ^ See the documentation under the 'Translator' type for@@ -232,7 +220,7 @@   , Y.pennyAcct = Y.PennyAcct . Bd.account . X.pack $ ax   , Y.defaultAcct = Y.DefaultAcct . Bd.account . X.pack $ df   , Y.currency = Y.Currency . L.Commodity . X.pack $ cy-  , Y.groupSpecs = gs+  , Y.qtySpec = gs   , Y.translator = tl   , Y.side = sd   , Y.spaceBetween = sb
lib/Penny/Brenner/Clear.hs view
@@ -60,18 +60,18 @@   } deriving Show  -mode :: MA.Mode (Maybe Y.FitAcct -> IO ())-mode = MA.Mode-  { MA.mName = "clear"-  , MA.mIntersperse = MA.Intersperse-  , MA.mOpts = [fmap AOutput Ly.output]-  , MA.mPosArgs = return . APosArg-  , MA.mProcess = process-  , MA.mHelp = help-  }+mode :: Y.Mode+mode mayFa = MA.modeHelp+  "clear"                  -- Mode name+  help                     -- Help function+  (process mayFa)          -- Processor+  [fmap AOutput Ly.output] -- Options+  MA.Intersperse           -- interspersion+  (return . APosArg)       -- Posarg processor -process :: [Arg] -> Maybe Y.FitAcct -> IO ()-process as mayFa = do++process :: Maybe Y.FitAcct -> [Arg] -> IO ()+process mayFa as = do   fa <- U.getFitAcct mayFa   (csv, ls) <- case mapMaybe toPosArg as of     [] -> fail "clear: you must provide a postings file."@@ -96,7 +96,7 @@   when (not (Set.null left))     (fail $ "some postings were not cleared. "       ++ "Those not cleared:\n" ++ ppShow left)-  case mapM (R.item (Y.groupSpecs c)) led'' of+  case mapM (R.item Nothing) led'' of     Nothing ->       fail "could not render resulting ledger."     Just txts ->
lib/Penny/Brenner/Database.hs view
@@ -16,21 +16,20 @@  data Arg = ArgPos String -mode :: MA.Mode (Maybe Y.FitAcct -> IO ())-mode = MA.Mode-  { MA.mName = "database"-  , MA.mIntersperse = MA.Intersperse-  , MA.mOpts = []-  , MA.mPosArgs = return . ArgPos-  , MA.mProcess = processor-  , MA.mHelp = help-  }+mode :: Y.Mode+mode mayFa = MA.modeHelp+  "database"        -- Mode name+  help              -- Help function+  (processor mayFa) -- Processing function+  []                -- Options+  MA.Intersperse    -- Interspersion+  (return . ArgPos) -- Posarg processor  processor-  :: [Arg]-  -> Maybe Y.FitAcct+  :: Maybe Y.FitAcct+  -> [Arg]   -> IO ()-processor ls mayFa+processor mayFa ls   | not . null $ ls = fail $         "penny-fit database: error: this command does"         ++ " not accept non-option arguments."
lib/Penny/Brenner/Import.hs view
@@ -32,27 +32,27 @@   , newUNumber :: Maybe Integer   } -mode-  :: MA.Mode (Maybe Y.FitAcct -> IO ())-mode = MA.Mode-  { MA.mName = "import"-  , MA.mIntersperse = MA.Intersperse-  , MA.mOpts =+mode :: Y.Mode+mode mayFa = MA.modeHelp+  "import"            -- Mode name+  help                -- Help function+  (processor mayFa)   -- Processing function+  opts                -- Options+  MA.Intersperse      -- Interspersion+  (return . AFitFile) -- Posarg processor+  where+    opts =       [ MA.OptSpec ["new"] "n" (MA.NoArg AAllowNew)       , MA.OptSpec ["unumber"] "u" . MA.OneArgE $ \s -> do           i <- MA.reader s           return $ AUNumber i       ]-  , MA.mPosArgs = return . AFitFile-  , MA.mProcess = processor-  , MA.mHelp = help-  }  processor-  :: [Arg]-  -> Maybe Y.FitAcct+  :: Maybe Y.FitAcct+  -> [Arg]   -> IO ()-processor as mayFa = do+processor mayFa as = do   fa <- U.getFitAcct mayFa   let (dbLoc, prsr) = (Y.dbLocation fa, snd . Y.parser $ fa)   loc <- case mapMaybe toFitFile as of
lib/Penny/Brenner/Info.hs view
@@ -7,7 +7,7 @@ import qualified Data.Text.IO as TIO import Data.Monoid ((<>)) import qualified Penny.Lincoln as L-import qualified Penny.Copper.Render as R+import qualified Penny.Steel.Sums as S import qualified System.Console.MultiArg as MA  help :: String -> String@@ -20,26 +20,25 @@   , "  -h, --help - show help and exit"   ] -mode :: MA.Mode (Maybe Y.ConfigLocation -> Y.Config -> IO ())-mode = MA.Mode-  { MA.mName = "info"-  , MA.mIntersperse = MA.Intersperse-  , MA.mOpts = []-  , MA.mPosArgs = const . Ex.throw . MA.ErrorMsg-    $ "this mode does not accept positional arguments"-  , MA.mProcess = const process-  , MA.mHelp = help-  }+mode :: Y.Config -> MA.Mode (MA.ProgName -> String) (IO ())+mode cf = MA.modeHelp+  "info"               -- Mode name+  help                 -- Help function+  (const (process cf)) -- Processing function+  []                   -- Options+  MA.Intersperse       -- Interspersion+  processPa            -- Posarg processor+  where+    processPa = const . Ex.throw . MA.ErrorMsg+      $ "this mode does not accept positional arguments" -process :: Maybe Y.ConfigLocation -> Y.Config -> IO ()-process cf cn = TIO.putStr $ showInfo cf cn+process :: Y.Config -> IO ()+process cf = TIO.putStr $ showInfo cf -showInfo :: Maybe Y.ConfigLocation -> Y.Config -> X.Text-showInfo cf cn =-  maybe "These settings are compiled into your program.\n\n"-        (\l -> label "From configuration file at" (L.text l) <> "\n\n")-        cf-  <> showConfig cn+showInfo :: Y.Config -> X.Text+showInfo cf =+  "These settings are compiled into your program.\n\n"+  <> showConfig cf  showConfig :: Y.Config -> X.Text showConfig (Y.Config dflt more) =@@ -73,11 +72,8 @@   , label "Penny account" (L.text . L.Delimited ":" . Y.pennyAcct $ c)   , label "Default account" (L.text . L.Delimited ":" . Y.defaultAcct $ c)   , label "Currency" (L.text . Y.currency $ c)-  , label "Group amounts to left of decimal point"-    (showGroupLeft . R.left . Y.groupSpecs $ c)--  , label "Group amounts to right of decimal point"-    (showGroupRight . R.right . Y.groupSpecs $ c)+  , label "Radix point and digit grouping"+    (showQtySpec . Y.qtySpec $ c)    , label "Financial institution increases are"     (showTranslator . Y.translator $ c)@@ -91,17 +87,17 @@   <> "Parser description:\n"   <> (L.text . fst . Y.parser $ c) -showGroupLeft :: R.GroupSpec -> X.Text-showGroupLeft s = case s of-  R.NoGrouping -> "never"-  R.GroupLarge -> "when greater than 9,999"-  R.GroupAll -> "always"+showQtySpec :: S.S3 L.Radix L.PeriodGrp L.CommaGrp -> X.Text+showQtySpec s = case s of+  S.S3a r -> "no digit grouping, use radix point: '"+             <> (L.showRadix r) <> "'"+  S.S3b p -> "group digits using: '"+             <> (X.singleton . L.groupChar $ p)+             <> "', radix point: '.'"+  S.S3c c -> "group digits using: '"+             <> (X.singleton . L.groupChar $ c)+             <> "', radix point: ','" -showGroupRight :: R.GroupSpec -> X.Text-showGroupRight s = case s of-  R.NoGrouping -> "never"-  R.GroupLarge -> "when mor than four decimal places"-  R.GroupAll -> "always"  showTranslator :: Y.Translator -> X.Text showTranslator y = case y of
lib/Penny/Brenner/Merge.hs view
@@ -36,20 +36,21 @@ toOutput :: Arg -> Maybe (X.Text -> IO ()) toOutput a = case a of { AOutput x -> Just x; _ -> Nothing } -mode :: MA.Mode (Maybe Y.FitAcct -> IO ())-mode = MA.Mode-  { MA.mName = "merge"-  , MA.mIntersperse = MA.Intersperse-  , MA.mOpts = [ MA.OptSpec ["no-auto"] "n" (MA.NoArg ANoAuto)-               , fmap AOutput Ly.output-               ]-  , MA.mPosArgs = return . APos-  , MA.mProcess = processor-  , MA.mHelp = help-  }+mode :: Y.Mode+mode mayFa = MA.modeHelp+  "merge"+  help+  (processor mayFa)+  opts+  MA.Intersperse+  (return . APos)+  where+    opts = [ MA.OptSpec ["no-auto"] "n" (MA.NoArg ANoAuto)+           , fmap AOutput Ly.output+           ] -processor :: [Arg] -> Maybe Y.FitAcct -> IO ()-processor as mayFa = do+processor :: Maybe Y.FitAcct -> [Arg] -> IO ()+processor mayFa as = do   fa <- U.getFitAcct mayFa   doMerge fa           (ANoAuto `elem` as)@@ -72,7 +73,7 @@                   l (filterDb (Y.pennyAcct acct) dbWithEntry l)       newTxns = createTransactions noAuto acct l dbLs db'       final = l' ++ newTxns-  case mapM (R.item (Y.groupSpecs acct)) (map C.stripMeta final) of+  case mapM (R.item Nothing) (map C.stripMeta final) of     Nothing -> fail "Could not render final ledger."     Just txts ->       let txt = X.concat txts@@ -206,7 +207,7 @@  -- | Pairs each association in a DbMap with an Entry representing the -- transaction's entry in the ledger.-pairWithEntry :: Y.FitAcct -> Y.Posting -> (Y.Posting, L.Entry)+pairWithEntry :: Y.FitAcct -> Y.Posting -> (Y.Posting, L.Entry L.Qty) pairWithEntry acct p = (p, en)   where     en = L.Entry dc (L.Amount qty cty)@@ -214,7 +215,7 @@     qty = U.parseQty (Y.amount p)     cty = Y.unCurrency . Y.currency $ acct -type DbWithEntry = M.Map Y.UNumber (Y.Posting, L.Entry)+type DbWithEntry = M.Map Y.UNumber (Y.Posting, L.Entry L.Qty)  -- | Does the given Penny transaction match this posting? Makes sure -- that the account, quantity, date, commodity, and DrCr match, and@@ -224,22 +225,29 @@   :: Y.FitAcct   -> L.TopLineData   -> L.Ent L.PostingData-  -> (a, (Y.Posting, L.Entry))+  -> (a, (Y.Posting, L.Entry L.Qty))   -> Bool pennyTxnMatches acct tl pstg (_, (a, e)) =   mA && noFlag && mQ && mDC && mDate && mCmdty   where     p = L.pdCore . L.meta $ pstg     mA = L.pAccount p == (Y.unPennyAcct . Y.pennyAcct $ acct)-    mQ = L.equivalent (L.qty . L.amount . L.entry $ pstg)+    mQ = L.equivalent (eitherToQty . L.entry $ pstg)                       (L.qty . L.amount $ e)-    mDC = (L.drCr e) == (L.drCr . L.entry $ pstg)+    mDC = (L.drCr e) == (either L.drCr L.drCr . L.entry $ pstg)     mDate = (L.day . L.tDateTime . L.tlCore $ tl) == (Y.unDate . Y.date $ a)     noFlag = isNothing . L.pNumber $ p-    mCmdty = (L.commodity . L.amount . L.entry $ pstg)+    mCmdty = (eitherToCmdty . L.entry $ pstg)              == (Y.unCurrency . Y.currency $ acct)  +eitherToCmdty :: Either (L.Entry a) (L.Entry b) -> L.Commodity+eitherToCmdty = either (L.commodity . L.amount) (L.commodity . L.amount)++eitherToQty :: Either (L.Entry L.QtyRep) (L.Entry L.Qty) -> L.Qty+eitherToQty = either (L.toQty . L.qty . L.amount)+                     (L.toQty . L.qty . L.amount)+ -- | Creates a new transaction corresponding to a given AmexTxn. Uses -- the Amex payee if that string is non empty; otherwise, uses the -- Amex description for the payee.@@ -248,9 +256,10 @@   -> Y.FitAcct   -> UNumberLookupMap   -> PyeLookupMap-  -> (Y.UNumber, (Y.Posting, L.Entry))+  -> (Y.UNumber, (Y.Posting, L.Entry L.Qty))   -> L.Transaction-newTransaction noAuto acct mu mp (u, (a, e)) = L.Transaction (tld, ents) where+newTransaction noAuto acct mu mp (u, (a, e))+  = L.Transaction (tld, ents) where   tld = L.TopLineData tlc Nothing Nothing   tlc = (L.emptyTopLineCore (L.dateTimeMidnightUTC . Y.unDate . Y.date $ a))         { L.tPayee = Just pa }@@ -271,8 +280,9 @@            }   p2core = L.emptyPostingCore ac   ents = L.rEnts (Y.unCurrency . Y.currency $ acct) (L.drCr e)-                 (L.qty . L.amount $ e, p1data)+                 (Left . L.qtyToRep gs . L.qty . L.amount $ e, p1data)                  [] p2data+  gs = Y.qtySpec acct  -- | Creates new transactions for all the items remaining in the -- DbMap. Appends a blank line after each one.
lib/Penny/Brenner/Print.hs view
@@ -23,22 +23,20 @@ data Arg   = ArgFile String -mode-  :: MA.Mode (Maybe Y.FitAcct -> IO ())-mode = MA.Mode-  { MA.mName = "print"-  , MA.mIntersperse = MA.Intersperse-  , MA.mOpts = []-  , MA.mPosArgs = return . ArgFile-  , MA.mProcess = processor-  , MA.mHelp = help-  }+mode :: Y.Mode+mode mayFa = MA.modeHelp+  "print"+  help+  (processor mayFa)+  []+  MA.Intersperse+  (return . ArgFile)  processor-  :: [Arg]-  -> Maybe Y.FitAcct+  :: Maybe Y.FitAcct+  -> [Arg]   -> IO ()-processor ls mayFa = do+processor mayFa ls = do   fa <- U.getFitAcct mayFa   doPrint (snd . Y.parser $ fa) ls 
lib/Penny/Brenner/Types.hs view
@@ -11,7 +11,6 @@   , DbMap   , DbList   , Posting(..)-  , ConfigLocation(..)   , DbLocation(..)   , FitAcctName(..)   , FitAcctDesc(..)@@ -25,18 +24,23 @@   , FitFileLocation(..)   , AllowNew(..)   , ParserFn+  , Mode   ) where  import Control.Applicative ((<$>), (<*>)) import qualified Control.Monad.Exception.Synchronous as Ex import qualified Data.Map as M import qualified Data.Time as Time-import qualified Penny.Copper.Render as R import qualified Penny.Lincoln as L import Data.Text (Text, pack, unpack) import qualified Data.Text.Encoding as E import qualified Data.Serialize as S+import qualified Penny.Steel.Sums as Su+import qualified System.Console.MultiArg as MA +-- | The type of all Brenner MultiArg modes.+type Mode = Maybe FitAcct -> MA.Mode (MA.ProgName -> String) (IO ())+ -- | The date reported by the financial institution. newtype Date = Date { unDate :: Time.Day }   deriving (Eq, Show, Ord, Read)@@ -196,13 +200,6 @@         <*> S.get         <*> S.get --- | Where is a configuration file-newtype ConfigLocation = ConfigLocation-  { unConfigLocation :: Text }-  deriving (Eq, Show)--instance L.HasText ConfigLocation where text = unConfigLocation- -- | Where is the database of postings? newtype DbLocation = DbLocation { unDbLocation :: Text }   deriving (Eq, Show)@@ -277,7 +274,10 @@   , pennyAcct :: PennyAcct   , defaultAcct :: DefaultAcct   , currency :: Currency-  , groupSpecs :: R.GroupSpecs++  , qtySpec :: Su.S3 L.Radix L.PeriodGrp L.CommaGrp+  -- ^ How to turn Qty into QtyRep.+   , translator :: Translator    , side :: L.Side
lib/Penny/Brenner/Util.hs view
@@ -10,13 +10,13 @@ import qualified Text.Parsec as P import qualified Penny.Lincoln as L import qualified System.Exit as Exit-import qualified System.Console.MultiArg as MA import qualified System.IO as IO+import System.Environment (getProgName)  -- | Print an error message and exit. errExit :: String -> IO a errExit s = do-  pn <- MA.getProgName+  pn <- getProgName   IO.hPutStrLn IO.stderr $ pn ++ ": error: " ++ s   Exit.exitFailure @@ -79,10 +79,10 @@ -- digits. All these values should parse. So if there is a problem it -- is a programmer error. Apply error. parseQty :: Y.Amount -> L.Qty-parseQty a = case P.parse CP.quantity "" (Y.unAmount a) of+parseQty a = case P.parse CP.unquotedQtyRep "" (Y.unAmount a) of   Left e -> error $ "could not parse quantity from string: "             ++ (X.unpack . Y.unAmount $ a) ++ ": " ++ show e-  Right g -> g+  Right g -> L.toQty g  label :: String -> X.Text -> String label s x = s ++ ": " ++ X.unpack x ++ "\n"
lib/Penny/Cabin/Balance/Convert.hs view
@@ -36,7 +36,7 @@ -- need to use if you are supplying options programatically (as -- opposed to parsing them in from the command line.) data Opts = Opts-  { balanceFormat :: L.Commodity -> L.Qty -> X.Text+  { balanceFormat :: L.Amount L.Qty -> X.Text   , showZeroBalances :: CO.ShowZeroBalances   , sorter :: Sorter   , target :: L.To@@ -48,16 +48,16 @@ -- BottomLine (unlike in the MultiCommodity report, where each -- subaccount may have more than one BottomLine, one for each -- commodity.)-type Sorter =-  (L.SubAccount, L.BottomLine)+type Sorter+  = (L.SubAccount, L.BottomLine)   -> (L.SubAccount, L.BottomLine)   -> Ordering  -- | Converts all commodities in a Balance to a single commodity and -- combines all the BottomLines into one. Fails with an error message -- if no conversion data is available.-convertBalance ::-  L.PriceDb+convertBalance+  :: L.PriceDb   -> L.DateTime   -> L.To   -> L.Balance@@ -68,8 +68,8 @@  -- | Converts a single BottomLine to a new commodity. Fails with an -- error message if no conversion data is available.-convertOne ::-  L.PriceDb+convertOne+  :: L.PriceDb   -> L.DateTime   -> L.To   -> (L.Commodity, L.BottomLine)@@ -85,8 +85,8 @@         g r = L.NonZero (L.Column dc r)  -- | Creates an error message for conversion errors.-convertError ::-  L.To+convertError+  :: L.To   -> L.From   -> L.PriceDbError   -> X.Text@@ -154,7 +154,7 @@ report os@(Opts getFmt _ _ _ _ txtFormats) ps bs = do   fstBl <- sumConvertSort os ps bs   let (rs, L.To cy) = rows fstBl-      fmt = getFmt cy+      fmt q = getFmt (L.Amount q cy)   return $ K.rowsToChunks txtFormats fmt rs  @@ -165,15 +165,15 @@   -> I.Report cmdLineReport o rt = (help o, mkMode)   where-    mkMode _ _ chgrs _ fsf = MA.Mode-      { MA.mName = "convert"-      , MA.mIntersperse = MA.Intersperse-      , MA.mOpts = map (fmap Right) P.allOptSpecs-      , MA.mPosArgs = return . Left-      , MA.mProcess = process rt chgrs o fsf-      , MA.mHelp = const (help o)-      }+    mkMode _ _ chgrs _ fsf = MA.modeHelp+      "convert"+      (const (help o))+      (process rt chgrs o fsf)+      (map (fmap Right) P.allOptSpecs)+      MA.Intersperse+      (return . Left) + process   :: S.Runtime   -> Scheme.Changers@@ -189,9 +189,9 @@       Ex.Success g -> return $         let noDefault = X.pack "no default price found"             f = fromParsedOpts chgrs g-            pr ts pps = do+            pr fmt ts pps = do               rptOpts <- Ex.fromMaybe noDefault $-                f pps (O.format defaultOpts)+                f pps fmt               let boxes = fsf ts               report rptOpts pps boxes         in (posArgs, pr)@@ -223,9 +223,10 @@ mostFrequent = U.lastMode . map (L.to . L.price)  -type DoReport = [L.PricePoint]-               -> (L.Commodity -> L.Qty -> X.Text)-               -> (Maybe Opts)+type DoReport+  = [L.PricePoint]+  -> (L.Amount L.Qty -> X.Text)+  -> (Maybe Opts)  -- | Get options for the report, depending on what options were parsed -- from the command line. Fails if the user did not specify a
lib/Penny/Cabin/Balance/Convert/Options.hs view
@@ -5,9 +5,7 @@ import qualified Penny.Cabin.Balance.Convert.Parser as P import qualified Penny.Cabin.Parsers as CP import qualified Penny.Cabin.Options as CO-import qualified Penny.Lincoln as L import qualified Penny.Shield as S-import qualified Data.Text as X  -- | Default options for the Convert report. This record is used as -- the starting point when parsing in options from the command@@ -19,7 +17,6 @@   , target :: P.Target   , sortOrder :: CP.SortOrder   , sortBy :: P.SortBy-  , format :: L.Commodity -> L.Qty -> X.Text   }  toParserOpts :: DefaultOpts -> S.Runtime -> P.Opts@@ -37,7 +34,6 @@   , target = P.AutoTarget   , sortOrder = CP.Ascending   , sortBy = P.SortByName-  , format = \_ q -> X.pack . L.prettyShowQty $ q   }  
lib/Penny/Cabin/Balance/MultiCommodity.hs view
@@ -5,7 +5,6 @@   Opts(..),   defaultOpts,   defaultParseOpts,-  defaultFormat,   parseReport,   defaultReport,   report@@ -34,15 +33,15 @@ -- needed to make the report if the options are not being parsed in -- from the command line. data Opts = Opts-  { balanceFormat :: L.Commodity -> L.Qty -> X.Text+  { balanceFormat :: L.Amount L.Qty -> X.Text   , showZeroBalances :: CO.ShowZeroBalances   , order :: L.SubAccount -> L.SubAccount -> Ordering   , textFormats :: E.Changers   } -defaultOpts :: Opts-defaultOpts = Opts-  { balanceFormat = defaultFormat+defaultOpts :: (L.Amount L.Qty -> X.Text) -> Opts+defaultOpts fmt = Opts+  { balanceFormat = fmt   , showZeroBalances = CO.ShowZeroBalances True   , order = compare   , textFormats = Schemes.darkLabels@@ -56,7 +55,7 @@  fromParseOpts   :: E.Changers-  -> (L.Commodity -> L.Qty -> X.Text)+  -> (L.Amount L.Qty -> X.Text)   -> P.ParseOpts   -> Opts fromParseOpts chgrs fmt (P.ParseOpts szb o) = Opts fmt szb o' chgrs@@ -65,9 +64,6 @@        CP.Ascending -> compare        CP.Descending -> CO.descending compare -defaultFormat :: a -> L.Qty -> X.Text-defaultFormat _ = X.pack . L.prettyShowQty- summedSortedBalTree ::   CO.ShowZeroBalances   -> (L.SubAccount -> L.SubAccount -> Ordering)@@ -100,48 +96,43 @@  -- | The MultiCommodity report with configurable options that have -- been parsed from the command line.-parseReport ::-  (L.Commodity -> L.Qty -> X.Text)-  -- ^ How to format balances. For instance you can use this to-  -- perform commodity-sensitive digit grouping.--  -> P.ParseOpts+parseReport+  :: P.ParseOpts   -- ^ Default options for the report. These can be overriden on the   -- command line.    -> I.Report-parseReport fmt o rt = (help o, makeMode)+parseReport o rt = (help o, makeMode)   where-    makeMode _ _ chgrs _ fsf = MA.Mode-      { MA.mName = "balance"-      , MA.mIntersperse = MA.Intersperse-      , MA.mOpts = map (fmap Right) P.allSpecs-      , MA.mPosArgs = return . Left-      , MA.mProcess = process chgrs fmt o rt fsf-      , MA.mHelp = const (help o)-      }+    makeMode _ _ chgrs _ fsf = MA.modeHelp+      "balance"+      (const (help o))+      (process chgrs o rt fsf)+      (map (fmap Right) P.allSpecs)+      MA.Intersperse+      (return . Left)  process   :: Applicative f   => E.Changers-  -> (L.Commodity -> L.Qty -> X.Text)   -> P.ParseOpts   -> a   -> ([L.Transaction] -> [(Ly.LibertyMeta, L.Posting)])   -> [Either String (P.ParseOpts -> P.ParseOpts)]   -> f I.ArgsAndReport-process chgrs fmt o _ fsf ls =+process chgrs o _ fsf ls =   let (posArgs, fns) = Ei.partitionEithers ls       mkParsedOpts = foldl (flip (.)) id fns       os' = mkParsedOpts o-      mcOpts = fromParseOpts chgrs fmt os'-      pr txns _ = return $ report mcOpts (fsf txns)+      pr fmt txns _ = return $ report mcOpts (fsf txns)+        where+          mcOpts = fromParseOpts chgrs fmt os'   in pure (posArgs, pr)   -- | The MultiCommodity report, with default options. defaultReport :: I.Report-defaultReport = parseReport defaultFormat defaultParseOpts+defaultReport = parseReport defaultParseOpts  ------------------------------------------------------------ -- ## Help
lib/Penny/Cabin/Balance/MultiCommodity/Chunker.hs view
@@ -149,7 +149,7 @@  rowsToChunks   :: E.Changers-  -> (L.Commodity -> L.Qty -> X.Text)+  -> (L.Amount L.Qty -> X.Text)   -- ^ How to format a balance to allow for digit grouping   -> [Row]   -> [Rb.Chunk]@@ -159,7 +159,7 @@  rowsToColumns   :: E.Changers-  -> (L.Commodity -> L.Qty -> X.Text)+  -> (L.Amount L.Qty -> X.Text)   -- ^ How to format a balance to allow for digit grouping    -> [Row]@@ -171,7 +171,7 @@  mkColumn   :: E.Changers-  -> (L.Commodity -> L.Qty -> X.Text)+  -> (L.Amount L.Qty -> X.Text)   -> (Meta.VisibleNum, Row)   -> Columns PreSpec mkColumn chgrs fmt (vn, (Row i acctTxt bs)) = Columns ca cd cc cq@@ -208,7 +208,7 @@  balanceChunks   :: E.Changers-  -> (L.Commodity -> L.Qty -> X.Text)+  -> (L.Amount L.Qty -> X.Text)   -> E.EvenOdd   -> (L.Commodity, L.BottomLine)   -> (Rb.Chunk, Rb.Chunk, Rb.Chunk)
lib/Penny/Cabin/Balance/Util.hs view
@@ -162,8 +162,11 @@   boxesBalance :: [(a, L.Posting)] -> L.Balance-boxesBalance = mconcat . map L.entryToBalance . map Q.entry-               . map snd+boxesBalance+  = mconcat+  . map (either L.entryToBalance L.entryToBalance)+  . map Q.entry+  . map snd  mapSnd :: (a -> b) -> (f, a) -> (f, b) mapSnd f (x, a) = (x, f a)
lib/Penny/Cabin/Interface.hs view
@@ -34,21 +34,28 @@ type ParseResult = Exceptional X.Text ArgsAndReport  type PrintReport-  = [L.Transaction]-  -- ^ All transactions; the report must sort and filter them+  = (L.Amount L.Qty -> X.Text)+  -- ^ Function that gives a rendering for quantities that are not+  -- already formatted.  This function is passed as part of+  -- PrintReport because it allows the function to be built after the+  -- ledger has already been parsed. +  -> [L.Transaction]+  -- ^ All transactions to be included in the report. The report must+  -- sort and filter them+   -> [L.PricePoint]   -- ^ PricePoints to be included in the report     -> Exceptional X.Text [R.Chunk]   -- ^ The exception type is a strict Text, containing the error--- message. The success type is a list of either a Chunk or a PreChunk--- containing the resulting report. This allows for errors after the--- list of transactions has been seen. The name of the executable and--- the word @error@ will be prepended to this Text; otherwise, it is--- printed as-is, so be sure to include any trailing newline if--- needed.+  -- message. The success type is a list of either a Chunk or a PreChunk+  -- containing the resulting report. This allows for errors after the+  -- list of transactions has been seen. The name of the executable and+  -- the word @error@ will be prepended to this Text; otherwise, it is+  -- printed as-is, so be sure to include any trailing newline if+  -- needed.   type Report = Runtime -> (HelpStr, MkReport)@@ -74,4 +81,4 @@   -- ^ Result from previous parsers that will sort and filter incoming   -- transactions -  -> MA.Mode ParseResult+  -> MA.Mode (MA.ProgName -> String) ParseResult
lib/Penny/Cabin/Posts.hs view
@@ -44,8 +44,6 @@   , A.SubAccountLength(..)   , A.alloc   , yearMonthDay-  , qtyAsIs-  , balanceAsIs   , defaultWidth   , columnsVarToWidth   , widthFromRuntime@@ -113,14 +111,13 @@ zincReport :: ZincOpts -> I.Report zincReport opts rt = (helpStr opts, md)   where-    md cs fty ch expr fsf = MA.Mode-      { MA.mName = "postings"-      , MA.mIntersperse = MA.Intersperse-      , MA.mOpts = specs rt-      , MA.mPosArgs = return . Left-      , MA.mProcess = process opts cs fty ch expr fsf-      , MA.mHelp = const (helpStr opts)-      }+    md cs fty ch expr fsf = MA.modeHelp+      "postings"+      (const (helpStr opts))+      (process opts cs fty ch expr fsf)+      (specs rt)+      MA.Intersperse+      (return . Left)  specs   :: Sh.Runtime@@ -152,13 +149,14 @@   -> I.ArgsAndReport mkPrintReport posArgs zo ch fsf st = (posArgs, f)   where-    f txns _ = do+    f fmt txns _ = do       pdct <- getPredicate (P.exprDesc st) (P.tokens st)       let boxes = fsf txns           rptChks = postsReport ch (P.showZeroBalances st) pdct-                    (P.postFilter st) (chunkOpts st zo) boxes+                    (P.postFilter st) (chunkOpts fmt st zo) boxes           expChks = showExpression (P.showExpression st) pdct-          verbChks = showVerboseFilter (P.verboseFilter st) pdct boxes+          verbChks = showVerboseFilter fmt (P.verboseFilter st)+                                       pdct boxes           chks = expChks                  ++ verbChks                  ++ rptChks@@ -181,16 +179,17 @@     chks = Pe.showPdct indentAmt 0 pdct  showVerboseFilter-  :: P.VerboseFilter+  :: (L.Amount L.Qty -> X.Text)+  -> P.VerboseFilter   -> Pe.Pdct (Ly.LibertyMeta, L.Posting)   -> [(Ly.LibertyMeta, L.Posting)]   -> [Rb.Chunk]-showVerboseFilter (P.VerboseFilter b) pdct bs =+showVerboseFilter fmt (P.VerboseFilter b) pdct bs =   if not b then [] else info : blankLine : (chks ++ [blankLine])   where     chks =       fst-      $ Pe.verboseFilter (L.display . snd) indentAmt False pdct bs+      $ Pe.verboseFilter ((L.display fmt) . snd) indentAmt False pdct bs     info = "Postings report filter:\n"  defaultOptions@@ -201,8 +200,6 @@   , width = widthFromRuntime rt   , showZeroBalances = CO.ShowZeroBalances False   , dateFormat = yearMonthDay-  , qtyFormat = qtyAsIs-  , balanceFormat = balanceAsIs   , subAccountLength = A.SubAccountLength 2   , payeeAllocation = A.alloc 60   , accountAllocation = A.alloc 40@@ -243,17 +240,6 @@     -- a PostingInfo so it has lots of information, but it     -- should return a date for use in the Date field. -  , qtyFormat :: (M.PostMeta, L.Posting) -> X.Text-    -- ^ How to display the quantity of the posting. This-    -- function is applied to a Box so it has lots of-    -- information, but it should return a formatted string of-    -- the quantity. Allows you to format digit grouping,-    -- radix points, perform rounding, etc.--  , balanceFormat :: L.Commodity -> L.Qty -> X.Text-    -- ^ How to display balance totals. Similar to-    -- balanceFormat.-   , subAccountLength :: A.SubAccountLength     -- ^ When shortening the names of sub accounts to make     -- them fit, they will be this long.@@ -276,14 +262,14 @@    } -chunkOpts ::-  P.State+chunkOpts+  :: (L.Amount L.Qty -> X.Text)+  -> P.State   -> ZincOpts   -> C.ChunkOpts-chunkOpts s z = C.ChunkOpts+chunkOpts fmt s z = C.ChunkOpts   { C.dateFormat = dateFormat z-  , C.qtyFormat = qtyFormat z-  , C.balanceFormat = balanceFormat z+  , C.qtyFormat = fmt   , C.fields = P.fields s   , C.subAccountLength = subAccountLength z   , C.payeeAllocation = payeeAllocation z@@ -321,16 +307,6 @@         . snd         $ p     fmt = "%Y-%m-%d"---- | Shows the quantity of a posting. Does no rounding or--- prettification; simply uses show on the underlying Decimal.-qtyAsIs :: (M.PostMeta, L.Posting) -> X.Text-qtyAsIs p = X.pack . L.prettyShowQty . Q.qty . snd $ p---- | Shows the quantity of a balance. If there is no quantity, shows--- two dashes.-balanceAsIs :: a -> L.Qty -> X.Text-balanceAsIs _ = X.pack . L.prettyShowQty  -- | The default width for the report. defaultWidth :: T.ReportWidth
lib/Penny/Cabin/Posts/Chunk.hs view
@@ -18,8 +18,7 @@  data ChunkOpts = ChunkOpts   { dateFormat :: (M.PostMeta, L.Posting) -> X.Text-  , qtyFormat :: (M.PostMeta, L.Posting) -> X.Text-  , balanceFormat :: L.Commodity -> L.Qty -> X.Text+  , qtyFormat :: L.Amount L.Qty -> X.Text   , fields :: F.Fields Bool   , subAccountLength :: A.SubAccountLength   , payeeAllocation :: A.Alloc@@ -32,7 +31,6 @@ growOpts c = G.GrowOpts   { G.dateFormat = dateFormat c   , G.qtyFormat = qtyFormat c-  , G.balanceFormat = balanceFormat c   , G.fields = fields c   } 
lib/Penny/Cabin/Posts/Growers.hs view
@@ -29,8 +29,7 @@ -- | All the options needed to grow the cells. data GrowOpts = GrowOpts   { dateFormat :: (M.PostMeta, L.Posting) -> X.Text-  , qtyFormat :: (M.PostMeta, L.Posting) -> X.Text-  , balanceFormat :: L.Commodity -> L.Qty -> X.Text+  , qtyFormat :: L.Amount L.Qty -> X.Text   , fields :: F.Fields Bool   } @@ -115,7 +114,7 @@   , postingQty           = \o ch -> getPostingQty ch (qtyFormat o)   , totalDrCr            = const getTotalDrCr   , totalCmdty           = const getTotalCmdty-  , totalQty             = \o ch -> getTotalQty ch (balanceFormat o)+  , totalQty             = \o ch -> getTotalQty ch (qtyFormat o)   }  -- | Make a left justified cell one line long that shows a serial.@@ -246,8 +245,16 @@ getPostingCmdty ch i = coloredPostingCell ch t i where   t = L.unCommodity . Q.commodity . snd $ i -getPostingQty :: E.Changers -> ((M.PostMeta, L.Posting) -> X.Text) -> (M.PostMeta, L.Posting) -> PreSpec-getPostingQty ch qf i = coloredPostingCell ch (qf i) i+getPostingQty+  :: E.Changers+  -> (L.Amount L.Qty -> X.Text) -- ((M.PostMeta, L.Posting) -> X.Text)+  -> (M.PostMeta, L.Posting)+  -> PreSpec+getPostingQty ch qf i = coloredPostingCell ch qtyStr i+  where+    qtyStr = case (L.entry . L.headEnt . snd . L.unPosting . snd $ i) of+      Left qr -> L.showQtyRep . L.qty . L.amount $ qr+      Right q -> qf . L.amount $ q  getTotalDrCr :: E.Changers -> (M.PostMeta, L.Posting) -> PreSpec getTotalDrCr ch i =@@ -280,7 +287,7 @@  getTotalQty   :: E.Changers-  -> (L.Commodity -> L.Qty -> X.Text)+  -> (L.Amount L.Qty -> X.Text)   -> (M.PostMeta, L.Posting)   -> PreSpec getTotalQty ch balFmt i =
lib/Penny/Cabin/Posts/Meta.hs view
@@ -73,7 +73,8 @@   -> (a, L.Posting)   -> (L.Balance, (L.Balance, (a, L.Posting))) balanceAccum (CO.ShowZeroBalances szb) balOld (x, po) =-  let balThis = L.entryToBalance . Q.entry $ po+  let balThis = either L.entryToBalance L.entryToBalance+                . Q.entry $ po       balNew = mappend balOld balThis       balNoZeroes = L.removeZeroCommodities balNew       bal' = if szb then balNew else balNoZeroes
lib/Penny/Cabin/Scheme.hs view
@@ -115,7 +115,7 @@  balanceToQtys   :: Changers-  -> (L.Commodity -> L.Qty -> X.Text)+  -> (L.Amount L.Qty -> X.Text)   -> EvenOdd   -> [(L.Commodity, L.BottomLine)]   -> [R.Chunk]@@ -128,7 +128,7 @@  bottomLineToQty   :: Changers-  -> (L.Commodity -> L.Qty -> X.Text)+  -> (L.Amount L.Qty -> X.Text)   -> EvenOdd   -> (L.Commodity, L.BottomLine)   -> R.Chunk@@ -136,6 +136,7 @@   where     (lbl, t) = case bl of       L.Zero -> (Zero, X.pack "--")-      L.NonZero (L.Column clmDrCr qt) -> (dcToLbl clmDrCr, getTxt cy qt)+      L.NonZero (L.Column clmDrCr qt) ->+        (dcToLbl clmDrCr, getTxt (L.Amount qt cy))     md = getEvenOddLabelValue lbl eo chgrs 
lib/Penny/Copper.hs view
@@ -82,8 +82,6 @@   , module Penny.Copper.Interface    -- * Rendering-  , R.GroupSpec(..)-  , R.GroupSpecs(..)   , R.item  
lib/Penny/Copper/Interface.hs view
@@ -1,12 +1,7 @@-{-# LANGUAGE DeriveGeneric #-}- module Penny.Copper.Interface where  import qualified Penny.Lincoln as L import qualified Data.Text as X-import qualified Data.Text.Encoding as XE-import GHC.Generics (Generic)-import qualified Data.Binary as B import qualified Penny.Steel.Sums as S  data ParsedTopLine = ParsedTopLine@@ -16,27 +11,19 @@   , ptlPayee :: Maybe L.Payee   , ptlMemo :: Maybe (L.Memo, L.TopMemoLine)   , ptlTopLineLine :: L.TopLineLine-  } deriving (Show, Generic)+  } deriving (Show)  toTopLineCore :: ParsedTopLine -> L.TopLineCore toTopLineCore (ParsedTopLine dt nu fl pa me _)   = L.TopLineCore dt nu fl pa (fmap fst me) -instance B.Binary ParsedTopLine- type ParsedTxn = (ParsedTopLine , L.Ents (L.PostingCore, L.PostingLine))  data BlankLine = BlankLine-  deriving (Eq, Show, Generic)--instance B.Binary BlankLine+  deriving (Eq, Show)  newtype Comment = Comment { unComment :: X.Text }   deriving (Eq, Show)--instance B.Binary Comment where-  get = fmap (Comment . XE.decodeUtf8) B.get-  put = B.put . XE.encodeUtf8 . unComment  type ParsedItem =   S.S4 ParsedTxn L.PricePoint Comment BlankLine
lib/Penny/Copper/Parsec.hs view
@@ -2,10 +2,12 @@ -- documented in EBNF in the file @doc\/ledger-grammar.org@. module Penny.Copper.Parsec where +-- # Imports+ import qualified Penny.Copper.Interface as I import qualified Penny.Copper.Terminals as T import Text.Parsec.Text (Parser)-import Text.Parsec (many, many1, satisfy)+import Text.Parsec (many, many1, satisfy, (<?>)) import qualified Text.Parsec as P import qualified Text.Parsec.Pos as Pos import Control.Applicative.Permutation (runPerms, maybeAtom)@@ -13,6 +15,7 @@                             (<|>), optional) import Control.Monad (replicateM, when) import qualified Control.Monad.Exception.Synchronous as Ex+import Data.List.NonEmpty (NonEmpty((:|))) import qualified Penny.Lincoln as L import qualified Penny.Steel.Sums as S import Data.Maybe (fromMaybe)@@ -24,6 +27,13 @@ import qualified System.IO as IO import qualified Data.Text.IO as TIO +-- # Helpers++nonEmpty :: Parser a -> Parser (NonEmpty a)+nonEmpty p = (:|) <$> p <*> many p++-- # Accounts+ lvl1SubAcct :: Parser L.SubAccount lvl1SubAcct =   (L.SubAccount . pack) <$> many1 (satisfy T.lvl1AcctChar)@@ -64,6 +74,8 @@ ledgerAcct :: Parser L.Account ledgerAcct = quotedLvl1Acct <|> lvl2Acct +-- # Commodities+ lvl1Cmdty :: Parser L.Commodity lvl1Cmdty = (L.Commodity . pack) <$> many1 (satisfy T.lvl1CmdtyChar) @@ -80,61 +92,141 @@ lvl3Cmdty :: Parser L.Commodity lvl3Cmdty = (L.Commodity . pack) <$> many1 (satisfy T.lvl3CmdtyChar) -digitGroup :: Parser [Char]-digitGroup = satisfy T.thinSpace *> many1 (satisfy T.digit)+-- # Quantities -digitSequence :: Parser [Char]-digitSequence =-  (++) <$> many1 (satisfy T.digit)-  <*> (concat <$> (many digitGroup))+digit :: Parser L.Digit+digit = (P.choice . map f $ zip ['0'..'9'] [minBound..maxBound])+  <?> "digit"+  where+    f (s, d) = d <$ P.char s -digitPostSequence :: Parser (Maybe [Char])-digitPostSequence = satisfy T.period *> optional digitSequence+digitList :: Parser L.DigitList+digitList = L.DigitList <$> nonEmpty digit -quantity :: Parser L.Qty-quantity = p >>= failOnErr-  where-    p = (L.RadFrac <$> (satisfy T.period *> digitSequence))-        <|> (f <$> digitSequence <*> optional digitPostSequence)-    f digSeq maybePostSeq = case maybePostSeq of-      Nothing -> L.Whole digSeq-      Just ps ->-        maybe (L.WholeRad digSeq) (L.WholeRadFrac digSeq) ps-    failOnErr = maybe (fail msg) return . L.toQty-    msg = "could not read quantity; zero quantities not allowed"+groupPart :: Parser a -> Parser (a, L.DigitList)+groupPart p = (,) <$> p <*> digitList +groupedDigits :: Parser a -> Parser (L.GroupedDigits a)+groupedDigits p+  = L.GroupedDigits <$> digitList <*> many (groupPart p)++-- | Parses a sequence of grouped digits, followed by an optional+-- radix point, followed by an optional additional sequence of grouped+-- digits.  Numbers such as .25 are not allowed; instead,+-- the user must enter 0.25. Also not allowed is something like+-- "25.". Intsead, if the user enters a radix, there must be a+-- character after it.+digitsRadDigits+  :: Parser a+  -- ^ Parses a single grouping character+  -> Parser void+  -- ^ Parses a radix point+  -> Parser (L.GroupedDigits a, Maybe (L.GroupedDigits a))+digitsRadDigits gc r = do+  g1 <- groupedDigits gc+  maybeRad <- optional r+  case maybeRad of+    Nothing -> return (g1, Nothing)+    Just _ -> do+      g2 <- groupedDigits gc+      return (g1, Just g2)++-- | Parses an unquoted QtyRep.+unquotedQtyRep :: Parser L.QtyRep+unquotedQtyRep = do+  let gc = P.choice [ L.PGThinSpace <$ P.char '\x2009'+                    , L.PGComma <$ P.char ',' ]+      r = P.char '.'+  (g1, mayg2) <- digitsRadDigits gc r+  case L.wholeOrFrac g1 mayg2 of+    Nothing -> fail "failed to parse quantity"+    Just ei -> return . L.wholeOrFracToQtyRep . Left $ ei++-- | Parses an unquoted QtyRep that also has spaces. Use only when+-- parsing command line items.+unquotedQtyRepWithSpaces :: Parser L.QtyRep+unquotedQtyRepWithSpaces = do+  let gc = P.choice [ L.PGThinSpace <$ P.char '\x2009'+                    , L.PGComma <$ P.char ','+                    , L.PGSpace <$ P.char ' ' ]+      r = P.char '.'+  (g1, mayg2) <- digitsRadDigits gc r+  case L.wholeOrFrac g1 mayg2 of+    Nothing -> fail "failed to parse quantity"+    Just ei -> return . L.wholeOrFracToQtyRep . Left $ ei++-- | Parses a QtyRep that is quoted with square braces. This is a+-- QtyRep that uses a comma as the radix point.+quotedCommaQtyRep :: Parser L.QtyRep+quotedCommaQtyRep = do+  let gc = P.choice [ L.CGThinSpace <$ P.char '\x2009'+                    , L.CGSpace <$ P.char ' '+                    , L.CGPeriod <$ P.char '.' ]+      r = P.char ','+  _ <- P.char '['+  (g1, mayg2) <- digitsRadDigits gc r+  _ <- P.char ']'+  case L.wholeOrFrac g1 mayg2 of+    Nothing -> fail "failed to parse quantity"+    Just ei -> return . L.wholeOrFracToQtyRep . Right $ ei++-- | Parses a QtyRep that is quoted with curly braces. This is a+-- QtyRep that uses a period as the radix point. Unlike an unquoted+-- QtyRep this can include spaces.+quotedPeriodQtyRep :: Parser L.QtyRep+quotedPeriodQtyRep = do+  let gc = P.choice [ L.PGThinSpace <$ P.char '\x2009'+                    , L.PGComma <$ P.char ','+                    , L.PGSpace <$ P.char ' '+                    ]+      r = P.char '.'+  _ <- P.char '{'+  (g1, mayg2) <- digitsRadDigits gc r+  _ <- P.char '}'+  case L.wholeOrFrac g1 mayg2 of+    Nothing -> fail "failed to parse quantity"+    Just ei -> return . L.wholeOrFracToQtyRep . Left $ ei++qtyRep :: Parser L.QtyRep+qtyRep = unquotedQtyRep <|> quotedPeriodQtyRep <|> quotedCommaQtyRep+         <?> "quantity"++-- # Amounts+ spaceBetween :: Parser L.SpaceBetween spaceBetween = f <$> optional (many1 (satisfy T.white))   where     f = maybe L.NoSpaceBetween (const L.SpaceBetween) -leftCmdtyLvl1Amt :: Parser (L.Amount, L.Side, L.SpaceBetween)+leftCmdtyLvl1Amt :: Parser (L.Amount L.QtyRep, L.Side, L.SpaceBetween) leftCmdtyLvl1Amt =-  f <$> quotedLvl1Cmdty <*> spaceBetween <*> quantity+  f <$> quotedLvl1Cmdty <*> spaceBetween <*> qtyRep   where     f c s q = (L.Amount q c , L.CommodityOnLeft, s) -leftCmdtyLvl3Amt :: Parser (L.Amount, L.Side, L.SpaceBetween)-leftCmdtyLvl3Amt = f <$> lvl3Cmdty <*> spaceBetween <*> quantity+leftCmdtyLvl3Amt :: Parser (L.Amount L.QtyRep, L.Side, L.SpaceBetween)+leftCmdtyLvl3Amt = f <$> lvl3Cmdty <*> spaceBetween <*> qtyRep   where     f c s q = (L.Amount q c, L.CommodityOnLeft, s) -leftSideCmdtyAmt :: Parser (L.Amount, L.Side, L.SpaceBetween)+leftSideCmdtyAmt :: Parser (L.Amount L.QtyRep, L.Side, L.SpaceBetween) leftSideCmdtyAmt = leftCmdtyLvl1Amt <|> leftCmdtyLvl3Amt  rightSideCmdty :: Parser L.Commodity rightSideCmdty = quotedLvl1Cmdty <|> lvl2Cmdty -rightSideCmdtyAmt :: Parser (L.Amount, L.Side, L.SpaceBetween)+rightSideCmdtyAmt :: Parser (L.Amount L.QtyRep, L.Side, L.SpaceBetween) rightSideCmdtyAmt =-  f <$> quantity <*> spaceBetween <*> rightSideCmdty+  f <$> qtyRep <*> spaceBetween <*> rightSideCmdty   where-    f q s c = (L.Amount q c ,L.CommodityOnRight, s)+    f q s c = (L.Amount q c, L.CommodityOnRight, s)  -amount :: Parser (L.Amount, L.Side, L.SpaceBetween)+amount :: Parser (L.Amount L.QtyRep, L.Side, L.SpaceBetween) amount = leftSideCmdtyAmt <|> rightSideCmdtyAmt +-- # Comments+ comment :: Parser I.Comment comment =   (I.Comment . pack)@@ -143,6 +235,8 @@   <* satisfy T.newline   <* many (satisfy T.white) +-- # Dates and times+ year :: Parser Integer year = read <$> replicateM 4 P.digit @@ -216,6 +310,8 @@                 z = fromMaybe L.noOffset mayTz             in ((hr, mn, sec), z) +-- # Debit and credit+ debit :: Parser L.DrCr debit = L.Debit <$ satisfy T.lessThan @@ -225,15 +321,23 @@ drCr :: Parser L.DrCr drCr = debit <|> credit -entry :: Parser (L.Entry, L.Side, L.SpaceBetween)+-- # Entries++entry :: Parser (L.Entry L.QtyRep, L.Side, L.SpaceBetween) entry = f <$> drCr <* (many (satisfy T.white)) <*> amount   where     f dc (am, sd, sb) = (L.Entry dc am, sd, sb) +-- # Flag+ flag :: Parser L.Flag flag = (L.Flag . pack) <$ satisfy T.openSquare   <*> many (satisfy T.flagChar) <* satisfy (T.closeSquare) +-- # Memos++-- ## Posting memo+ postingMemoLine :: Parser Text postingMemoLine =   pack@@ -244,6 +348,8 @@ postingMemo :: Parser L.Memo postingMemo = L.Memo <$> many1 postingMemoLine +-- ## Transaction memo+ transactionMemoLine :: Parser Text transactionMemoLine =   pack@@ -257,11 +363,15 @@                , L.Memo ls)  +-- # Number+ number :: Parser L.Number number =   L.Number . pack <$ satisfy T.openParen   <*> many (satisfy T.numberChar) <* satisfy T.closeParen +-- # Payees+ lvl1Payee :: Parser L.Payee lvl1Payee = L.Payee . pack <$> many (satisfy T.quotedPayeeChar) @@ -272,6 +382,8 @@ lvl2Payee = (\c cs -> L.Payee (pack (c:cs))) <$> satisfy T.letter             <*> many (satisfy T.nonNewline) +-- # Prices+ fromCmdty :: Parser L.From fromCmdty = L.From <$> (quotedLvl1Cmdty <|> lvl2Cmdty) @@ -294,6 +406,8 @@     msg = "could not parse price, make sure the from and to commodities "           ++ "are different" +-- # Tags+ tag :: Parser L.Tag tag = L.Tag . pack <$ satisfy T.asterisk <*> many (satisfy T.tagChar)       <* many (satisfy T.white)@@ -301,6 +415,8 @@ tags :: Parser L.Tags tags = (\t ts -> L.Tags (t:ts)) <$> tag <*> many tag +-- # TopLine+ topLinePayee :: Parser L.Payee topLinePayee = quotedLvl1Payee <|> lvl2Payee @@ -333,6 +449,8 @@       where         me = fmap (\(a, b) -> (b, a)) mayMe +-- # Postings+ flagNumPayee :: Parser (Maybe L.Flag, Maybe L.Number, Maybe L.Payee) flagNumPayee = runPerms   ( (,,) <$> maybeAtom (flag <* skipWhite)@@ -342,7 +460,7 @@ postingAcct :: Parser L.Account postingAcct = quotedLvl1Acct <|> lvl2Acct -posting :: Parser (L.PostingCore, L.PostingLine, Maybe L.Entry)+posting :: Parser (L.PostingCore, L.PostingLine, Maybe (L.Entry L.QtyRep)) posting = f <$> lineNum                <* skipWhite             <*> optional flagNumPayee  <* skipWhite             <*> postingAcct            <* skipWhite@@ -360,18 +478,24 @@         (en, sd, sb) = maybe (Nothing, Nothing, Nothing)           (\(a, b, c) -> (Just a, Just b, Just c)) mayEn +-- # Transaction+ transaction :: Parser I.ParsedTxn transaction = do   ptl <- topLine-  let getEntPair (core, lin, mayEn) = (mayEn, (core, lin))+  let getEntPair (core, lin, mayEn) = (fmap Left mayEn, (core, lin))   ts <- fmap (map getEntPair) $ many posting   ents <- maybe (fail "unbalanced transaction") return $ L.ents ts   return (ptl, ents)  +-- # Blank line+ blankLine :: Parser () blankLine = () <$ satisfy T.newline <* skipWhite +-- # Item+ item :: Parser I.ParsedItem item   = fmap S.S4a transaction@@ -379,6 +503,8 @@   <|> fmap S.S4c comment   <|> (S.S4d I.BlankLine) <$ blankLine ++-- # Parsing  parse   :: Text
lib/Penny/Copper/Render.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE OverloadedStrings #-}+ -- | Renders Penny data in a format that can be parsed by -- "Penny.Copper.Parsec". These functions render text that is -- compliant with the EBNF grammar which is at@@ -6,8 +8,8 @@  import Control.Monad (guard) import Control.Applicative ((<$>), (<|>), (<*>), pure)-import Data.List (intersperse, intercalate)-import Data.List.Split (chunksOf, splitOn)+import Data.List (intersperse)+import Data.Monoid ((<>)) import qualified Data.Text as X import Data.Text (Text, cons, snoc) import qualified Penny.Copper.Terminals as T@@ -115,98 +117,63 @@  -- * Quantities --- | Specifies how to perform digit grouping when rendering a--- quantity. All grouping groups into groups of 3 digits.-data GroupSpec =-  NoGrouping-  -- ^ Do not perform any digit grouping-  | GroupLarge-    -- ^ Group digits, but only if the number to be grouped is greater-    -- than 9,999 (if grouping the whole part) or if there are more-    -- than 4 decimal places (if grouping the fractional part).-  | GroupAll-    -- ^ Group digits whenever there are at least four decimal places.-  deriving (Eq, Show)+-- | Gets the characters necessary to quote a qtyRep.+quoteQtyRep :: L.QtyRep -> (Text, Text)+quoteQtyRep q = case q of+  L.QNoGrouping _ r -> case r of+    L.Period -> ("", "")+    L.Comma -> ("[", "]")+  L.QGrouped ei -> case ei of+    Left wf -> if hasSpace wf then ("{", "}") else ("", "")+    Right _ -> ("[", "]") +qtyRep :: L.QtyRep -> Text+qtyRep q = b <> L.showQtyRep q <> e+  where+    (b, e) = quoteQtyRep q -data GroupSpecs = GroupSpecs-  { left :: GroupSpec-  , right :: GroupSpec-  } deriving Show+hasSpace :: L.WholeOrFrac (L.GroupedDigits L.PeriodGrp) -> Bool+hasSpace (L.WholeOrFrac ei) = case ei of+  Left w -> grpHasSpace . L.unWholeOnly $ w+  Right wf -> grpHasSpace (L.whole wf) || grpHasSpace (L.frac wf)+  where+    grpHasSpace grp = L.PGSpace `elem` (map fst . L.dsNextParts $ grp)  -grouper :: String-grouper = "\x2009"--radix :: String-radix = "."---- | Performs grouping for amounts to the left of the radix point.-groupWhole :: GroupSpec -> String -> String-groupWhole gs o = let-  grouped = intercalate grouper-            . reverse-            . map reverse-            . chunksOf 3-            . reverse-            $ o-  in case gs of-    NoGrouping -> o-    GroupLarge -> if length o > 4 then grouped else o-    GroupAll -> grouped---- | Performs grouping for amounts to the right of the radix point.-groupDecimal :: GroupSpec -> String -> String-groupDecimal gs o = let-  grouped = intercalate grouper-            . chunksOf 3-            $ o-  in case gs of-    NoGrouping -> o-    GroupLarge -> if length o > 4 then grouped else o-    GroupAll -> grouped---- | Renders an unquoted Qty. Performs digit grouping as requested.-quantity-  :: GroupSpecs-  -- ^ Group for the portion to the left and right of the radix point?--  -> L.Qty-  -> X.Text-quantity gs q =-  let qs = L.prettyShowQty q-  in X.pack $ case splitOn "." qs of-    w:[] -> groupWhole (left gs) w-    w:d:[] ->-      groupWhole (left gs) w ++ radix ++ groupDecimal (right gs) d-    _ -> error "Qty.hs: rendering error"- -- * Amounts  -- | Render an Amount. The Format is required so that the commodity -- can be displayed in the right place. amount-  :: GroupSpecs+  :: Maybe (L.Amount L.Qty -> S.S3 L.Radix L.PeriodGrp L.CommaGrp)+  -- ^ If Just, render entries that are NOT inferred and that do not+  -- have a QtyRep.  If Nothing, fail if an entry is NOT inferred and+  -- does not have a QtyRep.  (Inferred entries are always rendered+  -- without an entry.)   -> Maybe L.Side   -> Maybe L.SpaceBetween-  -> L.Amount+  -> Either (L.Amount L.QtyRep) (L.Amount L.Qty)   -> Maybe X.Text-amount gs maySd maySb (L.Amount qt c) =-  let q = quantity gs qt-  in do-    sd <- maySd-    sb <- maySb-    let ws = case sb of-          L.SpaceBetween -> X.singleton ' '-          L.NoSpaceBetween -> X.empty-    (l, r) <- case sd of-          L.CommodityOnLeft -> do-            cx <- lvl3Cmdty c <|> quotedLvl1Cmdty c-            return (cx, q)-          L.CommodityOnRight -> do-            cx <- lvl2Cmdty c <|> quotedLvl1Cmdty c-            return (q, cx)-    return $ X.concat [l, ws, r]+amount mayFmt maySd maySb ei = do+  (q, c) <- case ei of+    Left a -> return (qtyRep . L.qty $ a, L.commodity a)+    Right a -> case mayFmt of+      Nothing -> Nothing+      Just f -> return ( qtyRep . L.qtyToRep (f a) . L.qty $ a,+                         L.commodity a)+  sd <- maySd+  sb <- maySb+  let ws = case sb of+        L.SpaceBetween -> X.singleton ' '+        L.NoSpaceBetween -> X.empty+  (l, r) <- case sd of+        L.CommodityOnLeft -> do+          cx <- lvl3Cmdty c <|> quotedLvl1Cmdty c+          return (cx, q)+        L.CommodityOnRight -> do+          cx <- lvl2Cmdty c <|> quotedLvl1Cmdty c+          return (q, cx)+  return $ X.concat [l, ws, r]  -- * Comments @@ -266,14 +233,20 @@ -- * Entries  entry-  :: GroupSpecs+  :: Maybe (L.Amount L.Qty -> S.S3 L.Radix L.PeriodGrp L.CommaGrp)+  -- ^ If Just, render entries that are NOT inferred and that do not+  -- have a QtyRep.  If Nothing, fail if an entry is NOT inferred and+  -- does not have a QtyRep.  (Inferred entries are always rendered+  -- without an entry.)   -> Maybe L.Side   -> Maybe L.SpaceBetween-  -> L.Entry+  -> Either (L.Entry L.QtyRep) (L.Entry L.Qty)   -> Maybe X.Text-entry gs sd sb (L.Entry dc a) = do-  amt <- amount gs sd sb a-  let dcTxt = X.pack $ case dc of+entry mayFmt sd sb ei = do+  amt <- amount mayFmt sd sb+                (either (Left . L.amount) (Right . L.amount) ei)+  let dc = either L.drCr L.drCr ei+      dcTxt = X.pack $ case dc of         L.Debit -> "<"         L.Credit -> ">"   return $ X.append (X.snoc dcTxt ' ') amt@@ -360,19 +333,18 @@  -- * Prices -price ::-  GroupSpecs-  -> L.PricePoint+price+  :: L.PricePoint   -> Maybe X.Text-price gs pp = let+price pp = let   dateTxt = dateTime (L.dateTime pp)   (L.From from) = L.from . L.price $ pp   (L.To to) = L.to . L.price $ pp   (L.CountPerUnit q) = L.countPerUnit . L.price $ pp   mayFromTxt = lvl3Cmdty from <|> quotedLvl1Cmdty from   in do-    amtTxt <- amount gs (L.ppSide pp) (L.ppSpaceBetween pp)-              (L.Amount q to)+    amtTxt <- amount Nothing (L.ppSide pp) (L.ppSpaceBetween pp)+              (Left (L.Amount q to))     fromTxt <- mayFromTxt     return $        (X.intercalate (X.singleton ' ')@@ -448,12 +420,16 @@ -- which case eight spaces will be emitted. (This allows the next -- posting to be indented properly.) posting-  :: GroupSpecs+  :: Maybe (L.Amount L.Qty -> S.S3 L.Radix L.PeriodGrp L.CommaGrp)+  -- ^ If Just, render entries that are NOT inferred and that do not+  -- have a QtyRep.  If Nothing, fail if an entry is NOT inferred and+  -- does not have a QtyRep.  (Inferred entries are always rendered+  -- without an entry.)   -> Bool   -- ^ If True, emit four spaces after the trailing newline.   -> L.Ent L.PostingCore   -> Maybe X.Text-posting gs pad ent = do+posting maySpec pad ent = do   let p = L.meta ent   fl <- renMaybe (L.pFlag p) flag   nu <- renMaybe (L.pNumber p) number@@ -461,14 +437,12 @@   ac <- ledgerAcct (L.pAccount p)   ta <- tags (L.pTags p)   me <- renMaybe (L.pMemo p) (postingMemo pad)-  let mayEn = case L.inferred ent of-        L.Inferred -> Nothing-        L.NotInferred -> (Just . L.entry $ ent)-  en <- renMaybe mayEn (entry gs (L.pSide p) (L.pSpaceBetween p))+  let mayEn = if L.inferred ent then Nothing else Just $ L.entry ent+  en <- renMaybe mayEn (entry maySpec (L.pSide p) (L.pSpaceBetween p))   return $ formatter pad fl nu pa ac ta en me -formatter ::-  Bool      -- ^ If True, emit four trailing spaces if no memo or+formatter+  :: Bool   -- ^ If True, emit four trailing spaces if no memo or             -- eight trailing spaces if there is a memo.   -> X.Text -- ^ Flag   -> X.Text -- ^ Number@@ -498,33 +472,41 @@ -- * Transaction  transaction-  :: GroupSpecs+  :: Maybe (L.Amount L.Qty -> S.S3 L.Radix L.PeriodGrp L.CommaGrp)+  -- ^ If Just, render entries that are NOT inferred and that do not+  -- have a QtyRep.  If Nothing, fail if an entry is NOT inferred and+  -- does not have a QtyRep.  (Inferred entries are always rendered+  -- without an entry.)   -> (L.TopLineCore, L.Ents L.PostingCore)   -> Maybe X.Text-transaction gs txn = do+transaction mayFmt txn = do   tlX <- topLine . fst $ txn   let (p1, p2, ps) = L.tupleEnts . snd $ txn-  p1X <- posting gs True p1-  p2X <- posting gs (not . null $ ps) p2+  p1X <- posting mayFmt True p1+  p2X <- posting mayFmt (not . null $ ps) p2   psX <- if null ps          then return X.empty          else let bs = replicate (length ps - 1) True ++ [False]               in fmap X.concat . sequence-                 $ zipWith (posting gs) bs ps+                 $ zipWith (posting mayFmt) bs ps   return $ X.concat [tlX, p1X, p2X, psX]  -- * Item  item-  :: GroupSpecs+  :: Maybe (L.Amount L.Qty -> S.S3 L.Radix L.PeriodGrp L.CommaGrp)+  -- ^ If Just, render entries that are NOT inferred and that do not+  -- have a QtyRep.  If Nothing, fail if an entry is NOT inferred and+  -- does not have a QtyRep.  (Inferred entries are always rendered+  -- without an entry.)   -> S.S4 (L.TopLineCore, L.Ents L.PostingCore)           L.PricePoint           I.Comment           I.BlankLine   -> Maybe X.Text-item gs =-  S.caseS4 (transaction gs)-           (price gs)+item mayFmt =+  S.caseS4 (transaction mayFmt)+           price            comment            (const (Just (X.pack "\n"))) 
lib/Penny/Liberty.hs view
@@ -82,7 +82,6 @@ import qualified Paths_penny_lib as PPL #endif import qualified Data.Version as V-import qualified System.Exit as Exit  -- | A multiline Text that holds an error message. type Error = Text@@ -134,7 +133,8 @@   -> [L.Transaction]   -- ^ The transactions to work on (probably parsed in from Copper) -  -> ([C.Chunk], [(LibertyMeta, L.Posting)])+  -> ( (L.Amount L.Qty -> X.Text) -> [C.Chunk]+     , [(LibertyMeta, L.Posting)])   -- ^ Sorted, filtered postings  xactionsToFiltered pdct postFilts srtr@@ -154,8 +154,15 @@   . processPostFilters postFilters   . addFilteredNum -mainFilter :: P.LPdct -> [L.Posting] -> ([C.Chunk], [L.Posting])-mainFilter pdct = E.verboseFilter L.display indentAmt False pdct+mainFilter+  :: P.LPdct+  -> [L.Posting]+  -> ((L.Amount L.Qty -> X.Text) -> [C.Chunk], [L.Posting])+mainFilter pdct pstgs = (getChks, ps')+  where+    ps' = E.filter pdct pstgs+    getChks fmt = fst $ E.verboseFilter (L.display fmt) indentAmt+                        False pdct pstgs   addFilteredNum :: [a] -> [(FilteredNum, a)]@@ -365,11 +372,9 @@     f a1 a2 = do       qt <- parseQty a2       parseComparer a1 (flip P.qty qt)-    parseQty a = case parse Pc.quantity "" (pack a) of-      Left e -> Ex.throw $ "could not parse quantity: "-        <> pack a <> " - "-        <> (pack . show $ e)-      Right g -> pure g+    parseQty a = case parse Pc.unquotedQtyRepWithSpaces "" (pack a) of+      Left _ -> Ex.throw $ "failed to parse quantity"+      Right g -> pure . L.toQty $ g   -- | Creates two options suitable for comparison of serial numbers,@@ -726,36 +731,30 @@     f a1 a2 = do       qt <- parseQty a2       parseComparer a1 (flip PS.qty qt)-    parseQty a = case parse Pc.quantity "" (pack a) of-      Left e -> Ex.throw $ "could not parse quantity: "-        <> pack a <> " - "-        <> (pack . show $ e)-      Right g -> pure g+    parseQty a = case parse Pc.unquotedQtyRepWithSpaces "" (pack a) of+      Left _ -> Ex.throw "could not parse quantity"+      Right g -> pure . L.toQty $ g  -- -- Versions -- --- | Parses the @--version@ option and returns an IO action that--- prints it and exits successfully. You supply the version of the--- executable, as there is no easy way to get that automatically.+-- | Prints the binary's version and the version of the library, and exits successfully.  version   :: V.Version-  -- ^ Version of binary-  -> OptSpec (IO a)-version v = C.OptSpec ["version"] [] (C.NoArg f)-  where-    f = do-      pn <- MA.getProgName-      putStrLn $ pn ++ " version " ++ V.showVersion v+  -> String+  -- ^ Program name+  -> String+version v pn = unlines+  [ pn ++ " version " ++ V.showVersion v #ifdef incabal-      putStrLn $ "using version " ++ V.showVersion PPL.version+  , "using version " ++ V.showVersion PPL.version #else-      putStrLn $ "using testing version"+  , "using testing version" #endif-                 ++ " of penny-lib"-      Exit.exitSuccess+    ++ " of penny-lib"+  ]  -- | An option for where the user would like to send output. output :: MA.OptSpec (X.Text -> IO ())
lib/Penny/Lincoln.hs view
@@ -45,8 +45,12 @@ -- Format: -- -- File LineNo Date Payee Acct DrCr Cmdty Qty-display :: Posting -> Text-display p = X.pack $ concat (intersperse " " ls)+display+  :: (Amount Qty -> X.Text)+  -- ^ How to format Qty that do not have a QtyRep+  -> Posting+  -> Text+display fmt p = X.pack $ concat (intersperse " " ls)   where     ls = [file, lineNo, dt, pye, acct, dc, cmdty, qt]     file = maybe (labelNo "filename") (X.unpack . unFilename)@@ -68,5 +72,6 @@       Debit -> "Dr"       Credit -> "Cr"     cmdty = X.unpack . unCommodity . Q.commodity $ p-    qt = prettyShowQty . Q.qty $ p+    getFmt q = fmt $ Amount q (Q.commodity p)+    qt = X.unpack . either showQtyRep getFmt . Q.eiQty $ p     labelNo s = "(no " ++ s ++ ")"
lib/Penny/Lincoln/Balance.hs view
@@ -32,7 +32,7 @@  -- | Returned by 'balanced'. data Balanced = Balanced-              | Inferable B.Entry+              | Inferable (B.Entry B.Qty)               | NotInferable               deriving (Show, Eq) @@ -45,13 +45,11 @@     Zero -> b     (NonZero col) -> case b of       Balanced -> let-        e = B.Entry dc a         dc = case colDrCr col of           B.Debit -> B.Credit           B.Credit -> B.Debit         q = colQty col-        a = B.Amount q c-        in Inferable e+        in Inferable (B.Entry dc (B.Amount q c))       _ -> NotInferable  isInferable :: Balanced -> Bool@@ -59,13 +57,13 @@ isInferable _ = False  -- | Converts an Entry to a Balance.-entryToBalance :: B.Entry -> Balance+entryToBalance :: B.HasQty q => B.Entry q -> Balance entryToBalance (B.Entry dc am) = Balance $ M.singleton c no where   c = B.commodity am-  no = NonZero (Column dc (B.qty am))+  no = NonZero (Column dc (B.toQty . B.qty $ am))  -- | Converts multiple Entries to a Balanced.-entriesToBalanced :: [B.Entry] -> Balanced+entriesToBalanced :: B.HasQty q => [B.Entry q] -> Balanced entriesToBalanced   = balanced   . mconcat
lib/Penny/Lincoln/Bits.hs view
@@ -1,5 +1,3 @@-{-# LANGUAGE DeriveGeneric #-}- -- | Essential data types used to make Transactions and Postings. module Penny.Lincoln.Bits   ( module Penny.Lincoln.Bits.Open@@ -33,17 +31,14 @@ import qualified Penny.Lincoln.Bits.Price as Pr import qualified Penny.Lincoln.Equivalent as Ev import Penny.Lincoln.Equivalent ((==~))-import qualified Data.Binary as B-import GHC.Generics (Generic)  data PricePoint = PricePoint { dateTime :: DT.DateTime                              , price :: Pr.Price                              , ppSide :: Maybe O.Side                              , ppSpaceBetween :: Maybe O.SpaceBetween                              , priceLine :: Maybe O.PriceLine }-                  deriving (Eq, Show, Generic)+                  deriving (Eq, Show) -instance B.Binary PricePoint  -- | PricePoint are equivalent if the dateTime and the Price are -- equivalent. Other elements of the PricePoint are ignored.@@ -59,13 +54,11 @@   { tlCore :: TopLineCore   , tlFileMeta :: Maybe TopLineFileMeta   , tlGlobal :: Maybe O.GlobalTransaction-  } deriving (Eq, Show, Generic)+  } deriving (Eq, Show)  emptyTopLineData :: DT.DateTime -> TopLineData emptyTopLineData dt = TopLineData (emptyTopLineCore dt) Nothing Nothing -instance B.Binary TopLineData- -- | Every TopLine has this data. data TopLineCore = TopLineCore   { tDateTime :: DT.DateTime@@ -73,7 +66,7 @@   , tFlag :: Maybe O.Flag   , tPayee :: Maybe O.Payee   , tMemo :: Maybe O.Memo-  } deriving (Eq, Show, Generic)+  } deriving (Eq, Show)  -- | TopLineCore are equivalent if their dates are equivalent and if -- everything else is equal.@@ -96,17 +89,13 @@ emptyTopLineCore :: DT.DateTime -> TopLineCore emptyTopLineCore dt = TopLineCore dt Nothing Nothing Nothing Nothing -instance B.Binary TopLineCore- -- | TopLines from files have this metadata. data TopLineFileMeta = TopLineFileMeta   { tFilename :: O.Filename   , tTopLineLine :: O.TopLineLine   , tTopMemoLine :: Maybe O.TopMemoLine   , tFileTransaction :: O.FileTransaction-  } deriving (Eq, Show, Generic)--instance B.Binary TopLineFileMeta+  } deriving (Eq, Show)   -- | All Postings have this data.@@ -119,7 +108,7 @@   , pMemo :: Maybe O.Memo   , pSide :: Maybe O.Side   , pSpaceBetween :: Maybe O.SpaceBetween-  } deriving (Eq, Show, Generic)+  } deriving (Eq, Show)  -- | Two PostingCore are equivalent if the Tags are equivalent and the -- other data is equal, exlucing the Side and the SpaceBetween, which are not considered at all.@@ -152,22 +141,19 @@   , pSpaceBetween = Nothing   } -instance B.Binary PostingCore- -- | Postings from files have this additional data. data PostingFileMeta = PostingFileMeta   { pPostingLine :: O.PostingLine   , pFilePosting :: O.FilePosting-  } deriving (Eq, Show, Generic)+  } deriving (Eq, Show) -instance B.Binary PostingFileMeta  -- | All the data that a Posting might have. data PostingData = PostingData   { pdCore :: PostingCore   , pdFileMeta :: Maybe PostingFileMeta   , pdGlobal :: Maybe O.GlobalPosting-  } deriving (Eq, Show, Generic)+  } deriving (Eq, Show)  emptyPostingData :: O.Account -> PostingData emptyPostingData ac = PostingData@@ -175,6 +161,4 @@   , pdFileMeta = Nothing   , pdGlobal = Nothing   }--instance B.Binary PostingData 
lib/Penny/Lincoln/Bits/DateTime.hs view
@@ -1,5 +1,3 @@-{-# LANGUAGE DeriveGeneric #-}- module Penny.Lincoln.Bits.DateTime   ( TimeZoneOffset ( offsetToMins )   , minsToOffset@@ -23,19 +21,13 @@   , showDateTime   ) where -import qualified Control.Monad as M import qualified Data.Time as T-import qualified Data.Binary as B-import Data.Binary (get, put)-import GHC.Generics (Generic) import qualified Penny.Lincoln.Equivalent as Ev  -- | The number of minutes that this timezone is offset from UTC. Can -- be positive, negative, or zero. newtype TimeZoneOffset = TimeZoneOffset { offsetToMins :: Int }-                         deriving (Eq, Ord, Show, Generic)--instance B.Binary TimeZoneOffset+                         deriving (Eq, Ord, Show)  -- | Convert minutes to a time zone offset. I'm having a hard time -- deciding whether to be liberal or strict in what to accept@@ -52,19 +44,13 @@ noOffset = TimeZoneOffset 0  newtype Hours = Hours { unHours :: Int }-                deriving (Eq, Ord, Show, Generic)--instance B.Binary Hours+                deriving (Eq, Ord, Show)  newtype Minutes = Minutes { unMinutes :: Int }-                  deriving (Eq, Ord, Show, Generic)--instance B.Binary Minutes+                  deriving (Eq, Ord, Show)  newtype Seconds = Seconds { unSeconds :: Int }-                  deriving (Eq, Ord, Show, Generic)--instance B.Binary Seconds+                  deriving (Eq, Ord, Show)  -- | succeeds if 0 <= x < 24 intToHours :: Int -> Maybe Hours@@ -109,12 +95,6 @@   , seconds :: Seconds   , timeZone :: TimeZoneOffset   } deriving (Eq, Ord, Show)--instance B.Binary DateTime where-  get = M.liftM5 DateTime (fmap T.ModifiedJulianDay B.get)-                 get get get get-  put (DateTime d h m s t) =-    put (T.toModifiedJulianDay d) >> put h >> put m >> put s >> put t  dateTimeMidnightUTC :: T.Day -> DateTime dateTimeMidnightUTC d = DateTime d h m s z
lib/Penny/Lincoln/Bits/Open.hs view
@@ -1,5 +1,3 @@-{-# LANGUAGE DeriveGeneric #-}- -- | These are the bits that are "open"; that is, their constructors -- are exported. This includes most bits. Some bits that have open -- constructors are not in this module because they include other bits@@ -11,34 +9,26 @@ import Data.Monoid ((<>)) import Data.Text (Text) import qualified Data.Text as X-import qualified Data.Text.Encoding as XE-import GHC.Generics (Generic) import qualified Penny.Lincoln.Equivalent as Ev import Penny.Lincoln.Equivalent ((==~)) import qualified Penny.Lincoln.Serial as S-import qualified Penny.Lincoln.Bits.Qty as Q-import qualified Data.Binary as B  newtype SubAccount =   SubAccount { unSubAccount :: Text }   deriving (Eq, Ord, Show) -instance B.Binary SubAccount where-  put = B.put . XE.encodeUtf8 . unSubAccount-  get = fmap (SubAccount . XE.decodeUtf8) B.get- newtype Account = Account { unAccount :: [SubAccount] }-                  deriving (Eq, Show, Ord, Generic)--instance B.Binary Account+                  deriving (Eq, Show, Ord) -data Amount = Amount { qty :: Q.Qty-                     , commodity :: Commodity }-              deriving (Eq, Show, Ord, Generic)+data Amount q = Amount+  { qty :: q+  , commodity :: Commodity }+  deriving (Eq, Show, Ord) -instance B.Binary Amount+instance Functor Amount where+  fmap f (Amount q c) = Amount (f q) c -instance Ev.Equivalent Amount where+instance Ev.Equivalent q => Ev.Equivalent (Amount q) where   equivalent (Amount q1 c1) (Amount q2 c2) =     q1 ==~ q2 && c1 == c2   compareEv (Amount q1 c1) (Amount q2 c2) =@@ -48,27 +38,24 @@   Commodity { unCommodity :: Text }   deriving (Eq, Ord, Show) -instance B.Binary Commodity where-  get = fmap (Commodity . XE.decodeUtf8) B.get-  put = B.put . XE.encodeUtf8 . unCommodity+data DrCr = Debit | Credit deriving (Eq, Show, Ord) -data DrCr = Debit | Credit deriving (Eq, Show, Ord, Generic) -instance B.Binary DrCr- -- | Debit returns Credit; Credit returns Debit opposite :: DrCr -> DrCr opposite d = case d of   Debit -> Credit   Credit -> Debit -data Entry = Entry { drCr :: DrCr-                   , amount :: Amount }-             deriving (Eq, Show, Ord, Generic)+data Entry q = Entry+  { drCr :: DrCr+  , amount :: Amount q }+  deriving (Eq, Show, Ord) -instance B.Binary Entry+instance Functor Entry where+  fmap f (Entry d a) = Entry d (fmap f a) -instance Ev.Equivalent Entry where+instance Ev.Equivalent q => Ev.Equivalent (Entry q) where   equivalent (Entry d1 a1) (Entry d2 a2) =     d1 == d2 && a1 ==~ a2   compareEv (Entry d1 a1) (Entry d2 a2) =@@ -77,44 +64,23 @@ newtype Flag = Flag { unFlag :: Text }              deriving (Eq, Show, Ord) -instance B.Binary Flag where-  get = fmap (Flag . XE.decodeUtf8) B.get-  put = B.put . XE.encodeUtf8 . unFlag- -- | There is one item in the list for each line of the memo. Do not -- include newlines in the texts themselves. However there is nothing -- to enforce this convention. newtype Memo = Memo { unMemo :: [Text] }              deriving (Eq, Show, Ord) -instance B.Binary Memo where-  get = fmap (Memo . map XE.decodeUtf8) B.get-  put = B.put . map XE.encodeUtf8 . unMemo- newtype Number = Number { unNumber :: Text }                  deriving (Eq, Show, Ord) -instance B.Binary Number where-  get = fmap (Number . XE.decodeUtf8) B.get-  put = B.put . XE.encodeUtf8 . unNumber-- newtype Payee = Payee { unPayee :: Text }               deriving (Eq, Show, Ord) -instance B.Binary Payee where-  get = fmap (Payee . XE.decodeUtf8) B.get-  put = B.put . XE.encodeUtf8 . unPayee- newtype Tag = Tag { unTag :: Text }                   deriving (Eq, Show, Ord) -instance B.Binary Tag where-  get = fmap (Tag . XE.decodeUtf8) B.get-  put = B.put . XE.encodeUtf8 . unTag- newtype Tags = Tags { unTags :: [Tag] }-               deriving (Eq, Show, Ord, Generic)+               deriving (Eq, Show, Ord)  -- | Tags are equivalent if they have the same tags (even if in a -- different order).@@ -123,22 +89,16 @@   compareEv (Tags t1) (Tags t2) =     compare (sort t1) (sort t2) -instance B.Binary Tags- -- Metadata  -- | The line number that the TopLine starts on (excluding the memo -- accompanying the TopLine). newtype TopLineLine = TopLineLine { unTopLineLine :: Int }-                      deriving (Eq, Show, Generic)--instance B.Binary TopLineLine+                      deriving (Eq, Show)  -- | The line number that the memo accompanying the TopLine starts on. newtype TopMemoLine = TopMemoLine { unTopMemoLine :: Int }-                      deriving (Eq, Show, Generic)--instance B.Binary TopMemoLine+                      deriving (Eq, Show)  -- | The commodity and and the quantity may appear with the commodity -- on the left (e.g. USD 2.14) or with the commodity on the right@@ -146,69 +106,57 @@ data Side   = CommodityOnLeft   | CommodityOnRight-  deriving (Eq, Show, Ord, Generic)+  deriving (Eq, Show, Ord) -instance B.Binary Side+instance Ev.Equivalent Side where+  equivalent = (==)+  compareEv = compare  -- | There may or may not be a space in between the commodity and the -- quantity. data SpaceBetween   = SpaceBetween   | NoSpaceBetween-  deriving (Eq, Show, Ord, Generic)+  deriving (Eq, Show, Ord) -instance B.Binary SpaceBetween +instance Ev.Equivalent SpaceBetween where+  equivalent = (==)+  compareEv = compare+ -- | The name of the file in which a transaction appears. newtype Filename = Filename { unFilename :: X.Text }                    deriving (Eq, Show) -instance B.Binary Filename where-  get = fmap (Filename . XE.decodeUtf8) B.get-  put = B.put . XE.encodeUtf8 . unFilename-- -- | The line number on which a price appears. newtype PriceLine = PriceLine { unPriceLine :: Int }-                    deriving (Eq, Show, Generic)--instance B.Binary PriceLine+                    deriving (Eq, Show)  -- | The line number on which a posting appears. newtype PostingLine = PostingLine { unPostingLine :: Int }-                      deriving (Eq, Show, Generic)--instance B.Binary PostingLine+                      deriving (Eq, Show)  -- | All postings are numbered in order, beginning with the first -- posting in the first file and ending with the last posting -- in the last file. newtype GlobalPosting =   GlobalPosting { unGlobalPosting :: S.Serial }-  deriving (Eq, Show, Generic)--instance B.Binary GlobalPosting+  deriving (Eq, Show)  -- | The postings in each file are numbered in order. newtype FilePosting =   FilePosting { unFilePosting :: S.Serial }-  deriving (Eq, Show, Generic)--instance B.Binary FilePosting+  deriving (Eq, Show)  -- | All transactions are numbered in order, beginning with the first -- transaction in the first file and ending with the last transaction -- in the last file. newtype GlobalTransaction =   GlobalTransaction { unGlobalTransaction :: S.Serial }-  deriving (Eq, Show, Generic)--instance B.Binary GlobalTransaction+  deriving (Eq, Show)  -- | The transactions in each file are numbered in order. newtype FileTransaction =   FileTransaction { unFileTransaction :: S.Serial }-  deriving (Eq, Show, Generic)--instance B.Binary FileTransaction+  deriving (Eq, Show) 
lib/Penny/Lincoln/Bits/Price.hs view
@@ -1,5 +1,3 @@-{-# LANGUAGE DeriveGeneric, GeneralizedNewtypeDeriving #-}- module Penny.Lincoln.Bits.Price (     From ( From, unFrom )   , To ( To, unTo )@@ -12,35 +10,26 @@ import qualified Penny.Lincoln.Equivalent as Ev import Penny.Lincoln.Equivalent ((==~)) import qualified Penny.Lincoln.Bits.Open as O-import Penny.Lincoln.Bits.Qty (Qty)-import GHC.Generics (Generic)-import qualified Data.Binary as B+import Penny.Lincoln.Bits.Qty (QtyRep) -newtype From = From { unFrom :: O.Commodity }-  deriving (Eq, Ord, Show, Generic) -instance B.Binary From+newtype From = From { unFrom :: O.Commodity }+  deriving (Eq, Ord, Show)  newtype To = To { unTo :: O.Commodity }-  deriving (Eq, Ord, Show, Generic)--instance B.Binary To+  deriving (Eq, Ord, Show) -newtype CountPerUnit = CountPerUnit { unCountPerUnit :: Qty }-  deriving (Eq, Ord, Show, Generic)+newtype CountPerUnit = CountPerUnit { unCountPerUnit :: QtyRep }+  deriving (Eq, Ord, Show)  instance Ev.Equivalent CountPerUnit where   equivalent (CountPerUnit x) (CountPerUnit y) = x ==~ y   compareEv (CountPerUnit x) (CountPerUnit y) = Ev.compareEv x y -instance B.Binary CountPerUnit- data Price = Price { from :: From                    , to :: To                    , countPerUnit :: CountPerUnit }-             deriving (Eq, Ord, Show, Generic)--instance B.Binary Price+             deriving (Eq, Ord, Show)  -- | Two Price are equivalent if the From and To are equal and the -- CountPerUnit is equivalent.
lib/Penny/Lincoln/Bits/Qty.hs view
@@ -1,17 +1,53 @@-{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE OverloadedStrings #-} -- | Penny quantities. A quantity is simply a count (possibly -- fractional) of something. It does not have a commodity or a -- Debit/Credit. module Penny.Lincoln.Bits.Qty-  ( Qty-  , NumberStr(..)-  , toQty-  , mantissa+  (+  -- * Quantity representations+  -- ** Components of quantity representations+    Digit(..)+  , DigitList(..)+  , Digits(..)+  , Grouper(..)+  , PeriodGrp(..)+  , CommaGrp(..)+  , GroupedDigits(..)++  , WholeFrac+  , whole+  , frac+  , wholeFrac+  , wholeOrFrac+  , WholeOrFracResult+  , wholeOrFracToQtyRep++  , WholeOnly+  , unWholeOnly+  , wholeOnly++  , WholeOrFrac(..)+  , Radix(..)+  , showRadix+  , QtyRep(..)++  -- ** Converting between quantity representations and quantities+  , qtyToRep+  , qtyToRepNoGrouping+  , qtyToRepGrouped++  -- ** Rendering quantity representations+  , showQtyRep+  , bestRadGroup++  -- * Other stuff+  , Qty+  , HasQty(..)+  , signif   , places-  , prettyShowQty   , compareQty   , newQty-  , Mantissa+  , Signif   , Places   , add   , mult@@ -25,46 +61,466 @@   , qtyOne   ) where +-- # Imports++import Control.Applicative ((<|>)) import qualified Control.Monad.Exception.Synchronous as Ex-import qualified Data.Binary as B-import GHC.Generics (Generic)-import Data.List (genericLength, genericReplicate, genericSplitAt, sortBy)-import Data.Ord (comparing)+import Data.Text (Text)+import qualified Data.Text as X+import Data.Ord(Down(..), comparing)+import Data.List ( genericLength, genericReplicate, sortBy, group, sort,+                   genericSplitAt )+import Data.List.Split (chunksOf)+import Data.List.NonEmpty (NonEmpty((:|)), toList, nonEmpty)+import qualified Data.List.NonEmpty as NE+import Data.Maybe (fromMaybe, listToMaybe, mapMaybe)+import qualified Data.Semigroup(Semigroup(..))+import Data.Semigroup(sconcat)+import Data.Monoid ((<>)) import qualified Penny.Lincoln.Equivalent as Ev+import Penny.Lincoln.Equivalent (Equivalent(..))+import qualified Penny.Steel.Sums as S -data NumberStr =-  Whole String-  -- ^ A whole number only. No radix point.-  | WholeRad String-    -- ^ A whole number and a radix point, but nothing after the radix-    -- point.-  | WholeRadFrac String String-    -- ^ A whole number and something after the radix point.-  | RadFrac String-    -- ^ A radix point and a fractional value after it, but nothing-    -- before the radix point.-  deriving Show+data Digit = D0 | D1 | D2 | D3 | D4 | D5 | D6 | D7 | D8 | D9+  deriving (Eq, Ord, Show, Enum, Bounded) +-- | The digit grouping character when the radix is a period.+data PeriodGrp+  = PGSpace+  -- ^ ASCII space+  | PGThinSpace+  -- ^ Unicode code point 0x2009+  | PGComma+  -- ^ Comma+  deriving (Eq, Show, Ord, Enum, Bounded) --- | Converts strings to Qty. Fails if any of the strings have--- non-digits, or if any are negative, or if the result is not greater--- than zero, or if the strings are empty.-toQty :: NumberStr -> Maybe Qty-toQty ns = case ns of-  Whole s -> fmap (\m -> Qty m 0) (readInteger s)-  WholeRad s -> fmap (\m -> Qty m 0) (readInteger s)-  WholeRadFrac w f -> fromWholeRadFrac w f-  RadFrac f -> fromWholeRadFrac "0" f+-- | The digit grouping character when the radix is a comma.+data CommaGrp+  = CGSpace+  -- ^ ASCII space+  | CGThinSpace+  -- ^ Unicode code point 0x2009+  | CGPeriod+  -- ^ Period+  deriving (Eq, Show, Ord, Enum, Bounded)++class Grouper a where+  groupChar :: a -> Char++instance Grouper PeriodGrp where+  groupChar c = case c of+    PGSpace -> ' '+    PGThinSpace -> '\x2009'+    PGComma -> ','++instance Grouper CommaGrp where+  groupChar c = case c of+    CGSpace -> ' '+    CGThinSpace -> '\x2009'+    CGPeriod -> '.'++newtype DigitList = DigitList { unDigitList :: NonEmpty Digit }+  deriving (Eq, Show, Ord)++instance Data.Semigroup.Semigroup DigitList where+  (<>) (DigitList l1) (DigitList l2) =+    DigitList $ l1 Data.Semigroup.<> l2++class Digits a where+  digits :: a -> DigitList++instance Digits DigitList where+  digits = id++instance Digits (GroupedDigits a) where+  digits (GroupedDigits d1 dr) = sconcat (d1 :| map snd dr)++-- | All of the digits on a single side of a radix point. Typically+-- this is parameterized on a type that represents the grouping+-- character.+data GroupedDigits a = GroupedDigits+  { dsFirstPart :: DigitList+  -- ^ The first chunk of digits+  , dsNextParts :: [(a, DigitList)]+  -- ^ Optional subsequent chunks of digits. Each is a grouping+  -- character followed by additional digits.+  } deriving (Eq, Show, Ord)+++-- | A quantity representation that has both a whole number and a+-- fractional part. Abstract because there must be a non-zero digit in+-- here somewhere, which 'wholeFrac' checks for.  Typically this is+-- parameterized on an instance of the Digits class, such as DigitList+-- or GroupedDigits.  This allows separate types for values that+-- cannot be grouped as well as those that can.+data WholeFrac a = WholeFrac+  { whole :: a+  , frac :: a+  } deriving (Eq, Show, Ord)++wholeFrac+  :: Digits a+  => a+  -- ^ Whole part+  -> a+  -- ^ Fractional part+  -> Maybe (WholeFrac a)+  -- ^ If there is no non-zero digit present, Nothing. Otherwise,+  -- returns the appropriate WholeFrac.+wholeFrac w f = if digitsHasNonZero w || digitsHasNonZero f+  then (Just (WholeFrac w f)) else Nothing++digitsHasNonZero :: Digits a => a -> Bool+digitsHasNonZero = any (/= D0) . toList . unDigitList . digits++-- | A quantity representation that has a whole part only. Abstract+-- because there must be a non-zero digit in here somewhere, which+-- 'wholeOnly' checks for.  Typically this is parameterized on an+-- instance of the Digits class, such as DigitList or GroupedDigits.+newtype WholeOnly a = WholeOnly { unWholeOnly :: a }+  deriving (Eq, Show, Ord)++wholeOnly :: Digits a => a -> Maybe (WholeOnly a)+wholeOnly d = if digitsHasNonZero d then Just (WholeOnly d) else Nothing++-- | Typically this is parameterized on an instance of the Digits+-- class, such as DigitList or GroupedDigits.+newtype WholeOrFrac a = WholeOrFrac+  { unWholeOrFrac :: Either (WholeOnly a) (WholeFrac a) }+  deriving (Eq, Show, Ord)++wholeOrFrac+  :: GroupedDigits a+  -- ^ What's before the radix point++  -> Maybe (GroupedDigits a)+  -- ^ What's after the radix point (if anything)++  -> Maybe (Either (WholeOrFrac DigitList)+                   (WholeOrFrac (GroupedDigits a)))+wholeOrFrac g@(GroupedDigits l1 lr) mayAft = case mayAft of+  Nothing -> case lr of+    [] -> fmap (Left . WholeOrFrac . Left) $ wholeOnly l1+    _ -> fmap (Right . WholeOrFrac . Left) $ wholeOnly g+  Just aft@(GroupedDigits r1 rr) -> case (lr, rr) of+    ([], []) -> fmap (Left . WholeOrFrac . Right) $ wholeFrac l1 r1+    _ -> fmap (Right . WholeOrFrac . Right) $ wholeFrac g aft+++data Radix = Period | Comma+  deriving (Eq, Show, Ord, Enum, Bounded)++type WholeOrFracResult a = Either (WholeOrFrac DigitList)+                                  (WholeOrFrac (GroupedDigits a))++wholeOrFracToQtyRep+  :: Either (WholeOrFracResult PeriodGrp) (WholeOrFracResult CommaGrp)+  -> QtyRep+wholeOrFracToQtyRep e = case e of+  Left p -> case p of+    Left dl -> QNoGrouping dl Period+    Right gd -> QGrouped (Left gd)+  Right c -> case c of+    Left dl -> QNoGrouping dl Comma+    Right gd -> QGrouped (Right gd)++data QtyRep+  = QNoGrouping (WholeOrFrac DigitList) Radix+  | QGrouped (Either (WholeOrFrac (GroupedDigits PeriodGrp))+                     (WholeOrFrac (GroupedDigits CommaGrp)))+  deriving (Eq, Show, Ord)++instance Equivalent QtyRep where+  equivalent x y = showQtyRep x == showQtyRep y+  compareEv x y = Ev.compareEv (toQty x) (toQty y)++-- | Converts an Integer to a list of digits.+intToDigits :: Integer -> NonEmpty Digit+intToDigits+  = fmap intToDigit+  . fromMaybe (error "intToDigits: show made empty list")+  . nonEmpty+  . show   where-    fromWholeRadFrac w f =-      fmap (\m -> Qty m (genericLength f)) (readInteger (w ++ f))+    intToDigit c = case c of+      { '0' -> D0; '1' -> D1; '2' -> D2; '3' -> D3; '4' -> D4;+        '5' -> D5; '6' -> D6; '7' -> D7; '8' -> D8; '9' -> D9;+        _ -> error "intToDigits: show made non-digit character" } --- | Reads non-negative integers only.-readInteger :: String -> Maybe Integer-readInteger s = case reads s of-  (i, ""):[] -> if i < 0 then Nothing else Just i-  _ -> Nothing+prependNonEmpty :: [a] -> NonEmpty a -> NonEmpty a+prependNonEmpty [] x = x+prependNonEmpty (x:xs) (y1 :| ys) = x :| (xs ++ (y1 : ys)) +qtyToRepNoGrouping :: Qty -> WholeOrFrac DigitList+qtyToRepNoGrouping q =+  let sig = intToDigits . signif $ q+      e = places q+      len = genericLength . toList $ sig+  in WholeOrFrac $ if e == 0+     then Left (WholeOnly (DigitList sig))+     else if e < len+          then let prefixLen = len - e+                   (pfx, sfx) = genericSplitAt prefixLen . toList $ sig+                   ne = fromMaybe+                        (error "qtyToRepNoGrouping: nonEmpty failed")+                        . NE.nonEmpty+                   (pfxNE, sfxNE) = (ne pfx, ne sfx)+                   (w, f) = (DigitList pfxNE, DigitList sfxNE)+               in Right (WholeFrac w f)+          else let leadZeroes = genericReplicate (e - len) D0+                   w = DigitList $ D0 :| []+                   f = DigitList $ prependNonEmpty leadZeroes sig+          in Right (WholeFrac w f)++++-- | Given a list of QtyRep, determine the most common radix and+-- grouping that are used.  If a single QtyRep is grouped, then the+-- result is also grouped.  The most common grouping character+-- determines which grouping character is used.+--+-- If no QtyRep are grouped, then the most common radix point is used+-- and the result is not grouped.+--+-- If there is no radix point found, returns Nothing.+bestRadGroup+  :: [QtyRep]+  -> Maybe (S.S3 Radix PeriodGrp CommaGrp)+bestRadGroup ls = fromGrouping <|> fromRadix+  where+    grpToRadix q = case q of+      QNoGrouping _ _ -> Nothing+      QGrouped e -> Just $ either (const Period) (const Comma) e+    mostCommonGrpRad = mode . mapMaybe grpToRadix $ ls+    fromGrouping = do+      rad <- mostCommonGrpRad+      case rad of+        Period -> fmap S.S3b . mostCommonPeriodGrp $ ls+        Comma -> fmap S.S3c . mostCommonCommaGrp $ ls+    fromRadix = fmap S.S3a . mode . mapMaybe noGrpToRadix $ ls+    noGrpToRadix q = case q of+      QNoGrouping _ r -> Just r+      _ -> Nothing++mostCommonPeriodGrp :: [QtyRep] -> Maybe PeriodGrp+mostCommonPeriodGrp+  = mode+  . concatMap f+  where+    f q = case q of+      QNoGrouping _ _ -> []+      QGrouped e -> case e of+        Left (WholeOrFrac ei) -> case ei of+          Left _ -> []+          Right (WholeFrac g1 g2) -> getSeps g1 ++ getSeps g2+        Right _ -> []++mostCommonCommaGrp :: [QtyRep] -> Maybe CommaGrp+mostCommonCommaGrp+  = mode+  . concatMap f+  where+    f q = case q of+      QNoGrouping _ _ -> []+      QGrouped e -> case e of+        Left _ -> []+        Right (WholeOrFrac ei) -> case ei of+          Left _ -> []+          Right (WholeFrac g1 g2) -> getSeps g1 ++ getSeps g2++getSeps :: GroupedDigits a -> [a]+getSeps (GroupedDigits _ ls) = map fst ls+++mode :: Ord a => [a] -> Maybe a+mode = listToMaybe . modes++modes :: Ord a => [a] -> [a]+modes+  = map (head . snd)+  . sortBy (comparing (Down . fst))+  . map (\ls -> (length ls, ls))+  . group+  . sort++-- | Groups digits, using the given split character.+groupDigits+  :: NonEmpty a+  -> (NonEmpty a, [NonEmpty a])+  -- ^ The first group of digits, and any subsequent groups.+groupDigits+  = toPair+  . reverse+  . map reverse+  . chunksOf 3+  . reverse+  . toList+  where+    toPair [] = error "groupDigits: chunksOf produced empty list"+    toPair (x:xs) = (ne x, map ne xs)+    ne = fromMaybe (error $ "groupDigits: chunksOf produced"+                            ++ " empty inner list") . nonEmpty++-- Digit grouping.  Here are the rules.+--+-- No digits to the right of the decimal point are ever grouped.  For+-- now I will consider this a rare enough case that I will not bother+-- with it.+--+-- Digits to the left of the decimal point are grouped as follows:+--+-- No grouping is performed unless the entire number (including the+-- fractional portion) is at least five digits long.  That means that+-- 1234.5 is grouped into 1,234.5 but 1234 is not grouped.+--+-- Grouping is performed on the whole part only, and digits are+-- grouped every third place.++qtyToRepGrouped :: g -> Qty -> WholeOrFrac (GroupedDigits g)+qtyToRepGrouped g q = WholeOrFrac+  $ case unWholeOrFrac $ qtyToRepNoGrouping q of+    Left (WholeOnly (DigitList ds)) ->+      Left $ WholeOnly (mkWholeGroups ds)+    Right (WholeFrac w f) ->+      Right $ mkWholeFracGroups (unDigitList w) (unDigitList f)+    where+      mkGroups ds =+        let (g1, gs) = groupDigits ds+            mkGrp dl = (g, DigitList dl)+        in GroupedDigits (DigitList g1) (map mkGrp gs)+      mkWholeGroups ds = if (length . toList $ ds) > maxUngrouped+                         then mkGroups ds+                         else GroupedDigits (DigitList ds) []+      mkWholeFracGroups w f = WholeFrac w' f'+        where+          f' = GroupedDigits (DigitList f) []+          w' = if (length . toList $ w) + (length . toList $ f)+                    > maxUngrouped+               then mkGroups w+               else GroupedDigits (DigitList w) []+      maxUngrouped = 4++qtyToRep+  :: S.S3 Radix PeriodGrp CommaGrp+  -> Qty+  -> QtyRep+qtyToRep x q = case x of+  S.S3a r -> QNoGrouping (qtyToRepNoGrouping q) r+  S.S3b g -> QGrouped . Left $ qtyToRepGrouped g q+  S.S3c g -> QGrouped . Right $ qtyToRepGrouped g q++class HasQty a where+  toQty :: a -> Qty++++digitToInt :: Digit -> Integer+digitToInt d = case d of+  { D0 -> 0; D1 -> 1; D2 -> 2; D3 -> 3; D4 -> 4; D5 -> 5;+    D6 -> 6; D7 -> 7; D8 -> 8; D9 -> 9 }++digitsToInt :: DigitList -> Integer+digitsToInt+  = sum+  . map (\(e, s) -> s * 10 ^ e)+  . zip ([0..] :: [Integer])+  . map digitToInt+  . reverse+  . toList+  . unDigitList++instance HasQty QtyRep where+  toQty q = case q of+    QNoGrouping (WholeOrFrac ei) _ -> case ei of+      Left (WholeOnly ds) -> Qty (digitsToInt ds) 0+      Right (WholeFrac w f) -> Qty sig ex+        where+          sig = digitsToInt ((Data.Semigroup.<>) w f)+          ex = genericLength . toList . unDigitList $ f+    QGrouped ei -> either groupedToQty groupedToQty ei++groupedToQty :: WholeOrFrac (GroupedDigits a) -> Qty+groupedToQty (WholeOrFrac ei) = case ei of+  Left (WholeOnly g) -> Qty (digitsToInt . digits $ g) 0+  Right (WholeFrac w f) -> Qty sig ex+    where+      sig = digitsToInt ((Data.Semigroup.<>) (digits w) (digits f))+      ex = genericLength . toList . unDigitList . digits $ f++instance HasQty Qty where+  toQty = id++showDigit :: Digit -> Text+showDigit d = case d of+  { D0 -> "0"; D1 -> "1"; D2 -> "2"; D3 -> "3"; D4 -> "4";+    D5 -> "5"; D6 -> "6"; D7 -> "7"; D8 -> "8"; D9 -> "9" }++showRadix :: Radix -> Text+showRadix r = case r of { Comma -> ","; Period -> "." }++showDigitList :: DigitList -> X.Text+showDigitList = X.concat . toList . fmap showDigit . unDigitList++showGroupedDigits+  :: Grouper a+  => GroupedDigits a+  -> Text+showGroupedDigits (GroupedDigits d ds)+  = showDigitList d <> (X.concat . map f $ ds)+  where+    f (c, cs) = (X.singleton $ groupChar c) <> showDigitList cs++showWholeOnlyDigitList :: WholeOnly DigitList -> Text+showWholeOnlyDigitList = showDigitList . unWholeOnly++showWholeOnlyGroupedDigits+  :: Grouper a+  => WholeOnly (GroupedDigits a)+  -> Text+showWholeOnlyGroupedDigits = showGroupedDigits . unWholeOnly++showWholeFracDigitList+  :: Radix+  -> WholeFrac DigitList+  -> Text+showWholeFracDigitList r wf+  = showDigitList (whole wf) <> showRadix r <> showDigitList (frac wf)++showWholeFracGroupedDigits+  :: Grouper a+  => Radix+  -> WholeFrac (GroupedDigits a)+  -> Text+showWholeFracGroupedDigits r wf+  = showGroupedDigits (whole wf) <> showRadix r+  <> showGroupedDigits (frac wf)++wholeOrFracGrouped+  :: Grouper a+  => Radix+  -> WholeOrFrac (GroupedDigits a)+  -> Text+wholeOrFracGrouped r+  = either showWholeOnlyGroupedDigits (showWholeFracGroupedDigits r)+  . unWholeOrFrac++wholeOrFracDigitList+  :: Radix+  -> WholeOrFrac DigitList+  -> Text+wholeOrFracDigitList r+  = either showWholeOnlyDigitList (showWholeFracDigitList r)+  . unWholeOrFrac+++showQtyRep :: QtyRep -> Text+showQtyRep q = case q of+  QNoGrouping wf r -> wholeOrFracDigitList r wf+  QGrouped ei ->+    either (wholeOrFracGrouped Period)+           (wholeOrFracGrouped Comma) ei++ -- | A quantity is always greater than zero. Various odd questions -- happen if quantities can be zero. For instance, what if you have a -- debit whose quantity is zero? Does it require a balancing credit@@ -82,31 +538,14 @@ -- /WARNING/ - before doing comparisons or equality tests -- -- The Eq instance is derived. Therefore q1 == q2 only if q1 and q2--- have both the same mantissa and the same exponent. You may instead+-- have both the same significand and the same exponent. You may instead -- want 'equivalent'. Similarly, the Ord instance is derived. It--- compares based on the integral value of the mantissa and of the+-- compares based on the integral value of the significand and of the -- exponent. You may instead want 'compareQty', which compares after -- equalizing the exponents.-data Qty = Qty { mantissa :: !Integer+data Qty = Qty { signif :: !Integer                , places :: !Integer-               } deriving (Eq, Generic, Show, Ord)---- | Shows a quantity, nicely formatted after accounting for both the--- mantissa and decimal places, e.g. @0.232@ or @232.12@ or whatever.-prettyShowQty :: Qty -> String-prettyShowQty q =-  let man = show . mantissa $ q-      e = places q-      len = genericLength man-      small = "0." ++ ((genericReplicate (e - len) '0') ++ man)-  in case compare e len of-      GT -> small-      EQ -> small-      LT ->-        let (b, end) = genericSplitAt (len - e) man-        in if e == 0-           then man-           else b ++ ['.'] ++ end+               } deriving (Eq, Show, Ord)  instance Ev.Equivalent Qty where   equivalent x y = x' == y'@@ -116,30 +555,25 @@     where       (x', y') = equalizeExponents x y -instance B.Binary Qty--type Mantissa = Integer+type Signif = Integer type Places = Integer --- | Mantissa 1, exponent 0+-- | Significand 1, exponent 0 qtyOne :: Qty qtyOne = Qty 1 0  ---newQty :: Mantissa -> Places -> Maybe Qty+newQty :: Signif -> Places -> Maybe Qty newQty m p   | m > 0  && p >= 0 = Just $ Qty m p   | otherwise = Nothing  - -- | Compares Qty after equalizing their exponents. -- -- > compareQty (newQty 15 1) (newQty 1500 3) == EQ compareQty :: Qty -> Qty -> Ordering-compareQty q1 q2 = compare (mantissa q1') (mantissa q2')+compareQty q1 q2 = compare (signif q1') (signif q2')   where     (q1', q2') = equalizeExponents q1 q2 @@ -183,7 +617,7 @@ difference :: Qty -> Qty -> Difference difference x y =   let (x', y') = equalizeExponents x y-      (mx, my) = (mantissa x', mantissa y')+      (mx, my) = (signif x', signif y')   in case compare mx my of     GT -> LeftBiggerBy (Qty (mx - my) (places x'))     LT -> RightBiggerBy (Qty (my - mx) (places x'))@@ -208,7 +642,7 @@ -- Adjust all exponents, both on the amount to be allocated and on all -- the votes, so that the exponents are all equal. ----- Allocate the mantissas.+-- Allocate the significands. -- -- Return the quantities with the original exponents. @@ -236,8 +670,8 @@ allocate' tot ls =   let ((tot':ls'), e) = sameExponent (tot:ls)       (moreE, (_, ss)) =-        multRemainderAllResultsAtLeast1 (mantissa tot')-        (map mantissa ls')+        multRemainderAllResultsAtLeast1 (signif tot')+        (map signif ls')       totE = e + moreE   in map (\m -> Qty m totE) ss 
lib/Penny/Lincoln/Ents.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE DeriveGeneric, DeriveFunctor #-}+{-# LANGUAGE DeriveFunctor #-}  -- | Containers for entries. --@@ -29,11 +29,10 @@  module Penny.Lincoln.Ents   ( -- * Ent-    Inferred(..)-  , Ent+    Ent   , entry-  , inferred   , meta+  , inferred    -- * Ents   , Ents@@ -56,8 +55,6 @@  import Control.Applicative import Control.Arrow (second)-import Data.Binary (Binary)-import GHC.Generics (Generic) import qualified Penny.Lincoln.Bits as B import qualified Penny.Lincoln.Balance as Bal import Control.Monad (guard)@@ -69,41 +66,36 @@ import qualified Data.Traversable as Tr import qualified Data.Foldable as Fdbl --- | An Ent is inferred if the user did not supply an entry for it and--- Penny was able to infer the correct entry. Otherwise it is not--- inferred.-data Inferred = Inferred | NotInferred-  deriving (Eq, Ord, Show, Generic) -instance Binary Inferred- -- | Information about an entry, along with whether it is inferred and -- its metadata. data Ent m = Ent-  { entry :: B.Entry-  -- ^ The entry from an Ent-  , inferred :: Inferred-  -- ^ Whether the entry was inferred+  { entry :: Either (B.Entry B.QtyRep) (B.Entry B.Qty)+  -- ^ The entry from an Ent.  If the Ent is inferred--that is, if the+  -- user did not supply an entry for it and Penny was able to infer+  -- the entry--this will be a Right with the inferred Entry.+   , meta :: m   -- ^ The metadata accompanying an Ent-  } deriving (Eq, Ord, Show, Generic) +  , inferred :: Bool+  -- ^ True if the entry was inferred.+  } deriving (Eq, Ord, Show)+ -- | Two Ents are equivalent if the entries are equivalent and the -- metadata is equivalent (whether the Ent is inferred or not is -- ignored.) instance Ev.Equivalent m => Ev.Equivalent (Ent m) where-  equivalent (Ent e1 _ m1) (Ent e2 _ m2) =-    e1 ==~ e2 && m1 ==~ m2-  compareEv (Ent e1 _ m1) (Ent e2 _ m2) =+  equivalent (Ent e1 m1 _) (Ent e2 m2 _) = e1 ==~ e2 && m1 ==~ m2++  compareEv (Ent e1 m1 _) (Ent e2 m2 _) =     Ev.compareEv e1 e2 <> Ev.compareEv m1 m2  instance Functor Ent where-  fmap f (Ent e i m) = Ent e i (f m)--instance Binary m => Binary (Ent m)+  fmap f (Ent e m i) = Ent e (f m) i  newtype Ents m = Ents { unEnts :: [Ent m] }-  deriving (Eq, Ord, Show, Generic, Functor)+  deriving (Eq, Ord, Show, Functor)  -- | Ents are equivalent if the content Ents of each are -- equivalent. The order of the ents is insignificant.@@ -139,10 +131,10 @@ -- instance. traverseEnts :: Applicative f => (Ent a -> f b) -> Ents a -> f (Ents b) traverseEnts f = fmap Ents . Tr.traverse f' . unEnts where-  f' en@(Ent e i _) = Ent <$> pure e <*> pure i <*> f en+  f' en@(Ent e _ i) = Ent <$> pure e <*> f en <*> pure i  seqEnt :: Applicative f => Ent (f a) -> f (Ent a)-seqEnt (Ent e i m) = Ent <$> pure e <*> pure i <*> m+seqEnt (Ent e m i) = Ent <$> pure e <*> m <*> pure i  -- | Every Ents alwas contains at least two ents, and possibly -- additional ones.@@ -151,8 +143,6 @@   t1:t2:ts -> (t1, t2, ts)   _ -> error "tupleEnts: ents does not have two ents" -instance Binary m => Binary (Ents m)- -- | In a Posting, the Ent at the front of the list of Ents is the -- main posting. There are additional postings. This function -- rearranges the Ents multiple times so that each posting is at the@@ -216,23 +206,27 @@ -- and this function will infer the remaining Entry. This function -- fails if it cannot create a balanced Ents. ents-  :: [(Maybe B.Entry, m)]+  :: [(Maybe (Either (B.Entry B.QtyRep) (B.Entry B.Qty)), m)]   -> Maybe (Ents m) ents ls = do   guard . not . null $ ls   let nNoEntries = length . filter (== Nothing) . map fst $ ls-  case Bal.entriesToBalanced . catMaybes . map fst $ ls of+  case Bal.entriesToBalanced+       . map (either (fmap B.toQty) id)+       . catMaybes+       . map fst+       $ ls of     Bal.NotInferable -> Nothing     Bal.Inferable e -> do       guard $ nNoEntries == 1       let makeEnt (mayEn, mt) = case mayEn of-            Nothing -> Ent e Inferred mt-            Just en -> Ent en NotInferred mt+            Nothing -> Ent (Right e) mt True+            Just en -> Ent en mt False       return . Ents $ map makeEnt ls     Bal.Balanced ->       let makeEnt (mayEn, mt) = case mayEn of             Nothing -> Nothing-            Just en -> Just $ Ent en NotInferred mt+            Just en -> Just $ Ent en mt False       in fmap Ents $ mapM makeEnt ls  @@ -245,21 +239,23 @@   -- ^ Commodity for all postings   -> B.DrCr   -- ^ DrCr for all non-inferred postings-  -> (B.Qty, m)+  -> (Either B.QtyRep B.Qty, m)   -- ^ Non-inferred posting 1-  -> [(B.Qty, m)]+  -> [(Either B.QtyRep B.Qty, m)]   -- ^ Remaining non-inferred postings   -> m   -- ^ Metadata for inferred posting   -> Ents m rEnts com dc (q1, m1) nonInfs lastMeta =-  let tot = foldl' B.add q1 . map fst $ nonInfs+  let tot = foldl' B.add (either B.toQty id q1)+            . map (either B.toQty id . fst) $ nonInfs       p1 = makePstg (q1, m1)       ps = map makePstg nonInfs-      makePstg (q, m) = Ent (B.Entry dc (B.Amount q com))-                                NotInferred m-      lastPstg = Ent (B.Entry (B.opposite dc) (B.Amount tot com))-                         Inferred lastMeta+      makeEntry = either (\q -> Left (B.Entry dc (B.Amount q com)))+                         (\q -> Right (B.Entry dc (B.Amount q com)))+      makePstg (q, m) = Ent (makeEntry q) m False+      lastPstg = Ent (Right (B.Entry (B.opposite dc)+                                     (B.Amount tot com))) lastMeta True   in Ents $ p1:ps ++ [lastPstg]  
lib/Penny/Lincoln/Equivalent.hs view
@@ -15,7 +15,36 @@ (==~) = equivalent infix 4 ==~ +instance Equivalent a => Equivalent (Maybe a) where+  equivalent a b = case (a, b) of+    (Just x, Just y) -> x ==~ y+    (Nothing, Nothing) -> True+    _ -> False+  compareEv a b = case (a, b) of+    (Just x, Just y) -> compareEv x y+    (Nothing, Nothing) -> EQ+    (Just _, Nothing) -> GT+    (Nothing, Just _) -> LT+ instance (Equivalent a, Equivalent b) => Equivalent (a, b) where   equivalent (a1, b1) (a2, b2) = a1 ==~ a2 && b1 ==~ b2   compareEv (a1, b1) (a2, b2) =     compareEv a1 a2 <> compareEv b1 b2++instance (Equivalent a, Equivalent b, Equivalent c) =>+  Equivalent (a, b, c) where+    equivalent (a1, b1, c1) (a2, b2, c2) = a1 ==~ a2+      && b1 ==~ b2 && c1 ==~ c2+    compareEv (a1, b1, c1) (a2, b2, c2) =+      compareEv a1 a2 <> compareEv b1 b2 <> compareEv c1 c2++instance (Equivalent a, Equivalent b) => Equivalent (Either a b) where+  equivalent e1 e2 = case (e1, e2) of+    (Left l1, Left l2) -> l1 ==~ l2+    (Right r1, Right r2) -> r1 ==~ r2+    _ -> False+  compareEv e1 e2 = case (e1, e2) of+    (Left l1, Left l2) -> compareEv l1 l2+    (Right l1, Right l2) -> compareEv l1 l2+    (Left _, Right _) -> LT+    (Right _, Left _) -> GT
lib/Penny/Lincoln/PriceDb.hs view
@@ -85,14 +85,15 @@ -- fail and throw PriceDbError. Internally uses 'getPrice', so read its -- documentation for details on how price lookup works. convertAsOf ::-  PriceDb+  B.HasQty q+  => PriceDb   -> B.DateTime   -> B.To-  -> B.Amount+  -> B.Amount q   -> Ex.Exceptional PriceDbError B.Qty convertAsOf db dt to (B.Amount qt fr)-  | fr == B.unTo to = return qt+  | fr == B.unTo to = return . B.toQty $ qt   | otherwise = do     cpu <- fmap B.unCountPerUnit (getPrice db (B.From fr) to dt)-    let qt' = B.mult cpu qt+    let qt' = B.mult (B.toQty cpu) (B.toQty qt)     return qt'
lib/Penny/Lincoln/Queries.hs view
@@ -52,23 +52,27 @@ tags :: E.Posting -> B.Tags tags = B.pTags . B.pdCore . E.meta . E.headEnt . snd . E.unPosting -entry :: E.Posting -> B.Entry+entry :: E.Posting -> Either (B.Entry B.QtyRep) (B.Entry B.Qty) entry = E.entry . E.headEnt . snd . E.unPosting  balance :: E.Posting -> Balance-balance = entryToBalance . entry+balance = either entryToBalance entryToBalance . entry  drCr :: E.Posting -> B.DrCr-drCr = B.drCr . entry+drCr = either B.drCr B.drCr . entry -amount :: E.Posting -> B.Amount-amount = B.amount . entry+amount :: E.Posting -> Either (B.Amount B.QtyRep) (B.Amount B.Qty)+amount = either (Left . B.amount) (Right . B.amount) . entry +eiQty :: E.Posting -> Either B.QtyRep B.Qty+eiQty = either (Left . B.qty) (Right . B.qty) . amount++-- | Every Posting either has a Qty or it can be computed from its QtyRep. qty :: E.Posting -> B.Qty-qty = B.qty . amount+qty = either B.toQty B.toQty . eiQty  commodity :: E.Posting -> B.Commodity-commodity = B.commodity . amount+commodity = either B.commodity B.commodity . amount  topMemoLine :: E.Posting -> Maybe B.TopMemoLine topMemoLine p = (B.tlFileMeta . fst . E.unPosting $ p) >>= B.tTopMemoLine
lib/Penny/Lincoln/Queries/Siblings.hs view
@@ -59,23 +59,23 @@ tags :: E.Posting -> [B.Tags] tags = sibs (B.pTags . B.pdCore . E.meta) -entry :: E.Posting -> [B.Entry]+entry :: E.Posting -> [Either (B.Entry B.QtyRep) (B.Entry B.Qty)] entry = sibs E.entry  balance :: E.Posting -> [Balance]-balance = map entryToBalance . entry+balance = map (either entryToBalance entryToBalance) . entry  drCr :: E.Posting -> [B.DrCr]-drCr = map B.drCr . entry+drCr = map (either B.drCr B.drCr) . entry -amount :: E.Posting -> [B.Amount]-amount = map B.amount . entry+amount :: E.Posting -> [Either (B.Amount B.QtyRep) (B.Amount B.Qty)]+amount = map (either (Left . B.amount) (Right . B.amount)) . entry  qty :: E.Posting -> [B.Qty]-qty = map B.qty . amount+qty = map (either (B.toQty . B.qty) (B.toQty . B.qty)) . amount  commodity :: E.Posting -> [B.Commodity]-commodity = map B.commodity . amount+commodity = map (either B.commodity B.commodity) . amount  postingLine :: E.Posting -> [Maybe B.PostingLine] postingLine = sibs (fmap B.pPostingLine . B.pdFileMeta . E.meta)
lib/Penny/Wheat.hs view
@@ -105,6 +105,9 @@     -- ^ Tests may use this date and time as they wish; see     -- 'tests'. Typically you will set this to the current instant. +  , formatQty :: [Cop.LedgerItem] -> L.Amount L.Qty -> X.Text+  -- ^ How to format quantities+   }  parseBaseTime :: String -> Ex.Exceptional MA.InputError Time.UTCTime@@ -143,14 +146,10 @@ -- corresponding to the ledger files provided on the command line. parseArgs :: V.Version -> WheatConf -> IO (WheatConf, [String]) parseArgs ver c = do-  parsed <- MA.simpleWithHelp (help c) MA.Intersperse-         (fmap Left (Ly.version ver) : (map (fmap (Right . Left)) allOpts))-         (return . Right . Right)-  let (showVers, optsAndArgs) = partitionEithers parsed-  case showVers of-    [] -> return ()-    x:_ -> x-  let (opts, args) = partitionEithers optsAndArgs+  parsed <- MA.simpleHelpVersion (help c) (Ly.version ver)+            (map (fmap Right) allOpts) MA.Intersperse+            (return . Left)+  let (args, opts) = partitionEithers parsed       fn = foldl (flip (.)) id opts       c' = fn c   return (c', args)@@ -169,12 +168,14 @@   rt <- S.runtime   (conf, args) <- parseArgs ver (getWc rt)   term <- Rb.smartTermFromEnv (colorToFile conf) IO.stdout-  pstgs <- getItems args+  items <- Cop.open args+  let pstgs = getItems items+      formatter = formatQty conf items   let tsts = filter ((testPred conf) . TT.testName)              . map ($ (L.toUTC . S.currentTime $ rt))              . tests              $ conf-  bs <- mapM (runTest conf pstgs term) tsts+  bs <- mapM (runTest formatter conf pstgs term) tsts   if and bs     then Exit.exitSuccess     else Exit.exitFailure@@ -183,25 +184,26 @@ -- set and if the test failed. Otherwise, returns whether the test -- succeeded or failed. runTest-  :: WheatConf+  :: (L.Amount L.Qty -> X.Text)+  -> WheatConf   -> [L.Posting]   -> Rb.Term   -> TT.Test L.Posting   -> IO Bool-runTest c ps term test = do+runTest fmt c ps term test = do   let rslt = TT.evalTest test ps-      cks = TT.showResult (indentAmt c) L.display (verbosity c) rslt+      cks = TT.showResult (indentAmt c) (L.display fmt)+                          (verbosity c) rslt   Rb.putChunks term cks   if stopOnFail c && not (TT.resultPass rslt)     then Exit.exitFailure     else return (TT.resultPass rslt) -getItems :: [String] -> IO [L.Posting]-getItems ss = fmap f $ Cop.open ss-  where-    f = concatMap L.transactionToPostings-        . mapMaybe ( let cn = const Nothing-                     in Su.caseS4 Just cn cn cn)+getItems :: [Cop.LedgerItem] -> [L.Posting]+getItems+  = concatMap L.transactionToPostings+  . mapMaybe ( let cn = const Nothing+               in Su.caseS4 Just cn cn cn)  -- -- Tests
lib/Penny/Zinc.hs view
@@ -23,7 +23,6 @@ import qualified Penny.Steel.Sums as Su  import Control.Applicative ((<*>), pure, (<$))-import Control.Monad (join) import qualified Control.Monad.Trans.State as St import qualified Control.Monad.Exception.Synchronous as Ex import Data.Char (toUpper, toLower)@@ -32,7 +31,7 @@ import Data.Maybe (mapMaybe, catMaybes, fromMaybe) import Data.Monoid (mappend, mconcat, (<>)) import Data.Ord (comparing)-import Data.Text (Text, pack)+import Data.Text (Text, pack, unpack) import Data.Version (Version) import qualified Data.Text.IO as TIO import qualified System.Console.MultiArg as MA@@ -51,8 +50,15 @@ runZinc ver df rt rs = do   let ord = sortPairsToFn . sorter $ df       hlp = helpText df rt rs-  join $ MA.modesWithHelp hlp (allOpts ver (S.currentTime rt) df)-    (processGlobal rt ord df rs)+      globals = MA.optsHelpVersion hlp (Ly.version ver)+                (allOpts (S.currentTime rt) df)+  ei <- MA.modesIO globals (processGlobal rt ord df rs)+  case ei of+    Left h -> do+      pn <- MA.getProgName+      putStr (h pn)+      Exit.exitSuccess+    Right act -> act   -- | Whether to use color when standard output is not a terminal.@@ -94,6 +100,7 @@     -- > [(Date, Ascending), (Payee, Ascending)]    , exprDesc :: X.ExprDesc+  , formatQty :: [C.LedgerItem] -> L.Amount L.Qty -> Text   }  sortPairToFn :: (SortField, P.SortOrder) -> Orderer@@ -164,7 +171,6 @@   | RExprDesc X.ExprDesc   | RShowExpression   | RVerboseFilter-  | RShowVersion (IO ())  getPostFilters   :: [OptResult]@@ -201,13 +207,6 @@      then return i      else fmap mconcat . sequence $ exSpecs -getShowVersion :: [OptResult] -> Maybe (IO ())-getShowVersion ls = case mapMaybe f ls of-  [] -> Nothing-  xs -> Just $ last xs-  where-    f o = case o of { RShowVersion i -> Just i; _ -> Nothing }- type Factory = M.CaseSensitive              -> Text -> Ex.Exceptional Text M.Matcher @@ -249,8 +248,8 @@   in fmap (\xs -> (xs, st')) . sequence . catMaybes $ ls  -allOpts :: Version -> L.DateTime -> Defaults -> [MA.OptSpec OptResult]-allOpts ver dt df =+allOpts :: L.DateTime -> Defaults -> [MA.OptSpec OptResult]+allOpts dt df =   map (fmap ROperand) (Ly.operandSpecs dt)   ++ [fmap RPostFilter . fst $ Ly.postFilterSpecs]   ++ [fmap RPostFilter . snd $ Ly.postFilterSpecs]@@ -264,7 +263,6 @@   ++ map (fmap RExprDesc) Ly.exprDesc   ++ [ RShowExpression <$ Ly.showExpression      , RVerboseFilter <$ Ly.verboseFilter-     , fmap RShowVersion (Ly.version ver)      ]  optColorToFile :: MA.OptSpec OptResult@@ -324,9 +322,12 @@     -- subsequent parses of the command line.    , foSorterFilterer :: [L.Transaction]-                    -> ([R.Chunk], [(Ly.LibertyMeta, L.Posting)])-    -- ^ Applied to a list of Transaction, will sort and filter-    -- the transactions and assign them LibertyMeta.+                     -> ( (L.Amount L.Qty -> Text) -> [R.Chunk]+                          , [(Ly.LibertyMeta, L.Posting)])+    -- ^ Applied to a list of Transaction, will sort and filter the+    -- transactions and assign them LibertyMeta. Also, returns a+    -- function that, when applied to a function that will format+    -- quantities, returns a verbose description of what was filtered.    , foTextSpecs :: Maybe E.Changers @@ -343,43 +344,40 @@   -> Defaults   -> [I.Report]   -> [OptResult]-  -> Either (a -> IO ()) [MA.Mode (IO ())]+  -> Ex.Exceptional String+      (Either a [MA.Mode (MA.ProgName -> String) (IO ())]) processGlobal rt srt df rpts os   = case processFiltOpts srt df os of-      Ex.Exception s -> Left $ (const $ handleTextError s)-      Ex.Success mayFo -> case mayFo of-        Left i -> Left . const $ i-        Right fo -> Right $ map (makeMode rt fo) rpts+      Ex.Exception s -> Ex.throw s+      Ex.Success fo ->+        return . Right $ map (makeMode (formatQty df) rt fo) rpts  processFiltOpts   :: Orderer   -> Defaults   -> [OptResult]-  -> Ex.Exceptional Error (Either (IO ()) FilterOpts)-  -- ^ Left if the user asked to see the version; Right with the-  -- FilterOpts otherwise.-processFiltOpts ord df os = case getShowVersion os of-  Just i -> return $ Left i-  Nothing -> do-    postFilts <- getPostFilters os-    sortSpec <- getSortSpec ord os-    (toks, (rs, rf)) <- makeTokens df os-    let ctf = getColorToFile df os-        sch = getScheme df os-        expDsc = getExprDesc df os-        showExpr = getShowExpression os-        verbFilt = getVerboseFilter os-    pdct <- Ly.parsePredicate expDsc toks-    let sf = Ly.xactionsToFiltered pdct postFilts sortSpec-    return . Right $ FilterOpts rf rs sf sch-                                ctf expDsc pdct showExpr verbFilt+  -> Ex.Exceptional String FilterOpts+processFiltOpts ord df os = Ex.mapException unpack $ do+  postFilts <- getPostFilters os+  sortSpec <- getSortSpec ord os+  (toks, (rs, rf)) <- makeTokens df os+  let ctf = getColorToFile df os+      sch = getScheme df os+      expDsc = getExprDesc df os+      showExpr = getShowExpression os+      verbFilt = getVerboseFilter os+  pdct <- Ly.parsePredicate expDsc toks+  let sf ls = Ly.xactionsToFiltered pdct postFilts sortSpec ls+  return $ FilterOpts rf rs sf sch+                      ctf expDsc pdct showExpr verbFilt  makeMode-  :: S.Runtime+  :: ([C.LedgerItem] -> L.Amount L.Qty -> Text)+  -> S.Runtime   -> FilterOpts   -> I.Report-  -> MA.Mode (IO ())-makeMode rt fo r = fmap makeIO mode+  -> MA.Mode (MA.ProgName -> String) (IO ())+makeMode mkFmt rt fo r = fmap makeIO mode   where     mode = snd (r rt) (foResultSensitive fo) (foResultFactory fo)            (fromMaybe Schemes.plainLabels . foTextSpecs $ fo)@@ -387,16 +385,18 @@     makeIO parseResult = do       (posArgs, printRpt) <-         Ex.switch handleTextError return parseResult-      (txns, pps) <- fmap splitLedger $ C.open posArgs-      let term = if unColorToFile (foColorToFile fo)+      items <- C.open posArgs+      let fmt = mkFmt items+          (txns, pps) = splitLedger items+          term = if unColorToFile (foColorToFile fo)                  then S.termFromEnv rt                  else S.autoTerm rt           printer = R.putChunks term-          verbFiltChunks = fst . foSorterFilterer fo $ txns+          verbFiltChunks = (fst . foSorterFilterer fo $ txns) fmt       showFilterExpression printer (foShowExpression fo) (foPredicate fo)       showVerboseFilter printer (foVerboseFilter fo) verbFiltChunks       Ex.switch handleTextError (R.putChunks term)-        $ printRpt txns pps+        $ printRpt fmt txns pps   handleTextError :: Text -> IO a
penny-lib.cabal view
@@ -1,5 +1,5 @@ Name: penny-lib-Version: 0.20.0.0+Version: 0.21.0.0 Cabal-version: >=1.8 Build-Type: Simple License: BSD3@@ -12,25 +12,10 @@ Category: Console, Finance License-File: LICENSE -synopsis: Extensible double-entry accounting system - library--description: Penny is a double-entry accounting system. It is inspired-  by, but incompatible with, John Wiegley's Ledger, which is available-  at <http://ledger-cli.org/>.--  .--  This package is a library. To start using Penny you will want to-  install the penny-bin package, which has the executable programs.-  More about the penny-bin package, along with a full sales pitch-  for Penny and how to get started, is at-  <http://hackage.haskell.org/package/penny-bin>. It may be installed-  with "cabal install penny-bin".--  .+synopsis: Deprecated - use penny package instead -  The Penny library is a full system to work with double-entry-  accounting transactions and postings and to make reports with them.+description:+  This package is now deprecated.  Use the penny package instead.  source-repository head   type: git@@ -46,7 +31,7 @@     , containers ==0.5.*     , explicit-exception ==0.1.*     , matchers ==0.6.*-    , multiarg ==0.16.*+    , multiarg ==0.18.*     , ofx ==0.2.*     , old-locale ==1.0.*     , parsec >= 3.1.2 && < 3.2@@ -157,7 +142,10 @@    -- Be sure the build-depends are listed within the if block;   -- otherwise, cabal install will always include these-  -- build-dependencies in any build, even non-test builds.+  -- build-dependencies in any build, even non-test builds. However,+  -- you still have to list all the build-depends--the library+  -- build-depends are included for dependency resolving purposes but+  -- not for building purposes.    -- Test dependencies. test-framework has issues with newer versions,   -- see@@ -167,6 +155,27 @@         QuickCheck ==2.5.*       , random-shuffle ==0.0.4 +      , base ==4.*+      , action-permutations ==0.0.0.0+      , binary ==0.7.*+      , bytestring ==0.10.*+      , cereal ==0.3.*+      , containers ==0.5.*+      , explicit-exception ==0.1.*+      , matchers ==0.6.*+      , multiarg ==0.18.*+      , ofx ==0.2.*+      , old-locale ==1.0.*+      , parsec >= 3.1.2 && < 3.2+      , prednote == 0.14.*+      , pretty-show ==1.5.*+      , rainbow ==0.4.*+      , semigroups ==0.9.*+      , split ==0.2.*+      , text ==0.11.*+      , time ==1.4.*+      , transformers == 0.3.*+   else     buildable: False @@ -180,6 +189,29 @@     build-depends:         QuickCheck ==2.5.*       , random-shuffle ==0.0.4+      , random ==1.0.*++      , base ==4.*+      , action-permutations ==0.0.0.0+      , binary ==0.7.*+      , bytestring ==0.10.*+      , cereal ==0.3.*+      , containers ==0.5.*+      , explicit-exception ==0.1.*+      , matchers ==0.6.*+      , multiarg ==0.18.*+      , ofx ==0.2.*+      , old-locale ==1.0.*+      , parsec >= 3.1.2 && < 3.2+      , prednote == 0.14.*+      , pretty-show ==1.5.*+      , rainbow ==0.4.*+      , semigroups ==0.9.*+      , split ==0.2.*+      , text ==0.11.*+      , time ==1.4.*+      , transformers == 0.3.*+   else     buildable: False 
tests/penny-gibberish.hs view
@@ -23,26 +23,15 @@   , "  -c, --count INT"   , "      Number of items (transactions, comments, prices, and"   , "      blank lines, total) to output. (default: 100)"-  , "  -l, --left GROUP_SPEC"-  , "      Group left of the decimal point (default: none)"-  , "  -r, --right GROUP_SPEC"-  , "      Group right of the decimal point (default: none)"-  , "      where GROUP_SPEC is:"-  , "        none - no digit grouping"-  , "        large - group if greater than 9,999 left of decimal,"-  , "                or more than 4 decimal places right of decimal"-  , "        all - group whenever there are at least 4 places"   ]  data Opts = Opts   { optSize :: Int   , optCount :: Int-  , optLeft :: R.GroupSpec-  , optRight :: R.GroupSpec   } deriving Show  defaultOpts :: Opts-defaultOpts = Opts 5 100 R.NoGrouping R.NoGrouping+defaultOpts = Opts 5 100  options :: [MA.OptSpec (Opts -> Opts)] options =@@ -57,21 +46,8 @@       if i < 1         then Ex.throw (MA.ErrorMsg "non-positive count parameter")         else return (\os -> os { optCount = i })--  , MA.OptSpec ["left"] "l" . MA.ChoiceArg-    . map (\(str, spec) -> (str, \os -> os { optLeft = spec }))-    $ groupSpecs--  , MA.OptSpec ["right"] "r" . MA.ChoiceArg-    . map (\(str, spec) -> (str, \os -> os { optRight = spec }))-    $ groupSpecs   ] -groupSpecs :: [(String, R.GroupSpec)]-groupSpecs = [ ("none",  R.NoGrouping)-             , ("large", R.GroupLarge)-             , ("all",   R.GroupAll  ) ]- posArg :: a -> Ex.Exceptional MA.InputError b posArg _ = Ex.throw (MA.ErrorMsg "no non-option arguments accepted") @@ -81,15 +57,14 @@ main :: IO () main = do   pn <- MA.getProgName-  os <- fmap parse $ MA.simpleWithHelp help MA.Intersperse options+  os <- fmap parse $ MA.simpleHelp help options MA.Intersperse                      posArg   gen <- Rand.getStdGen   let is = (\g -> G.unGen g gen (optSize os))            . fmap (map fst)            . replicateM (optCount os)            $ P.item-      gs = R.GroupSpecs (optLeft os) (optRight os)-      x = mapM (R.item gs) is+      x = mapM (R.item Nothing) is   case x of     Nothing -> do       IO.hPutStrLn IO.stderr $ pn ++ ": error: could not render ledger."
tests/penny-test.hs view
@@ -34,8 +34,8 @@  main :: IO () main = do-  opts <- MA.simpleWithHelp help MA.Intersperse-          options+  opts <- MA.simpleHelp help options+          MA.Intersperse           ( const . Ex.Exception . MA.ErrorMsg             $ "this command does not accept positional arguments")   let args = foldl (flip (.)) id opts Q.stdArgs