penny-lib 0.4.0.0 → 0.6.0.0
raw patch · 118 files changed
+11039/−8488 lines, 118 filesdep +bytestringdep +cerealdep +monad-loopsdep −Decimaldep ~matchersdep ~multiargdep ~splitPVP ok
version bump matches the API change (PVP)
Dependencies added: bytestring, cereal, monad-loops, pretty-show, strict, terminfo
Dependencies removed: Decimal
Dependency ranges changed: matchers, multiarg, split, time, transformers
API changes (from Hackage documentation)
- Penny: DefaultTimeZone :: TimeZoneOffset -> DefaultTimeZone
- Penny: T :: CaseSensitive -> (CaseSensitive -> Text -> Exceptional Text (Text -> Bool)) -> DateTime -> DefaultTimeZone -> RadGroup -> T
- Penny: allReportsWithDefaults :: DefaultTimeZone -> RadGroup -> [Report]
- Penny: commaPeriod :: RadGroup
- Penny: commaSpace :: RadGroup
- Penny: currentTime :: T -> DateTime
- Penny: customPenny :: DefaultTimeZone -> RadGroup -> (Runtime -> T) -> [Report] -> IO ()
- Penny: data RadGroup
- Penny: data T
- Penny: defaultFromRuntime :: DefaultTimeZone -> RadGroup -> Runtime -> T
- Penny: defaultPenny :: DefaultTimeZone -> RadGroup -> IO ()
- Penny: defaultTimeZone :: T -> DefaultTimeZone
- Penny: factory :: T -> CaseSensitive -> Text -> Exceptional Text (Text -> Bool)
- Penny: minsToDefaultTimeZone :: Int -> DefaultTimeZone
- Penny: newtype DefaultTimeZone
- Penny: periodComma :: RadGroup
- Penny: periodSpace :: RadGroup
- Penny: radGroup :: T -> RadGroup
- Penny: sensitive :: T -> CaseSensitive
- Penny: utcDefault :: DefaultTimeZone
- Penny.Cabin: Report :: Text -> String -> Parser ReportFunc -> Report
- Penny.Cabin: allReportsWithDefaults :: DefaultTimeZone -> RadGroup -> [Report]
- Penny.Cabin: data Report
- Penny.Cabin: help :: Report -> Text
- Penny.Cabin: name :: Report -> String
- Penny.Cabin: parseReport :: Report -> Parser ReportFunc
- Penny.Cabin.Allocate: allocate :: Ord k => Map k Allocation -> Int -> Map k Int
- Penny.Cabin.Allocate: allocation :: Int -> Allocation
- Penny.Cabin.Allocate: data Allocation
- Penny.Cabin.Allocate: instance Eq Allocation
- Penny.Cabin.Allocate: instance Ord Allocation
- Penny.Cabin.Allocate: instance Show Allocation
- Penny.Cabin.Allocate: unAllocation :: Allocation -> Int
- Penny.Cabin.Balance: BalanceOpts :: DrCrColors -> BaseColors -> (BottomLine -> Text) -> Colors -> ShowZeroBalances -> DefaultTimeZone -> Maybe (Commodity, DateTime) -> BalanceOpts
- Penny.Cabin.Balance: balanceAsIs :: BottomLine -> Text
- Penny.Cabin.Balance: balanceFormat :: BalanceOpts -> BottomLine -> Text
- Penny.Cabin.Balance: balanceReport :: (Runtime -> BalanceOpts) -> Report
- Penny.Cabin.Balance: baseColors :: BalanceOpts -> BaseColors
- Penny.Cabin.Balance: colorPref :: BalanceOpts -> Colors
- Penny.Cabin.Balance: converter :: (Commodity, DateTime) -> [PricePoint] -> [Box LibertyMeta] -> Exceptional Text PriceConverteds
- Penny.Cabin.Balance: data BalanceOpts
- Penny.Cabin.Balance: defaultOptions :: DefaultTimeZone -> Runtime -> BalanceOpts
- Penny.Cabin.Balance: defaultTimeZone :: BalanceOpts -> DefaultTimeZone
- Penny.Cabin.Balance: drCrColors :: BalanceOpts -> DrCrColors
- Penny.Cabin.Balance: nullConvert :: [Box LibertyMeta] -> PriceConverteds
- Penny.Cabin.Balance: report :: TreeOpts -> PriceConverteds -> [[Chunk]]
- Penny.Cabin.Balance: showZeroBalances :: BalanceOpts -> ShowZeroBalances
- Penny.Cabin.Balance.Help: help :: Text
- Penny.Cabin.Balance.Help: helpStr :: String
- Penny.Cabin.Balance.Parser: BadBackground :: String -> Error
- Penny.Cabin.Balance.Parser: BadColorName :: String -> Error
- Penny.Cabin.Balance.Parser: BadCommodity :: String -> Error
- Penny.Cabin.Balance.Parser: BadDate :: String -> Error
- Penny.Cabin.Balance.Parser: ParseOpts :: DrCrColors -> BaseColors -> Colors -> ShowZeroBalances -> Maybe (Commodity, DateTime) -> ParseOpts
- Penny.Cabin.Balance.Parser: baseColors :: ParseOpts -> BaseColors
- Penny.Cabin.Balance.Parser: colorPref :: ParseOpts -> Colors
- Penny.Cabin.Balance.Parser: convert :: ParseOpts -> Maybe (Commodity, DateTime)
- Penny.Cabin.Balance.Parser: data Error
- Penny.Cabin.Balance.Parser: data ParseOpts
- Penny.Cabin.Balance.Parser: drCrColors :: ParseOpts -> DrCrColors
- Penny.Cabin.Balance.Parser: instance Show Error
- Penny.Cabin.Balance.Parser: parseOptions :: Parser (Runtime -> DefaultTimeZone -> ParseOpts -> Exceptional Error ParseOpts)
- Penny.Cabin.Balance.Parser: showZeroBalances :: ParseOpts -> ShowZeroBalances
- Penny.Cabin.Balance.Tree: TreeOpts :: DrCrColors -> BaseColors -> (BottomLine -> Text) -> ShowZeroBalances -> TreeOpts
- Penny.Cabin.Balance.Tree: balanceFormat :: TreeOpts -> BottomLine -> Text
- Penny.Cabin.Balance.Tree: baseColors :: TreeOpts -> BaseColors
- Penny.Cabin.Balance.Tree: converter :: (Commodity, DateTime) -> [PricePoint] -> [Box LibertyMeta] -> Exceptional Text PriceConverteds
- Penny.Cabin.Balance.Tree: data PriceConverteds
- Penny.Cabin.Balance.Tree: data TreeOpts
- Penny.Cabin.Balance.Tree: drCrColors :: TreeOpts -> DrCrColors
- Penny.Cabin.Balance.Tree: instance Applicative Columns
- Penny.Cabin.Balance.Tree: instance Functor Columns
- Penny.Cabin.Balance.Tree: instance Monoid RawBal
- Penny.Cabin.Balance.Tree: instance Monoid SummedBal
- Penny.Cabin.Balance.Tree: instance Show a => Show (Columns a)
- Penny.Cabin.Balance.Tree: nullConvert :: [Box LibertyMeta] -> PriceConverteds
- Penny.Cabin.Balance.Tree: report :: TreeOpts -> PriceConverteds -> [[Chunk]]
- Penny.Cabin.Balance.Tree: showZeroBalances :: TreeOpts -> ShowZeroBalances
- Penny.Cabin.Chunk: Colors0 :: Colors
- Penny.Cabin.Chunk: Colors256 :: Colors
- Penny.Cabin.Chunk: Colors8 :: Colors
- Penny.Cabin.Chunk: chunksToText :: Colors -> [Chunk] -> Text
- Penny.Cabin.Chunk: data Colors
- Penny.Cabin.Chunk: instance Show Colors
- Penny.Cabin.Colors: BaseColors :: !TextSpec -> !TextSpec -> BaseColors
- Penny.Cabin.Colors: DrCrColors :: !TextSpec -> !TextSpec -> !TextSpec -> !TextSpec -> !TextSpec -> !TextSpec -> DrCrColors
- Penny.Cabin.Colors: bottomLineToBaseColors :: DrCrColors -> BottomLine -> BaseColors
- Penny.Cabin.Colors: colors :: VisibleNum -> BaseColors -> TextSpec
- Penny.Cabin.Colors: data BaseColors
- Penny.Cabin.Colors: data DrCrColors
- Penny.Cabin.Colors: drCrToBaseColors :: DrCr -> DrCrColors -> BaseColors
- Penny.Cabin.Colors: evenColors :: BaseColors -> !TextSpec
- Penny.Cabin.Colors: evenCredit :: DrCrColors -> !TextSpec
- Penny.Cabin.Colors: evenDebit :: DrCrColors -> !TextSpec
- Penny.Cabin.Colors: evenZero :: DrCrColors -> !TextSpec
- Penny.Cabin.Colors: noBalanceColors :: VisibleNum -> DrCrColors -> TextSpec
- Penny.Cabin.Colors: oddColors :: BaseColors -> !TextSpec
- Penny.Cabin.Colors: oddCredit :: DrCrColors -> !TextSpec
- Penny.Cabin.Colors: oddDebit :: DrCrColors -> !TextSpec
- Penny.Cabin.Colors: oddZero :: DrCrColors -> !TextSpec
- Penny.Cabin.Colors.DarkBackground: baseColors :: BaseColors
- Penny.Cabin.Colors.DarkBackground: credit :: TextSpec -> TextSpec
- Penny.Cabin.Colors.DarkBackground: debit :: TextSpec -> TextSpec
- Penny.Cabin.Colors.DarkBackground: drCrColors :: DrCrColors
- Penny.Cabin.Colors.DarkBackground: evenTextSpec :: TextSpec
- Penny.Cabin.Colors.DarkBackground: oddTextSpec :: TextSpec
- Penny.Cabin.Colors.DarkBackground: zero :: TextSpec -> TextSpec
- Penny.Cabin.Colors.LightBackground: baseColors :: BaseColors
- Penny.Cabin.Colors.LightBackground: credit :: TextSpec -> TextSpec
- Penny.Cabin.Colors.LightBackground: debit :: TextSpec -> TextSpec
- Penny.Cabin.Colors.LightBackground: drCrColors :: DrCrColors
- Penny.Cabin.Colors.LightBackground: evenTextSpec :: TextSpec
- Penny.Cabin.Colors.LightBackground: oddTextSpec :: TextSpec
- Penny.Cabin.Colors.LightBackground: zero :: TextSpec -> TextSpec
- Penny.Cabin.Interface: Report :: Text -> String -> Parser ReportFunc -> Report
- Penny.Cabin.Interface: data Report
- Penny.Cabin.Interface: help :: Report -> Text
- Penny.Cabin.Interface: name :: Report -> String
- Penny.Cabin.Interface: parseReport :: Report -> Parser ReportFunc
- Penny.Cabin.Interface: type ReportFunc = Runtime -> CaseSensitive -> (CaseSensitive -> Text -> Exceptional Text (Text -> Bool)) -> [Box LibertyMeta] -> [PricePoint] -> Exceptional Text Text
- Penny.Cabin.Options: Pref0 :: ColorPref
- Penny.Cabin.Options: Pref256 :: ColorPref
- Penny.Cabin.Options: Pref8 :: ColorPref
- Penny.Cabin.Options: PrefAuto :: ColorPref
- Penny.Cabin.Options: autoColors :: ColorPref -> Runtime -> Colors
- Penny.Cabin.Options: data ColorPref
- Penny.Cabin.Options: instance Show ColorPref
- Penny.Cabin.Options: maxCapableColors :: Runtime -> Colors
- Penny.Cabin.Posts: baseColors :: ZincOpts -> BaseColors
- Penny.Cabin.Posts: colorPref :: ZincOpts -> Colors
- Penny.Cabin.Posts: defaultTimeZone :: ZincOpts -> DefaultTimeZone
- Penny.Cabin.Posts: drCrColors :: ZincOpts -> DrCrColors
- Penny.Cabin.Posts: makeReport :: (Runtime -> ZincOpts) -> Report
- Penny.Cabin.Posts: parseReport :: (Runtime -> ZincOpts) -> Parser ReportFunc
- Penny.Cabin.Posts: radGroup :: ZincOpts -> RadGroup
- Penny.Cabin.Posts: ymd :: Box -> Text
- Penny.Cabin.Posts.Allocate: allocate :: (Functor f, Foldable f, Traversable f) => f Allocation -> Int -> f Int
- Penny.Cabin.Posts.Allocate: allocation :: Int -> Allocation
- Penny.Cabin.Posts.Allocate: data Allocation
- Penny.Cabin.Posts.Allocate: instance Eq Allocation
- Penny.Cabin.Posts.Allocate: instance Ord Allocation
- Penny.Cabin.Posts.Allocate: instance Show Allocation
- Penny.Cabin.Posts.Allocate: unAllocation :: Allocation -> Int
- Penny.Cabin.Posts.Allocated: accountAllocation :: AllocatedOpts -> Allocation
- Penny.Cabin.Posts.Allocated: baseColors :: AllocatedOpts -> BaseColors
- Penny.Cabin.Posts.Allocated: instance Show UnShrunkWidth
- Penny.Cabin.Posts.Allocated: payeeAllocation :: AllocatedOpts -> Allocation
- Penny.Cabin.Posts.BottomRows: baseColors :: BottomOpts -> BaseColors
- Penny.Cabin.Posts.Chunk: baseColors :: ChunkOpts -> BaseColors
- Penny.Cabin.Posts.Chunk: drCrColors :: ChunkOpts -> DrCrColors
- Penny.Cabin.Posts.Growers: baseColors :: GrowOpts -> BaseColors
- Penny.Cabin.Posts.Growers: drCrColors :: GrowOpts -> DrCrColors
- Penny.Cabin.Posts.Help: help :: Text
- Penny.Cabin.Posts.Help: helpStr :: String
- Penny.Cabin.Posts.Parser: BadBackgroundArg :: String -> Error
- Penny.Cabin.Posts.Parser: BadColorName :: String -> Error
- Penny.Cabin.Posts.Parser: BadComparator :: String -> Error
- Penny.Cabin.Posts.Parser: BadNumber :: String -> Error
- Penny.Cabin.Posts.Parser: BadWidthArg :: String -> Error
- Penny.Cabin.Posts.Parser: LibertyError :: Error -> Error
- Penny.Cabin.Posts.Parser: MultipleMatchingFieldNames :: [String] -> Error
- Penny.Cabin.Posts.Parser: NoMatchingFieldName :: Error
- Penny.Cabin.Posts.Parser: baseColors :: State -> BaseColors
- Penny.Cabin.Posts.Parser: colorPref :: State -> Colors
- Penny.Cabin.Posts.Parser: data Error
- Penny.Cabin.Posts.Parser: drCrColors :: State -> DrCrColors
- Penny.Cabin.Posts.Parser: instance Show Error
- Penny.Cabin.Posts.Parser: parseOptions :: Parser (Runtime -> DefaultTimeZone -> RadGroup -> State -> Exceptional Error State)
- Penny.Copper: CommentItem :: Comment -> Item
- Penny.Copper: DefaultTimeZone :: TimeZoneOffset -> DefaultTimeZone
- Penny.Copper: ErrorMsg :: Text -> ErrorMsg
- Penny.Copper: Price :: PricePoint -> Item
- Penny.Copper: commaPeriod :: RadGroup
- Penny.Copper: commaSpace :: RadGroup
- Penny.Copper: data Comment
- Penny.Copper: data GroupingSpec
- Penny.Copper: data Ledger
- Penny.Copper: data Line
- Penny.Copper: data RadGroup
- Penny.Copper: instance Show Ledger
- Penny.Copper: instance Show Other
- Penny.Copper: newtype DefaultTimeZone
- Penny.Copper: newtype ErrorMsg
- Penny.Copper: periodComma :: RadGroup
- Penny.Copper: periodSpace :: RadGroup
- Penny.Copper: render :: DefaultTimeZone -> (GroupingSpec, GroupingSpec) -> RadGroup -> Item -> Maybe Text
- Penny.Copper: unErrorMsg :: ErrorMsg -> Text
- Penny.Copper: utcDefault :: DefaultTimeZone
- Penny.Copper.Account: instance Eq Level
- Penny.Copper.Account: instance Ord Level
- Penny.Copper.Account: instance Show Level
- Penny.Copper.Account: lvl1Account :: Parser Account
- Penny.Copper.Account: lvl1AccountQuoted :: Parser Account
- Penny.Copper.Account: lvl1Char :: Char -> Bool
- Penny.Copper.Account: lvl2Account :: Parser Account
- Penny.Copper.Account: lvl2FirstChar :: Char -> Bool
- Penny.Copper.Account: lvl2RemainingChar :: Char -> Bool
- Penny.Copper.Account: render :: Account -> Maybe Text
- Penny.Copper.Amount: amount :: RadGroup -> Parser (Amount, Format)
- Penny.Copper.Amount: render :: (GroupingSpec, GroupingSpec) -> RadGroup -> Format -> Amount -> Maybe Text
- Penny.Copper.Comments: Comment :: Text -> Comment
- Penny.Copper.Comments: comment :: Parser Comment
- Penny.Copper.Comments: data Comment
- Penny.Copper.Comments: instance Eq Comment
- Penny.Copper.Comments: instance Show Comment
- Penny.Copper.Comments: isCommentChar :: Char -> Bool
- Penny.Copper.Comments: render :: Comment -> Maybe Text
- Penny.Copper.Commodity: commandLineCmdty :: Parser Commodity
- Penny.Copper.Commodity: leftSideCmdty :: Parser Commodity
- Penny.Copper.Commodity: lvl1Char :: Char -> Bool
- Penny.Copper.Commodity: lvl1Cmdty :: Parser Commodity
- Penny.Copper.Commodity: lvl2Cmdty :: Parser Commodity
- Penny.Copper.Commodity: lvl2FirstChar :: Char -> Bool
- Penny.Copper.Commodity: lvl2OtherChars :: Char -> Bool
- Penny.Copper.Commodity: lvl3Cmdty :: Parser Commodity
- Penny.Copper.Commodity: lvl3FirstChar :: Char -> Bool
- Penny.Copper.Commodity: lvl3OtherChars :: Char -> Bool
- Penny.Copper.Commodity: quotedLvl1Cmdty :: Parser Commodity
- Penny.Copper.Commodity: renderLvl2 :: Commodity -> Maybe Text
- Penny.Copper.Commodity: renderLvl3 :: Commodity -> Maybe Text
- Penny.Copper.Commodity: renderQuotedLvl1 :: Commodity -> Maybe Text
- Penny.Copper.Commodity: rightSideCmdty :: Parser Commodity
- Penny.Copper.DateTime: DefaultTimeZone :: TimeZoneOffset -> DefaultTimeZone
- Penny.Copper.DateTime: dateTime :: DefaultTimeZone -> Parser DateTime
- Penny.Copper.DateTime: instance Eq DefaultTimeZone
- Penny.Copper.DateTime: instance Show DefaultTimeZone
- Penny.Copper.DateTime: newtype DefaultTimeZone
- Penny.Copper.DateTime: render :: DefaultTimeZone -> DateTime -> Text
- Penny.Copper.DateTime: unDefaultTimeZone :: DefaultTimeZone -> TimeZoneOffset
- Penny.Copper.DateTime: utcDefault :: DefaultTimeZone
- Penny.Copper.Entry: entry :: RadGroup -> Parser (Entry, Format)
- Penny.Copper.Entry: render :: (GroupingSpec, GroupingSpec) -> RadGroup -> Format -> Entry -> Maybe Text
- Penny.Copper.Flag: flag :: Parser Flag
- Penny.Copper.Flag: isFlagChar :: Char -> Bool
- Penny.Copper.Flag: render :: Flag -> Maybe Text
- Penny.Copper.Item: BlankLine :: Item
- Penny.Copper.Item: CommentItem :: Comment -> Item
- Penny.Copper.Item: Price :: PricePoint -> Item
- Penny.Copper.Item: Transaction :: Transaction -> Item
- Penny.Copper.Item: data Item
- Penny.Copper.Item: data Line
- Penny.Copper.Item: instance Eq Line
- Penny.Copper.Item: instance Show Item
- Penny.Copper.Item: instance Show Line
- Penny.Copper.Item: itemWithLineNumber :: DefaultTimeZone -> RadGroup -> Parser (Line, Item)
- Penny.Copper.Item: render :: DefaultTimeZone -> (GroupingSpec, GroupingSpec) -> RadGroup -> Item -> Maybe Text
- Penny.Copper.Memos.Posting: isCommentChar :: Char -> Bool
- Penny.Copper.Memos.Posting: memo :: Parser Memo
- Penny.Copper.Memos.Posting: render :: Memo -> Maybe Text
- Penny.Copper.Memos.Transaction: isCommentChar :: Char -> Bool
- Penny.Copper.Memos.Transaction: memo :: Parser (Memo, TopMemoLine)
- Penny.Copper.Memos.Transaction: render :: Memo -> Maybe Text
- Penny.Copper.Number: isNumChar :: Char -> Bool
- Penny.Copper.Number: number :: Parser Number
- Penny.Copper.Number: render :: Number -> Maybe Text
- Penny.Copper.Payees: payee :: Parser Payee
- Penny.Copper.Payees: quoteRender :: Payee -> Maybe Text
- Penny.Copper.Payees: quotedChar :: Char -> Bool
- Penny.Copper.Payees: quotedPayee :: Parser Payee
- Penny.Copper.Payees: smartRender :: Payee -> Maybe Text
- Penny.Copper.Payees: unquotedFirstChar :: Char -> Bool
- Penny.Copper.Payees: unquotedPayee :: Parser Payee
- Penny.Copper.Payees: unquotedRestChars :: Char -> Bool
- Penny.Copper.Posting: instance Eq UnverifiedWithMeta
- Penny.Copper.Posting: instance Show UnverifiedWithMeta
- Penny.Copper.Posting: posting :: RadGroup -> Parser Posting
- Penny.Copper.Posting: render :: (GroupingSpec, GroupingSpec) -> RadGroup -> Posting -> Maybe Text
- Penny.Copper.Price: price :: DefaultTimeZone -> RadGroup -> Parser PricePoint
- Penny.Copper.Price: render :: DefaultTimeZone -> (GroupingSpec, GroupingSpec) -> RadGroup -> PricePoint -> Maybe Text
- Penny.Copper.Qty: GroupAll :: GroupingSpec
- Penny.Copper.Qty: GroupLarge :: GroupingSpec
- Penny.Copper.Qty: NoGrouping :: GroupingSpec
- Penny.Copper.Qty: commaPeriod :: RadGroup
- Penny.Copper.Qty: commaSpace :: RadGroup
- Penny.Copper.Qty: data GroupingSpec
- Penny.Copper.Qty: data RadGroup
- Penny.Copper.Qty: instance Eq Grouper
- Penny.Copper.Qty: instance Eq GroupingSpec
- Penny.Copper.Qty: instance Eq RadGroup
- Penny.Copper.Qty: instance Eq Radix
- Penny.Copper.Qty: instance Show Grouper
- Penny.Copper.Qty: instance Show GroupingSpec
- Penny.Copper.Qty: instance Show NumberStr
- Penny.Copper.Qty: instance Show RadGroup
- Penny.Copper.Qty: instance Show Radix
- Penny.Copper.Qty: periodComma :: RadGroup
- Penny.Copper.Qty: periodSpace :: RadGroup
- Penny.Copper.Qty: qty :: RadGroup -> Parser Qty
- Penny.Copper.Qty: qtyQuoted :: RadGroup -> Parser Qty
- Penny.Copper.Qty: qtyUnquoted :: RadGroup -> Parser Qty
- Penny.Copper.Qty: quote :: Text -> Text
- Penny.Copper.Qty: renderUnquoted :: RadGroup -> (GroupingSpec, GroupingSpec) -> Qty -> Text
- Penny.Copper.Tags: isTagChar :: Char -> Bool
- Penny.Copper.Tags: render :: Tags -> Maybe Text
- Penny.Copper.Tags: tags :: Parser Tags
- Penny.Copper.TopLine: render :: DefaultTimeZone -> TopLine -> Maybe Text
- Penny.Copper.TopLine: topLine :: DefaultTimeZone -> Parser TopLine
- Penny.Copper.Transaction: render :: DefaultTimeZone -> (GroupingSpec, GroupingSpec) -> RadGroup -> Transaction -> Maybe Text
- Penny.Copper.Transaction: transaction :: DefaultTimeZone -> RadGroup -> Parser Transaction
- Penny.Copper.Util: checkText :: HasText a => NonEmpty (Char -> Bool, b) -> a -> Maybe b
- Penny.Copper.Util: eol :: Parser ()
- Penny.Copper.Util: firstCharOfListIsOK :: HasTextNonEmptyList a => (Char -> Bool) -> a -> Bool
- Penny.Copper.Util: lexeme :: Parser a -> Parser a
- Penny.Copper.Util: listIsOK :: HasTextNonEmptyList a => (Char -> Bool) -> a -> Bool
- Penny.Copper.Util: rangeLetters :: Char -> Bool
- Penny.Copper.Util: rangeLettersNumbers :: Char -> Bool
- Penny.Copper.Util: rangeLettersToSymbols :: Char -> Bool
- Penny.Copper.Util: rangeMathCurrency :: Char -> Bool
- Penny.Copper.Util: rangeSymbols :: Char -> Bool
- Penny.Copper.Util: renMaybe :: Maybe a -> (a -> Maybe Text) -> Maybe Text
- Penny.Copper.Util: spaces :: Parser ()
- Penny.Copper.Util: txtWords :: [Text] -> Text
- Penny.Liberty: BadBackgroundArg :: Text -> Error
- Penny.Liberty: BadColorName :: Text -> Error
- Penny.Liberty: BadCommodityError :: Text -> Error
- Penny.Liberty: BadComparator :: Text -> Error
- Penny.Liberty: BadExpression :: Error
- Penny.Liberty: BadFieldName :: Text -> Error
- Penny.Liberty: BadNumberError :: Text -> Error
- Penny.Liberty: BadPatternError :: Text -> Error
- Penny.Liberty: BadQtyError :: Text -> Error
- Penny.Liberty: BadSortKeyError :: Text -> Error
- Penny.Liberty: DateParseError :: Error
- Penny.Liberty: MakeMatcherFactoryError :: Text -> Error
- Penny.Liberty: UnexpectedWord :: Text -> Text -> Error
- Penny.Liberty: data Error
- Penny.Liberty: instance Show Error
- Penny.Liberty: parseCaseSelect :: Parser CaseSensitive
- Penny.Liberty: parseMatcherSelect :: Parser MatcherFactory
- Penny.Liberty: parseOperand :: Parser (DateTime -> DefaultTimeZone -> RadGroup -> CaseSensitive -> MatcherFactory -> Exceptional Error Operand)
- Penny.Liberty: parseOperator :: Parser (Token (a -> Bool))
- Penny.Liberty: parsePostFilter :: Parser (Exceptional Error PostFilterFn)
- Penny.Liberty: parseSort :: Parser (Exceptional Error Orderer)
- Penny.Liberty: type Orderer = PostFam -> PostFam -> Ordering
- Penny.Lincoln: Delimited :: Text -> [a] -> Delimited a
- Penny.Lincoln: Format :: Side -> SpaceBetween -> Format
- Penny.Lincoln: MemoLine :: TextNonEmpty -> MemoLine
- Penny.Lincoln: PostingMeta :: Maybe PostingLine -> Maybe Format -> Maybe GlobalPosting -> Maybe FilePosting -> PostingMeta
- Penny.Lincoln: PriceMeta :: Maybe PriceLine -> Maybe Format -> PriceMeta
- Penny.Lincoln: SubAccountName :: TextNonEmpty -> SubAccountName
- Penny.Lincoln: SubCommodity :: TextNonEmpty -> SubCommodity
- Penny.Lincoln: TextNonEmpty :: Char -> Text -> TextNonEmpty
- Penny.Lincoln: TopLineMeta :: Maybe TopMemoLine -> Maybe TopLineLine -> Maybe Filename -> Maybe GlobalTransaction -> Maybe FileTransaction -> TopLineMeta
- Penny.Lincoln: addSerialsToEithers :: (Serial -> TopLineMeta -> TopLineMeta) -> (Serial -> PostingMeta -> PostingMeta) -> [Either a Transaction] -> [Either a Transaction]
- Penny.Lincoln: addSerialsToList :: (Serial -> TopLineMeta -> TopLineMeta) -> (Serial -> PostingMeta -> PostingMeta) -> [Transaction] -> [Transaction]
- Penny.Lincoln: between :: Format -> SpaceBetween
- Penny.Lincoln: changePostingMeta :: (PostingMeta -> PostingMeta) -> Transaction -> Transaction
- Penny.Lincoln: changeTransactionMeta :: (TopLineMeta -> TopLineMeta) -> Transaction -> Transaction
- Penny.Lincoln: charCommodity :: Char -> Commodity
- Penny.Lincoln: class HasTextNonEmpty a
- Penny.Lincoln: class HasTextNonEmptyList a
- Penny.Lincoln: crashy :: Show e => Exceptional e a -> a
- Penny.Lincoln: data Delimited a
- Penny.Lincoln: data Format
- Penny.Lincoln: data PostingMeta
- Penny.Lincoln: data PriceMeta
- Penny.Lincoln: data TextNonEmpty
- Penny.Lincoln: data TopLineMeta
- Penny.Lincoln: emptyPostingMeta :: PostingMeta
- Penny.Lincoln: emptyTopLineMeta :: TopLineMeta
- Penny.Lincoln: filePosting :: PostingMeta -> Maybe FilePosting
- Penny.Lincoln: fileTransaction :: TopLineMeta -> Maybe FileTransaction
- Penny.Lincoln: filename :: TopLineMeta -> Maybe Filename
- Penny.Lincoln: globalPosting :: PostingMeta -> Maybe GlobalPosting
- Penny.Lincoln: globalTransaction :: TopLineMeta -> Maybe GlobalTransaction
- Penny.Lincoln: localTime :: DateTime -> LocalTime
- Penny.Lincoln: newtype MemoLine
- Penny.Lincoln: newtype SubAccountName
- Penny.Lincoln: newtype SubCommodity
- Penny.Lincoln: offsetToMins :: TimeZoneOffset -> Int
- Penny.Lincoln: pMeta :: Posting -> PostingMeta
- Penny.Lincoln: partialNewQty :: Decimal -> Qty
- Penny.Lincoln: postingFormat :: PostingMeta -> Maybe Format
- Penny.Lincoln: postingLine :: PostingMeta -> Maybe PostingLine
- Penny.Lincoln: priceFormat :: PriceMeta -> Maybe Format
- Penny.Lincoln: serials :: [a] -> [Serial]
- Penny.Lincoln: subt :: Qty -> Qty -> Maybe Qty
- Penny.Lincoln: tMeta :: TopLine -> TopLineMeta
- Penny.Lincoln: textNonEmpty :: HasTextNonEmpty a => a -> TextNonEmpty
- Penny.Lincoln: textNonEmptyList :: HasTextNonEmptyList a => a -> NonEmpty TextNonEmpty
- Penny.Lincoln: topLineLine :: TopLineMeta -> Maybe TopLineLine
- Penny.Lincoln: topMemoLine :: TopLineMeta -> Maybe TopMemoLine
- Penny.Lincoln: unMemoLine :: MemoLine -> TextNonEmpty
- Penny.Lincoln: unQty :: Qty -> Decimal
- Penny.Lincoln: unSubAccountName :: SubAccountName -> TextNonEmpty
- Penny.Lincoln: unSubCommodity :: SubCommodity -> TextNonEmpty
- Penny.Lincoln: zero :: Qty
- Penny.Lincoln.Bits: MemoLine :: TextNonEmpty -> MemoLine
- Penny.Lincoln.Bits: SubAccountName :: TextNonEmpty -> SubAccountName
- Penny.Lincoln.Bits: SubCommodity :: TextNonEmpty -> SubCommodity
- Penny.Lincoln.Bits: charCommodity :: Char -> Commodity
- Penny.Lincoln.Bits: localTime :: DateTime -> LocalTime
- Penny.Lincoln.Bits: newtype MemoLine
- Penny.Lincoln.Bits: newtype SubAccountName
- Penny.Lincoln.Bits: newtype SubCommodity
- Penny.Lincoln.Bits: offsetToMins :: TimeZoneOffset -> Int
- Penny.Lincoln.Bits: partialNewQty :: Decimal -> Qty
- Penny.Lincoln.Bits: subt :: Qty -> Qty -> Maybe Qty
- Penny.Lincoln.Bits: unMemoLine :: MemoLine -> TextNonEmpty
- Penny.Lincoln.Bits: unQty :: Qty -> Decimal
- Penny.Lincoln.Bits: unSubAccountName :: SubAccountName -> TextNonEmpty
- Penny.Lincoln.Bits: unSubCommodity :: SubCommodity -> TextNonEmpty
- Penny.Lincoln.Bits: zero :: Qty
- Penny.Lincoln.Bits.Account: Account :: NonEmpty SubAccountName -> Account
- Penny.Lincoln.Bits.Account: SubAccountName :: TextNonEmpty -> SubAccountName
- Penny.Lincoln.Bits.Account: instance Eq Account
- Penny.Lincoln.Bits.Account: instance Eq SubAccountName
- Penny.Lincoln.Bits.Account: instance Ord Account
- Penny.Lincoln.Bits.Account: instance Ord SubAccountName
- Penny.Lincoln.Bits.Account: instance Show Account
- Penny.Lincoln.Bits.Account: instance Show SubAccountName
- Penny.Lincoln.Bits.Account: newtype Account
- Penny.Lincoln.Bits.Account: newtype SubAccountName
- Penny.Lincoln.Bits.Account: unAccount :: Account -> NonEmpty SubAccountName
- Penny.Lincoln.Bits.Account: unSubAccountName :: SubAccountName -> TextNonEmpty
- Penny.Lincoln.Bits.Amount: Amount :: Qty -> Commodity -> Amount
- Penny.Lincoln.Bits.Amount: commodity :: Amount -> Commodity
- Penny.Lincoln.Bits.Amount: data Amount
- Penny.Lincoln.Bits.Amount: instance Eq Amount
- Penny.Lincoln.Bits.Amount: instance Ord Amount
- Penny.Lincoln.Bits.Amount: instance Show Amount
- Penny.Lincoln.Bits.Amount: qty :: Amount -> Qty
- Penny.Lincoln.Bits.Commodity: Commodity :: NonEmpty SubCommodity -> Commodity
- Penny.Lincoln.Bits.Commodity: SubCommodity :: TextNonEmpty -> SubCommodity
- Penny.Lincoln.Bits.Commodity: charCommodity :: Char -> Commodity
- Penny.Lincoln.Bits.Commodity: instance Eq Commodity
- Penny.Lincoln.Bits.Commodity: instance Eq SubCommodity
- Penny.Lincoln.Bits.Commodity: instance Ord Commodity
- Penny.Lincoln.Bits.Commodity: instance Ord SubCommodity
- Penny.Lincoln.Bits.Commodity: instance Show Commodity
- Penny.Lincoln.Bits.Commodity: instance Show SubCommodity
- Penny.Lincoln.Bits.Commodity: newtype Commodity
- Penny.Lincoln.Bits.Commodity: newtype SubCommodity
- Penny.Lincoln.Bits.Commodity: unCommodity :: Commodity -> NonEmpty SubCommodity
- Penny.Lincoln.Bits.Commodity: unSubCommodity :: SubCommodity -> TextNonEmpty
- Penny.Lincoln.Bits.DateTime: dateTime :: LocalTime -> TimeZoneOffset -> DateTime
- Penny.Lincoln.Bits.DateTime: localTime :: DateTime -> LocalTime
- Penny.Lincoln.Bits.DateTime: offsetToMins :: TimeZoneOffset -> Int
- Penny.Lincoln.Bits.DrCr: Credit :: DrCr
- Penny.Lincoln.Bits.DrCr: Debit :: DrCr
- Penny.Lincoln.Bits.DrCr: data DrCr
- Penny.Lincoln.Bits.DrCr: instance Eq DrCr
- Penny.Lincoln.Bits.DrCr: instance Ord DrCr
- Penny.Lincoln.Bits.DrCr: instance Show DrCr
- Penny.Lincoln.Bits.DrCr: opposite :: DrCr -> DrCr
- Penny.Lincoln.Bits.Entry: Entry :: DrCr -> Amount -> Entry
- Penny.Lincoln.Bits.Entry: amount :: Entry -> Amount
- Penny.Lincoln.Bits.Entry: data Entry
- Penny.Lincoln.Bits.Entry: drCr :: Entry -> DrCr
- Penny.Lincoln.Bits.Entry: instance Eq Entry
- Penny.Lincoln.Bits.Entry: instance Ord Entry
- Penny.Lincoln.Bits.Entry: instance Show Entry
- Penny.Lincoln.Bits.Flag: Flag :: TextNonEmpty -> Flag
- Penny.Lincoln.Bits.Flag: instance Eq Flag
- Penny.Lincoln.Bits.Flag: instance Ord Flag
- Penny.Lincoln.Bits.Flag: instance Show Flag
- Penny.Lincoln.Bits.Flag: newtype Flag
- Penny.Lincoln.Bits.Flag: unFlag :: Flag -> TextNonEmpty
- Penny.Lincoln.Bits.Memo: Memo :: [MemoLine] -> Memo
- Penny.Lincoln.Bits.Memo: MemoLine :: TextNonEmpty -> MemoLine
- Penny.Lincoln.Bits.Memo: instance Eq Memo
- Penny.Lincoln.Bits.Memo: instance Eq MemoLine
- Penny.Lincoln.Bits.Memo: instance Ord Memo
- Penny.Lincoln.Bits.Memo: instance Ord MemoLine
- Penny.Lincoln.Bits.Memo: instance Show Memo
- Penny.Lincoln.Bits.Memo: instance Show MemoLine
- Penny.Lincoln.Bits.Memo: newtype Memo
- Penny.Lincoln.Bits.Memo: newtype MemoLine
- Penny.Lincoln.Bits.Memo: unMemo :: Memo -> [MemoLine]
- Penny.Lincoln.Bits.Memo: unMemoLine :: MemoLine -> TextNonEmpty
- Penny.Lincoln.Bits.Number: Number :: TextNonEmpty -> Number
- Penny.Lincoln.Bits.Number: instance Eq Number
- Penny.Lincoln.Bits.Number: instance Ord Number
- Penny.Lincoln.Bits.Number: instance Show Number
- Penny.Lincoln.Bits.Number: newtype Number
- Penny.Lincoln.Bits.Number: unNumber :: Number -> TextNonEmpty
- Penny.Lincoln.Bits.Payee: Payee :: TextNonEmpty -> Payee
- Penny.Lincoln.Bits.Payee: instance Eq Payee
- Penny.Lincoln.Bits.Payee: instance Ord Payee
- Penny.Lincoln.Bits.Payee: instance Show Payee
- Penny.Lincoln.Bits.Payee: newtype Payee
- Penny.Lincoln.Bits.Payee: unPayee :: Payee -> TextNonEmpty
- Penny.Lincoln.Bits.PricePoint: PricePoint :: DateTime -> Price -> PriceMeta -> PricePoint
- Penny.Lincoln.Bits.PricePoint: data PricePoint
- Penny.Lincoln.Bits.PricePoint: dateTime :: PricePoint -> DateTime
- Penny.Lincoln.Bits.PricePoint: instance Show PricePoint
- Penny.Lincoln.Bits.PricePoint: ppMeta :: PricePoint -> PriceMeta
- Penny.Lincoln.Bits.PricePoint: price :: PricePoint -> Price
- Penny.Lincoln.Bits.Qty: partialNewQty :: Decimal -> Qty
- Penny.Lincoln.Bits.Qty: subt :: Qty -> Qty -> Maybe Qty
- Penny.Lincoln.Bits.Qty: unQty :: Qty -> Decimal
- Penny.Lincoln.Bits.Qty: zero :: Qty
- Penny.Lincoln.Bits.Tags: Tag :: TextNonEmpty -> Tag
- Penny.Lincoln.Bits.Tags: Tags :: [Tag] -> Tags
- Penny.Lincoln.Bits.Tags: instance Eq Tag
- Penny.Lincoln.Bits.Tags: instance Eq Tags
- Penny.Lincoln.Bits.Tags: instance Ord Tag
- Penny.Lincoln.Bits.Tags: instance Ord Tags
- Penny.Lincoln.Bits.Tags: instance Show Tag
- Penny.Lincoln.Bits.Tags: instance Show Tags
- Penny.Lincoln.Bits.Tags: newtype Tag
- Penny.Lincoln.Bits.Tags: newtype Tags
- Penny.Lincoln.Bits.Tags: unTag :: Tag -> TextNonEmpty
- Penny.Lincoln.Bits.Tags: unTags :: Tags -> [Tag]
- Penny.Lincoln.Builders: commodity :: String -> Exceptional String Commodity
- Penny.Lincoln.Builders: crashy :: Show e => Exceptional e a -> a
- Penny.Lincoln.Family: mapChildrenM :: Monad m => (a -> m b) -> Family p a -> m (Family p b)
- Penny.Lincoln.Family: mapParentM :: Monad m => (a -> m b) -> Family a c -> m (Family b c)
- Penny.Lincoln.Family.Family: mapChildrenM :: Monad m => (a -> m b) -> Family p a -> m (Family p b)
- Penny.Lincoln.Family.Family: mapParentM :: Monad m => (a -> m b) -> Family a c -> m (Family b c)
- Penny.Lincoln.HasText: Delimited :: Text -> [a] -> Delimited a
- Penny.Lincoln.HasText: class HasTextNonEmpty a
- Penny.Lincoln.HasText: class HasTextNonEmptyList a
- Penny.Lincoln.HasText: data Delimited a
- Penny.Lincoln.HasText: instance HasText MemoLine
- Penny.Lincoln.HasText: instance HasText SubAccountName
- Penny.Lincoln.HasText: instance HasText SubCommodity
- Penny.Lincoln.HasText: instance HasText TextNonEmpty
- Penny.Lincoln.HasText: instance HasText a => HasText (Delimited a)
- Penny.Lincoln.HasText: instance HasText a => HasTextList (NonEmpty a)
- Penny.Lincoln.HasText: instance HasText a => HasTextList [a]
- Penny.Lincoln.HasText: instance HasTextList Commodity
- Penny.Lincoln.HasText: instance HasTextNonEmpty Flag
- Penny.Lincoln.HasText: instance HasTextNonEmpty Number
- Penny.Lincoln.HasText: instance HasTextNonEmpty Payee
- Penny.Lincoln.HasText: instance HasTextNonEmpty SubAccountName
- Penny.Lincoln.HasText: instance HasTextNonEmpty SubCommodity
- Penny.Lincoln.HasText: instance HasTextNonEmpty Tag
- Penny.Lincoln.HasText: instance HasTextNonEmpty TextNonEmpty
- Penny.Lincoln.HasText: instance HasTextNonEmpty a => HasTextNonEmptyList (NonEmpty a)
- Penny.Lincoln.HasText: instance HasTextNonEmptyList Account
- Penny.Lincoln.HasText: instance HasTextNonEmptyList Commodity
- Penny.Lincoln.HasText: instance Show a => Show (Delimited a)
- Penny.Lincoln.HasText: textNonEmpty :: HasTextNonEmpty a => a -> TextNonEmpty
- Penny.Lincoln.HasText: textNonEmptyList :: HasTextNonEmptyList a => a -> NonEmpty TextNonEmpty
- Penny.Lincoln.Meta: CommodityOnLeft :: Side
- Penny.Lincoln.Meta: CommodityOnRight :: Side
- Penny.Lincoln.Meta: FilePosting :: Serial -> FilePosting
- Penny.Lincoln.Meta: FileTransaction :: Serial -> FileTransaction
- Penny.Lincoln.Meta: Filename :: Text -> Filename
- Penny.Lincoln.Meta: Format :: Side -> SpaceBetween -> Format
- Penny.Lincoln.Meta: GlobalPosting :: Serial -> GlobalPosting
- Penny.Lincoln.Meta: GlobalTransaction :: Serial -> GlobalTransaction
- Penny.Lincoln.Meta: NoSpaceBetween :: SpaceBetween
- Penny.Lincoln.Meta: PostingLine :: Int -> PostingLine
- Penny.Lincoln.Meta: PostingMeta :: Maybe PostingLine -> Maybe Format -> Maybe GlobalPosting -> Maybe FilePosting -> PostingMeta
- Penny.Lincoln.Meta: PriceLine :: Int -> PriceLine
- Penny.Lincoln.Meta: PriceMeta :: Maybe PriceLine -> Maybe Format -> PriceMeta
- Penny.Lincoln.Meta: SpaceBetween :: SpaceBetween
- Penny.Lincoln.Meta: TopLineLine :: Int -> TopLineLine
- Penny.Lincoln.Meta: TopLineMeta :: Maybe TopMemoLine -> Maybe TopLineLine -> Maybe Filename -> Maybe GlobalTransaction -> Maybe FileTransaction -> TopLineMeta
- Penny.Lincoln.Meta: TopMemoLine :: Int -> TopMemoLine
- Penny.Lincoln.Meta: between :: Format -> SpaceBetween
- Penny.Lincoln.Meta: data Format
- Penny.Lincoln.Meta: data PostingMeta
- Penny.Lincoln.Meta: data PriceMeta
- Penny.Lincoln.Meta: data Side
- Penny.Lincoln.Meta: data SpaceBetween
- Penny.Lincoln.Meta: data TopLineMeta
- Penny.Lincoln.Meta: emptyPostingMeta :: PostingMeta
- Penny.Lincoln.Meta: emptyTopLineMeta :: TopLineMeta
- Penny.Lincoln.Meta: filePosting :: PostingMeta -> Maybe FilePosting
- Penny.Lincoln.Meta: fileTransaction :: TopLineMeta -> Maybe FileTransaction
- Penny.Lincoln.Meta: filename :: TopLineMeta -> Maybe Filename
- Penny.Lincoln.Meta: globalPosting :: PostingMeta -> Maybe GlobalPosting
- Penny.Lincoln.Meta: globalTransaction :: TopLineMeta -> Maybe GlobalTransaction
- Penny.Lincoln.Meta: instance Eq FilePosting
- Penny.Lincoln.Meta: instance Eq FileTransaction
- Penny.Lincoln.Meta: instance Eq Filename
- Penny.Lincoln.Meta: instance Eq Format
- Penny.Lincoln.Meta: instance Eq GlobalPosting
- Penny.Lincoln.Meta: instance Eq GlobalTransaction
- Penny.Lincoln.Meta: instance Eq PostingLine
- Penny.Lincoln.Meta: instance Eq PostingMeta
- Penny.Lincoln.Meta: instance Eq PriceLine
- Penny.Lincoln.Meta: instance Eq PriceMeta
- Penny.Lincoln.Meta: instance Eq Side
- Penny.Lincoln.Meta: instance Eq SpaceBetween
- Penny.Lincoln.Meta: instance Eq TopLineLine
- Penny.Lincoln.Meta: instance Eq TopLineMeta
- Penny.Lincoln.Meta: instance Eq TopMemoLine
- Penny.Lincoln.Meta: instance Show FilePosting
- Penny.Lincoln.Meta: instance Show FileTransaction
- Penny.Lincoln.Meta: instance Show Filename
- Penny.Lincoln.Meta: instance Show Format
- Penny.Lincoln.Meta: instance Show GlobalPosting
- Penny.Lincoln.Meta: instance Show GlobalTransaction
- Penny.Lincoln.Meta: instance Show PostingLine
- Penny.Lincoln.Meta: instance Show PostingMeta
- Penny.Lincoln.Meta: instance Show PriceLine
- Penny.Lincoln.Meta: instance Show PriceMeta
- Penny.Lincoln.Meta: instance Show Side
- Penny.Lincoln.Meta: instance Show SpaceBetween
- Penny.Lincoln.Meta: instance Show TopLineLine
- Penny.Lincoln.Meta: instance Show TopLineMeta
- Penny.Lincoln.Meta: instance Show TopMemoLine
- Penny.Lincoln.Meta: newtype FilePosting
- Penny.Lincoln.Meta: newtype FileTransaction
- Penny.Lincoln.Meta: newtype Filename
- Penny.Lincoln.Meta: newtype GlobalPosting
- Penny.Lincoln.Meta: newtype GlobalTransaction
- Penny.Lincoln.Meta: newtype PostingLine
- Penny.Lincoln.Meta: newtype PriceLine
- Penny.Lincoln.Meta: newtype TopLineLine
- Penny.Lincoln.Meta: newtype TopMemoLine
- Penny.Lincoln.Meta: postingFormat :: PostingMeta -> Maybe Format
- Penny.Lincoln.Meta: postingLine :: PostingMeta -> Maybe PostingLine
- Penny.Lincoln.Meta: priceFormat :: PriceMeta -> Maybe Format
- Penny.Lincoln.Meta: priceLine :: PriceMeta -> Maybe PriceLine
- Penny.Lincoln.Meta: side :: Format -> Side
- Penny.Lincoln.Meta: topLineLine :: TopLineMeta -> Maybe TopLineLine
- Penny.Lincoln.Meta: topMemoLine :: TopLineMeta -> Maybe TopMemoLine
- Penny.Lincoln.Meta: unFilePosting :: FilePosting -> Serial
- Penny.Lincoln.Meta: unFileTransaction :: FileTransaction -> Serial
- Penny.Lincoln.Meta: unFilename :: Filename -> Text
- Penny.Lincoln.Meta: unGlobalPosting :: GlobalPosting -> Serial
- Penny.Lincoln.Meta: unGlobalTransaction :: GlobalTransaction -> Serial
- Penny.Lincoln.Meta: unPostingLine :: PostingLine -> Int
- Penny.Lincoln.Meta: unPriceLine :: PriceLine -> Int
- Penny.Lincoln.Meta: unTopLineLine :: TopLineLine -> Int
- Penny.Lincoln.Meta: unTopMemoLine :: TopMemoLine -> Int
- Penny.Lincoln.Predicates: commodityAny :: (Text -> Bool) -> PostFam -> Bool
- Penny.Lincoln.Predicates: commodityLevel :: Int -> (Text -> Bool) -> PostFam -> Bool
- Penny.Lincoln.Queries: postingFormat :: PostFam -> Maybe Format
- Penny.Lincoln.Queries: postingMeta :: PostFam -> PostingMeta
- Penny.Lincoln.Queries: topLineMeta :: PostFam -> TopLineMeta
- Penny.Lincoln.Serial: data NextBack
- Penny.Lincoln.Serial: data NextFwd
- Penny.Lincoln.Serial: initNexts :: Int -> (NextFwd, NextBack)
- Penny.Lincoln.Serial: instance Eq Index
- Penny.Lincoln.Serial: instance Eq Total
- Penny.Lincoln.Serial: instance Ord Index
- Penny.Lincoln.Serial: instance Ord Total
- Penny.Lincoln.Serial: instance Show Index
- Penny.Lincoln.Serial: instance Show NextBack
- Penny.Lincoln.Serial: instance Show NextFwd
- Penny.Lincoln.Serial: instance Show Total
- Penny.Lincoln.Serial: serialChildrenInFamily :: (Serial -> cOld -> cNew) -> Family p cOld -> State (NextFwd, NextBack) (Family p cNew)
- Penny.Lincoln.Serial: serialEithers :: Monad m => (Serial -> a -> m c) -> (Serial -> b -> m d) -> [Either a b] -> m [Either c d]
- Penny.Lincoln.Serial: serialItemsM :: Monad m => (Serial -> a -> m b) -> [a] -> m [b]
- Penny.Lincoln.Serial: serials :: [a] -> [Serial]
- Penny.Lincoln.TextNonEmpty: TextNonEmpty :: Char -> Text -> TextNonEmpty
- Penny.Lincoln.TextNonEmpty: all :: (Char -> Bool) -> TextNonEmpty -> Bool
- Penny.Lincoln.TextNonEmpty: any :: (Char -> Bool) -> TextNonEmpty -> Bool
- Penny.Lincoln.TextNonEmpty: data TextNonEmpty
- Penny.Lincoln.TextNonEmpty: first :: TextNonEmpty -> Char
- Penny.Lincoln.TextNonEmpty: instance Eq TextNonEmpty
- Penny.Lincoln.TextNonEmpty: instance Ord TextNonEmpty
- Penny.Lincoln.TextNonEmpty: instance Show TextNonEmpty
- Penny.Lincoln.TextNonEmpty: rest :: TextNonEmpty -> Text
- Penny.Lincoln.TextNonEmpty: toText :: TextNonEmpty -> Text
- Penny.Lincoln.TextNonEmpty: unsafeTextNonEmpty :: String -> TextNonEmpty
- Penny.Lincoln.Transaction: addSerialsToEithers :: (Serial -> TopLineMeta -> TopLineMeta) -> (Serial -> PostingMeta -> PostingMeta) -> [Either a Transaction] -> [Either a Transaction]
- Penny.Lincoln.Transaction: addSerialsToList :: (Serial -> TopLineMeta -> TopLineMeta) -> (Serial -> PostingMeta -> PostingMeta) -> [Transaction] -> [Transaction]
- Penny.Lincoln.Transaction: changePostingMeta :: (PostingMeta -> PostingMeta) -> Transaction -> Transaction
- Penny.Lincoln.Transaction: changeTransactionMeta :: (TopLineMeta -> TopLineMeta) -> Transaction -> Transaction
- Penny.Lincoln.Transaction: pMeta :: Posting -> PostingMeta
- Penny.Lincoln.Transaction: tMeta :: TopLine -> TopLineMeta
- Penny.Lincoln.Transaction.Unverified: entry :: Posting -> Maybe Entry
- Penny.Zinc: T :: CaseSensitive -> (CaseSensitive -> Text -> Exceptional Text (Text -> Bool)) -> DateTime -> DefaultTimeZone -> RadGroup -> T
- Penny.Zinc: currentTime :: T -> DateTime
- Penny.Zinc: data T
- Penny.Zinc: defaultFromRuntime :: DefaultTimeZone -> RadGroup -> Runtime -> T
- Penny.Zinc: defaultTimeZone :: T -> DefaultTimeZone
- Penny.Zinc: factory :: T -> CaseSensitive -> Text -> Exceptional Text (Text -> Bool)
- Penny.Zinc: radGroup :: T -> RadGroup
- Penny.Zinc: runPenny :: Runtime -> DefaultTimeZone -> RadGroup -> (Runtime -> T) -> [Report] -> IO ()
- Penny.Zinc.Error: ParseError :: Text -> Error
- Penny.Zinc.Error: ReportError :: Text -> Error
- Penny.Zinc.Error: data Error
- Penny.Zinc.Error: instance Show Error
- Penny.Zinc.Error: printError :: Error -> Text
- Penny.Zinc.Help: help :: Text
- Penny.Zinc.Parser: T :: CaseSensitive -> (CaseSensitive -> Text -> Exceptional Text (Text -> Bool)) -> DateTime -> DefaultTimeZone -> RadGroup -> T
- Penny.Zinc.Parser: currentTime :: T -> DateTime
- Penny.Zinc.Parser: data T
- Penny.Zinc.Parser: defaultFromRuntime :: DefaultTimeZone -> RadGroup -> Runtime -> T
- Penny.Zinc.Parser: defaultTimeZone :: T -> DefaultTimeZone
- Penny.Zinc.Parser: factory :: T -> CaseSensitive -> Text -> Exceptional Text (Text -> Bool)
- Penny.Zinc.Parser: parser :: Runtime -> DefaultTimeZone -> RadGroup -> (Runtime -> T) -> [Report] -> Parser (IO ())
- Penny.Zinc.Parser: radGroup :: T -> RadGroup
- Penny.Zinc.Parser: sensitive :: T -> CaseSensitive
- Penny.Zinc.Parser.Defaults: T :: CaseSensitive -> (CaseSensitive -> Text -> Exceptional Text (Text -> Bool)) -> DateTime -> DefaultTimeZone -> RadGroup -> T
- Penny.Zinc.Parser.Defaults: currentTime :: T -> DateTime
- Penny.Zinc.Parser.Defaults: data T
- Penny.Zinc.Parser.Defaults: defaultFromRuntime :: DefaultTimeZone -> RadGroup -> Runtime -> T
- Penny.Zinc.Parser.Defaults: defaultTimeZone :: T -> DefaultTimeZone
- Penny.Zinc.Parser.Defaults: factory :: T -> CaseSensitive -> Text -> Exceptional Text (Text -> Bool)
- Penny.Zinc.Parser.Defaults: radGroup :: T -> RadGroup
- Penny.Zinc.Parser.Defaults: sensitive :: T -> CaseSensitive
- Penny.Zinc.Parser.Filter: LibertyError :: Error -> Error
- Penny.Zinc.Parser.Filter: NeedsHelp :: NeedsHelp
- Penny.Zinc.Parser.Filter: Result :: (CaseSensitive -> Text -> Exceptional Text (Text -> Bool)) -> CaseSensitive -> ([Transaction] -> [Box LibertyMeta]) -> Result
- Penny.Zinc.Parser.Filter: TokenParseError :: Error
- Penny.Zinc.Parser.Filter: data Error
- Penny.Zinc.Parser.Filter: data NeedsHelp
- Penny.Zinc.Parser.Filter: data Result
- Penny.Zinc.Parser.Filter: instance Show Error
- Penny.Zinc.Parser.Filter: instance Show NeedsHelp
- Penny.Zinc.Parser.Filter: parseFilter :: T -> Parser (Exceptional Error (Either NeedsHelp Result))
- Penny.Zinc.Parser.Filter: resultFactory :: Result -> CaseSensitive -> Text -> Exceptional Text (Text -> Bool)
- Penny.Zinc.Parser.Filter: resultSensitive :: Result -> CaseSensitive
- Penny.Zinc.Parser.Filter: sorterFilterer :: Result -> [Transaction] -> [Box LibertyMeta]
- Penny.Zinc.Parser.Ledgers: filenames :: Parser [Filename]
- Penny.Zinc.Parser.Ledgers: parseLedgers :: DefaultTimeZone -> RadGroup -> [(Filename, Text)] -> Exceptional Error ([Transaction], [PricePoint])
- Penny.Zinc.Parser.Ledgers: readLedgers :: [Filename] -> IO [(Filename, Text)]
- Penny.Zinc.Parser.Report: report :: [Report] -> Parser ReportFunc
+ Penny: Account :: SortField
+ Penny: Ascending :: SortOrder
+ Penny: AutoTarget :: Target
+ Penny: Commodity :: SortField
+ Penny: Date :: SortField
+ Penny: Defaults :: Bool -> Matcher -> Bool -> Maybe Scheme -> [Scheme] -> [(SortField, SortOrder)] -> (Commodity -> Qty -> Text) -> Bool -> SortOrder -> Bool -> Target -> SortOrder -> SortBy -> (Commodity -> Qty -> Text) -> Fields Bool -> Int -> Bool -> (Box -> Text) -> (Box -> Text) -> (Commodity -> Qty -> Text) -> Int -> Int -> Int -> Spacers Int -> Defaults
+ Penny: Descending :: SortOrder
+ Penny: DrCr :: SortField
+ Penny: EvenAndOdd :: a -> a -> EvenAndOdd a
+ Penny: Exact :: Matcher
+ Penny: Fields :: a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> Fields a
+ Penny: Flag :: SortField
+ Penny: Labels :: a -> a -> a -> a -> Labels a
+ Penny: ManualTarget :: String -> Target
+ Penny: Number :: SortField
+ Penny: PCRE :: Matcher
+ Penny: Payee :: SortField
+ Penny: PostingMemo :: SortField
+ Penny: Qty :: SortField
+ Penny: Scheme :: String -> String -> TextSpecs -> Scheme
+ Penny: SortByName :: SortBy
+ Penny: SortByQty :: SortBy
+ Penny: Spacers :: a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> Spacers a
+ Penny: TDFA :: Matcher
+ Penny: TransactionMemo :: SortField
+ Penny: Within :: Matcher
+ Penny: additionalSchemes :: Defaults -> [Scheme]
+ Penny: balanceAsIs :: a -> Qty -> Text
+ Penny: balanceFormat :: Defaults -> Commodity -> Qty -> Text
+ Penny: balanceOrder :: Defaults -> SortOrder
+ Penny: balanceShowZeroBalances :: Defaults -> Bool
+ Penny: caseSensitive :: Defaults -> Bool
+ Penny: colorToFile :: Defaults -> Bool
+ Penny: convertFormat :: Defaults -> Commodity -> Qty -> Text
+ Penny: convertOrder :: Defaults -> SortOrder
+ Penny: convertShowZeroBalances :: Defaults -> Bool
+ Penny: convertSortBy :: Defaults -> SortBy
+ Penny: convertTarget :: Defaults -> Target
+ Penny: credit :: Labels a -> a
+ Penny: data Defaults
+ Penny: data EvenAndOdd a
+ Penny: data Fields a
+ Penny: data Labels a
+ Penny: data Matcher
+ Penny: data Runtime
+ Penny: data Scheme
+ Penny: data SortBy
+ Penny: data SortField
+ Penny: data SortOrder
+ Penny: data Spacers a
+ Penny: data Target
+ Penny: data Text :: *
+ Penny: debit :: Labels a -> a
+ Penny: defaultQtyFormat :: Qty -> Text
+ Penny: defaultScheme :: Defaults -> Maybe Scheme
+ Penny: description :: Scheme -> String
+ Penny: environment :: Runtime -> [(String, String)]
+ Penny: eoEven :: EvenAndOdd a -> a
+ Penny: eoOdd :: EvenAndOdd a -> a
+ Penny: fAccount :: Fields a -> a
+ Penny: fDate :: Fields a -> a
+ Penny: fFilePosting :: Fields a -> a
+ Penny: fFileTransaction :: Fields a -> a
+ Penny: fFilename :: Fields a -> a
+ Penny: fFiltered :: Fields a -> a
+ Penny: fFlag :: Fields a -> a
+ Penny: fGlobalPosting :: Fields a -> a
+ Penny: fGlobalTransaction :: Fields a -> a
+ Penny: fLineNum :: Fields a -> a
+ Penny: fMemo :: Fields a -> a
+ Penny: fNumber :: Fields a -> a
+ Penny: fPayee :: Fields a -> a
+ Penny: fPostingCmdty :: Fields a -> a
+ Penny: fPostingDrCr :: Fields a -> a
+ Penny: fPostingQty :: Fields a -> a
+ Penny: fRevFilePosting :: Fields a -> a
+ Penny: fRevFileTransaction :: Fields a -> a
+ Penny: fRevFiltered :: Fields a -> a
+ Penny: fRevGlobalPosting :: Fields a -> a
+ Penny: fRevGlobalTransaction :: Fields a -> a
+ Penny: fRevSorted :: Fields a -> a
+ Penny: fRevVisible :: Fields a -> a
+ Penny: fSorted :: Fields a -> a
+ Penny: fTags :: Fields a -> a
+ Penny: fTotalCmdty :: Fields a -> a
+ Penny: fTotalDrCr :: Fields a -> a
+ Penny: fTotalQty :: Fields a -> a
+ Penny: fVisible :: Fields a -> a
+ Penny: instance Eq a => Eq (Fields a)
+ Penny: instance Eq a => Eq (Spacers a)
+ Penny: instance Show Target
+ Penny: instance Show a => Show (Fields a)
+ Penny: instance Show a => Show (Spacers a)
+ Penny: matcher :: Defaults -> Matcher
+ Penny: name :: Scheme -> String
+ Penny: other :: Labels a -> a
+ Penny: pack :: String -> Text
+ Penny: postingsAccountAllocation :: Defaults -> Int
+ Penny: postingsBalanceFormat :: Defaults -> Commodity -> Qty -> Text
+ Penny: postingsDateFormat :: Defaults -> Box -> Text
+ Penny: postingsFields :: Defaults -> Fields Bool
+ Penny: postingsPayeeAllocation :: Defaults -> Int
+ Penny: postingsQtyFormat :: Defaults -> Box -> Text
+ Penny: postingsShowZeroBalances :: Defaults -> Bool
+ Penny: postingsSpacers :: Defaults -> Spacers Int
+ Penny: postingsSubAccountLength :: Defaults -> Int
+ Penny: postingsWidth :: Defaults -> Int
+ Penny: qtyAsIs :: Box -> Text
+ Penny: runPenny :: (Runtime -> Defaults) -> IO ()
+ Penny: sAccount :: Spacers a -> a
+ Penny: sDate :: Spacers a -> a
+ Penny: sFilePosting :: Spacers a -> a
+ Penny: sFileTransaction :: Spacers a -> a
+ Penny: sFiltered :: Spacers a -> a
+ Penny: sFlag :: Spacers a -> a
+ Penny: sGlobalPosting :: Spacers a -> a
+ Penny: sGlobalTransaction :: Spacers a -> a
+ Penny: sLineNum :: Spacers a -> a
+ Penny: sNumber :: Spacers a -> a
+ Penny: sPayee :: Spacers a -> a
+ Penny: sPostingCmdty :: Spacers a -> a
+ Penny: sPostingDrCr :: Spacers a -> a
+ Penny: sPostingQty :: Spacers a -> a
+ Penny: sRevFilePosting :: Spacers a -> a
+ Penny: sRevFileTransaction :: Spacers a -> a
+ Penny: sRevFiltered :: Spacers a -> a
+ Penny: sRevGlobalPosting :: Spacers a -> a
+ Penny: sRevGlobalTransaction :: Spacers a -> a
+ Penny: sRevSorted :: Spacers a -> a
+ Penny: sRevVisible :: Spacers a -> a
+ Penny: sSorted :: Spacers a -> a
+ Penny: sTotalCmdty :: Spacers a -> a
+ Penny: sTotalDrCr :: Spacers a -> a
+ Penny: sVisible :: Spacers a -> a
+ Penny: sorter :: Defaults -> [(SortField, SortOrder)]
+ Penny: switchBackground :: Background8 -> Background256 -> TextSpec -> TextSpec
+ Penny: switchForeground :: Foreground8 -> Foreground256 -> TextSpec -> TextSpec
+ Penny: textSpecs :: Scheme -> TextSpecs
+ Penny: type Box = Box PostMeta
+ Penny: type TextSpecs = Labels (EvenAndOdd TextSpec)
+ Penny: widthFromRuntime :: Runtime -> Int
+ Penny: yearMonthDay :: Box -> Text
+ Penny: zero :: Labels a -> a
+ Penny.Brenner: CommodityOnLeft :: Side
+ Penny.Brenner: CommodityOnRight :: Side
+ Penny.Brenner: Config :: Maybe FitAcct -> [(String, FitAcct)] -> Config
+ Penny.Brenner: FitAcct :: String -> String -> String -> String -> GroupSpecs -> Translator -> Side -> SpaceBetween -> (String, FitFileLocation -> IO (Exceptional String [Posting])) -> FitAcct
+ Penny.Brenner: GroupAll :: GroupSpec
+ Penny.Brenner: GroupLarge :: GroupSpec
+ Penny.Brenner: GroupSpecs :: GroupSpec -> GroupSpec -> GroupSpecs
+ Penny.Brenner: IncreaseIsCredit :: Translator
+ Penny.Brenner: IncreaseIsDebit :: Translator
+ Penny.Brenner: NoGrouping :: GroupSpec
+ Penny.Brenner: NoSpaceBetween :: SpaceBetween
+ Penny.Brenner: SpaceBetween :: SpaceBetween
+ Penny.Brenner: brennerMain :: Config -> IO ()
+ Penny.Brenner: currency :: FitAcct -> String
+ Penny.Brenner: data Config
+ Penny.Brenner: data FitAcct
+ Penny.Brenner: data GroupSpec
+ Penny.Brenner: data GroupSpecs
+ Penny.Brenner: data Side
+ Penny.Brenner: data SpaceBetween
+ Penny.Brenner: data Translator
+ Penny.Brenner: dbLocation :: FitAcct -> String
+ Penny.Brenner: defaultAcct :: FitAcct -> String
+ Penny.Brenner: defaultFitAcct :: Config -> Maybe FitAcct
+ Penny.Brenner: groupSpecs :: FitAcct -> GroupSpecs
+ Penny.Brenner: instance Eq Arg
+ Penny.Brenner: instance Show Arg
+ Penny.Brenner: instance Show Config
+ Penny.Brenner: instance Show FitAcct
+ Penny.Brenner: left :: GroupSpecs -> GroupSpec
+ Penny.Brenner: moreFitAccts :: Config -> [(String, FitAcct)]
+ Penny.Brenner: parser :: FitAcct -> (String, FitFileLocation -> IO (Exceptional String [Posting]))
+ Penny.Brenner: pennyAcct :: FitAcct -> String
+ Penny.Brenner: right :: GroupSpecs -> GroupSpec
+ Penny.Brenner: side :: FitAcct -> Side
+ Penny.Brenner: spaceBetween :: FitAcct -> SpaceBetween
+ Penny.Brenner: translator :: FitAcct -> Translator
+ Penny.Brenner.Amex: parser :: (String, FitFileLocation -> IO (Exceptional String [Posting]))
+ Penny.Brenner.BofA: instance Eq Label
+ Penny.Brenner.BofA: instance Eq TagData
+ Penny.Brenner.BofA: instance Eq TagName
+ Penny.Brenner.BofA: instance Show Label
+ Penny.Brenner.BofA: instance Show TagData
+ Penny.Brenner.BofA: instance Show TagName
+ Penny.Brenner.BofA: parser :: (String, FitFileLocation -> IO (Exceptional String [Posting]))
+ Penny.Brenner.Clear: instance Eq Arg
+ Penny.Brenner.Clear: instance Show Arg
+ Penny.Brenner.Clear: instance Show Opts
+ Penny.Brenner.Clear: mode :: Maybe FitAcct -> Mode (Exceptional String (IO ()))
+ Penny.Brenner.Database: instance Eq Arg
+ Penny.Brenner.Database: instance Show Arg
+ Penny.Brenner.Database: mode :: Maybe FitAcct -> Mode (Exceptional String (IO ()))
+ Penny.Brenner.Import: instance Eq Arg
+ Penny.Brenner.Import: instance Show Arg
+ Penny.Brenner.Import: mode :: Maybe FitAcct -> Mode (Exceptional String (IO ()))
+ Penny.Brenner.Merge: instance Eq Arg
+ Penny.Brenner.Merge: instance Show Arg
+ Penny.Brenner.Merge: mode :: Maybe FitAcct -> Mode (Exceptional String (IO ()))
+ Penny.Brenner.Print: instance Eq Arg
+ Penny.Brenner.Print: instance Show Arg
+ Penny.Brenner.Print: mode :: Maybe FitAcct -> Mode (Exceptional String (IO ()))
+ Penny.Brenner.Types: AllowNew :: Bool -> AllowNew
+ Penny.Brenner.Types: Config :: Maybe FitAcct -> [(Name, FitAcct)] -> Config
+ Penny.Brenner.Types: Currency :: Commodity -> Currency
+ Penny.Brenner.Types: Date :: Day -> Date
+ Penny.Brenner.Types: DbLocation :: Text -> DbLocation
+ Penny.Brenner.Types: Decrease :: IncDec
+ Penny.Brenner.Types: DefaultAcct :: Account -> DefaultAcct
+ Penny.Brenner.Types: Desc :: Text -> Desc
+ Penny.Brenner.Types: FitAcct :: DbLocation -> PennyAcct -> DefaultAcct -> Currency -> GroupSpecs -> Translator -> Side -> SpaceBetween -> (String, FitFileLocation -> IO (Exceptional String [Posting])) -> FitAcct
+ Penny.Brenner.Types: FitFileLocation :: String -> FitFileLocation
+ Penny.Brenner.Types: FitId :: Text -> FitId
+ Penny.Brenner.Types: Increase :: IncDec
+ Penny.Brenner.Types: IncreaseIsCredit :: Translator
+ Penny.Brenner.Types: IncreaseIsDebit :: Translator
+ Penny.Brenner.Types: Name :: Text -> Name
+ Penny.Brenner.Types: Payee :: Text -> Payee
+ Penny.Brenner.Types: PennyAcct :: Account -> PennyAcct
+ Penny.Brenner.Types: Posting :: Date -> Desc -> IncDec -> Amount -> Payee -> FitId -> Posting
+ Penny.Brenner.Types: UNumber :: Integer -> UNumber
+ Penny.Brenner.Types: amount :: Posting -> Amount
+ Penny.Brenner.Types: currency :: FitAcct -> Currency
+ Penny.Brenner.Types: data Amount
+ Penny.Brenner.Types: data Config
+ Penny.Brenner.Types: data FitAcct
+ Penny.Brenner.Types: data IncDec
+ Penny.Brenner.Types: data Posting
+ Penny.Brenner.Types: data Translator
+ Penny.Brenner.Types: date :: Posting -> Date
+ Penny.Brenner.Types: dbLocation :: FitAcct -> DbLocation
+ Penny.Brenner.Types: defaultAcct :: FitAcct -> DefaultAcct
+ Penny.Brenner.Types: defaultFitAcct :: Config -> Maybe FitAcct
+ Penny.Brenner.Types: desc :: Posting -> Desc
+ Penny.Brenner.Types: fitId :: Posting -> FitId
+ Penny.Brenner.Types: groupSpecs :: FitAcct -> GroupSpecs
+ Penny.Brenner.Types: incDec :: Posting -> IncDec
+ Penny.Brenner.Types: instance Eq AllowNew
+ Penny.Brenner.Types: instance Eq Amount
+ Penny.Brenner.Types: instance Eq Currency
+ Penny.Brenner.Types: instance Eq Date
+ Penny.Brenner.Types: instance Eq DbLocation
+ Penny.Brenner.Types: instance Eq DefaultAcct
+ Penny.Brenner.Types: instance Eq Desc
+ Penny.Brenner.Types: instance Eq FitFileLocation
+ Penny.Brenner.Types: instance Eq FitId
+ Penny.Brenner.Types: instance Eq IncDec
+ Penny.Brenner.Types: instance Eq Name
+ Penny.Brenner.Types: instance Eq Payee
+ Penny.Brenner.Types: instance Eq PennyAcct
+ Penny.Brenner.Types: instance Eq Translator
+ Penny.Brenner.Types: instance Eq UNumber
+ Penny.Brenner.Types: instance Ord Amount
+ Penny.Brenner.Types: instance Ord Date
+ Penny.Brenner.Types: instance Ord Desc
+ Penny.Brenner.Types: instance Ord FitId
+ Penny.Brenner.Types: instance Ord Payee
+ Penny.Brenner.Types: instance Ord UNumber
+ Penny.Brenner.Types: instance Read Amount
+ Penny.Brenner.Types: instance Read Date
+ Penny.Brenner.Types: instance Read Desc
+ Penny.Brenner.Types: instance Read FitId
+ Penny.Brenner.Types: instance Read IncDec
+ Penny.Brenner.Types: instance Read Payee
+ Penny.Brenner.Types: instance Read Posting
+ Penny.Brenner.Types: instance Read UNumber
+ Penny.Brenner.Types: instance Serialize Amount
+ Penny.Brenner.Types: instance Serialize Date
+ Penny.Brenner.Types: instance Serialize Desc
+ Penny.Brenner.Types: instance Serialize FitId
+ Penny.Brenner.Types: instance Serialize IncDec
+ Penny.Brenner.Types: instance Serialize Payee
+ Penny.Brenner.Types: instance Serialize Posting
+ Penny.Brenner.Types: instance Serialize UNumber
+ Penny.Brenner.Types: instance Show AllowNew
+ Penny.Brenner.Types: instance Show Amount
+ Penny.Brenner.Types: instance Show Currency
+ Penny.Brenner.Types: instance Show Date
+ Penny.Brenner.Types: instance Show DbLocation
+ Penny.Brenner.Types: instance Show DefaultAcct
+ Penny.Brenner.Types: instance Show Desc
+ Penny.Brenner.Types: instance Show FitFileLocation
+ Penny.Brenner.Types: instance Show FitId
+ Penny.Brenner.Types: instance Show IncDec
+ Penny.Brenner.Types: instance Show Name
+ Penny.Brenner.Types: instance Show Payee
+ Penny.Brenner.Types: instance Show PennyAcct
+ Penny.Brenner.Types: instance Show Posting
+ Penny.Brenner.Types: instance Show Translator
+ Penny.Brenner.Types: instance Show UNumber
+ Penny.Brenner.Types: mkAmount :: String -> Maybe Amount
+ Penny.Brenner.Types: moreFitAccts :: Config -> [(Name, FitAcct)]
+ Penny.Brenner.Types: newtype AllowNew
+ Penny.Brenner.Types: newtype Currency
+ Penny.Brenner.Types: newtype Date
+ Penny.Brenner.Types: newtype DbLocation
+ Penny.Brenner.Types: newtype DefaultAcct
+ Penny.Brenner.Types: newtype Desc
+ Penny.Brenner.Types: newtype FitFileLocation
+ Penny.Brenner.Types: newtype FitId
+ Penny.Brenner.Types: newtype Name
+ Penny.Brenner.Types: newtype Payee
+ Penny.Brenner.Types: newtype PennyAcct
+ Penny.Brenner.Types: newtype UNumber
+ Penny.Brenner.Types: parser :: FitAcct -> (String, FitFileLocation -> IO (Exceptional String [Posting]))
+ Penny.Brenner.Types: payee :: Posting -> Payee
+ Penny.Brenner.Types: pennyAcct :: FitAcct -> PennyAcct
+ Penny.Brenner.Types: side :: FitAcct -> Side
+ Penny.Brenner.Types: spaceBetween :: FitAcct -> SpaceBetween
+ Penny.Brenner.Types: translate :: IncDec -> Translator -> DrCr
+ Penny.Brenner.Types: translator :: FitAcct -> Translator
+ Penny.Brenner.Types: type DbList = [(UNumber, Posting)]
+ Penny.Brenner.Types: type DbMap = Map UNumber Posting
+ Penny.Brenner.Types: unAllowNew :: AllowNew -> Bool
+ Penny.Brenner.Types: unCurrency :: Currency -> Commodity
+ Penny.Brenner.Types: unDate :: Date -> Day
+ Penny.Brenner.Types: unDbLocation :: DbLocation -> Text
+ Penny.Brenner.Types: unDefaultAcct :: DefaultAcct -> Account
+ Penny.Brenner.Types: unDesc :: Desc -> Text
+ Penny.Brenner.Types: unFitFileLocation :: FitFileLocation -> String
+ Penny.Brenner.Types: unFitId :: FitId -> Text
+ Penny.Brenner.Types: unName :: Name -> Text
+ Penny.Brenner.Types: unPayee :: Payee -> Text
+ Penny.Brenner.Types: unPennyAcct :: PennyAcct -> Account
+ Penny.Brenner.Types: unUNumber :: UNumber -> Integer
+ Penny.Brenner.Util: brenner :: String
+ Penny.Brenner.Util: label :: String -> Text -> String
+ Penny.Brenner.Util: loadDb :: AllowNew -> DbLocation -> IO (Exceptional String DbList)
+ Penny.Brenner.Util: parseQty :: Amount -> Qty
+ Penny.Brenner.Util: quitOnError :: IO (Exceptional String a) -> IO a
+ Penny.Brenner.Util: readDbTuple :: ByteString -> Exceptional String DbList
+ Penny.Brenner.Util: saveDb :: DbLocation -> DbList -> IO ()
+ Penny.Brenner.Util: saveDbTuple :: DbList -> ByteString
+ Penny.Brenner.Util: showDbPair :: (UNumber, Posting) -> String
+ Penny.Brenner.Util: showPosting :: Posting -> String
+ Penny.Brenner.Util: version :: Int
+ Penny.Cabin.Balance: multiCommodity :: Report
+ Penny.Cabin.Balance.Convert: Opts :: (Commodity -> Qty -> Text) -> ShowZeroBalances -> Sorter -> To -> DateTime -> Opts
+ Penny.Cabin.Balance.Convert: balanceFormat :: Opts -> Commodity -> Qty -> Text
+ Penny.Cabin.Balance.Convert: cmdLineReport :: DefaultOpts -> Report
+ Penny.Cabin.Balance.Convert: data Opts
+ Penny.Cabin.Balance.Convert: dateTime :: Opts -> DateTime
+ Penny.Cabin.Balance.Convert: getSorter :: SortOrder -> SortBy -> Sorter
+ Penny.Cabin.Balance.Convert: report :: Opts -> [PricePoint] -> [Box a] -> Exceptional Text [PreChunk]
+ Penny.Cabin.Balance.Convert: showZeroBalances :: Opts -> ShowZeroBalances
+ Penny.Cabin.Balance.Convert: sorter :: Opts -> Sorter
+ Penny.Cabin.Balance.Convert: target :: Opts -> To
+ Penny.Cabin.Balance.Convert: type Sorter = (SubAccount, BottomLine) -> (SubAccount, BottomLine) -> Ordering
+ Penny.Cabin.Balance.Convert.Chunker: MainRow :: Int -> Text -> BottomLine -> MainRow
+ Penny.Cabin.Balance.Convert.Chunker: OneColRow :: Int -> Text -> OneColRow
+ Penny.Cabin.Balance.Convert.Chunker: RMain :: MainRow -> Row
+ Penny.Cabin.Balance.Convert.Chunker: ROneCol :: OneColRow -> Row
+ Penny.Cabin.Balance.Convert.Chunker: data MainRow
+ Penny.Cabin.Balance.Convert.Chunker: data OneColRow
+ Penny.Cabin.Balance.Convert.Chunker: data Row
+ Penny.Cabin.Balance.Convert.Chunker: instance Applicative Columns
+ Penny.Cabin.Balance.Convert.Chunker: instance Functor Columns
+ Penny.Cabin.Balance.Convert.Chunker: instance Show a => Show (Columns a)
+ Penny.Cabin.Balance.Convert.Chunker: mrBottomLine :: MainRow -> BottomLine
+ Penny.Cabin.Balance.Convert.Chunker: mrIndentation :: MainRow -> Int
+ Penny.Cabin.Balance.Convert.Chunker: mrText :: MainRow -> Text
+ Penny.Cabin.Balance.Convert.Chunker: ocIndentation :: OneColRow -> Int
+ Penny.Cabin.Balance.Convert.Chunker: ocText :: OneColRow -> Text
+ Penny.Cabin.Balance.Convert.Chunker: rowsToChunks :: (Qty -> Text) -> [Row] -> [PreChunk]
+ Penny.Cabin.Balance.Convert.Options: DefaultOpts :: ShowZeroBalances -> Target -> SortOrder -> SortBy -> (Commodity -> Qty -> Text) -> DefaultOpts
+ Penny.Cabin.Balance.Convert.Options: data DefaultOpts
+ Penny.Cabin.Balance.Convert.Options: defaultOptions :: DefaultOpts
+ Penny.Cabin.Balance.Convert.Options: format :: DefaultOpts -> Commodity -> Qty -> Text
+ Penny.Cabin.Balance.Convert.Options: showZeroBalances :: DefaultOpts -> ShowZeroBalances
+ Penny.Cabin.Balance.Convert.Options: sortBy :: DefaultOpts -> SortBy
+ Penny.Cabin.Balance.Convert.Options: sortOrder :: DefaultOpts -> SortOrder
+ Penny.Cabin.Balance.Convert.Options: target :: DefaultOpts -> Target
+ Penny.Cabin.Balance.Convert.Options: toParserOpts :: DefaultOpts -> Runtime -> Opts
+ Penny.Cabin.Balance.Convert.Parser: AutoTarget :: Target
+ Penny.Cabin.Balance.Convert.Parser: ManualTarget :: To -> Target
+ Penny.Cabin.Balance.Convert.Parser: Opts :: ShowZeroBalances -> Target -> DateTime -> SortOrder -> SortBy -> Bool -> Opts
+ Penny.Cabin.Balance.Convert.Parser: SortByName :: SortBy
+ Penny.Cabin.Balance.Convert.Parser: SortByQty :: SortBy
+ Penny.Cabin.Balance.Convert.Parser: allOptSpecs :: [OptSpec (Opts -> Exceptional String Opts)]
+ Penny.Cabin.Balance.Convert.Parser: data Opts
+ Penny.Cabin.Balance.Convert.Parser: data SortBy
+ Penny.Cabin.Balance.Convert.Parser: data Target
+ Penny.Cabin.Balance.Convert.Parser: dateTime :: Opts -> DateTime
+ Penny.Cabin.Balance.Convert.Parser: instance Eq SortBy
+ Penny.Cabin.Balance.Convert.Parser: instance Ord SortBy
+ Penny.Cabin.Balance.Convert.Parser: instance Show SortBy
+ Penny.Cabin.Balance.Convert.Parser: showHelp :: Opts -> Bool
+ Penny.Cabin.Balance.Convert.Parser: showZeroBalances :: Opts -> ShowZeroBalances
+ Penny.Cabin.Balance.Convert.Parser: sortBy :: Opts -> SortBy
+ Penny.Cabin.Balance.Convert.Parser: sortOrder :: Opts -> SortOrder
+ Penny.Cabin.Balance.Convert.Parser: target :: Opts -> Target
+ Penny.Cabin.Balance.MultiCommodity: Opts :: (Commodity -> Qty -> Text) -> ShowZeroBalances -> (SubAccount -> SubAccount -> Ordering) -> Opts
+ Penny.Cabin.Balance.MultiCommodity: balanceFormat :: Opts -> Commodity -> Qty -> Text
+ Penny.Cabin.Balance.MultiCommodity: data Opts
+ Penny.Cabin.Balance.MultiCommodity: defaultFormat :: a -> Qty -> Text
+ Penny.Cabin.Balance.MultiCommodity: defaultOpts :: Opts
+ Penny.Cabin.Balance.MultiCommodity: defaultParseOpts :: ParseOpts
+ Penny.Cabin.Balance.MultiCommodity: defaultReport :: Report
+ Penny.Cabin.Balance.MultiCommodity: order :: Opts -> SubAccount -> SubAccount -> Ordering
+ Penny.Cabin.Balance.MultiCommodity: parseReport :: (Commodity -> Qty -> Text) -> ParseOpts -> Report
+ Penny.Cabin.Balance.MultiCommodity: report :: Opts -> [Box a] -> [PreChunk]
+ Penny.Cabin.Balance.MultiCommodity: showZeroBalances :: Opts -> ShowZeroBalances
+ Penny.Cabin.Balance.MultiCommodity.Chunker: Row :: Int -> Text -> [(Commodity, BottomLine)] -> Row
+ Penny.Cabin.Balance.MultiCommodity.Chunker: accountTxt :: Row -> Text
+ Penny.Cabin.Balance.MultiCommodity.Chunker: balances :: Row -> [(Commodity, BottomLine)]
+ Penny.Cabin.Balance.MultiCommodity.Chunker: data Row
+ Penny.Cabin.Balance.MultiCommodity.Chunker: indentation :: Row -> Int
+ Penny.Cabin.Balance.MultiCommodity.Chunker: instance Applicative Columns
+ Penny.Cabin.Balance.MultiCommodity.Chunker: instance Functor Columns
+ Penny.Cabin.Balance.MultiCommodity.Chunker: instance Show a => Show (Columns a)
+ Penny.Cabin.Balance.MultiCommodity.Chunker: rowsToChunks :: (Commodity -> Qty -> Text) -> [Row] -> [PreChunk]
+ Penny.Cabin.Balance.MultiCommodity.Parser: ParseOpts :: ShowZeroBalances -> SortOrder -> Bool -> ParseOpts
+ Penny.Cabin.Balance.MultiCommodity.Parser: allSpecs :: [OptSpec (ParseOpts -> ParseOpts)]
+ Penny.Cabin.Balance.MultiCommodity.Parser: data ParseOpts
+ Penny.Cabin.Balance.MultiCommodity.Parser: needsHelp :: ParseOpts -> Bool
+ Penny.Cabin.Balance.MultiCommodity.Parser: order :: ParseOpts -> SortOrder
+ Penny.Cabin.Balance.MultiCommodity.Parser: showZeroBalances :: ParseOpts -> ShowZeroBalances
+ Penny.Cabin.Balance.Util: balances :: ShowZeroBalances -> [Box a] -> Forest (SubAccount, Balance)
+ Penny.Cabin.Balance.Util: boxesBalance :: [Box a] -> Balance
+ Penny.Cabin.Balance.Util: filterForest :: (a -> Bool) -> Forest a -> Forest a
+ Penny.Cabin.Balance.Util: flatten :: Forest (SubAccount, Balance) -> [(Account, Balance)]
+ Penny.Cabin.Balance.Util: forestWithParents :: Forest a -> Forest (a, [a])
+ Penny.Cabin.Balance.Util: labelLevels :: Tree a -> Tree (Int, a)
+ Penny.Cabin.Balance.Util: lastMode :: Ord a => [a] -> Maybe a
+ Penny.Cabin.Balance.Util: sortForest :: (a -> a -> Ordering) -> Forest a -> Forest a
+ Penny.Cabin.Balance.Util: sortTree :: (a -> a -> Ordering) -> Tree a -> Tree a
+ Penny.Cabin.Balance.Util: sumForest :: s -> (s -> s -> s) -> Forest (a, s) -> (Forest (a, s), s)
+ Penny.Cabin.Balance.Util: sumTree :: s -> (s -> s -> s) -> Tree (a, s) -> Tree (a, s)
+ Penny.Cabin.Balance.Util: tieredForest :: Ord k => (a -> [k]) -> [a] -> Forest (k, [a])
+ Penny.Cabin.Balance.Util: tieredPostings :: [Box a] -> Forest (SubAccount, [Box a])
+ Penny.Cabin.Balance.Util: treeWithParents :: Tree a -> Tree (a, [a])
+ Penny.Cabin.Chunk: Dumb :: Term
+ Penny.Cabin.Chunk: TermName :: String -> Term
+ Penny.Cabin.Chunk: autoTerm :: Runtime -> Term
+ Penny.Cabin.Chunk: data Term
+ Penny.Cabin.Chunk: instance Eq Background256
+ Penny.Cabin.Chunk: instance Eq Background8
+ Penny.Cabin.Chunk: instance Eq Bold
+ Penny.Cabin.Chunk: instance Eq Chunk
+ Penny.Cabin.Chunk: instance Eq Flash
+ Penny.Cabin.Chunk: instance Eq Foreground256
+ Penny.Cabin.Chunk: instance Eq Foreground8
+ Penny.Cabin.Chunk: instance Eq Inverse
+ Penny.Cabin.Chunk: instance Eq Style256
+ Penny.Cabin.Chunk: instance Eq Style8
+ Penny.Cabin.Chunk: instance Eq StyleCommon
+ Penny.Cabin.Chunk: instance Eq Term
+ Penny.Cabin.Chunk: instance Eq TextSpec
+ Penny.Cabin.Chunk: instance Eq Underline
+ Penny.Cabin.Chunk: instance Ord Background256
+ Penny.Cabin.Chunk: instance Ord Background8
+ Penny.Cabin.Chunk: instance Ord Bold
+ Penny.Cabin.Chunk: instance Ord Chunk
+ Penny.Cabin.Chunk: instance Ord Flash
+ Penny.Cabin.Chunk: instance Ord Foreground256
+ Penny.Cabin.Chunk: instance Ord Foreground8
+ Penny.Cabin.Chunk: instance Ord Inverse
+ Penny.Cabin.Chunk: instance Ord Style256
+ Penny.Cabin.Chunk: instance Ord Style8
+ Penny.Cabin.Chunk: instance Ord StyleCommon
+ Penny.Cabin.Chunk: instance Ord TextSpec
+ Penny.Cabin.Chunk: instance Ord Underline
+ Penny.Cabin.Chunk: instance Show Background256
+ Penny.Cabin.Chunk: instance Show Background8
+ Penny.Cabin.Chunk: instance Show Bold
+ Penny.Cabin.Chunk: instance Show Chunk
+ Penny.Cabin.Chunk: instance Show Flash
+ Penny.Cabin.Chunk: instance Show Foreground256
+ Penny.Cabin.Chunk: instance Show Foreground8
+ Penny.Cabin.Chunk: instance Show Inverse
+ Penny.Cabin.Chunk: instance Show Style256
+ Penny.Cabin.Chunk: instance Show Style8
+ Penny.Cabin.Chunk: instance Show StyleCommon
+ Penny.Cabin.Chunk: instance Show Term
+ Penny.Cabin.Chunk: instance Show TextSpec
+ Penny.Cabin.Chunk: instance Show Underline
+ Penny.Cabin.Chunk: printChunks :: Term -> [Chunk] -> IO ()
+ Penny.Cabin.Chunk: termFromEnv :: Runtime -> Term
+ Penny.Cabin.Interface: type ArgsAndReport = ([PosArg], PrintReport)
+ Penny.Cabin.Interface: type HelpStr = String
+ Penny.Cabin.Interface: type MkReport = CaseSensitive -> (CaseSensitive -> Text -> Exceptional Text (Text -> Bool)) -> ([Transaction] -> [Box LibertyMeta]) -> Mode ParseResult
+ Penny.Cabin.Interface: type ParseResult = Exceptional String (Either HelpStr ArgsAndReport)
+ Penny.Cabin.Interface: type PosArg = String
+ Penny.Cabin.Interface: type PrintReport = [Transaction] -> [PricePoint] -> Exceptional Text [PreChunk]
+ Penny.Cabin.Interface: type Report = Runtime -> (HelpStr, MkReport)
+ Penny.Cabin.Meta: visibleNumBoxes :: (VisibleNum -> a -> b) -> [Box a] -> [Box b]
+ Penny.Cabin.Options: descending :: (a -> a -> Ordering) -> a -> a -> Ordering
+ Penny.Cabin.Parsers: Ascending :: SortOrder
+ Penny.Cabin.Parsers: Descending :: SortOrder
+ Penny.Cabin.Parsers: data SortOrder
+ Penny.Cabin.Parsers: help :: OptSpec ()
+ Penny.Cabin.Parsers: instance Eq SortOrder
+ Penny.Cabin.Parsers: instance Ord SortOrder
+ Penny.Cabin.Parsers: instance Show SortOrder
+ Penny.Cabin.Parsers: order :: OptSpec SortOrder
+ Penny.Cabin.Parsers: zeroBalances :: OptSpec ShowZeroBalances
+ Penny.Cabin.Posts: ReportWidth :: Int -> ReportWidth
+ Penny.Cabin.Posts: SubAccountLength :: Int -> SubAccountLength
+ Penny.Cabin.Posts: alloc :: Int -> Alloc
+ Penny.Cabin.Posts: data Alloc
+ Penny.Cabin.Posts: newtype ReportWidth
+ Penny.Cabin.Posts: newtype SubAccountLength
+ Penny.Cabin.Posts: unReportWidth :: ReportWidth -> Int
+ Penny.Cabin.Posts: unSubAccountLength :: SubAccountLength -> Int
+ Penny.Cabin.Posts: yearMonthDay :: Box -> Text
+ Penny.Cabin.Posts: zincReport :: ZincOpts -> Report
+ Penny.Cabin.Posts.Allocated: alloc :: Int -> Alloc
+ Penny.Cabin.Posts.Allocated: allocations :: AllocatedOpts -> Fields Alloc
+ Penny.Cabin.Posts.Allocated: data Alloc
+ Penny.Cabin.Posts.Allocated: instance Eq AvailableWidth
+ Penny.Cabin.Posts.Allocated: instance Eq Final
+ Penny.Cabin.Posts.Allocated: instance Eq Request
+ Penny.Cabin.Posts.Allocated: instance Ord AvailableWidth
+ Penny.Cabin.Posts.Allocated: instance Ord Final
+ Penny.Cabin.Posts.Allocated: instance Ord Request
+ Penny.Cabin.Posts.Allocated: instance Show Alloc
+ Penny.Cabin.Posts.Allocated: instance Show AvailableWidth
+ Penny.Cabin.Posts.Allocated: instance Show Final
+ Penny.Cabin.Posts.Allocated: instance Show Request
+ Penny.Cabin.Posts.Allocated: unAlloc :: Alloc -> Int
+ Penny.Cabin.Posts.Fields: fieldNames :: Fields String
+ Penny.Cabin.Posts.Parser: allSpecs :: Runtime -> [OptSpec (State -> Exceptional String State)]
+ Penny.Cabin.Posts.Parser: showHelp :: State -> Bool
+ Penny.Cabin.Scheme: Credit :: Label
+ Penny.Cabin.Scheme: Debit :: Label
+ Penny.Cabin.Scheme: Even :: EvenOdd
+ Penny.Cabin.Scheme: EvenAndOdd :: a -> a -> EvenAndOdd a
+ Penny.Cabin.Scheme: Labels :: a -> a -> a -> a -> Labels a
+ Penny.Cabin.Scheme: Odd :: EvenOdd
+ Penny.Cabin.Scheme: Other :: Label
+ Penny.Cabin.Scheme: PreChunk :: Label -> EvenOdd -> Text -> PreChunk
+ Penny.Cabin.Scheme: Scheme :: String -> String -> TextSpecs -> Scheme
+ Penny.Cabin.Scheme: Zero :: Label
+ Penny.Cabin.Scheme: balanceToQtys :: (Commodity -> Qty -> Text) -> EvenOdd -> [(Commodity, BottomLine)] -> [PreChunk]
+ Penny.Cabin.Scheme: balancesToCmdtys :: EvenOdd -> [(Commodity, BottomLine)] -> [PreChunk]
+ Penny.Cabin.Scheme: bottomLineToCmdty :: EvenOdd -> (Commodity, BottomLine) -> PreChunk
+ Penny.Cabin.Scheme: bottomLineToDrCr :: BottomLine -> EvenOdd -> PreChunk
+ Penny.Cabin.Scheme: bottomLineToQty :: (Commodity -> Qty -> Text) -> EvenOdd -> (Commodity, BottomLine) -> PreChunk
+ Penny.Cabin.Scheme: credit :: Labels a -> a
+ Penny.Cabin.Scheme: data EvenAndOdd a
+ Penny.Cabin.Scheme: data EvenOdd
+ Penny.Cabin.Scheme: data Label
+ Penny.Cabin.Scheme: data Labels a
+ Penny.Cabin.Scheme: data PreChunk
+ Penny.Cabin.Scheme: data Scheme
+ Penny.Cabin.Scheme: dcToLbl :: DrCr -> Label
+ Penny.Cabin.Scheme: debit :: Labels a -> a
+ Penny.Cabin.Scheme: description :: Scheme -> String
+ Penny.Cabin.Scheme: eoEven :: EvenAndOdd a -> a
+ Penny.Cabin.Scheme: eoOdd :: EvenAndOdd a -> a
+ Penny.Cabin.Scheme: evenOdd :: PreChunk -> EvenOdd
+ Penny.Cabin.Scheme: fromVisibleNum :: VisibleNum -> EvenOdd
+ Penny.Cabin.Scheme: getEvenOdd :: EvenOdd -> EvenAndOdd a -> a
+ Penny.Cabin.Scheme: getEvenOddLabelValue :: Label -> EvenOdd -> Labels (EvenAndOdd a) -> a
+ Penny.Cabin.Scheme: getLabelValue :: Label -> Labels a -> a
+ Penny.Cabin.Scheme: instance Eq EvenOdd
+ Penny.Cabin.Scheme: instance Eq Label
+ Penny.Cabin.Scheme: instance Eq PreChunk
+ Penny.Cabin.Scheme: instance Ord EvenOdd
+ Penny.Cabin.Scheme: instance Ord Label
+ Penny.Cabin.Scheme: instance Show EvenOdd
+ Penny.Cabin.Scheme: instance Show Label
+ Penny.Cabin.Scheme: instance Show PreChunk
+ Penny.Cabin.Scheme: instance Show Scheme
+ Penny.Cabin.Scheme: instance Show a => Show (EvenAndOdd a)
+ Penny.Cabin.Scheme: instance Show a => Show (Labels a)
+ Penny.Cabin.Scheme: label :: PreChunk -> Label
+ Penny.Cabin.Scheme: makeChunk :: TextSpecs -> PreChunk -> Chunk
+ Penny.Cabin.Scheme: name :: Scheme -> String
+ Penny.Cabin.Scheme: other :: Labels a -> a
+ Penny.Cabin.Scheme: text :: PreChunk -> Text
+ Penny.Cabin.Scheme: textSpecs :: Scheme -> TextSpecs
+ Penny.Cabin.Scheme: type TextSpecs = Labels (EvenAndOdd TextSpec)
+ Penny.Cabin.Scheme: width :: PreChunk -> Width
+ Penny.Cabin.Scheme: zero :: Labels a -> a
+ Penny.Cabin.Scheme.Schemes: dark :: Scheme
+ Penny.Cabin.Scheme.Schemes: darkCredit :: TextSpec -> TextSpec
+ Penny.Cabin.Scheme.Schemes: darkDebit :: TextSpec -> TextSpec
+ Penny.Cabin.Scheme.Schemes: darkEvenTextSpec :: TextSpec
+ Penny.Cabin.Scheme.Schemes: darkLabels :: Labels (EvenAndOdd TextSpec)
+ Penny.Cabin.Scheme.Schemes: darkOddTextSpec :: TextSpec
+ Penny.Cabin.Scheme.Schemes: darkZero :: TextSpec -> TextSpec
+ Penny.Cabin.Scheme.Schemes: light :: Scheme
+ Penny.Cabin.Scheme.Schemes: lightCredit :: TextSpec -> TextSpec
+ Penny.Cabin.Scheme.Schemes: lightDebit :: TextSpec -> TextSpec
+ Penny.Cabin.Scheme.Schemes: lightEvenTextSpec :: TextSpec
+ Penny.Cabin.Scheme.Schemes: lightLabels :: Labels (EvenAndOdd TextSpec)
+ Penny.Cabin.Scheme.Schemes: lightOddTextSpec :: TextSpec
+ Penny.Cabin.Scheme.Schemes: lightZero :: TextSpec -> TextSpec
+ Penny.Cabin.Scheme.Schemes: plain :: Scheme
+ Penny.Cabin.Scheme.Schemes: plainLabels :: Labels (EvenAndOdd TextSpec)
+ Penny.Copper: GroupSpecs :: GroupSpec -> GroupSpec -> GroupSpecs
+ Penny.Copper: IComment :: Comment -> Item
+ Penny.Copper: PricePoint :: PricePoint -> Item
+ Penny.Copper: data ErrorMsg
+ Penny.Copper: data GroupSpec
+ Penny.Copper: data GroupSpecs
+ Penny.Copper: ledger :: GroupSpecs -> Ledger -> Maybe Text
+ Penny.Copper: left :: GroupSpecs -> GroupSpec
+ Penny.Copper: mapItem :: (Comment -> Comment) -> (PricePoint -> PricePoint) -> (Transaction -> Transaction) -> Item -> Item
+ Penny.Copper: mapItemA :: Applicative a => (Comment -> a Comment) -> (PricePoint -> a PricePoint) -> (Transaction -> a Transaction) -> Item -> a Item
+ Penny.Copper: mapLedger :: (Item -> Item) -> Ledger -> Ledger
+ Penny.Copper: mapLedgerA :: Applicative a => (Item -> a Item) -> Ledger -> a Ledger
+ Penny.Copper: newtype Comment
+ Penny.Copper: newtype Ledger
+ Penny.Copper: open :: [String] -> IO Ledger
+ Penny.Copper: openStdin :: [String] -> IO Ledger
+ Penny.Copper: right :: GroupSpecs -> GroupSpec
+ Penny.Copper: unComment :: Comment -> Text
+ Penny.Copper.Parsec: amount :: Parser Amount
+ Penny.Copper.Parsec: blankLine :: Parser Item
+ Penny.Copper.Parsec: comment :: Parser Comment
+ Penny.Copper.Parsec: credit :: Parser DrCr
+ Penny.Copper.Parsec: date :: Parser Day
+ Penny.Copper.Parsec: dateTime :: Parser DateTime
+ Penny.Copper.Parsec: day :: Parser Int
+ Penny.Copper.Parsec: debit :: Parser DrCr
+ Penny.Copper.Parsec: digitGroup :: Parser [Char]
+ Penny.Copper.Parsec: digitPostSequence :: Parser (Maybe [Char])
+ Penny.Copper.Parsec: digitSequence :: Parser [Char]
+ Penny.Copper.Parsec: drCr :: Parser DrCr
+ Penny.Copper.Parsec: entry :: Parser Entry
+ Penny.Copper.Parsec: flag :: Parser Flag
+ Penny.Copper.Parsec: flagFirst :: Parser (Flag, Maybe Number, Maybe Payee)
+ Penny.Copper.Parsec: flagNumPayee :: Parser (Maybe Flag, Maybe Number, Maybe Payee)
+ Penny.Copper.Parsec: fromCmdty :: Parser From
+ Penny.Copper.Parsec: hours :: Parser Hours
+ Penny.Copper.Parsec: item :: Parser Item
+ Penny.Copper.Parsec: ledger :: Parser Ledger
+ Penny.Copper.Parsec: ledgerAcct :: Parser Account
+ Penny.Copper.Parsec: leftCmdtyLvl1Amt :: Parser Amount
+ Penny.Copper.Parsec: leftCmdtyLvl3Amt :: Parser Amount
+ Penny.Copper.Parsec: leftSideCmdtyAmt :: Parser Amount
+ Penny.Copper.Parsec: lineNum :: Parser Int
+ Penny.Copper.Parsec: lvl1Acct :: Parser Account
+ Penny.Copper.Parsec: lvl1Cmdty :: Parser Commodity
+ Penny.Copper.Parsec: lvl1FirstSubAcct :: Parser SubAccount
+ Penny.Copper.Parsec: lvl1OtherSubAcct :: Parser SubAccount
+ Penny.Copper.Parsec: lvl1Payee :: Parser Payee
+ Penny.Copper.Parsec: lvl1SubAcct :: Parser SubAccount
+ Penny.Copper.Parsec: lvl2Acct :: Parser Account
+ Penny.Copper.Parsec: lvl2Cmdty :: Parser Commodity
+ Penny.Copper.Parsec: lvl2FirstSubAcct :: Parser SubAccount
+ Penny.Copper.Parsec: lvl2OtherSubAcct :: Parser SubAccount
+ Penny.Copper.Parsec: lvl2Payee :: Parser Payee
+ Penny.Copper.Parsec: lvl3Cmdty :: Parser Commodity
+ Penny.Copper.Parsec: minutes :: Parser Minutes
+ Penny.Copper.Parsec: month :: Parser Int
+ Penny.Copper.Parsec: number :: Parser Number
+ Penny.Copper.Parsec: numberFirst :: Parser (Number, Maybe Flag, Maybe Payee)
+ Penny.Copper.Parsec: pairedMaybes :: Parser (a, Maybe b) -> Parser (Maybe a, b) -> Parser (Maybe a, Maybe b)
+ Penny.Copper.Parsec: parsePair :: Parser a -> Parser b -> Parser (Maybe a, Maybe b)
+ Penny.Copper.Parsec: parseTriple :: Parser a -> Parser b -> Parser c -> Parser (a, Maybe b, Maybe c)
+ Penny.Copper.Parsec: payeeFirst :: Parser (Payee, Maybe Flag, Maybe Number)
+ Penny.Copper.Parsec: posting :: Parser Posting
+ Penny.Copper.Parsec: postingAcct :: Parser Account
+ Penny.Copper.Parsec: postingMemo :: Parser Memo
+ Penny.Copper.Parsec: postingMemoLine :: Parser Text
+ Penny.Copper.Parsec: price :: Parser PricePoint
+ Penny.Copper.Parsec: quantity :: Parser Qty
+ Penny.Copper.Parsec: quotedLvl1Acct :: Parser Account
+ Penny.Copper.Parsec: quotedLvl1Cmdty :: Parser Commodity
+ Penny.Copper.Parsec: quotedLvl1Payee :: Parser Payee
+ Penny.Copper.Parsec: rightSideCmdty :: Parser Commodity
+ Penny.Copper.Parsec: rightSideCmdtyAmt :: Parser Amount
+ Penny.Copper.Parsec: seconds :: Parser Seconds
+ Penny.Copper.Parsec: skipWhite :: Parser ()
+ Penny.Copper.Parsec: spaceBetween :: Parser SpaceBetween
+ Penny.Copper.Parsec: tag :: Parser Tag
+ Penny.Copper.Parsec: tags :: Parser Tags
+ Penny.Copper.Parsec: time :: Parser (Hours, Minutes, Maybe Seconds)
+ Penny.Copper.Parsec: timeWithZone :: Parser (Hours, Minutes, Maybe Seconds, Maybe TimeZoneOffset)
+ Penny.Copper.Parsec: timeZone :: Parser TimeZoneOffset
+ Penny.Copper.Parsec: topLine :: Parser TopLine
+ Penny.Copper.Parsec: topLineFlagNum :: Parser (Maybe Flag, Maybe Number)
+ Penny.Copper.Parsec: topLinePayee :: Parser Payee
+ Penny.Copper.Parsec: transaction :: Parser Transaction
+ Penny.Copper.Parsec: transactionMemo :: Parser (TopMemoLine, Memo)
+ Penny.Copper.Parsec: transactionMemoLine :: Parser Text
+ Penny.Copper.Parsec: tzNumber :: Parser Int
+ Penny.Copper.Parsec: tzSign :: Parser (Int -> Int)
+ Penny.Copper.Parsec: year :: Parser Integer
+ 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: amount :: GroupSpecs -> Amount -> Maybe Text
+ Penny.Copper.Render: comment :: Comment -> Maybe Text
+ Penny.Copper.Render: data GroupSpec
+ Penny.Copper.Render: data GroupSpecs
+ Penny.Copper.Render: dateTime :: DateTime -> Text
+ Penny.Copper.Render: entry :: GroupSpecs -> Entry -> Maybe Text
+ Penny.Copper.Render: flag :: Flag -> Maybe Text
+ Penny.Copper.Render: formatter :: Bool -> Text -> Text -> Text -> Text -> Text -> Text -> Text -> Text
+ Penny.Copper.Render: groupDecimal :: GroupSpec -> String -> String
+ Penny.Copper.Render: groupWhole :: GroupSpec -> String -> String
+ Penny.Copper.Render: grouper :: String
+ Penny.Copper.Render: hoursMinsSecsZone :: Hours -> Minutes -> Seconds -> TimeZoneOffset -> Text
+ Penny.Copper.Render: instance Eq GroupSpec
+ Penny.Copper.Render: instance Show GroupSpec
+ Penny.Copper.Render: instance Show GroupSpecs
+ Penny.Copper.Render: isAcctLvl1 :: Account -> Bool
+ Penny.Copper.Render: isAcctLvl2 :: Account -> Bool
+ Penny.Copper.Render: isFirstSubAcctLvl2 :: SubAccount -> Bool
+ Penny.Copper.Render: isOtherSubAcctLvl2 :: SubAccount -> Bool
+ Penny.Copper.Render: isSubAcctLvl1 :: SubAccount -> Bool
+ Penny.Copper.Render: item :: GroupSpecs -> Item -> Maybe Text
+ Penny.Copper.Render: ledger :: GroupSpecs -> Ledger -> Maybe Text
+ Penny.Copper.Render: ledgerAcct :: Account -> Maybe Text
+ Penny.Copper.Render: left :: GroupSpecs -> GroupSpec
+ Penny.Copper.Render: lvl2Acct :: Account -> Maybe Text
+ Penny.Copper.Render: lvl2Cmdty :: Commodity -> Maybe Text
+ Penny.Copper.Render: lvl2Payee :: Payee -> Maybe Text
+ Penny.Copper.Render: lvl3Cmdty :: Commodity -> Maybe Text
+ Penny.Copper.Render: number :: Number -> Maybe Text
+ Penny.Copper.Render: pad2 :: Text -> Text
+ Penny.Copper.Render: pad4 :: Text -> Text
+ Penny.Copper.Render: payee :: Payee -> Maybe Text
+ Penny.Copper.Render: posting :: GroupSpecs -> Bool -> Posting -> Maybe Text
+ Penny.Copper.Render: postingMemo :: Bool -> Memo -> Maybe Text
+ Penny.Copper.Render: postingMemoLine :: Int -> Text -> Maybe Text
+ Penny.Copper.Render: price :: GroupSpecs -> PricePoint -> Maybe Text
+ Penny.Copper.Render: quantity :: GroupSpecs -> Qty -> Text
+ Penny.Copper.Render: quotedLvl1Acct :: Account -> Maybe Text
+ Penny.Copper.Render: quotedLvl1Cmdty :: Commodity -> Maybe Text
+ Penny.Copper.Render: quotedLvl1Payee :: Payee -> Maybe Text
+ Penny.Copper.Render: radix :: String
+ Penny.Copper.Render: renMaybe :: Maybe a -> (a -> Maybe Text) -> Maybe Text
+ Penny.Copper.Render: right :: GroupSpecs -> GroupSpec
+ Penny.Copper.Render: showX :: Show a => a -> Text
+ Penny.Copper.Render: tag :: Tag -> Maybe Text
+ Penny.Copper.Render: tags :: Tags -> Maybe Text
+ Penny.Copper.Render: topLine :: TopLine -> Maybe Text
+ Penny.Copper.Render: transaction :: GroupSpecs -> Transaction -> Maybe Text
+ Penny.Copper.Render: transactionMemo :: Memo -> Maybe Text
+ Penny.Copper.Render: transactionMemoLine :: Text -> Maybe Text
+ Penny.Copper.Render: txtWords :: [Text] -> Text
+ Penny.Copper.Terminals: apostrophe :: Char -> Bool
+ Penny.Copper.Terminals: asterisk :: Char -> Bool
+ Penny.Copper.Terminals: atSign :: Char -> Bool
+ Penny.Copper.Terminals: closeCurly :: Char -> Bool
+ Penny.Copper.Terminals: closeParen :: Char -> Bool
+ Penny.Copper.Terminals: closeSquare :: Char -> Bool
+ Penny.Copper.Terminals: colon :: Char -> Bool
+ Penny.Copper.Terminals: dateSep :: Char -> Bool
+ Penny.Copper.Terminals: digit :: Char -> Bool
+ Penny.Copper.Terminals: dollar :: Char -> Bool
+ Penny.Copper.Terminals: doubleQuote :: Char -> Bool
+ Penny.Copper.Terminals: flagChar :: Char -> Bool
+ Penny.Copper.Terminals: greaterThan :: Char -> Bool
+ Penny.Copper.Terminals: hash :: Char -> Bool
+ Penny.Copper.Terminals: invalid :: Char -> Bool
+ Penny.Copper.Terminals: lessThan :: Char -> Bool
+ Penny.Copper.Terminals: letter :: Char -> Bool
+ Penny.Copper.Terminals: lowerCaseAscii :: Char -> Bool
+ Penny.Copper.Terminals: lvl1AcctChar :: Char -> Bool
+ Penny.Copper.Terminals: lvl1CmdtyChar :: Char -> Bool
+ Penny.Copper.Terminals: lvl2AcctOtherChar :: Char -> Bool
+ Penny.Copper.Terminals: lvl2CmdtyFirstChar :: Char -> Bool
+ Penny.Copper.Terminals: lvl2CmdtyOtherChar :: Char -> Bool
+ Penny.Copper.Terminals: lvl3CmdtyChar :: Char -> Bool
+ Penny.Copper.Terminals: minus :: Char -> Bool
+ Penny.Copper.Terminals: newline :: Char -> Bool
+ Penny.Copper.Terminals: nonAscii :: Char -> Bool
+ Penny.Copper.Terminals: nonNewline :: Char -> Bool
+ Penny.Copper.Terminals: nonNewlineNonSpace :: Char -> Bool
+ Penny.Copper.Terminals: numberChar :: Char -> Bool
+ Penny.Copper.Terminals: openCurly :: Char -> Bool
+ Penny.Copper.Terminals: openParen :: Char -> Bool
+ Penny.Copper.Terminals: openSquare :: Char -> Bool
+ Penny.Copper.Terminals: period :: Char -> Bool
+ Penny.Copper.Terminals: plus :: Char -> Bool
+ Penny.Copper.Terminals: quotedPayeeChar :: Char -> Bool
+ Penny.Copper.Terminals: semicolon :: Char -> Bool
+ Penny.Copper.Terminals: space :: Char -> Bool
+ Penny.Copper.Terminals: tab :: Char -> Bool
+ Penny.Copper.Terminals: tagChar :: Char -> Bool
+ Penny.Copper.Terminals: thinSpace :: Char -> Bool
+ Penny.Copper.Terminals: tilde :: Char -> Bool
+ Penny.Copper.Terminals: underscore :: Char -> Bool
+ Penny.Copper.Terminals: unicode :: Char -> Bool
+ Penny.Copper.Terminals: upperCaseAscii :: Char -> Bool
+ Penny.Copper.Terminals: white :: Char -> Bool
+ Penny.Copper.Types: BlankLine :: Item
+ Penny.Copper.Types: Comment :: Text -> Comment
+ Penny.Copper.Types: IComment :: Comment -> Item
+ Penny.Copper.Types: Ledger :: [Item] -> Ledger
+ Penny.Copper.Types: PricePoint :: PricePoint -> Item
+ Penny.Copper.Types: Transaction :: Transaction -> Item
+ Penny.Copper.Types: data Item
+ Penny.Copper.Types: instance Eq Comment
+ Penny.Copper.Types: instance Monoid Ledger
+ Penny.Copper.Types: instance Show Comment
+ Penny.Copper.Types: instance Show Item
+ Penny.Copper.Types: instance Show Ledger
+ Penny.Copper.Types: mapItem :: (Comment -> Comment) -> (PricePoint -> PricePoint) -> (Transaction -> Transaction) -> Item -> Item
+ Penny.Copper.Types: mapItemA :: Applicative a => (Comment -> a Comment) -> (PricePoint -> a PricePoint) -> (Transaction -> a Transaction) -> Item -> a Item
+ Penny.Copper.Types: mapLedger :: (Item -> Item) -> Ledger -> Ledger
+ Penny.Copper.Types: mapLedgerA :: Applicative a => (Item -> a Item) -> Ledger -> a Ledger
+ Penny.Copper.Types: newtype Comment
+ Penny.Copper.Types: newtype Ledger
+ Penny.Copper.Types: unComment :: Comment -> Text
+ Penny.Copper.Types: unLedger :: Ledger -> [Item]
+ Penny.Liberty: TokBinary :: Precedence -> Associativity -> (a -> a -> a) -> Token a
+ Penny.Liberty: TokCloseParen :: Token a
+ Penny.Liberty: TokOpenParen :: Token a
+ Penny.Liberty: TokOperand :: a -> Token a
+ Penny.Liberty: TokUnaryPostfix :: (a -> a) -> Token a
+ Penny.Liberty: TokUnaryPrefix :: Precedence -> (a -> a) -> Token a
+ Penny.Liberty: caseSelectSpecs :: [OptSpec CaseSensitive]
+ Penny.Liberty: matcherSelectSpecs :: [OptSpec MatcherFactory]
+ Penny.Liberty: operandSpecs :: DateTime -> [OptSpec (CaseSensitive -> MatcherFactory -> Exceptional String Operand)]
+ Penny.Liberty: operatorSpecs :: [OptSpec (Token (a -> Bool))]
+ Penny.Liberty: parseInt :: String -> Exceptional String Int
+ Penny.Liberty: postFilterSpecs :: (OptSpec (Exceptional String PostFilterFn), OptSpec (Exceptional String PostFilterFn))
+ Penny.Liberty.Expressions: unOperand :: Operand a -> a
+ Penny.Liberty.Expressions.RPN: unOperand :: Operand a -> a
+ Penny.Lincoln: DateTime :: Day -> Hours -> Minutes -> Seconds -> TimeZoneOffset -> DateTime
+ Penny.Lincoln: PostingChangeData :: Maybe (Maybe Payee) -> Maybe (Maybe Number) -> Maybe (Maybe Flag) -> Maybe Account -> Maybe Tags -> Maybe (Maybe Memo) -> Maybe (Maybe Side) -> Maybe (Maybe SpaceBetween) -> Maybe (Maybe PostingLine) -> Maybe (Maybe GlobalPosting) -> Maybe (Maybe FilePosting) -> PostingChangeData
+ Penny.Lincoln: RTransaction :: Commodity -> Maybe Side -> Maybe SpaceBetween -> DrCr -> TopLine -> RPosting -> [RPosting] -> IPosting -> RTransaction
+ Penny.Lincoln: RadFrac :: String -> NumberStr
+ Penny.Lincoln: SubAccount :: Text -> SubAccount
+ Penny.Lincoln: TopLineChangeData :: Maybe DateTime -> Maybe (Maybe Flag) -> Maybe (Maybe Number) -> Maybe (Maybe Payee) -> Maybe (Maybe Memo) -> Maybe (Maybe TopLineLine) -> Maybe (Maybe TopMemoLine) -> Maybe (Maybe Filename) -> Maybe (Maybe GlobalTransaction) -> Maybe (Maybe FileTransaction) -> TopLineChangeData
+ Penny.Lincoln: Whole :: String -> NumberStr
+ Penny.Lincoln: WholeRad :: String -> NumberStr
+ Penny.Lincoln: WholeRadFrac :: String -> String -> NumberStr
+ Penny.Lincoln: allocate :: Qty -> NonEmpty Qty -> NonEmpty Qty
+ Penny.Lincoln: changeTransaction :: Family TopLineChangeData PostingChangeData -> Transaction -> Transaction
+ Penny.Lincoln: data GenSerial a
+ Penny.Lincoln: data Hours
+ Penny.Lincoln: data Minutes
+ Penny.Lincoln: data NumberStr
+ Penny.Lincoln: data PostingChangeData
+ Penny.Lincoln: data RTransaction
+ Penny.Lincoln: data Seconds
+ Penny.Lincoln: data TopLineChangeData
+ Penny.Lincoln: dateTimeMidnightUTC :: Day -> DateTime
+ Penny.Lincoln: day :: DateTime -> Day
+ Penny.Lincoln: emptyPostingChangeData :: PostingChangeData
+ Penny.Lincoln: emptyTopLineChangeData :: TopLineChangeData
+ Penny.Lincoln: equivalent :: Qty -> Qty -> Bool
+ Penny.Lincoln: filterChildren :: (a -> Bool) -> Family p a -> Maybe (Family p a)
+ Penny.Lincoln: find :: (p -> c -> Bool) -> Family p c -> Maybe c
+ Penny.Lincoln: fromZonedTime :: ZonedTime -> Maybe DateTime
+ Penny.Lincoln: getSerial :: GenSerial Serial
+ Penny.Lincoln: hours :: DateTime -> Hours
+ Penny.Lincoln: incrementBack :: GenSerial ()
+ Penny.Lincoln: intToHours :: Int -> Maybe Hours
+ Penny.Lincoln: intToMinutes :: Int -> Maybe Minutes
+ Penny.Lincoln: intToSeconds :: Int -> Maybe Seconds
+ Penny.Lincoln: makeSerials :: GenSerial a -> a
+ Penny.Lincoln: mantissa :: Qty -> Integer
+ Penny.Lincoln: mapChildren :: (a -> b) -> Family p a -> Family p b
+ Penny.Lincoln: mapChildrenA :: Applicative m => (a -> m b) -> Family p a -> m (Family p b)
+ Penny.Lincoln: mapParent :: (a -> b) -> Family a c -> Family b c
+ Penny.Lincoln: mapParentA :: Applicative m => (a -> m b) -> Family a c -> m (Family b c)
+ Penny.Lincoln: midnight :: (Hours, Minutes, Seconds)
+ Penny.Lincoln: minutes :: DateTime -> Minutes
+ Penny.Lincoln: nSerials :: Int -> [Serial]
+ Penny.Lincoln: newtype SubAccount
+ Penny.Lincoln: pFilePosting :: Posting -> Maybe FilePosting
+ Penny.Lincoln: pGlobalPosting :: Posting -> Maybe GlobalPosting
+ Penny.Lincoln: pPostingLine :: Posting -> Maybe PostingLine
+ Penny.Lincoln: pcAccount :: PostingChangeData -> Maybe Account
+ Penny.Lincoln: pcFilePosting :: PostingChangeData -> Maybe (Maybe FilePosting)
+ Penny.Lincoln: pcFlag :: PostingChangeData -> Maybe (Maybe Flag)
+ Penny.Lincoln: pcGlobalPosting :: PostingChangeData -> Maybe (Maybe GlobalPosting)
+ Penny.Lincoln: pcMemo :: PostingChangeData -> Maybe (Maybe Memo)
+ Penny.Lincoln: pcNumber :: PostingChangeData -> Maybe (Maybe Number)
+ Penny.Lincoln: pcPayee :: PostingChangeData -> Maybe (Maybe Payee)
+ Penny.Lincoln: pcPostingLine :: PostingChangeData -> Maybe (Maybe PostingLine)
+ Penny.Lincoln: pcSide :: PostingChangeData -> Maybe (Maybe Side)
+ Penny.Lincoln: pcSpaceBetween :: PostingChangeData -> Maybe (Maybe SpaceBetween)
+ Penny.Lincoln: pcTags :: PostingChangeData -> Maybe Tags
+ Penny.Lincoln: places :: Qty -> Integer
+ Penny.Lincoln: ppSide :: PricePoint -> Maybe Side
+ Penny.Lincoln: ppSpaceBetween :: PricePoint -> Maybe SpaceBetween
+ Penny.Lincoln: price :: PricePoint -> Price
+ Penny.Lincoln: rTransaction :: RTransaction -> Transaction
+ Penny.Lincoln: rtCommodity :: RTransaction -> Commodity
+ Penny.Lincoln: rtDrCr :: RTransaction -> DrCr
+ Penny.Lincoln: rtIPosting :: RTransaction -> IPosting
+ Penny.Lincoln: rtMorePostings :: RTransaction -> [RPosting]
+ Penny.Lincoln: rtPosting :: RTransaction -> RPosting
+ Penny.Lincoln: rtSide :: RTransaction -> Maybe Side
+ Penny.Lincoln: rtSpaceBetween :: RTransaction -> Maybe SpaceBetween
+ Penny.Lincoln: rtTopLine :: RTransaction -> TopLine
+ Penny.Lincoln: sameInstant :: DateTime -> DateTime -> Bool
+ Penny.Lincoln: seconds :: DateTime -> Seconds
+ Penny.Lincoln: spaceBetween :: Amount -> Maybe SpaceBetween
+ Penny.Lincoln: tFileTransaction :: TopLine -> Maybe FileTransaction
+ Penny.Lincoln: tFilename :: TopLine -> Maybe Filename
+ Penny.Lincoln: tGlobalTransaction :: TopLine -> Maybe GlobalTransaction
+ Penny.Lincoln: tTopLineLine :: TopLine -> Maybe TopLineLine
+ Penny.Lincoln: tTopMemoLine :: TopLine -> Maybe TopMemoLine
+ Penny.Lincoln: tcDateTime :: TopLineChangeData -> Maybe DateTime
+ Penny.Lincoln: tcFileTransaction :: TopLineChangeData -> Maybe (Maybe FileTransaction)
+ Penny.Lincoln: tcFilename :: TopLineChangeData -> Maybe (Maybe Filename)
+ Penny.Lincoln: tcFlag :: TopLineChangeData -> Maybe (Maybe Flag)
+ Penny.Lincoln: tcGlobalTransaction :: TopLineChangeData -> Maybe (Maybe GlobalTransaction)
+ Penny.Lincoln: tcMemo :: TopLineChangeData -> Maybe (Maybe Memo)
+ Penny.Lincoln: tcNumber :: TopLineChangeData -> Maybe (Maybe Number)
+ Penny.Lincoln: tcPayee :: TopLineChangeData -> Maybe (Maybe Payee)
+ Penny.Lincoln: tcTopLineLine :: TopLineChangeData -> Maybe (Maybe TopLineLine)
+ Penny.Lincoln: tcTopMemoLine :: TopLineChangeData -> Maybe (Maybe TopMemoLine)
+ Penny.Lincoln: toQty :: NumberStr -> Maybe Qty
+ Penny.Lincoln: toUTC :: DateTime -> UTCTime
+ Penny.Lincoln: toUnverified :: Transaction -> Family TopLine Posting
+ Penny.Lincoln: toZonedTime :: DateTime -> ZonedTime
+ Penny.Lincoln: type Mantissa = Integer
+ Penny.Lincoln: type Places = Integer
+ Penny.Lincoln: unSubAccount :: SubAccount -> Text
+ Penny.Lincoln: zeroSeconds :: Seconds
+ Penny.Lincoln.Balance: instance Monoid Balance
+ Penny.Lincoln.Bits: CommodityOnLeft :: Side
+ Penny.Lincoln.Bits: CommodityOnRight :: Side
+ Penny.Lincoln.Bits: DateTime :: Day -> Hours -> Minutes -> Seconds -> TimeZoneOffset -> DateTime
+ Penny.Lincoln.Bits: FilePosting :: Serial -> FilePosting
+ Penny.Lincoln.Bits: FileTransaction :: Serial -> FileTransaction
+ Penny.Lincoln.Bits: Filename :: Text -> Filename
+ Penny.Lincoln.Bits: GlobalPosting :: Serial -> GlobalPosting
+ Penny.Lincoln.Bits: GlobalTransaction :: Serial -> GlobalTransaction
+ Penny.Lincoln.Bits: NoSpaceBetween :: SpaceBetween
+ Penny.Lincoln.Bits: PostingLine :: Int -> PostingLine
+ Penny.Lincoln.Bits: PriceLine :: Int -> PriceLine
+ Penny.Lincoln.Bits: RadFrac :: String -> NumberStr
+ Penny.Lincoln.Bits: SpaceBetween :: SpaceBetween
+ Penny.Lincoln.Bits: SubAccount :: Text -> SubAccount
+ Penny.Lincoln.Bits: TopLineLine :: Int -> TopLineLine
+ Penny.Lincoln.Bits: TopMemoLine :: Int -> TopMemoLine
+ Penny.Lincoln.Bits: Whole :: String -> NumberStr
+ Penny.Lincoln.Bits: WholeRad :: String -> NumberStr
+ Penny.Lincoln.Bits: WholeRadFrac :: String -> String -> NumberStr
+ Penny.Lincoln.Bits: allocate :: Qty -> NonEmpty Qty -> NonEmpty Qty
+ Penny.Lincoln.Bits: data Hours
+ Penny.Lincoln.Bits: data Minutes
+ Penny.Lincoln.Bits: data NumberStr
+ Penny.Lincoln.Bits: data Seconds
+ Penny.Lincoln.Bits: data Side
+ Penny.Lincoln.Bits: data SpaceBetween
+ Penny.Lincoln.Bits: dateTimeMidnightUTC :: Day -> DateTime
+ Penny.Lincoln.Bits: day :: DateTime -> Day
+ Penny.Lincoln.Bits: equivalent :: Qty -> Qty -> Bool
+ Penny.Lincoln.Bits: fromZonedTime :: ZonedTime -> Maybe DateTime
+ Penny.Lincoln.Bits: hours :: DateTime -> Hours
+ Penny.Lincoln.Bits: instance Eq PricePoint
+ Penny.Lincoln.Bits: instance Show PricePoint
+ Penny.Lincoln.Bits: intToHours :: Int -> Maybe Hours
+ Penny.Lincoln.Bits: intToMinutes :: Int -> Maybe Minutes
+ Penny.Lincoln.Bits: intToSeconds :: Int -> Maybe Seconds
+ Penny.Lincoln.Bits: mantissa :: Qty -> Integer
+ Penny.Lincoln.Bits: midnight :: (Hours, Minutes, Seconds)
+ Penny.Lincoln.Bits: minutes :: DateTime -> Minutes
+ Penny.Lincoln.Bits: newtype FilePosting
+ Penny.Lincoln.Bits: newtype FileTransaction
+ Penny.Lincoln.Bits: newtype Filename
+ Penny.Lincoln.Bits: newtype GlobalPosting
+ Penny.Lincoln.Bits: newtype GlobalTransaction
+ Penny.Lincoln.Bits: newtype PostingLine
+ Penny.Lincoln.Bits: newtype PriceLine
+ Penny.Lincoln.Bits: newtype SubAccount
+ Penny.Lincoln.Bits: newtype TopLineLine
+ Penny.Lincoln.Bits: newtype TopMemoLine
+ Penny.Lincoln.Bits: places :: Qty -> Integer
+ Penny.Lincoln.Bits: ppSide :: PricePoint -> Maybe Side
+ Penny.Lincoln.Bits: ppSpaceBetween :: PricePoint -> Maybe SpaceBetween
+ Penny.Lincoln.Bits: price :: PricePoint -> Price
+ Penny.Lincoln.Bits: priceLine :: PricePoint -> Maybe PriceLine
+ Penny.Lincoln.Bits: sameInstant :: DateTime -> DateTime -> Bool
+ Penny.Lincoln.Bits: seconds :: DateTime -> Seconds
+ Penny.Lincoln.Bits: side :: Amount -> Maybe Side
+ Penny.Lincoln.Bits: spaceBetween :: Amount -> Maybe SpaceBetween
+ Penny.Lincoln.Bits: toQty :: NumberStr -> Maybe Qty
+ Penny.Lincoln.Bits: toUTC :: DateTime -> UTCTime
+ Penny.Lincoln.Bits: toZonedTime :: DateTime -> ZonedTime
+ Penny.Lincoln.Bits: type Mantissa = Integer
+ Penny.Lincoln.Bits: type Places = Integer
+ Penny.Lincoln.Bits: unFilePosting :: FilePosting -> Serial
+ Penny.Lincoln.Bits: unFileTransaction :: FileTransaction -> Serial
+ Penny.Lincoln.Bits: unFilename :: Filename -> Text
+ Penny.Lincoln.Bits: unGlobalPosting :: GlobalPosting -> Serial
+ Penny.Lincoln.Bits: unGlobalTransaction :: GlobalTransaction -> Serial
+ Penny.Lincoln.Bits: unPostingLine :: PostingLine -> Int
+ Penny.Lincoln.Bits: unPriceLine :: PriceLine -> Int
+ Penny.Lincoln.Bits: unSubAccount :: SubAccount -> Text
+ Penny.Lincoln.Bits: unTopLineLine :: TopLineLine -> Int
+ Penny.Lincoln.Bits: unTopMemoLine :: TopMemoLine -> Int
+ Penny.Lincoln.Bits: zeroHours :: Hours
+ Penny.Lincoln.Bits: zeroMinutes :: Minutes
+ Penny.Lincoln.Bits: zeroSeconds :: Seconds
+ Penny.Lincoln.Bits.DateTime: DateTime :: Day -> Hours -> Minutes -> Seconds -> TimeZoneOffset -> DateTime
+ Penny.Lincoln.Bits.DateTime: data Hours
+ Penny.Lincoln.Bits.DateTime: data Minutes
+ Penny.Lincoln.Bits.DateTime: data Seconds
+ Penny.Lincoln.Bits.DateTime: dateTimeMidnightUTC :: Day -> DateTime
+ Penny.Lincoln.Bits.DateTime: day :: DateTime -> Day
+ Penny.Lincoln.Bits.DateTime: fromZonedTime :: ZonedTime -> Maybe DateTime
+ Penny.Lincoln.Bits.DateTime: hours :: DateTime -> Hours
+ Penny.Lincoln.Bits.DateTime: instance Eq Hours
+ Penny.Lincoln.Bits.DateTime: instance Eq Minutes
+ Penny.Lincoln.Bits.DateTime: instance Eq Seconds
+ Penny.Lincoln.Bits.DateTime: instance Ord Hours
+ Penny.Lincoln.Bits.DateTime: instance Ord Minutes
+ Penny.Lincoln.Bits.DateTime: instance Ord Seconds
+ Penny.Lincoln.Bits.DateTime: instance Show Hours
+ Penny.Lincoln.Bits.DateTime: instance Show Minutes
+ Penny.Lincoln.Bits.DateTime: instance Show Seconds
+ Penny.Lincoln.Bits.DateTime: intToHours :: Int -> Maybe Hours
+ Penny.Lincoln.Bits.DateTime: intToMinutes :: Int -> Maybe Minutes
+ Penny.Lincoln.Bits.DateTime: intToSeconds :: Int -> Maybe Seconds
+ Penny.Lincoln.Bits.DateTime: midnight :: (Hours, Minutes, Seconds)
+ Penny.Lincoln.Bits.DateTime: minutes :: DateTime -> Minutes
+ Penny.Lincoln.Bits.DateTime: sameInstant :: DateTime -> DateTime -> Bool
+ Penny.Lincoln.Bits.DateTime: seconds :: DateTime -> Seconds
+ Penny.Lincoln.Bits.DateTime: toUTC :: DateTime -> UTCTime
+ Penny.Lincoln.Bits.DateTime: toZonedTime :: DateTime -> ZonedTime
+ Penny.Lincoln.Bits.DateTime: zeroHours :: Hours
+ Penny.Lincoln.Bits.DateTime: zeroMinutes :: Minutes
+ Penny.Lincoln.Bits.DateTime: zeroSeconds :: Seconds
+ Penny.Lincoln.Bits.Open: Account :: [SubAccount] -> Account
+ Penny.Lincoln.Bits.Open: Amount :: Qty -> Commodity -> Maybe Side -> Maybe SpaceBetween -> Amount
+ Penny.Lincoln.Bits.Open: Commodity :: Text -> Commodity
+ Penny.Lincoln.Bits.Open: CommodityOnLeft :: Side
+ Penny.Lincoln.Bits.Open: CommodityOnRight :: Side
+ Penny.Lincoln.Bits.Open: Credit :: DrCr
+ Penny.Lincoln.Bits.Open: Debit :: DrCr
+ Penny.Lincoln.Bits.Open: Entry :: DrCr -> Amount -> Entry
+ Penny.Lincoln.Bits.Open: FilePosting :: Serial -> FilePosting
+ Penny.Lincoln.Bits.Open: FileTransaction :: Serial -> FileTransaction
+ Penny.Lincoln.Bits.Open: Filename :: Text -> Filename
+ Penny.Lincoln.Bits.Open: Flag :: Text -> Flag
+ Penny.Lincoln.Bits.Open: GlobalPosting :: Serial -> GlobalPosting
+ Penny.Lincoln.Bits.Open: GlobalTransaction :: Serial -> GlobalTransaction
+ Penny.Lincoln.Bits.Open: Memo :: [Text] -> Memo
+ Penny.Lincoln.Bits.Open: NoSpaceBetween :: SpaceBetween
+ Penny.Lincoln.Bits.Open: Number :: Text -> Number
+ Penny.Lincoln.Bits.Open: Payee :: Text -> Payee
+ Penny.Lincoln.Bits.Open: PostingLine :: Int -> PostingLine
+ Penny.Lincoln.Bits.Open: PriceLine :: Int -> PriceLine
+ Penny.Lincoln.Bits.Open: SpaceBetween :: SpaceBetween
+ Penny.Lincoln.Bits.Open: SubAccount :: Text -> SubAccount
+ Penny.Lincoln.Bits.Open: Tag :: Text -> Tag
+ Penny.Lincoln.Bits.Open: Tags :: [Tag] -> Tags
+ Penny.Lincoln.Bits.Open: TopLineLine :: Int -> TopLineLine
+ Penny.Lincoln.Bits.Open: TopMemoLine :: Int -> TopMemoLine
+ Penny.Lincoln.Bits.Open: amount :: Entry -> Amount
+ Penny.Lincoln.Bits.Open: commodity :: Amount -> Commodity
+ Penny.Lincoln.Bits.Open: data Amount
+ Penny.Lincoln.Bits.Open: data DrCr
+ Penny.Lincoln.Bits.Open: data Entry
+ Penny.Lincoln.Bits.Open: data Side
+ Penny.Lincoln.Bits.Open: data SpaceBetween
+ Penny.Lincoln.Bits.Open: drCr :: Entry -> DrCr
+ Penny.Lincoln.Bits.Open: instance Eq Account
+ Penny.Lincoln.Bits.Open: instance Eq Amount
+ Penny.Lincoln.Bits.Open: instance Eq Commodity
+ Penny.Lincoln.Bits.Open: instance Eq DrCr
+ Penny.Lincoln.Bits.Open: instance Eq Entry
+ Penny.Lincoln.Bits.Open: instance Eq FilePosting
+ Penny.Lincoln.Bits.Open: instance Eq FileTransaction
+ Penny.Lincoln.Bits.Open: instance Eq Filename
+ Penny.Lincoln.Bits.Open: instance Eq Flag
+ Penny.Lincoln.Bits.Open: instance Eq GlobalPosting
+ Penny.Lincoln.Bits.Open: instance Eq GlobalTransaction
+ Penny.Lincoln.Bits.Open: instance Eq Memo
+ Penny.Lincoln.Bits.Open: instance Eq Number
+ Penny.Lincoln.Bits.Open: instance Eq Payee
+ Penny.Lincoln.Bits.Open: instance Eq PostingLine
+ Penny.Lincoln.Bits.Open: instance Eq PriceLine
+ Penny.Lincoln.Bits.Open: instance Eq Side
+ Penny.Lincoln.Bits.Open: instance Eq SpaceBetween
+ Penny.Lincoln.Bits.Open: instance Eq SubAccount
+ Penny.Lincoln.Bits.Open: instance Eq Tag
+ Penny.Lincoln.Bits.Open: instance Eq Tags
+ Penny.Lincoln.Bits.Open: instance Eq TopLineLine
+ Penny.Lincoln.Bits.Open: instance Eq TopMemoLine
+ Penny.Lincoln.Bits.Open: instance Ord Account
+ Penny.Lincoln.Bits.Open: instance Ord Amount
+ Penny.Lincoln.Bits.Open: instance Ord Commodity
+ Penny.Lincoln.Bits.Open: instance Ord DrCr
+ Penny.Lincoln.Bits.Open: instance Ord Entry
+ Penny.Lincoln.Bits.Open: instance Ord Flag
+ Penny.Lincoln.Bits.Open: instance Ord Memo
+ Penny.Lincoln.Bits.Open: instance Ord Number
+ Penny.Lincoln.Bits.Open: instance Ord Payee
+ Penny.Lincoln.Bits.Open: instance Ord Side
+ Penny.Lincoln.Bits.Open: instance Ord SpaceBetween
+ Penny.Lincoln.Bits.Open: instance Ord SubAccount
+ Penny.Lincoln.Bits.Open: instance Ord Tag
+ Penny.Lincoln.Bits.Open: instance Ord Tags
+ Penny.Lincoln.Bits.Open: instance Show Account
+ Penny.Lincoln.Bits.Open: instance Show Amount
+ Penny.Lincoln.Bits.Open: instance Show Commodity
+ Penny.Lincoln.Bits.Open: instance Show DrCr
+ Penny.Lincoln.Bits.Open: instance Show Entry
+ Penny.Lincoln.Bits.Open: instance Show FilePosting
+ Penny.Lincoln.Bits.Open: instance Show FileTransaction
+ Penny.Lincoln.Bits.Open: instance Show Filename
+ Penny.Lincoln.Bits.Open: instance Show Flag
+ Penny.Lincoln.Bits.Open: instance Show GlobalPosting
+ Penny.Lincoln.Bits.Open: instance Show GlobalTransaction
+ Penny.Lincoln.Bits.Open: instance Show Memo
+ Penny.Lincoln.Bits.Open: instance Show Number
+ Penny.Lincoln.Bits.Open: instance Show Payee
+ Penny.Lincoln.Bits.Open: instance Show PostingLine
+ Penny.Lincoln.Bits.Open: instance Show PriceLine
+ Penny.Lincoln.Bits.Open: instance Show Side
+ Penny.Lincoln.Bits.Open: instance Show SpaceBetween
+ Penny.Lincoln.Bits.Open: instance Show SubAccount
+ Penny.Lincoln.Bits.Open: instance Show Tag
+ Penny.Lincoln.Bits.Open: instance Show Tags
+ Penny.Lincoln.Bits.Open: instance Show TopLineLine
+ Penny.Lincoln.Bits.Open: instance Show TopMemoLine
+ Penny.Lincoln.Bits.Open: newtype Account
+ Penny.Lincoln.Bits.Open: newtype Commodity
+ Penny.Lincoln.Bits.Open: newtype FilePosting
+ Penny.Lincoln.Bits.Open: newtype FileTransaction
+ Penny.Lincoln.Bits.Open: newtype Filename
+ Penny.Lincoln.Bits.Open: newtype Flag
+ Penny.Lincoln.Bits.Open: newtype GlobalPosting
+ Penny.Lincoln.Bits.Open: newtype GlobalTransaction
+ Penny.Lincoln.Bits.Open: newtype Memo
+ Penny.Lincoln.Bits.Open: newtype Number
+ Penny.Lincoln.Bits.Open: newtype Payee
+ Penny.Lincoln.Bits.Open: newtype PostingLine
+ Penny.Lincoln.Bits.Open: newtype PriceLine
+ Penny.Lincoln.Bits.Open: newtype SubAccount
+ Penny.Lincoln.Bits.Open: newtype Tag
+ Penny.Lincoln.Bits.Open: newtype Tags
+ Penny.Lincoln.Bits.Open: newtype TopLineLine
+ Penny.Lincoln.Bits.Open: newtype TopMemoLine
+ Penny.Lincoln.Bits.Open: opposite :: DrCr -> DrCr
+ Penny.Lincoln.Bits.Open: qty :: Amount -> Qty
+ Penny.Lincoln.Bits.Open: side :: Amount -> Maybe Side
+ Penny.Lincoln.Bits.Open: spaceBetween :: Amount -> Maybe SpaceBetween
+ Penny.Lincoln.Bits.Open: unAccount :: Account -> [SubAccount]
+ Penny.Lincoln.Bits.Open: unCommodity :: Commodity -> Text
+ Penny.Lincoln.Bits.Open: unFilePosting :: FilePosting -> Serial
+ Penny.Lincoln.Bits.Open: unFileTransaction :: FileTransaction -> Serial
+ Penny.Lincoln.Bits.Open: unFilename :: Filename -> Text
+ Penny.Lincoln.Bits.Open: unFlag :: Flag -> Text
+ Penny.Lincoln.Bits.Open: unGlobalPosting :: GlobalPosting -> Serial
+ Penny.Lincoln.Bits.Open: unGlobalTransaction :: GlobalTransaction -> Serial
+ Penny.Lincoln.Bits.Open: unMemo :: Memo -> [Text]
+ Penny.Lincoln.Bits.Open: unNumber :: Number -> Text
+ Penny.Lincoln.Bits.Open: unPayee :: Payee -> Text
+ Penny.Lincoln.Bits.Open: unPostingLine :: PostingLine -> Int
+ Penny.Lincoln.Bits.Open: unPriceLine :: PriceLine -> Int
+ Penny.Lincoln.Bits.Open: unSubAccount :: SubAccount -> Text
+ Penny.Lincoln.Bits.Open: unTag :: Tag -> Text
+ Penny.Lincoln.Bits.Open: unTags :: Tags -> [Tag]
+ Penny.Lincoln.Bits.Open: unTopLineLine :: TopLineLine -> Int
+ Penny.Lincoln.Bits.Open: unTopMemoLine :: TopMemoLine -> Int
+ 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: allocate :: Qty -> NonEmpty Qty -> NonEmpty Qty
+ Penny.Lincoln.Bits.Qty: data NumberStr
+ Penny.Lincoln.Bits.Qty: equivalent :: Qty -> Qty -> Bool
+ Penny.Lincoln.Bits.Qty: instance Eq Difference
+ Penny.Lincoln.Bits.Qty: instance Show Difference
+ Penny.Lincoln.Bits.Qty: instance Show NumberStr
+ Penny.Lincoln.Bits.Qty: largestRemainderMethod :: TotSeats -> [PartyVotes] -> [SeatsWon]
+ Penny.Lincoln.Bits.Qty: mantissa :: Qty -> Integer
+ Penny.Lincoln.Bits.Qty: places :: Qty -> Integer
+ Penny.Lincoln.Bits.Qty: toQty :: NumberStr -> Maybe Qty
+ Penny.Lincoln.Bits.Qty: type Mantissa = Integer
+ Penny.Lincoln.Bits.Qty: type PartyVotes = Integer
+ Penny.Lincoln.Bits.Qty: type Places = Integer
+ Penny.Lincoln.Bits.Qty: type SeatsWon = Integer
+ Penny.Lincoln.Bits.Qty: type TotSeats = Integer
+ Penny.Lincoln.Family: filterChildren :: (a -> Bool) -> Family p a -> Maybe (Family p a)
+ Penny.Lincoln.Family: find :: (p -> c -> Bool) -> Family p c -> Maybe c
+ Penny.Lincoln.Family: mapChildrenA :: Applicative m => (a -> m b) -> Family p a -> m (Family p b)
+ Penny.Lincoln.Family: mapParentA :: Applicative m => (a -> m b) -> Family a c -> m (Family b c)
+ Penny.Lincoln.Family.Family: filterChildren :: (a -> Bool) -> Family p a -> Maybe (Family p a)
+ Penny.Lincoln.Family.Family: find :: (p -> c -> Bool) -> Family p c -> Maybe c
+ Penny.Lincoln.Family.Family: mapChildrenA :: Applicative m => (a -> m b) -> Family p a -> m (Family p b)
+ Penny.Lincoln.Family.Family: mapParentA :: Applicative m => (a -> m b) -> Family a c -> m (Family b c)
+ Penny.Lincoln.HasText: instance HasText Commodity
+ Penny.Lincoln.HasText: instance HasText SubAccount
+ Penny.Lincoln.NestedMap: toForest :: Ord k => NestedMap k l -> Forest (k, l)
+ Penny.Lincoln.Predicates: (&&&) :: (a -> Bool) -> (a -> Bool) -> (a -> Bool)
+ Penny.Lincoln.Predicates: (|||) :: (a -> Bool) -> (a -> Bool) -> (a -> Bool)
+ Penny.Lincoln.Predicates: clonedPostings :: Posting -> Posting -> Bool
+ Penny.Lincoln.Predicates: clonedTopLines :: TopLine -> TopLine -> Bool
+ Penny.Lincoln.Predicates: clonedTransactions :: Transaction -> Transaction -> Bool
+ Penny.Lincoln.Predicates: localDay :: (Day -> Bool) -> PostFam -> Bool
+ Penny.Lincoln.Queries: localDay :: PostFam -> Day
+ Penny.Lincoln.Queries: side :: PostFam -> Maybe Side
+ Penny.Lincoln.Queries: spaceBetween :: PostFam -> Maybe SpaceBetween
+ Penny.Lincoln.Serial: data GenSerial a
+ Penny.Lincoln.Serial: getSerial :: GenSerial Serial
+ Penny.Lincoln.Serial: incrementBack :: GenSerial ()
+ Penny.Lincoln.Serial: instance Applicative GenSerial
+ Penny.Lincoln.Serial: instance Functor GenSerial
+ Penny.Lincoln.Serial: instance Monad GenSerial
+ Penny.Lincoln.Serial: instance Ord Serial
+ Penny.Lincoln.Serial: instance Show SerialSt
+ Penny.Lincoln.Serial: makeSerials :: GenSerial a -> a
+ Penny.Lincoln.Serial: nSerials :: Int -> [Serial]
+ Penny.Lincoln.Transaction: PostingChangeData :: Maybe (Maybe Payee) -> Maybe (Maybe Number) -> Maybe (Maybe Flag) -> Maybe Account -> Maybe Tags -> Maybe (Maybe Memo) -> Maybe (Maybe Side) -> Maybe (Maybe SpaceBetween) -> Maybe (Maybe PostingLine) -> Maybe (Maybe GlobalPosting) -> Maybe (Maybe FilePosting) -> PostingChangeData
+ Penny.Lincoln.Transaction: RTransaction :: Commodity -> Maybe Side -> Maybe SpaceBetween -> DrCr -> TopLine -> RPosting -> [RPosting] -> IPosting -> RTransaction
+ Penny.Lincoln.Transaction: TopLineChangeData :: Maybe DateTime -> Maybe (Maybe Flag) -> Maybe (Maybe Number) -> Maybe (Maybe Payee) -> Maybe (Maybe Memo) -> Maybe (Maybe TopLineLine) -> Maybe (Maybe TopMemoLine) -> Maybe (Maybe Filename) -> Maybe (Maybe GlobalTransaction) -> Maybe (Maybe FileTransaction) -> TopLineChangeData
+ Penny.Lincoln.Transaction: changeTransaction :: Family TopLineChangeData PostingChangeData -> Transaction -> Transaction
+ Penny.Lincoln.Transaction: data PostingChangeData
+ Penny.Lincoln.Transaction: data RTransaction
+ Penny.Lincoln.Transaction: data TopLineChangeData
+ Penny.Lincoln.Transaction: emptyPostingChangeData :: PostingChangeData
+ Penny.Lincoln.Transaction: emptyTopLineChangeData :: TopLineChangeData
+ Penny.Lincoln.Transaction: instance Show PostingChangeData
+ Penny.Lincoln.Transaction: instance Show RTransaction
+ Penny.Lincoln.Transaction: instance Show TopLineChangeData
+ Penny.Lincoln.Transaction: pFilePosting :: Posting -> Maybe FilePosting
+ Penny.Lincoln.Transaction: pGlobalPosting :: Posting -> Maybe GlobalPosting
+ Penny.Lincoln.Transaction: pPostingLine :: Posting -> Maybe PostingLine
+ Penny.Lincoln.Transaction: pcAccount :: PostingChangeData -> Maybe Account
+ Penny.Lincoln.Transaction: pcFilePosting :: PostingChangeData -> Maybe (Maybe FilePosting)
+ Penny.Lincoln.Transaction: pcFlag :: PostingChangeData -> Maybe (Maybe Flag)
+ Penny.Lincoln.Transaction: pcGlobalPosting :: PostingChangeData -> Maybe (Maybe GlobalPosting)
+ Penny.Lincoln.Transaction: pcMemo :: PostingChangeData -> Maybe (Maybe Memo)
+ Penny.Lincoln.Transaction: pcNumber :: PostingChangeData -> Maybe (Maybe Number)
+ Penny.Lincoln.Transaction: pcPayee :: PostingChangeData -> Maybe (Maybe Payee)
+ Penny.Lincoln.Transaction: pcPostingLine :: PostingChangeData -> Maybe (Maybe PostingLine)
+ Penny.Lincoln.Transaction: pcSide :: PostingChangeData -> Maybe (Maybe Side)
+ Penny.Lincoln.Transaction: pcSpaceBetween :: PostingChangeData -> Maybe (Maybe SpaceBetween)
+ Penny.Lincoln.Transaction: pcTags :: PostingChangeData -> Maybe Tags
+ Penny.Lincoln.Transaction: rTransaction :: RTransaction -> Transaction
+ Penny.Lincoln.Transaction: rtCommodity :: RTransaction -> Commodity
+ Penny.Lincoln.Transaction: rtDrCr :: RTransaction -> DrCr
+ Penny.Lincoln.Transaction: rtIPosting :: RTransaction -> IPosting
+ Penny.Lincoln.Transaction: rtMorePostings :: RTransaction -> [RPosting]
+ Penny.Lincoln.Transaction: rtPosting :: RTransaction -> RPosting
+ Penny.Lincoln.Transaction: rtSide :: RTransaction -> Maybe Side
+ Penny.Lincoln.Transaction: rtSpaceBetween :: RTransaction -> Maybe SpaceBetween
+ Penny.Lincoln.Transaction: rtTopLine :: RTransaction -> TopLine
+ Penny.Lincoln.Transaction: tFileTransaction :: TopLine -> Maybe FileTransaction
+ Penny.Lincoln.Transaction: tFilename :: TopLine -> Maybe Filename
+ Penny.Lincoln.Transaction: tGlobalTransaction :: TopLine -> Maybe GlobalTransaction
+ Penny.Lincoln.Transaction: tTopLineLine :: TopLine -> Maybe TopLineLine
+ Penny.Lincoln.Transaction: tTopMemoLine :: TopLine -> Maybe TopMemoLine
+ Penny.Lincoln.Transaction: tcDateTime :: TopLineChangeData -> Maybe DateTime
+ Penny.Lincoln.Transaction: tcFileTransaction :: TopLineChangeData -> Maybe (Maybe FileTransaction)
+ Penny.Lincoln.Transaction: tcFilename :: TopLineChangeData -> Maybe (Maybe Filename)
+ Penny.Lincoln.Transaction: tcFlag :: TopLineChangeData -> Maybe (Maybe Flag)
+ Penny.Lincoln.Transaction: tcGlobalTransaction :: TopLineChangeData -> Maybe (Maybe GlobalTransaction)
+ Penny.Lincoln.Transaction: tcMemo :: TopLineChangeData -> Maybe (Maybe Memo)
+ Penny.Lincoln.Transaction: tcNumber :: TopLineChangeData -> Maybe (Maybe Number)
+ Penny.Lincoln.Transaction: tcPayee :: TopLineChangeData -> Maybe (Maybe Payee)
+ Penny.Lincoln.Transaction: tcTopLineLine :: TopLineChangeData -> Maybe (Maybe TopLineLine)
+ Penny.Lincoln.Transaction: tcTopMemoLine :: TopLineChangeData -> Maybe (Maybe TopMemoLine)
+ Penny.Lincoln.Transaction: toUnverified :: Transaction -> Family TopLine Posting
+ Penny.Lincoln.Transaction: unPostFam :: PostFam -> Child TopLine Posting
+ Penny.Lincoln.Transaction.Unverified: IPosting :: Maybe Payee -> Maybe Number -> Maybe Flag -> Account -> Tags -> Maybe Memo -> Maybe PostingLine -> Maybe GlobalPosting -> Maybe FilePosting -> IPosting
+ Penny.Lincoln.Transaction.Unverified: RPosting :: Maybe Payee -> Maybe Number -> Maybe Flag -> Account -> Tags -> Qty -> Maybe Memo -> Maybe PostingLine -> Maybe GlobalPosting -> Maybe FilePosting -> RPosting
+ Penny.Lincoln.Transaction.Unverified: data IPosting
+ Penny.Lincoln.Transaction.Unverified: data RPosting
+ Penny.Lincoln.Transaction.Unverified: emptyIPosting :: Account -> IPosting
+ Penny.Lincoln.Transaction.Unverified: emptyPosting :: Account -> Posting
+ Penny.Lincoln.Transaction.Unverified: emptyRPosting :: Account -> Qty -> RPosting
+ Penny.Lincoln.Transaction.Unverified: emptyTopLine :: DateTime -> TopLine
+ Penny.Lincoln.Transaction.Unverified: iAccount :: IPosting -> Account
+ Penny.Lincoln.Transaction.Unverified: iFilePosting :: IPosting -> Maybe FilePosting
+ Penny.Lincoln.Transaction.Unverified: iFlag :: IPosting -> Maybe Flag
+ Penny.Lincoln.Transaction.Unverified: iGlobalPosting :: IPosting -> Maybe GlobalPosting
+ Penny.Lincoln.Transaction.Unverified: iMemo :: IPosting -> Maybe Memo
+ Penny.Lincoln.Transaction.Unverified: iNumber :: IPosting -> Maybe Number
+ Penny.Lincoln.Transaction.Unverified: iPayee :: IPosting -> Maybe Payee
+ Penny.Lincoln.Transaction.Unverified: iPostingLine :: IPosting -> Maybe PostingLine
+ Penny.Lincoln.Transaction.Unverified: iTags :: IPosting -> Tags
+ Penny.Lincoln.Transaction.Unverified: instance Eq IPosting
+ Penny.Lincoln.Transaction.Unverified: instance Eq RPosting
+ Penny.Lincoln.Transaction.Unverified: instance Show IPosting
+ Penny.Lincoln.Transaction.Unverified: instance Show RPosting
+ Penny.Lincoln.Transaction.Unverified: pAccount :: Posting -> Account
+ Penny.Lincoln.Transaction.Unverified: pEntry :: Posting -> Maybe Entry
+ Penny.Lincoln.Transaction.Unverified: pFilePosting :: Posting -> Maybe FilePosting
+ Penny.Lincoln.Transaction.Unverified: pFlag :: Posting -> Maybe Flag
+ Penny.Lincoln.Transaction.Unverified: pGlobalPosting :: Posting -> Maybe GlobalPosting
+ Penny.Lincoln.Transaction.Unverified: pMemo :: Posting -> Maybe Memo
+ Penny.Lincoln.Transaction.Unverified: pNumber :: Posting -> Maybe Number
+ Penny.Lincoln.Transaction.Unverified: pPayee :: Posting -> Maybe Payee
+ Penny.Lincoln.Transaction.Unverified: pPostingLine :: Posting -> Maybe PostingLine
+ Penny.Lincoln.Transaction.Unverified: pTags :: Posting -> Tags
+ Penny.Lincoln.Transaction.Unverified: rAccount :: RPosting -> Account
+ Penny.Lincoln.Transaction.Unverified: rFilePosting :: RPosting -> Maybe FilePosting
+ Penny.Lincoln.Transaction.Unverified: rFlag :: RPosting -> Maybe Flag
+ Penny.Lincoln.Transaction.Unverified: rGlobalPosting :: RPosting -> Maybe GlobalPosting
+ Penny.Lincoln.Transaction.Unverified: rMemo :: RPosting -> Maybe Memo
+ Penny.Lincoln.Transaction.Unverified: rNumber :: RPosting -> Maybe Number
+ Penny.Lincoln.Transaction.Unverified: rPayee :: RPosting -> Maybe Payee
+ Penny.Lincoln.Transaction.Unverified: rPostingLine :: RPosting -> Maybe PostingLine
+ Penny.Lincoln.Transaction.Unverified: rQty :: RPosting -> Qty
+ Penny.Lincoln.Transaction.Unverified: rTags :: RPosting -> Tags
+ Penny.Lincoln.Transaction.Unverified: tDateTime :: TopLine -> DateTime
+ Penny.Lincoln.Transaction.Unverified: tFileTransaction :: TopLine -> Maybe FileTransaction
+ Penny.Lincoln.Transaction.Unverified: tFilename :: TopLine -> Maybe Filename
+ Penny.Lincoln.Transaction.Unverified: tFlag :: TopLine -> Maybe Flag
+ Penny.Lincoln.Transaction.Unverified: tGlobalTransaction :: TopLine -> Maybe GlobalTransaction
+ Penny.Lincoln.Transaction.Unverified: tMemo :: TopLine -> Maybe Memo
+ Penny.Lincoln.Transaction.Unverified: tNumber :: TopLine -> Maybe Number
+ Penny.Lincoln.Transaction.Unverified: tPayee :: TopLine -> Maybe Payee
+ Penny.Lincoln.Transaction.Unverified: tTopLineLine :: TopLine -> Maybe TopLineLine
+ Penny.Lincoln.Transaction.Unverified: tTopMemoLine :: TopLine -> Maybe TopMemoLine
+ Penny.Zinc: Account :: SortField
+ Penny.Zinc: ColorToFile :: Bool -> ColorToFile
+ Penny.Zinc: Commodity :: SortField
+ Penny.Zinc: Date :: SortField
+ Penny.Zinc: Defaults :: CaseSensitive -> Matcher -> ColorToFile -> Maybe Scheme -> [Scheme] -> [(SortField, SortOrder)] -> Defaults
+ Penny.Zinc: DrCr :: SortField
+ Penny.Zinc: Exact :: Matcher
+ Penny.Zinc: Flag :: SortField
+ Penny.Zinc: Number :: SortField
+ Penny.Zinc: PCRE :: Matcher
+ Penny.Zinc: Payee :: SortField
+ Penny.Zinc: PostingMemo :: SortField
+ Penny.Zinc: Qty :: SortField
+ Penny.Zinc: TDFA :: Matcher
+ Penny.Zinc: TransactionMemo :: SortField
+ Penny.Zinc: Within :: Matcher
+ Penny.Zinc: colorToFile :: Defaults -> ColorToFile
+ Penny.Zinc: data Defaults
+ Penny.Zinc: data Matcher
+ Penny.Zinc: data SortField
+ Penny.Zinc: defaultScheme :: Defaults -> Maybe Scheme
+ Penny.Zinc: instance Eq ColorToFile
+ Penny.Zinc: instance Eq Matcher
+ Penny.Zinc: instance Eq SortField
+ Penny.Zinc: instance Ord SortField
+ Penny.Zinc: instance Show ColorToFile
+ Penny.Zinc: instance Show Matcher
+ Penny.Zinc: instance Show SortField
+ Penny.Zinc: matcher :: Defaults -> Matcher
+ Penny.Zinc: moreSchemes :: Defaults -> [Scheme]
+ Penny.Zinc: newtype ColorToFile
+ Penny.Zinc: runZinc :: Defaults -> Runtime -> [Report] -> IO ()
+ Penny.Zinc: sorter :: Defaults -> [(SortField, SortOrder)]
+ Penny.Zinc: unColorToFile :: ColorToFile -> Bool
- Penny.Cabin.Balance: convert :: BalanceOpts -> Maybe (Commodity, DateTime)
+ Penny.Cabin.Balance: convert :: Report
- Penny.Cabin.Meta: visibleNums :: (VisibleNum -> a -> b) -> [Box a] -> [Box b]
+ Penny.Cabin.Meta: visibleNums :: (VisibleNum -> a -> b) -> [a] -> [b]
- Penny.Cabin.Posts: ZincOpts :: DefaultTimeZone -> RadGroup -> Fields Bool -> Colors -> DrCrColors -> BaseColors -> ReportWidth -> ShowZeroBalances -> (Box -> Text) -> (Box -> Text) -> (Commodity -> BottomLine -> Text) -> SubAccountLength -> Allocation -> Allocation -> Spacers Int -> ZincOpts
+ Penny.Cabin.Posts: ZincOpts :: Fields Bool -> ReportWidth -> ShowZeroBalances -> (Box -> Text) -> (Box -> Text) -> (Commodity -> Qty -> Text) -> SubAccountLength -> Alloc -> Alloc -> Spacers Int -> ZincOpts
- Penny.Cabin.Posts: accountAllocation :: ZincOpts -> Allocation
+ Penny.Cabin.Posts: accountAllocation :: ZincOpts -> Alloc
- Penny.Cabin.Posts: balanceAsIs :: a -> BottomLine -> Text
+ Penny.Cabin.Posts: balanceAsIs :: a -> Qty -> Text
- Penny.Cabin.Posts: balanceFormat :: ZincOpts -> Commodity -> BottomLine -> Text
+ Penny.Cabin.Posts: balanceFormat :: ZincOpts -> Commodity -> Qty -> Text
- Penny.Cabin.Posts: defaultOptions :: DefaultTimeZone -> RadGroup -> Runtime -> ZincOpts
+ Penny.Cabin.Posts: defaultOptions :: Runtime -> ZincOpts
- Penny.Cabin.Posts: payeeAllocation :: ZincOpts -> Allocation
+ Penny.Cabin.Posts: payeeAllocation :: ZincOpts -> Alloc
- Penny.Cabin.Posts: postsReport :: Colors -> ShowZeroBalances -> (Box LibertyMeta -> Bool) -> [PostFilterFn] -> ChunkOpts -> [Box LibertyMeta] -> Text
+ Penny.Cabin.Posts: postsReport :: ShowZeroBalances -> (Box LibertyMeta -> Bool) -> [PostFilterFn] -> ChunkOpts -> [Box LibertyMeta] -> [PreChunk]
- Penny.Cabin.Posts.Allocated: AllocatedOpts :: Fields Bool -> SubAccountLength -> BaseColors -> Allocation -> Allocation -> Spacers Int -> Fields (Maybe Int) -> ReportWidth -> AllocatedOpts
+ Penny.Cabin.Posts.Allocated: AllocatedOpts :: Fields Bool -> SubAccountLength -> Fields Alloc -> Spacers Int -> Fields (Maybe Int) -> ReportWidth -> AllocatedOpts
- Penny.Cabin.Posts.BottomRows: BottomOpts :: Fields (Maybe Int) -> Fields (Maybe Int) -> Fields Bool -> BaseColors -> ReportWidth -> Spacers Int -> BottomOpts
+ Penny.Cabin.Posts.BottomRows: BottomOpts :: Fields (Maybe Int) -> Fields (Maybe Int) -> Fields Bool -> ReportWidth -> Spacers Int -> BottomOpts
- Penny.Cabin.Posts.BottomRows: bottomRows :: BottomOpts -> [Box] -> Fields (Maybe [[Chunk]])
+ Penny.Cabin.Posts.BottomRows: bottomRows :: BottomOpts -> [Box] -> Fields (Maybe [[PreChunk]])
- Penny.Cabin.Posts.Chunk: ChunkOpts :: BaseColors -> DrCrColors -> (Box -> Text) -> (Box -> Text) -> (Commodity -> BottomLine -> Text) -> Fields Bool -> SubAccountLength -> Allocation -> Allocation -> Spacers Int -> ReportWidth -> ChunkOpts
+ Penny.Cabin.Posts.Chunk: ChunkOpts :: (Box -> Text) -> (Box -> Text) -> (Commodity -> Qty -> Text) -> Fields Bool -> SubAccountLength -> Alloc -> Alloc -> Spacers Int -> ReportWidth -> ChunkOpts
- Penny.Cabin.Posts.Chunk: accountAllocation :: ChunkOpts -> Allocation
+ Penny.Cabin.Posts.Chunk: accountAllocation :: ChunkOpts -> Alloc
- Penny.Cabin.Posts.Chunk: balanceFormat :: ChunkOpts -> Commodity -> BottomLine -> Text
+ Penny.Cabin.Posts.Chunk: balanceFormat :: ChunkOpts -> Commodity -> Qty -> Text
- Penny.Cabin.Posts.Chunk: makeChunk :: ChunkOpts -> [Box] -> [Chunk]
+ Penny.Cabin.Posts.Chunk: makeChunk :: ChunkOpts -> [Box] -> [PreChunk]
- Penny.Cabin.Posts.Chunk: payeeAllocation :: ChunkOpts -> Allocation
+ Penny.Cabin.Posts.Chunk: payeeAllocation :: ChunkOpts -> Alloc
- Penny.Cabin.Posts.Growers: GrowOpts :: BaseColors -> DrCrColors -> (Box -> Text) -> (Box -> Text) -> (Commodity -> BottomLine -> Text) -> Fields Bool -> GrowOpts
+ Penny.Cabin.Posts.Growers: GrowOpts :: (Box -> Text) -> (Box -> Text) -> (Commodity -> Qty -> Text) -> Fields Bool -> GrowOpts
- Penny.Cabin.Posts.Growers: balanceFormat :: GrowOpts -> Commodity -> BottomLine -> Text
+ Penny.Cabin.Posts.Growers: balanceFormat :: GrowOpts -> Commodity -> Qty -> Text
- Penny.Cabin.Posts.Parser: State :: CaseSensitive -> Factory -> [Token (Box LibertyMeta -> Bool)] -> [PostFilterFn] -> Fields Bool -> Colors -> DrCrColors -> BaseColors -> ReportWidth -> ShowZeroBalances -> State
+ Penny.Cabin.Posts.Parser: State :: CaseSensitive -> Factory -> [Token (Box LibertyMeta -> Bool)] -> [PostFilterFn] -> Fields Bool -> ReportWidth -> ShowZeroBalances -> Bool -> State
- Penny.Cabin.Row: ColumnSpec :: Justification -> Width -> TextSpec -> [Chunk] -> ColumnSpec
+ Penny.Cabin.Row: ColumnSpec :: Justification -> Width -> (Label, EvenOdd) -> [PreChunk] -> ColumnSpec
- Penny.Cabin.Row: bits :: ColumnSpec -> [Chunk]
+ Penny.Cabin.Row: bits :: ColumnSpec -> [PreChunk]
- Penny.Cabin.Row: padSpec :: ColumnSpec -> TextSpec
+ Penny.Cabin.Row: padSpec :: ColumnSpec -> (Label, EvenOdd)
- Penny.Cabin.Row: row :: [ColumnSpec] -> [Chunk]
+ Penny.Cabin.Row: row :: [ColumnSpec] -> [PreChunk]
- Penny.Copper: GroupAll :: GroupingSpec
+ Penny.Copper: GroupAll :: GroupSpec
- Penny.Copper: GroupLarge :: GroupingSpec
+ Penny.Copper: GroupLarge :: GroupSpec
- Penny.Copper: Ledger :: [(Line, Item)] -> Ledger
+ Penny.Copper: Ledger :: [Item] -> Ledger
- Penny.Copper: NoGrouping :: GroupingSpec
+ Penny.Copper: NoGrouping :: GroupSpec
- Penny.Copper: parse :: DefaultTimeZone -> RadGroup -> [(Filename, FileContents)] -> Exceptional ErrorMsg Ledger
+ Penny.Copper: parse :: [(Filename, FileContents)] -> Exceptional ErrorMsg Ledger
- Penny.Copper: unLedger :: Ledger -> [(Line, Item)]
+ Penny.Copper: unLedger :: Ledger -> [Item]
- Penny.Liberty: parseComparer :: (Eq a, Ord a) => String -> Maybe (a -> a -> Bool)
+ Penny.Liberty: parseComparer :: (Eq a, Ord a) => String -> Exceptional String (a -> a -> Bool)
- Penny.Lincoln: Account :: NonEmpty SubAccountName -> Account
+ Penny.Lincoln: Account :: [SubAccount] -> Account
- Penny.Lincoln: Amount :: Qty -> Commodity -> Amount
+ Penny.Lincoln: Amount :: Qty -> Commodity -> Maybe Side -> Maybe SpaceBetween -> Amount
- Penny.Lincoln: Commodity :: NonEmpty SubCommodity -> Commodity
+ Penny.Lincoln: Commodity :: Text -> Commodity
- Penny.Lincoln: Flag :: TextNonEmpty -> Flag
+ Penny.Lincoln: Flag :: Text -> Flag
- Penny.Lincoln: Memo :: [MemoLine] -> Memo
+ Penny.Lincoln: Memo :: [Text] -> Memo
- Penny.Lincoln: Number :: TextNonEmpty -> Number
+ Penny.Lincoln: Number :: Text -> Number
- Penny.Lincoln: Payee :: TextNonEmpty -> Payee
+ Penny.Lincoln: Payee :: Text -> Payee
- Penny.Lincoln: PricePoint :: DateTime -> Price -> PriceMeta -> PricePoint
+ Penny.Lincoln: PricePoint :: DateTime -> Price -> Maybe Side -> Maybe SpaceBetween -> Maybe PriceLine -> PricePoint
- Penny.Lincoln: Tag :: TextNonEmpty -> Tag
+ Penny.Lincoln: Tag :: Text -> Tag
- Penny.Lincoln: account :: String -> Exceptional String Account
+ Penny.Lincoln: account :: String -> Account
- Penny.Lincoln: convert :: PriceDb -> DateTime -> To -> Amount -> Exceptional PriceDbError Amount
+ Penny.Lincoln: convert :: PriceDb -> DateTime -> To -> Amount -> Exceptional PriceDbError Qty
- Penny.Lincoln: dateTime :: LocalTime -> TimeZoneOffset -> DateTime
+ Penny.Lincoln: dateTime :: PricePoint -> DateTime
- Penny.Lincoln: newQty :: Decimal -> Maybe Qty
+ Penny.Lincoln: newQty :: Mantissa -> Places -> Maybe Qty
- Penny.Lincoln: pFlag :: Posting -> (Maybe Flag)
+ Penny.Lincoln: pFlag :: Posting -> Maybe Flag
- Penny.Lincoln: pMemo :: Posting -> Memo
+ Penny.Lincoln: pMemo :: Posting -> Maybe Memo
- Penny.Lincoln: pNumber :: Posting -> (Maybe Number)
+ Penny.Lincoln: pNumber :: Posting -> Maybe Number
- Penny.Lincoln: pPayee :: Posting -> (Maybe Payee)
+ Penny.Lincoln: pPayee :: Posting -> Maybe Payee
- Penny.Lincoln: priceLine :: PriceMeta -> Maybe PriceLine
+ Penny.Lincoln: priceLine :: PricePoint -> Maybe PriceLine
- Penny.Lincoln: removeZeroCommodities :: Balance -> Maybe Balance
+ Penny.Lincoln: removeZeroCommodities :: Balance -> Balance
- Penny.Lincoln: side :: Format -> Side
+ Penny.Lincoln: side :: Amount -> Maybe Side
- Penny.Lincoln: tFlag :: TopLine -> (Maybe Flag)
+ Penny.Lincoln: tFlag :: TopLine -> Maybe Flag
- Penny.Lincoln: tMemo :: TopLine -> Memo
+ Penny.Lincoln: tMemo :: TopLine -> Maybe Memo
- Penny.Lincoln: tNumber :: TopLine -> (Maybe Number)
+ Penny.Lincoln: tNumber :: TopLine -> Maybe Number
- Penny.Lincoln: tPayee :: TopLine -> (Maybe Payee)
+ Penny.Lincoln: tPayee :: TopLine -> Maybe Payee
- Penny.Lincoln: unAccount :: Account -> NonEmpty SubAccountName
+ Penny.Lincoln: unAccount :: Account -> [SubAccount]
- Penny.Lincoln: unCommodity :: Commodity -> NonEmpty SubCommodity
+ Penny.Lincoln: unCommodity :: Commodity -> Text
- Penny.Lincoln: unFlag :: Flag -> TextNonEmpty
+ Penny.Lincoln: unFlag :: Flag -> Text
- Penny.Lincoln: unMemo :: Memo -> [MemoLine]
+ Penny.Lincoln: unMemo :: Memo -> [Text]
- Penny.Lincoln: unNumber :: Number -> TextNonEmpty
+ Penny.Lincoln: unNumber :: Number -> Text
- Penny.Lincoln: unPayee :: Payee -> TextNonEmpty
+ Penny.Lincoln: unPayee :: Payee -> Text
- Penny.Lincoln: unTag :: Tag -> TextNonEmpty
+ Penny.Lincoln: unTag :: Tag -> Text
- Penny.Lincoln.Balance: removeZeroCommodities :: Balance -> Maybe Balance
+ Penny.Lincoln.Balance: removeZeroCommodities :: Balance -> Balance
- Penny.Lincoln.Bits: Account :: NonEmpty SubAccountName -> Account
+ Penny.Lincoln.Bits: Account :: [SubAccount] -> Account
- Penny.Lincoln.Bits: Amount :: Qty -> Commodity -> Amount
+ Penny.Lincoln.Bits: Amount :: Qty -> Commodity -> Maybe Side -> Maybe SpaceBetween -> Amount
- Penny.Lincoln.Bits: Commodity :: NonEmpty SubCommodity -> Commodity
+ Penny.Lincoln.Bits: Commodity :: Text -> Commodity
- Penny.Lincoln.Bits: Flag :: TextNonEmpty -> Flag
+ Penny.Lincoln.Bits: Flag :: Text -> Flag
- Penny.Lincoln.Bits: Memo :: [MemoLine] -> Memo
+ Penny.Lincoln.Bits: Memo :: [Text] -> Memo
- Penny.Lincoln.Bits: Number :: TextNonEmpty -> Number
+ Penny.Lincoln.Bits: Number :: Text -> Number
- Penny.Lincoln.Bits: Payee :: TextNonEmpty -> Payee
+ Penny.Lincoln.Bits: Payee :: Text -> Payee
- Penny.Lincoln.Bits: PricePoint :: DateTime -> Price -> PriceMeta -> PricePoint
+ Penny.Lincoln.Bits: PricePoint :: DateTime -> Price -> Maybe Side -> Maybe SpaceBetween -> Maybe PriceLine -> PricePoint
- Penny.Lincoln.Bits: Tag :: TextNonEmpty -> Tag
+ Penny.Lincoln.Bits: Tag :: Text -> Tag
- Penny.Lincoln.Bits: dateTime :: LocalTime -> TimeZoneOffset -> DateTime
+ Penny.Lincoln.Bits: dateTime :: PricePoint -> DateTime
- Penny.Lincoln.Bits: newQty :: Decimal -> Maybe Qty
+ Penny.Lincoln.Bits: newQty :: Mantissa -> Places -> Maybe Qty
- Penny.Lincoln.Bits: unAccount :: Account -> NonEmpty SubAccountName
+ Penny.Lincoln.Bits: unAccount :: Account -> [SubAccount]
- Penny.Lincoln.Bits: unCommodity :: Commodity -> NonEmpty SubCommodity
+ Penny.Lincoln.Bits: unCommodity :: Commodity -> Text
- Penny.Lincoln.Bits: unFlag :: Flag -> TextNonEmpty
+ Penny.Lincoln.Bits: unFlag :: Flag -> Text
- Penny.Lincoln.Bits: unMemo :: Memo -> [MemoLine]
+ Penny.Lincoln.Bits: unMemo :: Memo -> [Text]
- Penny.Lincoln.Bits: unNumber :: Number -> TextNonEmpty
+ Penny.Lincoln.Bits: unNumber :: Number -> Text
- Penny.Lincoln.Bits: unPayee :: Payee -> TextNonEmpty
+ Penny.Lincoln.Bits: unPayee :: Payee -> Text
- Penny.Lincoln.Bits: unTag :: Tag -> TextNonEmpty
+ Penny.Lincoln.Bits: unTag :: Tag -> Text
- Penny.Lincoln.Bits.Qty: newQty :: Decimal -> Maybe Qty
+ Penny.Lincoln.Bits.Qty: newQty :: Mantissa -> Places -> Maybe Qty
- Penny.Lincoln.Builders: account :: String -> Exceptional String Account
+ Penny.Lincoln.Builders: account :: String -> Account
- Penny.Lincoln.Predicates: commodity :: Text -> (Text -> Bool) -> PostFam -> Bool
+ Penny.Lincoln.Predicates: commodity :: (Text -> Bool) -> PostFam -> Bool
- Penny.Lincoln.Predicates: matchMemo :: (Text -> Bool) -> Memo -> Bool
+ Penny.Lincoln.Predicates: matchMemo :: (Text -> Bool) -> Maybe Memo -> Bool
- Penny.Lincoln.PriceDb: convert :: PriceDb -> DateTime -> To -> Amount -> Exceptional PriceDbError Amount
+ Penny.Lincoln.PriceDb: convert :: PriceDb -> DateTime -> To -> Amount -> Exceptional PriceDbError Qty
- Penny.Lincoln.Queries: postingMemo :: PostFam -> Memo
+ Penny.Lincoln.Queries: postingMemo :: PostFam -> Maybe Memo
- Penny.Lincoln.Queries: transactionMemo :: PostFam -> Memo
+ Penny.Lincoln.Queries: transactionMemo :: PostFam -> Maybe Memo
- Penny.Lincoln.Transaction: pFlag :: Posting -> (Maybe Flag)
+ Penny.Lincoln.Transaction: pFlag :: Posting -> Maybe Flag
- Penny.Lincoln.Transaction: pMemo :: Posting -> Memo
+ Penny.Lincoln.Transaction: pMemo :: Posting -> Maybe Memo
- Penny.Lincoln.Transaction: pNumber :: Posting -> (Maybe Number)
+ Penny.Lincoln.Transaction: pNumber :: Posting -> Maybe Number
- Penny.Lincoln.Transaction: pPayee :: Posting -> (Maybe Payee)
+ Penny.Lincoln.Transaction: pPayee :: Posting -> Maybe Payee
- Penny.Lincoln.Transaction: tFlag :: TopLine -> (Maybe Flag)
+ Penny.Lincoln.Transaction: tFlag :: TopLine -> Maybe Flag
- Penny.Lincoln.Transaction: tMemo :: TopLine -> Memo
+ Penny.Lincoln.Transaction: tMemo :: TopLine -> Maybe Memo
- Penny.Lincoln.Transaction: tNumber :: TopLine -> (Maybe Number)
+ Penny.Lincoln.Transaction: tNumber :: TopLine -> Maybe Number
- Penny.Lincoln.Transaction: tPayee :: TopLine -> (Maybe Payee)
+ Penny.Lincoln.Transaction: tPayee :: TopLine -> Maybe Payee
- Penny.Lincoln.Transaction.Unverified: Posting :: (Maybe Payee) -> (Maybe Number) -> (Maybe Flag) -> Account -> Tags -> (Maybe Entry) -> Memo -> PostingMeta -> Posting
+ Penny.Lincoln.Transaction.Unverified: Posting :: Maybe Payee -> Maybe Number -> Maybe Flag -> Account -> Tags -> Maybe Entry -> Maybe Memo -> Maybe PostingLine -> Maybe GlobalPosting -> Maybe FilePosting -> Posting
- Penny.Lincoln.Transaction.Unverified: TopLine :: DateTime -> (Maybe Flag) -> (Maybe Number) -> (Maybe Payee) -> Memo -> TopLineMeta -> TopLine
+ Penny.Lincoln.Transaction.Unverified: TopLine :: DateTime -> Maybe Flag -> Maybe Number -> Maybe Payee -> Maybe Memo -> Maybe TopLineLine -> Maybe TopMemoLine -> Maybe Filename -> Maybe GlobalTransaction -> Maybe FileTransaction -> TopLine
- Penny.Zinc: sensitive :: T -> CaseSensitive
+ Penny.Zinc: sensitive :: Defaults -> CaseSensitive
Files
- LICENSE +5/−4
- Penny.hs +423/−69
- Penny/Brenner.hs +276/−0
- Penny/Brenner/Amex.hs +121/−0
- Penny/Brenner/BofA.hs +212/−0
- Penny/Brenner/Clear.hs +186/−0
- Penny/Brenner/Database.hs +51/−0
- Penny/Brenner/Import.hs +134/−0
- Penny/Brenner/Merge.hs +274/−0
- Penny/Brenner/Print.hs +71/−0
- Penny/Brenner/Types.hs +287/−0
- Penny/Brenner/Util.hs +97/−0
- Penny/Cabin.hs +5/−13
- Penny/Cabin/Allocate.hs +0/−72
- Penny/Cabin/Balance.hs +17/−103
- Penny/Cabin/Balance/Convert.hs +334/−0
- Penny/Cabin/Balance/Convert/Chunker.hs +227/−0
- Penny/Cabin/Balance/Convert/Options.hs +44/−0
- Penny/Cabin/Balance/Convert/Parser.hs +94/−0
- Penny/Cabin/Balance/Help.hs +0/−34
- Penny/Cabin/Balance/MultiCommodity.hs +173/−0
- Penny/Cabin/Balance/MultiCommodity/Chunker.hs +212/−0
- Penny/Cabin/Balance/MultiCommodity/Parser.hs +35/−0
- Penny/Cabin/Balance/Parser.hs +0/−156
- Penny/Cabin/Balance/Tree.hs +0/−468
- Penny/Cabin/Balance/Util.hs +228/−0
- Penny/Cabin/Chunk.hs +2468/−2407
- Penny/Cabin/Colors.hs +0/−50
- Penny/Cabin/Colors/DarkBackground.hs +0/−38
- Penny/Cabin/Colors/LightBackground.hs +0/−35
- Penny/Cabin/Interface.hs +37/−34
- Penny/Cabin/Meta.hs +27/−4
- Penny/Cabin/Options.hs +7/−24
- Penny/Cabin/Parsers.hs +24/−0
- Penny/Cabin/Posts.hs +276/−120
- Penny/Cabin/Posts/Allocate.hs +0/−65
- Penny/Cabin/Posts/Allocated.hs +208/−131
- Penny/Cabin/Posts/BottomRows.hs +70/−66
- Penny/Cabin/Posts/Chunk.hs +38/−49
- Penny/Cabin/Posts/Fields.hs +38/−5
- Penny/Cabin/Posts/Growers.hs +123/−182
- Penny/Cabin/Posts/Help.hs +0/−66
- Penny/Cabin/Posts/Meta.hs +13/−14
- Penny/Cabin/Posts/Parser.hs +127/−243
- Penny/Cabin/Posts/Spacers.hs +4/−5
- Penny/Cabin/Row.hs +51/−22
- Penny/Cabin/Scheme.hs +145/−0
- Penny/Cabin/Scheme/Schemes.hs +89/−0
- Penny/Copper.hs +187/−102
- Penny/Copper/Account.hs +0/−131
- Penny/Copper/Amount.hs +0/−96
- Penny/Copper/Comments.hs +0/−35
- Penny/Copper/Commodity.hs +0/−187
- Penny/Copper/DateTime.hs +0/−138
- Penny/Copper/Entry.hs +0/−43
- Penny/Copper/Flag.hs +0/−30
- Penny/Copper/Item.hs +0/−68
- Penny/Copper/Memos/Posting.hs +0/−39
- Penny/Copper/Memos/Transaction.hs +0/−45
- Penny/Copper/Number.hs +0/−29
- Penny/Copper/Parsec.hs +418/−0
- Penny/Copper/Payees.hs +0/−87
- Penny/Copper/Posting.hs +0/−126
- Penny/Copper/Price.hs +0/−97
- Penny/Copper/Qty.hs +0/−283
- Penny/Copper/Render.hs +523/−0
- Penny/Copper/Tags.hs +0/−37
- Penny/Copper/Terminals.hs +145/−0
- Penny/Copper/TopLine.hs +0/−59
- Penny/Copper/Transaction.hs +0/−75
- Penny/Copper/Types.hs +59/−0
- Penny/Copper/Util.hs +0/−150
- Penny/Liberty.hs +238/−341
- Penny/Liberty/Expressions.hs +3/−3
- Penny/Liberty/Expressions/Infix.hs +3/−3
- Penny/Liberty/Expressions/RPN.hs +2/−2
- Penny/Lincoln.hs +94/−81
- Penny/Lincoln/Balance.hs +14/−12
- Penny/Lincoln/Bits.hs +60/−38
- Penny/Lincoln/Bits/Account.hs +0/−12
- Penny/Lincoln/Bits/Amount.hs +0/−8
- Penny/Lincoln/Bits/Commodity.hs +0/−19
- Penny/Lincoln/Bits/DateTime.hs +105/−27
- Penny/Lincoln/Bits/DrCr.hs +0/−9
- Penny/Lincoln/Bits/Entry.hs +0/−9
- Penny/Lincoln/Bits/Flag.hs +0/−7
- Penny/Lincoln/Bits/Memo.hs +0/−9
- Penny/Lincoln/Bits/Number.hs +0/−6
- Penny/Lincoln/Bits/Open.hs +124/−0
- Penny/Lincoln/Bits/Payee.hs +0/−7
- Penny/Lincoln/Bits/Price.hs +6/−7
- Penny/Lincoln/Bits/PricePoint.hs +0/−10
- Penny/Lincoln/Bits/Qty.hs +264/−37
- Penny/Lincoln/Bits/Tags.hs +0/−10
- Penny/Lincoln/Builders.hs +8/−41
- Penny/Lincoln/Family.hs +6/−4
- Penny/Lincoln/Family/Family.hs +47/−21
- Penny/Lincoln/HasText.hs +11/−75
- Penny/Lincoln/Matchers.hs +3/−3
- Penny/Lincoln/Meta.hs +0/−71
- Penny/Lincoln/NestedMap.hs +12/−5
- Penny/Lincoln/Predicates.hs +90/−31
- Penny/Lincoln/PriceDb.hs +29/−70
- Penny/Lincoln/Queries.hs +26/−26
- Penny/Lincoln/Serial.hs +45/−92
- Penny/Lincoln/TextNonEmpty.hs +0/−25
- Penny/Lincoln/Transaction.hs +319/−99
- Penny/Lincoln/Transaction/Unverified.hs +123/−19
- Penny/Shield.hs +6/−7
- Penny/Zinc.hs +706/−19
- Penny/Zinc/Error.hs +0/−11
- Penny/Zinc/Help.hs +0/−125
- Penny/Zinc/Parser.hs +0/−86
- Penny/Zinc/Parser/Defaults.hs +0/−29
- Penny/Zinc/Parser/Filter.hs +0/−265
- Penny/Zinc/Parser/Ledgers.hs +0/−79
- Penny/Zinc/Parser/Report.hs +0/−30
- penny-lib.cabal +112/−262
LICENSE view
@@ -1,4 +1,4 @@-Copyright (c) 2011-2012 Omari Norman.+Copyright (c) 2011-2013 Omari Norman. All rights reserved. Redistribution and use in source and binary forms, with or without@@ -13,9 +13,10 @@ the documentation and/or other materials provided with the distribution. - * Neither the name of Omari Norman nor the names of its- contributors may be used to endorse or promote products derived- from this software without specific prior written permission.+ * Neither the name of Omari Norman nor the names of contributors+ to this software may be used to endorse or promote products+ derived from this software without specific prior written+ permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
Penny.hs view
@@ -1,88 +1,442 @@-{- | Penny - double-entry accounting system+-- | Penny - extensible double-entry accounting system -Penny is organized into a tree of modules, each with a name. Check out-the links for details on each component of Penny.+module Penny+ ( -- * Building a custom Penny binary -"Penny.Cabin" - Penny reports. Depends on Lincoln and Liberty.+ -- | Everything you need to create a custom Penny program is+ -- available by importing only this module.+ Defaults(..)+ , Z.Matcher(..) -"Penny.Copper" - the Penny parser. Depends on Lincoln.+ -- ** Color schemes+ , E.Scheme(..)+ , E.TextSpecs+ , E.Labels(..)+ , E.EvenAndOdd(..)+ , Sw.switchForeground+ , Sw.switchBackground+ , module Penny.Cabin.Chunk -"Penny.Liberty" - Penny command line parser helpers. Depends on-Lincoln and Copper.+ -- ** Sorting+ , Z.SortField(..)+ , CabP.SortOrder(..) -"Penny.Lincoln" - the Penny core. Depends on no other Penny components.+ -- ** Formatting quantities+ , defaultQtyFormat -"Penny.Shield" the Penny runtime environment+ -- ** Convert report options+ , Target(..)+ , CP.SortBy(..) -"Penny.Zinc" - the Penny command-line interface. Depends on Cabin,-Copper, Liberty, and Lincoln.+ -- ** Postings report options+ , M.Box+ , Fields(..)+ , Spacers(..)+ , widthFromRuntime+ , Ps.yearMonthDay+ , Ps.qtyAsIs+ , Ps.balanceAsIs -The dependencies are represented as a dot file in doc/dependencies.dot-in the Penny git repository.+ -- ** Runtime+ , S.Runtime+ , S.environment -This module exports a few functions that are useful for building a-simple command line program with default options. To make anything-more complicated you may need to import modules from deeper down-within the hierarchy, but for a program based on the defaults only you-should be able to import this module only (and nothing else).+ -- ** Text+ , X.Text+ , X.pack --}-module Penny (- -- * Default time zone- Copper.DefaultTimeZone (DefaultTimeZone)- , Copper.utcDefault- , minsToDefaultTimeZone- - -- * Radix and grouping characters- , Copper.RadGroup- , Copper.periodComma- , Copper.periodSpace- , Copper.commaPeriod- , Copper.commaSpace- - -- * Reports- , Cabin.allReportsWithDefaults- - -- * Parser defaults- , Z.T(..)- , Z.defaultFromRuntime- - -- * Main function- , defaultPenny- , customPenny- - -- * Other useful stuff - for custom reports+ -- ** Main function+ , runPenny++ -- * Developer overview++ -- | Penny is organized into a tree of modules, each with a+ -- name. Check out the links for details on each component of+ -- Penny.+ --+ -- "Penny.Brenner" - Penny financial institution transaction+ -- handling. Depends on Lincoln and Copper.+ --+ -- "Penny.Cabin" - Penny reports. Depends on Lincoln and Liberty.+ --+ -- "Penny.Copper" - the Penny parser. Depends on Lincoln.+ --+ -- "Penny.Liberty" - Penny command line parser helpers. Depends on+ -- Lincoln and Copper.+ --+ -- "Penny.Lincoln" - the Penny core. Depends on no other Penny+ -- components.+ --+ -- "Penny.Shield" the Penny runtime environment. Depends on+ -- Lincoln.+ --+ -- "Penny.Zinc" - the Penny command-line interface. Depends on+ -- Cabin, Copper, Liberty, and Lincoln.+ --+ -- The dependencies are represented as a dot file in+ -- bin/doc/dependencies.dot in the Penny git repository. ) where -import qualified Penny.Cabin as Cabin-import qualified Penny.Copper as Copper+import qualified Data.Text as X+import qualified Penny.Cabin.Balance.Convert as Conv+import qualified Penny.Cabin.Balance.Convert.Parser as CP+import qualified Penny.Cabin.Balance.Convert.Options as ConvOpts+import qualified Penny.Cabin.Balance.MultiCommodity as MC+import qualified Penny.Cabin.Balance.MultiCommodity.Parser as MP+import Penny.Cabin.Chunk+import qualified Penny.Cabin.Chunk.Switch as Sw+import qualified Penny.Cabin.Interface as I+import qualified Penny.Cabin.Options as CO+import qualified Penny.Cabin.Parsers as CabP+import qualified Penny.Cabin.Posts as Ps+import qualified Penny.Cabin.Posts.Fields as PF+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.Lincoln as L-import qualified Penny.Shield as S import qualified Penny.Zinc as Z+import qualified Penny.Shield as S+import qualified Text.Matchers.Text as Mr --- | Use to make a DefaultTimeZone based on the number of minutes the--- time zone is offset from UTC. Make sure the argument you supply is--- between (-840) and 840; otherwise your program will crash at--- runtime.-minsToDefaultTimeZone :: Int -> Copper.DefaultTimeZone-minsToDefaultTimeZone i = case L.minsToOffset i of- Nothing -> error $ "penny: error: minutes out of range of "- ++ "allowed values for time zone"- Just tzo -> Copper.DefaultTimeZone tzo+-- | 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.+data Defaults = Defaults+ { caseSensitive :: Bool+ -- ^ Whether the matcher is case sensitive by default -defaultPenny :: Copper.DefaultTimeZone -> Copper.RadGroup -> IO ()-defaultPenny dtz rg = do- rt <- S.runtime- let df = Z.defaultFromRuntime dtz rg- rs = Cabin.allReportsWithDefaults dtz rg- Z.runPenny rt dtz rg df rs+ , matcher :: Z.Matcher+ -- ^ Which matcher to use -customPenny ::- Copper.DefaultTimeZone- -> Copper.RadGroup- -> (S.Runtime -> Z.T)- -> [Cabin.Report]+ , colorToFile :: Bool+ -- ^ Use colors when standard output is not a terminal?++ , defaultScheme :: Maybe E.Scheme+ -- ^ Default color scheme. If Nothing, there is no default color+ -- scheme. If there is no default color scheme and the user does+ -- not pick one on the command line, no colors will be used.++ , additionalSchemes :: [E.Scheme]+ -- ^ Additional color schemes the user can pick from on the+ -- command line.++ , sorter :: [(Z.SortField, CabP.SortOrder)]+ -- ^ Postings are sorted in this order by default. For example, if+ -- the first pair is (Date, Ascending), then postings are first+ -- sorted by date in ascending order. If the second pair is+ -- (Payee, Ascending), then postings with the same date are then+ -- sorted by payee.+ --+ -- 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.++ , balanceShowZeroBalances :: Bool+ -- ^ Show zero balances in the balance report? If True, show them;+ -- if False, hide them.++ , balanceOrder :: CabP.SortOrder+ -- ^ Whether to sort the accounts in ascending or descending order+ -- by account name in the balance report.++ , convertShowZeroBalances :: Bool+ -- ^ Show zero balances in the convert report? If True, show them;+ -- if False, hide them.++ , convertTarget :: Target+ -- ^ The commodity to which to convert the commodities in the+ -- convert report.++ , convertOrder :: CabP.SortOrder+ -- ^ Sort the convert report in ascending or descending order.++ , 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.++ , postingsWidth :: Int+ -- ^ The postings report is roughly this wide by+ -- default. Typically this will be as wide as your terminal.++ , postingsShowZeroBalances :: Bool+ -- ^ Show zero balances in the postings report? If True, show+ -- them; if False, hide them.++ , postingsDateFormat :: M.Box -> X.Text+ -- ^ How to format dates in the postings report.++ , postingsQtyFormat :: M.Box -> 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+ -- width (see postingsWidth). Account names are only shortened as+ -- much as is necessary for them to fit; however, each sub-account+ -- name will not be shortened any more than the amount given here.++ , postingsPayeeAllocation :: Int+ -- ^ postingsPayeeAllocation and postingsAccountAllocation+ -- determine how much space is allotted to the payee and account+ -- fields in the postings report. These fields are variable+ -- width. After space for most other fields is allotted, space is+ -- allotted for these two fields. The two fields divide the space+ -- proportionally depending on postingsPayeeAllocation and+ -- postingsAccountAllocation. For example, if+ -- postingsPayeeAllocation is 60 and postingsAccountAllocation is+ -- 40, then the payee field gets 60 percent of the leftover space+ -- and the account field gets 40 percent of the leftover space.+ --+ -- Both postingsPayeeAllocation and postingsAccountAllocation+ -- must be positive integers; if either one is less than 1, your+ -- program will crash at runtime.++ , postingsAccountAllocation :: Int+ -- ^ See postingsPayeeAllocation above for an explanation++ , postingsSpacers :: Spacers Int+ -- ^ Determines the number of spaces that appears to the right of+ -- each named field; for example, sPayee indicates how many spaces+ -- will appear to the right of the payee field. Each field of the+ -- Spacers should be a non-negative integer (although currently+ -- the absolute value of the field is taken.)+ }++-- | Creates an IO action that you can use for the main function.+runPenny+ :: (S.Runtime -> Defaults)+ -- ^ runPenny will apply this function to the Runtime. This way+ -- the defaults you use can vary depending on environment+ -- variables, the terminal type, the date, etc. -> IO ()-customPenny dtz rg gd rs = do+runPenny getDefaults = do rt <- S.runtime- Z.runPenny rt dtz rg gd rs+ let df = getDefaults rt+ rs = allReports df+ Z.runZinc (toZincDefaults df) rt rs++-- | The commodity to which to convert the commodities in the convert+-- report.+data Target+ = AutoTarget+ -- ^ Selects a target commodity automatically, based on which+ -- commodity is the most common target commodity in the prices in+ -- your ledger files. If there is a tie for most common target+ -- commodity, the target that appears later in your ledger files+ -- is used.+ | ManualTarget String+ -- ^ Always uses the commodity named by the string given.+ deriving Show++-- | Gets the current screen width from the runtime. If the COLUMNS+-- environment variable is not set, uses 80.+widthFromRuntime :: S.Runtime -> Int+widthFromRuntime rt = case S.screenWidth rt of+ Nothing -> 80+ Just sw -> S.unScreenWidth sw++convTarget :: Target -> CP.Target+convTarget t = case t of+ AutoTarget -> CP.AutoTarget+ ManualTarget s -> CP.ManualTarget . L.To . L.Commodity . X.pack $ s++allReports+ :: Defaults+ -> [I.Report]+allReports df =+ let bd = toBalanceDefaults df+ cd = toConvertDefaults df+ pd = toPostingsDefaults df+ in [ Ps.zincReport pd+ , MC.parseReport (balanceFormat df) bd+ , Conv.cmdLineReport cd+ ]++toZincDefaults :: Defaults -> Z.Defaults+toZincDefaults d = Z.Defaults+ { Z.sensitive =+ if caseSensitive d then Mr.Sensitive else Mr.Insensitive+ , Z.matcher = matcher d+ , Z.colorToFile = Z.ColorToFile . colorToFile $ d+ , Z.defaultScheme = defaultScheme d+ , Z.moreSchemes = additionalSchemes d+ , Z.sorter = sorter d+ }++toBalanceDefaults :: Defaults -> MP.ParseOpts+toBalanceDefaults d = MP.ParseOpts+ { MP.showZeroBalances =+ CO.ShowZeroBalances . balanceShowZeroBalances $ d+ , MP.order = balanceOrder d+ , MP.needsHelp = False+ }++toConvertDefaults :: Defaults -> ConvOpts.DefaultOpts+toConvertDefaults d = ConvOpts.DefaultOpts+ { ConvOpts.showZeroBalances =+ CO.ShowZeroBalances . convertShowZeroBalances $ d+ , ConvOpts.target = convTarget . convertTarget $ d+ , ConvOpts.sortOrder = convertOrder d+ , ConvOpts.sortBy = convertSortBy d+ , ConvOpts.format = convertFormat d+ }++toPostingsDefaults :: Defaults -> Ps.ZincOpts+toPostingsDefaults d = Ps.ZincOpts+ { Ps.fields = convFields . postingsFields $ d+ , Ps.width = Ps.ReportWidth . postingsWidth $ d+ , 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 =+ Ps.alloc . postingsPayeeAllocation $ d+ , Ps.accountAllocation =+ Ps.alloc . postingsAccountAllocation $ d+ , Ps.spacers = convSpacers . postingsSpacers $ d+ }++defaultQtyFormat :: L.Qty -> X.Text+defaultQtyFormat = X.pack . show++data Spacers a = Spacers+ { sGlobalTransaction :: a+ , sRevGlobalTransaction :: a+ , sGlobalPosting :: a+ , sRevGlobalPosting :: a+ , sFileTransaction :: a+ , sRevFileTransaction :: a+ , sFilePosting :: a+ , sRevFilePosting :: a+ , sFiltered :: a+ , sRevFiltered :: a+ , sSorted :: a+ , sRevSorted :: a+ , sVisible :: a+ , sRevVisible :: a+ , sLineNum :: a+ , sDate :: a+ , sFlag :: a+ , sNumber :: a+ , sPayee :: a+ , sAccount :: a+ , sPostingDrCr :: a+ , sPostingCmdty :: a+ , sPostingQty :: a+ , sTotalDrCr :: a+ , sTotalCmdty :: a+ } deriving (Show, Eq)++data Fields a = Fields+ { fGlobalTransaction :: a+ , fRevGlobalTransaction :: a+ , fGlobalPosting :: a+ , fRevGlobalPosting :: a+ , fFileTransaction :: a+ , fRevFileTransaction :: a+ , fFilePosting :: a+ , fRevFilePosting :: a+ , fFiltered :: a+ , fRevFiltered :: a+ , fSorted :: a+ , fRevSorted :: a+ , fVisible :: a+ , fRevVisible :: a+ , fLineNum :: a+ , fDate :: a+ , fFlag :: a+ , fNumber :: a+ , fPayee :: a+ , fAccount :: a+ , fPostingDrCr :: a+ , fPostingCmdty :: a+ , fPostingQty :: a+ , fTotalDrCr :: a+ , fTotalCmdty :: a+ , fTotalQty :: a+ , fTags :: a+ , fMemo :: a+ , fFilename :: a+ } deriving (Show, Eq)++convSpacers :: Spacers a -> PS.Spacers a+convSpacers s = PS.Spacers+ { PS.globalTransaction = sGlobalTransaction s+ , PS.revGlobalTransaction = sRevGlobalTransaction s+ , PS.globalPosting = sGlobalPosting s+ , PS.revGlobalPosting = sRevGlobalPosting s+ , PS.fileTransaction = sFileTransaction s+ , PS.revFileTransaction = sRevFileTransaction s+ , PS.filePosting = sFilePosting s+ , PS.revFilePosting = sRevFilePosting s+ , PS.filtered = sFiltered s+ , PS.revFiltered = sRevFiltered s+ , PS.sorted = sSorted s+ , PS.revSorted = sRevSorted s+ , PS.visible = sVisible s+ , PS.revVisible = sRevVisible s+ , PS.lineNum = sLineNum s+ , PS.date = sDate s+ , PS.flag = sFlag s+ , PS.number = sNumber s+ , PS.payee = sPayee s+ , PS.account = sAccount s+ , PS.postingDrCr = sPostingDrCr s+ , PS.postingCmdty = sPostingCmdty s+ , PS.postingQty = sPostingQty s+ , PS.totalDrCr = sTotalDrCr s+ , PS.totalCmdty = sTotalCmdty s+ }++convFields :: Fields a -> PF.Fields a+convFields f = PF.Fields+ { PF.globalTransaction = fGlobalTransaction f+ , PF.revGlobalTransaction = fRevGlobalTransaction f+ , PF.globalPosting = fGlobalPosting f+ , PF.revGlobalPosting = fRevGlobalPosting f+ , PF.fileTransaction = fFileTransaction f+ , PF.revFileTransaction = fRevFileTransaction f+ , PF.filePosting = fFilePosting f+ , PF.revFilePosting = fRevFilePosting f+ , PF.filtered = fFiltered f+ , PF.revFiltered = fRevFiltered f+ , PF.sorted = fSorted f+ , PF.revSorted = fRevSorted f+ , PF.visible = fVisible f+ , PF.revVisible = fRevVisible f+ , PF.lineNum = fLineNum f+ , PF.date = fDate f+ , PF.flag = fFlag f+ , PF.number = fNumber f+ , PF.payee = fPayee f+ , PF.account = fAccount f+ , PF.postingDrCr = fPostingDrCr f+ , PF.postingCmdty = fPostingCmdty f+ , PF.postingQty = fPostingQty f+ , PF.totalDrCr = fTotalDrCr f+ , PF.totalCmdty = fTotalCmdty f+ , PF.totalQty = fTotalQty f+ , PF.tags = fTags f+ , PF.memo = fMemo f+ , PF.filename = fFilename f+ }+
+ Penny/Brenner.hs view
@@ -0,0 +1,276 @@+-- | Brenner - Penny financial institution interfaces+--+-- Brenner provides a uniform way to interact with downloaded data+-- from financial Given a parser, Brenner will import the transactions+-- and store them in a database. From there it is easy to merge the+-- transactions (without duplicates) into a ledger file, and then to+-- clear transactions from statements in an automated fashion.+module Penny.Brenner+ ( FitAcct(..)+ , Config(..)+ , R.GroupSpecs(..)+ , R.GroupSpec(..)+ , Y.Translator(..)+ , L.Side(..)+ , L.SpaceBetween(..)+ , brennerMain+ ) where++import qualified Penny.Brenner.Types as Y+import Data.Maybe (mapMaybe)+import qualified Data.Text as X+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+import qualified Penny.Brenner.Merge as M+import qualified Penny.Brenner.Print as P+import Control.Applicative ((<*>))+import qualified System.Console.MultiArg as MA+import qualified Control.Monad.Exception.Synchronous as Ex+import System.Exit (exitFailure)+import qualified System.IO as IO++brennerMain :: Config -> IO ()+brennerMain cf = do+ as <- MA.getArgs+ pr <- MA.getProgName+ let cf' = convertConfig cf+ r = MA.modes globalOpts (preProcessor cf') whatMode as+ processParseResult pr cf' r++data Arg+ = AHelp+ | AFitAcct String+ deriving (Eq, Show)++toFitAcctOpt :: Arg -> Maybe String+toFitAcctOpt a = case a of { AFitAcct s -> Just s; _ -> Nothing }++globalOpts :: [MA.OptSpec Arg]+globalOpts =+ [ MA.OptSpec ["help"] "h" (MA.NoArg AHelp)+ , MA.OptSpec ["fit-account"] "f" (MA.OneArg AFitAcct)+ ]++data PreProc+ = NeedsHelp+ | DoIt (Maybe Y.FitAcct)++preProcessor :: Y.Config -> [Arg] -> Ex.Exceptional String PreProc+preProcessor cf as =+ if any (== AHelp) as+ then return NeedsHelp+ else do+ let cardOpt = case mapMaybe toFitAcctOpt as of+ [] -> Nothing+ xs -> Just . last $ xs+ card <- case cardOpt of+ Nothing -> return $ Y.defaultFitAcct cf+ Just o ->+ let pdct (Y.Name n, _) = n == X.pack o+ in case filter pdct (Y.moreFitAccts cf) of+ [] -> Ex.throw $ "financial institution account "+ ++ o ++ " not configured."+ (_, c):[] -> return $ Just c+ _ -> Ex.throw $ "more than one financial institution account "+ ++ "named " ++ o ++ " configured."+ return $ DoIt card++whatMode+ :: PreProc+ -> Either (String -> String)+ [MA.Mode (Ex.Exceptional String (IO ()))]+whatMode pp = case pp of+ NeedsHelp -> Left id+ DoIt mayCd ->+ Right $ [C.mode, I.mode, M.mode, P.mode, D.mode] <*> [mayCd]+++processParseResult+ :: String+ -- ^ Program name+ -> Y.Config++ -> Ex.Exceptional MA.Error+ (a, Either b (Ex.Exceptional String (IO ())))+ -> IO ()+processParseResult pr cf ex =+ case ex of+ Ex.Exception err -> do+ IO.hPutStr IO.stderr $ MA.formatError pr err+ exitFailure+ Ex.Success g -> processResult pr cf g+++processResult+ :: String+ -- ^ Program name++ -> Y.Config+ -> (a, Either b (Ex.Exceptional String (IO ())))+ -> IO ()+processResult pr cf (_, ei) =+ case ei of+ Left _ -> putStr (help pr cf)+ Right ex -> case ex of+ Ex.Exception e -> do+ putStrLn $ pr ++ ": error: " ++ e+ exitFailure+ Ex.Success g -> g++help ::+ String+ -- ^ Program name++ -> Y.Config+ -> String+help n c = unlines ls ++ cs+ where+ ls = [ "usage: " ++ n ++ " [global-options]"+ ++ " COMMAND [local-options]"+ ++ " ARGS..."+ , ""+ , "where COMMAND is one of:"+ , "import, merge, clear, database, print"+ , ""+ , "For help on an individual command and its"+ ++ " local options, use "+ , n ++ " COMMAND --help"+ , ""+ , "Global Options:"+ , "-f, --fit-account ACCOUNT"+ , " Use one of the Additional Financial Institution"+ , " Accounts shown below. If this option does not appear,"+ , " the default account is used if there is one."+ , "-h, --help"+ , " Show help and exit"+ , ""+ ]+ showPair (Y.Name a, cd) = "Additional financial institution "+ ++ "account: " ++ X.unpack a ++ "\n" ++ showFitAcct cd+ cs = showDefaultFitAcct (Y.defaultFitAcct c)+ ++ more+ more = if null (Y.moreFitAccts c)+ then "No additional financial institution accounts\n"+ else concatMap showPair . Y.moreFitAccts $ c++showDefaultFitAcct :: Maybe Y.FitAcct -> String+showDefaultFitAcct mc = case mc of+ Nothing -> "No default financial institution account\n"+ Just c -> "Default financial institution account:\n" ++ showFitAcct c++label :: String -> String -> String+label l o = " " ++ l ++ ": " ++ o ++ "\n"++showAccount :: L.Account -> String+showAccount =+ X.unpack+ . X.intercalate (X.singleton ':')+ . map L.unSubAccount+ . L.unAccount++showFitAcct :: Y.FitAcct -> String+showFitAcct c =+ label "Database location"+ (X.unpack . Y.unDbLocation . Y.dbLocation $ c)++ ++ label "Penny account"+ (showAccount . Y.unPennyAcct . Y.pennyAcct $ c)++ ++ label "Account for new offsetting postings"+ (showAccount . Y.unDefaultAcct . Y.defaultAcct $ c)++ ++ label "Currency"+ (X.unpack . L.unCommodity . Y.unCurrency . Y.currency $ c)++ ++ "\n"++ ++ "More information about the parser:\n"+ ++ (fst . Y.parser $ c)+ ++ "\n\n"+++-- | Information to configure a single card account.+data FitAcct = FitAcct+ { dbLocation :: String+ -- ^ Path and filename to where the database is kept. You can use+ -- an absolute or relative path (if it is relative, it will be+ -- resolved relative to the current directory at runtime.)++ , pennyAcct :: String+ -- ^ The account that you use in your Penny file to hold+ -- transactions for this card. Separate each sub-account with+ -- colons (as you do in the Penny file.)++ , defaultAcct :: String+ -- ^ When new transactions are created, one of the postings will+ -- be in the amexAcct given above. The other posting will be in+ -- this account.++ , 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.++ , translator :: Y.Translator+ -- ^ See the documentation under the 'Translator' type for+ -- details.++ , side :: L.Side+ -- ^ When creating new transactions, the commodity will be on this+ -- side++ , spaceBetween :: L.SpaceBetween+ -- ^ When creating new transactions, is there a space between the+ -- commodity and the quantity++ , parser :: ( String+ , Y.FitFileLocation -> IO (Ex.Exceptional String [Y.Posting]))+ -- ^ Parses a file of transactions from the financial+ -- institution. The function must open the file and parse it. This+ -- is in the IO monad not only because the function must open the+ -- file itself, but also so the function can perform arbitrary IO+ -- (run pdftotext, maybe?) If there is failure, the function can+ -- return an Exceptional String, which is the error+ -- message. Alternatively the function can raise an exception in the+ -- IO monad (currently Brenner makes no attempt to catch these) so+ -- if any of the IO functions throw you can simply not handle the+ -- exceptions.+ --+ -- The first element of the pair is a help string which should+ -- indicate how to download the data, as a helpful reminder.+++ } deriving Show++convertFitAcct :: FitAcct -> Y.FitAcct+convertFitAcct (FitAcct db ax df cy gs tl sd sb ps) = Y.FitAcct+ { Y.dbLocation = Y.DbLocation . X.pack $ db+ , Y.pennyAcct = Y.PennyAcct . Bd.account $ ax+ , Y.defaultAcct = Y.DefaultAcct . Bd.account $ df+ , Y.currency = Y.Currency . L.Commodity . X.pack $ cy+ , Y.groupSpecs = gs+ , Y.translator = tl+ , Y.side = sd+ , Y.spaceBetween = sb+ , Y.parser = ps+ }++data Config = Config+ { defaultFitAcct :: Maybe FitAcct+ , moreFitAccts :: [(String, FitAcct)]+ } deriving Show++convertConfig :: Config -> Y.Config+convertConfig (Config d m) = Y.Config+ { Y.defaultFitAcct = fmap convertFitAcct d+ , Y.moreFitAccts =+ let f (n, c) = (Y.Name (X.pack n), convertFitAcct c)+ in map f m+ }
+ Penny/Brenner/Amex.hs view
@@ -0,0 +1,121 @@+-- | Parses Amex credit card data. See the help text in the 'help'+-- function for more details. Also, the file format is documented in+-- the file @doc\/amex-file-format.org@.+module Penny.Brenner.Amex (parser) where++import Control.Applicative ((<$>), (<*>), (<$), (<*), (*>), pure,+ (<|>), optional)+import qualified Data.Time as Time+import qualified Penny.Brenner.Types as Y+import Text.Parsec.Text (Parser)+import qualified Text.Parsec as P+import Text.Parsec (many1, char, many, satisfy)+import qualified Data.Text as X+import qualified Data.Text.IO as TIO+import qualified Control.Monad.Exception.Synchronous as Ex++parser :: (String, Y.FitFileLocation+ -> IO (Ex.Exceptional String [Y.Posting]))+parser = (help, loadIncoming)++help :: String+help = unlines+ [ "Parses American Express transaction data in CSV format."+ , "Not tested with American Express deposit accounts."+ , "To download, click the \"Download\" link which is visible"+ , "above and to the right of the transaction list. Under"+ , "\"Download Current View\" Select \"CSV\""+ , "and be sure to click"+ , "\"Include all additional Transaction Details.\""+ ]++-- | Loads incoming Amex transactions.+loadIncoming :: Y.FitFileLocation+ -> IO (Ex.Exceptional String [Y.Posting])+loadIncoming (Y.FitFileLocation loc) = do+ txt <- TIO.readFile loc+ let parsed = P.parse (P.many posting <* P.eof) "" txt+ err s = "could not parse incoming postings: " ++ show s+ return (Ex.mapException err . Ex.fromEither $ parsed)++skipThrough :: Char -> Parser ()+skipThrough c = () <$ many (satisfy (/= c)) <* char c++readThrough :: Char -> Parser String+readThrough c = many (satisfy (/= c)) <* char c++date :: Parser Y.Date+date = p >>= failOnErr+ where+ p = (,,)+ <$> fmap read (many1 P.digit)+ <* char '/'+ <*> fmap read (many1 P.digit)+ <* char '/'+ <*> fmap read (many1 P.digit)+ <* skipThrough ','+ failOnErr (m, d, y) = maybe (fail "could not parse date")+ (return . Y.Date)+ $ Time.fromGregorianValid y m d++incDecAmount :: Parser (Y.IncDec, Y.Amount)+incDecAmount = do+ incDec <- (Y.Decrease <$ char '-') <|> pure Y.Increase+ amtStr <- readThrough ','+ case Y.mkAmount amtStr of+ Nothing -> fail $ "could not parse amount: " ++ amtStr+ Just a -> return (incDec, a)++doubleQuoted :: Parser String+doubleQuoted = char '"' *> readThrough '"'++desc :: Parser Y.Desc+desc = fmap (Y.Desc . X.pack) $ doubleQuoted <* char ','++payee :: Parser Y.Payee+payee = fmap (Y.Payee . X.pack) $ doubleQuoted <* char ','++amexId :: Parser Y.FitId+amexId = fmap (Y.FitId . X.pack)+ $ char '"' *> char '\'' *> readThrough '\''+ <* char '"' <* char ','+++-- | Skips a field. Will skip a quoted field or,+-- alternatively, skip everything through to the next comma. Do not+-- use for the last field, as it looks for a trailing comma.+skipField :: Parser ()+skipField =+ ()+ <$ skipper+ <* char ','+ where+ skipper = (() <$ optional doubleQuoted)+ <|> (() <$ many (satisfy (/= ',')))+++-- | Parses last field (currently unknown). Parsers the EOL character.+skipLast :: Parser ()+skipLast = skipThrough '\n'++posting :: Parser Y.Posting+posting =+ f+ <$> date -- 1+ <* skipField -- 2 Unknown+ <*> desc -- 3 Description+ <* skipField -- 4 Unknown+ <* skipField -- 5 Unknown+ <* skipField -- 6 Unknown+ <* skipField -- 7 Unknown+ <*> incDecAmount -- 8+ <* skipField -- 9 Unknown+ <* skipField -- 10 Category+ <*> payee -- 11 D.B.A.+ <* skipField -- 12 Address+ <* skipField -- 13 Postcode+ <*> amexId -- 14+ <* skipField -- 15 Unknown+ <* skipLast -- 16+ where+ f dt ds (t, a) p i = Y.Posting dt ds t a p i
+ Penny/Brenner/BofA.hs view
@@ -0,0 +1,212 @@+-- | Parses statements for Bank of America deposit accounts. See the+-- help text in the 'help' function for more details. Also, the file+-- format is documented in the file @doc\/bofa-file-format.org@.+module Penny.Brenner.BofA (parser) where++import Control.Applicative ((<$>), (<*), (<$), (<*>))+import qualified Control.Monad.Exception.Synchronous as Ex+import Data.Char (isUpper)+import qualified Data.Time as T+import qualified Text.Parsec as P+import Text.Parsec (char, string, many, many1, satisfy, manyTill,+ (<?>), try)+import Text.Parsec.String (Parser)+import qualified Data.Tree as T+import Data.Tree (Tree(Node))+import qualified Penny.Brenner.Types as Y+import qualified Data.Text as X++newtype TagName = TagName { unTagName :: String }+ deriving (Eq, Show)++newtype TagData = TagData { unTagData :: String }+ deriving (Eq, Show)++data Label+ = Parent TagName+ | Terminal TagName TagData+ deriving (Eq, Show)++type ExS = Ex.Exceptional String++bOfAFile :: Parser ([(TagName, TagData)], Tree Label)+bOfAFile =+ (,)+ <$> many headerLine+ <* string "\r\n"+ <*> node++notReturn :: Parser Char+notReturn = satisfy (/= '\r')++headerLine :: Parser (TagName, TagData)+headerLine =+ (,)+ <$> (TagName <$> manyTill (satisfy isUpper) (char ':'))+ <*> (TagData <$> manyTill notReturn (char '\r')+ <* char '\n')++openTag :: Parser String+openTag = do+ { let pc = (satisfy (\c -> c /= '/' && c /= '>'))+ ; c <- try (char '<' >> pc)+ ; cs <- many pc+ ; _ <- char '>'+ ; return (c:cs)+ } <?> "open tag"++closeTag :: String -> Parser ()+closeTag s = () <$ string "</" <* string s <* char '>'+ <?> "close tag named " ++ s++-- | Reads in a tag, then examine what's next. If a backslash-r is+-- next, then this is the end of the line. That means it a nested+-- tag. Parse some more child nodes, then parse a closing node. If+-- anything else is next, this is a data node. Parse the data, then+-- return that node.+node :: Parser (Tree Label)+node = do+ tagName <- openTag+ next <- P.anyChar+ case next of+ '\r' -> do+ _ <- char '\n'+ kids <- many1 node+ closeTag tagName+ _ <- string "\r\n"+ return $ T.Node (Parent (TagName tagName)) kids+ o -> do+ rs <- manyTill notReturn (char '\r')+ _ <- char '\n'+ return $+ T.Node (Terminal (TagName tagName) (TagData $ o:rs)) []++findNodes :: Eq a => a -> Tree a -> [Tree a]+findNodes a = findNodesBy (== a)+++findNodesBy :: (a -> Bool) -> Tree a -> [Tree a]+findNodesBy f t@(Node l cs)+ | f l = [t]+ | otherwise = concatMap (findNodesBy f) cs++safeRead :: (Read r) => String -> Maybe r+safeRead s = case reads s of+ (i,""):[] -> Just i+ _ -> Nothing++-- | Parses a B of A date-time. The format is YYYYMMDDHHMMSS. Discards+-- the HHMMSS.+parseDateStr :: String -> ExS Y.Date+parseDateStr s =+ let (yr, r1) = splitAt 4 s+ (mo, r2) = splitAt 2 r1+ (da, _) = splitAt 2 r2+ in Ex.fromMaybe ("could not parse date: " ++ s) $ do+ yi <- safeRead yr+ ym <- safeRead mo+ yd <- safeRead da+ Y.Date <$> T.fromGregorianValid yi ym yd++parseAmountStr :: String -> ExS (Y.IncDec, Y.Amount)+parseAmountStr s = do+ (f, rs) <- case s of+ "" -> Ex.throw "empty string for amount"+ x:xs -> return (x, xs)+ let (amtStr, incDec) = case f of+ '-' -> (rs, Y.Decrease)+ _ -> (s, Y.Increase)+ amt <- Ex.fromMaybe ("could not parse amount: " ++ s)+ $ Y.mkAmount amtStr+ return (incDec, amt)++postings :: Tree Label -> ExS [Y.Posting]+postings t =+ let match = Parent (TagName "STMTTRN")+ in mapM posting .findNodes match $ t++posting :: Tree Label -> ExS Y.Posting+posting (Node l cs) = do+ tag <- case l of+ Parent n -> return n+ _ -> Ex.throw "did not find posting tree"+ Ex.assert "did not find STMTTRN tag" $ unTagName tag == "STMTTRN"+ tPosted <- findTerminal "DTPOSTED" cs+ tAmt <- findTerminal "TRNAMT" cs+ tId <- findTerminal "FITID" cs+ tName <- findTerminal "NAME" cs+ pPosted <- parseDateStr (X.unpack tPosted)+ (amtIncDec, pAmt) <- parseAmountStr (X.unpack tAmt)+ let pId = Y.FitId tId+ pName = Y.Desc tName+ pPayee = Y.Payee (X.empty)+ return $ Y.Posting pPosted pName amtIncDec pAmt pPayee pId++-- | Removes the TagData from a tree, after ensuring that the TagName+-- is correct and that the tree has no children.+terminalData+ :: String+ -- ^ The name of the terminal++ -> Tree Label++ -> ExS X.Text+ -- ^ Returns the data from the tag, or an error if this is not a+ -- terminal or if the terminal has children.+terminalData n (Node l cs) = do+ (tn, td) <- case l of+ Parent _ -> Ex.throw $ "looking for data tag named " ++ n+ ++ ", but that tag does not have data"+ Terminal x y -> return (x, y)+ let tagErr = "looking for tag named " ++ n+ ++ ", but found tag named " ++ unTagName tn+ Ex.assert tagErr $ tn == TagName n+ let kidsErr = "data tag " ++ n ++ " should have no children,"+ ++ " but does"+ Ex.assert kidsErr $ null cs+ return . X.pack . unTagData $ td++-- | Finds a terminal amongst a list of Trees; returns the data. Fails+-- if there is no terminal by the given name or of the terminal has+-- children (in which case it is not a terminal!)+findTerminal+ :: String+ -- ^ The name of the terminal++ -> [Tree Label]+ -> ExS X.Text+ -- ^ Returns the data from the terminal, or an error if there is no+ -- tag by this name or if it has chlidren.++findTerminal n ts = do+ let pdct lbl = case lbl of+ Terminal (TagName x) _ -> x == n+ _ -> False+ t <- case concatMap (findNodesBy pdct) ts of+ [] -> Ex.throw $ "looking for terminal named "+ ++ n ++ "; none found"+ x:[] -> return x+ _ -> Ex.throw $ "looking for terminal named "+ ++ n ++ "; multiple matches found"+ terminalData n t++help :: String+help = unlines+ [ "Parses Bank of America postings for deposit accounts, like checking"+ , "or savings. This parser is not tested with credit card accounts."+ , "To download the data, from the account activity screen click on"+ , "\"Download\", which is just above all the transaction information."+ , "Then download the \"WEB Connect for Quicken 2010 and above.\""+ ]++parser :: (String, Y.FitFileLocation+ -> IO (Ex.Exceptional String [Y.Posting]))+parser = (help, psr)+ where+ psr (Y.FitFileLocation path) = do+ str <- readFile path+ return $ case P.parse bOfAFile "" str of+ Left e -> Ex.throw+ $ "could not parse Bank of America transactions: "+ ++ show e+ Right (_, t) -> postings t
+ Penny/Brenner/Clear.hs view
@@ -0,0 +1,186 @@+module Penny.Brenner.Clear (mode) where++import qualified Control.Monad.Exception.Synchronous as Ex+import Control.Applicative (pure)+import Control.Monad (guard, mzero, when)+import Data.Maybe (mapMaybe, fromMaybe)+import Data.Monoid (mconcat, First(..))+import qualified Data.Set as Set+import qualified Data.Map as M+import qualified Data.Text as X+import qualified Data.Text.IO as TIO+import qualified System.Console.MultiArg as MA+import qualified Penny.Lincoln as L+import qualified Control.Monad.Trans.State as St+import qualified Control.Monad.Trans.Maybe as MT+import Control.Monad.Trans.Class (lift)+import qualified Penny.Copper.Types as Y+import qualified Penny.Copper as C+import qualified Penny.Copper.Render as R+import System.Exit (exitFailure)+import qualified System.IO as IO+import Text.Show.Pretty (ppShow)+import qualified Penny.Brenner.Types as Y+import qualified Penny.Brenner.Util as U+++help :: String+help = unlines+ [ "usage: penny-fit clear clear [options] FIT_FILE LEDGER_FILE..."+ , "Parses all postings that are in FIT_FILE. Then marks all"+ , "postings that are in the FILEs given that correspond to one"+ , "of the postings in the FIT_FILE as being cleared."+ , "Quits if one of the postings found in FIT_FILE is not found"+ , "in the database, if one of the postings in the database"+ , "is not found in one of the FILEs, or if any of the postings found"+ , "in one of the FILEs already has a flag."+ , ""+ , "Results are printed to standard output. If no FILE, or FILE is \"-\","+ , "read standard input."+ , ""+ , "Options:"+ , " -h, --help - show help and exit"+ ]++data Arg+ = AHelp+ | APosArg String+ deriving (Eq, Show)++toPosArg :: Arg -> Maybe String+toPosArg a = case a of { APosArg s -> Just s; _ -> Nothing }++data Opts = Opts+ { csvLocation :: Y.FitFileLocation+ , ledgerLocations :: [String]+ } deriving Show+++mode :: Maybe Y.FitAcct -> MA.Mode (Ex.Exceptional String (IO ()))+mode c = MA.Mode+ { MA.mName = "clear"+ , MA.mIntersperse = MA.Intersperse+ , MA.mOpts = [ MA.OptSpec ["help"] "h" (MA.NoArg AHelp) ]+ , MA.mPosArgs = APosArg+ , MA.mProcess = process c+ }++process :: Maybe Y.FitAcct -> [Arg] -> Ex.Exceptional String (IO ())+process mayC as =+ if any (== AHelp) as+ then return $ putStrLn help+ else do+ c <- case mayC of+ Just cd -> return cd+ Nothing -> Ex.throw $ "no financial institution account given"+ ++ " on command line, and no default financial"+ ++ " institution configured."+ (csv, ls) <- case mapMaybe toPosArg as of+ [] -> Ex.throw $ "clear: you must provide a postings file."+ x:xs -> return (Y.FitFileLocation x, xs)+ let os = Opts csv ls+ return $ runClear c os++fatal :: String -> IO a+fatal s = do+ IO.hPutStrLn IO.stderr $ "penny-fit clear: error: " ++ s+ exitFailure++runClear :: Y.FitAcct -> Opts -> IO ()+runClear c os = do+ dbList <- U.quitOnError+ $ U.loadDb (Y.AllowNew False) (Y.dbLocation c)+ let db = M.fromList dbList+ (_, prsr) = Y.parser c+ txns <- U.quitOnError $ prsr (csvLocation os)+ leds <- C.openStdin (ledgerLocations os)+ toClear <- case mapM (findUNumber db) txns of+ Nothing -> fatal $ "at least one posting was not found in the"+ ++ " database. Ensure all postings have "+ ++ "been imported and merged."+ Just ls -> return $ Set.fromList ls+ let (led', left) = changeLedger (Y.pennyAcct c) toClear leds+ when (not (Set.null left))+ (fatal $ "some postings were not cleared. "+ ++ "Those not cleared:\n" ++ ppShow left)+ case R.ledger (Y.groupSpecs c) led' of+ Nothing ->+ fatal "could not render resulting ledger."+ Just txt -> TIO.putStr txt+++-- | Examines an financial institution transaction and the DbMap to+-- find a matching UNumber. Fails if the financial institution+-- transaction is not in the Db.+findUNumber :: Y.DbMap -> Y.Posting -> Maybe Y.UNumber+findUNumber m pstg =+ let atn = Y.fitId pstg+ p ap = Y.fitId ap == atn+ filteredMap = M.filter p m+ ls = M.toList filteredMap+ in case ls of+ (n, _):[] -> Just n+ _ -> Nothing+++clearedFlag :: L.Flag+clearedFlag = L.Flag . X.singleton $ 'C'++-- | Changes a ledger to clear postings. Returns postings still not+-- cleared.+changeLedger+ :: Y.PennyAcct+ -> Set.Set Y.UNumber+ -> Y.Ledger+ -> (Y.Ledger, Set.Set Y.UNumber)+changeLedger ax s l = St.runState k s+ where+ k = Y.mapLedgerA f l+ f = Y.mapItemA pure pure (changeTxn ax)++changeTxn+ :: Y.PennyAcct+ -> L.Transaction+ -> St.State (Set.Set Y.UNumber) L.Transaction+changeTxn ax t = do+ let fam = L.unTransaction t+ fam' = L.mapParent (const L.emptyTopLineChangeData) fam+ fam'' <- L.mapChildrenA (changePstg ax) fam'+ return $ L.changeTransaction fam'' t+++-- | Sees if this posting is a posting in the right account and has a+-- UNumber that needs to be cleared. If so, clears it. If this posting+-- already has a flag, skips it.+changePstg+ :: Y.PennyAcct+ -> L.Posting+ -> St.State (Set.Set Y.UNumber) L.PostingChangeData+changePstg ax p =+ fmap (fromMaybe L.emptyPostingChangeData) . MT.runMaybeT $ do+ guard (L.pAccount p == (Y.unPennyAcct ax))+ let tags = L.pTags p+ un <- maybe mzero return $ parseUNumberFromTags tags+ guard (L.pFlag p == Nothing)+ set <- lift St.get+ guard (Set.member un set)+ lift $ St.put (Set.delete un set)+ return $ L.emptyPostingChangeData+ { L.pcFlag = Just (Just clearedFlag) }++parseUNumberFromTags :: L.Tags -> Maybe Y.UNumber+parseUNumberFromTags =+ getFirst+ . mconcat+ . map First+ . map parseUNumberFromTag+ . L.unTags++parseUNumberFromTag :: L.Tag -> Maybe Y.UNumber+parseUNumberFromTag (L.Tag x) = do+ (f, xs) <- X.uncons x+ guard (f == 'U')+ case reads . X.unpack $ xs of+ (u, ""):[] -> Just (Y.UNumber u)+ _ -> Nothing+
+ Penny/Brenner/Database.hs view
@@ -0,0 +1,51 @@+module Penny.Brenner.Database (mode) where++import qualified Penny.Brenner.Types as Y+import qualified Penny.Brenner.Util as U+import qualified System.Console.MultiArg as MA+import qualified Control.Monad.Exception.Synchronous as Ex++help :: String+help = unlines+ [ "penny-fit [global-options] database [local-options]"+ , "Shows the database of financial institution transactions."+ , "Does not accept any non-option arguments."+ , ""+ , "Local options:"+ , " --help, -h Show this help and exit."+ ]++data Arg = ArgHelp | ArgPos String deriving (Eq, Show)++mode+ :: Maybe Y.FitAcct+ -> MA.Mode (Ex.Exceptional String (IO ()))+mode mayFa = MA.Mode+ { MA.mName = "database"+ , MA.mIntersperse = MA.Intersperse+ , MA.mOpts = [MA.OptSpec ["help"] "h" (MA.NoArg ArgHelp)]+ , MA.mPosArgs = ArgPos+ , MA.mProcess = processor mayFa+ }++processor+ :: Maybe Y.FitAcct+ -> [Arg]+ -> Ex.Exceptional String (IO ())+processor mayFa ls+ | any (== ArgHelp) ls = return (putStrLn help)+ | any isArgPos ls = Ex.throw $+ "penny-fit database: error: this command does"+ ++ " not accept non-option arguments."+ | otherwise = case mayFa of+ Nothing -> Ex.throw $ "no financial institution account"+ ++ " selected on command line, and no default"+ ++ " financial instititution account configured."+ Just fa -> return $ do+ let dbLoc = Y.dbLocation fa+ db <- U.quitOnError $ U.loadDb (Y.AllowNew False) dbLoc+ mapM_ putStr . map U.showDbPair $ db++isArgPos :: Arg -> Bool+isArgPos (ArgPos _) = True+isArgPos _ = False
+ Penny/Brenner/Import.hs view
@@ -0,0 +1,134 @@+module Penny.Brenner.Import (mode) where++import Control.Monad.Exception.Synchronous as Ex+import Data.Maybe (mapMaybe)+import qualified System.Console.MultiArg as MA+import qualified Penny.Brenner.Types as Y+import qualified Penny.Brenner.Util as U+import qualified System.IO as IO+import qualified System.Exit as E+++data Arg+ = AHelp+ | AFitFile String+ | AAllowNew+ deriving (Eq, Show)++toFitFile :: Arg -> Maybe String+toFitFile a = case a of+ AFitFile s -> Just s+ _ -> Nothing++data ImportOpts = ImportOpts+ { fitFile :: Y.FitFileLocation+ , allowNew :: Y.AllowNew+ , parser :: Y.FitFileLocation+ -> IO (Ex.Exceptional String [Y.Posting])+ }++{-+mode+ :: Y.DbLocation+ -> ( Y.FitFileLocation+ -> IO (Ex.Exceptional String [Y.Posting]))+ -> MA.Mode (Ex.Exceptional String (IO ()))+mode dbLoc prsr = MA.Mode+ { MA.mName = "import"+ , MA.mIntersperse = MA.Intersperse+ , MA.mOpts =+ [ MA.OptSpec ["help"] "h" (MA.NoArg AHelp)+ , MA.OptSpec ["new"] "n" (MA.NoArg AAllowNew)+ ]+ , MA.mPosArgs = AFitFile+ , MA.mProcess = processor dbLoc prsr+ }+-}++mode+ :: Maybe Y.FitAcct+ -> MA.Mode (Ex.Exceptional String (IO ()))+mode mayFa = MA.Mode+ { MA.mName = "import"+ , MA.mIntersperse = MA.Intersperse+ , MA.mOpts =+ [ MA.OptSpec ["help"] "h" (MA.NoArg AHelp)+ , MA.OptSpec ["new"] "n" (MA.NoArg AAllowNew)+ ]+ , MA.mPosArgs = AFitFile+ , MA.mProcess = processor mayFa+ }++processor+ :: Maybe Y.FitAcct+ -> [Arg]+ -> Ex.Exceptional String (IO ())+processor mayFa as = do+ let err s = Ex.throw $ "import: " ++ s+ if any (== AHelp) as+ then return $ putStrLn help+ else do+ (dbLoc, prsr) <- case mayFa of+ Nothing -> err $ "no financial institution account provided"+ ++ " on command line, and no default financial institution"+ ++ " account is configured."+ Just fa -> return (Y.dbLocation fa, snd . Y.parser $ fa)+ loc <- case mapMaybe toFitFile as of+ [] -> err "you must provide a postings file to read"+ x:[] -> return (Y.FitFileLocation x)+ _ -> err "you cannot provide more than one postings file to read"+ let aNew = Y.AllowNew $ any (== AAllowNew) as+ return $ doImport dbLoc (ImportOpts loc aNew prsr)+++-- | Appends new Amex transactions to the existing list.+appendNew+ :: [(Y.UNumber, Y.Posting)]+ -- ^ Existing transactions++ -> [Y.Posting]+ -- ^ New transactions++ -> ([(Y.UNumber, Y.Posting)], Int)+ -- ^ New list, and number of transactions added++appendNew db new = (db ++ newWithU, length newWithU)+ where+ nextUNum = if null db+ then 0+ else (Y.unUNumber . maximum . map fst $ db) + 1+ currFitIds = map (Y.fitId . snd) db+ isNew p = not (any (== Y.fitId p) currFitIds)+ newPstgs = filter isNew new+ mkPair i p = (Y.UNumber i, p)+ newWithU = zipWith mkPair [nextUNum..] newPstgs+++doImport :: Y.DbLocation -> ImportOpts -> IO ()+doImport dbLoc os = do+ txnsOld <- U.quitOnError $ U.loadDb (allowNew os) dbLoc+ parseResult <- parser os (fitFile os)+ ins <- case parseResult of+ Ex.Exception e -> do+ IO.hPutStrLn IO.stderr $ "penny-fit: error: " ++ e+ E.exitFailure+ Ex.Success g -> return g+ let (new, len) = appendNew txnsOld ins+ U.saveDb dbLoc new+ putStrLn $ "imported " ++ show len ++ " new transactions."++help :: String+help = unlines+ [ "penny-fit [global-options] import [local-options] FIT_FILE"+ , "where FIT_FILE is the file downloaded from the financial"+ , "institution."+ , ""+ , "Local Options:"+ , ""+ , "-n, --new - Allows creation of new database. Without this option,"+ , "if the database file is not found, quits with an error."+ , ""+ , "-h, --help - Show this help."+ , ""+ ]+
+ Penny/Brenner/Merge.hs view
@@ -0,0 +1,274 @@+module Penny.Brenner.Merge (mode) where++import Control.Applicative (pure)+import Control.Monad (guard)+import qualified Control.Monad.Trans.State as St+import Data.List (find)+import qualified Data.Map as M+import Data.Maybe (mapMaybe, isNothing)+import Data.Monoid (First(..), mconcat)+import qualified Data.Text as X+import qualified Data.Text.IO as TIO+import qualified System.Console.MultiArg as MA+import qualified Control.Monad.Exception.Synchronous as Ex+import qualified System.IO as IO+import qualified Penny.Copper as C+import qualified Penny.Copper.Render as R+import qualified Penny.Lincoln as L+import qualified Penny.Lincoln.Transaction.Unverified as U+import qualified Penny.Lincoln.Queries as Q+import System.Exit (exitFailure)+import qualified Penny.Brenner.Types as Y+import qualified Penny.Brenner.Util as U++data Arg+ = AHelp+ | APos String+ deriving (Eq, Show)++toPosArg :: Arg -> Maybe String+toPosArg a = case a of { APos s -> Just s; _ -> Nothing }++mode :: Maybe Y.FitAcct -> MA.Mode (Ex.Exceptional String (IO ()))+mode maybeC = MA.Mode+ { MA.mName = "merge"+ , MA.mIntersperse = MA.Intersperse+ , MA.mOpts =+ [ MA.OptSpec ["help"] "h" (MA.NoArg AHelp)+ ]+ , MA.mPosArgs = APos+ , MA.mProcess = processor maybeC+ }++processor :: Maybe Y.FitAcct -> [Arg] -> Ex.Exceptional a (IO ())+processor maybeC as = return $+ if any (== AHelp) as+ then putStrLn help+ else doMerge maybeC (mapMaybe toPosArg as)++doMerge :: Maybe Y.FitAcct -> [String] -> IO ()+doMerge maybeAcct ss = do+ acct <- case maybeAcct of+ Nothing -> do+ IO.hPutStrLn IO.stderr $ "merge: error: no financial"+ ++ " institution account provided on command line, and"+ ++ " no default account configured."+ exitFailure+ Just ac -> return ac+ dbLs <- U.quitOnError+ $ U.loadDb (Y.AllowNew False) (Y.dbLocation acct)+ l <- C.openStdin ss+ let dbWithEntry = fmap (pairWithEntry acct) . M.fromList $ dbLs+ (l', db') = changeItems acct+ l (filterDb (Y.pennyAcct acct) dbWithEntry l)+ newTxns = createTransactions acct db'+ final = C.Ledger (C.unLedger l' ++ newTxns)+ case R.ledger (Y.groupSpecs acct) final of+ Nothing -> do+ IO.hPutStrLn IO.stderr "Could not render final ledger."+ exitFailure+ Just x -> TIO.putStr x+++help :: String+help = unlines+ [ "penny-fit merge: merges new transactions from database"+ , "to ledger file."+ , "usage: penny-fit merge [options] FILE..."+ , "Results are printed to standard output. If no FILE, or if FILE is -,"+ , "read standard input."+ , ""+ , "Options:"+ , " -h, --help - show help and exit"+ ]++-- | Removes all Brenner postings that already have a Penny posting+-- with the correct uNumber.+filterDb :: Y.PennyAcct -> DbWithEntry -> C.Ledger -> DbWithEntry+filterDb ax m l = M.difference m ml+ where+ ml = M.fromList+ . flip zip (repeat ())+ . mapMaybe toUNum+ . filter inPennyAcct+ . concatMap L.postFam+ . mapMaybe toTxn+ . C.unLedger+ $ l+ toTxn t = case t of+ C.Transaction x -> Just x+ _ -> Nothing+ inPennyAcct p = Q.account p == (Y.unPennyAcct ax)+ toUNum p = getUNumberFromTags . Q.tags $ p++-- | Gets the first UNumber from a list of Tags.+getUNumberFromTags :: L.Tags -> Maybe Y.UNumber+getUNumberFromTags =+ getFirst+ . mconcat+ . map First+ . map getUNumberFromTag+ . L.unTags++-- | Examines a tag to see if it is a uNumber. If so, returns the+-- UNumber. Otherwise, returns Nothing.+getUNumberFromTag :: L.Tag -> Maybe Y.UNumber+getUNumberFromTag (L.Tag x) = do+ (f, r) <- X.uncons x+ guard (f == 'U')+ case reads . X.unpack $ r of+ (y, ""):[] -> return $ Y.UNumber y+ _ -> Nothing+++-- | Changes a single Item.+changeItem+ :: Y.FitAcct+ -> C.Item+ -> St.State DbWithEntry C.Item+changeItem acct =+ C.mapItemA pure pure (changeTransaction acct)+++-- | Changes all postings that match an AmexTxn to assign them the+-- proper UNumber. Returns a list of changed items, and the DbMap of+-- still-unassigned AmexTxns.+changeItems+ :: Y.FitAcct+ -> C.Ledger+ -> DbWithEntry+ -> (C.Ledger, DbWithEntry)+changeItems acct l =+ St.runState (C.mapLedgerA (changeItem acct) l)+++changeTransaction+ :: Y.FitAcct+ -> L.Transaction+ -> St.State DbWithEntry L.Transaction+changeTransaction acct txn = do+ let fam = L.unTransaction txn+ fam' = L.mapParent (const L.emptyTopLineChangeData) fam+ fam'' <- L.mapChildrenA (inspectAndChange acct txn) fam'+ return $ L.changeTransaction fam'' txn++-- | Inspects a posting to see if it is an Amex posting and, if so,+-- whether it matches one of the remaining AmexTxns. If so, then+-- changes the transaction's UNumber, and remove that UNumber from the+-- DbMap. If the posting alreay has a Number (UNumber or otherwise)+-- skips it.+inspectAndChange+ :: Y.FitAcct+ -> L.Transaction+ -> L.Posting+ -> St.State DbWithEntry L.PostingChangeData+inspectAndChange acct t p = do+ m <- St.get+ case findMatch acct t p m of+ Nothing -> return L.emptyPostingChangeData+ Just (n, m') ->+ let L.Tags oldTags = L.pTags p+ tags' = L.Tags (oldTags ++ [newLincolnUNumber n])+ pcd = L.emptyPostingChangeData+ { L.pcTags = Just tags' }+ in St.put m' >> return pcd++newLincolnUNumber :: Y.UNumber -> L.Tag+newLincolnUNumber a =+ L.Tag ('U' `X.cons` (X.pack . show . Y.unUNumber $ a))+++-- | Searches a DbMap for an AmexTxn that matches a given posting. If+-- a match is found, returns the matching UNumber and a new DbMap that+-- has the match removed.+findMatch+ :: Y.FitAcct+ -> L.Transaction+ -> L.Posting+ -> DbWithEntry+ -> Maybe (Y.UNumber, DbWithEntry)+findMatch acct t p m = fmap toResult findResult+ where+ findResult = find (pennyTxnMatches acct t p)+ . M.toList $ m+ toResult (u, (_, _)) = (u, M.delete u m)++-- | 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 acct p = (p, en)+ where+ en = L.Entry dc (L.Amount qty cty (Just (Y.side acct))+ (Just (Y.spaceBetween acct)))+ dc = Y.translate (Y.incDec p) (Y.translator acct)+ qty = U.parseQty (Y.amount p)+ cty = Y.unCurrency . Y.currency $ acct++type DbWithEntry = M.Map Y.UNumber (Y.Posting, L.Entry)++-- | Does the given Penny transaction match this posting? Makes sure+-- that the account, quantity, date, commodity, and DrCr match, and+-- that the posting does not have a number (it's OK if the transaction+-- has a number.)+pennyTxnMatches+ :: Y.FitAcct+ -> L.Transaction+ -> L.Posting+ -> (a, (Y.Posting, L.Entry))+ -> Bool+pennyTxnMatches acct t p (_, (a, e)) =+ mA && noFlag && mQ && mDC && mDate && mCmdty+ where+ mA = L.pAccount p == (Y.unPennyAcct . Y.pennyAcct $ acct)+ mQ = L.equivalent (L.qty . L.amount . L.pEntry $ p)+ (L.qty . L.amount $ e)+ mDC = (L.drCr e) == (L.drCr . L.pEntry $ p)+ (L.Family tl _ _ _) = L.unTransaction t+ mDate = (L.day . L.tDateTime $ tl) == (Y.unDate . Y.date $ a)+ noFlag = isNothing . L.pNumber $ p+ mCmdty = (L.commodity . L.amount $ e)+ == (Y.unCurrency . Y.currency $ acct)+++-- | 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.+newTransaction+ :: Y.FitAcct+ -> (Y.UNumber, (Y.Posting, L.Entry))+ -> L.Transaction+newTransaction acct (u, (a, e)) = L.rTransaction rt+ where+ rt = L.RTransaction+ { L.rtCommodity = Y.unCurrency . Y.currency $ acct+ , L.rtSide = Just . Y.side $ acct+ , L.rtSpaceBetween = Just . Y.spaceBetween $ acct+ , L.rtDrCr = L.drCr e+ , L.rtTopLine = tl+ , L.rtPosting = p1+ , L.rtMorePostings = []+ , L.rtIPosting = p2+ }+ tl = (U.emptyTopLine ( L.dateTimeMidnightUTC . Y.unDate+ . Y.date $ a))+ { U.tPayee = Just (L.Payee pa) }+ pa = if X.null . Y.unPayee . Y.payee $ a+ then Y.unDesc . Y.desc $ a+ else Y.unPayee . Y.payee $ a+ pennyAcct = Y.unPennyAcct . Y.pennyAcct $ acct+ p1 = (U.emptyRPosting pennyAcct (L.qty . L.amount $ e))+ { U.rTags = L.Tags [newLincolnUNumber u] }+ p2 = U.emptyIPosting (Y.unDefaultAcct . Y.defaultAcct $ acct)++-- | Creates new transactions for all the items remaining in the+-- DbMap. Appends a blank line after each one.+createTransactions+ :: Y.FitAcct+ -> DbWithEntry+ -> [C.Item]+createTransactions acct =+ concatMap (\i -> [i, C.BlankLine])+ . map C.Transaction+ . map (newTransaction acct)+ . M.assocs+
+ Penny/Brenner/Print.hs view
@@ -0,0 +1,71 @@+-- | Prints parsed transactions.+--+-- TODO add support to this and other Brenner components for reading+-- from stdin.+module Penny.Brenner.Print (mode) where++import qualified Penny.Brenner.Types as Y+import qualified Penny.Brenner.Util as U+import qualified System.Console.MultiArg as MA+import qualified System.IO as IO+import qualified System.Exit as E+import qualified Control.Monad.Exception.Synchronous as Ex+import Data.Maybe (mapMaybe)++help :: String+help = unlines+ [ "penny-fit [global-options] print [local-options] FILE..."+ , "Parses the transactions in each FILE using the appropriate parser"+ , "and prints the parse result to standard output."+ , ""+ , "Local options:"+ , " --help, -h Show this help and exit."+ ]++data Arg+ = ArgHelp+ | ArgFile String+ deriving (Eq, Show)++mode+ :: Maybe Y.FitAcct+ -> MA.Mode (Ex.Exceptional String (IO ()))+mode mayFa = MA.Mode+ { MA.mName = "print"+ , MA.mIntersperse = MA.Intersperse+ , MA.mOpts = [MA.OptSpec ["help"] "h" (MA.NoArg ArgHelp)]+ , MA.mPosArgs = ArgFile+ , MA.mProcess = processor mayFa+ }++processor+ :: Maybe Y.FitAcct+ -> [Arg]+ -> Ex.Exceptional String (IO ())+processor mayFa ls =+ if any (== ArgHelp) ls+ then return (putStrLn help)+ else case mayFa of+ Nothing -> Ex.throw $+ "no financial institution account"+ ++ " provided on command line, and no account"+ ++ " configured by default."+ Just fa -> return $ doPrint (snd . Y.parser $ fa) ls++doPrint+ :: (Y.FitFileLocation -> IO (Ex.Exceptional String [Y.Posting]))+ -> [Arg]+ -> IO ()+doPrint prsr ls = mapM_ f . mapMaybe toFile $ ls+ where+ f file = do+ r <- prsr file+ case r of+ Ex.Exception s -> do+ IO.hPutStrLn IO.stderr $ "penny-fit print: error: " ++ s+ E.exitFailure+ Ex.Success ps -> mapM putStr . map U.showPosting $ ps+ toFile a = case a of+ ArgFile s -> Just (Y.FitFileLocation s)+ _ -> Nothing+
+ Penny/Brenner/Types.hs view
@@ -0,0 +1,287 @@+module Penny.Brenner.Types+ ( Date(..)+ , IncDec(..)+ , UNumber(..)+ , FitId(..)+ , Payee(..)+ , Desc(..)+ , Amount(unAmount)+ , mkAmount+ , translate+ , DbMap+ , DbList+ , Posting(..)+ , DbLocation(..)+ , Name(..)+ , PennyAcct(..)+ , Translator(..)+ , DefaultAcct(..)+ , Currency(..)+ , FitAcct(..)+ , Config(..)+ , FitFileLocation(..)+ , AllowNew(..)+ ) 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++-- | The date reported by the financial institution.+newtype Date = Date { unDate :: Time.Day }+ deriving (Eq, Show, Ord, Read)++instance S.Serialize Date where+ put = S.put . show . unDate+ get = Date <$> (read <$> S.get)++-- | Reports changes in account balances. Avoids using /debit/ and+-- /credit/ as these terms are used differently by the bank than in+-- your ledger (that is, the bank reports it from their perspective,+-- not yours) so instead the terms /increase/ and /decrease/ are+-- used. IncDec is used to record the bank's transactions so+-- /increase/ and /decrease/ are used in the same way you would see+-- them on a bank statement, whether it's a credit card, loan,+-- checking account, etc.+data IncDec+ = Increase+ -- ^ Increases the account balance. For a checking or savings+ -- account, this is a deposit. For a credit card, this is a purchase.++ | Decrease+ -- ^ Decreases the account balance. On a credit card, this is a+ -- payment. On a checking account, this is a withdrawal.+ deriving (Eq, Show, Read)++instance S.Serialize IncDec where+ put x = case x of+ Increase -> S.putWord8 0+ Decrease -> S.putWord8 1+ get = S.getWord8 >>= f+ where+ f x = case x of+ 0 -> return Increase+ 1 -> return Decrease+ _ -> fail "read IncDec error"++-- | A unique number assigned by Brenner to identify each+-- posting. This is unique within a particular financial institution+-- account only.+newtype UNumber = UNumber { unUNumber :: Integer }+ deriving (Eq, Show, Ord, Read)++instance S.Serialize UNumber where+ put = S.put . unUNumber+ get = UNumber <$> S.get++putText :: Text -> S.Put+putText = S.put . E.encodeUtf8++getText :: S.Get Text+getText = S.get >>= f+ where+ f bs = case E.decodeUtf8' bs of+ Left _ -> fail "text reading failed"+ Right x -> return x+++-- | For Brenner to work, the bank has to assign unique identifiers to+-- each transaction that it gives you for download. This is the+-- easiest reliable way to ensure duplicates are not processed+-- multiple times. (There are other ways to accomplish this, but they+-- are much harder and less reliable.) If the bank does not do this,+-- you can't use Brenner.+newtype FitId = FitId { unFitId :: Text }+ deriving (Eq, Show, Ord, Read)++instance S.Serialize FitId where+ put = putText . unFitId+ get = FitId <$> getText++-- | Some financial institutions assign a separate Payee in addition+-- to a description. Others just have a single Description field. If+-- this institution uses both, put something here. Brenner will prefer+-- the Payee if it is not zero length; then it will use the Desc.+newtype Payee = Payee { unPayee :: Text }+ deriving (Eq, Show, Ord, Read)++instance S.Serialize Payee where+ put = putText . unPayee+ get = Payee <$> getText++-- | The transaction description. Some institutions assign only a+-- description (sometimes muddling a payee with long codes, some+-- dates, etc). Brenner prefers the Payee if there is one, and uses a+-- Desc otherwise.+newtype Desc =+ Desc { unDesc :: Text }+ deriving (Eq, Show, Ord, Read)++instance S.Serialize Desc where+ put = putText . unDesc+ get = Desc <$> getText++-- | The amount of the transaction. Do not include any leading plus or+-- minus signs; this should be only digits and a decimal point.+newtype Amount = Amount { unAmount :: Text }+ deriving (Eq, Show, Ord, Read)++instance S.Serialize Amount where+ put = putText . unAmount+ get = getText >>= f+ where+ f x = case mkAmount . unpack $ x of+ Nothing -> fail $ "failed to load amount: " ++ unpack x+ Just a -> return a++-- | Ensures that incoming Amounts have only digits and (up to) one+-- decimal point.+mkAmount :: String -> Maybe Amount+mkAmount s =+ let isDigit c = c >= '0' && c <= '9'+ (_, rs) = span isDigit s+ in case rs of+ "" -> if not . null $ s+ then return . Amount . pack $ s+ else Nothing+ '.':rest -> if all isDigit rest+ then return . Amount . pack $ s+ else Nothing+ _ -> Nothing++translate+ :: IncDec+ -> Translator+ -> L.DrCr+translate Increase IncreaseIsDebit = L.Debit+translate Increase IncreaseIsCredit = L.Credit+translate Decrease IncreaseIsDebit = L.Credit+translate Decrease IncreaseIsCredit = L.Debit++type DbMap = M.Map UNumber Posting+type DbList = [(UNumber, Posting)]++data Posting = Posting+ { date :: Date+ , desc :: Desc+ , incDec :: IncDec+ , amount :: Amount+ , payee :: Payee+ , fitId :: FitId+ } deriving (Read, Show)+++instance S.Serialize Posting where+ put x = S.put (date x)+ >> S.put (desc x)+ >> S.put (incDec x)+ >> S.put (amount x)+ >> S.put (payee x)+ >> S.put (fitId x)+ get = Posting+ <$> S.get+ <*> S.get+ <*> S.get+ <*> S.get+ <*> S.get+ <*> S.get++-- | Where is the database of postings?+newtype DbLocation = DbLocation { unDbLocation :: Text }+ deriving (Eq, Show)++-- | A name used to refer to a batch of settings.+newtype Name = Name { unName :: Text }+ deriving (Eq, Show)++-- | The Penny account holding postings for this financial+-- institution. For instance it might be @Assets:Checking@ if this is+-- your checking account, @Liabilities:Credit Card@, or whatever.+newtype PennyAcct = PennyAcct { unPennyAcct :: L.Account }+ deriving (Eq, Show)++-- | What the financial institution shows as an increase or decrease+-- has to be recorded as a debit or credit in the PennyAcct.+data Translator+ = IncreaseIsDebit+ -- ^ That is, when the financial institution shows a posting that+ -- increases your account balance, you record a debit. You will+ -- probably use this for deposit accounts, like checking and+ -- savings. These are asset accounts so if the balance goes up you+ -- record a debit in your ledger.++ | IncreaseIsCredit+ -- ^ That is, when the financial institution shows a posting that+ -- increases your account balance, you record a credit. You will+ -- probably use this for liabilities, such as credit cards and other+ -- loans.++ deriving (Eq, Show)++-- | The default account to place unclassified postings in. For+-- instance @Expenses:Unclassified@.+newtype DefaultAcct = DefaultAcct { unDefaultAcct :: L.Account }+ deriving (Eq, Show)++-- | The currency for all transactions, e.g. @$@.+newtype Currency = Currency { unCurrency :: L.Commodity }+ deriving (Eq, Show)++-- | A batch of settings representing a single financial institution+-- account.+data FitAcct = FitAcct+ { dbLocation :: DbLocation+ , pennyAcct :: PennyAcct+ , defaultAcct :: DefaultAcct+ , currency :: Currency+ , groupSpecs :: R.GroupSpecs+ , translator :: Translator++ , side :: L.Side+ -- ^ When creating new transactions, the commodity will be on this+ -- side++ , spaceBetween :: L.SpaceBetween+ -- ^ When creating new transactions, is there a space between the+ -- commodity and the quantity++ , parser :: ( String+ , FitFileLocation -> IO (Ex.Exceptional String [Posting]))+ -- ^ Parses a file of transactions from the financial+ -- institution. The function must open the file and parse it. This+ -- is in the IO monad not only because the function must open the+ -- file itself, but also so the function can perform arbitrary IO+ -- (run pdftotext, maybe?) If there is failure, the function can+ -- return an Exceptional String, which is the error+ -- message. Alternatively the function can raise an exception in the+ -- IO monad (currently Brenner makes no attempt to catch these) so+ -- if any of the IO functions throw you can simply not handle the+ -- exceptions.+ --+ -- The first element of the pair is a help string which should+ -- indicate how to download the data, as a helpful reminder.++ }++-- | Configuration for the Brenner program. You can optionally have+-- a default FitAcct, which is used if you do not specify any FitAcct on the+-- command line. You can also name any number of additional FitAccts. If+-- you do not specify a default FitAcct, you must specify a FitAcct on the+-- command line.++data Config = Config+ { defaultFitAcct :: Maybe FitAcct+ , moreFitAccts :: [(Name, FitAcct)]+ }++newtype FitFileLocation = FitFileLocation { unFitFileLocation :: String }+ deriving (Show, Eq)++newtype AllowNew = AllowNew { unAllowNew :: Bool }+ deriving (Show, Eq)
+ Penny/Brenner/Util.hs view
@@ -0,0 +1,97 @@+module Penny.Brenner.Util where++import Control.Monad.Exception.Synchronous as Ex+import qualified Penny.Brenner.Types as Y+import qualified Data.ByteString as BS+import qualified System.IO.Error as IOE+import qualified Data.Serialize as S+import qualified Data.Text as X+import qualified Penny.Copper.Parsec as CP+import qualified Text.Parsec as P+import qualified Penny.Lincoln as L+import Control.Monad (join)+import qualified System.IO as IO+import qualified System.Exit as E++-- | Loads the database from disk. If allowNew is True, then does not+-- fail if the file was not found.+loadDb+ :: Y.AllowNew+ -- ^ Is a new file allowed?++ -> Y.DbLocation+ -- ^ DB location++ -> IO (Ex.Exceptional String Y.DbList)+loadDb (Y.AllowNew allowNew) (Y.DbLocation dbLoc) = do+ eiStr <- IOE.tryIOError (BS.readFile . X.unpack $ dbLoc)+ case eiStr of+ Left e ->+ if allowNew && IOE.isDoesNotExistError e+ then return (return [])+ else IOE.ioError e+ Right g -> return . readDbTuple $ g++-- | File version. Increment this when anything in the file format+-- changes.+version :: Int+version = 0++brenner :: String+brenner = "penny.brenner"++readDbTuple+ :: BS.ByteString+ -> Ex.Exceptional String Y.DbList+readDbTuple bs = do+ (s, v, ls) <- Ex.fromEither $ S.decode bs+ Ex.assert "database file format not recognized." $ s == brenner+ Ex.assert "wrong database version." $ v == version+ return ls++saveDbTuple :: Y.DbList -> BS.ByteString+saveDbTuple ls = S.encode (brenner, version, ls)++-- | Writes a new database to disk.+saveDb :: Y.DbLocation -> Y.DbList -> IO ()+saveDb (Y.DbLocation p) = BS.writeFile (X.unpack p) . saveDbTuple++-- | Parses quantities from amounts. All amounts should be verified as+-- having only digits, optionally followed by a point and then more+-- 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+ Left e -> error $ "could not parse quantity from string: "+ ++ (X.unpack . Y.unAmount $ a) ++ ": " ++ show e+ Right g -> g++quitOnError+ :: IO (Ex.Exceptional String a)+ -> IO a+quitOnError = join . fmap (Ex.switch err return)+ where+ err s = do+ IO.hPutStrLn IO.stderr $ "penny-fit: error: " ++ s+ E.exitFailure++label :: String -> X.Text -> String+label s x = s ++ ": " ++ X.unpack x ++ "\n"++-- | Shows a Posting in human readable format.+showPosting :: Y.Posting -> String+showPosting (Y.Posting dt dc nc am py fd) =+ label "Date" (X.pack . show . Y.unDate $ dt)+ ++ label "Description" (Y.unDesc dc)+ ++ label "Type" (X.pack $ case nc of+ Y.Increase -> "increase"+ Y.Decrease -> "decrease")+ ++ label "Amount" (Y.unAmount am)+ ++ label "Payee" (Y.unPayee py)+ ++ label "Financial institution ID" (Y.unFitId fd)+ ++ "\n"++showDbPair :: (Y.UNumber, Y.Posting) -> String+showDbPair (Y.UNumber u, p) =+ label "U number" (X.pack . show $ u)+ ++ showPosting p
Penny/Cabin.hs view
@@ -1,15 +1,7 @@ -- | Cabin - Penny reports-module Penny.Cabin (allReportsWithDefaults, I.Report(..)) where--import qualified Penny.Cabin.Balance as B-import qualified Penny.Cabin.Posts as P-import qualified Penny.Copper as C-import qualified Penny.Cabin.Interface as I+--+-- Cabin contains reports, or functions that take a list of postings+-- and return a formatted Text to display data in a human-readable+-- format.+module Penny.Cabin where -allReportsWithDefaults ::- C.DefaultTimeZone- -> C.RadGroup- -> [I.Report]-allReportsWithDefaults dtz rg =- [ B.balanceReport (B.defaultOptions dtz)- , P.makeReport (P.defaultOptions dtz rg) ]
− Penny/Cabin/Allocate.hs
@@ -1,72 +0,0 @@-module Penny.Cabin.Allocate (- Allocation,- allocation,- unAllocation,- allocate) where--import qualified Control.Monad.Trans.State as St-import qualified Data.Foldable as F-import qualified Data.Traversable as T-import qualified Data.Map as M--newtype Allocation = Allocation { unAllocation :: Int }- deriving (Show, Eq, Ord)--allocation :: Int -> Allocation-allocation i =- if i < 1- then error "Allocations must be at least 1"- else Allocation i---- | Divide up a whole number proportionally into several parts. The--- sum of the parts is guaranteed to add up to original number.-allocate ::- Ord k- => M.Map k Allocation- -- ^ Maps arbitrary unique values to the allocations, so that you- -- can look up the corresponding allocations.-- -> Int- -- ^ Allocated values will add up to this number.- - -> M.Map k Int- -- ^ The given number, allocated into proportional parts.--allocate m t = let- tot = F.sum . fmap (toDouble . unAllocation) $ m- ratios = fmap ((/tot) . toDouble . unAllocation) m- rounded = fmap (round . (* (toDouble t))) ratios- toDouble = fromIntegral :: Int -> Double- - in if M.null m- then M.empty- else adjust rounded t--adjust ::- Ord k- => M.Map k Int- -> Int- -> M.Map k Int-adjust ws w = let- wsInts = fmap fromIntegral ws- diff = (fromIntegral w) - F.sum wsInts in- if M.null ws- then M.empty- else if diff == 0- then ws- else let- ws' = St.evalState (T.mapM adjustMap ws) diff- in adjust ws' w---- | The state is the target number minus the current actual total.-adjustMap :: Int -> St.State Int Int-adjustMap w = do- diff <- St.get- case compare diff 0 of- EQ -> return w- GT -> do- St.put (pred diff)- return (succ w)- LT -> do- St.put (succ diff)- return (pred w)
Penny/Cabin/Balance.hs view
@@ -1,107 +1,21 @@--- | The Penny balance report-module Penny.Cabin.Balance (- -- * Making reports- - -- | Use these functions if you are building a report from your own- -- code and you are not using the Zinc parser.- T.report- , T.nullConvert- , T.converter- - -- * Parsing reports- -- | For use with the Zinc command line parser.- , BalanceOpts(..)- , balanceReport- , defaultOptions- , balanceAsIs-- ) where+-- | Penny balance reports. Currently there are two balance reports:+-- the MultiCommodity report, which cannot convert commodities and+-- which therefore might show more than one commodity in a single+-- report, and the Convert report, which uses price data in the Penny+-- file to convert all commodities to a single commodity. The Convert+-- report always displays only one commodity per account and this one+-- commodity for the whole report.+module Penny.Cabin.Balance where -import qualified Penny.Cabin.Balance.Tree as T-import qualified Penny.Cabin.Balance.Parser as P-import qualified Penny.Cabin.Balance.Help as H-import qualified Penny.Cabin.Chunk as Chunk-import qualified Penny.Cabin.Colors as C-import qualified Penny.Cabin.Colors.DarkBackground as Dark+import qualified Penny.Cabin.Balance.MultiCommodity as MC import qualified Penny.Cabin.Interface as I-import qualified Penny.Cabin.Options as CO-import qualified Penny.Copper as Cop-import qualified Penny.Lincoln as L-import qualified Penny.Lincoln.Balance as Bal-import qualified Penny.Shield as S--import qualified Control.Monad.Exception.Synchronous as Ex-import qualified Data.Text as X-import System.Console.MultiArg.Prim (Parser)--data BalanceOpts = BalanceOpts {- drCrColors :: C.DrCrColors- , baseColors :: C.BaseColors- , balanceFormat :: L.BottomLine -> X.Text- , colorPref :: Chunk.Colors- , showZeroBalances :: CO.ShowZeroBalances- , defaultTimeZone :: Cop.DefaultTimeZone- , convert :: Maybe (L.Commodity, L.DateTime)- }--toParseOpts :: BalanceOpts -> P.ParseOpts-toParseOpts b = P.ParseOpts {- P.drCrColors = drCrColors b- , P.baseColors = baseColors b- , P.colorPref = colorPref b- , P.showZeroBalances = showZeroBalances b- , P.convert = convert b- }--toTreeOpts :: P.ParseOpts -> BalanceOpts -> T.TreeOpts-toTreeOpts p b = T.TreeOpts {- T.drCrColors = P.drCrColors p- , T.baseColors = P.baseColors p- , T.balanceFormat = balanceFormat b- , T.showZeroBalances = P.showZeroBalances p- }--parser :: (S.Runtime -> BalanceOpts) -> Parser I.ReportFunc-parser frt = do- parsed <- P.parseOptions- let rf rt _ _ ps pps = do- let bo = frt rt- po = toParseOpts bo- po' <- Ex.mapException showParseErr $- parsed rt (defaultTimeZone bo) po- let to = toTreeOpts po' bo- conv <- case P.convert po' of- Nothing -> return $ T.nullConvert ps- Just co -> T.converter co pps ps- return- . Chunk.chunksToText (P.colorPref po')- . concat- . T.report to- $ conv- return rf- --showParseErr :: P.Error -> X.Text-showParseErr = X.pack . show---balanceReport :: (S.Runtime -> BalanceOpts) -> I.Report-balanceReport f = I.Report H.help "balance" (parser f)---defaultOptions :: Cop.DefaultTimeZone -> S.Runtime -> BalanceOpts-defaultOptions dtz rt = BalanceOpts {- drCrColors = Dark.drCrColors- , baseColors = Dark.baseColors- , balanceFormat = balanceAsIs- , colorPref = CO.maxCapableColors rt- , showZeroBalances = CO.ShowZeroBalances False- , defaultTimeZone = dtz- , convert = Nothing- }+import qualified Penny.Cabin.Balance.Convert as C+import qualified Penny.Cabin.Balance.Convert.Options as ConvOpts -balanceAsIs :: L.BottomLine -> X.Text-balanceAsIs n = case n of- L.Zero -> X.pack "--"- L.NonZero c -> X.pack . show . L.unQty . Bal.qty $ c+-- | The default multi-commodity balance report.+multiCommodity :: I.Report+multiCommodity = MC.defaultReport +-- | The default converting balance report.+convert :: I.Report+convert = C.cmdLineReport ConvOpts.defaultOptions
+ Penny/Cabin/Balance/Convert.hs view
@@ -0,0 +1,334 @@+-- | The Convert report. This report converts all account balances to+-- a single commodity, which must be specified.++module Penny.Cabin.Balance.Convert (+ Opts(..)+ , Sorter+ , report+ , cmdLineReport+ , getSorter+ ) where++import Control.Applicative ((<$>), (<*>))+import qualified Control.Monad.Exception.Synchronous as Ex+import qualified Data.Tree as E+import qualified Data.Traversable as Tvbl+import qualified Penny.Cabin.Options as CO+import qualified Penny.Cabin.Parsers as CP+import qualified Penny.Cabin.Scheme as Scheme+import qualified Penny.Cabin.Balance.Util as U+import qualified Penny.Cabin.Balance.Convert.Chunker as K+import qualified Penny.Cabin.Balance.Convert.Options as O+import qualified Penny.Cabin.Balance.Convert.Parser as P+import qualified Penny.Cabin.Interface as I+import qualified Penny.Lincoln as L+import qualified Penny.Lincoln.Balance as Bal+import qualified Penny.Liberty as Ly+import qualified Penny.Shield as S+import qualified Data.Either as Ei+import qualified Data.Map as M+import qualified Data.Text as X+import Data.Monoid (mempty, mappend, mconcat)+import qualified System.Console.MultiArg as MA++-- | Options for the Convert report. These are the only options you+-- 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+ , showZeroBalances :: CO.ShowZeroBalances+ , sorter :: Sorter+ , target :: L.To+ , dateTime :: L.DateTime+ }++-- | How to sort each line of the report. Each subaccount has only one+-- 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)+ -> (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+ -> L.DateTime+ -> L.To+ -> L.Balance+ -> Ex.Exceptional X.Text L.BottomLine+convertBalance db dt to bal = fmap mconcat r+ where+ r = mapM (convertOne db dt to) . M.assocs . L.unBalance $ bal++-- | Converts a single BottomLine to a new commodity. Fails with an+-- error message if no conversion data is available.+convertOne ::+ L.PriceDb+ -> L.DateTime+ -> L.To+ -> (L.Commodity, L.BottomLine)+ -> Ex.Exceptional X.Text L.BottomLine+convertOne db dt to (cty, bl) =+ case bl of+ L.Zero -> return L.Zero+ L.NonZero (L.Column dc qt) -> Ex.mapExceptional e g ex+ where+ ex = L.convert db dt to am+ am = L.Amount qt cty Nothing Nothing+ e = convertError to (L.From cty)+ g r = L.NonZero (L.Column dc r)++-- | Creates an error message for conversion errors.+convertError ::+ L.To+ -> L.From+ -> L.PriceDbError+ -> X.Text+convertError (L.To to) (L.From fr) e =+ let fromErr = L.unCommodity fr+ toErr = L.unCommodity to+ in case e of+ L.FromNotFound ->+ X.pack "no data to convert from commodity "+ `X.append` fromErr+ L.ToNotFound ->+ X.pack "no data to convert to commodity "+ `X.append` toErr+ L.CpuNotFound ->+ X.pack "no data to convert from commodity "+ `X.append` fromErr+ `X.append` (X.pack " to commodity ")+ `X.append` toErr+ `X.append` (X.pack " at given date and time")+++-- | Create a price database.+buildDb :: [L.PricePoint] -> L.PriceDb+buildDb = foldl f L.emptyDb where+ f db pb = L.addPrice db pb++-- | All data for the report after all balances have been converted to+-- a single commodity and all the sums of the child accounts have been+-- added to the parent accounts.+data ForestAndBL = ForestAndBL {+ _tbForest :: E.Forest (L.SubAccount, L.BottomLine)+ , _tbTotal :: L.BottomLine+ , _tbTo :: L.To+ }++-- | Converts the balance data in preparation for screen rendering.+rows :: ForestAndBL -> ([K.Row], L.To)+rows (ForestAndBL f tot to) = (first:second:rest, to)+ where+ first = K.ROneCol $ K.OneColRow 0 desc+ desc = X.pack "All amounts reported in commodity: "+ `X.append` (L.unCommodity+ . L.unTo+ $ to)+ second = K.RMain $ K.MainRow 0 (X.pack "Total") tot+ rest = map mainRow+ . concatMap E.flatten+ . map U.labelLevels+ $ f+++mainRow :: (Int, (L.SubAccount, L.BottomLine)) -> K.Row+mainRow (l, (a, b)) = K.RMain $ K.MainRow l x b+ where+ x = L.text a++-- | The function for the Convert report. Use this function if you are+-- setting the options from a program (as opposed to parsing them in+-- from the command line.) Will fail if the balance conversions fail.+report ::+ Opts+ -> [L.PricePoint]+ -> [L.Box a]+ -> Ex.Exceptional X.Text [Scheme.PreChunk]+report os@(Opts getFmt _ _ _ _) ps bs = do+ fstBl <- sumConvertSort os ps bs+ let (rs, L.To cy) = rows fstBl+ fmt = getFmt cy+ return $ K.rowsToChunks fmt rs+++-- | Creates a report respecting the standard interface for reports+-- whose options are parsed in from the command line.+cmdLineReport+ :: O.DefaultOpts+ -> I.Report+cmdLineReport o rt = (help o, mkMode)+ where+ mkMode _ _ fsf = MA.Mode+ { MA.mName = "convert"+ , MA.mIntersperse = MA.Intersperse+ , MA.mOpts = map (fmap Right) P.allOptSpecs+ , MA.mPosArgs = Left+ , MA.mProcess = process rt o fsf }++process+ :: S.Runtime+ -> O.DefaultOpts+ -> ([L.Transaction] -> [L.Box Ly.LibertyMeta])+ -> [Either String (P.Opts -> Ex.Exceptional String P.Opts)]+ -> Ex.Exceptional String (Either I.HelpStr I.ArgsAndReport)+process rt defaultOpts fsf ls = do+ let (posArgs, parsed) = Ei.partitionEithers ls+ op' = foldl (>>=) (return (O.toParserOpts defaultOpts rt)) parsed+ case op' of+ Ex.Exception s -> Ex.throw s+ Ex.Success g -> return $+ let noDefault = X.pack "no default price found"+ in case fromParsedOpts g of+ NeedsHelp -> Left $ help defaultOpts+ DoReport f ->+ let pr ts pps = do+ rptOpts <- Ex.fromMaybe noDefault $+ f pps (O.format defaultOpts)+ let boxes = fsf ts+ report rptOpts pps boxes+ in Right (posArgs, pr)+++-- | Sums the balances from the bottom to the top of the tree (so that+-- parent accounts have the sum of the balances of all their+-- children.) Then converts the commodities to a single commodity, and+-- sorts the accounts as requested. Fails if the conversion fails.+sumConvertSort+ :: Opts+ -> [L.PricePoint]+ -> [L.Box a]+ -> Ex.Exceptional X.Text ForestAndBL+sumConvertSort os ps bs = mkResult <$> convertedFrst <*> convertedTot+ where+ (Opts _ szb str tgt dt) = os+ bals = U.balances szb bs+ (frst, tot) = U.sumForest mempty mappend bals+ convertBal (a, bal) =+ (\bl -> (a, bl)) <$> convertBalance db dt tgt bal+ db = buildDb ps+ convertedFrst = mapM (Tvbl.mapM convertBal) frst+ convertedTot = convertBalance db dt tgt tot+ mkResult f t = ForestAndBL (U.sortForest str f) t tgt++-- | Determine the most frequent To commodity.+mostFrequent :: [L.PricePoint] -> Maybe L.To+mostFrequent = U.lastMode . map (L.to . L.price)+++data HelpOrOpts+ = NeedsHelp+ | DoReport ( [L.PricePoint]+ -> (L.Commodity -> 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+-- commodity and mostFrequent fails.+fromParsedOpts+ :: P.Opts+ -> HelpOrOpts+fromParsedOpts (P.Opts szb tgt dt so sb hlp) =+ if hlp+ then NeedsHelp+ else DoReport $ \pps fmt -> case tgt of+ P.ManualTarget to ->+ Just $ Opts fmt szb (getSorter so sb) to dt+ P.AutoTarget ->+ case mostFrequent pps of+ Nothing -> Nothing+ Just to ->+ Just $ Opts fmt szb (getSorter so sb) to dt++-- | Returns a function usable to sort pairs of SubAccount and+-- BottomLine depending on how you want them sorted.+getSorter :: CP.SortOrder -> P.SortBy -> Sorter+getSorter o b = flipper f+ where+ flipper = case o of+ CP.Ascending -> id+ CP.Descending ->+ \g p1 p2 -> case g p1 p2 of+ LT -> GT+ GT -> LT+ EQ -> EQ+ f p1@(a1, _) p2@(a2, _) = case b of+ P.SortByName -> compare a1 a2+ P.SortByQty -> cmpBottomLine p1 p2++cmpBottomLine :: Sorter+cmpBottomLine (n1, bl1) (n2, bl2) =+ case (bl1, bl2) of+ (L.Zero, L.Zero) -> EQ+ (L.NonZero _, L.Zero) -> LT+ (L.Zero, L.NonZero _) -> GT+ (L.NonZero c1, L.NonZero c2) ->+ mconcat [dc, qt, na]+ where+ dc = case (Bal.drCr c1, Bal.drCr c2) of+ (L.Debit, L.Debit) -> EQ+ (L.Debit, L.Credit) -> LT+ (L.Credit, L.Debit) -> GT+ (L.Credit, L.Credit) -> EQ+ qt = compare (Bal.qty c1) (Bal.qty c2)+ na = compare n1 n2++------------------------------------------------------------+-- ## Help+------------------------------------------------------------+ifDefault :: Bool -> String+ifDefault b = if b then " (default)" else ""++help :: O.DefaultOpts -> String+help o = unlines $+ [ "convert"+ , " Show account balances, after converting all amounts"+ , " to a single commodity. Accepts ONLY the following options:"+ , ""+ , "--show-zero-balances"+ , " Show balances that are zero"+ ++ ifDefault (CO.unShowZeroBalances . O.showZeroBalances $ o)+ , "--hide-zero-balances"+ , " Hide balances that are zero"+ ++ ifDefault (not . CO.unShowZeroBalances . O.showZeroBalances $ o)+ , ""+ , "--commodity TARGET-COMMMODITY, -c TARGET-COMMODITY"+ , " Convert all commodities to TARGET-COMMODITY."+ ] ++ case O.target o of+ P.ManualTarget (L.To cy) ->+ [ " default: " ++ (X.unpack . L.unCommodity $ cy) ]+ _ -> []+ +++ [ "--auto-commodity"+ , " convert all commodities to the commodity that appears most"+ , " often as the target commodity in your price data. If"+ , " there is a tie, the price closest to the end of your list"+ , " of prices is used."+ ++ case O.target o of+ P.AutoTarget -> " (default)"+ _ -> ""+ , ""+ , "--date DATE-TIME, -d DATE-TIME"+ , " Convert prices as of the date and time given"+ , " (by default, the current date and time is used.)"+ , ""+ , "--sort qty|name, -s qty|name"+ , " Sort balances by sub-account name"+ ++ ifDefault (O.sortBy o == P.SortByName)+ ++ " or by quantity"+ ++ ifDefault (O.sortBy o == P.SortByQty)+ , "--ascending"+ , " Sort in ascending order"+ ++ ifDefault (O.sortOrder o == CP.Ascending)+ , "--descending"+ , " Sort in descending order"+ ++ ifDefault (O.sortOrder o == CP.Descending)+ , ""+ , "--help, -h"+ , " Show this help and exit"+ ]+
+ Penny/Cabin/Balance/Convert/Chunker.hs view
@@ -0,0 +1,227 @@+-- | Creates the output Chunks for the Balance report for+-- multi-commodity reports only.++module Penny.Cabin.Balance.Convert.Chunker (+ MainRow(..),+ OneColRow(..),+ Row(..),+ rowsToChunks+ ) where+++import Control.Applicative+ (Applicative (pure), (<$>), (<*>))+import qualified Penny.Cabin.Scheme as E+import qualified Penny.Cabin.Chunk as Chunk+import qualified Penny.Cabin.Meta as Meta+import qualified Penny.Cabin.Row as R+import qualified Penny.Lincoln as L+import qualified Data.Foldable as Fdbl+import qualified Data.Text as X++type IsEven = Bool++data Columns a = Columns {+ acct :: a+ , drCr :: a+ , quantity :: a+ } deriving Show++instance Functor Columns where+ fmap f c = Columns {+ acct = f (acct c)+ , drCr = f (drCr c)+ , quantity = f (quantity c)+ }++instance Applicative Columns where+ pure a = Columns a a a+ fn <*> fa = Columns {+ acct = (acct fn) (acct fa)+ , drCr = (drCr fn) (drCr fa)+ , quantity = (quantity fn) (quantity fa)+ }++data PreSpec = PreSpec {+ _justification :: R.Justification+ , _padSpec :: (E.Label, E.EvenOdd)+ , bits :: E.PreChunk }++-- | When given a list of columns, determine the widest row in each+-- column.+maxWidths :: [Columns PreSpec] -> Columns R.Width+maxWidths = Fdbl.foldl' maxWidthPerColumn (pure (R.Width 0))++-- | Applied to a Columns of PreSpec and a Colums of widths, return a+-- Columns that has the wider of the two values.+maxWidthPerColumn ::+ Columns R.Width+ -> Columns PreSpec+ -> Columns R.Width+maxWidthPerColumn w p = f <$> w <*> p where+ f old new = max old (E.width . bits $ new)++-- | Changes a single set of Columns to a set of ColumnSpec of the+-- given width.+preSpecToSpec ::+ Columns R.Width+ -> Columns PreSpec+ -> Columns R.ColumnSpec+preSpecToSpec ws p = f <$> ws <*> p where+ f width (PreSpec j ps bs) = R.ColumnSpec j width ps [bs]++resizeColumnsInList :: [Columns PreSpec] -> [Columns R.ColumnSpec]+resizeColumnsInList cs = map (preSpecToSpec w) cs where+ w = maxWidths cs+++widthSpacerAcct :: Int+widthSpacerAcct = 4++widthSpacerDrCr :: Int+widthSpacerDrCr = 1++colsToBits ::+ IsEven+ -> Columns R.ColumnSpec+ -> [E.PreChunk]+colsToBits isEven (Columns a dc q) = let+ fillSpec = if isEven+ then (E.Other, E.Even)+ else (E.Other, E.Odd)+ spacer w = R.ColumnSpec j (Chunk.Width w) fillSpec []+ j = R.LeftJustify+ cs = a+ : spacer widthSpacerAcct+ : dc+ : spacer widthSpacerDrCr+ : q+ : []+ in R.row cs++colsListToBits+ :: [Columns R.ColumnSpec]+ -> [[E.PreChunk]]+colsListToBits = zipWith f bools where+ f b c = colsToBits b c+ bools = iterate not True++preSpecsToBits+ :: [Columns PreSpec]+ -> [E.PreChunk]+preSpecsToBits =+ concat+ . colsListToBits+ . resizeColumnsInList++data Row = RMain MainRow | ROneCol OneColRow++-- | Displays a one-column row.+data OneColRow = OneColRow {+ ocIndentation :: Int+ -- ^ Indent the text by this many levels (not by this many+ -- spaces; this number is multiplied by another number in the+ -- Chunker source to arrive at the final indentation amount)++ , ocText :: X.Text+ -- ^ Text for the left column+ }++-- | Displays a single account in a Balance report. In a+-- single-commodity report, this account will only be one screen line+-- long. In a multi-commodity report, it might be multiple lines long,+-- with one screen line for each commodity.+data MainRow = MainRow {+ mrIndentation :: Int+ -- ^ Indent the account name by this many levels (not by this many+ -- spaces; this number is multiplied by another number in the+ -- Chunker source to arrive at the final indentation amount)++ , mrText :: X.Text+ -- ^ Text for the name of the account++ , mrBottomLine :: L.BottomLine+ -- ^ Commodity balances. If this list is empty, dashes are+ -- displayed for the DrCr and Qty.+ }+++rowsToChunks ::+ (L.Qty -> X.Text)+ -- ^ How to format a balance to allow for digit grouping+ -> [Row]+ -> [E.PreChunk]+rowsToChunks fmt =+ preSpecsToBits+ . rowsToColumns fmt++rowsToColumns ::+ (L.Qty -> X.Text)+ -- ^ How to format a balance to allow for digit grouping++ -> [Row]+ -> [Columns PreSpec]+rowsToColumns fmt rs = map (mkRow fmt) pairs+ where+ pairs = Meta.visibleNums (,) rs+++mkRow ::+ (L.Qty -> X.Text)+ -> (Meta.VisibleNum, Row)+ -> Columns PreSpec+mkRow fmt (vn, r) = case r of+ RMain m -> mkMainRow fmt (vn, m)+ ROneCol c -> mkOneColRow (vn, c)++mkOneColRow ::+ (Meta.VisibleNum, OneColRow)+ -> Columns PreSpec+mkOneColRow (vn, (OneColRow i t)) = Columns ca cd cq+ where+ txt = X.append indents t+ indents = X.replicate (indentAmount * max 0 i)+ (X.singleton ' ')+ eo = E.fromVisibleNum vn+ lbl = E.Other+ ca = PreSpec R.LeftJustify (lbl, eo) (E.PreChunk lbl eo txt)+ cd = PreSpec R.LeftJustify (lbl, eo) (E.PreChunk lbl eo X.empty)+ cq = cd++mkMainRow ::+ (L.Qty -> X.Text)+ -> (Meta.VisibleNum, MainRow)+ -> Columns PreSpec+mkMainRow fmt (vn, (MainRow i acctTxt b)) = Columns ca cd cq+ where+ eo = E.fromVisibleNum vn+ lbl = E.Other+ ca = PreSpec R.LeftJustify (lbl, eo) (E.PreChunk lbl eo txt)+ where+ txt = X.append indents acctTxt+ indents = X.replicate (indentAmount * max 0 i)+ (X.singleton ' ')+ cd = PreSpec R.LeftJustify (lbl, eo) cksDrCr+ cq = PreSpec R.LeftJustify (lbl, eo) cksQty+ (cksDrCr, cksQty) = balanceChunks fmt vn b+++balanceChunks ::+ (L.Qty -> X.Text)+ -> Meta.VisibleNum+ -> L.BottomLine+ -> (E.PreChunk, E.PreChunk)+balanceChunks fmt vn bl = (chkDc, chkQt)+ where+ eo = E.fromVisibleNum vn+ chkDc = E.bottomLineToDrCr bl eo+ chkQt = E.PreChunk lbl eo t+ where+ (lbl, t) = case bl of+ L.Zero -> (E.Zero, X.pack "--")+ L.NonZero (L.Column dc qt) -> (E.dcToLbl dc, fmt qt)+++indentAmount :: Int+indentAmount = 2+
+ Penny/Cabin/Balance/Convert/Options.hs view
@@ -0,0 +1,44 @@+-- | Default options for the Convert report when used from the command+-- line.+module Penny.Cabin.Balance.Convert.Options where++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+-- line. You don't need to use it if you are setting the options for+-- the Convert report directly from your own code.++data DefaultOpts = DefaultOpts+ { showZeroBalances :: CO.ShowZeroBalances+ , target :: P.Target+ , sortOrder :: CP.SortOrder+ , sortBy :: P.SortBy+ , format :: L.Commodity -> L.Qty -> X.Text+ }++toParserOpts :: DefaultOpts -> S.Runtime -> P.Opts+toParserOpts d rt = P.Opts+ { P.showZeroBalances = showZeroBalances d+ , P.target = target d+ , P.dateTime = S.currentTime rt+ , P.sortOrder = sortOrder d+ , P.sortBy = sortBy d+ , P.showHelp = False+ }++defaultOptions :: DefaultOpts+defaultOptions = DefaultOpts+ { showZeroBalances = CO.ShowZeroBalances False+ , target = P.AutoTarget+ , sortOrder = CP.Ascending+ , sortBy = P.SortByName+ , format = \_ q -> X.pack . show $ q+ }++
+ Penny/Cabin/Balance/Convert/Parser.hs view
@@ -0,0 +1,94 @@+-- | Parsing options for the Convert report from the command line.+module Penny.Cabin.Balance.Convert.Parser (+ Opts(..)+ , Target(..)+ , SortBy(..)+ , allOptSpecs+ ) where+++import qualified Control.Monad.Exception.Synchronous as Ex+import qualified Data.Text as X+import qualified Penny.Cabin.Options as CO+import qualified Penny.Cabin.Parsers as P+import qualified Penny.Lincoln as L+import qualified Penny.Copper.Parsec as Pc+import qualified System.Console.MultiArg.Combinator as C+import qualified Text.Parsec as Parsec+++-- | Is the target commodity determined by the user or automatically?+data Target = AutoTarget | ManualTarget L.To++data SortBy = SortByQty | SortByName deriving (Eq, Show, Ord)++-- | Default starting options for the Convert report. After+-- considering what is parsed in from the command line and price data,+-- a Convert.Opts will be generated.+data Opts = Opts+ { showZeroBalances :: CO.ShowZeroBalances+ , target :: Target+ , dateTime :: L.DateTime+ , sortOrder :: P.SortOrder+ , sortBy :: SortBy+ , showHelp :: Bool+ }++-- | Do not be tempted to change the setup in this module so that the+-- individual functions such as parseColor and parseBackground return+-- parsers rather than OptSpec. Such an arrangement breaks the correct+-- parsing of abbreviated long options.+allOptSpecs :: [C.OptSpec (Opts -> Ex.Exceptional String Opts)]+allOptSpecs =+ [ fmap toExc parseZeroBalances+ , parseCommodity+ , fmap toExc parseAuto+ , parseDate+ , fmap toExc parseSort+ , fmap toExc parseOrder+ , fmap toExc parseHelp ]+ where+ toExc f = return . f++parseZeroBalances :: C.OptSpec (Opts -> Opts)+parseZeroBalances = fmap f P.zeroBalances+ where+ f x o = o { showZeroBalances = x }+++parseCommodity :: C.OptSpec (Opts -> Ex.Exceptional String Opts)+parseCommodity = C.OptSpec ["commodity"] "c" (C.OneArg f)+ where+ f a1 os =+ case Parsec.parse Pc.lvl1Cmdty "" (X.pack a1) of+ Left _ -> Ex.throw $ "invalid commodity: " ++ a1+ Right g -> return $ os { target = ManualTarget . L.To $ g }++parseAuto :: C.OptSpec (Opts -> Opts)+parseAuto = C.OptSpec ["auto-commodity"] "" (C.NoArg f)+ where+ f os = os { target = AutoTarget }++parseDate :: C.OptSpec (Opts -> Ex.Exceptional String Opts)+parseDate = C.OptSpec ["date"] "d" (C.OneArg f)+ where+ f a1 os =+ case Parsec.parse Pc.dateTime "" (X.pack a1) of+ Left _ -> Ex.throw $ "invalid date: " ++ a1+ Right g -> return $ os { dateTime = g }++parseSort :: C.OptSpec (Opts -> Opts)+parseSort = C.OptSpec ["sort"] "s" (C.ChoiceArg ls)+ where+ ls = [ ("qty", (\os -> os { sortBy = SortByQty }))+ , ("name", (\os -> os { sortBy = SortByName })) ]++parseOrder :: C.OptSpec (Opts -> Opts)+parseOrder = fmap f P.order+ where+ f x o = o { sortOrder = x }++parseHelp :: C.OptSpec (Opts -> Opts)+parseHelp = fmap f P.help+ where+ f _ o = o { showHelp = True }
− Penny/Cabin/Balance/Help.hs
@@ -1,34 +0,0 @@-module Penny.Cabin.Balance.Help where--import qualified Data.Text as X--help :: X.Text-help = X.pack helpStr--helpStr :: String-helpStr = unlines [- "balance, bal",- " Show account balances. Accepts ONLY the following options:",- "",- " --color yes|no|auto|256",- " yes: show 8 colors always",- " no: never show colors",- " auto: show 8 or 256 colors, but only if stdout is a terminal",- " 256: show 256 colors always",- " --background light|dark",- " Use appropriate color scheme for terminal background",- "",- " --show-zero-balances",- " Show balances that are zero",- " --hide-zero-balances",- " Hide balances that are zero",- "",- " --convert commodity dateTime",- " Convert all commodities to the given commodity using their",- " price at the given date (and, optionally, time.) Fails if",- " any commodity does not have the necessary price.",- " -c commodity",- " Same as \"--convert commodity [right now]\"",- ""- ]-
+ Penny/Cabin/Balance/MultiCommodity.hs view
@@ -0,0 +1,173 @@+-- | The multi-commodity Balance report. This is the simpler balance+-- report because it does not allow for commodities to be converted.++module Penny.Cabin.Balance.MultiCommodity (+ Opts(..),+ defaultOpts,+ defaultParseOpts,+ defaultFormat,+ parseReport,+ defaultReport,+ report+ ) where++import Control.Applicative (Applicative, pure)+import qualified Penny.Cabin.Balance.Util as U+import qualified Penny.Cabin.Scheme as E+import qualified Penny.Lincoln as L+import qualified Penny.Liberty as Ly+import qualified Data.Either as Ei+import qualified Data.Map as M+import qualified Penny.Cabin.Options as CO+import Data.Monoid (mappend, mempty)+import qualified Data.Text as X+import qualified Data.Tree as E+import qualified Penny.Cabin.Balance.MultiCommodity.Chunker as K+import qualified Penny.Cabin.Balance.MultiCommodity.Parser as P+import qualified Penny.Cabin.Interface as I+import qualified Penny.Cabin.Parsers as CP+import qualified System.Console.MultiArg as MA++-- | Options for making the balance report. These are the only options+-- 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+ , showZeroBalances :: CO.ShowZeroBalances+ , order :: L.SubAccount -> L.SubAccount -> Ordering+ }++defaultOpts :: Opts+defaultOpts = Opts+ { balanceFormat = defaultFormat+ , showZeroBalances = CO.ShowZeroBalances True+ , order = compare+ }++defaultParseOpts :: P.ParseOpts+defaultParseOpts = P.ParseOpts+ { P.showZeroBalances = CO.ShowZeroBalances False+ , P.order = CP.Ascending+ , P.needsHelp = False+ }++fromParseOpts ::+ (L.Commodity -> L.Qty -> X.Text)+ -> P.ParseOpts+ -> Opts+fromParseOpts fmt (P.ParseOpts szb o _) = Opts fmt szb o'+ where+ o' = case o of+ CP.Ascending -> compare+ CP.Descending -> CO.descending compare++defaultFormat :: a -> L.Qty -> X.Text+defaultFormat _ = X.pack . show++summedSortedBalTree ::+ CO.ShowZeroBalances+ -> (L.SubAccount -> L.SubAccount -> Ordering)+ -> [L.Box a]+ -> (E.Forest (L.SubAccount, L.Balance), L.Balance)+summedSortedBalTree szb o =+ U.sumForest mempty mappend+ . U.sortForest o'+ . U.balances szb+ where+ o' x y = o (fst x) (fst y)++rows ::+ (E.Forest (L.SubAccount, L.Balance), L.Balance)+ -> [K.Row]+rows (o, b) = first:rest+ where+ first = K.Row 0 (X.pack "Total") (M.assocs . L.unBalance $ b)+ rest = map row . concatMap E.flatten . map U.labelLevels $ o+ row (l, (s, ib)) =+ K.Row l (L.text s) (M.assocs . L.unBalance $ ib)++-- | This report is what to use if you already have your options (that+-- is, you are not parsing them in from the command line.)+report :: Opts -> [L.Box a] -> [E.PreChunk]+report (Opts bf szb o) =+ K.rowsToChunks bf+ . rows+ . summedSortedBalTree szb o++-- | 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+ -- ^ Default options for the report. These can be overriden on the+ -- command line.++ -> I.Report+parseReport fmt o rt = (help o, makeMode)+ where+ makeMode _ _ fsf = MA.Mode+ { MA.mName = "balance"+ , MA.mIntersperse = MA.Intersperse+ , MA.mOpts = map (fmap Right) P.allSpecs+ , MA.mPosArgs = Left+ , MA.mProcess = process fmt o rt fsf+ }++process+ :: Applicative f+ => (L.Commodity -> L.Qty -> X.Text)+ -> P.ParseOpts+ -> a+ -> ([L.Transaction] -> [L.Box Ly.LibertyMeta])+ -> [Either String (P.ParseOpts -> P.ParseOpts)]+ -> f (Either I.HelpStr I.ArgsAndReport)+process fmt o _ fsf ls =+ let (posArgs, fns) = Ei.partitionEithers ls+ mkParsedOpts = foldl (flip (.)) id fns+ os' = mkParsedOpts o+ mcOpts = fromParseOpts fmt os'+ pr txns _ = return $ report mcOpts (fsf txns)+ in pure $ if P.needsHelp os'+ then Left $ help o+ else Right (posArgs, pr)+++-- | The MultiCommodity report, with default options.+defaultReport :: I.Report+defaultReport = parseReport defaultFormat defaultParseOpts++------------------------------------------------------------+-- ## Help+------------------------------------------------------------+ifDefault :: Bool -> String+ifDefault b = if b then " (default)" else ""++help :: P.ParseOpts -> String+help o = unlines+ [ "balance"+ , " Show account balances. Accepts ONLY the following options:"+ , ""+ , "--show-zero-balances"+ , " Show balances that are zero"+ ++ ifDefault (CO.unShowZeroBalances . P.showZeroBalances $ o)+ , "--hide-zero-balances"+ , " Hide balances that are zero"+ ++ ifDefault ( not . CO.unShowZeroBalances+ . P.showZeroBalances $ o)+ , ""+ , "--ascending"+ , " Sort in ascending order by account name"+ ++ ifDefault (P.order o == CP.Ascending)++ , "--descending"+ , " Sort in descending order by account name"+ ++ ifDefault (P.order o == CP.Descending)++ , ""+ , "--help, -h"+ , " Show this help and exit"+ ]+
+ Penny/Cabin/Balance/MultiCommodity/Chunker.hs view
@@ -0,0 +1,212 @@+-- | Creates the output Chunks for the Balance report for both+-- multi-commodity reports.++module Penny.Cabin.Balance.MultiCommodity.Chunker (+ Row(..),+ rowsToChunks+ ) where+++import Control.Applicative+ (Applicative (pure), (<$>), (<*>))+import qualified Penny.Cabin.Chunk as Chunk+import qualified Penny.Cabin.Meta as Meta+import qualified Penny.Cabin.Row as R+import qualified Penny.Cabin.Scheme as E+import qualified Penny.Lincoln as L+import qualified Data.Foldable as Fdbl+import qualified Data.Text as X++type IsEven = Bool++data Columns a = Columns {+ acct :: a+ , drCr :: a+ , commodity :: a+ , quantity :: a+ } deriving Show++instance Functor Columns where+ fmap f c = Columns {+ acct = f (acct c)+ , drCr = f (drCr c)+ , commodity = f (commodity c)+ , quantity = f (quantity c)+ }++instance Applicative Columns where+ pure a = Columns a a a a+ fn <*> fa = Columns {+ acct = (acct fn) (acct fa)+ , drCr = (drCr fn) (drCr fa)+ , commodity = (commodity fn) (commodity fa)+ , quantity = (quantity fn) (quantity fa)+ }++data PreSpec = PreSpec {+ _justification :: R.Justification+ , _padSpec :: (E.Label, E.EvenOdd)+ , bits :: [E.PreChunk] }++-- | When given a list of columns, determine the widest row in each+-- column.+maxWidths :: [Columns PreSpec] -> Columns R.Width+maxWidths = Fdbl.foldl' maxWidthPerColumn (pure (R.Width 0))++-- | Applied to a Columns of PreSpec and a Colums of widths, return a+-- Columns that has the wider of the two values.+maxWidthPerColumn ::+ Columns R.Width+ -> Columns PreSpec+ -> Columns R.Width+maxWidthPerColumn w p = f <$> w <*> p where+ f old new = max old ( safeMaximum (R.Width 0)+ . map E.width . bits $ new)+ safeMaximum d ls = if null ls then d else maximum ls++-- | Changes a single set of Columns to a set of ColumnSpec of the+-- given width.+preSpecToSpec ::+ Columns R.Width+ -> Columns PreSpec+ -> Columns R.ColumnSpec+preSpecToSpec ws p = f <$> ws <*> p where+ f width (PreSpec j ps bs) = R.ColumnSpec j width ps bs++resizeColumnsInList :: [Columns PreSpec] -> [Columns R.ColumnSpec]+resizeColumnsInList cs = map (preSpecToSpec w) cs where+ w = maxWidths cs+++-- Step 9+widthSpacerAcct :: Int+widthSpacerAcct = 4++widthSpacerDrCr :: Int+widthSpacerDrCr = 1++widthSpacerCommodity :: Int+widthSpacerCommodity = 1++colsToBits ::+ IsEven+ -> Columns R.ColumnSpec+ -> [E.PreChunk]+colsToBits isEven (Columns a dc c q) = let+ fillSpec = if isEven+ then (E.Other, E.Even)+ else (E.Other, E.Odd)+ spacer w = R.ColumnSpec j (Chunk.Width w) fillSpec []+ j = R.LeftJustify+ cs = a+ : spacer widthSpacerAcct+ : dc+ : spacer widthSpacerDrCr+ : c+ : spacer widthSpacerCommodity+ : q+ : []+ in R.row cs++colsListToBits+ :: [Columns R.ColumnSpec]+ -> [[E.PreChunk]]+colsListToBits = zipWith f bools where+ f b c = colsToBits b c+ bools = iterate not True++preSpecsToBits+ :: [Columns PreSpec]+ -> [E.PreChunk]+preSpecsToBits =+ concat+ . colsListToBits+ . resizeColumnsInList++-- | Displays a single account in a Balance report. In a+-- single-commodity report, this account will only be one screen line+-- long. In a multi-commodity report, it might be multiple lines long,+-- with one screen line for each commodity.+data Row = Row+ { indentation :: Int+ -- ^ Indent the account name by this many levels (not by this many+ -- spaces; this number is multiplied by another number in the+ -- Chunker source to arrive at the final indentation amount)++ , accountTxt :: X.Text+ -- ^ Text for the name of the account++ , balances :: [(L.Commodity, L.BottomLine)]+ -- ^ Commodity balances. If this list is empty, dashes are+ -- displayed for the DrCr, Commodity, and Qty.+ }++rowsToChunks ::+ (L.Commodity -> L.Qty -> X.Text)+ -- ^ How to format a balance to allow for digit grouping+ -> [Row]+ -> [E.PreChunk]+rowsToChunks fmt =+ preSpecsToBits+ . rowsToColumns fmt++rowsToColumns ::+ (L.Commodity -> L.Qty -> X.Text)+ -- ^ How to format a balance to allow for digit grouping++ -> [Row]+ -> [Columns PreSpec]+rowsToColumns fmt rs = map (mkColumn fmt) pairs+ where+ pairs = Meta.visibleNums (,) rs+++mkColumn ::+ (L.Commodity -> L.Qty -> X.Text)+ -> (Meta.VisibleNum, Row)+ -> Columns PreSpec+mkColumn fmt (vn, (Row i acctTxt bs)) = Columns ca cd cc cq+ where+ lbl = E.Other+ eo = E.fromVisibleNum vn+ ca = PreSpec R.LeftJustify (lbl, eo) [E.PreChunk lbl eo txt]+ where+ txt = X.append indents acctTxt+ indents = X.replicate (indentAmount * max 0 i)+ (X.singleton ' ')+ cd = PreSpec R.LeftJustify (lbl, eo) cksDrCr+ cc = PreSpec R.RightJustify (lbl, eo) cksCmdty+ cq = PreSpec R.LeftJustify (lbl, eo) cksQty+ (cksDrCr, cksCmdty, cksQty) =+ if null bs+ then balanceChunksEmpty eo+ else+ let balChks = map (balanceChunks fmt eo) bs+ cDrCr = map (\(a, _, _) -> a) balChks+ cCmdty = map (\(_, a, _) -> a) balChks+ cQty = map (\(_, _, a) -> a) balChks+ in (cDrCr, cCmdty, cQty)+++balanceChunksEmpty+ :: E.EvenOdd+ -> ([E.PreChunk], [E.PreChunk], [E.PreChunk])+balanceChunksEmpty eo = (dash, dash, dash)+ where+ dash = [E.PreChunk E.Zero eo (X.pack "--")]++balanceChunks+ :: (L.Commodity -> L.Qty -> X.Text)+ -> E.EvenOdd+ -> (L.Commodity, L.BottomLine)+ -> (E.PreChunk, E.PreChunk, E.PreChunk)+balanceChunks fmt eo (cty, bl) = (chkDc, chkCt, chkQt)+ where+ chkDc = E.bottomLineToDrCr bl eo+ chkCt = E.bottomLineToCmdty eo (cty, bl)+ chkQt = E.bottomLineToQty fmt eo (cty, bl)+++indentAmount :: Int+indentAmount = 2+
+ Penny/Cabin/Balance/MultiCommodity/Parser.hs view
@@ -0,0 +1,35 @@+module Penny.Cabin.Balance.MultiCommodity.Parser (+ ParseOpts(..)+ , allSpecs+ ) where++import qualified Penny.Cabin.Options as CO+import qualified Penny.Cabin.Parsers as P+import qualified System.Console.MultiArg as MA++-- | Options for the Balance report that have been parsed from the+-- command line.+data ParseOpts = ParseOpts+ { showZeroBalances :: CO.ShowZeroBalances+ , order :: P.SortOrder+ , needsHelp :: Bool+ }+++parseHelp :: MA.OptSpec (ParseOpts -> ParseOpts)+parseHelp = fmap f P.help+ where+ f _ o = o { needsHelp = True }++zeroBalances :: MA.OptSpec (ParseOpts -> ParseOpts)+zeroBalances = fmap toResult P.zeroBalances+ where+ toResult szb o = o { showZeroBalances = szb }++parseOrder :: MA.OptSpec (ParseOpts -> ParseOpts)+parseOrder = fmap toResult P.order+ where+ toResult x o = o { order = x }++allSpecs :: [MA.OptSpec (ParseOpts -> ParseOpts)]+allSpecs = [parseHelp, zeroBalances, parseOrder]
− Penny/Cabin/Balance/Parser.hs
@@ -1,156 +0,0 @@-module Penny.Cabin.Balance.Parser (- Error(..)- , ParseOpts(..)- , parseOptions- ) where--import qualified Data.Text as X-import Control.Applicative ((<|>), many, Applicative, pure)-import Control.Monad ((>=>))-import qualified Control.Monad.Exception.Synchronous as Ex-import qualified Penny.Cabin.Colors as Col-import qualified Penny.Cabin.Colors.DarkBackground as DB-import qualified Penny.Cabin.Colors.LightBackground as LB-import qualified Penny.Cabin.Chunk as Chk-import qualified Penny.Cabin.Options as CO-import qualified Penny.Copper.Commodity as CC-import qualified Penny.Copper.DateTime as CD-import qualified Penny.Lincoln as L-import qualified Penny.Shield as S-import System.Console.MultiArg.Prim (Parser)-import qualified System.Console.MultiArg.Combinator as C-import qualified Text.Parsec as Parsec--data ParseOpts = ParseOpts {- drCrColors :: Col.DrCrColors- , baseColors :: Col.BaseColors- , colorPref :: Chk.Colors- , showZeroBalances :: CO.ShowZeroBalances- , convert :: Maybe (L.Commodity, L.DateTime)- }---data Error = BadColorName String- | BadBackground String- | BadCommodity String- | BadDate String- deriving Show---processColorArg ::- S.Runtime- -> String- -> Maybe Chk.Colors-processColorArg rt x- | x == "yes" = return Chk.Colors8- | x == "no" = return Chk.Colors0- | x == "auto" = return (CO.maxCapableColors rt)- | x == "256" = return Chk.Colors256- | otherwise = Nothing--parseOpt :: [String] -> [Char] -> C.ArgSpec a -> Parser a-parseOpt ss cs a = C.parseOption [C.OptSpec ss cs a]--color :: Parser (S.Runtime- -> ParseOpts- -> Ex.Exceptional Error ParseOpts)-color = parseOpt ["color"] "" (C.OneArg f)- where- f a1 rt op = case processColorArg rt a1 of- Nothing -> Ex.throw . BadColorName $ a1- Just c -> return (op { colorPref = c })--processBackgroundArg ::- String- -> Maybe (Col.DrCrColors, Col.BaseColors)-processBackgroundArg x- | x == "light" = return (LB.drCrColors, LB.baseColors)- | x == "dark" = return (DB.drCrColors, DB.baseColors)- | otherwise = Nothing---background :: Parser (ParseOpts -> Ex.Exceptional Error ParseOpts)-background = parseOpt ["background"] "" (C.OneArg f)- where- f a1 op = case processBackgroundArg a1 of- Nothing -> Ex.throw . BadBackground $ a1- Just (dc, base) ->- return op { drCrColors = dc- , baseColors = base }--parseShowZeroBalances :: Parser (ParseOpts -> ParseOpts)-parseShowZeroBalances = parseOpt opt "" (C.NoArg f)- where- opt = ["show-zero-balances"] - f op =- op {showZeroBalances = CO.ShowZeroBalances True }--hideZeroBalances :: Parser (ParseOpts -> ParseOpts)-hideZeroBalances = parseOpt ["hide-zero-balances"] "" (C.NoArg f)- where- f op =- op {showZeroBalances = CO.ShowZeroBalances False }--convertLong ::- Parser (CD.DefaultTimeZone- -> ParseOpts- -> Ex.Exceptional Error ParseOpts)-convertLong = parseOpt ["convert"] "" (C.TwoArg f)- where- f a1 a2 dtz op = do- cty <- case Parsec.parse CC.lvl1Cmdty "" (X.pack a1) of- Left _ -> Ex.throw . BadCommodity $ a1- Right g -> return g- let parseDate = CD.dateTime dtz- dt <- case Parsec.parse parseDate "" (X.pack a2) of- Left _ -> Ex.throw . BadDate $ a2- Right g -> return g- let op' = op { convert = Just (cty, dt) }- return op'--convertShort :: Parser (S.Runtime- -> ParseOpts- -> Ex.Exceptional Error ParseOpts)-convertShort = parseOpt [] ['c'] (C.OneArg f)- where- f a1 rt op = do- cty <- case Parsec.parse CC.lvl1Cmdty "" (X.pack a1) of- Left _ -> Ex.throw . BadCommodity $ a1- Right g -> return g- let dt = S.currentTime rt- op' = op { convert = Just (cty, dt) }- return op'- --parseOptions :: Parser (S.Runtime- -> CD.DefaultTimeZone- -> ParseOpts- -> Ex.Exceptional Error ParseOpts)-parseOptions = do- fns <- many parseOption- let f rt dtz o1 =- let fns' = map (\fn -> fn rt dtz) fns- in foldl (>=>) return fns' o1- return f--parseOption :: Parser (S.Runtime- -> CD.DefaultTimeZone- -> ParseOpts- -> Ex.Exceptional Error ParseOpts)-parseOption =- (do { f <- color; return (\rt _ o -> f rt o )})- <|> wrap background- <|> wrap (impurify parseShowZeroBalances)- <|> wrap (impurify hideZeroBalances)- <|> (do { f <- convertLong; return (\_ dtz o -> f dtz o )})- <|> (do { f <- convertShort; return (\rt _ o -> f rt o )})- where- wrap p = do- f <- p- return (\_ _ op -> f op)--impurify ::- (Applicative m, Functor f)- => f (a -> a)- -> f (a -> m a)-impurify = fmap (\f -> pure . f)
− Penny/Cabin/Balance/Tree.hs
@@ -1,468 +0,0 @@--- | Takes postings and places them into a tree for further--- processing.------ Steps:------ * 1. [LT.PostingInfo] -> [PriceConverted]------ * 2. [PriceConverted] -> FlatMap------ * 3. FlatMap -> RawBals------ * 4. RawBals -> (SummedBals, TotalBal)------ * 5. (SummedBals, TotalBal) -> (SummedWithIsEven, TotalBal)------ * 6. (SummedWithIsEven, TotalBal) -> (PreSpecMap, TotalBal)------ * 7. (PreSpecMap, TotalBal) -> [Columns PreSpec]------ * 8. [Columns PreSpec] -> [Columns R.ColumnSpec] (strict)------ * 9. [Columns R.ColumnSpec] -> [[Chunk.Bit]] (lazy)-module Penny.Cabin.Balance.Tree (- report- , TreeOpts(..)- , PriceConverteds- , nullConvert- , converter- ) where--import Control.Applicative(Applicative(pure, (<*>)), (<$>))-import qualified Control.Monad.Exception.Synchronous as Ex-import qualified Control.Monad.Trans.State as St-import qualified Penny.Cabin.Row as R-import qualified Data.Foldable as Fdbl-import qualified Data.Functor.Identity as Id-import qualified Data.Map as M-import qualified Data.Monoid as Monoid-import qualified Penny.Lincoln.NestedMap as NM-import qualified Data.Text as X-import qualified Data.Traversable as Tr-import qualified Penny.Cabin.Options as CO-import qualified Penny.Cabin.Chunk as Chunk-import qualified Penny.Cabin.Colors as C-import qualified Penny.Liberty as Ly-import qualified Penny.Lincoln as L-import qualified Penny.Lincoln.Queries as Q-import qualified Penny.Lincoln.Balance as Bal-import qualified Data.Semigroup as S---- Step 1. Convert prices.--data PriceConverted = PriceConverted {- pcEntry :: L.Entry- , pcAccount :: L.Account }--newtype PriceConverteds =- PriceConverteds { unPriceConverteds :: [PriceConverted] }--convertOne ::- L.PriceDb- -> (L.Commodity, L.DateTime)- -> L.Entry- -> Ex.Exceptional X.Text L.Entry-convertOne db (cty, dt) en@(L.Entry dc am@(L.Amount _ fr)) =- if fr == cty- then return en- else do- let to = L.To cty- am' <- case L.convert db dt to am of- Ex.Exception e -> Ex.throw (convertError cty am e)- Ex.Success g -> return g- return (L.Entry dc am')--convertError ::- L.Commodity- -> L.Amount- -> L.PriceDbError- -> X.Text-convertError to (L.Amount _ fr) e =- let fromErr = L.text (L.Delimited (X.singleton ':')- (Fdbl.toList . L.unCommodity $ fr))- toErr = L.text (L.Delimited (X.singleton ':')- (Fdbl.toList . L.unCommodity $ to))- in case e of- L.FromNotFound ->- X.pack "no data to convert from commodity "- `X.append` fromErr- L.ToNotFound ->- X.pack "no data to convert to commodity "- `X.append` toErr- L.CpuNotFound ->- X.pack "no data to convert from commodity "- `X.append` fromErr- `X.append` (X.pack " to commodity ")- `X.append` toErr- `X.append` (X.pack " at given date and time")- --buildDb :: [L.PricePoint] -> L.PriceDb-buildDb = foldl f L.emptyDb where- f db pb = L.addPrice db pb---- | Makes PriceConverteds where there is no target commodity.-nullConvert :: [L.Box Ly.LibertyMeta] -> PriceConverteds-nullConvert =- let f b = PriceConverted en ac- where- p = L.boxPostFam b- en = Q.entry p- ac = Q.account p- in PriceConverteds . map f---- | Makes PriceConverteds where commodities will have to be changed--- to a target commodity. This will fail if necessary price data is--- lacking.-converter ::- (L.Commodity, L.DateTime)- -> [L.PricePoint]- -> [L.Box Ly.LibertyMeta]- -> Ex.Exceptional X.Text PriceConverteds-converter conv pbs bs =- let f b = let p = L.boxPostFam b- en = Q.entry p- ac = Q.account p- in do- en' <- convertOne db conv en- return (PriceConverted en' ac)- db = buildDb pbs- in fmap PriceConverteds $ mapM f bs----- Step 2. This puts all the PriceConverteds into a flat map, where--- the key is the full account name and the value is the balance. If--- the user desired, zero balances are eliminated from the flat map.-newtype FlatMap = FlatMap { _unFlatMap :: M.Map L.Account Bal.Balance }--toFlatMap :: TreeOpts -> PriceConverteds -> FlatMap-toFlatMap o = FlatMap . foldr f M.empty . unPriceConverteds where- remove = not . CO.unShowZeroBalances . showZeroBalances $ o- f pc m =- let a = pcAccount pc- bal = Bal.entryToBalance . pcEntry $ pc- in case M.lookup a m of- Nothing -> M.insert a bal m- Just oldBal ->- let added = Bal.addBalances oldBal bal- newBal = if remove- then Bal.removeZeroCommodities added- else Just added- in case newBal of- Nothing -> M.delete a m- Just b' -> M.insert a b' m---- Step 3-newtype RawBal = RawBal { unRawBal :: S.Option Bal.Balance }-instance Monoid.Monoid RawBal where- mappend (RawBal b1) (RawBal b2) = RawBal $ b1 `Monoid.mappend` b2- mempty = RawBal Monoid.mempty--type RawBals = NM.NestedMap L.SubAccountName RawBal---- | Inserts a pair from the FlatMap into the Balances NestedMap.-insertBalance ::- L.Account- -> Bal.Balance- -> RawBals- -> RawBals-insertBalance a b rbs = let- rb = RawBal . S.Option . Just $ b- subs = Fdbl.toList . L.unAccount $ a- in NM.insert rbs subs rb- -rawBalances :: FlatMap -> RawBals-rawBalances (FlatMap m) = M.foldrWithKey insertBalance NM.empty m---- Step 4-newtype SummedBal =- SummedBal { unSummedBal :: S.Option Bal.Balance }-instance Monoid.Monoid SummedBal where- mappend (SummedBal b1) (SummedBal b2) =- SummedBal $ b1 `Monoid.mappend` b2- mempty = SummedBal Monoid.mempty-type TotalBal = SummedBal-type SummedBals = NM.NestedMap L.SubAccountName SummedBal--sumBalances :: RawBals -> (SummedBals, TotalBal)-sumBalances rb = (sb, (SummedBal . unRawBal $ tb)) where- (tb, rawBals) = NM.cumulativeTotal rb- sb = fmap (SummedBal . unRawBal) rawBals---- Step 5-type SummedWithIsEven = NM.NestedMap L.SubAccountName (SummedBal, Bool)--makeSummedWithIsEven ::- (SummedBals, TotalBal)- -> (SummedWithIsEven, TotalBal)-makeSummedWithIsEven (sb, tb) = (swie, tb) where- swie = St.evalState (Tr.mapM f sb) False- f lbl = do- st <- St.get- St.put (not st)- return (lbl, st)---- Step 6-data Columns a = Columns {- account :: a- , drCr :: a- , commodity :: a- , quantity :: a- } deriving Show--instance Functor Columns where- fmap f c = Columns {- account = f (account c)- , drCr = f (drCr c)- , commodity = f (commodity c)- , quantity = f (quantity c)- }--instance Applicative Columns where- pure a = Columns a a a a- fn <*> fa = Columns {- account = (account fn) (account fa)- , drCr = (drCr fn) (drCr fa)- , commodity = (commodity fn) (commodity fa)- , quantity = (quantity fn) (quantity fa)- }--data PreSpec = PreSpec {- _justification :: R.Justification- , _padSpec :: Chunk.TextSpec- , bits :: [Chunk.Chunk] }--type PreSpecMap = NM.NestedMap L.SubAccountName (Columns PreSpec)---- | Takes a list of triples from bottomLineChunks and creates three--- Cells, one each for DrCr, Commodity, and Qty.-bottomLineBalCells ::- Chunk.TextSpec -- ^ Fill colors- -> [(Chunk.Chunk, Chunk.Chunk, Chunk.Chunk)]- -> (PreSpec, PreSpec, PreSpec)-bottomLineBalCells spec ts = (mkSpec dc, mkSpec ct, mkSpec qt) where- mkSpec ls = PreSpec R.LeftJustify spec ls- (dc, ct, qt) = foldr f ([], [], []) ts- f (da, ca, qa) (d, c, q) = (da:d, ca:c, qa:q)---type IsEven = Bool---- | Returns a triple (x, y, z), where x is the DrCr chunk, y is the--- commodity chunk, and z is the qty chunk.-bottomLineBalChunks ::- TreeOpts- -> IsEven- -> (L.Commodity, Bal.BottomLine)- -> (Chunk.Chunk, Chunk.Chunk, Chunk.Chunk)-bottomLineBalChunks os isEven (comm, bl) = (dc, cty, qty) where- dc = Chunk.chunk ts dcTxt- cty = Chunk.chunk ts ctyTxt- qty = Chunk.chunk ts qtyTxt- ctyTxt = L.text (L.Delimited (X.singleton ':') (L.textList comm))- (ts, dcTxt, qtyTxt) = case bl of- Bal.Zero -> let- getTs = if isEven then C.evenZero else C.oddZero- dcT = X.pack "--"- qtyT = dcT- in (getTs . drCrColors $ os, dcT, qtyT)- Bal.NonZero clm -> let- (getTs, dcT) = case Bal.drCr clm of- L.Debit ->- (if isEven then C.evenDebit else C.oddDebit,- X.pack "Dr")- L.Credit ->- (if isEven then C.evenCredit else C.oddCredit,- X.pack "Cr")- qTxt = (balanceFormat os) bl- in (getTs . drCrColors $ os, dcT, qTxt)---fillTextSpec ::- TreeOpts- -> IsEven- -> Chunk.TextSpec-fillTextSpec os isEven = let- getTs = if isEven then C.evenColors else C.oddColors- in getTs . baseColors $ os- --bottomLineCells ::- TreeOpts- -> IsEven- -> SummedBal- -> (PreSpec, PreSpec, PreSpec)-bottomLineCells os isEven mayBal = let- fill = fillTextSpec os isEven- tsZero = if isEven- then C.evenZero . drCrColors $ os- else C.oddZero . drCrColors $ os- zeroSpec =- PreSpec R.LeftJustify- tsZero [Chunk.chunk tsZero (X.pack "--")]- zeroSpecs = (zeroSpec, zeroSpec, zeroSpec)- in case S.getOption . unSummedBal $ mayBal of- Nothing -> zeroSpecs- Just bal -> bottomLineBalCells fill- . map (bottomLineBalChunks os isEven)- . M.assocs- . Bal.unBalance- $ bal--padding :: Int-padding = 2--accountPreSpec ::- TreeOpts- -> IsEven- -> Int- -> L.SubAccountName- -> PreSpec-accountPreSpec os isEven lvl acct = PreSpec j ts [bit] where- j = R.LeftJustify- ts = if isEven- then C.evenColors . baseColors $ os- else C.oddColors . baseColors $ os- bit = Chunk.chunk ts txt where- txt = pad `X.append` (L.text acct)- pad = X.replicate (padding * lvl) (X.singleton ' ')---makePreSpec ::- TreeOpts- -> [(L.SubAccountName, (SummedBal, Bool))]- -> L.SubAccountName- -> (SummedBal, Bool)- -> Columns PreSpec-makePreSpec os ps a (mayBal, isEven) = Columns act dc com qt where- lvl = length ps + 1- act = accountPreSpec os isEven lvl a- (dc, com, qt) = bottomLineCells os isEven mayBal--traverser ::- TreeOpts- -> [(L.SubAccountName, (SummedBal, Bool))]- -> L.SubAccountName- -> (SummedBal, Bool)- -> a- -> Id.Identity (Maybe (Columns PreSpec))-traverser os hist a mayBal _ =- return (Just $ makePreSpec os hist a mayBal)--makePreSpecMap ::- TreeOpts- -> (SummedWithIsEven, TotalBal)- -> (PreSpecMap, TotalBal)-makePreSpecMap os (sb, tb) = (cim, tb) where- cim = Id.runIdentity (NM.traverseWithTrail (traverser os) sb)---- Step 7-makeTotalCells ::- TreeOpts- -> SummedBal- -> Columns PreSpec-makeTotalCells os mayBal = Columns act dc com qt where- act = accountPreSpec os True 0 tot- tot = L.SubAccountName $ L.TextNonEmpty 'T' (X.pack "otal")- (dc, com, qt) = bottomLineCells os True mayBal--makeColumnList :: TreeOpts -> (PreSpecMap, TotalBal) -> [Columns PreSpec]-makeColumnList os (cim, tb) = totCols : restCols where- totCols = makeTotalCells os tb- restCols = Fdbl.toList cim----- Step 8---- | When given a list of columns, determine the widest row in each--- column.-maxWidths :: [Columns PreSpec] -> Columns R.Width-maxWidths = Fdbl.foldl' maxWidthPerColumn (pure (R.Width 0))---- | Applied to a Columns of PreSpec and a Colums of widths, return a--- Columns that has the wider of the two values.-maxWidthPerColumn ::- Columns R.Width- -> Columns PreSpec- -> Columns R.Width-maxWidthPerColumn w p = f <$> w <*> p where- f old new = max old (maximum . map Chunk.chunkWidth . bits $ new)- --- | Changes a single set of Columns to a set of ColumnSpec of the--- given width.-preSpecToSpec ::- Columns R.Width- -> Columns PreSpec- -> Columns R.ColumnSpec-preSpecToSpec ws p = f <$> ws <*> p where- f width (PreSpec j ps bs) = R.ColumnSpec j width ps bs--resizeColumnsInList :: [Columns PreSpec] -> [Columns R.ColumnSpec]-resizeColumnsInList cs = map (preSpecToSpec w) cs where- w = maxWidths cs----- Step 9-widthSpacerAcct :: Int-widthSpacerAcct = 4--widthSpacerDrCr :: Int-widthSpacerDrCr = 1--widthSpacerCommodity :: Int-widthSpacerCommodity = 1--colsToBits ::- TreeOpts- -> IsEven- -> Columns R.ColumnSpec- -> [Chunk.Chunk]-colsToBits os isEven (Columns a dc c q) = let- fillSpec = if isEven- then C.evenColors . baseColors $ os- else C.oddColors . baseColors $ os- spacer w = R.ColumnSpec j (Chunk.Width w) fillSpec []- j = R.LeftJustify- cs = a- : spacer widthSpacerAcct- : dc- : spacer widthSpacerDrCr- : c- : spacer widthSpacerCommodity- : q- : []- in R.row cs--colsListToBits ::- TreeOpts- -> [Columns R.ColumnSpec]- -> [[Chunk.Chunk]]-colsListToBits os = zipWith f bools where- f b c = colsToBits os b c- bools = iterate not True----- Options-data TreeOpts = TreeOpts {- drCrColors :: C.DrCrColors- , baseColors :: C.BaseColors- , balanceFormat :: L.BottomLine -> X.Text- , showZeroBalances :: CO.ShowZeroBalances- }---- Tie it all together--report ::- TreeOpts- -> PriceConverteds- -> [[Chunk.Chunk]]-report os =- colsListToBits os- . resizeColumnsInList- . makeColumnList os- . makePreSpecMap os- . makeSummedWithIsEven- . sumBalances- . rawBalances- . toFlatMap os-
+ Penny/Cabin/Balance/Util.hs view
@@ -0,0 +1,228 @@+-- | Grab bag of utility functions.++module Penny.Cabin.Balance.Util+ ( tieredForest+ , tieredPostings+ , filterForest+ , balances+ , flatten+ , treeWithParents+ , forestWithParents+ , sumForest+ , sumTree+ , boxesBalance+ , labelLevels+ , sortForest+ , sortTree+ , lastMode+ ) where++import qualified Penny.Cabin.Options as CO+import qualified Penny.Lincoln as L+import qualified Penny.Lincoln.NestedMap as NM+import qualified Data.Foldable as Fdbl+import qualified Data.Map as M+import Data.Ord (comparing)+import Data.List (sortBy, maximumBy, groupBy)+import Data.Monoid (mconcat, Monoid)+import Data.Maybe (mapMaybe)+import qualified Data.Tree as T+import qualified Penny.Lincoln.Queries as Q++-- | Constructs a forest sorted into tiers based on lists of keys that+-- are extracted from the elements.+tieredForest ::+ Ord k+ => (a -> [k])+ -- ^ Extracts a key from the elements we are putting in the tree. If+ -- this function returns an empty list for any element, the element+ -- will not appear in the tiered forest.+ -> [a]+ -> T.Forest (k, [a])+tieredForest getKeys ls = fmap (fmap revSnd) . NM.toForest $ nm+ where+ revSnd (a, xs) = (a, reverse xs)+ nm = foldr f NM.empty ls+ f a m = NM.relabel m ps+ where+ ps = case getKeys a of+ [] -> []+ ks ->+ let mkInitPair k = (k, maybe [] id)+ mkLastPair k = (k, maybe [a] (a:))+ in (map mkInitPair . init $ ks)+ ++ [(mkLastPair (last ks))]++-- | Takes a list of postings and puts them into a Forest. Each level+-- of each of the trees corresponds to a sub account. The label of the+-- node tells you the sub account name and gives you a list of the+-- postings at that level.+tieredPostings :: [L.Box a] -> T.Forest (L.SubAccount, [L.Box a])+tieredPostings = tieredForest e+ where+ e = Fdbl.toList . L.unAccount . Q.account . L.boxPostFam++-- | Keeps only Trees that match a given condition. First examines+-- child trees to determine whether they should be retained. If a+-- child tree is retained, does not delete the parent tree.+filterForest :: (a -> Bool) -> T.Forest a -> T.Forest a+filterForest f = mapMaybe pruneTree+ where+ pruneTree (T.Node a fs) =+ case filterForest f fs of+ [] -> if not (f a) then Nothing else Just (T.Node a [])+ cs -> Just (T.Node a cs)+++-- | Puts all Boxes into a Tree and sums the balances. Removes+-- accounts that have empty balances if requested. Does NOT sum+-- balances from the bottom up.+balances ::+ CO.ShowZeroBalances+ -> [L.Box a]+ -> T.Forest (L.SubAccount, L.Balance)+balances (CO.ShowZeroBalances szb) =+ remover+ . map (fmap (mapSnd boxesBalance))+ . tieredPostings+ where+ remover =+ if szb+ then id+ else filterForest (not . M.null . L.unBalance . snd)+ . map (fmap (mapSnd L.removeZeroCommodities))+++-- | Takes a tree of Balances (like what is produced by the 'balances'+-- function) and produces a flat list of accounts with the balance of+-- each account.+flatten+ :: T.Forest (L.SubAccount, L.Balance)+ -> [(L.Account, L.Balance)]+flatten =+ concatMap T.flatten+ . map (fmap toPair) . forestWithParents+ where+ toPair ((s, b), ls) =+ case reverse . map fst $ ls of+ [] -> (L.Account [s], b)+ s1:sr -> (L.Account (s1 : (sr ++ [s])), b)++-- | Takes a Tree and returns a Tree where each node has information+-- about its parent Nodes. The list of parent nodes has the most+-- immediate parent first and the most distant parent last.+treeWithParents :: T.Tree a -> T.Tree (a, [a])+treeWithParents = treeWithParentsR []++-- | Given a list of the parents seen so far, return a Tree where each+-- node contains information about its parents.+treeWithParentsR :: [a] -> T.Tree a -> T.Tree (a, [a])+treeWithParentsR ls (T.Node n cs) = T.Node (n, ls) cs'+ where+ cs' = map (treeWithParentsR (n:ls)) cs++-- | Takes a Forest and returns a Forest where each node has+-- information about its parent Nodes.+forestWithParents :: T.Forest a -> T.Forest (a, [a])+forestWithParents = map (treeWithParentsR [])++-- | Sums a forest from the bottom up. Returns a pair, where the first+-- element is the forest, but with the second element of each node+-- replaced with the sum of that node and all its children. The second+-- element is the sum of all the second elements in the forest.+sumForest ::+ s+ -- ^ Zero++ -> (s -> s -> s)+ -- ^ Combiner++ -> T.Forest (a, s)+ -> (T.Forest (a, s), s)+sumForest z f ts = (ts', s)+ where+ ts' = map (sumTree z f) ts+ s = foldr f z . map (snd . T.rootLabel) $ ts'++-- | Sums a tree from the bottom up.+sumTree ::+ s+ -- ^ Zero++ -> (s -> s -> s)+ -- ^ Combiner++ -> T.Tree (a, s)+ -> T.Tree (a, s)+sumTree z f (T.Node (a, s) cs) = T.Node (a, f s cSum) cs'+ where+ (cs', cSum) = sumForest z f cs+++boxesBalance :: [L.Box a] -> L.Balance+boxesBalance = mconcat . map L.entryToBalance . map Q.entry+ . map L.boxPostFam++mapSnd :: (a -> b) -> (f, a) -> (f, b)+mapSnd f (x, a) = (x, f a)++-- | Label each level of a Tree with an integer indicating how deep it+-- is. The top node of the tree is level 0.+labelLevels :: T.Tree a -> T.Tree (Int, a)+labelLevels = go 0+ where+ go l (T.Node x xs) = T.Node (l, x) (map (go (l + 1)) xs)++-- | Sorts each level of a Forest.+sortForest ::+ (a -> a -> Ordering)+ -> T.Forest a+ -> T.Forest a+sortForest o f = sortBy o' (map (sortTree o) f)+ where+ o' x y = o (T.rootLabel x) (T.rootLabel y)++-- | Sorts each level of a Tree.+sortTree ::+ (a -> a -> Ordering)+ -> T.Tree a+ -> T.Tree a+sortTree o (T.Node l f) = T.Node l (sortForest o f)++-- | Like lastModeBy but using Ord.+lastMode :: Ord a => [a] -> Maybe a+lastMode = lastModeBy compare++-- | Finds the mode of a list. Takes the mode that is located last in+-- the list. Returns Nothing if there is no mode (that is, if the list+-- is empty).+lastModeBy ::+ (a -> a -> Ordering)+ -> [a]+ -> Maybe a+lastModeBy o ls =+ case modesBy o' ls' of+ [] -> Nothing+ ms -> Just . fst . maximumBy fx $ ms+ where+ fx = comparing snd+ ls' = zip ls ([0..] :: [Int])+ o' x y = o (fst x) (fst y)++-- | Finds the modes of a list.+modesBy :: (a -> a -> Ordering) -> [a] -> [a]+modesBy o =+ concat+ . longestLists+ . groupBy (\x y -> o x y == EQ)+ . sortBy o+++-- | Returns the longest lists. This function is partial. It is bottom+-- if the argument list is empty. Therefore, do not export this+-- function.+longestLists :: [[a]] -> [[a]]+longestLists as =+ let lengths = map (\ls -> (ls, length ls)) as+ maxLen = maximum . map snd $ lengths+ in map fst . filter (\(_, len) -> len == maxLen) $ lengths
Penny/Cabin/Chunk.hs view
@@ -1,2408 +1,2469 @@--- | Handles colors and special effects for text. This module was--- written using the control sequences documented for xterm in--- ctlseqs.txt, which is included in the xterm source code (on Debian--- GNU/Linux systems, it is at--- \/usr\/share\/doc\/xterm\/ctlseqs.txt.gz). The code in here should also--- work for any terminal which recognizes ISO 6429 escape sequences--- (also known as ANSI escape sequences), but only for 8 colors. This--- module also generates sequences for 256 color xterms; though this--- works fine with xterm, it might not work on other terminals (I--- believe it works for other terminals commonly found on Linux--- systems, such as gnome-terminal and Konsole, but I have not tested--- these terminals as I do not use them.) Perhaps it also works on--- Windows or Mac OS X systems and their terminals, though I have not--- tested them (in particular, Windows could be problematic as not all--- Windows terminals support ISO 6429.)------ In theory there are more portable ways to generate color codes,--- such as through curses (or is it ncurses?) but support for ISO 6429--- is widespread enough that I am prepared to say that if your--- terminal does not support it, too bad; just use the colorless mode.-module Penny.Cabin.Chunk (- -- * Colors- Colors(Colors0, Colors8, Colors256),- Background8,- Background256,- Foreground8,- Foreground256,-- -- * Chunks- Chunk,- chunk,- Width(Width, unWidth),- chunkWidth,- chunksToText,- - -- * Effects- Bold(Bold, unBold),- Underline(Underline, unUnderline),- Flash(Flash, unFlash),- Inverse(Inverse, unInverse),-- -- * Style and TextSpec- - -- | A style is a bundle of attributes that describes text- -- attributes, such as its color and whether it is bold.- StyleCommon (StyleCommon, bold, underline, flash, inverse),- Style8 (Style8, foreground8, background8, common8),- Style256 (Style256, foreground256, background256, common256),- defaultStyleCommon,- defaultStyle8,- defaultStyle256,- - TextSpec (TextSpec, style8, style256),- defaultTextSpec,- - -- * Specific colors- -- * 8 color foreground colors- color8_f_default,- color8_f_black,- color8_f_red,- color8_f_green,- color8_f_yellow,- color8_f_blue,- color8_f_magenta,- color8_f_cyan,- color8_f_white,-- -- ** 8 color background colors- color8_b_default,- color8_b_black,- color8_b_red,- color8_b_green,- color8_b_yellow,- color8_b_blue,- color8_b_magenta,- color8_b_cyan,- color8_b_white,-- -- * 256 color foreground colors- color256_f_default,- color256_f_0,- color256_f_1,- color256_f_2,- color256_f_3,- color256_f_4,- color256_f_5,- color256_f_6,- color256_f_7,- color256_f_8,- color256_f_9,- color256_f_10,- color256_f_11,- color256_f_12,- color256_f_13,- color256_f_14,- color256_f_15,- color256_f_16,- color256_f_17,- color256_f_18,- color256_f_19,- color256_f_20,- color256_f_21,- color256_f_22,- color256_f_23,- color256_f_24,- color256_f_25,- color256_f_26,- color256_f_27,- color256_f_28,- color256_f_29,- color256_f_30,- color256_f_31,- color256_f_32,- color256_f_33,- color256_f_34,- color256_f_35,- color256_f_36,- color256_f_37,- color256_f_38,- color256_f_39,- color256_f_40,- color256_f_41,- color256_f_42,- color256_f_43,- color256_f_44,- color256_f_45,- color256_f_46,- color256_f_47,- color256_f_48,- color256_f_49,- color256_f_50,- color256_f_51,- color256_f_52,- color256_f_53,- color256_f_54,- color256_f_55,- color256_f_56,- color256_f_57,- color256_f_58,- color256_f_59,- color256_f_60,- color256_f_61,- color256_f_62,- color256_f_63,- color256_f_64,- color256_f_65,- color256_f_66,- color256_f_67,- color256_f_68,- color256_f_69,- color256_f_70,- color256_f_71,- color256_f_72,- color256_f_73,- color256_f_74,- color256_f_75,- color256_f_76,- color256_f_77,- color256_f_78,- color256_f_79,- color256_f_80,- color256_f_81,- color256_f_82,- color256_f_83,- color256_f_84,- color256_f_85,- color256_f_86,- color256_f_87,- color256_f_88,- color256_f_89,- color256_f_90,- color256_f_91,- color256_f_92,- color256_f_93,- color256_f_94,- color256_f_95,- color256_f_96,- color256_f_97,- color256_f_98,- color256_f_99,- color256_f_100,- color256_f_101,- color256_f_102,- color256_f_103,- color256_f_104,- color256_f_105,- color256_f_106,- color256_f_107,- color256_f_108,- color256_f_109,- color256_f_110,- color256_f_111,- color256_f_112,- color256_f_113,- color256_f_114,- color256_f_115,- color256_f_116,- color256_f_117,- color256_f_118,- color256_f_119,- color256_f_120,- color256_f_121,- color256_f_122,- color256_f_123,- color256_f_124,- color256_f_125,- color256_f_126,- color256_f_127,- color256_f_128,- color256_f_129,- color256_f_130,- color256_f_131,- color256_f_132,- color256_f_133,- color256_f_134,- color256_f_135,- color256_f_136,- color256_f_137,- color256_f_138,- color256_f_139,- color256_f_140,- color256_f_141,- color256_f_142,- color256_f_143,- color256_f_144,- color256_f_145,- color256_f_146,- color256_f_147,- color256_f_148,- color256_f_149,- color256_f_150,- color256_f_151,- color256_f_152,- color256_f_153,- color256_f_154,- color256_f_155,- color256_f_156,- color256_f_157,- color256_f_158,- color256_f_159,- color256_f_160,- color256_f_161,- color256_f_162,- color256_f_163,- color256_f_164,- color256_f_165,- color256_f_166,- color256_f_167,- color256_f_168,- color256_f_169,- color256_f_170,- color256_f_171,- color256_f_172,- color256_f_173,- color256_f_174,- color256_f_175,- color256_f_176,- color256_f_177,- color256_f_178,- color256_f_179,- color256_f_180,- color256_f_181,- color256_f_182,- color256_f_183,- color256_f_184,- color256_f_185,- color256_f_186,- color256_f_187,- color256_f_188,- color256_f_189,- color256_f_190,- color256_f_191,- color256_f_192,- color256_f_193,- color256_f_194,- color256_f_195,- color256_f_196,- color256_f_197,- color256_f_198,- color256_f_199,- color256_f_200,- color256_f_201,- color256_f_202,- color256_f_203,- color256_f_204,- color256_f_205,- color256_f_206,- color256_f_207,- color256_f_208,- color256_f_209,- color256_f_210,- color256_f_211,- color256_f_212,- color256_f_213,- color256_f_214,- color256_f_215,- color256_f_216,- color256_f_217,- color256_f_218,- color256_f_219,- color256_f_220,- color256_f_221,- color256_f_222,- color256_f_223,- color256_f_224,- color256_f_225,- color256_f_226,- color256_f_227,- color256_f_228,- color256_f_229,- color256_f_230,- color256_f_231,- color256_f_232,- color256_f_233,- color256_f_234,- color256_f_235,- color256_f_236,- color256_f_237,- color256_f_238,- color256_f_239,- color256_f_240,- color256_f_241,- color256_f_242,- color256_f_243,- color256_f_244,- color256_f_245,- color256_f_246,- color256_f_247,- color256_f_248,- color256_f_249,- color256_f_250,- color256_f_251,- color256_f_252,- color256_f_253,- color256_f_254,- color256_f_255,-- -- ** 256 color background colors- color256_b_default,- color256_b_0,- color256_b_1,- color256_b_2,- color256_b_3,- color256_b_4,- color256_b_5,- color256_b_6,- color256_b_7,- color256_b_8,- color256_b_9,- color256_b_10,- color256_b_11,- color256_b_12,- color256_b_13,- color256_b_14,- color256_b_15,- color256_b_16,- color256_b_17,- color256_b_18,- color256_b_19,- color256_b_20,- color256_b_21,- color256_b_22,- color256_b_23,- color256_b_24,- color256_b_25,- color256_b_26,- color256_b_27,- color256_b_28,- color256_b_29,- color256_b_30,- color256_b_31,- color256_b_32,- color256_b_33,- color256_b_34,- color256_b_35,- color256_b_36,- color256_b_37,- color256_b_38,- color256_b_39,- color256_b_40,- color256_b_41,- color256_b_42,- color256_b_43,- color256_b_44,- color256_b_45,- color256_b_46,- color256_b_47,- color256_b_48,- color256_b_49,- color256_b_50,- color256_b_51,- color256_b_52,- color256_b_53,- color256_b_54,- color256_b_55,- color256_b_56,- color256_b_57,- color256_b_58,- color256_b_59,- color256_b_60,- color256_b_61,- color256_b_62,- color256_b_63,- color256_b_64,- color256_b_65,- color256_b_66,- color256_b_67,- color256_b_68,- color256_b_69,- color256_b_70,- color256_b_71,- color256_b_72,- color256_b_73,- color256_b_74,- color256_b_75,- color256_b_76,- color256_b_77,- color256_b_78,- color256_b_79,- color256_b_80,- color256_b_81,- color256_b_82,- color256_b_83,- color256_b_84,- color256_b_85,- color256_b_86,- color256_b_87,- color256_b_88,- color256_b_89,- color256_b_90,- color256_b_91,- color256_b_92,- color256_b_93,- color256_b_94,- color256_b_95,- color256_b_96,- color256_b_97,- color256_b_98,- color256_b_99,- color256_b_100,- color256_b_101,- color256_b_102,- color256_b_103,- color256_b_104,- color256_b_105,- color256_b_106,- color256_b_107,- color256_b_108,- color256_b_109,- color256_b_110,- color256_b_111,- color256_b_112,- color256_b_113,- color256_b_114,- color256_b_115,- color256_b_116,- color256_b_117,- color256_b_118,- color256_b_119,- color256_b_120,- color256_b_121,- color256_b_122,- color256_b_123,- color256_b_124,- color256_b_125,- color256_b_126,- color256_b_127,- color256_b_128,- color256_b_129,- color256_b_130,- color256_b_131,- color256_b_132,- color256_b_133,- color256_b_134,- color256_b_135,- color256_b_136,- color256_b_137,- color256_b_138,- color256_b_139,- color256_b_140,- color256_b_141,- color256_b_142,- color256_b_143,- color256_b_144,- color256_b_145,- color256_b_146,- color256_b_147,- color256_b_148,- color256_b_149,- color256_b_150,- color256_b_151,- color256_b_152,- color256_b_153,- color256_b_154,- color256_b_155,- color256_b_156,- color256_b_157,- color256_b_158,- color256_b_159,- color256_b_160,- color256_b_161,- color256_b_162,- color256_b_163,- color256_b_164,- color256_b_165,- color256_b_166,- color256_b_167,- color256_b_168,- color256_b_169,- color256_b_170,- color256_b_171,- color256_b_172,- color256_b_173,- color256_b_174,- color256_b_175,- color256_b_176,- color256_b_177,- color256_b_178,- color256_b_179,- color256_b_180,- color256_b_181,- color256_b_182,- color256_b_183,- color256_b_184,- color256_b_185,- color256_b_186,- color256_b_187,- color256_b_188,- color256_b_189,- color256_b_190,- color256_b_191,- color256_b_192,- color256_b_193,- color256_b_194,- color256_b_195,- color256_b_196,- color256_b_197,- color256_b_198,- color256_b_199,- color256_b_200,- color256_b_201,- color256_b_202,- color256_b_203,- color256_b_204,- color256_b_205,- color256_b_206,- color256_b_207,- color256_b_208,- color256_b_209,- color256_b_210,- color256_b_211,- color256_b_212,- color256_b_213,- color256_b_214,- color256_b_215,- color256_b_216,- color256_b_217,- color256_b_218,- color256_b_219,- color256_b_220,- color256_b_221,- color256_b_222,- color256_b_223,- color256_b_224,- color256_b_225,- color256_b_226,- color256_b_227,- color256_b_228,- color256_b_229,- color256_b_230,- color256_b_231,- color256_b_232,- color256_b_233,- color256_b_234,- color256_b_235,- color256_b_236,- color256_b_237,- color256_b_238,- color256_b_239,- color256_b_240,- color256_b_241,- color256_b_242,- color256_b_243,- color256_b_244,- color256_b_245,- color256_b_246,- color256_b_247,- color256_b_248,- color256_b_249,- color256_b_250,- color256_b_251,- color256_b_252,- color256_b_253,- color256_b_254,- color256_b_255-- ) where---import Data.Monoid (Monoid, mempty, mappend)-import Data.Text (Text)-import qualified Data.Text as X-import qualified Data.Text.Lazy as XL-import qualified Data.Text.Lazy.Builder as TB------- Colors------- | How many colors to actually show.-data Colors = Colors0 | Colors8 | Colors256- deriving Show---- | Background color in an 8 color setting.-newtype Background8 = Background8 { unBackground8 :: Code }---- | Background color in a 256 color setting.-newtype Background256 = Background256 { unBackground256 :: Code }---- | Foreground color in an 8 color setting.-newtype Foreground8 = Foreground8 { unForeground8 :: Code }---- | Foreground color in a 256 color setting.-newtype Foreground256 = Foreground256 { unForeground256 :: Code }-------- Chunks------- | A chunk is some textual data coupled with a description of what--- color the text is. The chunk knows what colors to use for both--- foreground color and background color, in both an 8 color terminal--- and a 256 color terminal. The chunk has only one set of color--- descriptions. To change colors, you must use a new chunk.------ There is no way to combine chunks. To print large numbers of--- chunks, lazily build a list of them and then print them using--- chunksToText.-data Chunk = Chunk TextSpec Text--chunk :: TextSpec -> Text -> Chunk-chunk = Chunk---- | How wide the text of a chunk is.-newtype Width = Width { unWidth :: Int }- deriving (Show, Eq, Ord)--instance Monoid Width where- mempty = Width 0- mappend (Width w1) (Width w2) = Width $ w1 + w2--chunkWidth :: Chunk -> Width-chunkWidth (Chunk _ t) = Width . X.length $ t---- | Transforms chunks to a lazy Text. This function runs lazily and--- in constant time and space.-chunksToText :: Colors -> [Chunk] -> XL.Text-chunksToText c = TB.toLazyText . foldr (builder c) (printReset c)-------- Effects----newtype Bold = Bold { unBold :: Bool }-newtype Underline = Underline { unUnderline :: Bool }-newtype Flash = Flash { unFlash :: Bool }-newtype Inverse = Inverse { unInverse :: Bool }------- Styles------- | Style elements that apply in both 8 and 256 color--- terminals. However, the elements are described separately for 8 and--- 256 color terminals, so that the text appearance can change--- depending on how many colors a terminal has.-data StyleCommon = StyleCommon {- bold :: Bold- , underline :: Underline- , flash :: Flash- , inverse :: Inverse }---- | Describes text appearance (foreground and background colors, as--- well as other attributes such as bold) for an 8 color terminal.-data Style8 = Style8 {- foreground8 :: Foreground8- , background8 :: Background8- , common8 :: StyleCommon }---- | Describes text appearance (foreground and background colors, as--- well as other attributes such as bold) for a 256 color terminal.-data Style256 = Style256 {- foreground256 :: Foreground256- , background256 :: Background256- , common256 :: StyleCommon }---- | Has all bold, flash, underline, and inverse turned off.-defaultStyleCommon :: StyleCommon-defaultStyleCommon = StyleCommon {- bold = Bold False- , underline = Underline False- , flash = Flash False- , inverse = Inverse False }---- | Uses the default terminal colors (which will vary depending on--- the terminal).-defaultStyle8 :: Style8-defaultStyle8 = Style8 {- foreground8 = color8_f_default- , background8 = color8_b_default- , common8 = defaultStyleCommon }---- | Uses the default terminal colors (which will vary depending on--- the terminal).-defaultStyle256 :: Style256-defaultStyle256 = Style256 {- foreground256 = color256_f_default- , background256 = color256_b_default- , common256 = defaultStyleCommon }------- TextSpec------- | The TextSpec bundles together the styles for the 8 and 256 color--- terminals, so that the text can be portrayed on any terminal.-data TextSpec = TextSpec {- style8 :: Style8- , style256 :: Style256 }---- | A TextSpec with the default colors on 8 and 256 color terminals,--- with all attributes turned off.-defaultTextSpec :: TextSpec-defaultTextSpec = TextSpec {- style8 = defaultStyle8- , style256 = defaultStyle256 }------- Internal-----(+++) :: TB.Builder -> TB.Builder -> TB.Builder-(+++) = mappend-infixr 5 +++--builder :: Colors -> Chunk -> TB.Builder -> TB.Builder-builder c (Chunk ts t) acc =- printReset c- +++ textSpecCodes c ts - +++ TB.fromText t- +++ acc--textSpecCodes :: Colors -> TextSpec -> TB.Builder-textSpecCodes c s = case c of- Colors0 -> mempty- Colors8 -> style8Colors $ style8 s- Colors256 -> style256Colors $ style256 s--style8Colors :: Style8 -> TB.Builder-style8Colors s =- (unCode . unForeground8 . foreground8 $ s)- +++ (unCode . unBackground8 . background8 $ s)- +++ (common (common8 s))--style256Colors :: Style256 -> TB.Builder-style256Colors s =- (unCode . unForeground256 . foreground256 $ s)- +++ (unCode . unBackground256 . background256 $ s)- +++ (common (common256 s))--common :: StyleCommon -> TB.Builder-common s =- (if unBold . bold $ s then unCode boldOn else mempty)- +++ (if unUnderline . underline $ s- then unCode underlineOn else mempty)- +++ (if unFlash . flash $ s- then unCode flashOn else mempty)- +++ (if unInverse . inverse $ s- then unCode inverseOn else mempty)--newtype Code = Code { unCode :: TB.Builder }--code :: String -> Code-code s = Code $- TB.fromString (toEnum 27:'[':[])- +++ TB.fromString s- +++ TB.singleton 'm'--boldOn :: Code-boldOn = code "1"--underlineOn :: Code-underlineOn = code "4"--flashOn :: Code-flashOn = code "5"--inverseOn :: Code-inverseOn = code "7"--resetOn :: Code-resetOn = code "0"--printReset :: Colors -> TB.Builder-printReset c = case c of- Colors0 -> mempty- _ -> unCode resetOn------- Color basement----color8_f_default :: Foreground8-color8_f_default = Foreground8 . Code $ mempty--color8_f_black :: Foreground8-color8_f_black = Foreground8 . code $ "30"--color8_f_red :: Foreground8-color8_f_red = Foreground8 . code $ "31"--color8_f_green :: Foreground8-color8_f_green = Foreground8 . code $ "32"--color8_f_yellow :: Foreground8-color8_f_yellow = Foreground8 . code $ "33"--color8_f_blue :: Foreground8-color8_f_blue = Foreground8 . code $ "34"--color8_f_magenta :: Foreground8-color8_f_magenta = Foreground8 . code $ "35"--color8_f_cyan :: Foreground8-color8_f_cyan = Foreground8 . code $ "36"--color8_f_white :: Foreground8-color8_f_white = Foreground8 . code $ "37"--color8_b_default :: Background8-color8_b_default = Background8 . Code $ mempty--color8_b_black :: Background8-color8_b_black = Background8 . code $ "40"--color8_b_red :: Background8-color8_b_red = Background8 . code $ "41"--color8_b_green :: Background8-color8_b_green = Background8 . code $ "42"--color8_b_yellow :: Background8-color8_b_yellow = Background8 . code $ "43"--color8_b_blue :: Background8-color8_b_blue = Background8 . code $ "44"--color8_b_magenta :: Background8-color8_b_magenta = Background8 . code $ "45"--color8_b_cyan :: Background8-color8_b_cyan = Background8 . code $ "46"--color8_b_white :: Background8-color8_b_white = Background8 . code $ "47"--color256_f_default :: Foreground256-color256_f_default = Foreground256 . Code $ mempty--color256_f_0 :: Foreground256-color256_f_0 = Foreground256 . code $ "38;5;0"--color256_f_1 :: Foreground256-color256_f_1 = Foreground256 . code $ "38;5;1"--color256_f_2 :: Foreground256-color256_f_2 = Foreground256 . code $ "38;5;2"--color256_f_3 :: Foreground256-color256_f_3 = Foreground256 . code $ "38;5;3"--color256_f_4 :: Foreground256-color256_f_4 = Foreground256 . code $ "38;5;4"--color256_f_5 :: Foreground256-color256_f_5 = Foreground256 . code $ "38;5;5"--color256_f_6 :: Foreground256-color256_f_6 = Foreground256 . code $ "38;5;6"--color256_f_7 :: Foreground256-color256_f_7 = Foreground256 . code $ "38;5;7"--color256_f_8 :: Foreground256-color256_f_8 = Foreground256 . code $ "38;5;8"--color256_f_9 :: Foreground256-color256_f_9 = Foreground256 . code $ "38;5;9"--color256_f_10 :: Foreground256-color256_f_10 = Foreground256 . code $ "38;5;10"--color256_f_11 :: Foreground256-color256_f_11 = Foreground256 . code $ "38;5;11"--color256_f_12 :: Foreground256-color256_f_12 = Foreground256 . code $ "38;5;12"--color256_f_13 :: Foreground256-color256_f_13 = Foreground256 . code $ "38;5;13"--color256_f_14 :: Foreground256-color256_f_14 = Foreground256 . code $ "38;5;14"--color256_f_15 :: Foreground256-color256_f_15 = Foreground256 . code $ "38;5;15"--color256_f_16 :: Foreground256-color256_f_16 = Foreground256 . code $ "38;5;16"--color256_f_17 :: Foreground256-color256_f_17 = Foreground256 . code $ "38;5;17"--color256_f_18 :: Foreground256-color256_f_18 = Foreground256 . code $ "38;5;18"--color256_f_19 :: Foreground256-color256_f_19 = Foreground256 . code $ "38;5;19"--color256_f_20 :: Foreground256-color256_f_20 = Foreground256 . code $ "38;5;20"--color256_f_21 :: Foreground256-color256_f_21 = Foreground256 . code $ "38;5;21"--color256_f_22 :: Foreground256-color256_f_22 = Foreground256 . code $ "38;5;22"--color256_f_23 :: Foreground256-color256_f_23 = Foreground256 . code $ "38;5;23"--color256_f_24 :: Foreground256-color256_f_24 = Foreground256 . code $ "38;5;24"--color256_f_25 :: Foreground256-color256_f_25 = Foreground256 . code $ "38;5;25"--color256_f_26 :: Foreground256-color256_f_26 = Foreground256 . code $ "38;5;26"--color256_f_27 :: Foreground256-color256_f_27 = Foreground256 . code $ "38;5;27"--color256_f_28 :: Foreground256-color256_f_28 = Foreground256 . code $ "38;5;28"--color256_f_29 :: Foreground256-color256_f_29 = Foreground256 . code $ "38;5;29"--color256_f_30 :: Foreground256-color256_f_30 = Foreground256 . code $ "38;5;30"--color256_f_31 :: Foreground256-color256_f_31 = Foreground256 . code $ "38;5;31"--color256_f_32 :: Foreground256-color256_f_32 = Foreground256 . code $ "38;5;32"--color256_f_33 :: Foreground256-color256_f_33 = Foreground256 . code $ "38;5;33"--color256_f_34 :: Foreground256-color256_f_34 = Foreground256 . code $ "38;5;34"--color256_f_35 :: Foreground256-color256_f_35 = Foreground256 . code $ "38;5;35"--color256_f_36 :: Foreground256-color256_f_36 = Foreground256 . code $ "38;5;36"--color256_f_37 :: Foreground256-color256_f_37 = Foreground256 . code $ "38;5;37"--color256_f_38 :: Foreground256-color256_f_38 = Foreground256 . code $ "38;5;38"--color256_f_39 :: Foreground256-color256_f_39 = Foreground256 . code $ "38;5;39"--color256_f_40 :: Foreground256-color256_f_40 = Foreground256 . code $ "38;5;40"--color256_f_41 :: Foreground256-color256_f_41 = Foreground256 . code $ "38;5;41"--color256_f_42 :: Foreground256-color256_f_42 = Foreground256 . code $ "38;5;42"--color256_f_43 :: Foreground256-color256_f_43 = Foreground256 . code $ "38;5;43"--color256_f_44 :: Foreground256-color256_f_44 = Foreground256 . code $ "38;5;44"--color256_f_45 :: Foreground256-color256_f_45 = Foreground256 . code $ "38;5;45"--color256_f_46 :: Foreground256-color256_f_46 = Foreground256 . code $ "38;5;46"--color256_f_47 :: Foreground256-color256_f_47 = Foreground256 . code $ "38;5;47"--color256_f_48 :: Foreground256-color256_f_48 = Foreground256 . code $ "38;5;48"--color256_f_49 :: Foreground256-color256_f_49 = Foreground256 . code $ "38;5;49"--color256_f_50 :: Foreground256-color256_f_50 = Foreground256 . code $ "38;5;50"--color256_f_51 :: Foreground256-color256_f_51 = Foreground256 . code $ "38;5;51"--color256_f_52 :: Foreground256-color256_f_52 = Foreground256 . code $ "38;5;52"--color256_f_53 :: Foreground256-color256_f_53 = Foreground256 . code $ "38;5;53"--color256_f_54 :: Foreground256-color256_f_54 = Foreground256 . code $ "38;5;54"--color256_f_55 :: Foreground256-color256_f_55 = Foreground256 . code $ "38;5;55"--color256_f_56 :: Foreground256-color256_f_56 = Foreground256 . code $ "38;5;56"--color256_f_57 :: Foreground256-color256_f_57 = Foreground256 . code $ "38;5;57"--color256_f_58 :: Foreground256-color256_f_58 = Foreground256 . code $ "38;5;58"--color256_f_59 :: Foreground256-color256_f_59 = Foreground256 . code $ "38;5;59"--color256_f_60 :: Foreground256-color256_f_60 = Foreground256 . code $ "38;5;60"--color256_f_61 :: Foreground256-color256_f_61 = Foreground256 . code $ "38;5;61"--color256_f_62 :: Foreground256-color256_f_62 = Foreground256 . code $ "38;5;62"--color256_f_63 :: Foreground256-color256_f_63 = Foreground256 . code $ "38;5;63"--color256_f_64 :: Foreground256-color256_f_64 = Foreground256 . code $ "38;5;64"--color256_f_65 :: Foreground256-color256_f_65 = Foreground256 . code $ "38;5;65"--color256_f_66 :: Foreground256-color256_f_66 = Foreground256 . code $ "38;5;66"--color256_f_67 :: Foreground256-color256_f_67 = Foreground256 . code $ "38;5;67"--color256_f_68 :: Foreground256-color256_f_68 = Foreground256 . code $ "38;5;68"--color256_f_69 :: Foreground256-color256_f_69 = Foreground256 . code $ "38;5;69"--color256_f_70 :: Foreground256-color256_f_70 = Foreground256 . code $ "38;5;70"--color256_f_71 :: Foreground256-color256_f_71 = Foreground256 . code $ "38;5;71"--color256_f_72 :: Foreground256-color256_f_72 = Foreground256 . code $ "38;5;72"--color256_f_73 :: Foreground256-color256_f_73 = Foreground256 . code $ "38;5;73"--color256_f_74 :: Foreground256-color256_f_74 = Foreground256 . code $ "38;5;74"--color256_f_75 :: Foreground256-color256_f_75 = Foreground256 . code $ "38;5;75"--color256_f_76 :: Foreground256-color256_f_76 = Foreground256 . code $ "38;5;76"--color256_f_77 :: Foreground256-color256_f_77 = Foreground256 . code $ "38;5;77"--color256_f_78 :: Foreground256-color256_f_78 = Foreground256 . code $ "38;5;78"--color256_f_79 :: Foreground256-color256_f_79 = Foreground256 . code $ "38;5;79"--color256_f_80 :: Foreground256-color256_f_80 = Foreground256 . code $ "38;5;80"--color256_f_81 :: Foreground256-color256_f_81 = Foreground256 . code $ "38;5;81"--color256_f_82 :: Foreground256-color256_f_82 = Foreground256 . code $ "38;5;82"--color256_f_83 :: Foreground256-color256_f_83 = Foreground256 . code $ "38;5;83"--color256_f_84 :: Foreground256-color256_f_84 = Foreground256 . code $ "38;5;84"--color256_f_85 :: Foreground256-color256_f_85 = Foreground256 . code $ "38;5;85"--color256_f_86 :: Foreground256-color256_f_86 = Foreground256 . code $ "38;5;86"--color256_f_87 :: Foreground256-color256_f_87 = Foreground256 . code $ "38;5;87"--color256_f_88 :: Foreground256-color256_f_88 = Foreground256 . code $ "38;5;88"--color256_f_89 :: Foreground256-color256_f_89 = Foreground256 . code $ "38;5;89"--color256_f_90 :: Foreground256-color256_f_90 = Foreground256 . code $ "38;5;90"--color256_f_91 :: Foreground256-color256_f_91 = Foreground256 . code $ "38;5;91"--color256_f_92 :: Foreground256-color256_f_92 = Foreground256 . code $ "38;5;92"--color256_f_93 :: Foreground256-color256_f_93 = Foreground256 . code $ "38;5;93"--color256_f_94 :: Foreground256-color256_f_94 = Foreground256 . code $ "38;5;94"--color256_f_95 :: Foreground256-color256_f_95 = Foreground256 . code $ "38;5;95"--color256_f_96 :: Foreground256-color256_f_96 = Foreground256 . code $ "38;5;96"--color256_f_97 :: Foreground256-color256_f_97 = Foreground256 . code $ "38;5;97"--color256_f_98 :: Foreground256-color256_f_98 = Foreground256 . code $ "38;5;98"--color256_f_99 :: Foreground256-color256_f_99 = Foreground256 . code $ "38;5;99"--color256_f_100 :: Foreground256-color256_f_100 = Foreground256 . code $ "38;5;100"--color256_f_101 :: Foreground256-color256_f_101 = Foreground256 . code $ "38;5;101"--color256_f_102 :: Foreground256-color256_f_102 = Foreground256 . code $ "38;5;102"--color256_f_103 :: Foreground256-color256_f_103 = Foreground256 . code $ "38;5;103"--color256_f_104 :: Foreground256-color256_f_104 = Foreground256 . code $ "38;5;104"--color256_f_105 :: Foreground256-color256_f_105 = Foreground256 . code $ "38;5;105"--color256_f_106 :: Foreground256-color256_f_106 = Foreground256 . code $ "38;5;106"--color256_f_107 :: Foreground256-color256_f_107 = Foreground256 . code $ "38;5;107"--color256_f_108 :: Foreground256-color256_f_108 = Foreground256 . code $ "38;5;108"--color256_f_109 :: Foreground256-color256_f_109 = Foreground256 . code $ "38;5;109"--color256_f_110 :: Foreground256-color256_f_110 = Foreground256 . code $ "38;5;110"--color256_f_111 :: Foreground256-color256_f_111 = Foreground256 . code $ "38;5;111"--color256_f_112 :: Foreground256-color256_f_112 = Foreground256 . code $ "38;5;112"--color256_f_113 :: Foreground256-color256_f_113 = Foreground256 . code $ "38;5;113"--color256_f_114 :: Foreground256-color256_f_114 = Foreground256 . code $ "38;5;114"--color256_f_115 :: Foreground256-color256_f_115 = Foreground256 . code $ "38;5;115"--color256_f_116 :: Foreground256-color256_f_116 = Foreground256 . code $ "38;5;116"--color256_f_117 :: Foreground256-color256_f_117 = Foreground256 . code $ "38;5;117"--color256_f_118 :: Foreground256-color256_f_118 = Foreground256 . code $ "38;5;118"--color256_f_119 :: Foreground256-color256_f_119 = Foreground256 . code $ "38;5;119"--color256_f_120 :: Foreground256-color256_f_120 = Foreground256 . code $ "38;5;120"--color256_f_121 :: Foreground256-color256_f_121 = Foreground256 . code $ "38;5;121"--color256_f_122 :: Foreground256-color256_f_122 = Foreground256 . code $ "38;5;122"--color256_f_123 :: Foreground256-color256_f_123 = Foreground256 . code $ "38;5;123"--color256_f_124 :: Foreground256-color256_f_124 = Foreground256 . code $ "38;5;124"--color256_f_125 :: Foreground256-color256_f_125 = Foreground256 . code $ "38;5;125"--color256_f_126 :: Foreground256-color256_f_126 = Foreground256 . code $ "38;5;126"--color256_f_127 :: Foreground256-color256_f_127 = Foreground256 . code $ "38;5;127"--color256_f_128 :: Foreground256-color256_f_128 = Foreground256 . code $ "38;5;128"--color256_f_129 :: Foreground256-color256_f_129 = Foreground256 . code $ "38;5;129"--color256_f_130 :: Foreground256-color256_f_130 = Foreground256 . code $ "38;5;130"--color256_f_131 :: Foreground256-color256_f_131 = Foreground256 . code $ "38;5;131"--color256_f_132 :: Foreground256-color256_f_132 = Foreground256 . code $ "38;5;132"--color256_f_133 :: Foreground256-color256_f_133 = Foreground256 . code $ "38;5;133"--color256_f_134 :: Foreground256-color256_f_134 = Foreground256 . code $ "38;5;134"--color256_f_135 :: Foreground256-color256_f_135 = Foreground256 . code $ "38;5;135"--color256_f_136 :: Foreground256-color256_f_136 = Foreground256 . code $ "38;5;136"--color256_f_137 :: Foreground256-color256_f_137 = Foreground256 . code $ "38;5;137"--color256_f_138 :: Foreground256-color256_f_138 = Foreground256 . code $ "38;5;138"--color256_f_139 :: Foreground256-color256_f_139 = Foreground256 . code $ "38;5;139"--color256_f_140 :: Foreground256-color256_f_140 = Foreground256 . code $ "38;5;140"--color256_f_141 :: Foreground256-color256_f_141 = Foreground256 . code $ "38;5;141"--color256_f_142 :: Foreground256-color256_f_142 = Foreground256 . code $ "38;5;142"--color256_f_143 :: Foreground256-color256_f_143 = Foreground256 . code $ "38;5;143"--color256_f_144 :: Foreground256-color256_f_144 = Foreground256 . code $ "38;5;144"--color256_f_145 :: Foreground256-color256_f_145 = Foreground256 . code $ "38;5;145"--color256_f_146 :: Foreground256-color256_f_146 = Foreground256 . code $ "38;5;146"--color256_f_147 :: Foreground256-color256_f_147 = Foreground256 . code $ "38;5;147"--color256_f_148 :: Foreground256-color256_f_148 = Foreground256 . code $ "38;5;148"--color256_f_149 :: Foreground256-color256_f_149 = Foreground256 . code $ "38;5;149"--color256_f_150 :: Foreground256-color256_f_150 = Foreground256 . code $ "38;5;150"--color256_f_151 :: Foreground256-color256_f_151 = Foreground256 . code $ "38;5;151"--color256_f_152 :: Foreground256-color256_f_152 = Foreground256 . code $ "38;5;152"--color256_f_153 :: Foreground256-color256_f_153 = Foreground256 . code $ "38;5;153"--color256_f_154 :: Foreground256-color256_f_154 = Foreground256 . code $ "38;5;154"--color256_f_155 :: Foreground256-color256_f_155 = Foreground256 . code $ "38;5;155"--color256_f_156 :: Foreground256-color256_f_156 = Foreground256 . code $ "38;5;156"--color256_f_157 :: Foreground256-color256_f_157 = Foreground256 . code $ "38;5;157"--color256_f_158 :: Foreground256-color256_f_158 = Foreground256 . code $ "38;5;158"--color256_f_159 :: Foreground256-color256_f_159 = Foreground256 . code $ "38;5;159"--color256_f_160 :: Foreground256-color256_f_160 = Foreground256 . code $ "38;5;160"--color256_f_161 :: Foreground256-color256_f_161 = Foreground256 . code $ "38;5;161"--color256_f_162 :: Foreground256-color256_f_162 = Foreground256 . code $ "38;5;162"--color256_f_163 :: Foreground256-color256_f_163 = Foreground256 . code $ "38;5;163"--color256_f_164 :: Foreground256-color256_f_164 = Foreground256 . code $ "38;5;164"--color256_f_165 :: Foreground256-color256_f_165 = Foreground256 . code $ "38;5;165"--color256_f_166 :: Foreground256-color256_f_166 = Foreground256 . code $ "38;5;166"--color256_f_167 :: Foreground256-color256_f_167 = Foreground256 . code $ "38;5;167"--color256_f_168 :: Foreground256-color256_f_168 = Foreground256 . code $ "38;5;168"--color256_f_169 :: Foreground256-color256_f_169 = Foreground256 . code $ "38;5;169"--color256_f_170 :: Foreground256-color256_f_170 = Foreground256 . code $ "38;5;170"--color256_f_171 :: Foreground256-color256_f_171 = Foreground256 . code $ "38;5;171"--color256_f_172 :: Foreground256-color256_f_172 = Foreground256 . code $ "38;5;172"--color256_f_173 :: Foreground256-color256_f_173 = Foreground256 . code $ "38;5;173"--color256_f_174 :: Foreground256-color256_f_174 = Foreground256 . code $ "38;5;174"--color256_f_175 :: Foreground256-color256_f_175 = Foreground256 . code $ "38;5;175"--color256_f_176 :: Foreground256-color256_f_176 = Foreground256 . code $ "38;5;176"--color256_f_177 :: Foreground256-color256_f_177 = Foreground256 . code $ "38;5;177"--color256_f_178 :: Foreground256-color256_f_178 = Foreground256 . code $ "38;5;178"--color256_f_179 :: Foreground256-color256_f_179 = Foreground256 . code $ "38;5;179"--color256_f_180 :: Foreground256-color256_f_180 = Foreground256 . code $ "38;5;180"--color256_f_181 :: Foreground256-color256_f_181 = Foreground256 . code $ "38;5;181"--color256_f_182 :: Foreground256-color256_f_182 = Foreground256 . code $ "38;5;182"--color256_f_183 :: Foreground256-color256_f_183 = Foreground256 . code $ "38;5;183"--color256_f_184 :: Foreground256-color256_f_184 = Foreground256 . code $ "38;5;184"--color256_f_185 :: Foreground256-color256_f_185 = Foreground256 . code $ "38;5;185"--color256_f_186 :: Foreground256-color256_f_186 = Foreground256 . code $ "38;5;186"--color256_f_187 :: Foreground256-color256_f_187 = Foreground256 . code $ "38;5;187"--color256_f_188 :: Foreground256-color256_f_188 = Foreground256 . code $ "38;5;188"--color256_f_189 :: Foreground256-color256_f_189 = Foreground256 . code $ "38;5;189"--color256_f_190 :: Foreground256-color256_f_190 = Foreground256 . code $ "38;5;190"--color256_f_191 :: Foreground256-color256_f_191 = Foreground256 . code $ "38;5;191"--color256_f_192 :: Foreground256-color256_f_192 = Foreground256 . code $ "38;5;192"--color256_f_193 :: Foreground256-color256_f_193 = Foreground256 . code $ "38;5;193"--color256_f_194 :: Foreground256-color256_f_194 = Foreground256 . code $ "38;5;194"--color256_f_195 :: Foreground256-color256_f_195 = Foreground256 . code $ "38;5;195"--color256_f_196 :: Foreground256-color256_f_196 = Foreground256 . code $ "38;5;196"--color256_f_197 :: Foreground256-color256_f_197 = Foreground256 . code $ "38;5;197"--color256_f_198 :: Foreground256-color256_f_198 = Foreground256 . code $ "38;5;198"--color256_f_199 :: Foreground256-color256_f_199 = Foreground256 . code $ "38;5;199"--color256_f_200 :: Foreground256-color256_f_200 = Foreground256 . code $ "38;5;200"--color256_f_201 :: Foreground256-color256_f_201 = Foreground256 . code $ "38;5;201"--color256_f_202 :: Foreground256-color256_f_202 = Foreground256 . code $ "38;5;202"--color256_f_203 :: Foreground256-color256_f_203 = Foreground256 . code $ "38;5;203"--color256_f_204 :: Foreground256-color256_f_204 = Foreground256 . code $ "38;5;204"--color256_f_205 :: Foreground256-color256_f_205 = Foreground256 . code $ "38;5;205"--color256_f_206 :: Foreground256-color256_f_206 = Foreground256 . code $ "38;5;206"--color256_f_207 :: Foreground256-color256_f_207 = Foreground256 . code $ "38;5;207"--color256_f_208 :: Foreground256-color256_f_208 = Foreground256 . code $ "38;5;208"--color256_f_209 :: Foreground256-color256_f_209 = Foreground256 . code $ "38;5;209"--color256_f_210 :: Foreground256-color256_f_210 = Foreground256 . code $ "38;5;210"--color256_f_211 :: Foreground256-color256_f_211 = Foreground256 . code $ "38;5;211"--color256_f_212 :: Foreground256-color256_f_212 = Foreground256 . code $ "38;5;212"--color256_f_213 :: Foreground256-color256_f_213 = Foreground256 . code $ "38;5;213"--color256_f_214 :: Foreground256-color256_f_214 = Foreground256 . code $ "38;5;214"--color256_f_215 :: Foreground256-color256_f_215 = Foreground256 . code $ "38;5;215"--color256_f_216 :: Foreground256-color256_f_216 = Foreground256 . code $ "38;5;216"--color256_f_217 :: Foreground256-color256_f_217 = Foreground256 . code $ "38;5;217"--color256_f_218 :: Foreground256-color256_f_218 = Foreground256 . code $ "38;5;218"--color256_f_219 :: Foreground256-color256_f_219 = Foreground256 . code $ "38;5;219"--color256_f_220 :: Foreground256-color256_f_220 = Foreground256 . code $ "38;5;220"--color256_f_221 :: Foreground256-color256_f_221 = Foreground256 . code $ "38;5;221"--color256_f_222 :: Foreground256-color256_f_222 = Foreground256 . code $ "38;5;222"--color256_f_223 :: Foreground256-color256_f_223 = Foreground256 . code $ "38;5;223"--color256_f_224 :: Foreground256-color256_f_224 = Foreground256 . code $ "38;5;224"--color256_f_225 :: Foreground256-color256_f_225 = Foreground256 . code $ "38;5;225"--color256_f_226 :: Foreground256-color256_f_226 = Foreground256 . code $ "38;5;226"--color256_f_227 :: Foreground256-color256_f_227 = Foreground256 . code $ "38;5;227"--color256_f_228 :: Foreground256-color256_f_228 = Foreground256 . code $ "38;5;228"--color256_f_229 :: Foreground256-color256_f_229 = Foreground256 . code $ "38;5;229"--color256_f_230 :: Foreground256-color256_f_230 = Foreground256 . code $ "38;5;230"--color256_f_231 :: Foreground256-color256_f_231 = Foreground256 . code $ "38;5;231"--color256_f_232 :: Foreground256-color256_f_232 = Foreground256 . code $ "38;5;232"--color256_f_233 :: Foreground256-color256_f_233 = Foreground256 . code $ "38;5;233"--color256_f_234 :: Foreground256-color256_f_234 = Foreground256 . code $ "38;5;234"--color256_f_235 :: Foreground256-color256_f_235 = Foreground256 . code $ "38;5;235"--color256_f_236 :: Foreground256-color256_f_236 = Foreground256 . code $ "38;5;236"--color256_f_237 :: Foreground256-color256_f_237 = Foreground256 . code $ "38;5;237"--color256_f_238 :: Foreground256-color256_f_238 = Foreground256 . code $ "38;5;238"--color256_f_239 :: Foreground256-color256_f_239 = Foreground256 . code $ "38;5;239"--color256_f_240 :: Foreground256-color256_f_240 = Foreground256 . code $ "38;5;240"--color256_f_241 :: Foreground256-color256_f_241 = Foreground256 . code $ "38;5;241"--color256_f_242 :: Foreground256-color256_f_242 = Foreground256 . code $ "38;5;242"--color256_f_243 :: Foreground256-color256_f_243 = Foreground256 . code $ "38;5;243"--color256_f_244 :: Foreground256-color256_f_244 = Foreground256 . code $ "38;5;244"--color256_f_245 :: Foreground256-color256_f_245 = Foreground256 . code $ "38;5;245"--color256_f_246 :: Foreground256-color256_f_246 = Foreground256 . code $ "38;5;246"--color256_f_247 :: Foreground256-color256_f_247 = Foreground256 . code $ "38;5;247"--color256_f_248 :: Foreground256-color256_f_248 = Foreground256 . code $ "38;5;248"--color256_f_249 :: Foreground256-color256_f_249 = Foreground256 . code $ "38;5;249"--color256_f_250 :: Foreground256-color256_f_250 = Foreground256 . code $ "38;5;250"--color256_f_251 :: Foreground256-color256_f_251 = Foreground256 . code $ "38;5;251"--color256_f_252 :: Foreground256-color256_f_252 = Foreground256 . code $ "38;5;252"--color256_f_253 :: Foreground256-color256_f_253 = Foreground256 . code $ "38;5;253"--color256_f_254 :: Foreground256-color256_f_254 = Foreground256 . code $ "38;5;254"--color256_f_255 :: Foreground256-color256_f_255 = Foreground256 . code $ "38;5;255"--color256_b_default :: Background256-color256_b_default = Background256 . Code $ mempty--color256_b_0 :: Background256-color256_b_0 = Background256 . code $ "48;5;0"--color256_b_1 :: Background256-color256_b_1 = Background256 . code $ "48;5;1"--color256_b_2 :: Background256-color256_b_2 = Background256 . code $ "48;5;2"--color256_b_3 :: Background256-color256_b_3 = Background256 . code $ "48;5;3"--color256_b_4 :: Background256-color256_b_4 = Background256 . code $ "48;5;4"--color256_b_5 :: Background256-color256_b_5 = Background256 . code $ "48;5;5"--color256_b_6 :: Background256-color256_b_6 = Background256 . code $ "48;5;6"--color256_b_7 :: Background256-color256_b_7 = Background256 . code $ "48;5;7"--color256_b_8 :: Background256-color256_b_8 = Background256 . code $ "48;5;8"--color256_b_9 :: Background256-color256_b_9 = Background256 . code $ "48;5;9"--color256_b_10 :: Background256-color256_b_10 = Background256 . code $ "48;5;10"--color256_b_11 :: Background256-color256_b_11 = Background256 . code $ "48;5;11"--color256_b_12 :: Background256-color256_b_12 = Background256 . code $ "48;5;12"--color256_b_13 :: Background256-color256_b_13 = Background256 . code $ "48;5;13"--color256_b_14 :: Background256-color256_b_14 = Background256 . code $ "48;5;14"--color256_b_15 :: Background256-color256_b_15 = Background256 . code $ "48;5;15"--color256_b_16 :: Background256-color256_b_16 = Background256 . code $ "48;5;16"--color256_b_17 :: Background256-color256_b_17 = Background256 . code $ "48;5;17"--color256_b_18 :: Background256-color256_b_18 = Background256 . code $ "48;5;18"--color256_b_19 :: Background256-color256_b_19 = Background256 . code $ "48;5;19"--color256_b_20 :: Background256-color256_b_20 = Background256 . code $ "48;5;20"--color256_b_21 :: Background256-color256_b_21 = Background256 . code $ "48;5;21"--color256_b_22 :: Background256-color256_b_22 = Background256 . code $ "48;5;22"--color256_b_23 :: Background256-color256_b_23 = Background256 . code $ "48;5;23"--color256_b_24 :: Background256-color256_b_24 = Background256 . code $ "48;5;24"--color256_b_25 :: Background256-color256_b_25 = Background256 . code $ "48;5;25"--color256_b_26 :: Background256-color256_b_26 = Background256 . code $ "48;5;26"--color256_b_27 :: Background256-color256_b_27 = Background256 . code $ "48;5;27"--color256_b_28 :: Background256-color256_b_28 = Background256 . code $ "48;5;28"--color256_b_29 :: Background256-color256_b_29 = Background256 . code $ "48;5;29"--color256_b_30 :: Background256-color256_b_30 = Background256 . code $ "48;5;30"--color256_b_31 :: Background256-color256_b_31 = Background256 . code $ "48;5;31"--color256_b_32 :: Background256-color256_b_32 = Background256 . code $ "48;5;32"--color256_b_33 :: Background256-color256_b_33 = Background256 . code $ "48;5;33"--color256_b_34 :: Background256-color256_b_34 = Background256 . code $ "48;5;34"--color256_b_35 :: Background256-color256_b_35 = Background256 . code $ "48;5;35"--color256_b_36 :: Background256-color256_b_36 = Background256 . code $ "48;5;36"--color256_b_37 :: Background256-color256_b_37 = Background256 . code $ "48;5;37"--color256_b_38 :: Background256-color256_b_38 = Background256 . code $ "48;5;38"--color256_b_39 :: Background256-color256_b_39 = Background256 . code $ "48;5;39"--color256_b_40 :: Background256-color256_b_40 = Background256 . code $ "48;5;40"--color256_b_41 :: Background256-color256_b_41 = Background256 . code $ "48;5;41"--color256_b_42 :: Background256-color256_b_42 = Background256 . code $ "48;5;42"--color256_b_43 :: Background256-color256_b_43 = Background256 . code $ "48;5;43"--color256_b_44 :: Background256-color256_b_44 = Background256 . code $ "48;5;44"--color256_b_45 :: Background256-color256_b_45 = Background256 . code $ "48;5;45"--color256_b_46 :: Background256-color256_b_46 = Background256 . code $ "48;5;46"--color256_b_47 :: Background256-color256_b_47 = Background256 . code $ "48;5;47"--color256_b_48 :: Background256-color256_b_48 = Background256 . code $ "48;5;48"--color256_b_49 :: Background256-color256_b_49 = Background256 . code $ "48;5;49"--color256_b_50 :: Background256-color256_b_50 = Background256 . code $ "48;5;50"--color256_b_51 :: Background256-color256_b_51 = Background256 . code $ "48;5;51"--color256_b_52 :: Background256-color256_b_52 = Background256 . code $ "48;5;52"--color256_b_53 :: Background256-color256_b_53 = Background256 . code $ "48;5;53"--color256_b_54 :: Background256-color256_b_54 = Background256 . code $ "48;5;54"--color256_b_55 :: Background256-color256_b_55 = Background256 . code $ "48;5;55"--color256_b_56 :: Background256-color256_b_56 = Background256 . code $ "48;5;56"--color256_b_57 :: Background256-color256_b_57 = Background256 . code $ "48;5;57"--color256_b_58 :: Background256-color256_b_58 = Background256 . code $ "48;5;58"--color256_b_59 :: Background256-color256_b_59 = Background256 . code $ "48;5;59"--color256_b_60 :: Background256-color256_b_60 = Background256 . code $ "48;5;60"--color256_b_61 :: Background256-color256_b_61 = Background256 . code $ "48;5;61"--color256_b_62 :: Background256-color256_b_62 = Background256 . code $ "48;5;62"--color256_b_63 :: Background256-color256_b_63 = Background256 . code $ "48;5;63"--color256_b_64 :: Background256-color256_b_64 = Background256 . code $ "48;5;64"--color256_b_65 :: Background256-color256_b_65 = Background256 . code $ "48;5;65"--color256_b_66 :: Background256-color256_b_66 = Background256 . code $ "48;5;66"--color256_b_67 :: Background256-color256_b_67 = Background256 . code $ "48;5;67"--color256_b_68 :: Background256-color256_b_68 = Background256 . code $ "48;5;68"--color256_b_69 :: Background256-color256_b_69 = Background256 . code $ "48;5;69"--color256_b_70 :: Background256-color256_b_70 = Background256 . code $ "48;5;70"--color256_b_71 :: Background256-color256_b_71 = Background256 . code $ "48;5;71"--color256_b_72 :: Background256-color256_b_72 = Background256 . code $ "48;5;72"--color256_b_73 :: Background256-color256_b_73 = Background256 . code $ "48;5;73"--color256_b_74 :: Background256-color256_b_74 = Background256 . code $ "48;5;74"--color256_b_75 :: Background256-color256_b_75 = Background256 . code $ "48;5;75"--color256_b_76 :: Background256-color256_b_76 = Background256 . code $ "48;5;76"--color256_b_77 :: Background256-color256_b_77 = Background256 . code $ "48;5;77"--color256_b_78 :: Background256-color256_b_78 = Background256 . code $ "48;5;78"--color256_b_79 :: Background256-color256_b_79 = Background256 . code $ "48;5;79"--color256_b_80 :: Background256-color256_b_80 = Background256 . code $ "48;5;80"--color256_b_81 :: Background256-color256_b_81 = Background256 . code $ "48;5;81"--color256_b_82 :: Background256-color256_b_82 = Background256 . code $ "48;5;82"--color256_b_83 :: Background256-color256_b_83 = Background256 . code $ "48;5;83"--color256_b_84 :: Background256-color256_b_84 = Background256 . code $ "48;5;84"--color256_b_85 :: Background256-color256_b_85 = Background256 . code $ "48;5;85"--color256_b_86 :: Background256-color256_b_86 = Background256 . code $ "48;5;86"--color256_b_87 :: Background256-color256_b_87 = Background256 . code $ "48;5;87"--color256_b_88 :: Background256-color256_b_88 = Background256 . code $ "48;5;88"--color256_b_89 :: Background256-color256_b_89 = Background256 . code $ "48;5;89"--color256_b_90 :: Background256-color256_b_90 = Background256 . code $ "48;5;90"--color256_b_91 :: Background256-color256_b_91 = Background256 . code $ "48;5;91"--color256_b_92 :: Background256-color256_b_92 = Background256 . code $ "48;5;92"--color256_b_93 :: Background256-color256_b_93 = Background256 . code $ "48;5;93"--color256_b_94 :: Background256-color256_b_94 = Background256 . code $ "48;5;94"--color256_b_95 :: Background256-color256_b_95 = Background256 . code $ "48;5;95"--color256_b_96 :: Background256-color256_b_96 = Background256 . code $ "48;5;96"--color256_b_97 :: Background256-color256_b_97 = Background256 . code $ "48;5;97"--color256_b_98 :: Background256-color256_b_98 = Background256 . code $ "48;5;98"--color256_b_99 :: Background256-color256_b_99 = Background256 . code $ "48;5;99"--color256_b_100 :: Background256-color256_b_100 = Background256 . code $ "48;5;100"--color256_b_101 :: Background256-color256_b_101 = Background256 . code $ "48;5;101"--color256_b_102 :: Background256-color256_b_102 = Background256 . code $ "48;5;102"--color256_b_103 :: Background256-color256_b_103 = Background256 . code $ "48;5;103"--color256_b_104 :: Background256-color256_b_104 = Background256 . code $ "48;5;104"--color256_b_105 :: Background256-color256_b_105 = Background256 . code $ "48;5;105"--color256_b_106 :: Background256-color256_b_106 = Background256 . code $ "48;5;106"--color256_b_107 :: Background256-color256_b_107 = Background256 . code $ "48;5;107"--color256_b_108 :: Background256-color256_b_108 = Background256 . code $ "48;5;108"--color256_b_109 :: Background256-color256_b_109 = Background256 . code $ "48;5;109"--color256_b_110 :: Background256-color256_b_110 = Background256 . code $ "48;5;110"--color256_b_111 :: Background256-color256_b_111 = Background256 . code $ "48;5;111"--color256_b_112 :: Background256-color256_b_112 = Background256 . code $ "48;5;112"--color256_b_113 :: Background256-color256_b_113 = Background256 . code $ "48;5;113"--color256_b_114 :: Background256-color256_b_114 = Background256 . code $ "48;5;114"--color256_b_115 :: Background256-color256_b_115 = Background256 . code $ "48;5;115"--color256_b_116 :: Background256-color256_b_116 = Background256 . code $ "48;5;116"--color256_b_117 :: Background256-color256_b_117 = Background256 . code $ "48;5;117"--color256_b_118 :: Background256-color256_b_118 = Background256 . code $ "48;5;118"--color256_b_119 :: Background256-color256_b_119 = Background256 . code $ "48;5;119"--color256_b_120 :: Background256-color256_b_120 = Background256 . code $ "48;5;120"--color256_b_121 :: Background256-color256_b_121 = Background256 . code $ "48;5;121"--color256_b_122 :: Background256-color256_b_122 = Background256 . code $ "48;5;122"--color256_b_123 :: Background256-color256_b_123 = Background256 . code $ "48;5;123"--color256_b_124 :: Background256-color256_b_124 = Background256 . code $ "48;5;124"--color256_b_125 :: Background256-color256_b_125 = Background256 . code $ "48;5;125"--color256_b_126 :: Background256-color256_b_126 = Background256 . code $ "48;5;126"--color256_b_127 :: Background256-color256_b_127 = Background256 . code $ "48;5;127"--color256_b_128 :: Background256-color256_b_128 = Background256 . code $ "48;5;128"--color256_b_129 :: Background256-color256_b_129 = Background256 . code $ "48;5;129"--color256_b_130 :: Background256-color256_b_130 = Background256 . code $ "48;5;130"--color256_b_131 :: Background256-color256_b_131 = Background256 . code $ "48;5;131"--color256_b_132 :: Background256-color256_b_132 = Background256 . code $ "48;5;132"--color256_b_133 :: Background256-color256_b_133 = Background256 . code $ "48;5;133"--color256_b_134 :: Background256-color256_b_134 = Background256 . code $ "48;5;134"--color256_b_135 :: Background256-color256_b_135 = Background256 . code $ "48;5;135"--color256_b_136 :: Background256-color256_b_136 = Background256 . code $ "48;5;136"--color256_b_137 :: Background256-color256_b_137 = Background256 . code $ "48;5;137"--color256_b_138 :: Background256-color256_b_138 = Background256 . code $ "48;5;138"--color256_b_139 :: Background256-color256_b_139 = Background256 . code $ "48;5;139"--color256_b_140 :: Background256-color256_b_140 = Background256 . code $ "48;5;140"--color256_b_141 :: Background256-color256_b_141 = Background256 . code $ "48;5;141"--color256_b_142 :: Background256-color256_b_142 = Background256 . code $ "48;5;142"--color256_b_143 :: Background256-color256_b_143 = Background256 . code $ "48;5;143"--color256_b_144 :: Background256-color256_b_144 = Background256 . code $ "48;5;144"--color256_b_145 :: Background256-color256_b_145 = Background256 . code $ "48;5;145"--color256_b_146 :: Background256-color256_b_146 = Background256 . code $ "48;5;146"--color256_b_147 :: Background256-color256_b_147 = Background256 . code $ "48;5;147"--color256_b_148 :: Background256-color256_b_148 = Background256 . code $ "48;5;148"--color256_b_149 :: Background256-color256_b_149 = Background256 . code $ "48;5;149"--color256_b_150 :: Background256-color256_b_150 = Background256 . code $ "48;5;150"--color256_b_151 :: Background256-color256_b_151 = Background256 . code $ "48;5;151"--color256_b_152 :: Background256-color256_b_152 = Background256 . code $ "48;5;152"--color256_b_153 :: Background256-color256_b_153 = Background256 . code $ "48;5;153"--color256_b_154 :: Background256-color256_b_154 = Background256 . code $ "48;5;154"--color256_b_155 :: Background256-color256_b_155 = Background256 . code $ "48;5;155"--color256_b_156 :: Background256-color256_b_156 = Background256 . code $ "48;5;156"--color256_b_157 :: Background256-color256_b_157 = Background256 . code $ "48;5;157"--color256_b_158 :: Background256-color256_b_158 = Background256 . code $ "48;5;158"--color256_b_159 :: Background256-color256_b_159 = Background256 . code $ "48;5;159"--color256_b_160 :: Background256-color256_b_160 = Background256 . code $ "48;5;160"--color256_b_161 :: Background256-color256_b_161 = Background256 . code $ "48;5;161"--color256_b_162 :: Background256-color256_b_162 = Background256 . code $ "48;5;162"--color256_b_163 :: Background256-color256_b_163 = Background256 . code $ "48;5;163"--color256_b_164 :: Background256-color256_b_164 = Background256 . code $ "48;5;164"--color256_b_165 :: Background256-color256_b_165 = Background256 . code $ "48;5;165"--color256_b_166 :: Background256-color256_b_166 = Background256 . code $ "48;5;166"--color256_b_167 :: Background256-color256_b_167 = Background256 . code $ "48;5;167"--color256_b_168 :: Background256-color256_b_168 = Background256 . code $ "48;5;168"--color256_b_169 :: Background256-color256_b_169 = Background256 . code $ "48;5;169"--color256_b_170 :: Background256-color256_b_170 = Background256 . code $ "48;5;170"--color256_b_171 :: Background256-color256_b_171 = Background256 . code $ "48;5;171"--color256_b_172 :: Background256-color256_b_172 = Background256 . code $ "48;5;172"--color256_b_173 :: Background256-color256_b_173 = Background256 . code $ "48;5;173"--color256_b_174 :: Background256-color256_b_174 = Background256 . code $ "48;5;174"--color256_b_175 :: Background256-color256_b_175 = Background256 . code $ "48;5;175"--color256_b_176 :: Background256-color256_b_176 = Background256 . code $ "48;5;176"--color256_b_177 :: Background256-color256_b_177 = Background256 . code $ "48;5;177"--color256_b_178 :: Background256-color256_b_178 = Background256 . code $ "48;5;178"--color256_b_179 :: Background256-color256_b_179 = Background256 . code $ "48;5;179"--color256_b_180 :: Background256-color256_b_180 = Background256 . code $ "48;5;180"--color256_b_181 :: Background256-color256_b_181 = Background256 . code $ "48;5;181"--color256_b_182 :: Background256-color256_b_182 = Background256 . code $ "48;5;182"--color256_b_183 :: Background256-color256_b_183 = Background256 . code $ "48;5;183"--color256_b_184 :: Background256-color256_b_184 = Background256 . code $ "48;5;184"--color256_b_185 :: Background256-color256_b_185 = Background256 . code $ "48;5;185"--color256_b_186 :: Background256-color256_b_186 = Background256 . code $ "48;5;186"--color256_b_187 :: Background256-color256_b_187 = Background256 . code $ "48;5;187"--color256_b_188 :: Background256-color256_b_188 = Background256 . code $ "48;5;188"--color256_b_189 :: Background256-color256_b_189 = Background256 . code $ "48;5;189"--color256_b_190 :: Background256-color256_b_190 = Background256 . code $ "48;5;190"--color256_b_191 :: Background256-color256_b_191 = Background256 . code $ "48;5;191"--color256_b_192 :: Background256-color256_b_192 = Background256 . code $ "48;5;192"--color256_b_193 :: Background256-color256_b_193 = Background256 . code $ "48;5;193"--color256_b_194 :: Background256-color256_b_194 = Background256 . code $ "48;5;194"--color256_b_195 :: Background256-color256_b_195 = Background256 . code $ "48;5;195"--color256_b_196 :: Background256-color256_b_196 = Background256 . code $ "48;5;196"--color256_b_197 :: Background256-color256_b_197 = Background256 . code $ "48;5;197"--color256_b_198 :: Background256-color256_b_198 = Background256 . code $ "48;5;198"--color256_b_199 :: Background256-color256_b_199 = Background256 . code $ "48;5;199"--color256_b_200 :: Background256-color256_b_200 = Background256 . code $ "48;5;200"--color256_b_201 :: Background256-color256_b_201 = Background256 . code $ "48;5;201"--color256_b_202 :: Background256-color256_b_202 = Background256 . code $ "48;5;202"--color256_b_203 :: Background256-color256_b_203 = Background256 . code $ "48;5;203"--color256_b_204 :: Background256-color256_b_204 = Background256 . code $ "48;5;204"--color256_b_205 :: Background256-color256_b_205 = Background256 . code $ "48;5;205"--color256_b_206 :: Background256-color256_b_206 = Background256 . code $ "48;5;206"--color256_b_207 :: Background256-color256_b_207 = Background256 . code $ "48;5;207"--color256_b_208 :: Background256-color256_b_208 = Background256 . code $ "48;5;208"--color256_b_209 :: Background256-color256_b_209 = Background256 . code $ "48;5;209"--color256_b_210 :: Background256-color256_b_210 = Background256 . code $ "48;5;210"--color256_b_211 :: Background256-color256_b_211 = Background256 . code $ "48;5;211"--color256_b_212 :: Background256-color256_b_212 = Background256 . code $ "48;5;212"--color256_b_213 :: Background256-color256_b_213 = Background256 . code $ "48;5;213"--color256_b_214 :: Background256-color256_b_214 = Background256 . code $ "48;5;214"--color256_b_215 :: Background256-color256_b_215 = Background256 . code $ "48;5;215"--color256_b_216 :: Background256-color256_b_216 = Background256 . code $ "48;5;216"--color256_b_217 :: Background256-color256_b_217 = Background256 . code $ "48;5;217"--color256_b_218 :: Background256-color256_b_218 = Background256 . code $ "48;5;218"--color256_b_219 :: Background256-color256_b_219 = Background256 . code $ "48;5;219"--color256_b_220 :: Background256-color256_b_220 = Background256 . code $ "48;5;220"--color256_b_221 :: Background256-color256_b_221 = Background256 . code $ "48;5;221"--color256_b_222 :: Background256-color256_b_222 = Background256 . code $ "48;5;222"--color256_b_223 :: Background256-color256_b_223 = Background256 . code $ "48;5;223"--color256_b_224 :: Background256-color256_b_224 = Background256 . code $ "48;5;224"--color256_b_225 :: Background256-color256_b_225 = Background256 . code $ "48;5;225"--color256_b_226 :: Background256-color256_b_226 = Background256 . code $ "48;5;226"--color256_b_227 :: Background256-color256_b_227 = Background256 . code $ "48;5;227"--color256_b_228 :: Background256-color256_b_228 = Background256 . code $ "48;5;228"--color256_b_229 :: Background256-color256_b_229 = Background256 . code $ "48;5;229"--color256_b_230 :: Background256-color256_b_230 = Background256 . code $ "48;5;230"--color256_b_231 :: Background256-color256_b_231 = Background256 . code $ "48;5;231"--color256_b_232 :: Background256-color256_b_232 = Background256 . code $ "48;5;232"--color256_b_233 :: Background256-color256_b_233 = Background256 . code $ "48;5;233"--color256_b_234 :: Background256-color256_b_234 = Background256 . code $ "48;5;234"--color256_b_235 :: Background256-color256_b_235 = Background256 . code $ "48;5;235"--color256_b_236 :: Background256-color256_b_236 = Background256 . code $ "48;5;236"--color256_b_237 :: Background256-color256_b_237 = Background256 . code $ "48;5;237"--color256_b_238 :: Background256-color256_b_238 = Background256 . code $ "48;5;238"--color256_b_239 :: Background256-color256_b_239 = Background256 . code $ "48;5;239"--color256_b_240 :: Background256-color256_b_240 = Background256 . code $ "48;5;240"--color256_b_241 :: Background256-color256_b_241 = Background256 . code $ "48;5;241"--color256_b_242 :: Background256-color256_b_242 = Background256 . code $ "48;5;242"--color256_b_243 :: Background256-color256_b_243 = Background256 . code $ "48;5;243"--color256_b_244 :: Background256-color256_b_244 = Background256 . code $ "48;5;244"--color256_b_245 :: Background256-color256_b_245 = Background256 . code $ "48;5;245"--color256_b_246 :: Background256-color256_b_246 = Background256 . code $ "48;5;246"--color256_b_247 :: Background256-color256_b_247 = Background256 . code $ "48;5;247"--color256_b_248 :: Background256-color256_b_248 = Background256 . code $ "48;5;248"--color256_b_249 :: Background256-color256_b_249 = Background256 . code $ "48;5;249"--color256_b_250 :: Background256-color256_b_250 = Background256 . code $ "48;5;250"--color256_b_251 :: Background256-color256_b_251 = Background256 . code $ "48;5;251"--color256_b_252 :: Background256-color256_b_252 = Background256 . code $ "48;5;252"--color256_b_253 :: Background256-color256_b_253 = Background256 . code $ "48;5;253"--color256_b_254 :: Background256-color256_b_254 = Background256 . code $ "48;5;254"--color256_b_255 :: Background256-color256_b_255 = Background256 . code $ "48;5;255"+-- | Handles colors and special effects for text. Internally this+-- module uses the Haskell terminfo library, which links against the+-- UNIX library of the same name, so it should work with a wide+-- variety of UNIX terminals. This module is a layer between terminfo+-- and the rest of Penny, allowing the Chunk internals to be swapped+-- out and replaced. (I know this because this module previously used+-- a home-grown implementation written based on the Xterm+-- configuration; that implementation was swapped out and replaced+-- with terminfo with minimal disruption.)+--+-- However, terminfo is a UNIX thing, so at this time Penny probably+-- would not work on a Windows based system.+--+-- There is an ansi-terminal library on Hackage, which does support+-- Windows. One problem with that though is that ansi-terminal does+-- not appear to support more than 8 colors; Chunk supports 256+-- colors, which is very helpful for Penny.+module Penny.Cabin.Chunk (+ -- * Colors+ Term(..),+ autoTerm,+ termFromEnv,+ Background8,+ Background256,+ Foreground8,+ Foreground256,++ -- * Chunks+ Chunk,+ chunk,+ Width(Width, unWidth),+ chunkWidth,+ printChunks,++ -- * Effects+ Bold(Bold, unBold),+ Underline(Underline, unUnderline),+ Flash(Flash, unFlash),+ Inverse(Inverse, unInverse),++ -- * Style and TextSpec++ -- | A style is a bundle of attributes that describes text+ -- attributes, such as its color and whether it is bold.+ StyleCommon (StyleCommon, bold, underline, flash, inverse),+ Style8 (Style8, foreground8, background8, common8),+ Style256 (Style256, foreground256, background256, common256),+ defaultStyleCommon,+ defaultStyle8,+ defaultStyle256,++ TextSpec (TextSpec, style8, style256),+ defaultTextSpec,++ -- * Specific colors+ -- * 8 color foreground colors+ color8_f_default,+ color8_f_black,+ color8_f_red,+ color8_f_green,+ color8_f_yellow,+ color8_f_blue,+ color8_f_magenta,+ color8_f_cyan,+ color8_f_white,++ -- ** 8 color background colors+ color8_b_default,+ color8_b_black,+ color8_b_red,+ color8_b_green,+ color8_b_yellow,+ color8_b_blue,+ color8_b_magenta,+ color8_b_cyan,+ color8_b_white,++ -- * 256 color foreground colors+ color256_f_default,+ color256_f_0,+ color256_f_1,+ color256_f_2,+ color256_f_3,+ color256_f_4,+ color256_f_5,+ color256_f_6,+ color256_f_7,+ color256_f_8,+ color256_f_9,+ color256_f_10,+ color256_f_11,+ color256_f_12,+ color256_f_13,+ color256_f_14,+ color256_f_15,+ color256_f_16,+ color256_f_17,+ color256_f_18,+ color256_f_19,+ color256_f_20,+ color256_f_21,+ color256_f_22,+ color256_f_23,+ color256_f_24,+ color256_f_25,+ color256_f_26,+ color256_f_27,+ color256_f_28,+ color256_f_29,+ color256_f_30,+ color256_f_31,+ color256_f_32,+ color256_f_33,+ color256_f_34,+ color256_f_35,+ color256_f_36,+ color256_f_37,+ color256_f_38,+ color256_f_39,+ color256_f_40,+ color256_f_41,+ color256_f_42,+ color256_f_43,+ color256_f_44,+ color256_f_45,+ color256_f_46,+ color256_f_47,+ color256_f_48,+ color256_f_49,+ color256_f_50,+ color256_f_51,+ color256_f_52,+ color256_f_53,+ color256_f_54,+ color256_f_55,+ color256_f_56,+ color256_f_57,+ color256_f_58,+ color256_f_59,+ color256_f_60,+ color256_f_61,+ color256_f_62,+ color256_f_63,+ color256_f_64,+ color256_f_65,+ color256_f_66,+ color256_f_67,+ color256_f_68,+ color256_f_69,+ color256_f_70,+ color256_f_71,+ color256_f_72,+ color256_f_73,+ color256_f_74,+ color256_f_75,+ color256_f_76,+ color256_f_77,+ color256_f_78,+ color256_f_79,+ color256_f_80,+ color256_f_81,+ color256_f_82,+ color256_f_83,+ color256_f_84,+ color256_f_85,+ color256_f_86,+ color256_f_87,+ color256_f_88,+ color256_f_89,+ color256_f_90,+ color256_f_91,+ color256_f_92,+ color256_f_93,+ color256_f_94,+ color256_f_95,+ color256_f_96,+ color256_f_97,+ color256_f_98,+ color256_f_99,+ color256_f_100,+ color256_f_101,+ color256_f_102,+ color256_f_103,+ color256_f_104,+ color256_f_105,+ color256_f_106,+ color256_f_107,+ color256_f_108,+ color256_f_109,+ color256_f_110,+ color256_f_111,+ color256_f_112,+ color256_f_113,+ color256_f_114,+ color256_f_115,+ color256_f_116,+ color256_f_117,+ color256_f_118,+ color256_f_119,+ color256_f_120,+ color256_f_121,+ color256_f_122,+ color256_f_123,+ color256_f_124,+ color256_f_125,+ color256_f_126,+ color256_f_127,+ color256_f_128,+ color256_f_129,+ color256_f_130,+ color256_f_131,+ color256_f_132,+ color256_f_133,+ color256_f_134,+ color256_f_135,+ color256_f_136,+ color256_f_137,+ color256_f_138,+ color256_f_139,+ color256_f_140,+ color256_f_141,+ color256_f_142,+ color256_f_143,+ color256_f_144,+ color256_f_145,+ color256_f_146,+ color256_f_147,+ color256_f_148,+ color256_f_149,+ color256_f_150,+ color256_f_151,+ color256_f_152,+ color256_f_153,+ color256_f_154,+ color256_f_155,+ color256_f_156,+ color256_f_157,+ color256_f_158,+ color256_f_159,+ color256_f_160,+ color256_f_161,+ color256_f_162,+ color256_f_163,+ color256_f_164,+ color256_f_165,+ color256_f_166,+ color256_f_167,+ color256_f_168,+ color256_f_169,+ color256_f_170,+ color256_f_171,+ color256_f_172,+ color256_f_173,+ color256_f_174,+ color256_f_175,+ color256_f_176,+ color256_f_177,+ color256_f_178,+ color256_f_179,+ color256_f_180,+ color256_f_181,+ color256_f_182,+ color256_f_183,+ color256_f_184,+ color256_f_185,+ color256_f_186,+ color256_f_187,+ color256_f_188,+ color256_f_189,+ color256_f_190,+ color256_f_191,+ color256_f_192,+ color256_f_193,+ color256_f_194,+ color256_f_195,+ color256_f_196,+ color256_f_197,+ color256_f_198,+ color256_f_199,+ color256_f_200,+ color256_f_201,+ color256_f_202,+ color256_f_203,+ color256_f_204,+ color256_f_205,+ color256_f_206,+ color256_f_207,+ color256_f_208,+ color256_f_209,+ color256_f_210,+ color256_f_211,+ color256_f_212,+ color256_f_213,+ color256_f_214,+ color256_f_215,+ color256_f_216,+ color256_f_217,+ color256_f_218,+ color256_f_219,+ color256_f_220,+ color256_f_221,+ color256_f_222,+ color256_f_223,+ color256_f_224,+ color256_f_225,+ color256_f_226,+ color256_f_227,+ color256_f_228,+ color256_f_229,+ color256_f_230,+ color256_f_231,+ color256_f_232,+ color256_f_233,+ color256_f_234,+ color256_f_235,+ color256_f_236,+ color256_f_237,+ color256_f_238,+ color256_f_239,+ color256_f_240,+ color256_f_241,+ color256_f_242,+ color256_f_243,+ color256_f_244,+ color256_f_245,+ color256_f_246,+ color256_f_247,+ color256_f_248,+ color256_f_249,+ color256_f_250,+ color256_f_251,+ color256_f_252,+ color256_f_253,+ color256_f_254,+ color256_f_255,++ -- ** 256 color background colors+ color256_b_default,+ color256_b_0,+ color256_b_1,+ color256_b_2,+ color256_b_3,+ color256_b_4,+ color256_b_5,+ color256_b_6,+ color256_b_7,+ color256_b_8,+ color256_b_9,+ color256_b_10,+ color256_b_11,+ color256_b_12,+ color256_b_13,+ color256_b_14,+ color256_b_15,+ color256_b_16,+ color256_b_17,+ color256_b_18,+ color256_b_19,+ color256_b_20,+ color256_b_21,+ color256_b_22,+ color256_b_23,+ color256_b_24,+ color256_b_25,+ color256_b_26,+ color256_b_27,+ color256_b_28,+ color256_b_29,+ color256_b_30,+ color256_b_31,+ color256_b_32,+ color256_b_33,+ color256_b_34,+ color256_b_35,+ color256_b_36,+ color256_b_37,+ color256_b_38,+ color256_b_39,+ color256_b_40,+ color256_b_41,+ color256_b_42,+ color256_b_43,+ color256_b_44,+ color256_b_45,+ color256_b_46,+ color256_b_47,+ color256_b_48,+ color256_b_49,+ color256_b_50,+ color256_b_51,+ color256_b_52,+ color256_b_53,+ color256_b_54,+ color256_b_55,+ color256_b_56,+ color256_b_57,+ color256_b_58,+ color256_b_59,+ color256_b_60,+ color256_b_61,+ color256_b_62,+ color256_b_63,+ color256_b_64,+ color256_b_65,+ color256_b_66,+ color256_b_67,+ color256_b_68,+ color256_b_69,+ color256_b_70,+ color256_b_71,+ color256_b_72,+ color256_b_73,+ color256_b_74,+ color256_b_75,+ color256_b_76,+ color256_b_77,+ color256_b_78,+ color256_b_79,+ color256_b_80,+ color256_b_81,+ color256_b_82,+ color256_b_83,+ color256_b_84,+ color256_b_85,+ color256_b_86,+ color256_b_87,+ color256_b_88,+ color256_b_89,+ color256_b_90,+ color256_b_91,+ color256_b_92,+ color256_b_93,+ color256_b_94,+ color256_b_95,+ color256_b_96,+ color256_b_97,+ color256_b_98,+ color256_b_99,+ color256_b_100,+ color256_b_101,+ color256_b_102,+ color256_b_103,+ color256_b_104,+ color256_b_105,+ color256_b_106,+ color256_b_107,+ color256_b_108,+ color256_b_109,+ color256_b_110,+ color256_b_111,+ color256_b_112,+ color256_b_113,+ color256_b_114,+ color256_b_115,+ color256_b_116,+ color256_b_117,+ color256_b_118,+ color256_b_119,+ color256_b_120,+ color256_b_121,+ color256_b_122,+ color256_b_123,+ color256_b_124,+ color256_b_125,+ color256_b_126,+ color256_b_127,+ color256_b_128,+ color256_b_129,+ color256_b_130,+ color256_b_131,+ color256_b_132,+ color256_b_133,+ color256_b_134,+ color256_b_135,+ color256_b_136,+ color256_b_137,+ color256_b_138,+ color256_b_139,+ color256_b_140,+ color256_b_141,+ color256_b_142,+ color256_b_143,+ color256_b_144,+ color256_b_145,+ color256_b_146,+ color256_b_147,+ color256_b_148,+ color256_b_149,+ color256_b_150,+ color256_b_151,+ color256_b_152,+ color256_b_153,+ color256_b_154,+ color256_b_155,+ color256_b_156,+ color256_b_157,+ color256_b_158,+ color256_b_159,+ color256_b_160,+ color256_b_161,+ color256_b_162,+ color256_b_163,+ color256_b_164,+ color256_b_165,+ color256_b_166,+ color256_b_167,+ color256_b_168,+ color256_b_169,+ color256_b_170,+ color256_b_171,+ color256_b_172,+ color256_b_173,+ color256_b_174,+ color256_b_175,+ color256_b_176,+ color256_b_177,+ color256_b_178,+ color256_b_179,+ color256_b_180,+ color256_b_181,+ color256_b_182,+ color256_b_183,+ color256_b_184,+ color256_b_185,+ color256_b_186,+ color256_b_187,+ color256_b_188,+ color256_b_189,+ color256_b_190,+ color256_b_191,+ color256_b_192,+ color256_b_193,+ color256_b_194,+ color256_b_195,+ color256_b_196,+ color256_b_197,+ color256_b_198,+ color256_b_199,+ color256_b_200,+ color256_b_201,+ color256_b_202,+ color256_b_203,+ color256_b_204,+ color256_b_205,+ color256_b_206,+ color256_b_207,+ color256_b_208,+ color256_b_209,+ color256_b_210,+ color256_b_211,+ color256_b_212,+ color256_b_213,+ color256_b_214,+ color256_b_215,+ color256_b_216,+ color256_b_217,+ color256_b_218,+ color256_b_219,+ color256_b_220,+ color256_b_221,+ color256_b_222,+ color256_b_223,+ color256_b_224,+ color256_b_225,+ color256_b_226,+ color256_b_227,+ color256_b_228,+ color256_b_229,+ color256_b_230,+ color256_b_231,+ color256_b_232,+ color256_b_233,+ color256_b_234,+ color256_b_235,+ color256_b_236,+ color256_b_237,+ color256_b_238,+ color256_b_239,+ color256_b_240,+ color256_b_241,+ color256_b_242,+ color256_b_243,+ color256_b_244,+ color256_b_245,+ color256_b_246,+ color256_b_247,+ color256_b_248,+ color256_b_249,+ color256_b_250,+ color256_b_251,+ color256_b_252,+ color256_b_253,+ color256_b_254,+ color256_b_255++ ) where+++import Data.Monoid (Monoid, mempty, mappend, mconcat)+import Data.Text (Text)+import Data.Maybe (fromMaybe)+import qualified Data.Text as X+import qualified System.Console.Terminfo as T+import qualified Penny.Shield as S++--+-- Colors+--++-- | Which terminal definition to use.+data Term+ = Dumb+ -- ^ Using this terminal should always succeed. This suppresses all+ -- colors. Uesful if output is not going to a TTY, or if you just do+ -- not like colors.++ | TermName String+ -- ^ Use the terminal with this given name. You might get this from+ -- the TERM environment variable, or set it explicitly. A runtime+ -- error will result if the terminfo database does not have a+ -- definition for this terminal. If this terminal supports 256+ -- colors, then 256 colors are used. If this terminal supports less+ -- than 256 colors, but at least 8 colors, then 8 colors are+ -- used. Otherwise, no colors are used.+ deriving (Eq, Show)+++-- | Determines which Term to use based on the TERM environment+-- variable, regardless of whether standard output is a+-- terminal. Uses Dumb if TERM is not set.+termFromEnv :: S.Runtime -> Term+termFromEnv rt = case S.term rt of+ Just t -> TermName . S.unTerm $ t+ Nothing -> Dumb++-- | Determines which Term to use based on whether standard output is+-- a terminal. Uses Dumb if standard output is not a terminal;+-- otherwise, uses the TERM environment variable.+autoTerm :: S.Runtime -> Term+autoTerm rt = case S.output rt of+ S.IsTTY -> termFromEnv rt+ S.NotTTY -> Dumb++-- For Background8, Background256, Foreground8, Foreground256: the+-- newtype wraps a Terminfo Color. If Nothing, use the default color.++-- | Background color in an 8 color setting.+newtype Background8 = Background8 { unBackground8 :: Maybe T.Color }+ deriving (Eq, Show, Ord)++-- | Background color in a 256 color setting.+newtype Background256 = Background256 { unBackground256 :: Maybe T.Color }+ deriving (Eq, Show, Ord)++-- | Foreground color in an 8 color setting.+newtype Foreground8 = Foreground8 { unForeground8 :: Maybe T.Color }+ deriving (Eq, Show, Ord)++-- | Foreground color in a 256 color setting.+newtype Foreground256 = Foreground256 { unForeground256 :: Maybe T.Color }+ deriving (Eq, Show, Ord)+++--+-- Chunks+--++-- | A chunk is some textual data coupled with a description of what+-- color the text is, attributes like whether it is bold or+-- underlined, etc. The chunk knows what foreground and background+-- colors and what attributes to use for both an 8 color terminal and+-- a 256 color terminal. To change these attributes and colors, you+-- must make a new chunk.+--+-- There is no way to combine chunks. To print large numbers of+-- chunks, lazily build a list of them and then print them using+-- 'printChunks'.++data Chunk = Chunk TextSpec Text+ deriving (Eq, Show, Ord)++-- | Makes new Chunks.+chunk :: TextSpec -> Text -> Chunk+chunk = Chunk++-- | How wide the text of a chunk is.+newtype Width = Width { unWidth :: Int }+ deriving (Show, Eq, Ord)++instance Monoid Width where+ mempty = Width 0+ mappend (Width w1) (Width w2) = Width $ w1 + w2++chunkWidth :: Chunk -> Width+chunkWidth (Chunk _ t) = Width . X.length $ t++-- | Sends a list of chunks to standard output for printing. Sets up+-- the terminal (this only needs to be done once.) Lazily processes+-- the list of Chunk.+printChunks :: Term -> [Chunk] -> IO ()+printChunks t cs = do+ let setup = case t of+ Dumb -> T.setupTerm "dumb"+ TermName s -> T.setupTerm s+ term <- setup+ mapM_ (printChunk term) cs+ T.runTermOutput term (defaultColors term)+ T.runTermOutput term+ $ case T.getCapability term T.allAttributesOff of+ Nothing -> error $ "Penny.Cabin.Chunk.printChunks: error: "+ ++ "allAttributesOff failed"+ Just s -> s++printChunk :: T.Terminal -> Chunk -> IO ()+printChunk t (Chunk ts x) =+ T.runTermOutput t+ . mconcat+ $ [defaultColors t, codes, txt]+ where+ codes = getTermCodes t ts+ txt = T.termText . X.unpack $ x++defaultColors :: T.Terminal -> T.TermOutput+defaultColors term =+ fromMaybe mempty (T.getCapability term T.restoreDefaultColors)++--+-- Effects+--+newtype Bold = Bold { unBold :: Bool }+ deriving (Show, Eq, Ord)++newtype Underline = Underline { unUnderline :: Bool }+ deriving (Show, Eq, Ord)++newtype Flash = Flash { unFlash :: Bool }+ deriving (Show, Eq, Ord)++newtype Inverse = Inverse { unInverse :: Bool }+ deriving (Show, Eq, Ord)++--+-- Styles+--++-- | Style elements that apply in both 8 and 256 color+-- terminals. However, the elements are described separately for 8 and+-- 256 color terminals, so that the text appearance can change+-- depending on how many colors a terminal has.+data StyleCommon = StyleCommon+ { bold :: Bold+ , underline :: Underline+ , flash :: Flash+ , inverse :: Inverse+ } deriving (Show, Eq, Ord)++-- | Describes text appearance (foreground and background colors, as+-- well as other attributes such as bold) for an 8 color terminal.+data Style8 = Style8+ { foreground8 :: Foreground8+ , background8 :: Background8+ , common8 :: StyleCommon+ } deriving (Show, Eq, Ord)++-- | Describes text appearance (foreground and background colors, as+-- well as other attributes such as bold) for a 256 color terminal.+data Style256 = Style256+ { foreground256 :: Foreground256+ , background256 :: Background256+ , common256 :: StyleCommon+ } deriving (Show, Eq, Ord)++-- | Has all bold, flash, underline, and inverse turned off.+defaultStyleCommon :: StyleCommon+defaultStyleCommon = StyleCommon+ { bold = Bold False+ , underline = Underline False+ , flash = Flash False+ , inverse = Inverse False+ }++-- | Uses the default terminal colors (which will vary depending on+-- the terminal).+defaultStyle8 :: Style8+defaultStyle8 = Style8+ { foreground8 = color8_f_default+ , background8 = color8_b_default+ , common8 = defaultStyleCommon+ }++-- | Uses the default terminal colors (which will vary depending on+-- the terminal).+defaultStyle256 :: Style256+defaultStyle256 = Style256+ { foreground256 = color256_f_default+ , background256 = color256_b_default+ , common256 = defaultStyleCommon+ }++--+-- TextSpec+--++-- | The TextSpec bundles together the styles for the 8 and 256 color+-- terminals, so that the text can be portrayed on any terminal.+data TextSpec = TextSpec+ { style8 :: Style8+ , style256 :: Style256+ } deriving (Show, Eq, Ord)++-- | A TextSpec with the default colors on 8 and 256 color terminals,+-- with all attributes turned off.+defaultTextSpec :: TextSpec+defaultTextSpec = TextSpec+ { style8 = defaultStyle8+ , style256 = defaultStyle256+ }++--+-- Internal+--++commonAttrs :: T.Terminal -> StyleCommon -> T.TermOutput+commonAttrs t s =+ let a = T.Attributes+ { T.standoutAttr = False+ , T.underlineAttr = unUnderline . underline $ s+ , T.reverseAttr = unInverse . inverse $ s+ , T.blinkAttr = unFlash . flash $ s+ , T.dimAttr = False+ , T.boldAttr = unBold . bold $ s+ , T.invisibleAttr = False+ , T.protectedAttr = False+ }+ in case T.getCapability t (T.setAttributes) of+ Nothing -> error $ "Penny.Cabin.Chunk: commonAttrs: "+ ++ "capability failed; should never happen"+ Just f -> f a+++-- | Gets the right set of terminal codes to apply the desired+-- highlighting, bold, underlining, etc. Be sure to apply the+-- attributes first (bold, underlining, etc) and then the+-- colors. Setting the colors first and then the attributes seems to+-- reset the colors, giving blank output.+getTermCodes+ :: T.Terminal+ -> TextSpec+ -> T.TermOutput+getTermCodes t ts = fromMaybe mempty $ do+ cols <- T.getCapability t T.termColors+ let TextSpec s8 s256 = ts+ Style8 f8 b8 c8 = s8+ Style256 f256 b256 c256 = s256+ setFg <- T.getCapability t T.setForegroundColor+ setBg <- T.getCapability t T.setBackgroundColor+ (fg, bg, cm) <- case () of+ _ | cols >= 256 -> Just $ ( unForeground256 f256+ , unBackground256 b256+ , c256 )+ | cols >= 8 -> Just ( unForeground8 f8+ , unBackground8 b8+ , c8)+ | otherwise -> Nothing+ let oFg = maybe mempty setFg fg+ oBg = maybe mempty setBg bg+ oCm = commonAttrs t cm+ return $ mconcat [oCm, oFg, oBg]+++--+-- Color basement+--+color8_f_default :: Foreground8+color8_f_default = Foreground8 Nothing++color8_f_black :: Foreground8+color8_f_black = Foreground8 $ Just T.Black++color8_f_red :: Foreground8+color8_f_red = Foreground8 $ Just T.Red++color8_f_green :: Foreground8+color8_f_green = Foreground8 $ Just T.Green++color8_f_yellow :: Foreground8+color8_f_yellow = Foreground8 $ Just T.Yellow++color8_f_blue :: Foreground8+color8_f_blue = Foreground8 $ Just T.Blue++color8_f_magenta :: Foreground8+color8_f_magenta = Foreground8 $ Just T.Magenta++color8_f_cyan :: Foreground8+color8_f_cyan = Foreground8 $ Just T.Cyan++color8_f_white :: Foreground8+color8_f_white = Foreground8 $ Just T.White++color8_b_default :: Background8+color8_b_default = Background8 Nothing++color8_b_black :: Background8+color8_b_black = Background8 $ Just T.Black++color8_b_red :: Background8+color8_b_red = Background8 $ Just T.Red++color8_b_green :: Background8+color8_b_green = Background8 $ Just T.Green++color8_b_yellow :: Background8+color8_b_yellow = Background8 $ Just T.Yellow++color8_b_blue :: Background8+color8_b_blue = Background8 $ Just T.Blue++color8_b_magenta :: Background8+color8_b_magenta = Background8 $ Just T.Magenta++color8_b_cyan :: Background8+color8_b_cyan = Background8 $ Just T.Cyan++color8_b_white :: Background8+color8_b_white = Background8 $ Just T.White++color256_f_default :: Foreground256+color256_f_default = Foreground256 Nothing++color256_f_0 :: Foreground256+color256_f_0 = Foreground256 (Just (T.ColorNumber 0))++color256_f_1 :: Foreground256+color256_f_1 = Foreground256 (Just (T.ColorNumber 1))++color256_f_2 :: Foreground256+color256_f_2 = Foreground256 (Just (T.ColorNumber 2))++color256_f_3 :: Foreground256+color256_f_3 = Foreground256 (Just (T.ColorNumber 3))++color256_f_4 :: Foreground256+color256_f_4 = Foreground256 (Just (T.ColorNumber 4))++color256_f_5 :: Foreground256+color256_f_5 = Foreground256 (Just (T.ColorNumber 5))++color256_f_6 :: Foreground256+color256_f_6 = Foreground256 (Just (T.ColorNumber 6))++color256_f_7 :: Foreground256+color256_f_7 = Foreground256 (Just (T.ColorNumber 7))++color256_f_8 :: Foreground256+color256_f_8 = Foreground256 (Just (T.ColorNumber 8))++color256_f_9 :: Foreground256+color256_f_9 = Foreground256 (Just (T.ColorNumber 9))++color256_f_10 :: Foreground256+color256_f_10 = Foreground256 (Just (T.ColorNumber 10))++color256_f_11 :: Foreground256+color256_f_11 = Foreground256 (Just (T.ColorNumber 11))++color256_f_12 :: Foreground256+color256_f_12 = Foreground256 (Just (T.ColorNumber 12))++color256_f_13 :: Foreground256+color256_f_13 = Foreground256 (Just (T.ColorNumber 13))++color256_f_14 :: Foreground256+color256_f_14 = Foreground256 (Just (T.ColorNumber 14))++color256_f_15 :: Foreground256+color256_f_15 = Foreground256 (Just (T.ColorNumber 15))++color256_f_16 :: Foreground256+color256_f_16 = Foreground256 (Just (T.ColorNumber 16))++color256_f_17 :: Foreground256+color256_f_17 = Foreground256 (Just (T.ColorNumber 17))++color256_f_18 :: Foreground256+color256_f_18 = Foreground256 (Just (T.ColorNumber 18))++color256_f_19 :: Foreground256+color256_f_19 = Foreground256 (Just (T.ColorNumber 19))++color256_f_20 :: Foreground256+color256_f_20 = Foreground256 (Just (T.ColorNumber 20))++color256_f_21 :: Foreground256+color256_f_21 = Foreground256 (Just (T.ColorNumber 21))++color256_f_22 :: Foreground256+color256_f_22 = Foreground256 (Just (T.ColorNumber 22))++color256_f_23 :: Foreground256+color256_f_23 = Foreground256 (Just (T.ColorNumber 23))++color256_f_24 :: Foreground256+color256_f_24 = Foreground256 (Just (T.ColorNumber 24))++color256_f_25 :: Foreground256+color256_f_25 = Foreground256 (Just (T.ColorNumber 25))++color256_f_26 :: Foreground256+color256_f_26 = Foreground256 (Just (T.ColorNumber 26))++color256_f_27 :: Foreground256+color256_f_27 = Foreground256 (Just (T.ColorNumber 27))++color256_f_28 :: Foreground256+color256_f_28 = Foreground256 (Just (T.ColorNumber 28))++color256_f_29 :: Foreground256+color256_f_29 = Foreground256 (Just (T.ColorNumber 29))++color256_f_30 :: Foreground256+color256_f_30 = Foreground256 (Just (T.ColorNumber 30))++color256_f_31 :: Foreground256+color256_f_31 = Foreground256 (Just (T.ColorNumber 31))++color256_f_32 :: Foreground256+color256_f_32 = Foreground256 (Just (T.ColorNumber 32))++color256_f_33 :: Foreground256+color256_f_33 = Foreground256 (Just (T.ColorNumber 33))++color256_f_34 :: Foreground256+color256_f_34 = Foreground256 (Just (T.ColorNumber 34))++color256_f_35 :: Foreground256+color256_f_35 = Foreground256 (Just (T.ColorNumber 35))++color256_f_36 :: Foreground256+color256_f_36 = Foreground256 (Just (T.ColorNumber 36))++color256_f_37 :: Foreground256+color256_f_37 = Foreground256 (Just (T.ColorNumber 37))++color256_f_38 :: Foreground256+color256_f_38 = Foreground256 (Just (T.ColorNumber 38))++color256_f_39 :: Foreground256+color256_f_39 = Foreground256 (Just (T.ColorNumber 39))++color256_f_40 :: Foreground256+color256_f_40 = Foreground256 (Just (T.ColorNumber 40))++color256_f_41 :: Foreground256+color256_f_41 = Foreground256 (Just (T.ColorNumber 41))++color256_f_42 :: Foreground256+color256_f_42 = Foreground256 (Just (T.ColorNumber 42))++color256_f_43 :: Foreground256+color256_f_43 = Foreground256 (Just (T.ColorNumber 43))++color256_f_44 :: Foreground256+color256_f_44 = Foreground256 (Just (T.ColorNumber 44))++color256_f_45 :: Foreground256+color256_f_45 = Foreground256 (Just (T.ColorNumber 45))++color256_f_46 :: Foreground256+color256_f_46 = Foreground256 (Just (T.ColorNumber 46))++color256_f_47 :: Foreground256+color256_f_47 = Foreground256 (Just (T.ColorNumber 47))++color256_f_48 :: Foreground256+color256_f_48 = Foreground256 (Just (T.ColorNumber 48))++color256_f_49 :: Foreground256+color256_f_49 = Foreground256 (Just (T.ColorNumber 49))++color256_f_50 :: Foreground256+color256_f_50 = Foreground256 (Just (T.ColorNumber 50))++color256_f_51 :: Foreground256+color256_f_51 = Foreground256 (Just (T.ColorNumber 51))++color256_f_52 :: Foreground256+color256_f_52 = Foreground256 (Just (T.ColorNumber 52))++color256_f_53 :: Foreground256+color256_f_53 = Foreground256 (Just (T.ColorNumber 53))++color256_f_54 :: Foreground256+color256_f_54 = Foreground256 (Just (T.ColorNumber 54))++color256_f_55 :: Foreground256+color256_f_55 = Foreground256 (Just (T.ColorNumber 55))++color256_f_56 :: Foreground256+color256_f_56 = Foreground256 (Just (T.ColorNumber 56))++color256_f_57 :: Foreground256+color256_f_57 = Foreground256 (Just (T.ColorNumber 57))++color256_f_58 :: Foreground256+color256_f_58 = Foreground256 (Just (T.ColorNumber 58))++color256_f_59 :: Foreground256+color256_f_59 = Foreground256 (Just (T.ColorNumber 59))++color256_f_60 :: Foreground256+color256_f_60 = Foreground256 (Just (T.ColorNumber 60))++color256_f_61 :: Foreground256+color256_f_61 = Foreground256 (Just (T.ColorNumber 61))++color256_f_62 :: Foreground256+color256_f_62 = Foreground256 (Just (T.ColorNumber 62))++color256_f_63 :: Foreground256+color256_f_63 = Foreground256 (Just (T.ColorNumber 63))++color256_f_64 :: Foreground256+color256_f_64 = Foreground256 (Just (T.ColorNumber 64))++color256_f_65 :: Foreground256+color256_f_65 = Foreground256 (Just (T.ColorNumber 65))++color256_f_66 :: Foreground256+color256_f_66 = Foreground256 (Just (T.ColorNumber 66))++color256_f_67 :: Foreground256+color256_f_67 = Foreground256 (Just (T.ColorNumber 67))++color256_f_68 :: Foreground256+color256_f_68 = Foreground256 (Just (T.ColorNumber 68))++color256_f_69 :: Foreground256+color256_f_69 = Foreground256 (Just (T.ColorNumber 69))++color256_f_70 :: Foreground256+color256_f_70 = Foreground256 (Just (T.ColorNumber 70))++color256_f_71 :: Foreground256+color256_f_71 = Foreground256 (Just (T.ColorNumber 71))++color256_f_72 :: Foreground256+color256_f_72 = Foreground256 (Just (T.ColorNumber 72))++color256_f_73 :: Foreground256+color256_f_73 = Foreground256 (Just (T.ColorNumber 73))++color256_f_74 :: Foreground256+color256_f_74 = Foreground256 (Just (T.ColorNumber 74))++color256_f_75 :: Foreground256+color256_f_75 = Foreground256 (Just (T.ColorNumber 75))++color256_f_76 :: Foreground256+color256_f_76 = Foreground256 (Just (T.ColorNumber 76))++color256_f_77 :: Foreground256+color256_f_77 = Foreground256 (Just (T.ColorNumber 77))++color256_f_78 :: Foreground256+color256_f_78 = Foreground256 (Just (T.ColorNumber 78))++color256_f_79 :: Foreground256+color256_f_79 = Foreground256 (Just (T.ColorNumber 79))++color256_f_80 :: Foreground256+color256_f_80 = Foreground256 (Just (T.ColorNumber 80))++color256_f_81 :: Foreground256+color256_f_81 = Foreground256 (Just (T.ColorNumber 81))++color256_f_82 :: Foreground256+color256_f_82 = Foreground256 (Just (T.ColorNumber 82))++color256_f_83 :: Foreground256+color256_f_83 = Foreground256 (Just (T.ColorNumber 83))++color256_f_84 :: Foreground256+color256_f_84 = Foreground256 (Just (T.ColorNumber 84))++color256_f_85 :: Foreground256+color256_f_85 = Foreground256 (Just (T.ColorNumber 85))++color256_f_86 :: Foreground256+color256_f_86 = Foreground256 (Just (T.ColorNumber 86))++color256_f_87 :: Foreground256+color256_f_87 = Foreground256 (Just (T.ColorNumber 87))++color256_f_88 :: Foreground256+color256_f_88 = Foreground256 (Just (T.ColorNumber 88))++color256_f_89 :: Foreground256+color256_f_89 = Foreground256 (Just (T.ColorNumber 89))++color256_f_90 :: Foreground256+color256_f_90 = Foreground256 (Just (T.ColorNumber 90))++color256_f_91 :: Foreground256+color256_f_91 = Foreground256 (Just (T.ColorNumber 91))++color256_f_92 :: Foreground256+color256_f_92 = Foreground256 (Just (T.ColorNumber 92))++color256_f_93 :: Foreground256+color256_f_93 = Foreground256 (Just (T.ColorNumber 93))++color256_f_94 :: Foreground256+color256_f_94 = Foreground256 (Just (T.ColorNumber 94))++color256_f_95 :: Foreground256+color256_f_95 = Foreground256 (Just (T.ColorNumber 95))++color256_f_96 :: Foreground256+color256_f_96 = Foreground256 (Just (T.ColorNumber 96))++color256_f_97 :: Foreground256+color256_f_97 = Foreground256 (Just (T.ColorNumber 97))++color256_f_98 :: Foreground256+color256_f_98 = Foreground256 (Just (T.ColorNumber 98))++color256_f_99 :: Foreground256+color256_f_99 = Foreground256 (Just (T.ColorNumber 99))++color256_f_100 :: Foreground256+color256_f_100 = Foreground256 (Just (T.ColorNumber 100))++color256_f_101 :: Foreground256+color256_f_101 = Foreground256 (Just (T.ColorNumber 101))++color256_f_102 :: Foreground256+color256_f_102 = Foreground256 (Just (T.ColorNumber 102))++color256_f_103 :: Foreground256+color256_f_103 = Foreground256 (Just (T.ColorNumber 103))++color256_f_104 :: Foreground256+color256_f_104 = Foreground256 (Just (T.ColorNumber 104))++color256_f_105 :: Foreground256+color256_f_105 = Foreground256 (Just (T.ColorNumber 105))++color256_f_106 :: Foreground256+color256_f_106 = Foreground256 (Just (T.ColorNumber 106))++color256_f_107 :: Foreground256+color256_f_107 = Foreground256 (Just (T.ColorNumber 107))++color256_f_108 :: Foreground256+color256_f_108 = Foreground256 (Just (T.ColorNumber 108))++color256_f_109 :: Foreground256+color256_f_109 = Foreground256 (Just (T.ColorNumber 109))++color256_f_110 :: Foreground256+color256_f_110 = Foreground256 (Just (T.ColorNumber 110))++color256_f_111 :: Foreground256+color256_f_111 = Foreground256 (Just (T.ColorNumber 111))++color256_f_112 :: Foreground256+color256_f_112 = Foreground256 (Just (T.ColorNumber 112))++color256_f_113 :: Foreground256+color256_f_113 = Foreground256 (Just (T.ColorNumber 113))++color256_f_114 :: Foreground256+color256_f_114 = Foreground256 (Just (T.ColorNumber 114))++color256_f_115 :: Foreground256+color256_f_115 = Foreground256 (Just (T.ColorNumber 115))++color256_f_116 :: Foreground256+color256_f_116 = Foreground256 (Just (T.ColorNumber 116))++color256_f_117 :: Foreground256+color256_f_117 = Foreground256 (Just (T.ColorNumber 117))++color256_f_118 :: Foreground256+color256_f_118 = Foreground256 (Just (T.ColorNumber 118))++color256_f_119 :: Foreground256+color256_f_119 = Foreground256 (Just (T.ColorNumber 119))++color256_f_120 :: Foreground256+color256_f_120 = Foreground256 (Just (T.ColorNumber 120))++color256_f_121 :: Foreground256+color256_f_121 = Foreground256 (Just (T.ColorNumber 121))++color256_f_122 :: Foreground256+color256_f_122 = Foreground256 (Just (T.ColorNumber 122))++color256_f_123 :: Foreground256+color256_f_123 = Foreground256 (Just (T.ColorNumber 123))++color256_f_124 :: Foreground256+color256_f_124 = Foreground256 (Just (T.ColorNumber 124))++color256_f_125 :: Foreground256+color256_f_125 = Foreground256 (Just (T.ColorNumber 125))++color256_f_126 :: Foreground256+color256_f_126 = Foreground256 (Just (T.ColorNumber 126))++color256_f_127 :: Foreground256+color256_f_127 = Foreground256 (Just (T.ColorNumber 127))++color256_f_128 :: Foreground256+color256_f_128 = Foreground256 (Just (T.ColorNumber 128))++color256_f_129 :: Foreground256+color256_f_129 = Foreground256 (Just (T.ColorNumber 129))++color256_f_130 :: Foreground256+color256_f_130 = Foreground256 (Just (T.ColorNumber 130))++color256_f_131 :: Foreground256+color256_f_131 = Foreground256 (Just (T.ColorNumber 131))++color256_f_132 :: Foreground256+color256_f_132 = Foreground256 (Just (T.ColorNumber 132))++color256_f_133 :: Foreground256+color256_f_133 = Foreground256 (Just (T.ColorNumber 133))++color256_f_134 :: Foreground256+color256_f_134 = Foreground256 (Just (T.ColorNumber 134))++color256_f_135 :: Foreground256+color256_f_135 = Foreground256 (Just (T.ColorNumber 135))++color256_f_136 :: Foreground256+color256_f_136 = Foreground256 (Just (T.ColorNumber 136))++color256_f_137 :: Foreground256+color256_f_137 = Foreground256 (Just (T.ColorNumber 137))++color256_f_138 :: Foreground256+color256_f_138 = Foreground256 (Just (T.ColorNumber 138))++color256_f_139 :: Foreground256+color256_f_139 = Foreground256 (Just (T.ColorNumber 139))++color256_f_140 :: Foreground256+color256_f_140 = Foreground256 (Just (T.ColorNumber 140))++color256_f_141 :: Foreground256+color256_f_141 = Foreground256 (Just (T.ColorNumber 141))++color256_f_142 :: Foreground256+color256_f_142 = Foreground256 (Just (T.ColorNumber 142))++color256_f_143 :: Foreground256+color256_f_143 = Foreground256 (Just (T.ColorNumber 143))++color256_f_144 :: Foreground256+color256_f_144 = Foreground256 (Just (T.ColorNumber 144))++color256_f_145 :: Foreground256+color256_f_145 = Foreground256 (Just (T.ColorNumber 145))++color256_f_146 :: Foreground256+color256_f_146 = Foreground256 (Just (T.ColorNumber 146))++color256_f_147 :: Foreground256+color256_f_147 = Foreground256 (Just (T.ColorNumber 147))++color256_f_148 :: Foreground256+color256_f_148 = Foreground256 (Just (T.ColorNumber 148))++color256_f_149 :: Foreground256+color256_f_149 = Foreground256 (Just (T.ColorNumber 149))++color256_f_150 :: Foreground256+color256_f_150 = Foreground256 (Just (T.ColorNumber 150))++color256_f_151 :: Foreground256+color256_f_151 = Foreground256 (Just (T.ColorNumber 151))++color256_f_152 :: Foreground256+color256_f_152 = Foreground256 (Just (T.ColorNumber 152))++color256_f_153 :: Foreground256+color256_f_153 = Foreground256 (Just (T.ColorNumber 153))++color256_f_154 :: Foreground256+color256_f_154 = Foreground256 (Just (T.ColorNumber 154))++color256_f_155 :: Foreground256+color256_f_155 = Foreground256 (Just (T.ColorNumber 155))++color256_f_156 :: Foreground256+color256_f_156 = Foreground256 (Just (T.ColorNumber 156))++color256_f_157 :: Foreground256+color256_f_157 = Foreground256 (Just (T.ColorNumber 157))++color256_f_158 :: Foreground256+color256_f_158 = Foreground256 (Just (T.ColorNumber 158))++color256_f_159 :: Foreground256+color256_f_159 = Foreground256 (Just (T.ColorNumber 159))++color256_f_160 :: Foreground256+color256_f_160 = Foreground256 (Just (T.ColorNumber 160))++color256_f_161 :: Foreground256+color256_f_161 = Foreground256 (Just (T.ColorNumber 161))++color256_f_162 :: Foreground256+color256_f_162 = Foreground256 (Just (T.ColorNumber 162))++color256_f_163 :: Foreground256+color256_f_163 = Foreground256 (Just (T.ColorNumber 163))++color256_f_164 :: Foreground256+color256_f_164 = Foreground256 (Just (T.ColorNumber 164))++color256_f_165 :: Foreground256+color256_f_165 = Foreground256 (Just (T.ColorNumber 165))++color256_f_166 :: Foreground256+color256_f_166 = Foreground256 (Just (T.ColorNumber 166))++color256_f_167 :: Foreground256+color256_f_167 = Foreground256 (Just (T.ColorNumber 167))++color256_f_168 :: Foreground256+color256_f_168 = Foreground256 (Just (T.ColorNumber 168))++color256_f_169 :: Foreground256+color256_f_169 = Foreground256 (Just (T.ColorNumber 169))++color256_f_170 :: Foreground256+color256_f_170 = Foreground256 (Just (T.ColorNumber 170))++color256_f_171 :: Foreground256+color256_f_171 = Foreground256 (Just (T.ColorNumber 171))++color256_f_172 :: Foreground256+color256_f_172 = Foreground256 (Just (T.ColorNumber 172))++color256_f_173 :: Foreground256+color256_f_173 = Foreground256 (Just (T.ColorNumber 173))++color256_f_174 :: Foreground256+color256_f_174 = Foreground256 (Just (T.ColorNumber 174))++color256_f_175 :: Foreground256+color256_f_175 = Foreground256 (Just (T.ColorNumber 175))++color256_f_176 :: Foreground256+color256_f_176 = Foreground256 (Just (T.ColorNumber 176))++color256_f_177 :: Foreground256+color256_f_177 = Foreground256 (Just (T.ColorNumber 177))++color256_f_178 :: Foreground256+color256_f_178 = Foreground256 (Just (T.ColorNumber 178))++color256_f_179 :: Foreground256+color256_f_179 = Foreground256 (Just (T.ColorNumber 179))++color256_f_180 :: Foreground256+color256_f_180 = Foreground256 (Just (T.ColorNumber 180))++color256_f_181 :: Foreground256+color256_f_181 = Foreground256 (Just (T.ColorNumber 181))++color256_f_182 :: Foreground256+color256_f_182 = Foreground256 (Just (T.ColorNumber 182))++color256_f_183 :: Foreground256+color256_f_183 = Foreground256 (Just (T.ColorNumber 183))++color256_f_184 :: Foreground256+color256_f_184 = Foreground256 (Just (T.ColorNumber 184))++color256_f_185 :: Foreground256+color256_f_185 = Foreground256 (Just (T.ColorNumber 185))++color256_f_186 :: Foreground256+color256_f_186 = Foreground256 (Just (T.ColorNumber 186))++color256_f_187 :: Foreground256+color256_f_187 = Foreground256 (Just (T.ColorNumber 187))++color256_f_188 :: Foreground256+color256_f_188 = Foreground256 (Just (T.ColorNumber 188))++color256_f_189 :: Foreground256+color256_f_189 = Foreground256 (Just (T.ColorNumber 189))++color256_f_190 :: Foreground256+color256_f_190 = Foreground256 (Just (T.ColorNumber 190))++color256_f_191 :: Foreground256+color256_f_191 = Foreground256 (Just (T.ColorNumber 191))++color256_f_192 :: Foreground256+color256_f_192 = Foreground256 (Just (T.ColorNumber 192))++color256_f_193 :: Foreground256+color256_f_193 = Foreground256 (Just (T.ColorNumber 193))++color256_f_194 :: Foreground256+color256_f_194 = Foreground256 (Just (T.ColorNumber 194))++color256_f_195 :: Foreground256+color256_f_195 = Foreground256 (Just (T.ColorNumber 195))++color256_f_196 :: Foreground256+color256_f_196 = Foreground256 (Just (T.ColorNumber 196))++color256_f_197 :: Foreground256+color256_f_197 = Foreground256 (Just (T.ColorNumber 197))++color256_f_198 :: Foreground256+color256_f_198 = Foreground256 (Just (T.ColorNumber 198))++color256_f_199 :: Foreground256+color256_f_199 = Foreground256 (Just (T.ColorNumber 199))++color256_f_200 :: Foreground256+color256_f_200 = Foreground256 (Just (T.ColorNumber 200))++color256_f_201 :: Foreground256+color256_f_201 = Foreground256 (Just (T.ColorNumber 201))++color256_f_202 :: Foreground256+color256_f_202 = Foreground256 (Just (T.ColorNumber 202))++color256_f_203 :: Foreground256+color256_f_203 = Foreground256 (Just (T.ColorNumber 203))++color256_f_204 :: Foreground256+color256_f_204 = Foreground256 (Just (T.ColorNumber 204))++color256_f_205 :: Foreground256+color256_f_205 = Foreground256 (Just (T.ColorNumber 205))++color256_f_206 :: Foreground256+color256_f_206 = Foreground256 (Just (T.ColorNumber 206))++color256_f_207 :: Foreground256+color256_f_207 = Foreground256 (Just (T.ColorNumber 207))++color256_f_208 :: Foreground256+color256_f_208 = Foreground256 (Just (T.ColorNumber 208))++color256_f_209 :: Foreground256+color256_f_209 = Foreground256 (Just (T.ColorNumber 209))++color256_f_210 :: Foreground256+color256_f_210 = Foreground256 (Just (T.ColorNumber 210))++color256_f_211 :: Foreground256+color256_f_211 = Foreground256 (Just (T.ColorNumber 211))++color256_f_212 :: Foreground256+color256_f_212 = Foreground256 (Just (T.ColorNumber 212))++color256_f_213 :: Foreground256+color256_f_213 = Foreground256 (Just (T.ColorNumber 213))++color256_f_214 :: Foreground256+color256_f_214 = Foreground256 (Just (T.ColorNumber 214))++color256_f_215 :: Foreground256+color256_f_215 = Foreground256 (Just (T.ColorNumber 215))++color256_f_216 :: Foreground256+color256_f_216 = Foreground256 (Just (T.ColorNumber 216))++color256_f_217 :: Foreground256+color256_f_217 = Foreground256 (Just (T.ColorNumber 217))++color256_f_218 :: Foreground256+color256_f_218 = Foreground256 (Just (T.ColorNumber 218))++color256_f_219 :: Foreground256+color256_f_219 = Foreground256 (Just (T.ColorNumber 219))++color256_f_220 :: Foreground256+color256_f_220 = Foreground256 (Just (T.ColorNumber 220))++color256_f_221 :: Foreground256+color256_f_221 = Foreground256 (Just (T.ColorNumber 221))++color256_f_222 :: Foreground256+color256_f_222 = Foreground256 (Just (T.ColorNumber 222))++color256_f_223 :: Foreground256+color256_f_223 = Foreground256 (Just (T.ColorNumber 223))++color256_f_224 :: Foreground256+color256_f_224 = Foreground256 (Just (T.ColorNumber 224))++color256_f_225 :: Foreground256+color256_f_225 = Foreground256 (Just (T.ColorNumber 225))++color256_f_226 :: Foreground256+color256_f_226 = Foreground256 (Just (T.ColorNumber 226))++color256_f_227 :: Foreground256+color256_f_227 = Foreground256 (Just (T.ColorNumber 227))++color256_f_228 :: Foreground256+color256_f_228 = Foreground256 (Just (T.ColorNumber 228))++color256_f_229 :: Foreground256+color256_f_229 = Foreground256 (Just (T.ColorNumber 229))++color256_f_230 :: Foreground256+color256_f_230 = Foreground256 (Just (T.ColorNumber 230))++color256_f_231 :: Foreground256+color256_f_231 = Foreground256 (Just (T.ColorNumber 231))++color256_f_232 :: Foreground256+color256_f_232 = Foreground256 (Just (T.ColorNumber 232))++color256_f_233 :: Foreground256+color256_f_233 = Foreground256 (Just (T.ColorNumber 233))++color256_f_234 :: Foreground256+color256_f_234 = Foreground256 (Just (T.ColorNumber 234))++color256_f_235 :: Foreground256+color256_f_235 = Foreground256 (Just (T.ColorNumber 235))++color256_f_236 :: Foreground256+color256_f_236 = Foreground256 (Just (T.ColorNumber 236))++color256_f_237 :: Foreground256+color256_f_237 = Foreground256 (Just (T.ColorNumber 237))++color256_f_238 :: Foreground256+color256_f_238 = Foreground256 (Just (T.ColorNumber 238))++color256_f_239 :: Foreground256+color256_f_239 = Foreground256 (Just (T.ColorNumber 239))++color256_f_240 :: Foreground256+color256_f_240 = Foreground256 (Just (T.ColorNumber 240))++color256_f_241 :: Foreground256+color256_f_241 = Foreground256 (Just (T.ColorNumber 241))++color256_f_242 :: Foreground256+color256_f_242 = Foreground256 (Just (T.ColorNumber 242))++color256_f_243 :: Foreground256+color256_f_243 = Foreground256 (Just (T.ColorNumber 243))++color256_f_244 :: Foreground256+color256_f_244 = Foreground256 (Just (T.ColorNumber 244))++color256_f_245 :: Foreground256+color256_f_245 = Foreground256 (Just (T.ColorNumber 245))++color256_f_246 :: Foreground256+color256_f_246 = Foreground256 (Just (T.ColorNumber 246))++color256_f_247 :: Foreground256+color256_f_247 = Foreground256 (Just (T.ColorNumber 247))++color256_f_248 :: Foreground256+color256_f_248 = Foreground256 (Just (T.ColorNumber 248))++color256_f_249 :: Foreground256+color256_f_249 = Foreground256 (Just (T.ColorNumber 249))++color256_f_250 :: Foreground256+color256_f_250 = Foreground256 (Just (T.ColorNumber 250))++color256_f_251 :: Foreground256+color256_f_251 = Foreground256 (Just (T.ColorNumber 251))++color256_f_252 :: Foreground256+color256_f_252 = Foreground256 (Just (T.ColorNumber 252))++color256_f_253 :: Foreground256+color256_f_253 = Foreground256 (Just (T.ColorNumber 253))++color256_f_254 :: Foreground256+color256_f_254 = Foreground256 (Just (T.ColorNumber 254))++color256_f_255 :: Foreground256+color256_f_255 = Foreground256 (Just (T.ColorNumber 255))++color256_b_default :: Background256+color256_b_default = Background256 Nothing++color256_b_0 :: Background256+color256_b_0 = Background256 (Just (T.ColorNumber 0))++color256_b_1 :: Background256+color256_b_1 = Background256 (Just (T.ColorNumber 1))++color256_b_2 :: Background256+color256_b_2 = Background256 (Just (T.ColorNumber 2))++color256_b_3 :: Background256+color256_b_3 = Background256 (Just (T.ColorNumber 3))++color256_b_4 :: Background256+color256_b_4 = Background256 (Just (T.ColorNumber 4))++color256_b_5 :: Background256+color256_b_5 = Background256 (Just (T.ColorNumber 5))++color256_b_6 :: Background256+color256_b_6 = Background256 (Just (T.ColorNumber 6))++color256_b_7 :: Background256+color256_b_7 = Background256 (Just (T.ColorNumber 7))++color256_b_8 :: Background256+color256_b_8 = Background256 (Just (T.ColorNumber 8))++color256_b_9 :: Background256+color256_b_9 = Background256 (Just (T.ColorNumber 9))++color256_b_10 :: Background256+color256_b_10 = Background256 (Just (T.ColorNumber 10))++color256_b_11 :: Background256+color256_b_11 = Background256 (Just (T.ColorNumber 11))++color256_b_12 :: Background256+color256_b_12 = Background256 (Just (T.ColorNumber 12))++color256_b_13 :: Background256+color256_b_13 = Background256 (Just (T.ColorNumber 13))++color256_b_14 :: Background256+color256_b_14 = Background256 (Just (T.ColorNumber 14))++color256_b_15 :: Background256+color256_b_15 = Background256 (Just (T.ColorNumber 15))++color256_b_16 :: Background256+color256_b_16 = Background256 (Just (T.ColorNumber 16))++color256_b_17 :: Background256+color256_b_17 = Background256 (Just (T.ColorNumber 17))++color256_b_18 :: Background256+color256_b_18 = Background256 (Just (T.ColorNumber 18))++color256_b_19 :: Background256+color256_b_19 = Background256 (Just (T.ColorNumber 19))++color256_b_20 :: Background256+color256_b_20 = Background256 (Just (T.ColorNumber 20))++color256_b_21 :: Background256+color256_b_21 = Background256 (Just (T.ColorNumber 21))++color256_b_22 :: Background256+color256_b_22 = Background256 (Just (T.ColorNumber 22))++color256_b_23 :: Background256+color256_b_23 = Background256 (Just (T.ColorNumber 23))++color256_b_24 :: Background256+color256_b_24 = Background256 (Just (T.ColorNumber 24))++color256_b_25 :: Background256+color256_b_25 = Background256 (Just (T.ColorNumber 25))++color256_b_26 :: Background256+color256_b_26 = Background256 (Just (T.ColorNumber 26))++color256_b_27 :: Background256+color256_b_27 = Background256 (Just (T.ColorNumber 27))++color256_b_28 :: Background256+color256_b_28 = Background256 (Just (T.ColorNumber 28))++color256_b_29 :: Background256+color256_b_29 = Background256 (Just (T.ColorNumber 29))++color256_b_30 :: Background256+color256_b_30 = Background256 (Just (T.ColorNumber 30))++color256_b_31 :: Background256+color256_b_31 = Background256 (Just (T.ColorNumber 31))++color256_b_32 :: Background256+color256_b_32 = Background256 (Just (T.ColorNumber 32))++color256_b_33 :: Background256+color256_b_33 = Background256 (Just (T.ColorNumber 33))++color256_b_34 :: Background256+color256_b_34 = Background256 (Just (T.ColorNumber 34))++color256_b_35 :: Background256+color256_b_35 = Background256 (Just (T.ColorNumber 35))++color256_b_36 :: Background256+color256_b_36 = Background256 (Just (T.ColorNumber 36))++color256_b_37 :: Background256+color256_b_37 = Background256 (Just (T.ColorNumber 37))++color256_b_38 :: Background256+color256_b_38 = Background256 (Just (T.ColorNumber 38))++color256_b_39 :: Background256+color256_b_39 = Background256 (Just (T.ColorNumber 39))++color256_b_40 :: Background256+color256_b_40 = Background256 (Just (T.ColorNumber 40))++color256_b_41 :: Background256+color256_b_41 = Background256 (Just (T.ColorNumber 41))++color256_b_42 :: Background256+color256_b_42 = Background256 (Just (T.ColorNumber 42))++color256_b_43 :: Background256+color256_b_43 = Background256 (Just (T.ColorNumber 43))++color256_b_44 :: Background256+color256_b_44 = Background256 (Just (T.ColorNumber 44))++color256_b_45 :: Background256+color256_b_45 = Background256 (Just (T.ColorNumber 45))++color256_b_46 :: Background256+color256_b_46 = Background256 (Just (T.ColorNumber 46))++color256_b_47 :: Background256+color256_b_47 = Background256 (Just (T.ColorNumber 47))++color256_b_48 :: Background256+color256_b_48 = Background256 (Just (T.ColorNumber 48))++color256_b_49 :: Background256+color256_b_49 = Background256 (Just (T.ColorNumber 49))++color256_b_50 :: Background256+color256_b_50 = Background256 (Just (T.ColorNumber 50))++color256_b_51 :: Background256+color256_b_51 = Background256 (Just (T.ColorNumber 51))++color256_b_52 :: Background256+color256_b_52 = Background256 (Just (T.ColorNumber 52))++color256_b_53 :: Background256+color256_b_53 = Background256 (Just (T.ColorNumber 53))++color256_b_54 :: Background256+color256_b_54 = Background256 (Just (T.ColorNumber 54))++color256_b_55 :: Background256+color256_b_55 = Background256 (Just (T.ColorNumber 55))++color256_b_56 :: Background256+color256_b_56 = Background256 (Just (T.ColorNumber 56))++color256_b_57 :: Background256+color256_b_57 = Background256 (Just (T.ColorNumber 57))++color256_b_58 :: Background256+color256_b_58 = Background256 (Just (T.ColorNumber 58))++color256_b_59 :: Background256+color256_b_59 = Background256 (Just (T.ColorNumber 59))++color256_b_60 :: Background256+color256_b_60 = Background256 (Just (T.ColorNumber 60))++color256_b_61 :: Background256+color256_b_61 = Background256 (Just (T.ColorNumber 61))++color256_b_62 :: Background256+color256_b_62 = Background256 (Just (T.ColorNumber 62))++color256_b_63 :: Background256+color256_b_63 = Background256 (Just (T.ColorNumber 63))++color256_b_64 :: Background256+color256_b_64 = Background256 (Just (T.ColorNumber 64))++color256_b_65 :: Background256+color256_b_65 = Background256 (Just (T.ColorNumber 65))++color256_b_66 :: Background256+color256_b_66 = Background256 (Just (T.ColorNumber 66))++color256_b_67 :: Background256+color256_b_67 = Background256 (Just (T.ColorNumber 67))++color256_b_68 :: Background256+color256_b_68 = Background256 (Just (T.ColorNumber 68))++color256_b_69 :: Background256+color256_b_69 = Background256 (Just (T.ColorNumber 69))++color256_b_70 :: Background256+color256_b_70 = Background256 (Just (T.ColorNumber 70))++color256_b_71 :: Background256+color256_b_71 = Background256 (Just (T.ColorNumber 71))++color256_b_72 :: Background256+color256_b_72 = Background256 (Just (T.ColorNumber 72))++color256_b_73 :: Background256+color256_b_73 = Background256 (Just (T.ColorNumber 73))++color256_b_74 :: Background256+color256_b_74 = Background256 (Just (T.ColorNumber 74))++color256_b_75 :: Background256+color256_b_75 = Background256 (Just (T.ColorNumber 75))++color256_b_76 :: Background256+color256_b_76 = Background256 (Just (T.ColorNumber 76))++color256_b_77 :: Background256+color256_b_77 = Background256 (Just (T.ColorNumber 77))++color256_b_78 :: Background256+color256_b_78 = Background256 (Just (T.ColorNumber 78))++color256_b_79 :: Background256+color256_b_79 = Background256 (Just (T.ColorNumber 79))++color256_b_80 :: Background256+color256_b_80 = Background256 (Just (T.ColorNumber 80))++color256_b_81 :: Background256+color256_b_81 = Background256 (Just (T.ColorNumber 81))++color256_b_82 :: Background256+color256_b_82 = Background256 (Just (T.ColorNumber 82))++color256_b_83 :: Background256+color256_b_83 = Background256 (Just (T.ColorNumber 83))++color256_b_84 :: Background256+color256_b_84 = Background256 (Just (T.ColorNumber 84))++color256_b_85 :: Background256+color256_b_85 = Background256 (Just (T.ColorNumber 85))++color256_b_86 :: Background256+color256_b_86 = Background256 (Just (T.ColorNumber 86))++color256_b_87 :: Background256+color256_b_87 = Background256 (Just (T.ColorNumber 87))++color256_b_88 :: Background256+color256_b_88 = Background256 (Just (T.ColorNumber 88))++color256_b_89 :: Background256+color256_b_89 = Background256 (Just (T.ColorNumber 89))++color256_b_90 :: Background256+color256_b_90 = Background256 (Just (T.ColorNumber 90))++color256_b_91 :: Background256+color256_b_91 = Background256 (Just (T.ColorNumber 91))++color256_b_92 :: Background256+color256_b_92 = Background256 (Just (T.ColorNumber 92))++color256_b_93 :: Background256+color256_b_93 = Background256 (Just (T.ColorNumber 93))++color256_b_94 :: Background256+color256_b_94 = Background256 (Just (T.ColorNumber 94))++color256_b_95 :: Background256+color256_b_95 = Background256 (Just (T.ColorNumber 95))++color256_b_96 :: Background256+color256_b_96 = Background256 (Just (T.ColorNumber 96))++color256_b_97 :: Background256+color256_b_97 = Background256 (Just (T.ColorNumber 97))++color256_b_98 :: Background256+color256_b_98 = Background256 (Just (T.ColorNumber 98))++color256_b_99 :: Background256+color256_b_99 = Background256 (Just (T.ColorNumber 99))++color256_b_100 :: Background256+color256_b_100 = Background256 (Just (T.ColorNumber 100))++color256_b_101 :: Background256+color256_b_101 = Background256 (Just (T.ColorNumber 101))++color256_b_102 :: Background256+color256_b_102 = Background256 (Just (T.ColorNumber 102))++color256_b_103 :: Background256+color256_b_103 = Background256 (Just (T.ColorNumber 103))++color256_b_104 :: Background256+color256_b_104 = Background256 (Just (T.ColorNumber 104))++color256_b_105 :: Background256+color256_b_105 = Background256 (Just (T.ColorNumber 105))++color256_b_106 :: Background256+color256_b_106 = Background256 (Just (T.ColorNumber 106))++color256_b_107 :: Background256+color256_b_107 = Background256 (Just (T.ColorNumber 107))++color256_b_108 :: Background256+color256_b_108 = Background256 (Just (T.ColorNumber 108))++color256_b_109 :: Background256+color256_b_109 = Background256 (Just (T.ColorNumber 109))++color256_b_110 :: Background256+color256_b_110 = Background256 (Just (T.ColorNumber 110))++color256_b_111 :: Background256+color256_b_111 = Background256 (Just (T.ColorNumber 111))++color256_b_112 :: Background256+color256_b_112 = Background256 (Just (T.ColorNumber 112))++color256_b_113 :: Background256+color256_b_113 = Background256 (Just (T.ColorNumber 113))++color256_b_114 :: Background256+color256_b_114 = Background256 (Just (T.ColorNumber 114))++color256_b_115 :: Background256+color256_b_115 = Background256 (Just (T.ColorNumber 115))++color256_b_116 :: Background256+color256_b_116 = Background256 (Just (T.ColorNumber 116))++color256_b_117 :: Background256+color256_b_117 = Background256 (Just (T.ColorNumber 117))++color256_b_118 :: Background256+color256_b_118 = Background256 (Just (T.ColorNumber 118))++color256_b_119 :: Background256+color256_b_119 = Background256 (Just (T.ColorNumber 119))++color256_b_120 :: Background256+color256_b_120 = Background256 (Just (T.ColorNumber 120))++color256_b_121 :: Background256+color256_b_121 = Background256 (Just (T.ColorNumber 121))++color256_b_122 :: Background256+color256_b_122 = Background256 (Just (T.ColorNumber 122))++color256_b_123 :: Background256+color256_b_123 = Background256 (Just (T.ColorNumber 123))++color256_b_124 :: Background256+color256_b_124 = Background256 (Just (T.ColorNumber 124))++color256_b_125 :: Background256+color256_b_125 = Background256 (Just (T.ColorNumber 125))++color256_b_126 :: Background256+color256_b_126 = Background256 (Just (T.ColorNumber 126))++color256_b_127 :: Background256+color256_b_127 = Background256 (Just (T.ColorNumber 127))++color256_b_128 :: Background256+color256_b_128 = Background256 (Just (T.ColorNumber 128))++color256_b_129 :: Background256+color256_b_129 = Background256 (Just (T.ColorNumber 129))++color256_b_130 :: Background256+color256_b_130 = Background256 (Just (T.ColorNumber 130))++color256_b_131 :: Background256+color256_b_131 = Background256 (Just (T.ColorNumber 131))++color256_b_132 :: Background256+color256_b_132 = Background256 (Just (T.ColorNumber 132))++color256_b_133 :: Background256+color256_b_133 = Background256 (Just (T.ColorNumber 133))++color256_b_134 :: Background256+color256_b_134 = Background256 (Just (T.ColorNumber 134))++color256_b_135 :: Background256+color256_b_135 = Background256 (Just (T.ColorNumber 135))++color256_b_136 :: Background256+color256_b_136 = Background256 (Just (T.ColorNumber 136))++color256_b_137 :: Background256+color256_b_137 = Background256 (Just (T.ColorNumber 137))++color256_b_138 :: Background256+color256_b_138 = Background256 (Just (T.ColorNumber 138))++color256_b_139 :: Background256+color256_b_139 = Background256 (Just (T.ColorNumber 139))++color256_b_140 :: Background256+color256_b_140 = Background256 (Just (T.ColorNumber 140))++color256_b_141 :: Background256+color256_b_141 = Background256 (Just (T.ColorNumber 141))++color256_b_142 :: Background256+color256_b_142 = Background256 (Just (T.ColorNumber 142))++color256_b_143 :: Background256+color256_b_143 = Background256 (Just (T.ColorNumber 143))++color256_b_144 :: Background256+color256_b_144 = Background256 (Just (T.ColorNumber 144))++color256_b_145 :: Background256+color256_b_145 = Background256 (Just (T.ColorNumber 145))++color256_b_146 :: Background256+color256_b_146 = Background256 (Just (T.ColorNumber 146))++color256_b_147 :: Background256+color256_b_147 = Background256 (Just (T.ColorNumber 147))++color256_b_148 :: Background256+color256_b_148 = Background256 (Just (T.ColorNumber 148))++color256_b_149 :: Background256+color256_b_149 = Background256 (Just (T.ColorNumber 149))++color256_b_150 :: Background256+color256_b_150 = Background256 (Just (T.ColorNumber 150))++color256_b_151 :: Background256+color256_b_151 = Background256 (Just (T.ColorNumber 151))++color256_b_152 :: Background256+color256_b_152 = Background256 (Just (T.ColorNumber 152))++color256_b_153 :: Background256+color256_b_153 = Background256 (Just (T.ColorNumber 153))++color256_b_154 :: Background256+color256_b_154 = Background256 (Just (T.ColorNumber 154))++color256_b_155 :: Background256+color256_b_155 = Background256 (Just (T.ColorNumber 155))++color256_b_156 :: Background256+color256_b_156 = Background256 (Just (T.ColorNumber 156))++color256_b_157 :: Background256+color256_b_157 = Background256 (Just (T.ColorNumber 157))++color256_b_158 :: Background256+color256_b_158 = Background256 (Just (T.ColorNumber 158))++color256_b_159 :: Background256+color256_b_159 = Background256 (Just (T.ColorNumber 159))++color256_b_160 :: Background256+color256_b_160 = Background256 (Just (T.ColorNumber 160))++color256_b_161 :: Background256+color256_b_161 = Background256 (Just (T.ColorNumber 161))++color256_b_162 :: Background256+color256_b_162 = Background256 (Just (T.ColorNumber 162))++color256_b_163 :: Background256+color256_b_163 = Background256 (Just (T.ColorNumber 163))++color256_b_164 :: Background256+color256_b_164 = Background256 (Just (T.ColorNumber 164))++color256_b_165 :: Background256+color256_b_165 = Background256 (Just (T.ColorNumber 165))++color256_b_166 :: Background256+color256_b_166 = Background256 (Just (T.ColorNumber 166))++color256_b_167 :: Background256+color256_b_167 = Background256 (Just (T.ColorNumber 167))++color256_b_168 :: Background256+color256_b_168 = Background256 (Just (T.ColorNumber 168))++color256_b_169 :: Background256+color256_b_169 = Background256 (Just (T.ColorNumber 169))++color256_b_170 :: Background256+color256_b_170 = Background256 (Just (T.ColorNumber 170))++color256_b_171 :: Background256+color256_b_171 = Background256 (Just (T.ColorNumber 171))++color256_b_172 :: Background256+color256_b_172 = Background256 (Just (T.ColorNumber 172))++color256_b_173 :: Background256+color256_b_173 = Background256 (Just (T.ColorNumber 173))++color256_b_174 :: Background256+color256_b_174 = Background256 (Just (T.ColorNumber 174))++color256_b_175 :: Background256+color256_b_175 = Background256 (Just (T.ColorNumber 175))++color256_b_176 :: Background256+color256_b_176 = Background256 (Just (T.ColorNumber 176))++color256_b_177 :: Background256+color256_b_177 = Background256 (Just (T.ColorNumber 177))++color256_b_178 :: Background256+color256_b_178 = Background256 (Just (T.ColorNumber 178))++color256_b_179 :: Background256+color256_b_179 = Background256 (Just (T.ColorNumber 179))++color256_b_180 :: Background256+color256_b_180 = Background256 (Just (T.ColorNumber 180))++color256_b_181 :: Background256+color256_b_181 = Background256 (Just (T.ColorNumber 181))++color256_b_182 :: Background256+color256_b_182 = Background256 (Just (T.ColorNumber 182))++color256_b_183 :: Background256+color256_b_183 = Background256 (Just (T.ColorNumber 183))++color256_b_184 :: Background256+color256_b_184 = Background256 (Just (T.ColorNumber 184))++color256_b_185 :: Background256+color256_b_185 = Background256 (Just (T.ColorNumber 185))++color256_b_186 :: Background256+color256_b_186 = Background256 (Just (T.ColorNumber 186))++color256_b_187 :: Background256+color256_b_187 = Background256 (Just (T.ColorNumber 187))++color256_b_188 :: Background256+color256_b_188 = Background256 (Just (T.ColorNumber 188))++color256_b_189 :: Background256+color256_b_189 = Background256 (Just (T.ColorNumber 189))++color256_b_190 :: Background256+color256_b_190 = Background256 (Just (T.ColorNumber 190))++color256_b_191 :: Background256+color256_b_191 = Background256 (Just (T.ColorNumber 191))++color256_b_192 :: Background256+color256_b_192 = Background256 (Just (T.ColorNumber 192))++color256_b_193 :: Background256+color256_b_193 = Background256 (Just (T.ColorNumber 193))++color256_b_194 :: Background256+color256_b_194 = Background256 (Just (T.ColorNumber 194))++color256_b_195 :: Background256+color256_b_195 = Background256 (Just (T.ColorNumber 195))++color256_b_196 :: Background256+color256_b_196 = Background256 (Just (T.ColorNumber 196))++color256_b_197 :: Background256+color256_b_197 = Background256 (Just (T.ColorNumber 197))++color256_b_198 :: Background256+color256_b_198 = Background256 (Just (T.ColorNumber 198))++color256_b_199 :: Background256+color256_b_199 = Background256 (Just (T.ColorNumber 199))++color256_b_200 :: Background256+color256_b_200 = Background256 (Just (T.ColorNumber 200))++color256_b_201 :: Background256+color256_b_201 = Background256 (Just (T.ColorNumber 201))++color256_b_202 :: Background256+color256_b_202 = Background256 (Just (T.ColorNumber 202))++color256_b_203 :: Background256+color256_b_203 = Background256 (Just (T.ColorNumber 203))++color256_b_204 :: Background256+color256_b_204 = Background256 (Just (T.ColorNumber 204))++color256_b_205 :: Background256+color256_b_205 = Background256 (Just (T.ColorNumber 205))++color256_b_206 :: Background256+color256_b_206 = Background256 (Just (T.ColorNumber 206))++color256_b_207 :: Background256+color256_b_207 = Background256 (Just (T.ColorNumber 207))++color256_b_208 :: Background256+color256_b_208 = Background256 (Just (T.ColorNumber 208))++color256_b_209 :: Background256+color256_b_209 = Background256 (Just (T.ColorNumber 209))++color256_b_210 :: Background256+color256_b_210 = Background256 (Just (T.ColorNumber 210))++color256_b_211 :: Background256+color256_b_211 = Background256 (Just (T.ColorNumber 211))++color256_b_212 :: Background256+color256_b_212 = Background256 (Just (T.ColorNumber 212))++color256_b_213 :: Background256+color256_b_213 = Background256 (Just (T.ColorNumber 213))++color256_b_214 :: Background256+color256_b_214 = Background256 (Just (T.ColorNumber 214))++color256_b_215 :: Background256+color256_b_215 = Background256 (Just (T.ColorNumber 215))++color256_b_216 :: Background256+color256_b_216 = Background256 (Just (T.ColorNumber 216))++color256_b_217 :: Background256+color256_b_217 = Background256 (Just (T.ColorNumber 217))++color256_b_218 :: Background256+color256_b_218 = Background256 (Just (T.ColorNumber 218))++color256_b_219 :: Background256+color256_b_219 = Background256 (Just (T.ColorNumber 219))++color256_b_220 :: Background256+color256_b_220 = Background256 (Just (T.ColorNumber 220))++color256_b_221 :: Background256+color256_b_221 = Background256 (Just (T.ColorNumber 221))++color256_b_222 :: Background256+color256_b_222 = Background256 (Just (T.ColorNumber 222))++color256_b_223 :: Background256+color256_b_223 = Background256 (Just (T.ColorNumber 223))++color256_b_224 :: Background256+color256_b_224 = Background256 (Just (T.ColorNumber 224))++color256_b_225 :: Background256+color256_b_225 = Background256 (Just (T.ColorNumber 225))++color256_b_226 :: Background256+color256_b_226 = Background256 (Just (T.ColorNumber 226))++color256_b_227 :: Background256+color256_b_227 = Background256 (Just (T.ColorNumber 227))++color256_b_228 :: Background256+color256_b_228 = Background256 (Just (T.ColorNumber 228))++color256_b_229 :: Background256+color256_b_229 = Background256 (Just (T.ColorNumber 229))++color256_b_230 :: Background256+color256_b_230 = Background256 (Just (T.ColorNumber 230))++color256_b_231 :: Background256+color256_b_231 = Background256 (Just (T.ColorNumber 231))++color256_b_232 :: Background256+color256_b_232 = Background256 (Just (T.ColorNumber 232))++color256_b_233 :: Background256+color256_b_233 = Background256 (Just (T.ColorNumber 233))++color256_b_234 :: Background256+color256_b_234 = Background256 (Just (T.ColorNumber 234))++color256_b_235 :: Background256+color256_b_235 = Background256 (Just (T.ColorNumber 235))++color256_b_236 :: Background256+color256_b_236 = Background256 (Just (T.ColorNumber 236))++color256_b_237 :: Background256+color256_b_237 = Background256 (Just (T.ColorNumber 237))++color256_b_238 :: Background256+color256_b_238 = Background256 (Just (T.ColorNumber 238))++color256_b_239 :: Background256+color256_b_239 = Background256 (Just (T.ColorNumber 239))++color256_b_240 :: Background256+color256_b_240 = Background256 (Just (T.ColorNumber 240))++color256_b_241 :: Background256+color256_b_241 = Background256 (Just (T.ColorNumber 241))++color256_b_242 :: Background256+color256_b_242 = Background256 (Just (T.ColorNumber 242))++color256_b_243 :: Background256+color256_b_243 = Background256 (Just (T.ColorNumber 243))++color256_b_244 :: Background256+color256_b_244 = Background256 (Just (T.ColorNumber 244))++color256_b_245 :: Background256+color256_b_245 = Background256 (Just (T.ColorNumber 245))++color256_b_246 :: Background256+color256_b_246 = Background256 (Just (T.ColorNumber 246))++color256_b_247 :: Background256+color256_b_247 = Background256 (Just (T.ColorNumber 247))++color256_b_248 :: Background256+color256_b_248 = Background256 (Just (T.ColorNumber 248))++color256_b_249 :: Background256+color256_b_249 = Background256 (Just (T.ColorNumber 249))++color256_b_250 :: Background256+color256_b_250 = Background256 (Just (T.ColorNumber 250))++color256_b_251 :: Background256+color256_b_251 = Background256 (Just (T.ColorNumber 251))++color256_b_252 :: Background256+color256_b_252 = Background256 (Just (T.ColorNumber 252))++color256_b_253 :: Background256+color256_b_253 = Background256 (Just (T.ColorNumber 253))++color256_b_254 :: Background256+color256_b_254 = Background256 (Just (T.ColorNumber 254))++color256_b_255 :: Background256+color256_b_255 = Background256 (Just (T.ColorNumber 255))
− Penny/Cabin/Colors.hs
@@ -1,50 +0,0 @@-module Penny.Cabin.Colors where--import qualified Penny.Lincoln as L-import qualified Penny.Lincoln.Balance as Bal-import qualified Penny.Lincoln.Bits as Bits-import qualified Penny.Cabin.Chunk as C-import qualified Penny.Cabin.Meta as M---- | The colors for debits and credits. These are used for the entry's--- amount and for the total amounts that accompany each entry. Here,--- @even@ and @odd@ refer to the the posting's visible number. @Zero@--- is for the balance entries whose balance is zero.-data DrCrColors =- DrCrColors { evenDebit :: !C.TextSpec- , evenCredit :: !C.TextSpec- , oddDebit :: !C.TextSpec- , oddCredit :: !C.TextSpec- , evenZero :: !C.TextSpec- , oddZero :: !C.TextSpec }---- | The colors for all fields other than entries and totals.-data BaseColors =- BaseColors { evenColors :: !C.TextSpec- , oddColors :: !C.TextSpec }---- | Change a BaseColors to a TextSpec.-colors :: M.VisibleNum -> BaseColors -> C.TextSpec-colors vn b = let n = L.forward . M.unVisibleNum $ vn in- if odd n then oddColors b else evenColors b---- | Change a DrCrColors to a BaseColors; you can then use 'colors' to--- change that to a TextSpec.-drCrToBaseColors :: Bits.DrCr -> DrCrColors -> BaseColors-drCrToBaseColors dc col = case dc of- Bits.Debit -> BaseColors (evenDebit col) (oddDebit col)- Bits.Credit -> BaseColors (evenCredit col) (oddCredit col)---- | Change a DrCrColors to a BaseColors, based on a balance (unlike--- entries, balances might be zero.)-bottomLineToBaseColors :: DrCrColors -> Bal.BottomLine -> BaseColors-bottomLineToBaseColors col no = case no of- Bal.Zero -> BaseColors (evenZero col) (oddZero col)- Bal.NonZero column -> drCrToBaseColors (Bal.drCr column) col- --- | TextSpec to use when showing the lack of a balance.-noBalanceColors :: M.VisibleNum -> DrCrColors -> C.TextSpec-noBalanceColors vn dc =- if odd . L.forward . M.unVisibleNum $ vn- then oddZero dc- else evenZero dc
− Penny/Cabin/Colors/DarkBackground.hs
@@ -1,38 +0,0 @@--- | The default colors; the user may override.-module Penny.Cabin.Colors.DarkBackground where--import qualified Penny.Cabin.Colors as PC-import qualified Penny.Cabin.Chunk as CC-import qualified Penny.Cabin.Chunk.Switch as S--baseColors :: PC.BaseColors-baseColors = PC.BaseColors evenTextSpec oddTextSpec--drCrColors :: PC.DrCrColors-drCrColors = PC.DrCrColors { PC.evenDebit = debit evenTextSpec- , PC.evenCredit = credit evenTextSpec- , PC.oddDebit = debit oddTextSpec- , PC.oddCredit = credit oddTextSpec- , PC.evenZero = zero evenTextSpec- , PC.oddZero = zero oddTextSpec }--evenTextSpec :: CC.TextSpec-evenTextSpec = CC.defaultTextSpec--oddTextSpec :: CC.TextSpec-oddTextSpec = S.switchBackground CC.color8_b_default- CC.color256_b_235 evenTextSpec- --- | Debits in 256 colors are orange; in 8 colors, magenta-debit :: CC.TextSpec -> CC.TextSpec-debit = S.switchForeground CC.color8_f_magenta CC.color256_f_208---- | Credits in 256 colors are cyan; in 8 colors, cyan-credit :: CC.TextSpec -> CC.TextSpec-credit = S.switchForeground CC.color8_f_cyan (CC.color256_f_45)---- | Zero values are white-zero :: CC.TextSpec -> CC.TextSpec-zero = S.switchForeground CC.color8_f_white (CC.color256_f_15)--
− Penny/Cabin/Colors/LightBackground.hs
@@ -1,35 +0,0 @@--- | The default colors; the user may override.-module Penny.Cabin.Colors.LightBackground where--import qualified Penny.Cabin.Colors as PC-import qualified Penny.Cabin.Chunk as CC-import qualified Penny.Cabin.Chunk.Switch as S--baseColors :: PC.BaseColors-baseColors = PC.BaseColors evenTextSpec oddTextSpec--drCrColors :: PC.DrCrColors-drCrColors = PC.DrCrColors { PC.evenDebit = debit evenTextSpec- , PC.evenCredit = credit evenTextSpec- , PC.oddDebit = debit oddTextSpec- , PC.oddCredit = credit oddTextSpec- , PC.evenZero = zero evenTextSpec- , PC.oddZero = zero oddTextSpec }--evenTextSpec :: CC.TextSpec-evenTextSpec = CC.defaultTextSpec--oddTextSpec :: CC.TextSpec-oddTextSpec = S.switchBackground CC.color8_b_default- (CC.color256_b_247) evenTextSpec- -debit :: CC.TextSpec -> CC.TextSpec-debit = S.switchForeground CC.color8_f_magenta (CC.color256_f_13)--credit :: CC.TextSpec -> CC.TextSpec-credit = S.switchForeground CC.color8_f_cyan (CC.color256_f_14)--zero :: CC.TextSpec -> CC.TextSpec-zero = S.switchForeground CC.color8_f_white (CC.color256_f_15)--
Penny/Cabin/Interface.hs view
@@ -2,54 +2,57 @@ -- anything that is a 'Report'. module Penny.Cabin.Interface where +import qualified Penny.Cabin.Scheme as S import Control.Monad.Exception.Synchronous (Exceptional) import qualified Data.Text as X-import qualified Data.Text.Lazy as XL import Text.Matchers.Text (CaseSensitive)-import System.Console.MultiArg.Prim (Parser)+import qualified System.Console.MultiArg as MA import qualified Penny.Lincoln as L import qualified Penny.Liberty as Ly import Penny.Shield (Runtime) -type ReportFunc =- Runtime- -- ^ Information only known at runtime, such as the- -- environment. Does not include any information that is derived- -- from parsing the command line.+-- | The function that will print the report, and the positional+-- arguments. If there was a problem parsing the command line options,+-- return an Exception with an error message. - -> CaseSensitive+-- | Parsing the filter options can have one of two results: a help+-- string, or a list of positional arguments and a function that+-- prints a report. Or, the parse might fail.++type PosArg = String+type HelpStr = String+type ArgsAndReport = ([PosArg], PrintReport)+type ParseResult = Exceptional String (Either HelpStr ArgsAndReport)++type PrintReport+ = [L.Transaction]+ -- ^ All transactions; the report must sort and filter them++ -> [L.PricePoint]+ -- ^ PricePoints to be included in the report+++ -> Exceptional X.Text [S.PreChunk]+ -- ^ The exception type is a strict Text, containing the error+ -- message. The success type is a list of PreChunks containing the+ -- resulting report. This allows for errors after the list of+ -- transactions has been seen.+++type Report = Runtime -> (HelpStr, MkReport)+type MkReport+ = CaseSensitive -- ^ Result from previous parses indicating whether the user desires -- case sensitivity (this may have been changed in the filtering -- options)- + -> (CaseSensitive -> X.Text -> Exceptional X.Text (X.Text -> Bool)) -- ^ Result from previous parsers indicating the matcher factory the -- user wishes to use- - -> [L.Box Ly.LibertyMeta]- -- ^ Postings that will be included in the report- - -> [L.PricePoint]- -- ^ PricePoints to be included in the report- - - -> Exceptional X.Text XL.Text- -- ^ The exception type is a strict Text, containing the error- -- message. The success type is a lazy Text, containing the- -- resulting report. + -> ([L.Transaction] -> [L.Box Ly.LibertyMeta])+ -- ^ Result from previous parsers that will sort and filter incoming+ -- transactions -data Report =- Report { help :: X.Text- -- ^ A strict Text containing a help message.- - , name :: String- -- ^ The name of the report- - , parseReport :: Parser ReportFunc- -- ^ The parser must parse everything beginning with the- -- first word after the name of the report (the parser does- -- not parse the name of the report) up until, but not- -- including, the first non-option word.- }+ -> MA.Mode ParseResult
Penny/Cabin/Meta.hs view
@@ -1,15 +1,38 @@+-- | Metadata that is specific to Cabin. module Penny.Cabin.Meta (VisibleNum, unVisibleNum,- visibleNums ) where+ visibleNumBoxes, visibleNums ) where +import Control.Applicative ((*>))+import qualified Data.Traversable as Tr import qualified Penny.Lincoln as L +-- | Each row that is visible on screen is assigned a VisibleNum. This+-- is used to number the rows in the report for the user's benefit. It+-- is also used to determine whether the row is even or odd for the+-- purpose of assigning the background color (this way the background+-- colors can alternate, like a checkbook register.) newtype VisibleNum = VisibleNum { unVisibleNum :: L.Serial } deriving (Eq, Show) -visibleNums ::+-- | Assigns VisibleNum to a list of boxes.+visibleNumBoxes :: (VisibleNum -> a -> b) -> [L.Box a] -> [L.Box b]-visibleNums f = L.serialItems s' where- s' ser (L.Box m pf) = L.Box (f (VisibleNum ser) m) pf+visibleNumBoxes f bs = L.makeSerials k+ where+ k = Tr.sequenceA (replicate (length bs) L.incrementBack)+ *> mapM assign bs+ assign (L.Box m pf) = fmap g L.getSerial+ where+ g ser = L.Box (f (VisibleNum ser) m) pf+++-- | Assigns VisibleNum to a list.+visibleNums :: (VisibleNum -> a -> b) -> [a] -> [b]+visibleNums f as = L.makeSerials k+ where+ k = Tr.sequenceA (replicate (length as) L.incrementBack)+ *> mapM assign as+ assign a = fmap (\ser -> f (VisibleNum ser) a) L.getSerial
Penny/Cabin/Options.hs view
@@ -2,32 +2,15 @@ module Penny.Cabin.Options where -import qualified Penny.Cabin.Chunk as C-import qualified Penny.Shield as S---- | The user's color preference.-data ColorPref = Pref0 | Pref8 | Pref256 | PrefAuto- deriving Show--maxCapableColors :: S.Runtime -> C.Colors-maxCapableColors r = case S.output r of- S.NotTTY -> C.Colors0- S.IsTTY ->- case lookup "TERM" (S.environment r) of- Nothing -> C.Colors8- (Just t) -> if t == "xterm-256color"- then C.Colors256- else C.Colors8--autoColors :: ColorPref -> S.Runtime -> C.Colors-autoColors c r = case c of- Pref0 -> C.Colors0- Pref8 -> C.Colors8- Pref256 -> C.Colors256- PrefAuto -> maxCapableColors r- -- | Whether to show zero balances in reports. newtype ShowZeroBalances = ShowZeroBalances { unShowZeroBalances :: Bool } deriving (Show, Eq) +-- | Converts an ordering to a descending order.+descending :: (a -> a -> Ordering)+ -> a -> a -> Ordering+descending f x y = case f x y of+ LT -> GT+ GT -> LT+ EQ -> EQ
+ Penny/Cabin/Parsers.hs view
@@ -0,0 +1,24 @@+-- | Command line parsers that are common to various Cabin reports.++module Penny.Cabin.Parsers where++import qualified Penny.Cabin.Options as CO+import qualified System.Console.MultiArg.Combinator as C+++zeroBalances :: C.OptSpec CO.ShowZeroBalances+zeroBalances = C.OptSpec ["zero-balances"] "" (C.ChoiceArg ls)+ where+ ls = [ ("show", CO.ShowZeroBalances True)+ , ("hide", CO.ShowZeroBalances False) ]++data SortOrder = Ascending | Descending deriving (Eq, Ord, Show)++order :: C.OptSpec SortOrder+order = C.OptSpec ["order"] "" (C.ChoiceArg ls)+ where+ ls = [ ("ascending", Ascending)+ , ("descending", Descending) ]++help :: C.OptSpec ()+help = C.OptSpec ["help"] "h" (C.NoArg ())
Penny/Cabin/Posts.hs view
@@ -33,13 +33,15 @@ -- show various fields. However, the order of the fields is not -- configurable without editing the source code (sorry). -module Penny.Cabin.Posts (- postsReport- , parseReport- , makeReport+module Penny.Cabin.Posts+ ( postsReport+ , zincReport , defaultOptions , ZincOpts(..)- , ymd+ , A.Alloc+ , A.SubAccountLength(..)+ , A.alloc+ , yearMonthDay , qtyAsIs , balanceAsIs , defaultWidth@@ -47,120 +49,121 @@ , widthFromRuntime , defaultFields , defaultSpacerWidth+ , T.ReportWidth(..) ) where +import Control.Applicative ((<$>), (<*>)) import qualified Control.Monad.Exception.Synchronous as Ex+import qualified Data.Either as Ei import qualified Data.Text as X-import qualified Data.Text.Lazy as XL-import qualified Penny.Cabin.Chunk as CC-import qualified Penny.Cabin.Colors as PC-import qualified Penny.Cabin.Colors.DarkBackground as Dark import qualified Penny.Cabin.Interface as I import qualified Penny.Cabin.Options as CO import qualified Penny.Cabin.Posts.Allocated as A-import qualified Penny.Cabin.Posts.Allocate as Alc import qualified Penny.Cabin.Posts.Chunk as C import qualified Penny.Cabin.Posts.Fields as F-import qualified Penny.Cabin.Posts.Help as H import qualified Penny.Cabin.Posts.Meta as M import Penny.Cabin.Posts.Meta (Box) import qualified Penny.Cabin.Posts.Parser as P import qualified Penny.Cabin.Posts.Spacers as S import qualified Penny.Cabin.Posts.Types as T+import qualified Penny.Cabin.Scheme as E -import qualified Penny.Copper as Cop import qualified Penny.Lincoln as L-import qualified Penny.Lincoln.Balance as Bal import qualified Penny.Lincoln.Queries as Q import qualified Penny.Liberty as Ly import qualified Penny.Shield as Sh +import Data.List (intersperse)+import Data.Maybe (catMaybes)+import qualified Data.Foldable as Fdbl import Data.Time as Time-import System.Console.MultiArg.Prim (Parser)+import qualified System.Console.MultiArg as MA import System.Locale (defaultTimeLocale) import Text.Matchers.Text (CaseSensitive) -- | All information needed to make a Posts report. This function -- never fails. postsReport ::- CC.Colors- -- ^ How many colors to show.- -> CO.ShowZeroBalances+ CO.ShowZeroBalances -> (L.Box Ly.LibertyMeta -> Bool) -- ^ Removes posts from the report if applying this function to the -- post returns False. Posts removed still affect the running -- balance.- + -> [Ly.PostFilterFn] -- ^ Applies these post-filters to the list of posts that results -- from applying the predicate above. Might remove more -- postings. Postings removed still affect the running balance.- + -> C.ChunkOpts -> [L.Box Ly.LibertyMeta]- -> XL.Text+ -> [E.PreChunk] -postsReport col szb pdct pff co =- CC.chunksToText col- . C.makeChunk co+postsReport szb pdct pff co =+ C.makeChunk co . M.toBoxList szb pdct pff -parseReport ::- (Sh.Runtime -> ZincOpts)- -> Parser I.ReportFunc-parseReport frt = do- getState <- P.parseOptions- let rf rt cs fty ps _ = do- let zo = frt rt- maySt' = getState rt dtz rg st- where- dtz = defaultTimeZone zo- rg = radGroup zo- st = newParseState cs fty zo- st' <- Ex.mapException showParserError maySt'- pdct <- getPredicate . P.tokens $ st'- return $ postsReport (P.colorPref st')- (P.showZeroBalances st') pdct- (P.postFilter st') (chunkOpts st' zo) ps- return rf- - -makeReport ::- (Sh.Runtime -> ZincOpts)- -> I.Report-makeReport frt = I.Report {- I.help = H.help- , I.name = "postings"- , I.parseReport = parseReport frt }+zincReport :: ZincOpts -> I.Report+zincReport opts rt = (helpStr opts, md)+ where+ md cs fty fsf = MA.Mode+ { MA.mName = "postings"+ , MA.mIntersperse = MA.Intersperse+ , MA.mOpts = map (fmap Right) (P.allSpecs rt)+ , MA.mPosArgs = Left+ , MA.mProcess = process opts cs fty fsf+ } +process+ :: ZincOpts+ -> CaseSensitive+ -> L.Factory+ -> ([L.Transaction] -> [L.Box Ly.LibertyMeta])+ -> [Either String (P.State -> Ex.Exceptional String P.State)]+ -> Ex.Exceptional String (Either I.HelpStr I.ArgsAndReport)+process os cs fty fsf ls =+ let (posArgs, clOpts) = Ei.partitionEithers ls+ pState = newParseState cs fty os+ exState' = foldl (>>=) (return pState) clOpts+ in fmap (mkPrintReport posArgs os fsf) exState' -defaultOptions ::- Cop.DefaultTimeZone- -> Cop.RadGroup- -> Sh.Runtime+mkPrintReport+ :: [String] -> ZincOpts-defaultOptions dtz rg rt = ZincOpts {- defaultTimeZone = dtz- , radGroup = rg- , fields = defaultFields- , colorPref = CO.maxCapableColors rt- , drCrColors = Dark.drCrColors- , baseColors = Dark.baseColors+ -> ([L.Transaction] -> [L.Box Ly.LibertyMeta])+ -> P.State+ -> Either I.HelpStr I.ArgsAndReport+mkPrintReport posArgs zo fsf st = r+ where+ r = if P.showHelp st then Left $ helpStr zo else Right pr+ pr = (posArgs, f)+ f txns _ = fmap mkChunks exPdct+ where+ exPdct = getPredicate (P.tokens st)+ mkChunks pdct = chks+ where+ chks = postsReport (P.showZeroBalances st) pdct+ (P.postFilter st) (chunkOpts st zo) boxes+ boxes = fsf txns+++defaultOptions+ :: Sh.Runtime+ -> ZincOpts+defaultOptions rt = ZincOpts+ { fields = defaultFields , width = widthFromRuntime rt , showZeroBalances = CO.ShowZeroBalances False- , dateFormat = ymd+ , dateFormat = yearMonthDay , qtyFormat = qtyAsIs , balanceFormat = balanceAsIs , subAccountLength = A.SubAccountLength 2- , payeeAllocation = Alc.allocation 60- , accountAllocation = Alc.allocation 40+ , payeeAllocation = A.alloc 60+ , accountAllocation = A.alloc 40 , spacers = defaultSpacerWidth } -showParserError :: P.Error -> X.Text-showParserError = X.pack . show- getPredicate :: [Ly.Token (L.Box Ly.LibertyMeta -> Bool)] -> Ex.Exceptional X.Text (L.Box Ly.LibertyMeta -> Bool)@@ -175,34 +178,10 @@ -- | All the information to configure the postings report if the -- options will be parsed in from the command line.-data ZincOpts = ZincOpts {- defaultTimeZone :: Cop.DefaultTimeZone- -- ^ The postings report takes options to determine which posts will- -- be visible. This determines the time zone to use when parsing- -- these options. Does not affect how the resulting report is- -- formatted. For that, see the dateFormat field.- - , radGroup :: Cop.RadGroup- -- ^ The postings report takes options to determine which posts- -- will be visible. This determines the radix point and grouping- -- character to use when parsing these options. Does not affect- -- how the resulting report is formatted. For that, see the- -- qtyFormat field.-- , fields :: F.Fields Bool+data ZincOpts = ZincOpts+ { fields :: F.Fields Bool -- ^ Default fields to show in the report.- - , colorPref :: CC.Colors- -- ^ How many colors you want to see, or do it- -- automatically. - , drCrColors :: PC.DrCrColors- -- ^ Colors to use when displaying debits, credits, and- -- when displaying balance totals-- , baseColors :: PC.BaseColors- -- ^ Colors to use when displaying everything else- , width :: T.ReportWidth -- ^ Gives the default report width. This can be -- overridden on the command line. You can use the@@ -225,7 +204,7 @@ -- the quantity. Allows you to format digit grouping, -- radix points, perform rounding, etc. - , balanceFormat :: L.Commodity -> L.BottomLine -> X.Text+ , balanceFormat :: L.Commodity -> L.Qty -> X.Text -- ^ How to display balance totals. Similar to -- balanceFormat. @@ -233,7 +212,7 @@ -- ^ When shortening the names of sub accounts to make -- them fit, they will be this long. - , payeeAllocation :: Alc.Allocation+ , payeeAllocation :: A.Alloc -- ^ This and accountAllocation determine how much space -- payees and accounts receive. They divide up the -- remaining space after everything else is displayed. For@@ -241,24 +220,22 @@ -- is 40, the payee takes about 60 percent of the -- remaining space and the account takes about 40 percent. - , accountAllocation :: Alc.Allocation+ , accountAllocation :: A.Alloc -- ^ See payeeAllocation above , spacers :: S.Spacers Int -- ^ Default width for spacer fields. If any of these Ints are -- less than or equal to zero, there will be no spacer. There is -- never a spacer for fields that do not appear in the report.- + } chunkOpts ::- P.State + P.State -> ZincOpts -> C.ChunkOpts-chunkOpts s z = C.ChunkOpts {- C.baseColors = P.baseColors s- , C.drCrColors = P.drCrColors s- , C.dateFormat = dateFormat z+chunkOpts s z = C.ChunkOpts+ { C.dateFormat = dateFormat z , C.qtyFormat = qtyFormat z , C.balanceFormat = balanceFormat z , C.fields = P.fields s@@ -275,40 +252,36 @@ -> L.Factory -> ZincOpts -> P.State-newParseState cs fty o = P.State {- P.sensitive = cs+newParseState cs fty o = P.State+ { P.sensitive = cs , P.factory = fty , P.tokens = [] , P.postFilter = [] , P.fields = fields o- , P.colorPref = colorPref o- , P.drCrColors = drCrColors o- , P.baseColors = baseColors o , P.width = width o , P.showZeroBalances = showZeroBalances o+ , P.showHelp = False } -- | Shows the date of a posting in YYYY-MM-DD format.-ymd :: Box -> X.Text-ymd p = X.pack (Time.formatTime defaultTimeLocale fmt d) where- d = Time.localDay- . L.localTime- . Q.dateTime- . L.boxPostFam- $ p- fmt = "%Y-%m-%d"+yearMonthDay :: Box -> X.Text+yearMonthDay p = X.pack (Time.formatTime defaultTimeLocale fmt d)+ where+ d = L.day+ . Q.dateTime+ . L.boxPostFam+ $ p+ fmt = "%Y-%m-%d" -- | Shows the quantity of a posting. Does no rounding or -- prettification; simply uses show on the underlying Decimal. qtyAsIs :: Box -> X.Text-qtyAsIs p = X.pack . show . L.unQty . Q.qty . L.boxPostFam $ p+qtyAsIs p = X.pack . show . Q.qty . L.boxPostFam $ p -- | Shows the quantity of a balance. If there is no quantity, shows -- two dashes.-balanceAsIs :: a -> L.BottomLine -> X.Text-balanceAsIs _ n = case n of- L.Zero -> X.pack "--"- L.NonZero c -> X.pack . show . L.unQty . Bal.qty $ c+balanceAsIs :: a -> L.Qty -> X.Text+balanceAsIs _ = X.pack . show -- | The default width for the report. defaultWidth :: T.ReportWidth@@ -394,3 +367,186 @@ , S.postingQty = 1 , S.totalDrCr = 1 , S.totalCmdty = 1 }++------------------------------------------------------------+-- ## Help+------------------------------------------------------------++ifDefault :: Bool -> String+ifDefault b = if b then " (default)" else ""++bundles :: Int -> [a] -> [[a]]+bundles c ls+ | c < 1 = error "bundles: argument must be positive"+ | otherwise = case splitAt c ls of+ (r, []) -> [r]+ (r, rs) -> r : bundles c rs++helpStr :: ZincOpts -> String+helpStr o = unlines $+ [ "postings"+ , " Show postings in order with a running balance."+ , " Accepts the following options:"+ , ""+ , "Posting filters"+ , "==============="+ , "These options affect which postings are shown in the report."+ , "Postings not shown still affect the running balance."+ , ""+ , "Dates"+ , "-----"+ , ""+ , "--date cmp timespec, -d cmp timespec"+ , " Date must be within the time frame given. timespec"+ , " is a day or a day and a time. Valid values for cmp:"+ , " <, >, <=, >=, ==, /=, !="+ , "--current"+ , " Same as \"--date <= (right now) \""+ , ""+ , "Serials"+ , "-------"+ , "These options take the form --option cmp num; the given"+ , "sequence number must fall within the given range. \"rev\""+ , "in the option name indicates numbering is from end to beginning."+ , ""+ , "--globalTransaction, --revGlobalTransaction"+ , " All transactions, after reading the ledger files"+ , "--globalPosting, --revGlobalPosting"+ , " All postings, after reading the leder files"+ , "--fileTransaction, --revFileTransaction"+ , " Transactions in each ledger file, after reading the files"+ , " (numbering restarts with each file)"+ , "--filePosting, --revFilePosting"+ , " Postings in each ledger file, after reading the files"+ , " (numbering restarts with each file)"+ , "--filtered, --revFiltered"+ , " All postings, after filters given in the filter"+ , " specification portion of the command line are"+ , " applied"+ , "--sorted, --revSorted"+ , " All postings remaining after filtering and after"+ , " postings have been sorted"+ , ""+ , "Pattern matching"+ , "----------------"+ , ""+ , "-a pattern, --account pattern"+ , " Pattern must match colon-separated account name"+ , "--account-level num pat"+ , " Pattern must match sub account at given level"+ , "--account-any pat"+ , " Pattern must match sub account at any level"+ , "-p pattern, --payee pattern"+ , " Payee must match pattern"+ , "-t pattern, --tag pattern"+ , " Tag must match pattern"+ , "--number pattern"+ , " Number must match pattern"+ , "--flag pattern"+ , " Flag must match pattern"+ , "--commodity pattern"+ , " Pattern must match colon-separated commodity name"+ , "--posting-memo pattern"+ , " Posting memo must match pattern"+ , "--transaction-memo pattern"+ , " Transaction memo must match pattern"+ , ""+ , "Other posting characteristics"+ , "-----------------------------"+ , "--debit"+ , " Entry must be a debit"+ , "--credit"+ , " Entry must be a credit"+ , "--qty cmp number"+ , " Entry quantity must fall within given range"+ , ""+ , "Operators - from highest to lowest precedence"+ , "(all are left associative)"+ , "=========================="+ , "--open expr --close"+ , " Force precedence (as in \"open\" and \"close\" parentheses)"+ , "--not expr"+ , " True if expr is false"+ , "expr1 --and expr2 "+ , " True if expr and expr2 are both true"+ , "expr1 --or expr2"+ , " True if either expr1 or expr2 is true"+ , ""+ , "Options affecting patterns"+ , "=========================="+ , ""+ , "-i, --case-insensitive"+ , " Be case insensitive"+ , "-I, --case-sensitive"+ , " Be case sensitive"+ , ""+ , "--within"+ , " Use \"within\" matcher"+ , "--pcre"+ , " Use \"pcre\" matcher"+ , "--posix"+ , " Use \"posix\" matcher"+ , "--exact"+ , " Use \"exact\" matcher"+ , ""+ , "Removing postings after sorting and filtering"+ , "============================================="+ , "--head n"+ , " Keep only the first n postings"+ , "--tail n"+ , " Keep only the last n postings"+ , ""+ , "Other options"+ , "============="+ , "--width num"+ , " Hint for roughly how wide the report should be in columns"+ , " (currently: " ++ (show . T.unReportWidth . width $ o) ++ ")"+ , "--show field, --hide field"+ , " show or hide this field, where field is one of:"+ , " globalTransaction, revGlobalTransaction,"+ , " globalPosting, revGlobalPosting,"+ , " fileTransaction, revFileTransaction,"+ , " filePosting, revFilePosting,"+ , " filtered, revFiltered,"+ , " sorted, revSorted,"+ , " visible, revVisible,"+ , " lineNum,"+ , " date, flag, number, payee, account,"+ , " postingDrCr, postingCommodity, postingQty,"+ , " totalDrCr, totalCommodity, totalQty,"+ , " tags, memo, filename"+ , "--show-all"+ , " Show all fields"+ , "--hide-all"+ , " Hide all fields"+ , ""+ ] ++ showDefaultFields (fields o) +++ [ ""+ , "--show-zero-balances"+ , " Show balances that are zero"+ ++ ifDefault (CO.unShowZeroBalances . showZeroBalances $ o)+ , "--hide-zero-balances"+ , " Hide balances that are zero"+ ++ ifDefault (not . CO.unShowZeroBalances . showZeroBalances $ o)+ , ""+ , "--help, -h"+ , " Show this help and exit"+ ]++-- | Shows which fields are on by default.+showDefaultFields :: F.Fields Bool -> [String]+showDefaultFields i = hdr : rest+ where+ hdr = "Fields shown by default:"+ ++ if null rest then " (none)" else ""+ rest =+ map (" " ++)+ . map concat+ . map (intersperse ", ")+ . bundles 3+ . catMaybes+ . Fdbl.toList+ . toMaybes+ $ i+ toMaybes flds = f <$> flds <*> F.fieldNames+ f b n = if b then Just n else Nothing
− Penny/Cabin/Posts/Allocate.hs
@@ -1,65 +0,0 @@-module Penny.Cabin.Posts.Allocate (- Allocation,- allocation,- unAllocation,- allocate) where--import qualified Control.Monad.Trans.State as St-import qualified Data.Foldable as F-import qualified Data.Traversable as T---- | Allocations are integers. The absolute value of the integer is--- used, so for practical purposes @-4@ is the same allocation as @4@.-newtype Allocation = Allocation { unAllocation :: Int }- deriving (Show, Eq, Ord)--allocation :: Int -> Allocation-allocation = Allocation---- | Properties of the allocated result:------ * If the sum of the absolute values of the allocations is zero,--- then each of the elements in the result will also be zero.------ * Otherwise, if any allocation is zero, then the corresponding--- amount in the result will also be zero. The sum of the results will--- equal the total requested.-allocate ::- (Functor f, F.Foldable f, T.Traversable f)- => f Allocation- -> Int- -> f Int-allocate m t = let- tot = F.sum . fmap (toDouble . abs . unAllocation) $ m- ratios = fmap ((/tot) . toDouble . abs . unAllocation) m- rounded = fmap (round . (* (toDouble t))) ratios- toDouble = fromIntegral :: Int -> Double- in if tot == 0- then fmap (const 0) m- else adjust rounded t--adjust :: (Functor f, F.Foldable f, T.Traversable f)- => f Int- -> Int- -> f Int-adjust ws w = let- wsInts = fmap fromIntegral ws- diff = (fromIntegral w) - F.sum wsInts in- if diff == 0- then ws- else let- ws' = St.evalState (T.mapM adjustMap ws) diff- in adjust ws' w---- | The state is the target number minus the current actual total.-adjustMap :: Int -> St.State Int Int-adjustMap w = if w == 0 then return 0 else do- diff <- St.get- case compare diff 0 of- EQ -> return w- GT -> do- St.put (pred diff)- return (succ w)- LT -> do- St.put (succ diff)- return (pred w)
Penny/Cabin/Posts/Allocated.hs view
@@ -14,29 +14,49 @@ -- c. If the rightmost growing field is to the right of Account but is -- not TotalQty, omit its spacer. ----- 2. Subtract from this sum the width of the Payee and Account--- spacers:+-- 2. Obtain the width of the Payee and Account spacers. Include each+-- spacer if its corresponding field appears in the report. ----- a. Subtract the width of Payee spacer if it appears.+-- 3. Subtract from the total report width the width of the the+-- growing cells and the width of the Payee and Account spacers. This+-- gives the total width available for the Payee and Account+-- fields. If there are not at least two columns available, return+-- without including the Payee and Account fields. ----- b. Subtract the width of the Account spacer if it appears.+-- 4. Determine the total width that the Payee and Account fields+-- would obtain if they had all the space they could ever need. This+-- is the "requested width". ----- 3. If the remaining width is 0 or less, do nothing. Return, but--- indicate in return value that neither Payee nor Account is showing.+-- 5. Split up the available width for the Payee and Account fields+-- depending on which fields appear: ----- 4. Allocate the remaining width. If only Payee or Account appears,--- it gets all the width; otherwise, allocate the widths. No special--- arrangements are made if either field gets an allocation of 0.+-- a. If only the one field appears, then it shall be as wide as the+-- total available width or the its requested width, whichever is+-- smaller. ----- 5. Fill cell contents. Return filled cells.+-- b. If both fields appear, then calculate the allocated width for+-- each field. If either field's requested width is less than its+-- allocated width, then that field is only as wide as its requested+-- width. The other field is then as wide as (the sum of its allocated+-- width and the leftover width from the other field) or its requested+-- width, whichever is smaller. If neither field's requested width is+-- less than its allocated width, then each field gets ts allocated+-- width.+--+-- 6. Fill cell contents; return filled cells.+ module Penny.Cabin.Posts.Allocated ( payeeAndAcct , AllocatedOpts(..) , Fields(..) , SubAccountLength(..)+ , Alloc+ , alloc+ , unAlloc ) where import Control.Applicative(Applicative((<*>), pure), (<$>))+import Control.Arrow (second) import Data.Maybe (catMaybes, isJust) import Data.List (intersperse) import qualified Data.Foldable as Fdbl@@ -45,15 +65,15 @@ import qualified Data.Text as X import qualified Penny.Cabin.Chunk as C import qualified Penny.Cabin.Row as R-import qualified Penny.Cabin.Posts.Allocate as A-import qualified Penny.Cabin.Colors as PC import qualified Penny.Cabin.Posts.Growers as G import qualified Penny.Cabin.Posts.Meta as M import Penny.Cabin.Posts.Meta (Box) import qualified Penny.Cabin.Posts.Spacers as S import qualified Penny.Cabin.Posts.Types as Ty+import qualified Penny.Cabin.Scheme as E import qualified Penny.Cabin.TextFormat as TF import qualified Penny.Lincoln as L+import qualified Penny.Lincoln.Bits.Qty as Qty import qualified Penny.Lincoln.Queries as Q import qualified Penny.Lincoln.HasText as HT @@ -66,13 +86,22 @@ SubAccountLength { unSubAccountLength :: Int } deriving Show +newtype Alloc = Alloc { unAlloc :: Int }+ deriving Show++alloc :: Int -> Alloc+alloc i =+ if i < 1+ then error $ "allocations must be greater than zero."+ ++ " supplied allocation: " ++ show i+ else Alloc i++ -- | All the information needed for allocated cells.-data AllocatedOpts = AllocatedOpts {- fields :: Fields Bool+data AllocatedOpts = AllocatedOpts+ { fields :: Fields Bool , subAccountLength :: SubAccountLength- , baseColors :: PC.BaseColors- , payeeAllocation :: A.Allocation- , accountAllocation :: A.Allocation+ , allocations :: Fields Alloc , spacers :: S.Spacers Int , growerWidths :: G.Fields (Maybe Int) , reportWidth :: Ty.ReportWidth@@ -85,79 +114,54 @@ -- did not ask for them, or because there was no room) or Just cs i, -- where cs is a list of all the cells, and i is the width of all the -- cells.-payeeAndAcct ::- AllocatedOpts+payeeAndAcct+ :: AllocatedOpts -> [Box] -> Fields (Maybe ([R.ColumnSpec], Int))-payeeAndAcct as = allocateCells sl bc ws- where- sl = subAccountLength as- bc = baseColors as- ws = fieldWidth (fields as) (payeeAllocation as)- (accountAllocation as) (spacers as)- (growerWidths as) (reportWidth as)+payeeAndAcct ao bs =+ let allBuilders =+ T.traverse (builders (subAccountLength ao)) bs+ availWidth = availableWidthForAllocs (growerWidths ao)+ (spacers ao) (fields ao) (reportWidth ao)+ finals = divideAvailableWidth availWidth (fields ao)+ (allocations ao)+ ( fmap (safeMaximum (Request 0))+ . fmap (fmap fst) $ allBuilders)+ in fmap (fmap (second unFinal))+ . buildSpecs finals+ . fmap (fmap snd)+ $ allBuilders --- | Allocates cells. Returns a pair, with the first element being the--- list of allocated cells, and the second indicating the width of the--- cells, which will be greater than zero.-allocateCells ::- SubAccountLength- -> PC.BaseColors- -> Fields UnShrunkWidth- -> [Box]- -> Fields (Maybe ([R.ColumnSpec], Int))-allocateCells sl bc fs bs =- let mkPayees i b = allocPayee i bc b- mkAccts i b = allocAcct i sl bc b- cellMakers = Fields mkPayees mkAccts- mkCells (UnShrunkWidth width) maker =- if width > 0- then Just (map (maker width) bs)- else Nothing- unShrunkCells = mkCells <$> fs <*> cellMakers- in fmap (fmap removeExtraSpace) unShrunkCells-+safeMaximum :: Ord a => a -> [a] -> a+safeMaximum d ls = case ls of+ [] -> d+ xs -> maximum xs --- | After first being allocated by allocPayee and allocAcct, cells--- are as wide as the total space allocated. This function removes the--- extra space, making all the cells as wide as the widest--- cell. Returns the resized cells and the new width.-removeExtraSpace :: [R.ColumnSpec] -> ([R.ColumnSpec], Int)-removeExtraSpace cs = (trimmed, len) where- len = Fdbl.foldl' f 0 cs where- f acc c = max acc (Fdbl.foldl' g 0 (R.bits c)) where- g inAcc chk = max inAcc (C.unWidth . C.chunkWidth $ chk)- trimmed = map f cs where- f c = c { R.width = C.Width len }+payeeAndAccountSpacerWidth+ :: Fields Bool+ -> S.Spacers Int+ -> Int+payeeAndAccountSpacerWidth flds ss = pye + act+ where+ pye = if payee flds then abs (S.payee ss) else 0+ act = if account flds then abs (S.account ss) else 0 --- | The width of an on-screen field, after accounting for the width--- of the entire report and the allocations but before shrinking.-newtype UnShrunkWidth = UnShrunkWidth Int- deriving Show+newtype AvailableWidth = AvailableWidth Int+ deriving (Eq, Ord, Show) --- | Gets the width of the two allocated fields.-fieldWidth ::- Fields Bool- -> A.Allocation -- ^ Payee allocation- -> A.Allocation -- ^ Accout allocation+availableWidthForAllocs+ :: G.Fields (Maybe Int) -> S.Spacers Int- -> G.Fields (Maybe Int)+ -> Fields Bool -> Ty.ReportWidth- -> Fields UnShrunkWidth-fieldWidth flds pa aa ss fs (Ty.ReportWidth rw) =- let grownWidth = sumGrowersAndSpacers fs ss- widthForCells = rw - grownWidth - allocSpacerWidth- payeeSpacerWidth = if payee flds then abs (S.payee ss) else 0- acctSpacerWidth = if account flds then abs (S.account ss) else 0- allocSpacerWidth = payeeSpacerWidth + acctSpacerWidth- allocs = (\bool alloc -> if bool then alloc else A.allocation 0)- <$> flds- <*> Fields pa aa- in if widthForCells < 1- then pure (UnShrunkWidth 0)- else fmap UnShrunkWidth $ A.allocate allocs widthForCells-+ -> AvailableWidth+availableWidthForAllocs growers ss flds (Ty.ReportWidth w) =+ AvailableWidth $ max 0 diff+ where+ tot = sumGrowersAndSpacers growers ss+ + payeeAndAccountSpacerWidth flds ss+ diff = w - tot -- | Sums spacers for growing cells. This function is intended for use -- only by the functions that allocate cells for the report, so it@@ -179,8 +183,8 @@ . Fdbl.toList . fmap toWidth . pairedWithSpacers fs- + -- | Takes a triple: -- -- * The first element is Just _ if the field appears in the report;@@ -230,7 +234,7 @@ <$> G.pairWithSpacer f s <*> G.eFields --- | Sums the contents of growing cells and their accompanying+-- | Sums the widths of growing cells and their accompanying -- spacers; makes the adjustments described in sumSpacers. sumGrowersAndSpacers :: G.Fields (Maybe Int)@@ -243,59 +247,132 @@ Nothing -> acc Just i -> acc + i +newtype Request = Request { unRequest :: Int }+ deriving (Eq, Ord, Show) -allocPayee ::- Int- -- ^ Width that is permitted for this column- -> PC.BaseColors+newtype Final = Final { unFinal :: Int }+ deriving (Eq, Ord, Show)+++buildSpecs+ :: Fields (Maybe Final)+ -> Fields ([Final -> R.ColumnSpec])+ -> Fields (Maybe ([R.ColumnSpec], Final))+buildSpecs finals bs = f <$> finals <*> bs+ where+ f mayFinal gs = case mayFinal of+ Nothing -> Nothing+ Just fin -> Just ((gs <*> pure fin), fin)+++-- | Divide the total available width between the two fields.+divideAvailableWidth+ :: AvailableWidth+ -> Fields Bool+ -> Fields Alloc+ -> Fields Request+ -> Fields (Maybe Final)+divideAvailableWidth (AvailableWidth aw) appear allocs rws = Fields pye act+ where+ minFinal i1 i2 =+ let m = min i1 i2+ in if m > 0 then Just . Final $ m else Nothing+ pairAtLeast i1 i2 = (atLeast i1, atLeast i2)+ where atLeast i = if i > 0 then Just . Final $ i else Nothing+ reqP = unRequest . payee $ rws+ reqA = unRequest . account $ rws+ (pye, act) = case (payee appear, account appear) of+ (False, False) -> (Nothing, Nothing)+ (True, False) -> (minFinal reqP aw, Nothing)+ (False, True) -> (Nothing, minFinal reqA aw)+ (True, True) ->+ let votes = [unAlloc . payee $ allocs, unAlloc . account $ allocs]+ allocRslt = Qty.largestRemainderMethod (fromIntegral aw)+ (map fromIntegral votes)+ (allocP, allocA) = case allocRslt of+ x:y:[] -> (fromIntegral x, fromIntegral y)+ _ -> error "divideAvailableWidth error"+ in case (allocP > reqP, allocA > reqA) of+ (True, True) -> pairAtLeast reqP reqA+ (True, False) ->+ pairAtLeast reqP $ (min (allocA + (allocP - reqP))) reqA+ (False, True) ->+ pairAtLeast (min reqP (allocP + (allocA - reqA))) reqA+ (False, False) -> pairAtLeast allocP allocA+++builders+ :: SubAccountLength -> Box- -> R.ColumnSpec-allocPayee w bc i =- let pb = L.boxPostFam i- ts = PC.colors (M.visibleNum . L.boxMeta $ i) bc- c = R.ColumnSpec j (C.Width w) ts sq- j = R.LeftJustify- sq = case Q.payee pb of- Nothing -> []- Just pye ->- let wrapped =- Fdbl.toList- . TF.unLines - . TF.wordWrap w- . TF.txtWords- . HT.text- $ pye- toBit (TF.Words seqTxts) =- C.chunk ts- . X.unwords- . Fdbl.toList- $ seqTxts- in fmap toBit wrapped- in c+ -> Fields (Request, Final -> R.ColumnSpec)+builders sl b = Fields (buildPayee b) (buildAcct sl b) +buildPayee+ :: Box+ -> (Request, Final -> R.ColumnSpec)+ -- ^ Returns a tuple. The first element is the maximum width that+ -- this cell needs to display its value perfectly. The second+ -- element is a function that, when applied to an actual width,+ -- returns a ColumnSpec. -allocAcct ::- Int- -- ^ Width that is permitted for this column- -> SubAccountLength- -> PC.BaseColors+buildPayee i = (maxW, mkSpec)+ where+ pb = L.boxPostFam i+ eo = E.fromVisibleNum . M.visibleNum . L.boxMeta $ i+ j = R.LeftJustify+ ps = (E.Other, eo)+ mayPye = Q.payee pb+ maxW = Request $ maybe 0 (X.length . HT.text) mayPye+ mkSpec (Final w) = R.ColumnSpec j (C.Width w) ps sq+ where+ sq = case mayPye of+ Nothing -> []+ Just pye ->+ let wrapped =+ Fdbl.toList+ . TF.unLines+ . TF.wordWrap w+ . TF.txtWords+ . HT.text+ $ pye+ toBit (TF.Words seqTxts) =+ E.PreChunk E.Other eo+ . X.unwords+ . Fdbl.toList+ $ seqTxts+ in fmap toBit wrapped+++buildAcct ::+ SubAccountLength -> Box- -> R.ColumnSpec-allocAcct aw sl bc i =- let pb = L.boxPostFam i- ts = PC.colors (M.visibleNum . L.boxMeta $ i) bc- in R.ColumnSpec R.LeftJustify (C.Width aw) ts $- let target = TF.Target aw- shortest = TF.Shortest . unSubAccountLength $ sl- a = Q.account pb- ws = TF.Words . Seq.fromList . HT.textList $ a- (TF.Words shortened) = TF.shorten shortest target ws- in [C.chunk ts- . X.concat- . intersperse (X.singleton ':')- . Fdbl.toList- $ shortened]+ -> (Request, Final -> R.ColumnSpec)+ -- ^ Returns a tuple. The first element is the maximum width that+ -- this cell needs to display its value perfectly. The second+ -- element is a function that, when applied to an actual width,+ -- returns a ColumnSpec. +buildAcct sl i = (maxW, mkSpec)+ where+ pb = L.boxPostFam i+ eo = E.fromVisibleNum . M.visibleNum . L.boxMeta $ i+ ps = (E.Other, eo)+ aList = L.unAccount . Q.account $ pb+ maxW = Request+ $ (sum . map (X.length . L.unSubAccount) $ aList)+ + max 0 (length aList - 1)+ mkSpec (Final aw) = R.ColumnSpec R.LeftJustify (C.Width aw) ps sq+ where+ target = TF.Target aw+ shortest = TF.Shortest . unSubAccountLength $ sl+ ws = TF.Words . Seq.fromList . map L.unSubAccount $ aList+ (TF.Words shortened) = TF.shorten shortest target ws+ sq = [ E.PreChunk E.Other eo+ . X.concat+ . intersperse (X.singleton ':')+ . Fdbl.toList+ $ shortened ]+ instance Functor Fields where fmap f i = Fields { payee = f (payee i)@@ -314,4 +391,4 @@ instance T.Traversable Fields where traverse f flds = Fields <$> f (payee flds) <*> f (account flds)- +
Penny/Cabin/Posts/BottomRows.hs view
@@ -32,10 +32,10 @@ import qualified Data.Text as X import qualified Data.Traversable as T import qualified Penny.Cabin.Chunk as C+import qualified Penny.Cabin.Scheme as E import qualified Penny.Cabin.Row as R import qualified Penny.Cabin.TextFormat as TF import qualified Penny.Cabin.Posts.Allocated as A-import qualified Penny.Cabin.Colors as PC import qualified Penny.Cabin.Posts.Fields as F import qualified Penny.Cabin.Posts.Growers as G import qualified Penny.Cabin.Posts.Meta as M@@ -46,11 +46,10 @@ import qualified Penny.Lincoln.HasText as HT import qualified Penny.Lincoln.Queries as Q -data BottomOpts = BottomOpts {- growingWidths :: G.Fields (Maybe Int)+data BottomOpts = BottomOpts+ { growingWidths :: G.Fields (Maybe Int) , allocatedWidths :: A.Fields (Maybe Int) , fields :: F.Fields Bool- , baseColors :: PC.BaseColors , reportWidth :: Ty.ReportWidth , spacers :: S.Spacers Int }@@ -58,10 +57,10 @@ bottomRows :: BottomOpts -> [Box]- -> Fields (Maybe [[C.Chunk]])+ -> Fields (Maybe [[E.PreChunk]]) bottomRows os bs = makeRows bs pcs where pcs = infoProcessors topSpecs (reportWidth os) wanted- wanted = requestedMakers (fields os) (baseColors os)+ wanted = requestedMakers (fields os) topSpecs = topCellSpecs (growingWidths os) (allocatedWidths os) (spacers os) @@ -110,16 +109,16 @@ hanging :: [TopCellSpec]- -> Maybe ((Box -> Int -> (C.TextSpec, R.ColumnSpec))- -> Box -> [C.Chunk])+ -> Maybe ((Box -> Int -> ((E.Label, E.EvenOdd), R.ColumnSpec))+ -> Box -> [E.PreChunk]) hanging specs = hangingWidths specs >>= return . hangingInfoProcessor hangingInfoProcessor :: Hanging Int- -> (Box -> Int -> (C.TextSpec, R.ColumnSpec))+ -> (Box -> Int -> ((E.Label, E.EvenOdd), R.ColumnSpec)) -> Box- -> [C.Chunk]+ -> [E.PreChunk] hangingInfoProcessor widths mkr info = row where row = R.row [left, mid, right] (ts, mid) = mkr info (mainCell widths)@@ -129,8 +128,8 @@ widthOfTopColumns :: [TopCellSpec]- -> Maybe ((Box -> Int -> (C.TextSpec, R.ColumnSpec))- -> Box -> [C.Chunk])+ -> Maybe ((Box -> Int -> ((E.Label, E.EvenOdd), R.ColumnSpec))+ -> Box -> [E.PreChunk]) widthOfTopColumns ts = if null ts then Nothing@@ -142,18 +141,18 @@ widthOfReport :: Ty.ReportWidth- -> (Box -> Int -> (C.TextSpec, R.ColumnSpec))+ -> (Box -> Int -> ((E.Label, E.EvenOdd), R.ColumnSpec)) -> Box- -> [C.Chunk]+ -> [E.PreChunk] widthOfReport (Ty.ReportWidth rw) fn info = makeSpecificWidth rw fn info chooseProcessor :: [TopCellSpec] -> Ty.ReportWidth- -> (Box -> Int -> (C.TextSpec, R.ColumnSpec))+ -> (Box -> Int -> ((E.Label, E.EvenOdd), R.ColumnSpec)) -> Box- -> [C.Chunk]+ -> [E.PreChunk] chooseProcessor specs rw fn = let firstTwo = First (hanging specs) `mappend` First (widthOfTopColumns specs)@@ -164,8 +163,8 @@ infoProcessors :: [TopCellSpec] -> Ty.ReportWidth- -> Fields (Maybe (Box -> Int -> (C.TextSpec, R.ColumnSpec)))- -> Fields (Maybe (Box -> [C.Chunk]))+ -> Fields (Maybe (Box -> Int -> ((E.Label, E.EvenOdd), R.ColumnSpec)))+ -> Fields (Maybe (Box -> [E.PreChunk])) infoProcessors specs rw flds = let chooser = chooseProcessor specs rw mkProcessor mayFn = case mayFn of@@ -176,8 +175,8 @@ makeRows :: [Box]- -> Fields (Maybe (Box -> [C.Chunk]))- -> Fields (Maybe [[C.Chunk]])+ -> Fields (Maybe (Box -> [E.PreChunk]))+ -> Fields (Maybe [[E.PreChunk]]) makeRows is flds = let mkRow fn = map fn is in fmap (fmap mkRow) flds@@ -234,8 +233,8 @@ Nothing -> Nothing Just c -> Just (e, maybeS, c) in catMaybes (map toMaybe list)- + -- | Merges a TopRowCells with a Spacers. Returns Maybes because -- totalQty has no spacer. mergeWithSpacers ::@@ -274,12 +273,15 @@ -- | Applied to a function that, when applied to the width of a cell, -- returns a cell filled with data, returns a Row with that cell. makeSpecificWidth :: Int -> (Box -> Int -> (a, R.ColumnSpec))- -> Box -> [C.Chunk]+ -> Box -> [E.PreChunk] makeSpecificWidth w f i = R.row [c] where (_, c) = f i w -type Maker = PC.BaseColors -> Box -> Int -> (C.TextSpec, R.ColumnSpec)+type Maker+ = Box+ -> Int+ -> ((E.Label, E.EvenOdd), R.ColumnSpec) makers :: Fields Maker makers = Fields tagsCell memoCell filenameCell@@ -289,18 +291,21 @@ -- field that the user wants to see. requestedMakers :: F.Fields Bool- -> PC.BaseColors- -> Fields (Maybe (Box -> Int -> (C.TextSpec, R.ColumnSpec)))-requestedMakers allFlds bc =+ -> Fields (Maybe (Box -> Int -> ((E.Label, E.EvenOdd), R.ColumnSpec)))+requestedMakers allFlds = let flds = bottomRowsFields allFlds- filler b mkr = if b then Just $ mkr bc else Nothing+ filler b mkr = if b then Just $ mkr else Nothing in filler <$> flds <*> makers -tagsCell :: PC.BaseColors -> Box -> Int -> (C.TextSpec, R.ColumnSpec)-tagsCell bc info w = (ts, cell) where+tagsCell+ :: Box+ -> Int+ -> ((E.Label, E.EvenOdd), R.ColumnSpec)+tagsCell info w = (ts, cell) where vn = M.visibleNum . L.boxMeta $ info cell = R.ColumnSpec R.LeftJustify (C.Width w) ts cs- ts = PC.colors vn bc+ eo = E.fromVisibleNum vn+ ts = (E.Other, eo) cs = Fdbl.toList . fmap toBit@@ -313,12 +318,12 @@ . Q.tags . L.boxPostFam $ info- toBit (TF.Words ws) = C.chunk ts t where+ toBit (TF.Words ws) = E.PreChunk E.Other eo t where t = X.concat . intersperse (X.singleton ' ') . Fdbl.toList $ ws -memoBits :: C.TextSpec -> L.Memo -> C.Width -> [C.Chunk]-memoBits ts m (C.Width w) = cs where+memoBits :: (E.Label, E.EvenOdd) -> L.Memo -> C.Width -> [E.PreChunk]+memoBits (lbl, eo) m (C.Width w) = cs where cs = Fdbl.toList . fmap toBit . TF.unLines@@ -326,45 +331,44 @@ . TF.Words . Seq.fromList . X.words- . HT.text- . HT.Delimited (X.singleton ' ')- . HT.textList+ . X.intercalate (X.singleton ' ')+ . L.unMemo $ m- toBit (TF.Words ws) = C.chunk ts (X.unwords . Fdbl.toList $ ws)+ toBit (TF.Words ws) = E.PreChunk lbl eo (X.unwords . Fdbl.toList $ ws) -memoCell :: PC.BaseColors -> Box -> Int -> (C.TextSpec, R.ColumnSpec)-memoCell bc info width = (ts, cell) where+memoCell :: Box -> Int -> ((E.Label, E.EvenOdd), R.ColumnSpec)+memoCell info width = (ts, cell) where w = C.Width width vn = M.visibleNum . L.boxMeta $ info+ eo = E.fromVisibleNum vn+ ts = (E.Other, eo) cell = R.ColumnSpec R.LeftJustify w ts cs- ts = PC.colors vn bc- pm = Q.postingMemo . L.boxPostFam $ info- tm = Q.transactionMemo . L.boxPostFam $ info- nullMemo (L.Memo m) = null m- cs = case (nullMemo pm, nullMemo tm) of- (True, True) -> mempty- (False, True) -> memoBits ts pm w- (True, False) -> memoBits ts tm w- (False, False) -> memoBits ts pm w `mappend` memoBits ts tm w- + mayPm = Q.postingMemo . L.boxPostFam $ info+ mayTm = Q.transactionMemo . L.boxPostFam $ info+ cs = case (mayPm, mayTm) of+ (Nothing, Nothing) -> mempty+ (Nothing, Just tm) -> memoBits ts tm w+ (Just pm, Nothing) -> memoBits ts pm w+ (Just pm, Just tm) -> memoBits ts pm w `mappend` memoBits ts tm w -filenameCell :: PC.BaseColors -> Box -> Int -> (C.TextSpec, R.ColumnSpec)-filenameCell bc info width = (ts, cell) where++filenameCell :: Box -> Int -> ((E.Label, E.EvenOdd), R.ColumnSpec)+filenameCell info width = (ts, cell) where w = C.Width width vn = M.visibleNum . L.boxMeta $ info+ eo = E.fromVisibleNum vn+ ts = (E.Other, eo) cell = R.ColumnSpec R.LeftJustify w ts cs- toBit n = C.chunk ts+ toBit n = (E.PreChunk E.Other eo) . X.drop (max 0 (X.length n - width)) $ n cs = case Q.filename . L.boxPostFam $ info of Nothing -> [] Just fn -> [toBit . L.unFilename $ fn]- ts = PC.colors vn bc --data TopRowCells a = TopRowCells {- globalTransaction :: a+data TopRowCells a = TopRowCells+ { globalTransaction :: a , revGlobalTransaction :: a , globalPosting :: a , revGlobalPosting :: a@@ -394,8 +398,8 @@ deriving (Show, Eq) topRowCells :: G.Fields a -> A.Fields a -> TopRowCells a-topRowCells g a = TopRowCells {- globalTransaction = G.globalTransaction g+topRowCells g a = TopRowCells+ { globalTransaction = G.globalTransaction g , revGlobalTransaction = G.revGlobalTransaction g , globalPosting = G.globalPosting g , revGlobalPosting = G.revGlobalPosting g@@ -453,8 +457,8 @@ deriving (Show, Eq, Enum) eTopRowCells :: TopRowCells ETopRowCells-eTopRowCells = TopRowCells {- globalTransaction = EGlobalTransaction+eTopRowCells = TopRowCells+ { globalTransaction = EGlobalTransaction , revGlobalTransaction = ERevGlobalTransaction , globalPosting = EGlobalPosting , revGlobalPosting = ERevGlobalPosting@@ -482,8 +486,8 @@ , totalQty = ETotalQty } instance Functor TopRowCells where- fmap f t = TopRowCells {- globalTransaction = f (globalTransaction t)+ fmap f t = TopRowCells+ { globalTransaction = f (globalTransaction t) , revGlobalTransaction = f (revGlobalTransaction t) , globalPosting = f (globalPosting t) , revGlobalPosting = f (revGlobalPosting t)@@ -511,8 +515,8 @@ , totalQty = f (totalQty t) } instance Applicative TopRowCells where- pure a = TopRowCells {- globalTransaction = a+ pure a = TopRowCells+ { globalTransaction = a , revGlobalTransaction = a , globalPosting = a , revGlobalPosting = a@@ -539,8 +543,8 @@ , totalCmdty = a , totalQty = a } - ff <*> fa = TopRowCells {- globalTransaction = globalTransaction ff (globalTransaction fa)+ ff <*> fa = TopRowCells+ { globalTransaction = globalTransaction ff (globalTransaction fa) , revGlobalTransaction = revGlobalTransaction ff (revGlobalTransaction fa) , globalPosting = globalPosting ff (globalPosting fa) , revGlobalPosting = revGlobalPosting ff (revGlobalPosting fa)
Penny/Cabin/Posts/Chunk.hs view
@@ -6,50 +6,44 @@ import qualified Penny.Cabin.Posts.Fields as F import qualified Penny.Cabin.Posts.Growers as G import qualified Penny.Cabin.Posts.Allocated as A-import qualified Penny.Cabin.Posts.Allocate as Alc import qualified Penny.Cabin.Posts.BottomRows as B import qualified Penny.Cabin.Posts.Spacers as S import qualified Penny.Cabin.Row as R+import qualified Penny.Cabin.Scheme as E import qualified Penny.Cabin.Chunk as C-import qualified Penny.Cabin.Colors as PC import Penny.Cabin.Posts.Meta (Box) import qualified Penny.Lincoln as L import qualified Data.Text as X import qualified Penny.Cabin.Posts.Types as Ty -data ChunkOpts = ChunkOpts {- baseColors :: PC.BaseColors- , drCrColors :: PC.DrCrColors- , dateFormat :: Box -> X.Text+data ChunkOpts = ChunkOpts+ { dateFormat :: Box -> X.Text , qtyFormat :: Box -> X.Text- , balanceFormat :: L.Commodity -> L.BottomLine -> X.Text+ , balanceFormat :: L.Commodity -> L.Qty -> X.Text , fields :: F.Fields Bool , subAccountLength :: A.SubAccountLength- , payeeAllocation :: Alc.Allocation- , accountAllocation :: Alc.Allocation+ , payeeAllocation :: A.Alloc+ , accountAllocation :: A.Alloc , spacers :: S.Spacers Int , reportWidth :: Ty.ReportWidth } growOpts :: ChunkOpts -> G.GrowOpts-growOpts c = G.GrowOpts {- G.baseColors = baseColors c- , G.drCrColors = drCrColors c- , G.dateFormat = dateFormat c+growOpts c = G.GrowOpts+ { G.dateFormat = dateFormat c , G.qtyFormat = qtyFormat c , G.balanceFormat = balanceFormat c , G.fields = fields c } allocatedOpts :: ChunkOpts -> G.Fields (Maybe Int) -> A.AllocatedOpts-allocatedOpts c g = A.AllocatedOpts {- A.fields = let f = fields c- in A.Fields { A.payee = F.payee f- , A.account = F.account f }+allocatedOpts c g = A.AllocatedOpts+ { A.fields = let f = fields c+ in A.Fields { A.payee = F.payee f+ , A.account = F.account f } , A.subAccountLength = subAccountLength c- , A.baseColors = baseColors c- , A.payeeAllocation = payeeAllocation c- , A.accountAllocation = accountAllocation c+ , A.allocations = A.Fields { A.payee = payeeAllocation c+ , A.account = accountAllocation c } , A.spacers = spacers c , A.growerWidths = g , A.reportWidth = reportWidth c@@ -64,7 +58,6 @@ B.growingWidths = g , B.allocatedWidths = a , B.fields = fields c- , B.baseColors = baseColors c , B.reportWidth = reportWidth c , B.spacers = spacers c }@@ -72,7 +65,7 @@ makeChunk :: ChunkOpts -> [Box]- -> [C.Chunk]+ -> [E.PreChunk] makeChunk c bs = let fmapSnd = fmap (fmap snd) fmapFst = fmap (fmap fst)@@ -83,74 +76,70 @@ bFlds = B.bottomRows (bottomOpts c gFldW aFldW) bs topCells = B.topRowCells (fmapFst gFlds) (fmap (fmap fst) aFlds) withSpacers = B.mergeWithSpacers topCells (spacers c)- topRows = makeTopRows (baseColors c) withSpacers+ topRows = makeTopRows withSpacers bottomRows = makeBottomRows bFlds in makeAllRows topRows bottomRows -topRowsCells ::- PC.BaseColors- -> B.TopRowCells (Maybe [R.ColumnSpec], Maybe Int)+topRowsCells+ :: B.TopRowCells (Maybe [R.ColumnSpec], Maybe Int) -> [[(R.ColumnSpec, Maybe R.ColumnSpec)]]-topRowsCells bc t = let+topRowsCells t = let toWithSpc (mayCs, maySp) = case mayCs of Nothing -> Nothing- Just cs -> Just (makeSpacers bc cs maySp)+ Just cs -> Just (makeSpacers cs maySp) f mayPairList acc = case mayPairList of Nothing -> acc (Just pairList) -> pairList : acc in transpose $ Fdbl.foldr f [] (fmap toWithSpc t) -makeRow :: [(R.ColumnSpec, Maybe R.ColumnSpec)] -> [C.Chunk]+makeRow :: [(R.ColumnSpec, Maybe R.ColumnSpec)] -> [E.PreChunk] makeRow = R.row . foldr f [] where f (c, mayC) acc = case mayC of Nothing -> c:acc Just spcr -> c:spcr:acc -makeSpacers ::- PC.BaseColors- -> [R.ColumnSpec]+makeSpacers+ :: [R.ColumnSpec] -> Maybe Int -> [(R.ColumnSpec, Maybe R.ColumnSpec)]-makeSpacers bc cs mayI = case mayI of+makeSpacers cs mayI = case mayI of Nothing -> map (\c -> (c, Nothing)) cs- Just i -> makeEvenOddSpacers bc cs i+ Just i -> makeEvenOddSpacers cs i -makeEvenOddSpacers ::- PC.BaseColors- -> [R.ColumnSpec]+makeEvenOddSpacers+ :: [R.ColumnSpec] -> Int -> [(R.ColumnSpec, Maybe R.ColumnSpec)]-makeEvenOddSpacers bc cs i = let absI = abs i in+makeEvenOddSpacers cs i = let absI = abs i in if absI == 0 then map (\c -> (c, Nothing)) cs else let spcrs = cycle [Just $ mkSpcr evenTs, Just $ mkSpcr oddTs] mkSpcr ts = R.ColumnSpec R.LeftJustify (C.Width absI) ts []- evenTs = PC.evenColors bc- oddTs = PC.oddColors bc+ evenTs = (E.Other, E.Even)+ oddTs = (E.Other, E.Odd) in zip cs spcrs -makeTopRows ::- PC.BaseColors- -> B.TopRowCells (Maybe [R.ColumnSpec], Maybe Int)- -> Maybe [[C.Chunk]]-makeTopRows bc trc =+makeTopRows+ :: B.TopRowCells (Maybe [R.ColumnSpec], Maybe Int)+ -> Maybe [[E.PreChunk]]+makeTopRows trc = if Fdbl.all (isNothing . fst) trc then Nothing- else Just $ map makeRow . topRowsCells bc $ trc+ else Just $ map makeRow . topRowsCells $ trc makeBottomRows ::- B.Fields (Maybe [[C.Chunk]])- -> Maybe [[[C.Chunk]]]+ B.Fields (Maybe [[E.PreChunk]])+ -> Maybe [[[E.PreChunk]]] makeBottomRows flds = if Fdbl.all isNothing flds then Nothing else Just . transpose . catMaybes . Fdbl.toList $ flds -makeAllRows :: Maybe [[C.Chunk]] -> Maybe [[[C.Chunk]]] -> [C.Chunk]+makeAllRows :: Maybe [[E.PreChunk]] -> Maybe [[[E.PreChunk]]] -> [E.PreChunk] makeAllRows mayrs mayrrs = case (mayrs, mayrrs) of (Nothing, Nothing) -> [] (Just rs, Nothing) -> concat rs
Penny/Cabin/Posts/Fields.hs view
@@ -4,8 +4,8 @@ import Control.Applicative(Applicative(pure, (<*>))) import qualified Data.Foldable as F -data Fields a = Fields {- globalTransaction :: a+data Fields a = Fields+ { globalTransaction :: a , revGlobalTransaction :: a , globalPosting :: a , revGlobalPosting :: a@@ -20,7 +20,6 @@ , visible :: a , revVisible :: a , lineNum :: a- -- ^ The line number from the posting's metadata , date :: a , flag :: a , number :: a@@ -34,8 +33,8 @@ , totalQty :: a , tags :: a , memo :: a- , filename :: a }- deriving (Show, Eq)+ , filename :: a+ } deriving (Show, Eq) instance Functor Fields where fmap f fa = Fields {@@ -164,3 +163,37 @@ (f (tags t) (f (memo t) (f (filename t) z))))))))))))))))))))))))))))+++fieldNames :: Fields String+fieldNames = Fields+ { globalTransaction = "globalTransaction"+ , revGlobalTransaction = "revGlobalTransaction"+ , globalPosting = "globalPosting"+ , revGlobalPosting = "revGlobalPosting"+ , fileTransaction = "fileTransaction"+ , revFileTransaction = "revFileTransaction"+ , filePosting = "filePosting"+ , revFilePosting = "revFilePosting"+ , filtered = "filtered"+ , revFiltered = "revFiltered"+ , sorted = "sorted"+ , revSorted = "revSorted"+ , visible = "visible"+ , revVisible = "revVisible"+ , lineNum = "lineNum"+ , date = "date"+ , flag = "flag"+ , number = "number"+ , payee = "payee"+ , account = "account"+ , postingDrCr = "postingDrCr"+ , postingCmdty = "postingCmdty"+ , postingQty = "postingQty"+ , totalDrCr = "totalDrCr"+ , totalCmdty = "totalCmdty"+ , totalQty = "totalQty"+ , tags = "tags"+ , memo = "memo"+ , filename = "filename"+ }
Penny/Cabin/Posts/Growers.hs view
@@ -8,30 +8,28 @@ import Control.Applicative((<$>), Applicative(pure, (<*>))) import qualified Data.Foldable as Fdbl-import Data.Map (elems, assocs)+import Data.Map (elems)+import qualified Data.Map as Map import qualified Data.Semigroup as Semi import Data.Semigroup ((<>)) import Data.Text (Text, pack, empty) import qualified Data.Text as X-import qualified Penny.Cabin.Chunk as C-import qualified Penny.Cabin.Colors as CC import qualified Penny.Cabin.Posts.Fields as F import qualified Penny.Cabin.Posts.Meta as M import Penny.Cabin.Posts.Meta (Box) import qualified Penny.Cabin.Posts.Spacers as S import qualified Penny.Cabin.Row as R+import qualified Penny.Cabin.Scheme as E import qualified Penny.Liberty as Ly import qualified Penny.Lincoln as L import qualified Penny.Lincoln.Queries as Q -- | All the options needed to grow the cells.-data GrowOpts = GrowOpts {- baseColors :: CC.BaseColors- , drCrColors :: CC.DrCrColors- , dateFormat :: Box -> X.Text+data GrowOpts = GrowOpts+ { dateFormat :: Box -> X.Text , qtyFormat :: Box -> X.Text- , balanceFormat :: L.Commodity -> L.BottomLine -> X.Text+ , balanceFormat :: L.Commodity -> L.Qty -> X.Text , fields :: F.Fields Bool } @@ -62,12 +60,14 @@ widestLine :: PreSpec -> Int widestLine (PreSpec _ _ bs) =- maximum . map (R.unWidth . C.chunkWidth) $ bs+ case bs of+ [] -> 0+ xs -> maximum . map (R.unWidth . E.width) $ xs data PreSpec = PreSpec { _justification :: R.Justification- , _padSpec :: C.TextSpec- , _bits :: [C.Chunk] }+ , _padSpec :: (E.Label, E.EvenOdd)+ , _bits :: [E.PreChunk] } -- | Given a PreSpec and a width, create a ColumnSpec of the right@@ -77,74 +77,52 @@ -- | Makes a left justified cell that is only one line long. The width -- is unset.-oneLine :: Text -> CC.BaseColors -> Box -> PreSpec-oneLine t bc b =- let ts = CC.colors (M.visibleNum . L.boxMeta $ b) bc+oneLine :: Text -> E.Label -> Box -> PreSpec+oneLine t lbl b =+ let eo = E.fromVisibleNum . M.visibleNum . L.boxMeta $ b j = R.LeftJustify- bit = C.chunk ts t- in PreSpec j ts [bit]---- | Converts a function that accepts a BaseColors to one that accepts--- a GrowOpts.-bcToGrowOpts ::- (CC.BaseColors -> Box -> PreSpec)- -> GrowOpts -> Box -> PreSpec-bcToGrowOpts f g = f (baseColors g)---- | Converts a function that accepts a DrCrColors to one that accepts--- a GrowOpts.-dcToGrowOpts ::- (CC.DrCrColors -> Box -> PreSpec)- -> GrowOpts -> Box -> PreSpec-dcToGrowOpts f g = f (drCrColors g)+ pcs = E.PreChunk lbl eo t+ in PreSpec j (lbl, eo) [pcs] -- | Gets a Fields with each field filled with the function that fills -- the cells for that field. growers :: Fields (GrowOpts -> Box -> PreSpec)-growers = Fields {- globalTransaction = bcToGrowOpts getGlobalTransaction- , revGlobalTransaction = bcToGrowOpts getRevGlobalTransaction- , globalPosting = bcToGrowOpts getGlobalPosting- , revGlobalPosting = bcToGrowOpts getRevGlobalPosting- , fileTransaction = bcToGrowOpts getFileTransaction- , revFileTransaction = bcToGrowOpts getRevFileTransaction- , filePosting = bcToGrowOpts getFilePosting- , revFilePosting = bcToGrowOpts getRevFilePosting- , filtered = bcToGrowOpts getFiltered- , revFiltered = bcToGrowOpts getRevFiltered- , sorted = bcToGrowOpts getSorted- , revSorted = bcToGrowOpts getRevSorted- , visible = bcToGrowOpts getVisible- , revVisible = bcToGrowOpts getRevVisible- , lineNum = bcToGrowOpts getLineNum- , date = \o -> getDate (baseColors o) (dateFormat o)- , flag = bcToGrowOpts getFlag- , number = bcToGrowOpts getNumber- , postingDrCr = dcToGrowOpts getPostingDrCr- , postingCmdty = dcToGrowOpts getPostingCmdty- , postingQty = let fn o =- let fmt = qtyFormat o- dc = drCrColors o- in getPostingQty fmt dc- in fn- , totalDrCr = dcToGrowOpts getTotalDrCr- , totalCmdty = dcToGrowOpts getTotalCmdty- , totalQty = let fn o =- let fmt = balanceFormat o- dc = drCrColors o- in getTotalQty fmt dc- in fn }-+growers = Fields+ { globalTransaction = const getGlobalTransaction+ , revGlobalTransaction = const getRevGlobalTransaction+ , globalPosting = const getGlobalPosting+ , revGlobalPosting = const getRevGlobalPosting+ , fileTransaction = const getFileTransaction+ , revFileTransaction = const getRevFileTransaction+ , filePosting = const getFilePosting+ , revFilePosting = const getRevFilePosting+ , filtered = const getFiltered+ , revFiltered = const getRevFiltered+ , sorted = const getSorted+ , revSorted = const getRevSorted+ , visible = const getVisible+ , revVisible = const getRevVisible+ , lineNum = const getLineNum+ , date = \o -> getDate (dateFormat o)+ , flag = const getFlag+ , number = const getNumber+ , postingDrCr = const getPostingDrCr+ , postingCmdty = const getPostingCmdty+ , postingQty = \o -> getPostingQty (qtyFormat o)+ , totalDrCr = const getTotalDrCr+ , totalCmdty = const getTotalCmdty+ , totalQty = \o -> getTotalQty (balanceFormat o)+ } -- | Make a left justified cell one line long that shows a serial. serialCellMaybe :: (L.PostFam -> Maybe Int) -- ^ When applied to a Box, this function returns Just Int if the -- box has a serial, or Nothing if not.- - -> CC.BaseColors -> Box -> PreSpec-serialCellMaybe f bc b = oneLine t bc b++ -> Box -> PreSpec+serialCellMaybe f b = oneLine t E.Other b where t = case f (L.boxPostFam b) of Nothing -> X.empty@@ -152,200 +130,163 @@ serialCell :: (M.PostMeta -> Int)- -> CC.BaseColors -> Box -> PreSpec-serialCell f bc b = oneLine t bc b+ -> Box -> PreSpec+serialCell f b = oneLine t E.Other b where t = pack . show . f . L.boxMeta $ b -getGlobalTransaction :: CC.BaseColors -> Box -> PreSpec+getGlobalTransaction :: Box -> PreSpec getGlobalTransaction = serialCellMaybe (fmap (L.forward . L.unGlobalTransaction) . Q.globalTransaction) -getRevGlobalTransaction :: CC.BaseColors -> Box -> PreSpec+getRevGlobalTransaction :: Box -> PreSpec getRevGlobalTransaction = serialCellMaybe (fmap (L.backward . L.unGlobalTransaction) . Q.globalTransaction) -getGlobalPosting :: CC.BaseColors -> Box -> PreSpec+getGlobalPosting :: Box -> PreSpec getGlobalPosting = serialCellMaybe (fmap (L.forward . L.unGlobalPosting) . Q.globalPosting) -getRevGlobalPosting :: CC.BaseColors -> Box -> PreSpec+getRevGlobalPosting :: Box -> PreSpec getRevGlobalPosting = serialCellMaybe (fmap (L.backward . L.unGlobalPosting) . Q.globalPosting) -getFileTransaction :: CC.BaseColors -> Box -> PreSpec+getFileTransaction :: Box -> PreSpec getFileTransaction = serialCellMaybe (fmap (L.forward . L.unFileTransaction) . Q.fileTransaction) -getRevFileTransaction :: CC.BaseColors -> Box -> PreSpec+getRevFileTransaction :: Box -> PreSpec getRevFileTransaction = serialCellMaybe (fmap (L.backward . L.unFileTransaction) . Q.fileTransaction) -getFilePosting :: CC.BaseColors -> Box -> PreSpec+getFilePosting :: Box -> PreSpec getFilePosting = serialCellMaybe (fmap (L.forward . L.unFilePosting) . Q.filePosting) -getRevFilePosting :: CC.BaseColors -> Box -> PreSpec+getRevFilePosting :: Box -> PreSpec getRevFilePosting = serialCellMaybe (fmap (L.backward . L.unFilePosting) . Q.filePosting) -getSorted :: CC.BaseColors -> Box -> PreSpec+getSorted :: Box -> PreSpec getSorted = serialCell (L.forward . Ly.unSortedNum . M.sortedNum) -getRevSorted :: CC.BaseColors -> Box -> PreSpec+getRevSorted :: Box -> PreSpec getRevSorted = serialCell (L.backward . Ly.unSortedNum . M.sortedNum) -getFiltered :: CC.BaseColors -> Box -> PreSpec+getFiltered :: Box -> PreSpec getFiltered = serialCell (L.forward . Ly.unFilteredNum . M.filteredNum) -getRevFiltered :: CC.BaseColors -> Box -> PreSpec+getRevFiltered :: Box -> PreSpec getRevFiltered = serialCell (L.backward . Ly.unFilteredNum . M.filteredNum) -getVisible :: CC.BaseColors -> Box -> PreSpec+getVisible :: Box -> PreSpec getVisible = serialCell (L.forward . M.unVisibleNum . M.visibleNum) -getRevVisible :: CC.BaseColors -> Box -> PreSpec+getRevVisible :: Box -> PreSpec getRevVisible = serialCell (L.backward . M.unVisibleNum . M.visibleNum) -getLineNum :: CC.BaseColors -> Box -> PreSpec-getLineNum bc b = oneLine t bc b where+getLineNum :: Box -> PreSpec+getLineNum b = oneLine t E.Other b where lineTxt = pack . show . L.unPostingLine t = maybe empty lineTxt (Q.postingLine . L.boxPostFam $ b) -getDate :: CC.BaseColors -> (Box -> X.Text) -> Box -> PreSpec-getDate bc gd b = oneLine (gd b) bc b+getDate :: (Box -> X.Text) -> Box -> PreSpec+getDate gd b = oneLine (gd b) E.Other b -getFlag :: CC.BaseColors -> Box -> PreSpec-getFlag bc i = oneLine t bc i where+getFlag :: Box -> PreSpec+getFlag i = oneLine t E.Other i where t = maybe empty L.text (Q.flag . L.boxPostFam $ i) -getNumber :: CC.BaseColors -> Box -> PreSpec-getNumber bc i = oneLine t bc i where+getNumber :: Box -> PreSpec+getNumber i = oneLine t E.Other i where t = maybe empty L.text (Q.number . L.boxPostFam $ i) dcTxt :: L.DrCr -> Text-dcTxt L.Debit = pack "Dr"-dcTxt L.Credit = pack "Cr"+dcTxt L.Debit = X.singleton '<'+dcTxt L.Credit = X.singleton '>' -- | Gives a one-line cell that is colored according to whether the -- posting is a debit or credit.-coloredPostingCell :: Text -> CC.DrCrColors -> Box -> PreSpec-coloredPostingCell t dccol i = PreSpec j ts [bit] where+coloredPostingCell :: Text -> Box -> PreSpec+coloredPostingCell t i = PreSpec j (lbl, eo) [bit] where j = R.LeftJustify- bit = C.chunk ts t- dc = Q.drCr . L.boxPostFam $ i- ts = CC.colors (M.visibleNum . L.boxMeta $ i)- . CC.drCrToBaseColors dc- $ dccol+ lbl = case Q.drCr . L.boxPostFam $ i of+ L.Debit -> E.Debit+ L.Credit -> E.Credit+ eo = E.fromVisibleNum . M.visibleNum . L.boxMeta $ i+ bit = E.PreChunk lbl eo t -getPostingDrCr :: CC.DrCrColors -> Box -> PreSpec-getPostingDrCr dc i = coloredPostingCell t dc i where+getPostingDrCr :: Box -> PreSpec+getPostingDrCr i = coloredPostingCell t i where t = dcTxt . Q.drCr . L.boxPostFam $ i -getPostingCmdty :: CC.DrCrColors -> Box -> PreSpec-getPostingCmdty dc i = coloredPostingCell t dc i where- t = L.text . L.Delimited (X.singleton ':') - . L.textList . Q.commodity . L.boxPostFam $ i+getPostingCmdty :: Box -> PreSpec+getPostingCmdty i = coloredPostingCell t i where+ t = L.unCommodity . Q.commodity . L.boxPostFam $ i -getPostingQty :: (Box -> X.Text) -> CC.DrCrColors -> Box -> PreSpec-getPostingQty qf dc i = coloredPostingCell (qf i) dc i+getPostingQty :: (Box -> X.Text) -> Box -> PreSpec+getPostingQty qf i = coloredPostingCell (qf i) i -getTotalDrCr :: CC.DrCrColors -> Box -> PreSpec-getTotalDrCr dccol i =+getTotalDrCr :: Box -> PreSpec+getTotalDrCr i = let vn = M.visibleNum . L.boxMeta $ i- ts = CC.colors vn bc- bc = CC.drCrToBaseColors dc dccol+ ps = (lbl, eo) dc = Q.drCr . L.boxPostFam $ i- bits = case M.balance . L.boxMeta $ i of- Nothing ->- let spec = CC.noBalanceColors vn dccol- in [C.chunk spec (pack "--")]- Just bal ->- let toBit bl =- let spec = - CC.colors vn- . CC.bottomLineToBaseColors dccol- $ bl- txt = case bl of- L.Zero -> pack "--"- L.NonZero (L.Column clmDrCr _) ->- dcTxt clmDrCr- in C.chunk spec txt- in fmap toBit . elems . L.unBalance $ bal+ lbl = E.dcToLbl dc+ eo = E.fromVisibleNum vn+ bal = L.unBalance . M.balance . L.boxMeta $ i+ bits =+ if Map.null bal+ then [E.PreChunk E.Zero eo (pack "--")]+ else fmap (flip E.bottomLineToDrCr eo) . elems $ bal j = R.LeftJustify- in PreSpec j ts bits+ in PreSpec j ps bits -getTotalCmdty :: CC.DrCrColors -> Box -> PreSpec-getTotalCmdty dccol i =+getTotalCmdty :: Box -> PreSpec+getTotalCmdty i = let vn = M.visibleNum . L.boxMeta $ i j = R.RightJustify- ts = CC.colors vn bc- bc = CC.drCrToBaseColors dc dccol+ ps = (lbl, eo) dc = Q.drCr . L.boxPostFam $ i- bits = case M.balance . L.boxMeta $ i of- Nothing ->- let spec = CC.noBalanceColors vn dccol- in [C.chunk spec (pack "--")]- Just bal ->- let toBit (com, nou) =- let spec =- CC.colors vn- . CC.bottomLineToBaseColors dccol- $ nou- txt = L.text- . L.Delimited (X.singleton ':')- . L.textList- $ com- in C.chunk spec txt- in fmap toBit . assocs . L.unBalance $ bal- in PreSpec j ts bits+ eo = E.fromVisibleNum vn+ lbl = E.dcToLbl dc+ bal = Map.toList . L.unBalance . M.balance . L.boxMeta $ i+ preChunks = E.balancesToCmdtys eo bal+ in PreSpec j ps preChunks getTotalQty ::- (L.Commodity -> L.BottomLine -> X.Text)- -> CC.DrCrColors+ (L.Commodity -> L.Qty -> X.Text) -> Box -> PreSpec-getTotalQty balFmt dccol i =+getTotalQty balFmt i = let vn = M.visibleNum . L.boxMeta $ i j = R.LeftJustify- ts = CC.colors vn bc- bc = CC.drCrToBaseColors dc dccol dc = Q.drCr . L.boxPostFam $ i- bits = case M.balance . L.boxMeta $ i of- Nothing ->- let spec = CC.noBalanceColors vn dccol- in [C.chunk spec (pack "--")]- Just bal ->- fmap toChunk . assocs . L.unBalance $ bal- where- toChunk (com, nou) =- let spec = - CC.colors vn- . CC.bottomLineToBaseColors dccol- $ nou- txt = balFmt com nou- in C.chunk spec txt- in PreSpec j ts bits+ ps = (E.dcToLbl dc, eo)+ eo = E.fromVisibleNum vn+ bal = Map.toList . L.unBalance . M.balance . L.boxMeta $ i+ preChunks = E.balanceToQtys balFmt eo bal+ in PreSpec j ps preChunks growingFields :: F.Fields Bool -> Fields Bool-growingFields f = Fields {- globalTransaction = F.globalTransaction f+growingFields f = Fields+ { globalTransaction = F.globalTransaction f , revGlobalTransaction = F.revGlobalTransaction f , globalPosting = F.globalPosting f , revGlobalPosting = F.revGlobalPosting f@@ -400,8 +341,8 @@ -- | Returns a Fields where each record has its corresponding EField. eFields :: Fields EFields-eFields = Fields {- globalTransaction = EGlobalTransaction+eFields = Fields+ { globalTransaction = EGlobalTransaction , revGlobalTransaction = ERevGlobalTransaction , globalPosting = EGlobalPosting , revGlobalPosting = ERevGlobalPosting@@ -427,8 +368,8 @@ , totalQty = ETotalQty } -- | All growing fields.-data Fields a = Fields {- globalTransaction :: a+data Fields a = Fields+ { globalTransaction :: a , revGlobalTransaction :: a , globalPosting :: a , revGlobalPosting :: a@@ -483,8 +424,8 @@ (f (totalQty i) z))))))))))))))))))))))) instance Functor Fields where- fmap f i = Fields {- globalTransaction = f (globalTransaction i)+ fmap f i = Fields+ { globalTransaction = f (globalTransaction i) , revGlobalTransaction = f (revGlobalTransaction i) , globalPosting = f (globalPosting i) , revGlobalPosting = f (revGlobalPosting i)@@ -510,8 +451,8 @@ , totalQty = f (totalQty i) } instance Applicative Fields where- pure a = Fields {- globalTransaction = a+ pure a = Fields+ { globalTransaction = a , revGlobalTransaction = a , globalPosting = a , revGlobalPosting = a@@ -536,8 +477,8 @@ , totalCmdty = a , totalQty = a } - fl <*> fa = Fields {- globalTransaction = globalTransaction fl (globalTransaction fa)+ fl <*> fa = Fields+ { globalTransaction = globalTransaction fl (globalTransaction fa) , revGlobalTransaction = revGlobalTransaction fl (revGlobalTransaction fa) , globalPosting = globalPosting fl (globalPosting fa) , revGlobalPosting = revGlobalPosting fl (revGlobalPosting fa)
− Penny/Cabin/Posts/Help.hs
@@ -1,66 +0,0 @@-module Penny.Cabin.Posts.Help where--import qualified Data.Text as X--help :: X.Text-help = X.pack helpStr--helpStr :: String-helpStr = unlines [- "postings, pos",- " Show postings in order with a running balance.",- " The postings report takes the same options shown",- " from above in the categories \"Posting filters\" to",- " \"Removing postings after sorting and filtering\",",- " with the options affecting which postings are shown on screen.",- " Postings not shown still affect the running balance.",- "",- " Additional sequence number filtering options",- " Like the \"Sequence numbers\" options above, these",- " options take the form --option cmp num.",- "",- " --filtered, --revFiltered",- " All postings, after filters given in the filter",- " specification portion of the command line are",- " applied",- " --sorted, --revSorted",- " All postings remaining after filtering and after",- " postings have been sorted",- "",- " Other additional options:",- "",- " --color yes|no|auto|256",- " yes: show 8 colors always",- " no: never show colors",- " auto: show 8 or 256 colors, but only if stdout is a terminal",- " 256: show 256 colors always",- " --background light|dark",- " Use appropriate color scheme for terminal background",- "",- " --width num",- " Hint for roughly how wide the report should be in columns",- " --show field, --hide field",- " show or hide this field, where field is one of:",- " globalTransaction, revGlobalTransaction,",- " globalPosting, revGlobalPosting,",- " fileTransaction, revFileTransaction,",- " filePosting, revFilePosting,",- " filtered, revFiltered,",- " sorted, revSorted,",- " visible, revVisible,",- " lineNum,",- " date, flag, number, payee, account,",- " postingDrCr, postingCommodity, postingQty,",- " totalDrCr, totalCommodity, totalQty,",- " tags, memo, filename",- " --show-all",- " Show all fields",- " --hide-all",- " Hide all fields",- "",- " --show-zero-balances",- " Show balances that are zero",- " --hide-zero-balances",- " Hide balances that are zero",- ""- ]
Penny/Cabin/Posts/Meta.hs view
@@ -11,6 +11,7 @@ import qualified Penny.Liberty as Ly import qualified Penny.Cabin.Meta as M import qualified Penny.Cabin.Options as CO+import Data.Monoid (mempty, mappend) -- | The Box type that is used throughout the Posts modules. type Box = L.Box PostMeta@@ -19,16 +20,16 @@ PostMeta { filteredNum :: Ly.FilteredNum , sortedNum :: Ly.SortedNum , visibleNum :: M.VisibleNum- , balance :: Maybe L.Balance }+ , balance :: L.Balance } deriving Show addMetadata ::- [(L.Box (Ly.LibertyMeta, Maybe L.Balance))]+ [(L.Box (Ly.LibertyMeta, L.Balance))] -> [Box]-addMetadata = M.visibleNums f where- f vn (lm, mb) =- PostMeta (Ly.filteredNum lm) (Ly.sortedNum lm) vn mb+addMetadata = M.visibleNumBoxes f where+ f vn (lm, b) =+ PostMeta (Ly.filteredNum lm) (Ly.sortedNum lm) vn b -- | Adds appropriate metadata, including the running balance, to a -- list of Box. Because all posts are incorporated into the running@@ -59,21 +60,19 @@ addBalances :: CO.ShowZeroBalances -> [L.Box Ly.LibertyMeta]- -> [(L.Box (Ly.LibertyMeta, Maybe L.Balance))]-addBalances szb = snd . mapAccumL (balanceAccum szb) Nothing+ -> [(L.Box (Ly.LibertyMeta, L.Balance))]+addBalances szb = snd . mapAccumL (balanceAccum szb) mempty balanceAccum :: CO.ShowZeroBalances- -> Maybe L.Balance+ -> L.Balance -> L.Box Ly.LibertyMeta- -> (Maybe L.Balance, (L.Box (Ly.LibertyMeta, Maybe L.Balance)))-balanceAccum (CO.ShowZeroBalances szb) mb po =+ -> (L.Balance, (L.Box (Ly.LibertyMeta, L.Balance)))+balanceAccum (CO.ShowZeroBalances szb) balOld po = let balThis = L.entryToBalance . Q.entry . L.boxPostFam $ po- balNew = case mb of- Nothing -> balThis- Just balOld -> L.addBalances balOld balThis+ balNew = mappend balOld balThis balNoZeroes = L.removeZeroCommodities balNew- bal' = if szb then Just balNew else balNoZeroes+ bal' = if szb then balNew else balNoZeroes po' = L.Box (L.boxMeta po, bal') (L.boxPostFam po) in (bal', po')
Penny/Cabin/Posts/Parser.hs view
@@ -1,278 +1,166 @@-module Penny.Cabin.Posts.Parser (State(..), parseOptions,- Error(..)) where+module Penny.Cabin.Posts.Parser (State(..),+ allSpecs) where -import Control.Applicative ((<|>), (<$>), pure, many, (<*>),+import Control.Applicative ((<$>), pure, (<*>), Applicative)-import Control.Monad ((>=>)) import qualified Control.Monad.Exception.Synchronous as Ex import Data.Char (toLower) import qualified Data.Foldable as Fdbl import qualified System.Console.MultiArg.Combinator as C-import System.Console.MultiArg.Prim (Parser)+import qualified System.Console.MultiArg as MA -import qualified Penny.Cabin.Chunk as CC-import qualified Penny.Cabin.Colors as PC+import qualified Penny.Cabin.Parsers as P import qualified Penny.Cabin.Posts.Fields as F import qualified Penny.Cabin.Posts.Types as Ty-import qualified Penny.Cabin.Colors.DarkBackground as DB-import qualified Penny.Cabin.Colors.LightBackground as LB import qualified Penny.Cabin.Options as CO-import qualified Penny.Copper as Cop import qualified Penny.Liberty as Ly import qualified Penny.Liberty.Expressions as Exp import qualified Penny.Lincoln as L import qualified Penny.Shield as S import qualified Text.Matchers.Text as M -data Error = BadColorName String- | BadBackgroundArg String- | BadWidthArg String- | NoMatchingFieldName- | MultipleMatchingFieldNames [String]- | LibertyError Ly.Error- | BadNumber String- | BadComparator String- deriving Show--data State = State {- sensitive :: M.CaseSensitive+data State = State+ { sensitive :: M.CaseSensitive , factory :: L.Factory , tokens :: [Ly.Token (L.Box Ly.LibertyMeta -> Bool)] , postFilter :: [Ly.PostFilterFn] , fields :: F.Fields Bool- , colorPref :: CC.Colors- , drCrColors :: PC.DrCrColors- , baseColors :: PC.BaseColors , width :: Ty.ReportWidth , showZeroBalances :: CO.ShowZeroBalances+ , showHelp :: Bool } --- | Parses the command line from the first word remaining up until,--- but not including, the first non-option argment.-parseOptions ::- Parser (S.Runtime- -> Cop.DefaultTimeZone- -> Cop.RadGroup- -> State- -> Ex.Exceptional Error State)-parseOptions = f <$> many parseOption where- f ls =- let g rt dtz rg st =- let ls' = map (\fn -> fn rt dtz rg) ls- in (foldl (>=>) return ls') st- in g+allSpecs+ :: S.Runtime -> [MA.OptSpec (State -> Ex.Exceptional String State)]+allSpecs rt =+ operand rt+ ++ boxFilters+ ++ parsePostFilter+ ++ matcherSelect+ ++ caseSelect+ ++ operator+ ++ [ parseWidth+ , showField+ , hideField+ , showAllFields+ , hideAllFields+ , parseZeroBalances+ , optHelp+ ] -parseOption ::- Parser (S.Runtime- -> Cop.DefaultTimeZone- -> Cop.RadGroup- -> State- -> Ex.Exceptional Error State)-parseOption =- operand- <|> wrap boxFilters- <|> wrap parsePostFilter- <|> wrap (impurify matcherSelect)- <|> wrap (impurify caseSelect)- <|> wrap (impurify operator)- <|> (do { f <- color; return (\rt _ _ st -> f rt st)})- <|> wrap background- <|> wrap parseWidth- <|> wrap showField- <|> wrap hideField- <|> wrap (impurify showAllFields)- <|> wrap (impurify hideAllFields)- <|> wrap (impurify parseShowZeroBalances)- <|> wrap (impurify hideZeroBalances)- where- wrap p = do- f <- p- return (\_ _ _ st -> f st)--impurify ::- (Functor f, Applicative m)- => f (a -> b)- -> f (a -> m b)-impurify = fmap (\g -> pure . g)--operand :: Parser (S.Runtime- -> Cop.DefaultTimeZone- -> Cop.RadGroup- -> State- -> Ex.Exceptional Error State)-operand = f <$> Ly.parseOperand+operand+ :: S.Runtime+ -> [MA.OptSpec (State -> Ex.Exceptional String State)]+operand rt = map (fmap f) (Ly.operandSpecs (S.currentTime rt)) where- f lyFn rt dtz rg st =- let dt = S.currentTime rt- cs = sensitive st+ f lyFn st = do+ let cs = sensitive st fty = factory st- in case lyFn dt dtz rg cs fty of- Ex.Exception e -> Ex.throw . LibertyError $ e- Ex.Success (Exp.Operand g) ->- let g' = g . L.boxPostFam- ts' = tokens st ++ [Exp.TokOperand g']- in return st { tokens = ts' }+ (Exp.Operand g) <- lyFn cs fty+ let g' = g . L.boxPostFam+ ts' = tokens st ++ [Exp.TokOperand g']+ return $ st { tokens = ts' } + -- | Processes a option for box-level serials. optBoxSerial :: [String] -- ^ Long options- + -> [Char] -- ^ Short options- + -> (Ly.LibertyMeta -> Int) -- ^ Pulls the serial from the PostMeta- - -> Parser (State -> Ex.Exceptional Error State) -optBoxSerial ls ss f = parseOpt ls ss (C.TwoArg g)+ -> C.OptSpec (State -> Ex.Exceptional String State)++optBoxSerial ls ss f = C.OptSpec ls ss (C.TwoArg g) where g a1 a2 st = do- cmp <- Ex.fromMaybe (BadComparator a1) (Ly.parseComparer a1)- i <- parseInt a2+ cmp <- Ly.parseComparer a1+ i <- Ly.parseInt a2 let h box = let ser = f . L.boxMeta $ box in ser `cmp` i tok = Exp.TokOperand h- return st { tokens = tokens st ++ [tok] }+ return $ st { tokens = tokens st ++ [tok] } -optFilteredNum :: Parser (State -> Ex.Exceptional Error State)+optFilteredNum :: C.OptSpec (State -> Ex.Exceptional String State) optFilteredNum = optBoxSerial ["filtered"] "" f where f = L.forward . Ly.unFilteredNum . Ly.filteredNum -optRevFilteredNum :: Parser (State -> Ex.Exceptional Error State)+optRevFilteredNum :: C.OptSpec (State -> Ex.Exceptional String State) optRevFilteredNum = optBoxSerial ["revFiltered"] "" f where f = L.backward . Ly.unFilteredNum . Ly.filteredNum -optSortedNum :: Parser (State -> Ex.Exceptional Error State)+optSortedNum :: C.OptSpec (State -> Ex.Exceptional String State) optSortedNum = optBoxSerial ["sorted"] "" f where f = L.forward . Ly.unSortedNum . Ly.sortedNum -optRevSortedNum :: Parser (State -> Ex.Exceptional Error State)+optRevSortedNum :: C.OptSpec (State -> Ex.Exceptional String State) optRevSortedNum = optBoxSerial ["revSorted"] "" f where f = L.backward . Ly.unSortedNum . Ly.sortedNum -parseInt :: String -> Ex.Exceptional Error Int-parseInt s = case reads s of- (i, ""):[] -> return i- _ -> Ex.throw . BadNumber $ s--boxFilters :: Parser (State -> Ex.Exceptional Error State)+boxFilters :: [C.OptSpec (State -> Ex.Exceptional String State)] boxFilters =- optFilteredNum- <|> optRevFilteredNum- <|> optSortedNum- <|> optRevSortedNum+ [ optFilteredNum+ , optRevFilteredNum+ , optSortedNum+ , optRevSortedNum+ ] -parsePostFilter :: Parser (State -> Ex.Exceptional Error State)-parsePostFilter = f <$> Ly.parsePostFilter+parsePostFilter :: [C.OptSpec (State -> Ex.Exceptional String State)]+parsePostFilter = [fmap f optH, fmap f optT] where- f ex st =- case ex of- Ex.Exception e -> Ex.throw . LibertyError $ e- Ex.Success pf ->- return st { postFilter = postFilter st ++ [pf] }+ (optH, optT) = Ly.postFilterSpecs+ f exc = case exc of+ Ex.Exception s -> const $ Ex.throw s+ Ex.Success pf ->+ let g st = return $ st { postFilter = postFilter st ++ [pf] }+ in g -matcherSelect :: Parser (State -> State)-matcherSelect = f <$> Ly.parseMatcherSelect- where- f mf st = st { factory = mf } -caseSelect :: Parser (State -> State)-caseSelect = f <$> Ly.parseCaseSelect- where- f cs st = st { sensitive = cs }--operator :: Parser (State -> State)-operator = f <$> Ly.parseOperator- where- f oo st = st { tokens = tokens st ++ [oo] }--parseOpt :: [String] -> [Char] -> C.ArgSpec a -> Parser a-parseOpt ss cs a = C.parseOption [C.OptSpec ss cs a]--color :: Parser (S.Runtime -> State -> Ex.Exceptional Error State)-color = parseOpt ["color"] "" (C.OneArg f)+matcherSelect :: Applicative f => [C.OptSpec (State -> f State)]+matcherSelect = map (fmap f) Ly.matcherSelectSpecs where- f a1 rt st = case pickColorArg rt a1 of- Nothing -> Ex.throw . BadColorName $ a1- Just c -> return (st { colorPref = c })--pickColorArg :: S.Runtime -> String -> Maybe CC.Colors-pickColorArg rt t- | t == "yes" = Just CC.Colors8- | t == "no" = Just CC.Colors0- | t == "256" = Just CC.Colors256- | t == "auto" = Just . CO.maxCapableColors $ rt- | otherwise = Nothing--pickBackgroundArg :: String -> Maybe (PC.DrCrColors, PC.BaseColors)-pickBackgroundArg t- | t == "light" = Just (LB.drCrColors, LB.baseColors)- | t == "dark" = Just (DB.drCrColors, DB.baseColors)- | otherwise = Nothing+ f mf st = pure $ st { factory = mf } -background :: Parser (State -> Ex.Exceptional Error State)-background = parseOpt ["background"] "" (C.OneArg f)+caseSelect :: Applicative f => [C.OptSpec (State -> f State)]+caseSelect = map (fmap f) Ly.caseSelectSpecs where- f a1 st = case pickBackgroundArg a1 of- Nothing -> Ex.throw . BadBackgroundArg $ a1- Just (dc, bc) -> return (st { drCrColors = dc- , baseColors = bc } )-+ f cs st = pure $ st { sensitive = cs } -parseWidth :: Parser (State -> Ex.Exceptional Error State)-parseWidth = parseOpt ["width"] "" (C.OneArg f)+operator :: Applicative f => [C.OptSpec (State -> f State)]+operator = map (fmap f) Ly.operatorSpecs where- f a1 st = case reads a1 of- (i, ""):[] -> return (st { width = Ty.ReportWidth i })- _ -> Ex.throw . BadWidthArg $ a1+ f oo st = pure $ st { tokens = tokens st ++ [oo] } -showField :: Parser (State -> Ex.Exceptional Error State)-showField = parseOpt ["show"] "" (C.OneArg f)+parseWidth :: C.OptSpec (State -> Ex.Exceptional String State)+parseWidth = C.OptSpec ["width"] "" (C.OneArg f) where f a1 st = do- fl <- parseField a1- let newFl = fieldOn (fields st) fl- return st { fields = newFl }+ i <- Ly.parseInt a1+ return $ st { width = Ty.ReportWidth i } -hideField :: Parser (State -> Ex.Exceptional Error State)-hideField = parseOpt ["hide"] "" (C.OneArg f)- where- f a1 st = do- fl <- parseField a1- let newFl = fieldOff (fields st) fl- return st { fields = newFl }+parseField :: String -> Ex.Exceptional String (F.Fields Bool)+parseField str =+ let lower = map toLower str+ checkField s =+ if (map toLower s) == lower+ then (s, True)+ else (s, False)+ flds = checkField <$> F.fieldNames+ in checkFields flds -showAllFields :: Parser (State -> State)-showAllFields = parseOpt ["show-all"] "" (C.NoArg f)- where- f st = st {fields = pure True} -hideAllFields :: Parser (State -> State)-hideAllFields = parseOpt ["hide-all"] "" (C.NoArg f)- where- f st = st {fields = pure False}--parseShowZeroBalances :: Parser (State -> State)-parseShowZeroBalances = parseOpt opt "" (C.NoArg f)- where- opt = ["show-zero-balances"]- f st = st {showZeroBalances = CO.ShowZeroBalances True }--hideZeroBalances :: Parser (State -> State)-hideZeroBalances = parseOpt ["hide-zero-balances"] "" (C.NoArg f)- where- f st = st {showZeroBalances = CO.ShowZeroBalances False }- -- | Turns a field on if it is True. fieldOn :: F.Fields Bool@@ -281,7 +169,7 @@ -> F.Fields Bool -- ^ Record that should have one True element indicating a field -- name seen on the command line; other elements should be False- + -> F.Fields Bool -- ^ Fields as seen so far, with new field added @@ -291,11 +179,11 @@ fieldOff :: F.Fields Bool -- ^ Fields seen so far- + -> F.Fields Bool -- ^ Record that should have one True element indicating a field -- name seen on the command line; other elements should be False- + -> F.Fields Bool -- ^ Fields as seen so far, with new field added @@ -304,55 +192,51 @@ f o False = o f _ True = False -parseField :: String -> Ex.Exceptional Error (F.Fields Bool)-parseField str =- let lower = map toLower str- checkField s =- if (map toLower s) == lower- then (s, True)- else (s, False)- flds = checkField <$> fieldNames- in checkFields flds+showField :: C.OptSpec (State -> Ex.Exceptional String State)+showField = C.OptSpec ["show"] "" (C.OneArg f)+ where+ f a1 st = do+ fl <- parseField a1+ let newFl = fieldOn (fields st) fl+ return $ st { fields = newFl } +hideField :: C.OptSpec (State -> Ex.Exceptional String State)+hideField = C.OptSpec ["hide"] "" (C.OneArg f)+ where+ f a1 st = do+ fl <- parseField a1+ let newFl = fieldOff (fields st) fl+ return $ st { fields = newFl }++showAllFields :: Applicative f => C.OptSpec (State -> f State)+showAllFields = C.OptSpec ["show-all"] "" (C.NoArg f)+ where+ f st = pure $ st {fields = pure True}++hideAllFields :: Applicative f => C.OptSpec (State -> f State)+hideAllFields = C.OptSpec ["hide-all"] "" (C.NoArg f)+ where+ f st = pure $ st {fields = pure False}++optHelp :: Applicative f => C.OptSpec (State -> f State)+optHelp = fmap f P.help+ where+ f _ st = pure $ st { showHelp = True }++parseZeroBalances :: Applicative f => C.OptSpec (State -> f State)+parseZeroBalances = fmap f P.zeroBalances+ where+ f szb st = pure $ st { showZeroBalances = szb }+ -- | Checks the fields with the True value to ensure there is only one.-checkFields :: F.Fields (String, Bool) -> Ex.Exceptional Error (F.Fields Bool)+checkFields ::+ F.Fields (String, Bool)+ -> Ex.Exceptional String (F.Fields Bool) checkFields fs = let f (s, b) ls = if b then s:ls else ls in case Fdbl.foldr f [] fs of- [] -> Ex.throw NoMatchingFieldName+ [] -> Ex.throw "no matching field names" _:[] -> return (snd <$> fs)- ls -> Ex.throw . MultipleMatchingFieldNames $ ls-+ _ -> Ex.throw "multiple matching field names" -fieldNames :: F.Fields String-fieldNames = F.Fields {- F.globalTransaction = "globalTransaction"- , F.revGlobalTransaction = "revGlobalTransaction"- , F.globalPosting = "globalPosting"- , F.revGlobalPosting = "revGlobalPosting"- , F.fileTransaction = "fileTransaction"- , F.revFileTransaction = "revFileTransaction"- , F.filePosting = "filePosting"- , F.revFilePosting = "revFilePosting"- , F.filtered = "filtered"- , F.revFiltered = "revFiltered"- , F.sorted = "sorted"- , F.revSorted = "revSorted"- , F.visible = "visible"- , F.revVisible = "revVisible"- , F.lineNum = "lineNum"- , F.date = "date"- , F.flag = "flag"- , F.number = "number"- , F.payee = "payee"- , F.account = "account"- , F.postingDrCr = "postingDrCr"- , F.postingCmdty = "postingCmdty"- , F.postingQty = "postingQty"- , F.totalDrCr = "totalDrCr"- , F.totalCmdty = "totalCmdty"- , F.totalQty = "totalQty"- , F.tags = "tags"- , F.memo = "memo"- , F.filename = "filename" }
Penny/Cabin/Posts/Spacers.hs view
@@ -3,8 +3,8 @@ -- field. module Penny.Cabin.Posts.Spacers where -data Spacers a = Spacers {- globalTransaction :: a+data Spacers a = Spacers+ { globalTransaction :: a , revGlobalTransaction :: a , globalPosting :: a , revGlobalPosting :: a@@ -19,7 +19,6 @@ , visible :: a , revVisible :: a , lineNum :: a- -- ^ The line number from the posting's metadata , date :: a , flag :: a , number :: a@@ -29,5 +28,5 @@ , postingCmdty :: a , postingQty :: a , totalDrCr :: a- , totalCmdty :: a }- deriving (Show, Eq)+ , totalCmdty :: a+ } deriving (Show, Eq)
Penny/Cabin/Row.hs view
@@ -1,3 +1,27 @@+-- | Displays a single on-screen row. A row may contain multiple+-- screen lines and multiple columns.+--+-- This module only deals with a single row at a time. Each cell in+-- the row can have more than one screen line; this module will make+-- sure that the cells have appropriate padding on the bottom so that+-- the row appears nicely. This module will also justify each cell so+-- that its left side or right side is ragged; however, you first have+-- to specify how wide you want the cell to be.+--+-- This module is a little dumber than you might first think it could+-- be. For instance it would be possible to write a function that+-- takes a number of rows and automatically justifies all the cells by+-- finding the widest cell in a column. Indeed I might eventually+-- write such a function because it might be useful in, for example,+-- the multi-commodity balance report. However, such a function would+-- not be useful in all cases; in particular, the Posts report is very+-- complicated to lay out, and the automatic function described above+-- would not do the right thing.+--+-- So this module offers some useful automation, even if it is at a+-- level that is apparently lower that what is possible. Thus the+-- present 'row' function likely will not change, even if eventually I+-- add a 'table' function that automatically justifies many rows. module Penny.Cabin.Row ( Justification(LeftJustify, RightJustify), ColumnSpec(ColumnSpec, justification, width, padSpec, bits),@@ -7,6 +31,7 @@ import Data.List (transpose) import qualified Data.Text as X import qualified Penny.Cabin.Chunk as C+import qualified Penny.Cabin.Scheme as E -- | How to justify cells. LeftJustify leaves the right side -- ragged. RightJustify leaves the left side ragged.@@ -24,44 +49,48 @@ data ColumnSpec = ColumnSpec { justification :: Justification , width :: C.Width- , padSpec :: C.TextSpec- , bits :: [C.Chunk] }+ , padSpec :: (E.Label, E.EvenOdd)+ , bits :: [E.PreChunk] } -newtype JustifiedCell = JustifiedCell (Either (C.Chunk, C.Chunk) C.Chunk)+newtype JustifiedCell = JustifiedCell (Either (E.PreChunk, E.PreChunk)+ E.PreChunk) data JustifiedColumn = JustifiedColumn { justifiedCells :: [JustifiedCell] , _justifiedWidth :: C.Width- , _justifiedPadSpec :: C.TextSpec }+ , _justifiedPadSpec :: (E.Label, E.EvenOdd) } newtype PaddedColumns = PaddedColumns [[JustifiedCell]] newtype CellsByRow = CellsByRow [[JustifiedCell]] newtype CellRowsWithNewlines = CellRowsWithNewlines [[JustifiedCell]] -justify ::- C.TextSpec- -> C.Width+justify+ :: C.Width -> Justification- -> C.Chunk+ -> E.PreChunk -> JustifiedCell-justify ts (C.Width w) j b+justify (C.Width w) j pc | origWidth < w = JustifiedCell . Left $ pair- | otherwise = JustifiedCell . Right $ b+ | otherwise = JustifiedCell . Right $ pc where- origWidth = C.unWidth . C.chunkWidth $ b- pad = C.chunk ts t+ origWidth = C.unWidth . E.width $ pc+ lbl = E.label pc+ eo = E.evenOdd pc+ pad = E.PreChunk lbl eo t t = X.replicate (w - origWidth) (X.singleton ' ') pair = case j of- LeftJustify -> (b, pad)- RightJustify -> (pad, b)+ LeftJustify -> (pc, pad)+ RightJustify -> (pad, pc) newtype Height = Height { _unHeight :: Int } deriving (Show, Eq, Ord) height :: [[a]] -> Height-height = Height . maximum . map length+height xs = case xs of+ [] -> Height 0+ ls -> Height . maximum . map length $ ls -row :: [ColumnSpec] -> [C.Chunk]+row :: [ColumnSpec] -> [E.PreChunk] row = concat . concat@@ -73,17 +102,17 @@ justifiedColumn :: ColumnSpec -> JustifiedColumn justifiedColumn (ColumnSpec j w ts bs) = JustifiedColumn cs w ts where- cs = map (justify ts w j) $ bs+ cs = map (justify w j) $ bs bottomPad :: [JustifiedColumn] -> PaddedColumns bottomPad jcs = PaddedColumns pcs where justCells = map justifiedCells jcs (Height h) = height justCells pcs = map toPaddedColumn jcs- toPaddedColumn (JustifiedColumn cs (C.Width w) ts) = let+ toPaddedColumn (JustifiedColumn cs (C.Width w) (lbl, eo)) = let l = length cs nPads = h - l- pad = C.chunk ts t+ pad = E.PreChunk lbl eo t t = X.replicate w (X.singleton ' ') pads = replicate nPads . JustifiedCell . Right $ pad cs'@@ -101,11 +130,11 @@ CellRowsWithNewlines bs' where bs' = foldr f [] bs newline = JustifiedCell . Right- $ C.chunk C.defaultTextSpec (X.singleton '\n')+ $ E.PreChunk E.Other E.Even (X.singleton '\n') f cells acc = (cells ++ [newline]) : acc- -toBits :: CellRowsWithNewlines -> [[[C.Chunk]]]++toBits :: CellRowsWithNewlines -> [[[E.PreChunk]]] toBits (CellRowsWithNewlines cs) = map (map toB) cs where toB (JustifiedCell c) = case c of Left (lb, rb) -> [lb, rb]
+ Penny/Cabin/Scheme.hs view
@@ -0,0 +1,145 @@+-- | Cabin color schemes+--+-- Each element of a Cabin report identifies what it is--a debit on an+-- even line, a credit on an odd line, etc. The user can have several+-- color schemes; the scheme contains color assignments for 8 and 256+-- color terminals. This allows the use of different schemes for light+-- and dark terminals or for any other reason.++module Penny.Cabin.Scheme where++import qualified Penny.Cabin.Chunk as C+import qualified Penny.Cabin.Meta as M+import qualified Penny.Lincoln as L+import qualified Data.Text as X++data Label+ = Debit+ | Credit+ | Zero+ | Other+ deriving (Eq, Ord, Show)++data EvenOdd = Even | Odd deriving (Eq, Ord, Show)++data Labels a = Labels+ { debit :: a+ , credit :: a+ , zero :: a+ , other :: a+ } deriving Show++getLabelValue :: Label -> Labels a -> a+getLabelValue l ls = case l of+ Debit -> debit ls+ Credit -> credit ls+ Zero -> zero ls+ Other -> other ls++data EvenAndOdd a = EvenAndOdd+ { eoEven :: a+ , eoOdd :: a+ } deriving Show++type TextSpecs = Labels (EvenAndOdd C.TextSpec)++data Scheme = Scheme+ { name :: String+ -- ^ The name of this scheme. How it will be identified on the+ -- command line.++ , description :: String+ -- ^ A brief (one-line) description of what this scheme is, such+ -- as @for dark background terminals@++ , textSpecs :: TextSpecs+ } deriving Show+++getEvenOdd :: EvenOdd -> EvenAndOdd a -> a+getEvenOdd eo eao = case eo of+ Even -> eoEven eao+ Odd -> eoOdd eao++getEvenOddLabelValue+ :: Label+ -> EvenOdd+ -> Labels (EvenAndOdd a)+ -> a+getEvenOddLabelValue l eo ls =+ getEvenOdd eo (getLabelValue l ls)++data PreChunk = PreChunk+ { label :: Label+ , evenOdd :: EvenOdd+ , text :: X.Text+ } deriving (Eq, Show)++width :: PreChunk -> C.Width+width = C.Width . X.length . text++makeChunk :: TextSpecs -> PreChunk -> C.Chunk+makeChunk s p =+ C.chunk (getEvenOddLabelValue (label p) (evenOdd p) s)+ (text p)++fromVisibleNum :: M.VisibleNum -> EvenOdd+fromVisibleNum vn =+ let s = M.unVisibleNum vn in+ if even . L.forward $ s then Even else Odd++dcToLbl :: L.DrCr -> Label+dcToLbl L.Debit = Debit+dcToLbl L.Credit = Credit++bottomLineToDrCr :: L.BottomLine -> EvenOdd -> PreChunk+bottomLineToDrCr bl eo = PreChunk lbl eo t+ where+ (lbl, t) = case bl of+ L.Zero -> (Zero, X.pack "--")+ L.NonZero (L.Column clmDrCr _) -> case clmDrCr of+ L.Debit -> (Debit, X.singleton '<')+ L.Credit -> (Credit, X.singleton '>')++balancesToCmdtys+ :: EvenOdd+ -> [(L.Commodity, L.BottomLine)]+ -> [PreChunk]+balancesToCmdtys eo ls =+ if null ls+ then [PreChunk Zero eo (X.pack "--")]+ else map (bottomLineToCmdty eo) ls++bottomLineToCmdty+ :: EvenOdd+ -> (L.Commodity, L.BottomLine)+ -> PreChunk+bottomLineToCmdty eo (cy, bl) = PreChunk lbl eo t+ where+ t = L.unCommodity cy+ lbl = case bl of+ L.Zero -> Zero+ L.NonZero (L.Column clmDrCr _) -> dcToLbl clmDrCr++balanceToQtys+ :: (L.Commodity -> L.Qty -> X.Text)+ -> EvenOdd+ -> [(L.Commodity, L.BottomLine)]+ -> [PreChunk]+balanceToQtys getTxt eo ls =+ if null ls+ then [PreChunk Zero eo (X.pack "--")]+ else map (bottomLineToQty getTxt eo) ls+++bottomLineToQty+ :: (L.Commodity -> L.Qty -> X.Text)+ -> EvenOdd+ -> (L.Commodity, L.BottomLine)+ -> PreChunk+bottomLineToQty getTxt eo (cy, bl) = PreChunk lbl eo t+ where+ (lbl, t) = case bl of+ L.Zero -> (Zero, X.pack "--")+ L.NonZero (L.Column clmDrCr qt) -> (dcToLbl clmDrCr, getTxt cy qt)+
+ Penny/Cabin/Scheme/Schemes.hs view
@@ -0,0 +1,89 @@+-- | Some schemes you can use.++module Penny.Cabin.Scheme.Schemes where++import qualified Penny.Cabin.Scheme as E+import qualified Penny.Cabin.Chunk as C+import qualified Penny.Cabin.Chunk.Switch as Sw++-- | The light color scheme. You can change various values below to+-- affect the color scheme.+light :: E.Scheme+light = E.Scheme "light" "for light background terminals"+ lightLabels++lightLabels :: E.Labels (E.EvenAndOdd C.TextSpec)+lightLabels = E.Labels+ { E.debit = E.EvenAndOdd { E.eoEven = lightDebit lightEvenTextSpec+ , E.eoOdd = lightDebit lightOddTextSpec }+ , E.credit = E.EvenAndOdd { E.eoEven = lightCredit lightEvenTextSpec+ , E.eoOdd = lightCredit lightOddTextSpec }+ , E.zero = E.EvenAndOdd { E.eoEven = lightZero lightEvenTextSpec+ , E.eoOdd = lightZero lightOddTextSpec }+ , E.other = E.EvenAndOdd { E.eoEven = lightEvenTextSpec+ , E.eoOdd = lightOddTextSpec }+ }++lightEvenTextSpec :: C.TextSpec+lightEvenTextSpec = C.defaultTextSpec++lightOddTextSpec :: C.TextSpec+lightOddTextSpec = Sw.switchBackground C.color8_b_default+ C.color256_b_255 lightEvenTextSpec++lightDebit :: C.TextSpec -> C.TextSpec+lightDebit = Sw.switchForeground C.color8_f_magenta C.color256_f_52++lightCredit :: C.TextSpec -> C.TextSpec+lightCredit = Sw.switchForeground C.color8_f_cyan C.color256_f_21++lightZero :: C.TextSpec -> C.TextSpec+lightZero = Sw.switchForeground C.color8_f_black C.color256_f_0++-- | The dark color scheme. You can change various values below to+-- affect the color scheme.+dark :: E.Scheme+dark = E.Scheme "dark" "for dark background terminals"+ darkLabels++darkLabels :: E.Labels (E.EvenAndOdd C.TextSpec)+darkLabels = E.Labels+ { E.debit = E.EvenAndOdd { E.eoEven = darkDebit darkEvenTextSpec+ , E.eoOdd = darkDebit darkOddTextSpec }+ , E.credit = E.EvenAndOdd { E.eoEven = darkCredit darkEvenTextSpec+ , E.eoOdd = darkCredit darkOddTextSpec }+ , E.zero = E.EvenAndOdd { E.eoEven = darkZero darkEvenTextSpec+ , E.eoOdd = darkZero darkOddTextSpec }+ , E.other = E.EvenAndOdd { E.eoEven = darkEvenTextSpec+ , E.eoOdd = darkOddTextSpec }+ }++darkEvenTextSpec :: C.TextSpec+darkEvenTextSpec = C.defaultTextSpec++darkOddTextSpec :: C.TextSpec+darkOddTextSpec = Sw.switchBackground C.color8_b_default+ C.color256_b_235 darkEvenTextSpec++darkDebit :: C.TextSpec -> C.TextSpec+darkDebit = Sw.switchForeground C.color8_f_magenta C.color256_f_208++darkCredit :: C.TextSpec -> C.TextSpec+darkCredit = Sw.switchForeground C.color8_f_cyan C.color256_f_45++darkZero :: C.TextSpec -> C.TextSpec+darkZero = Sw.switchForeground C.color8_f_white C.color256_f_15++-- | Plain scheme has no colors at all.+plain :: E.Scheme+plain = E.Scheme "plain" "uses default terminal colors"+ plainLabels++plainLabels :: E.Labels (E.EvenAndOdd C.TextSpec)+plainLabels = E.Labels+ { E.debit = E.EvenAndOdd C.defaultTextSpec C.defaultTextSpec+ , E.credit = E.EvenAndOdd C.defaultTextSpec C.defaultTextSpec+ , E.zero = E.EvenAndOdd C.defaultTextSpec C.defaultTextSpec+ , E.other = E.EvenAndOdd C.defaultTextSpec C.defaultTextSpec+ }+
Penny/Copper.hs view
@@ -1,51 +1,51 @@--- | Copper - the Penny parser-module Penny.Copper (- -- * Comments- C.Comment(Comment),-- -- * Radix and grouping- Q.RadGroup,- Q.periodComma, Q.periodSpace, Q.commaPeriod, Q.commaSpace,- Q.GroupingSpec(..),- - -- * Default time zone- DT.DefaultTimeZone(DefaultTimeZone),- DT.utcDefault,- - -- * FileContents- FileContents(FileContents, unFileContents),- - -- * Errors- ErrorMsg (ErrorMsg, unErrorMsg),+-- | Copper - the Penny parser.+--+-- The parse functions in this module only accept lists of files+-- rather than individual files because in order to correctly assign+-- the global serials a single function must be able to see all the+-- transactions, not just the transactions in a single file.+module Penny.Copper+ (+ -- * Convenience functions to read and parse files+ parse+ , open+ , openStdin - -- * Items- I.Item(Transaction, Price, CommentItem, BlankLine),- I.Line(unLine),+ -- * Types for things found in ledger files+ , Y.Item(BlankLine, IComment, PricePoint, Transaction)+ , Y.mapItem+ , Y.mapItemA+ , Y.Ledger(Ledger, unLedger)+ , Y.mapLedger+ , Y.mapLedgerA+ , Y.Comment(Comment, unComment)+ , FileContents(FileContents, unFileContents)+ , ErrorMsg (unErrorMsg) - -- * Parsing- Ledger(Ledger, unLedger),- parse,- -- * Rendering- I.render- ) where+ , R.GroupSpec(..)+ , R.GroupSpecs(..)+ , R.ledger + ) where -import Control.Applicative ((<$>))+import Control.Monad (when, replicateM_)+import Control.Applicative (pure, (*>), (<$>))+import Data.Functor.Compose (Compose(Compose, getCompose))+import Data.Maybe (mapMaybe)+import Data.Monoid (mconcat) import qualified Control.Monad.Exception.Synchronous as Ex+import qualified Data.Foldable as F import qualified Data.Text as X-import Text.Parsec ( manyTill, eof )-import qualified Text.Parsec as P+import qualified Data.Text.IO as TIO+import qualified Text.Parsec as Parsec+import qualified Penny.Copper.Parsec as CP -import qualified Penny.Copper.Comments as C-import qualified Penny.Copper.Qty as Q-import qualified Penny.Copper.DateTime as DT-import qualified Penny.Copper.Item as I import qualified Penny.Lincoln as L--data Ledger =- Ledger { unLedger :: [(I.Line, I.Item)] }- deriving Show+import qualified Penny.Copper.Render as R+import qualified Penny.Copper.Types as Y+import qualified System.IO.Error as E+import qualified System.IO as IO newtype FileContents = FileContents { unFileContents :: X.Text } deriving (Eq, Show)@@ -54,79 +54,164 @@ deriving (Eq, Show) parseFile ::- DT.DefaultTimeZone- -> Q.RadGroup- -> (L.Filename, FileContents)- -> Ex.Exceptional ErrorMsg- [(I.Line, I.Item)]-parseFile dtz rg (fn, (FileContents c)) =- let p = addFileMetadata fn- <$> manyTill (I.itemWithLineNumber dtz rg) eof+ (L.Filename, FileContents)+ -> Ex.Exceptional ErrorMsg Y.Ledger+parseFile (fn, (FileContents c)) =+ let p = fmap (addFileMetadata fn) CP.ledger fnStr = X.unpack . L.unFilename $ fn- in case P.parse p fnStr c of+ in case Parsec.parse p fnStr c of Left err -> Ex.throw (ErrorMsg . X.pack . show $ err) Right g -> return g -addFileMetadata ::- L.Filename- -> [(I.Line, I.Item)]- -> [(I.Line, I.Item)]-addFileMetadata fn ls =- let (lns, is) = (map fst ls, map snd ls)- eis = map toEiItem is- procTop s m =- m { L.fileTransaction = Just (L.FileTransaction s)- , L.filename = Just fn }- procPstg s m =- m { L.filePosting = Just (L.FilePosting s) }- eis' = L.addSerialsToEithers procTop procPstg eis- is' = map fromEiItem eis'- in zip lns is'+addFileTransaction+ :: L.Filename+ -> L.Transaction+ -> L.GenSerial L.Transaction+addFileTransaction fn t = f <$> L.getSerial+ where+ f ser = L.changeTransaction fam t+ where+ fam = L.Family tl e e []+ e = L.emptyPostingChangeData+ tl = L.emptyTopLineChangeData+ { L.tcFileTransaction =+ Just (Just $ L.FileTransaction ser)+ , L.tcFilename =+ Just (Just fn) } +addFilePosting+ :: L.Transaction+ -> L.GenSerial L.Transaction+addFilePosting t = f <$> (L.mapChildrenA g (L.unTransaction t))+ where+ f fam = L.changeTransaction+ (L.mapParent (const L.emptyTopLineChangeData) fam) t+ g = const $ fmap h L.getSerial+ where h ser = L.emptyPostingChangeData+ { L.pcFilePosting = Just (Just (L.FilePosting ser)) } -addGlobalMetadata ::- [[(I.Line, I.Item)]]- -> [(I.Line, I.Item)]-addGlobalMetadata lss =- let ls = concat lss- procTop s m =- m { L.globalTransaction = Just (L.GlobalTransaction s) }- procPstg s m =- m { L.globalPosting = Just (L.GlobalPosting s) }- (lns, is) = (map fst ls, map snd ls)- eis = map toEiItem is- eis' = L.addSerialsToEithers procTop procPstg eis- is' = map fromEiItem eis'- in zip lns is'+addFileMetadataTxn+ :: L.Filename+ -> L.Transaction+ -> Compose L.GenSerial L.GenSerial L.Transaction+addFileMetadataTxn fn t = Compose $ do+ t' <- addFileTransaction fn t+ return (addFilePosting t') +toPostings :: L.Transaction -> [L.Posting]+toPostings = F.toList . L.orphans . L.unTransaction++initCntTxn :: [a] -> L.GenSerial ()+initCntTxn ts = replicateM_ (length ts) L.incrementBack++initCntPstg :: [Y.Item] -> L.GenSerial ()+initCntPstg fs = replicateM_ (length ls) L.incrementBack+ where+ ls = concatMap toPostings . mapMaybe toTxn $ fs++toTxn :: Y.Item -> Maybe L.Transaction+toTxn i = case i of+ Y.Transaction t -> Just t+ _ -> Nothing++addFileMetadata :: L.Filename -> Y.Ledger -> Y.Ledger+addFileMetadata fn a@(Y.Ledger ls) =+ (L.makeSerials . (initCntPstg ls *>))+ . (L.makeSerials . (initCntTxn ls *>))+ . getCompose+ . Y.mapLedgerA (Y.mapItemA pure pure (addFileMetadataTxn fn))+ $ a+++addGlobalTransaction+ :: L.Transaction+ -> L.GenSerial L.Transaction+addGlobalTransaction t = f <$> L.getSerial+ where+ f ser = L.changeTransaction fam t+ where+ fam = L.Family tl e e []+ e = L.emptyPostingChangeData+ tl = L.emptyTopLineChangeData+ { L.tcGlobalTransaction =+ Just (Just $ L.GlobalTransaction ser) }++addGlobalPosting+ :: L.Transaction+ -> L.GenSerial L.Transaction+addGlobalPosting t = f <$> (L.mapChildrenA g (L.unTransaction t))+ where+ f fam = L.changeTransaction+ (L.mapParent (const L.emptyTopLineChangeData) fam) t+ g = const $ fmap h L.getSerial+ where+ h ser = L.emptyPostingChangeData+ { L.pcGlobalPosting = Just (Just (L.GlobalPosting ser)) }++addGlobalMetadataTxn ::+ L.Transaction+ -> Compose L.GenSerial L.GenSerial L.Transaction+addGlobalMetadataTxn t = Compose $ do+ t' <- addGlobalTransaction t+ return (addGlobalPosting t')++addGlobalMetadata :: [Y.Ledger] -> Y.Ledger+addGlobalMetadata ls =+ (L.makeSerials . (initCntPstg ls' *>))+ . (L.makeSerials . (initCntTxn ls' *>))+ . getCompose+ . Y.mapLedgerA (Y.mapItemA pure pure addGlobalMetadataTxn)+ $ a+ where+ a@(Y.Ledger ls') = mconcat ls+ parse ::- DT.DefaultTimeZone- -> Q.RadGroup- -> [(L.Filename, FileContents)]- -> Ex.Exceptional ErrorMsg Ledger-parse dtz rg ps =- mapM (parseFile dtz rg) ps- >>= (return . Ledger . addGlobalMetadata)+ [(L.Filename, FileContents)]+ -> Ex.Exceptional ErrorMsg Y.Ledger+parse ps = fmap addGlobalMetadata $ mapM parseFile ps -data Other = OPrice L.PricePoint- | OCommentItem C.Comment- | OBlankLine- deriving Show+-- | Reads and parses the given filename. Does not do anything to+-- handle @-@ arguments; for that, see openStdin. Errors are indicated+-- by throwing an exception.+open :: [String] -> IO Y.Ledger+open = fmap addGlobalMetadata+ . mapM (\s -> getFileContents s >>= parseAndResolve) -type EiItem = Either Other L.Transaction -toEiItem :: I.Item -> EiItem-toEiItem i = case i of- I.Transaction t -> Right t- I.Price p -> Left (OPrice p)- I.CommentItem c -> Left (OCommentItem c)- I.BlankLine -> Left OBlankLine+getFileContents :: String -> IO (L.Filename, FileContents)+getFileContents s = fmap f (TIO.readFile s)+ where f c = (L.Filename . X.pack $ s, FileContents c) -fromEiItem :: EiItem -> I.Item-fromEiItem i = case i of- Left l -> case l of- OPrice p -> I.Price p- OCommentItem c -> I.CommentItem c- OBlankLine -> I.BlankLine- Right t -> I.Transaction t+parseAndResolve :: (L.Filename, FileContents) -> IO Y.Ledger+parseAndResolve p@(L.Filename fn, _) =+ Ex.switch err return $ parseFile p+ where+ err (ErrorMsg x) =+ E.ioError $ E.mkIOError E.userErrorType+ ("could not parse file: " ++ X.unpack x)+ Nothing (Just . X.unpack $ fn) ++-- | Reads and parses the given files. If any of the files is @-@,+-- reads standard input. If the list of files is empty, reads standard+-- input. Errors are indicated by throwing an exception.+openStdin :: [String] -> IO Y.Ledger+openStdin ss =+ let ls = if null ss+ then fmap (\x -> [x]) (getFileContentsStdin "-")+ else mapM getFileContentsStdin ss+ in fmap addGlobalMetadata (ls >>= mapM parseAndResolve)++getFileContentsStdin :: String -> IO (L.Filename, FileContents)+getFileContentsStdin s = do+ txt <- if s == "-"+ then do+ isTerm <- IO.hIsTerminalDevice IO.stdin+ when isTerm+ (IO.hPutStrLn IO.stderr $+ "warning: reading from standard input, which"+ ++ "is a terminal.")+ TIO.hGetContents IO.stdin+ else TIO.readFile s+ let fn = L.Filename . X.pack $ if s == "-" then "<stdin>" else s+ return (fn, FileContents txt)
− Penny/Copper/Account.hs
@@ -1,131 +0,0 @@--- | Account parsers. Account names fall into one of three groups:------ * Level 1 account. Can have nearly any character, including--- spaces. However, when in a Ledger file they must be quoted.------ * Level 2 account. The first sub-account begins with a letter. All--- other characters may be nearly any character, except for a space.-module Penny.Copper.Account (- lvl1Account- , lvl1AccountQuoted- , lvl2Account- , render- , lvl1Char- , lvl2FirstChar- , lvl2RemainingChar- ) where--import Control.Applicative((<$>), (<*>), (*>))-import qualified Data.Foldable as F-import Data.Text ( snoc, cons, pack, Text )-import qualified Data.Traversable as T-import Text.Parsec (- char, satisfy, many, (<?>),- many1, between, sepBy1, option )-import Text.Parsec.Text ( Parser )--import Data.List.NonEmpty (NonEmpty((:|)))-import qualified Data.List.NonEmpty as NE-import qualified Penny.Lincoln.Bits as B-import Penny.Lincoln.TextNonEmpty ( TextNonEmpty ( TextNonEmpty ),- unsafeTextNonEmpty )-import qualified Penny.Lincoln.HasText as HT-import qualified Penny.Copper.Util as U---- | Characters allowed in a Level 1 account. (Check the source code--- to see what these are).-lvl1Char :: Char -> Bool-lvl1Char c = allowed && notBanned where- allowed = U.rangeLettersToSymbols c || c == ' '- notBanned = not $ c `elem` "}:"--lvl1Sub :: Parser B.SubAccountName-lvl1Sub = f <$> p <?> e where- f = B.SubAccountName . unsafeTextNonEmpty- p = many1 (satisfy lvl1Char)- e = "sub account name"--lvl1Account :: Parser B.Account-lvl1Account = B.Account . NE.fromList <$> p <?> e where- e = "account name"- p = sepBy1 lvl1Sub (char ':')--lvl1AccountQuoted :: Parser B.Account-lvl1AccountQuoted = between (char '{') (char '}') lvl1Account---- | Characters allowed for the first character of a Level 2 account.-lvl2FirstChar :: Char -> Bool-lvl2FirstChar = U.rangeLetters---- | Characters allowed for the remaining characters of a Level 2--- account.-lvl2RemainingChar :: Char -> Bool-lvl2RemainingChar c = allowed && notBanned where- allowed = U.rangeLettersToSymbols c- notBanned = not $ c `elem` "}:"--lvl2SubAccountFirst :: Parser B.SubAccountName-lvl2SubAccountFirst = f <$> c1 <*> cs <?> e where- c1 = satisfy lvl2FirstChar- cs = many (satisfy lvl2RemainingChar)- f l1 lr = B.SubAccountName (TextNonEmpty l1 (pack lr))- e = "sub account name beginning with a letter"- -lvl2SubAccountRest :: Parser B.SubAccountName-lvl2SubAccountRest = f <$> cs <?> e where- cs = many1 (satisfy p)- p c = allowed && notBanned where- allowed = U.rangeLettersToSymbols c- notBanned = not $ c `elem` "}:"- f = B.SubAccountName . unsafeTextNonEmpty- e = "sub account name"--lvl2Account :: Parser B.Account-lvl2Account = f <$> p1 <*> p2 <?> e where- f x y = B.Account (x :| y)- p1 = lvl2SubAccountFirst- p2 = option [] $- char ':' *> sepBy1 lvl2SubAccountRest (char ':')- e = "account name"--data Level = L1 | L2- deriving (Eq, Ord, Show)---- | Checks an account to see what level to render it at.-checkAccount :: B.Account -> Maybe Level-checkAccount (B.Account subs) = let- checkFirst = checkFirstSubAccount (NE.head subs)- checkRest = map checkFirstSubAccount (NE.tail subs)- in F.minimum <$> T.sequenceA (checkFirst : checkRest)---- | Checks the first sub account to see if it qualifies as a Level 1--- or Level 2 sub account.-checkFirstSubAccount ::- B.SubAccountName- -> Maybe Level-checkFirstSubAccount s = do- l <- checkOtherSubAccount s- return $ case l of- L1 -> L1- L2 -> let (B.SubAccountName (TextNonEmpty c _)) = s- in if lvl2FirstChar c then L2 else L1---- | Checks a sub account other than the first one to see if it--- qualifies as a Level 1 or Level 2 sub account.-checkOtherSubAccount ::- B.SubAccountName- -> Maybe Level-checkOtherSubAccount = U.checkText ls where- ls = (lvl2RemainingChar, L2) :| [(lvl1Char, L1)]---- | Shows an account, with the minimum level of quoting--- possible. Fails with an error if any one of the characters in the--- account name does not satisfy the 'lvl1Char' predicate. Otherwise--- returns a rendered account, quoted if necessary.-render :: B.Account -> Maybe Text-render a = do- l <- checkAccount a- let t = HT.text . HT.Delimited (pack ":") . HT.textList $ a- return $ case l of- L1 -> cons '{' t `snoc` '}'- L2 -> t
− Penny/Copper/Amount.hs
@@ -1,96 +0,0 @@--- | Amount parsers. An amount is a commodity and a quantity. (An--- entry is an amount and a debit or credit).------ Possible combinations:------ * quoted Level 1 commodity, optional whitespace, quantity------ * Level 3 commodity, optional whitespace, quantity------ * Quantity, optional whitespace, quoted Level 1 commodity------ * Quantity, optional whitespace, Level 2 commodity------ Each quantity may be quoted or unquoted.-module Penny.Copper.Amount (- amount- , render- ) where--import Control.Applicative ((<$>), (<*>), (<|>))-import qualified Data.Text as X-import Text.Parsec ( char, many, (<?>) )-import Text.Parsec.Text ( Parser )--import qualified Penny.Copper.Commodity as C-import qualified Penny.Copper.Qty as Q-import qualified Penny.Lincoln as L---- | Parse optional spaces, returns appropriate metadata.-spaces :: Parser L.SpaceBetween-spaces = f <$> many (char ' ') where- f l = if null l then L.NoSpaceBetween else L.SpaceBetween--cmdtyQty :: Parser L.Commodity- -> Q.RadGroup- -> Parser (L.Amount, L.Format)-cmdtyQty p rg = let- f c s q = (a, fmt) where- a = L.Amount q c- fmt = L.Format L.CommodityOnLeft s- e = "amount, commodity on left"- in f <$> p <*> spaces <*> Q.qty rg <?> e--lvl1CmdtyQty :: Q.RadGroup -> Parser (L.Amount, L.Format)-lvl1CmdtyQty = cmdtyQty C.quotedLvl1Cmdty--lvl3CmdtyQty :: Q.RadGroup -> Parser (L.Amount, L.Format)-lvl3CmdtyQty = cmdtyQty C.lvl3Cmdty--cmdtyOnRight :: Q.RadGroup -> Parser (L.Amount, L.Format)-cmdtyOnRight rg = let - f q s c = (a, fmt) where- a = L.Amount q c- fmt = L.Format L.CommodityOnRight s- e = "amount, commodity on right"- in f- <$> Q.qty rg- <*> spaces- <*> (C.quotedLvl1Cmdty <|> C.lvl2Cmdty)- <?> e---- | Parses an amount with its metadata. Handles all combinations of--- commodities and quantities.-amount :: Q.RadGroup -> Parser (L.Amount, L.Format)-amount rg = lvl1CmdtyQty rg- <|> lvl3CmdtyQty rg- <|> cmdtyOnRight rg- <?> "amount"---- | Render an Amount. The Format is required so that the commodity--- can be displayed in the right place.-render ::- (Q.GroupingSpec, Q.GroupingSpec)- -- ^ Grouping- -> Q.RadGroup- -> L.Format- -> L.Amount- -> Maybe X.Text-render gs rg f a = let- (q, c) = (L.qty a, L.commodity a)- qty = Q.quote $ Q.renderUnquoted rg gs q- ws = case L.between f of- L.SpaceBetween -> X.singleton ' '- L.NoSpaceBetween -> X.empty- mayLvl3 = C.renderLvl3 c- mayLvl2 = C.renderLvl2 c- in do- quotedLvl1 <- C.renderQuotedLvl1 c- let (l, r) = case L.side f of- L.CommodityOnLeft -> case mayLvl3 of- Nothing -> (quotedLvl1, qty)- Just l3 -> (l3, qty)- L.CommodityOnRight -> case mayLvl2 of- Nothing -> (qty, quotedLvl1)- Just l2 -> (qty, l2)- return $ X.concat [l, ws, r]
− Penny/Copper/Comments.hs
@@ -1,35 +0,0 @@-module Penny.Copper.Comments (- Comment(Comment)- , comment- , isCommentChar- , render- ) where--import Control.Applicative ((<*>), (<*), (<$))-import Data.Text ( Text, pack, cons, snoc )-import qualified Data.Text as X-import Text.Parsec (many, char, satisfy, (<?>))-import Text.Parsec.Text ( Parser )--import Penny.Copper.Util (eol)-import qualified Penny.Copper.Util as U--data Comment = Comment Text- deriving (Eq, Show)--isCommentChar :: Char -> Bool-isCommentChar c = inCategory || allowed where- allowed = c `elem` " "- inCategory = U.rangeLettersToSymbols c--comment :: Parser Comment-comment = Comment . pack- <$ char '#'- <*> many (satisfy isCommentChar)- <* eol- <?> "single line comment"--render :: Comment -> Maybe Text-render (Comment t) = case X.find (not . isCommentChar) t of- Nothing -> return $ '#' `cons` t `snoc` '\n'- Just _ -> Nothing
− Penny/Copper/Commodity.hs
@@ -1,187 +0,0 @@--- | Commodity parsers. Commodity names fall into three groups:------ * Level 1 commodities. These can have a broad selection of--- characters, including spaces. The downside is that they need to be--- quoted when they appear in a file. (They don't have to be quoted--- from the command line, presuming that a single command line--- argument captures the entire commodity name and nothing else.)------ * Level 2 commodities. The first sub-commodity begins with a letter--- or a symbol. All other characters may be nearly any other--- character. Spaces however are not permitted.------ * Level 3 commodities. All charcters must be letters or symbols. In--- addition, the first character cannot be a @+@ or a @-@.-module Penny.Copper.Commodity (- -- * Level 1 commodities- lvl1Char,- lvl1Cmdty,- quotedLvl1Cmdty,- commandLineCmdty,- - -- * Level 2 commodities- lvl2FirstChar,- lvl2OtherChars,- lvl2Cmdty,- - -- * Level 3 commodities- lvl3FirstChar,- lvl3OtherChars,- lvl3Cmdty,- - -- * Helpers when parsing from a file- leftSideCmdty,- rightSideCmdty,- - -- * Rendering- renderQuotedLvl1,- renderLvl2,- renderLvl3- ) where--import Control.Applicative ((<*>), (<$>), (*>), (<|>))-import Control.Monad (guard)-import Data.Text ( pack, Text, cons, snoc, singleton )-import Text.Parsec ( satisfy, many, char, sepBy1, many1, (<?>),- between, option, sepBy )-import Text.Parsec.Text ( Parser )--import qualified Penny.Lincoln.Bits as B-import Data.List.NonEmpty (NonEmpty((:|)), fromList)-import Penny.Copper.Util (listIsOK, firstCharOfListIsOK)-import qualified Penny.Copper.Util as U-import qualified Penny.Lincoln.HasText as HT-import Penny.Lincoln.TextNonEmpty ( TextNonEmpty ( TextNonEmpty ),- unsafeTextNonEmpty )---- | Most liberal set of letters allowed in a commodity. -lvl1Char :: Char -> Bool-lvl1Char c = (category || specific) && notBanned where- category = U.rangeLettersToSymbols c- specific = c == ' '- notBanned = not $ c `elem` ['"', ':']---- | A sub commodity comprised of the most liberal characters.-lvl1SubCmdty :: Parser B.SubCommodity-lvl1SubCmdty = f <$> m <?> "sub commodity" where- m = many1 (satisfy lvl1Char)- f cs = B.SubCommodity (TextNonEmpty (head cs) (pack $ tail cs))---- | A commodity that might have spaces inside of the name. To parse--- this when it is in a ledger file, it must be quoted; use--- quotedLvl1Cmdty for that. This parser can be used directly for values--- entered from the command line.-lvl1Cmdty :: Parser B.Commodity-lvl1Cmdty = (B.Commodity . fromList)- <$> sepBy1 lvl1SubCmdty (char ':')- <?> "commodity with spaces"----- | A commodity that may have spaces in the name; is wrapped inside--- of double quotes.-quotedLvl1Cmdty :: Parser B.Commodity-quotedLvl1Cmdty = between q q lvl1Cmdty- <?> "quoted commodity" where- q = char '"'---- | Allows only letters and symbols.-lvl2FirstChar :: Char -> Bool-lvl2FirstChar c = U.rangeLetters c || U.rangeMathCurrency c--lvl2OtherChars :: Char -> Bool-lvl2OtherChars c = category && notBanned where- category = U.rangeLettersToSymbols c- notBanned = not $ c `elem` ['"', ':']--lvl2FirstSubCmdty :: Parser B.SubCommodity-lvl2FirstSubCmdty = f <$> firstLet <*> restLet <?> e where- e = "sub commodity, first character is letter or symbol"- firstLet = satisfy lvl2FirstChar- restLet = many (satisfy lvl2OtherChars)- f l1 lr = B.SubCommodity (TextNonEmpty l1 (pack lr))--lvl2OtherSubCmdty :: Parser B.SubCommodity-lvl2OtherSubCmdty = f <$> ls <?> e where- e = "sub commodity"- ls = many1 (satisfy lvl2OtherChars)- f = B.SubCommodity . unsafeTextNonEmpty--lvl2Cmdty :: Parser B.Commodity-lvl2Cmdty = f <$> firstSub <*> restSubs <?> e where- e = "commodity, first character is letter or symbol"- firstSub = lvl2FirstSubCmdty- restSubs = option []- $ char ':'- *> sepBy1 lvl2OtherSubCmdty (char ':')- f s1 sr = B.Commodity (s1 :| sr)--lvl3OtherChars :: Char -> Bool-lvl3OtherChars c = U.rangeLetters c || U.rangeMathCurrency c--lvl3FirstChar :: Char -> Bool-lvl3FirstChar c = lvl3OtherChars c && (not $ c `elem` "+-")--lvl3FirstSubCmdty :: Parser B.SubCommodity-lvl3FirstSubCmdty = f <$> c <*> cs <?> e where- e = "first sub commodity, letters and symbols only, "- ++ "first character not a + or -"- f c1 cr = B.SubCommodity (TextNonEmpty c1 (pack cr))- c = satisfy lvl3FirstChar- cs = many (satisfy lvl3OtherChars)--lvl3OtherSubCmdty :: Parser B.SubCommodity-lvl3OtherSubCmdty = f <$> ls <?> e where - e = "sub commodity, letters and symbols only"- f = B.SubCommodity . unsafeTextNonEmpty- ls = many1 (satisfy lvl3OtherChars)--lvl3Cmdty :: Parser B.Commodity-lvl3Cmdty = f <$> p1 <*> pr <?> e where- f cf cs = B.Commodity (cf :| cs)- p1 = lvl3FirstSubCmdty- pr = option [] $ char ':' *> sepBy lvl3OtherSubCmdty (char ':')- e = "commodity, letters and symbols only"---- | A commodity being read in from the command line, where the--- commodity is guaranteed to be the only thing to parse.-commandLineCmdty :: Parser B.Commodity-commandLineCmdty = lvl1Cmdty---- | A commodity on the left side of a quantity in a ledger file.-leftSideCmdty :: Parser B.Commodity-leftSideCmdty =- quotedLvl1Cmdty- <|> lvl3Cmdty- <?> "commodity to the left of the quantity"---- | A commodity on the right side of a quantity in a ledger file.-rightSideCmdty :: Parser B.Commodity-rightSideCmdty =- quotedLvl1Cmdty- <|> lvl2Cmdty- <?> "commodity to the right of the quantity"---- | Render a quoted Level 1 commodity. Fails if any character does--- not satisfy lvl1Char.-renderQuotedLvl1 :: B.Commodity -> Maybe Text-renderQuotedLvl1 ca@(B.Commodity c) = do- guard $ listIsOK lvl1Char ca- return $ '"'- `cons` HT.text (HT.Delimited (singleton ':') (HT.textList c))- `snoc` '"'---- | Render a Level 2 commodity. Fails if the first character is not a--- letter or a symbol, or if any other character is a space.-renderLvl2 :: B.Commodity -> Maybe Text-renderLvl2 (B.Commodity c) = do- guard $ firstCharOfListIsOK lvl2FirstChar c- guard $ listIsOK lvl2OtherChars c- return $ HT.text (HT.Delimited (singleton ':') (HT.textList c))---- | Render a Level 3 commodity. Fails if any character is not a--- letter or a symbol.-renderLvl3 :: B.Commodity -> Maybe Text-renderLvl3 (B.Commodity c) = do- guard $ listIsOK lvl3OtherChars c- guard $ firstCharOfListIsOK lvl3FirstChar c- return $ HT.text (HT.Delimited (singleton ':') (HT.textList c))
− Penny/Copper/DateTime.hs
@@ -1,138 +0,0 @@-module Penny.Copper.DateTime (- DefaultTimeZone(DefaultTimeZone, unDefaultTimeZone)- , dateTime- , utcDefault- , render- ) where--import Control.Applicative ((<$>), optional, (<*>))-import qualified Data.Text as X-import Data.Time (fromGregorianValid)-import Data.Maybe (fromMaybe)-import qualified Data.Time as T-import Text.Parsec (char, digit, (<|>), (<?>))-import Text.Parsec.Text ( Parser )-import Control.Monad ( void, when )-import Data.Fixed ( Pico )-import System.Locale (defaultTimeLocale)--import Penny.Copper.Util (spaces)-import qualified Penny.Lincoln.Bits as B--newtype DefaultTimeZone =- DefaultTimeZone { unDefaultTimeZone :: B.TimeZoneOffset }- deriving (Eq, Show)--utcDefault :: DefaultTimeZone-utcDefault = DefaultTimeZone B.noOffset--charToDigit :: Char -> Int-charToDigit c = case c of- '0' -> 0- '1' -> 1- '2' -> 2- '3' -> 3- '4' -> 4- '5' -> 5- '6' -> 6- '7' -> 7- '8' -> 8- '9' -> 9- _ -> error "unrecognized digit"--read2digits :: Parser Int-read2digits = f <$> digit <*> digit where- f d1 d2 = charToDigit d1 * 10 + charToDigit d2--read4digits :: Parser Integer-read4digits = f <$> digit <*> digit <*> digit <*> digit where- f d1 d2 d3 d4 = fromIntegral $- charToDigit d1 * 1000- + charToDigit d2 * 100- + charToDigit d3 * 10- + charToDigit d4--date :: Parser T.Day-date = do- let slash = void $ char '/' <|> char '-'- y <- read4digits- slash- m <- read2digits- slash- d <- read2digits- case fromGregorianValid y m d of- Nothing -> fail "invalid date"- Just dt -> return dt--colon :: Parser ()-colon = void $ char ':'--hrs :: Parser Int-hrs = do- h <- read2digits- when (h > 23) $ fail "invalid hour"- return h--mins :: Parser Int-mins = do- m <- read2digits- when (m > 59) $ fail "invalid minute"- return m--secs :: Parser Pico-secs = do- s <- fromIntegral <$> read2digits- when (s > 59) $ fail "invalid seconds"- return s--timeOfDay :: Parser T.TimeOfDay-timeOfDay = do- h <- hrs- colon- m <- mins- maybeS <- optional (colon >> secs)- let s = fromMaybe (fromIntegral (0 :: Int)) maybeS- return $ T.TimeOfDay h m s--timeZoneOffset :: Parser B.TimeZoneOffset-timeZoneOffset = do- changeSign <-- (char '+' >> return id)- <|> (char '-' >> return (negate :: Int -> Int))- <?> "time zone sign"- h <- read2digits- m <- read2digits- let mi = h * 60 + m- maybe (fail "invalid time zone offset") return- $ B.minsToOffset (changeSign mi)--dateTime :: DefaultTimeZone -> Parser B.DateTime-dateTime (DefaultTimeZone dtz) = do- d <- date- spaces- mayTod <- optional timeOfDay- spaces- mayTz <- optional timeZoneOffset- let tod = fromMaybe T.midnight mayTod- tz = fromMaybe dtz mayTz- return (B.dateTime (T.LocalTime d tod) tz)---- | Render a DateTime. If the DateTime is in the given--- DefaultTimeZone, and the DateTime is midnight, then the time and--- time zone will not be printed. Otherwise, the time and time zone--- will both be printed. The test for time zone equality depends only--- upon the time zone's offset from UTC.-render :: DefaultTimeZone -> B.DateTime -> X.Text-render (DefaultTimeZone dtz) dt = let- lt = B.localTime dt- off = B.timeZone dt- fmtLong = "%F %T %z"- fmtShort = "%F"- sameZone = dtz == off- local = T.localTimeOfDay lt- isMidnight = local == T.midnight- fmt = if sameZone && isMidnight- then fmtShort- else fmtLong- zt = T.ZonedTime lt (T.minutesToTimeZone (B.offsetToMins off))- in X.pack $ T.formatTime defaultTimeLocale fmt zt
− Penny/Copper/Entry.hs
@@ -1,43 +0,0 @@-module Penny.Copper.Entry (entry, render) where--import Control.Applicative ((<$>), (<*>))-import Control.Monad ( void )-import qualified Data.Text as X-import Text.Parsec ( char, string, optional, (<|>), (<?>) )-import Text.Parsec.Text ( Parser )--import Penny.Copper.Amount (amount)-import qualified Penny.Copper.Amount as A-import qualified Penny.Copper.Qty as Q-import Penny.Copper.Util (lexeme)-import qualified Penny.Lincoln as L--drCr :: Parser L.DrCr-drCr = let- dr = do- void (char 'D' <|> char 'd')- void (char 'r') <|> void (string "ebit")- return L.Debit- cr = do- void (char 'C' <|> char 'c')- void (optional (char 'r' >> optional (string "edit")))- return L.Credit- in dr <|> cr--entry :: Q.RadGroup -> Parser (L.Entry, L.Format)-entry rg = f <$> lexeme drCr <*> amount rg <?> e where- f dc (am, fmt) = (L.Entry dc am, fmt)- e = "entry"--render ::- (Q.GroupingSpec, Q.GroupingSpec)- -> Q.RadGroup- -> L.Format- -> L.Entry- -> Maybe X.Text-render gs rg f (L.Entry dc a) = do- amt <- A.render gs rg f a- let dcTxt = X.pack $ case dc of- L.Debit -> "Dr"- L.Credit -> "Cr"- return $ X.append (X.snoc dcTxt ' ') amt
− Penny/Copper/Flag.hs
@@ -1,30 +0,0 @@-module Penny.Copper.Flag (flag, isFlagChar, render) where--import Control.Applicative ((<$>), (<*>))-import Data.Text ( pack, cons, snoc )-import qualified Data.Text as X-import Text.Parsec ( char, satisfy, many, between, (<?>))-import Text.Parsec.Text ( Parser )--import qualified Penny.Copper.Util as U-import qualified Penny.Lincoln.Bits as B-import Penny.Lincoln.TextNonEmpty ( TextNonEmpty ( TextNonEmpty ) )-import qualified Penny.Lincoln.TextNonEmpty as TNE--isFlagChar :: Char -> Bool-isFlagChar c = allowed && not banned where- allowed = U.rangeLettersToSymbols c ||- c == ' '- banned = c == ']'--flag :: Parser B.Flag-flag = between (char '[') (char ']') p <?> "flag" where- p = (\c cs -> B.Flag (TextNonEmpty c (pack cs)))- <$> satisfy isFlagChar- <*> many (satisfy isFlagChar)--render :: B.Flag -> Maybe X.Text-render (B.Flag fl) =- if TNE.all isFlagChar fl- then Just $ '[' `cons` (TNE.toText fl) `snoc` ']'- else Nothing
− Penny/Copper/Item.hs
@@ -1,68 +0,0 @@-module Penny.Copper.Item (- itemWithLineNumber,- Item(Transaction, Price, CommentItem, BlankLine),- Line(unLine),- render- ) where--import Control.Applicative ((<$>), (<*>), (<$))-import qualified Data.Text as X-import Text.Parsec (getPosition, sourceLine, (<|>),- (<?>))-import Text.Parsec.Text ( Parser )--import qualified Penny.Copper.Comments as C-import qualified Penny.Copper.DateTime as DT-import qualified Penny.Lincoln as L-import qualified Penny.Copper.Qty as Q-import Penny.Copper.Price ( price )-import qualified Penny.Copper.Price as P-import qualified Penny.Copper.Transaction as T-import Penny.Copper.Transaction ( transaction )-import Penny.Copper.Util (eol)----- | An Item is used both to hold the result of parsing an item from a--- file and for rendering. It is parameterized on two types: the--- metadata type for the TopLine, and the metadata type for the--- Posting.-data Item = Transaction L.Transaction- | Price L.PricePoint- | CommentItem C.Comment- | BlankLine- deriving Show--newtype Line = Line { unLine :: Int }- deriving (Eq, Show)--itemWithLineNumber ::- DT.DefaultTimeZone- -> Q.RadGroup- -> Parser (Line, Item)-itemWithLineNumber dtz rg = (,)- <$> ((Line . sourceLine) <$> getPosition)- <*> parseItem dtz rg--parseItem ::- DT.DefaultTimeZone- -> Q.RadGroup- -> Parser Item-parseItem dtz rg = let- bl = BlankLine <$ eol <?> "blank line"- t = Transaction <$> transaction dtz rg- p = Price <$> price dtz rg- c = CommentItem <$> C.comment- in (bl <|> t <|> p <|> c)--render ::- DT.DefaultTimeZone- -> (Q.GroupingSpec, Q.GroupingSpec)- -> Q.RadGroup- -> Item- -> Maybe X.Text-render dtz gs rg i = case i of- Transaction t -> T.render dtz gs rg t- Price pp -> P.render dtz gs rg pp- CommentItem c -> C.render c- BlankLine -> Just $ X.singleton '\n'-
− Penny/Copper/Memos/Posting.hs
@@ -1,39 +0,0 @@-module Penny.Copper.Memos.Posting (- memo, render, isCommentChar) where--import Control.Applicative ((<*>), (<*), (<$), (<$>))-import qualified Data.Text as X-import Text.Parsec (char, satisfy, many, (<?>))-import Text.Parsec.Text ( Parser )--import Penny.Copper.Util (eol, rangeLettersToSymbols)-import qualified Penny.Lincoln.Bits as B-import qualified Penny.Lincoln.TextNonEmpty as TNE--isCommentChar :: Char -> Bool-isCommentChar c = rangeLettersToSymbols c- || c == ' '--memo :: Parser B.Memo-memo = B.Memo <$> many memoLine--memoLine :: Parser B.MemoLine-memoLine = (\c cs -> B.MemoLine $ TNE.TextNonEmpty c (X.pack cs))- <$ char '\''- <*> satisfy isCommentChar- <*> many (satisfy isCommentChar)- <* eol- <?> "posting memo"--render :: B.Memo -> Maybe X.Text-render (B.Memo ls) = X.concat <$> mapM renderLine ls --renderLine :: B.MemoLine -> Maybe X.Text-renderLine (B.MemoLine l) =- if TNE.all isCommentChar l- then Just $- X.pack (replicate 8 ' ')- `X.snoc` '\''- `X.append` TNE.toText l- `X.snoc` '\n'- else Nothing
− Penny/Copper/Memos/Transaction.hs
@@ -1,45 +0,0 @@-module Penny.Copper.Memos.Transaction (- memo, render, isCommentChar- ) where--import Control.Applicative ((<*>), (<$>), (<*), (<$))-import qualified Data.Text as X-import Text.Parsec (- char, satisfy, sourceLine, getPosition, (<?>),- many)-import Text.Parsec.Text ( Parser )--import Penny.Copper.Util (rangeLettersToSymbols, eol)-import qualified Penny.Lincoln as L-import qualified Penny.Lincoln.TextNonEmpty as TNE--isCommentChar :: Char -> Bool-isCommentChar c = rangeLettersToSymbols c- || c == ' '--memoLine :: Parser L.MemoLine-memoLine = L.MemoLine <$> (- TNE.TextNonEmpty- <$ char ';'- <*> satisfy isCommentChar- <*> (X.pack <$> many (satisfy isCommentChar))- <* eol )- <?> "posting memo line"---- | Parses a transaction memo and associated whitespace afterward.-memo :: Parser (L.Memo, L.TopMemoLine)-memo =- flip (,)- <$> ((L.TopMemoLine . sourceLine) <$> getPosition)- <*> (L.Memo <$> many memoLine)- <?> "transaction memo"- --- | Renders a transaction memo. Fails if the memo is not renderable.-render :: L.Memo -> Maybe X.Text-render (L.Memo m) = X.concat <$> mapM renderLine m--renderLine :: L.MemoLine -> Maybe X.Text-renderLine (L.MemoLine l) =- if TNE.all isCommentChar l- then Just $ X.singleton ';' `X.append` TNE.toText l `X.snoc` '\n'- else Nothing
− Penny/Copper/Number.hs
@@ -1,29 +0,0 @@-module Penny.Copper.Number (isNumChar, number, render) where--import Control.Applicative ((<$>), (<*>))-import Data.Text ( pack, cons, snoc, Text )-import Text.Parsec ( char, satisfy, many, between, (<?>))-import Text.Parsec.Text ( Parser )--import Penny.Copper.Util (rangeLettersToSymbols)-import qualified Penny.Lincoln.Bits as B-import Penny.Lincoln.TextNonEmpty ( TextNonEmpty ( TextNonEmpty ) )-import qualified Penny.Lincoln.TextNonEmpty as TNE--isNumChar :: Char -> Bool-isNumChar c = allowed && not banned where- allowed = rangeLettersToSymbols c ||- c == ' '- banned = c == ')'--number :: Parser B.Number-number = between (char '(') (char ')') p <?> "number" where- p = (\c cs -> B.Number (TextNonEmpty c (pack cs)))- <$> satisfy isNumChar- <*> many (satisfy isNumChar)--render :: B.Number -> Maybe Text-render (B.Number tne) =- if TNE.all isNumChar tne- then Just $ '(' `cons` TNE.toText tne `snoc` ')'- else Nothing
+ Penny/Copper/Parsec.hs view
@@ -0,0 +1,418 @@+-- | Parsec parsers for the ledger file format. The format is+-- documented in EBNF in the file @doc\/ledger-grammar.org@.+module Penny.Copper.Parsec where++import qualified Penny.Copper.Terminals as T+import qualified Penny.Copper.Types as Y+import Text.Parsec.Text (Parser)+import Text.Parsec (many, many1, satisfy)+import qualified Text.Parsec as P+import qualified Text.Parsec.Pos as Pos+import Control.Arrow (first, second)+import Control.Applicative ((<$>), (<$), (<*>), (*>), (<*),+ (<|>), optional)+import Control.Monad (replicateM)+import qualified Control.Monad.Exception.Synchronous as Ex+import qualified Penny.Lincoln as L+import qualified Penny.Lincoln.Transaction.Unverified as U+import Data.Maybe (fromMaybe)+import Data.Text (Text, pack)+import qualified Data.Time as Time++lvl1SubAcct :: Parser L.SubAccount+lvl1SubAcct =+ (L.SubAccount . pack) <$> many1 (satisfy T.lvl1AcctChar)++lvl1FirstSubAcct :: Parser L.SubAccount+lvl1FirstSubAcct = lvl1SubAcct++lvl1OtherSubAcct :: Parser L.SubAccount+lvl1OtherSubAcct = satisfy T.colon *> lvl1SubAcct++lvl1Acct :: Parser L.Account+lvl1Acct = f <$> lvl1FirstSubAcct <*> many lvl1OtherSubAcct+ where+ f a as = L.Account (a:as)++quotedLvl1Acct :: Parser L.Account+quotedLvl1Acct =+ satisfy T.openCurly *> lvl1Acct <* satisfy T.closeCurly++lvl2FirstSubAcct :: Parser L.SubAccount+lvl2FirstSubAcct =+ (\c cs -> L.SubAccount (pack (c:cs)))+ <$> satisfy T.letter+ <*> many (satisfy T.lvl2AcctOtherChar)++lvl2OtherSubAcct :: Parser L.SubAccount+lvl2OtherSubAcct =+ (L.SubAccount . pack)+ <$ satisfy T.colon+ <*> many1 (satisfy T.lvl2AcctOtherChar)++lvl2Acct :: Parser L.Account+lvl2Acct =+ (\a as -> L.Account (a:as))+ <$> lvl2FirstSubAcct+ <*> many lvl2OtherSubAcct++ledgerAcct :: Parser L.Account+ledgerAcct = quotedLvl1Acct <|> lvl2Acct++lvl1Cmdty :: Parser L.Commodity+lvl1Cmdty = (L.Commodity . pack) <$> many1 (satisfy T.lvl1CmdtyChar)++quotedLvl1Cmdty :: Parser L.Commodity+quotedLvl1Cmdty =+ satisfy T.doubleQuote *> lvl1Cmdty <* satisfy (T.doubleQuote)++lvl2Cmdty :: Parser L.Commodity+lvl2Cmdty =+ (\c cs -> L.Commodity (pack (c:cs)))+ <$> satisfy T.lvl2CmdtyFirstChar+ <*> many (satisfy T.lvl2CmdtyOtherChar)++lvl3Cmdty :: Parser L.Commodity+lvl3Cmdty = (L.Commodity . pack) <$> many1 (satisfy T.lvl3CmdtyChar)++digitGroup :: Parser [Char]+digitGroup = satisfy T.thinSpace *> many1 (satisfy T.digit)++digitSequence :: Parser [Char]+digitSequence =+ (++) <$> many1 (satisfy T.digit)+ <*> (concat <$> (many digitGroup))++digitPostSequence :: Parser (Maybe [Char])+digitPostSequence = satisfy T.period *> optional digitSequence++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"++spaceBetween :: Parser L.SpaceBetween+spaceBetween = f <$> optional (many1 (satisfy T.white))+ where+ f = maybe L.NoSpaceBetween (const L.SpaceBetween)++leftCmdtyLvl1Amt :: Parser L.Amount+leftCmdtyLvl1Amt =+ f <$> quotedLvl1Cmdty <*> spaceBetween <*> quantity+ where+ f c s q = L.Amount q c (Just L.CommodityOnLeft) (Just s)++leftCmdtyLvl3Amt :: Parser L.Amount+leftCmdtyLvl3Amt = f <$> lvl3Cmdty <*> spaceBetween <*> quantity+ where+ f c s q = L.Amount q c (Just L.CommodityOnLeft) (Just s)++leftSideCmdtyAmt :: Parser L.Amount+leftSideCmdtyAmt = leftCmdtyLvl1Amt <|> leftCmdtyLvl3Amt++rightSideCmdty :: Parser L.Commodity+rightSideCmdty = quotedLvl1Cmdty <|> lvl2Cmdty++rightSideCmdtyAmt :: Parser L.Amount+rightSideCmdtyAmt =+ f <$> quantity <*> spaceBetween <*> rightSideCmdty+ where+ f q s c = L.Amount q c (Just L.CommodityOnRight) (Just s)+++amount :: Parser L.Amount+amount = leftSideCmdtyAmt <|> rightSideCmdtyAmt++comment :: Parser Y.Comment+comment =+ (Y.Comment . pack)+ <$ satisfy T.hash+ <*> many (satisfy T.nonNewline)+ <* satisfy T.newline+ <* many (satisfy T.white)++year :: Parser Integer+year = read <$> replicateM 4 P.digit++month :: Parser Int+month = read <$> replicateM 2 P.digit++day :: Parser Int+day = read <$> replicateM 2 P.digit++date :: Parser Time.Day+date = p >>= failOnErr+ where+ p = Time.fromGregorianValid+ <$> year <* satisfy T.dateSep+ <*> month <* satisfy T.dateSep+ <*> day+ failOnErr = maybe (fail "could not parse date") return++hours :: Parser L.Hours+hours = p >>= (maybe (fail "could not parse hours") return)+ where+ p = f <$> satisfy T.digit <*> satisfy T.digit+ f d1 d2 = L.intToHours . read $ [d1,d2]+++minutes :: Parser L.Minutes+minutes = p >>= maybe (fail "could not parse minutes") return+ where+ p = f <$ satisfy T.colon <*> satisfy T.digit <*> satisfy T.digit+ f d1 d2 = L.intToMinutes . read $ [d1, d2]++seconds :: Parser L.Seconds+seconds = p >>= maybe (fail "could not parse seconds") return+ where+ p = f <$ satisfy T.colon <*> satisfy T.digit <*> satisfy T.digit+ f d1 d2 = L.intToSeconds . read $ [d1, d2]++time :: Parser (L.Hours, L.Minutes, Maybe L.Seconds)+time = (,,) <$> hours <*> minutes <*> optional seconds++tzSign :: Parser (Int -> Int)+tzSign = (id <$ satisfy T.plus) <|> (negate <$ satisfy T.minus)++tzNumber :: Parser Int+tzNumber = read <$> replicateM 4 (satisfy T.digit)++timeZone :: Parser L.TimeZoneOffset+timeZone = p >>= maybe (fail "could not parse time zone") return+ where+ p = f <$> tzSign <*> tzNumber+ f s = L.minsToOffset . s++timeWithZone+ :: Parser (L.Hours, L.Minutes,+ Maybe L.Seconds, Maybe L.TimeZoneOffset)+timeWithZone =+ f <$> time <* many (satisfy T.white) <*> optional timeZone+ where+ f (h, m, s) tz = (h, m, s, tz)++dateTime :: Parser L.DateTime+dateTime =+ f <$> date <* many (satisfy T.white) <*> optional timeWithZone+ where+ f d mayTwithZ = L.DateTime d h m s tz+ where+ ((h, m, s), tz) = case mayTwithZ of+ Nothing -> (L.midnight, L.noOffset)+ Just (hr, mn, mayS, mayTz) ->+ let sec = fromMaybe L.zeroSeconds mayS+ z = fromMaybe L.noOffset mayTz+ in ((hr, mn, sec), z)++debit :: Parser L.DrCr+debit = L.Debit <$ satisfy T.lessThan++credit :: Parser L.DrCr+credit = L.Credit <$ satisfy T.greaterThan++drCr :: Parser L.DrCr+drCr = debit <|> credit++entry :: Parser L.Entry+entry = f <$> drCr <* (many (satisfy T.white)) <*> amount+ where+ f dc am = L.Entry dc am++flag :: Parser L.Flag+flag = (L.Flag . pack) <$ satisfy T.openSquare+ <*> many (satisfy T.flagChar) <* satisfy (T.closeSquare)++postingMemoLine :: Parser Text+postingMemoLine =+ pack+ <$ satisfy T.apostrophe+ <*> many (satisfy T.nonNewline)+ <* satisfy T.newline <* many (satisfy T.white)++postingMemo :: Parser L.Memo+postingMemo = L.Memo <$> many1 postingMemoLine++transactionMemoLine :: Parser Text+transactionMemoLine =+ pack+ <$ satisfy T.semicolon <*> many (satisfy T.nonNewline)+ <* satisfy T.newline <* skipWhite++transactionMemo :: Parser (L.TopMemoLine, L.Memo)+transactionMemo = f <$> lineNum <*> many1 transactionMemoLine+ where+ f tml ls = (L.TopMemoLine tml+ , L.Memo ls)+++number :: Parser L.Number+number =+ L.Number . pack <$ satisfy T.openParen+ <*> many (satisfy T.numberChar) <* satisfy T.closeParen++lvl1Payee :: Parser L.Payee+lvl1Payee = L.Payee . pack <$> many (satisfy T.quotedPayeeChar)++quotedLvl1Payee :: Parser L.Payee+quotedLvl1Payee = satisfy T.tilde *> lvl1Payee <* satisfy T.tilde++lvl2Payee :: Parser L.Payee+lvl2Payee = (\c cs -> L.Payee (pack (c:cs))) <$> satisfy T.letter+ <*> many (satisfy T.nonNewline)++fromCmdty :: Parser L.From+fromCmdty = L.From <$> (quotedLvl1Cmdty <|> lvl2Cmdty)++lineNum :: Parser Int+lineNum = Pos.sourceLine <$> P.getPosition++price :: Parser L.PricePoint+price = p >>= maybe (fail msg) return+ where+ f li dt fr (L.Amount qt to sd sb) =+ let cpu = L.CountPerUnit qt+ in case L.newPrice fr (L.To to) cpu of+ Nothing -> Nothing+ Just pr -> Just $ L.PricePoint dt pr+ sd sb (Just $ L.PriceLine li)+ p = f <$> lineNum <* satisfy T.atSign <* skipWhite+ <*> dateTime <* skipWhite+ <*> fromCmdty <* skipWhite+ <*> amount <* satisfy T.newline <* skipWhite+ msg = "could not parse price, make sure the from and to commodities "+ ++ "are different"++tag :: Parser L.Tag+tag = L.Tag . pack <$ satisfy T.asterisk <*> many (satisfy T.tagChar)+ <* many (satisfy T.white)++tags :: Parser L.Tags+tags = (\t ts -> L.Tags (t:ts)) <$> tag <*> many tag++topLinePayee :: Parser L.Payee+topLinePayee = quotedLvl1Payee <|> lvl2Payee++topLineFlagNum :: Parser (Maybe L.Flag, Maybe L.Number)+topLineFlagNum = p1 <|> p2+ where+ p1 = ( (,) <$> optional flag+ <* many (satisfy T.white) <*> optional number)+ p2 = ( flip (,)+ <$> optional number+ <* many (satisfy T.white) <*> optional flag)++skipWhite :: Parser ()+skipWhite = () <$ many (satisfy T.white)++topLine :: Parser U.TopLine+topLine =+ f <$> optional transactionMemo+ <*> lineNum+ <*> dateTime+ <* skipWhite+ <*> topLineFlagNum+ <* skipWhite+ <*> optional topLinePayee+ <* satisfy T.newline+ <* skipWhite+ where+ f mayMe lin dt (mayFl, mayNum) mayPy =+ U.TopLine dt mayFl mayNum mayPy me tll tml Nothing+ Nothing Nothing+ where+ (tml, me) = case mayMe of+ Nothing -> (Nothing, Nothing)+ Just (l, m) -> (Just l, Just m)+ tll = Just (L.TopLineLine lin)++pairedMaybes+ :: Parser (a, Maybe b)+ -> Parser (Maybe a, b)+ -> Parser (Maybe a, Maybe b)+pairedMaybes p1 p2 =+ (fmap (first Just) p1) <|> (fmap (second Just) p2)++parsePair+ :: Parser a+ -> Parser b+ -> Parser (Maybe a, Maybe b)+parsePair a b = pairedMaybes aFirst bFirst+ where+ aFirst = (,) <$> a <* skipWhite <*> optional b+ bFirst = flip (,) <$> b <* skipWhite <*> optional a++parseTriple+ :: Parser a+ -> Parser b+ -> Parser c+ -> Parser (a, Maybe b, Maybe c)+parseTriple a b c =+ f+ <$> a+ <* skipWhite+ <*> optional (parsePair b c)+ where+ f ra mayRbc = case mayRbc of+ Nothing -> (ra, Nothing, Nothing)+ Just (rb, rc) -> (ra, rb, rc)+++flagFirst :: Parser (L.Flag, Maybe L.Number, Maybe L.Payee)+flagFirst = parseTriple flag number quotedLvl1Payee++numberFirst :: Parser (L.Number, Maybe L.Flag, Maybe L.Payee)+numberFirst = parseTriple number flag quotedLvl1Payee++payeeFirst :: Parser (L.Payee, Maybe L.Flag, Maybe L.Number)+payeeFirst = parseTriple quotedLvl1Payee flag number++flagNumPayee :: Parser (Maybe L.Flag, Maybe L.Number, Maybe L.Payee)+flagNumPayee =+ ((\(f, n, p) -> (Just f, n, p)) <$> flagFirst)+ <|> ((\(n, f, p) -> (f, Just n, p)) <$> numberFirst)+ <|> ((\(p, f, n) -> (f, n, Just p)) <$> payeeFirst)+++postingAcct :: Parser L.Account+postingAcct = quotedLvl1Acct <|> lvl2Acct++posting :: Parser U.Posting+posting = f <$> lineNum <* skipWhite+ <*> optional flagNumPayee <* skipWhite+ <*> postingAcct <* skipWhite+ <*> optional tags <* skipWhite+ <*> optional entry <* skipWhite+ <* satisfy T.newline <* skipWhite+ <*> optional postingMemo <* skipWhite+ where+ f li mayFnp ac ta mayEn me =+ U.Posting pa nu fl ac tgs mayEn me pl Nothing Nothing+ where+ tgs = fromMaybe (L.Tags []) ta+ pl = Just . L.PostingLine $ li+ (fl, nu, pa) = fromMaybe (Nothing, Nothing, Nothing) mayFnp++transaction :: Parser L.Transaction+transaction = p >>= Ex.switch (fail . show) return+ where+ p = L.transaction <$>+ (L.Family <$> topLine <*> posting+ <*> posting <*> many posting)+++blankLine :: Parser Y.Item+blankLine = Y.BlankLine <$ satisfy T.newline <* skipWhite++item :: Parser Y.Item+item = fmap Y.IComment comment <|> fmap Y.PricePoint price+ <|> fmap Y.Transaction transaction <|> blankLine++ledger :: Parser Y.Ledger+ledger = Y.Ledger <$ skipWhite <*> many item <* P.eof
− Penny/Copper/Payees.hs
@@ -1,87 +0,0 @@--- | Payee parsers. There are two types of payee parsers:------ Quoted payees. These allow the most latitude in the characters--- allowed. They are surrounded by @<@ and @>@.------ Unquoted payees. These are not surrounded by @<@ and @>@. Their--- first character must be a letter or number.--module Penny.Copper.Payees (- -- * Parse any payee- payee- - -- * Quoted payees- , quotedChar- , quotedPayee- - -- * Unquoted payees- , unquotedFirstChar- , unquotedRestChars- , unquotedPayee- - -- * Rendering- , smartRender- , quoteRender- ) where--import Control.Applicative ((<$>), (<*>), (<|>))-import Data.Text (pack, Text, snoc, cons)-import qualified Data.Text as X-import Text.Parsec (char, satisfy, many, between, (<?>))-import Text.Parsec.Text ( Parser )--import Penny.Copper.Util (rangeLettersToSymbols, rangeLetters)-import qualified Penny.Lincoln.Bits as B-import Penny.Lincoln.TextNonEmpty as TNE--quotedChar :: Char -> Bool-quotedChar c = allowed && not banned where- allowed = rangeLettersToSymbols c || c == ' '- banned = c == '>'--payee :: Parser B.Payee-payee = quotedPayee <|> unquotedPayee--quotedPayee :: Parser B.Payee-quotedPayee = between (char '<') (char '>') p <?> "quoted payee" where- p = (\c cs -> B.Payee (TextNonEmpty c (pack cs)))- <$> satisfy quotedChar- <*> many (satisfy quotedChar)--unquotedFirstChar :: Char -> Bool-unquotedFirstChar = rangeLetters--unquotedRestChars :: Char -> Bool-unquotedRestChars = quotedChar--unquotedPayee :: Parser B.Payee-unquotedPayee = let- p c cs = B.Payee (TextNonEmpty c (pack cs))- in p- <$> satisfy unquotedFirstChar- <*> many (satisfy unquotedRestChars)- <?> "unquoted payee"---- | Render a payee with a minimum of quoting. Fails if cannot be--- rendered at all.-smartRender :: B.Payee -> Maybe Text-smartRender (B.Payee p) = let- TextNonEmpty f r = p- noQuoteNeeded = unquotedFirstChar f- && X.all unquotedRestChars r- renderable = TNE.all quotedChar p- quoted = '<' `cons` TNE.toText p `snoc` '>'- makeText- | noQuoteNeeded = Just $ TNE.toText p- | renderable = Just quoted- | otherwise = Nothing- in makeText---- | Renders with quotes, whether the payee needs it or not.-quoteRender :: B.Payee -> Maybe Text-quoteRender (B.Payee p) = let- renderable = TNE.all quotedChar p- quoted = '<' `cons` TNE.toText p `snoc` '>'- in if renderable- then Just quoted- else Nothing
− Penny/Copper/Posting.hs
@@ -1,126 +0,0 @@-module Penny.Copper.Posting (- posting, render- ) where--import Control.Applicative ((<$>), (<*>), (<*), (<|>))-import qualified Data.Text as X-import Text.Parsec (- getParserState,- statePos, optionMaybe, sourceLine, (<?>),- State)-import Text.Parsec.Text ( Parser )--import qualified Penny.Lincoln as L-import qualified Penny.Lincoln.Bits as B-import qualified Penny.Copper.Account as Ac-import qualified Penny.Copper.Entry as En-import qualified Penny.Copper.Flag as Fl-import qualified Penny.Copper.Memos.Posting as Me-import qualified Penny.Copper.Number as Nu-import qualified Penny.Copper.Payees as Pa-import qualified Penny.Copper.Qty as Qt-import qualified Penny.Copper.Tags as Ta-import Penny.Copper.Util (lexeme, eol, renMaybe, txtWords)-import qualified Penny.Lincoln.Transaction as T-import qualified Penny.Lincoln.Transaction.Unverified as U--posting :: Qt.RadGroup- -> Parser U.Posting-posting rg =- makeUnverified- <$> (L.PostingLine . sourceLine . statePos- <$> getParserState)- <*> (UnverifiedWithMeta- <$> optionMaybe (lexeme Fl.flag)- <*> optionMaybe (lexeme Nu.number)- <*> optionMaybe (lexeme Pa.quotedPayee)- <*> lexeme (Ac.lvl1AccountQuoted <|> Ac.lvl2Account)- <*> lexeme Ta.tags- <*> optionMaybe (lexeme (En.entry rg))- <* eol- <*> Me.memo)- <?> "posting"--data UnverifiedWithMeta = UnverifiedWithMeta {- flag :: Maybe B.Flag- , number :: Maybe B.Number- , payee :: Maybe B.Payee- , account :: B.Account- , tags :: B.Tags- , entry :: Maybe (B.Entry, L.Format)- , memo :: B.Memo- } deriving (Eq, Show)- -makeUnverified ::- L.PostingLine- -> UnverifiedWithMeta- -> U.Posting-makeUnverified pl u = upo where- upo = U.Posting (payee u) (number u) (flag u) (account u)- (tags u) en (memo u) meta- (en, fmt) = case entry u of- Nothing -> (Nothing, Nothing)- Just (e, f) -> (Just e, Just f)- meta = L.PostingMeta (Just pl) fmt Nothing Nothing----- | Renders a Posting. Fails if any of the components--- fail to render. In addition, if the unverified Posting has an--- Entry, a Format must be provided, otherwise render fails.------ The columns look like this. Column numbers begin with 0 (like they--- do in Emacs) rather than with column 1 (like they do in--- Vim). (Really Emacs is the strange one; most CLI utilities seem to--- start with column 1 too...)------ > ID COLUMN WIDTH WHAT--- > ------------------------------------------------------ > A 0 4 Blank spaces for indentation--- > B 4 50 Flag, Number, Payee, Account, Tags--- > C 54 2 Blank spaces for padding--- > D 56 NA Entry------ Omit the padding after column B if there is no entry; also omit--- columns C and D entirely if there is no Entry. (It is annoying to--- have extraneous blank space in a file).-render ::- (Qt.GroupingSpec, Qt.GroupingSpec)- -> Qt.RadGroup- -> T.Posting- -> Maybe X.Text-render gs rg p = do- fl <- renMaybe (T.pFlag p) Fl.render- nu <- renMaybe (T.pNumber p) Nu.render- pa <- renMaybe (T.pPayee p) Pa.quoteRender- ac <- Ac.render (T.pAccount p)- ta <- Ta.render (T.pTags p)- me <- Me.render (T.pMemo p)- maybePair <- case (T.pInferred p, L.postingFormat . T.pMeta $ p) of- (T.Inferred, Nothing) -> return Nothing- (T.NotInferred, Just f) -> return (Just (T.pEntry p, f))- _ -> Nothing- let renderEn (e, f) = En.render gs rg f e- en <- renMaybe maybePair renderEn- return $ formatter fl nu pa ac ta en me--formatter ::- X.Text -- ^ Flag- -> X.Text -- ^ Number- -> X.Text -- ^ Payee- -> X.Text -- ^ Account- -> X.Text -- ^ Tags- -> X.Text -- ^ Entry- -> X.Text -- ^ Memo- -> X.Text-formatter fl nu pa ac ta en me = let- colA = X.pack (replicate 4 ' ')- colBnoPad = txtWords [fl, nu, pa, ac, ta]- colD = en- colB = if X.null en- then colBnoPad- else X.justifyLeft 50 ' ' colBnoPad- colC = if X.null en- then X.empty- else X.pack (replicate 2 ' ')- rtn = X.singleton '\n'- in X.concat [colA, colB, colC, colD, rtn, me]
− Penny/Copper/Price.hs
@@ -1,97 +0,0 @@-module Penny.Copper.Price (price, render) where--import Text.Parsec ( char, getPosition, sourceLine, (<?>),- SourcePos )-import Text.Parsec.Text ( Parser )--import Control.Applicative ((<*>), (<*), (<|>), (<$))-import Data.Text (singleton, snoc, intercalate)-import qualified Data.Text as X--import qualified Penny.Lincoln as L-import qualified Penny.Lincoln.Bits.PricePoint as PP-import qualified Penny.Copper.Amount as A-import qualified Penny.Copper.Commodity as C-import qualified Penny.Copper.DateTime as DT-import qualified Penny.Copper.Qty as Q-import Penny.Copper.Util (lexeme, eol)--{--BNF-style specification for prices:--<price> ::= "dateTime" <fromCmdty> <toAmount>-<fromCmdty> ::= "quotedLvl1Cmdty" | "lvl2Cmdty"-<toAmount> ::= "amount"--}--mkPrice :: SourcePos- -> L.DateTime- -> L.Commodity- -> (L.Amount, L.Format)- -> Maybe L.PricePoint-mkPrice pos dt from (am, fmt) = let- to = L.commodity am- q = L.qty am- pm = L.PriceMeta (Just pl) (Just fmt)- pl = L.PriceLine . sourceLine $ pos- in do- p <- L.newPrice (L.From from) (L.To to) (L.CountPerUnit q)- return $ L.PricePoint dt p pm--maybePrice ::- DT.DefaultTimeZone- -> Q.RadGroup- -> Parser (Maybe L.PricePoint)-maybePrice dtz rg =- mkPrice- <$ lexeme (char '@')- <*> getPosition- <*> lexeme (DT.dateTime dtz)- <*> lexeme (C.quotedLvl1Cmdty <|> C.lvl2Cmdty)- <*> A.amount rg- <* eol- <?> "price"- --- | A price with an EOL and whitespace after the EOL. @price d r@--- will parse a PriceBox, where @d@ is the DefaultTimeZone for the--- DateTime in the PricePoint, and @r@ is the radix point and grouping--- character to parse the Amount. Fails if the price is not valid--- (e.g. the from and to commodities are the same).-price ::- DT.DefaultTimeZone- -> Q.RadGroup- -> Parser L.PricePoint-price dtz rg = do- b <- maybePrice dtz rg- case b of- Nothing -> fail "invalid price given"- Just p -> return p---- | @render dtz rg f pp@ renders a price point @pp@. @dtz@ is the--- DefaultTimeZone for rendering of the DateTime in the Price--- Point. @rg@ is the radix point and grouping character for the--- amount. @f@ is the Format for how the price will be--- formatted. Fails if either the From or the To commodity cannot be--- rendered.-render ::- DT.DefaultTimeZone- -> (Q.GroupingSpec, Q.GroupingSpec)- -> Q.RadGroup- -> L.PricePoint- -> Maybe X.Text-render dtz gs rg pp = let- dateTxt = DT.render dtz (PP.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 = C.renderLvl3 from <|> C.renderQuotedLvl1 from- amt = L.Amount q to- in do- fmt <- L.priceFormat . L.ppMeta $ pp- let mayAmtTxt = A.render gs rg fmt amt- amtTxt <- mayAmtTxt- fromTxt <- mayFromTxt- return $- (intercalate (singleton ' ')- [singleton '@', dateTxt, fromTxt, amtTxt])- `snoc` '\n'
− Penny/Copper/Qty.hs
@@ -1,283 +0,0 @@-module Penny.Copper.Qty (- -- * Setting the radix and separator characters- RadGroup, periodComma, periodSpace, commaPeriod,- commaSpace,- - -- * Rendering- GroupingSpec(NoGrouping, GroupLarge, GroupAll),- renderUnquoted,- quote,-- -- * Parsing quantities- qtyUnquoted,- qtyQuoted,- qty) where--import Control.Applicative ((<$>), (<*>), (<$), (*>), optional)-import qualified Data.Decimal as D-import Data.List (intercalate)-import Data.List.Split (splitEvery, splitOn)-import qualified Data.Text as X-import Data.Text (snoc, cons)-import Text.Parsec ( char, (<|>), many1, (<?>), - sepBy1, digit, between)-import qualified Text.Parsec as P-import Text.Parsec.Text ( Parser )--import Penny.Lincoln.Bits.Qty ( Qty, partialNewQty, unQty )--data Radix = RComma | RPeriod deriving (Eq, Show)-data Grouper = GComma | GPeriod | GSpace deriving (Eq, Show)--data RadGroup = RadGroup Radix Grouper deriving (Eq, Show)---- | Radix is period, grouping is comma-periodComma :: RadGroup-periodComma = RadGroup RPeriod GComma---- | Radix is period, grouping is space-periodSpace :: RadGroup-periodSpace = RadGroup RPeriod GSpace---- | Radix is comma, grouping is period-commaPeriod :: RadGroup-commaPeriod = RadGroup RComma GPeriod---- | Radix is comma, grouping is space-commaSpace :: RadGroup-commaSpace = RadGroup RComma GSpace--parseRadix :: Radix -> Parser ()-parseRadix r = () <$ char c <?> "radix point" where- c = case r of RComma -> ','; RPeriod -> '.'--parseGrouper :: Grouper -> Parser ()-parseGrouper g = () <$ char c <?> "grouping character" where- c = case g of- GComma -> ','- GPeriod -> '.'- GSpace -> ' '--{- a BNF style specification for numbers.--<radix> ::= (as specified)-<grouper> ::= (as specified)-<digit> ::= "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "0"-<digits> ::= <digit> | <digit> <digits>-<firstGroups> ::= <digits> <grouper> <digits>-<nextGroup> ::= <grouper> <digits>-<nextGroups> ::= <nextGroup> | <nextGroup> <nextGroups>-<allGroups> ::= <firstGroups> | <firstGroups> <nextGroups>-<whole> ::= <allGroups> | <digits>-<fractional> ::= <digits>-<number> ::= <whole>- | <whole> <radix>- | <whole> <radix> <fractional>- | <radix> <fractional>--}--wholeGrouped :: Grouper -> Parser String-wholeGrouped g = p <$> group1 <*> optional groupRest <?> e where - e = "whole number"- group1 = many1 digit- groupRest = parseGrouper g *> sepBy1 (many1 digit) (parseGrouper g)- p g1 gr = case gr of- Nothing -> g1- Just groups -> g1 ++ concat groups--fractionalGrouped :: Grouper -> Parser String-fractionalGrouped g =- p <$> group1 <*> optional groupRest <?> e where- e = "fractional number"- group1 = many1 digit- groupRest = parseGrouper g *> sepBy1 (many1 digit) (parseGrouper g)- p g1 gr = case gr of- Nothing -> g1- Just groups -> g1 ++ concat groups--wholeNonGrouped :: Parser String-wholeNonGrouped = many1 digit--fractionalOnly :: Radix -> Parser String-fractionalOnly r = parseRadix r *> many1 P.digit--numberStrGrouped :: Radix -> Grouper -> Parser NumberStr-numberStrGrouped r g = startsWhole <|> fracOnly <?> e where- e = "quantity, with optional grouping"- startsWhole = p <?> "whole number" where- p = do- wholeStr <- wholeGrouped g- mayRad <- optional (parseRadix r)- case mayRad of- Nothing -> return $ Whole wholeStr- Just _ -> do- mayFrac <- optional $ fractionalGrouped g- case mayFrac of- Nothing -> return $ WholeRad wholeStr- Just frac -> return $ WholeRadFrac wholeStr frac- fracOnly = RadFrac <$> fractionalOnly r--numberStrNonGrouped :: Radix -> Parser NumberStr-numberStrNonGrouped r = startsWhole <|> fracOnly <?> e where- e = "quantity, no grouping"- startsWhole = p <?> "whole number" where- p = do- wholeStr <- wholeNonGrouped- mayRad <- optional (parseRadix r)- case mayRad of- Nothing -> return $ Whole wholeStr- Just _ -> do- mayFrac <- optional $ many1 P.digit- case mayFrac of- Nothing -> return $ WholeRad wholeStr- Just frac -> return $ WholeRadFrac wholeStr frac- fracOnly = RadFrac <$> fractionalOnly r----- | A number string after radix and grouping characters have been--- stripped out.-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---- | Do not use Prelude.read or Prelude.reads on whole decimal strings--- like @232.72@. Sometimes it will fail, though sometimes it will--- succeed; why is not clear to me. Hopefully reading integers won't--- fail! However, in case it does, use read', whose error message will--- at least tell you what number was being read.------ Data.Decimal cannot handle decimals whose exponent would exceed--- 255, which is the maximum that a Word8 can hold. A Word8 is used to--- hold the exponent. If the exponent would exceed 255, this function--- fails.-toDecimal :: NumberStr -> Maybe D.Decimal-toDecimal ns = case ns of- Whole s -> Just $ D.Decimal 0 (readWithErr s)- WholeRad s -> Just $ D.Decimal 0 (readWithErr s)- WholeRadFrac w f -> fromWholeRadFrac w f- RadFrac f -> fromWholeRadFrac "0" f- where- fromWholeRadFrac w f = let- len = length f- in if len > 255- then Nothing- else Just $ D.Decimal (fromIntegral len) (readWithErr (w ++ f))--readWithErr :: String -> Integer-readWithErr s = let- readSresult = reads s- in case readSresult of- (i, ""):[] -> i- _ -> error $ "readWithErr failed. String being read: " ++ s- ++ " Result of reads: " ++ show readSresult- --- | Unquoted quantity. These include no spaces, regardless of what--- the grouping character is.-qtyUnquoted :: RadGroup -> Parser Qty-qtyUnquoted (RadGroup r g) = do- nStr <- case g of- GSpace -> numberStrNonGrouped r- _ -> numberStrGrouped r g- d <- case toDecimal nStr of- Nothing -> fail $ "fractional part too big: " ++ show nStr- Just dec -> return dec- return $ partialNewQty d- --- | Parse quoted quantity. It can include spaces, if the grouping--- character is a space. However these must be quoted when in a Ledger--- file (from the command line they need not be quoted). The quote--- character is a caret, @^@.-qtyQuoted :: RadGroup -> Parser Qty-qtyQuoted (RadGroup r g) = between (char '^') (char '^') p where- p = do- nStr <- numberStrGrouped r g- d <- case toDecimal nStr of- Nothing -> fail $ "fractional part too big: " ++ show nStr- Just dec -> return dec- return $ partialNewQty d---- | Parse a quoted quantity or, if that fails, an unquoted--- quantity.-qty :: RadGroup -> Parser Qty-qty r = qtyQuoted r <|> qtyUnquoted r <?> "quantity"---- | Specifies how to perform digit grouping when rendering a--- quantity. All grouping groups into groups of 3 digits.-data GroupingSpec = - 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)---- | Quotes a rendered Qty, but only if necessary; otherwise, simply--- leaves it unquoted.-quote :: X.Text -> X.Text-quote t = case X.find (== ' ') t of- Nothing -> t- Just _ -> '^' `cons` t `snoc` '^'---- | Renders an unquoted Qty. Performs digit grouping as requested. -renderUnquoted ::- RadGroup- -> (GroupingSpec, GroupingSpec)- -- ^ Group for the portion to the left and right of the radix point?-- -> Qty- -> X.Text-renderUnquoted (RadGroup r g) (gl, gr) q = let- qs = show . unQty $ q- in X.pack $ case splitOn "." qs of- w:[] -> groupWhole g gl w- w:d:[] ->- groupWhole g gl w ++ renderRadix r ++ groupDecimal g gr d- _ -> error "Qty.hs: rendering error"--renderGrouper :: Grouper -> String-renderGrouper g = case g of- GComma -> ","- GPeriod -> "."- GSpace -> " "--renderRadix :: Radix -> String-renderRadix r = case r of- RComma -> ","- RPeriod -> "."---- | Performs grouping for amounts to the left of the radix point.-groupWhole :: Grouper -> GroupingSpec -> String -> String-groupWhole g gs o = let- grouped = intercalate (renderGrouper g)- . reverse- . map reverse- . splitEvery 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 :: Grouper -> GroupingSpec -> String -> String-groupDecimal g gs o = let- grouped = intercalate (renderGrouper g)- . splitEvery 3- $ o- in case gs of- NoGrouping -> o- GroupLarge -> if length o > 4 then grouped else o- GroupAll -> grouped
+ Penny/Copper/Render.hs view
@@ -0,0 +1,523 @@+-- | 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+-- @doc\/ledger-grammar.org@.+module Penny.Copper.Render where++import Control.Monad (guard)+import Control.Applicative ((<$>), (<|>), (<*>))+import Data.List (intersperse, intercalate)+import Data.List.Split (chunksOf, splitOn)+import qualified Data.Text as X+import Data.Text (Text, cons, snoc)+import qualified Penny.Copper.Terminals as T+import qualified Penny.Lincoln.Transaction as LT+import qualified Data.Time as Time+import qualified Penny.Copper.Types as Y+import qualified Penny.Lincoln as L++-- * Helpers++-- | Merges a list of words into one Text; however, if any given Text+-- is empty, that Text is first dropped from the list.+txtWords :: [X.Text] -> X.Text+txtWords xs = case filter (not . X.null) xs of+ [] -> X.empty+ rs -> X.unwords rs++-- | Takes a field that may or may not be present and a function that+-- renders it. If the field is not present at all, returns an empty+-- Text. Otherwise will succeed or fail depending upon whether the+-- rendering function succeeds or fails.+renMaybe :: Maybe a -> (a -> Maybe X.Text) -> Maybe X.Text+renMaybe mx f = case mx of+ Nothing -> Just X.empty+ Just a -> f a+++-- * Accounts++-- | Is True if a sub account can be rendered at Level 1;+-- False otherwise.+isSubAcctLvl1 :: L.SubAccount -> Bool+isSubAcctLvl1 (L.SubAccount x) =+ X.all T.lvl1AcctChar x && not (X.null x)++isAcctLvl1 :: L.Account -> Bool+isAcctLvl1 (L.Account ls) =+ (not . null $ ls)+ && (all isSubAcctLvl1 ls)++quotedLvl1Acct :: L.Account -> Maybe Text+quotedLvl1Acct a@(L.Account ls) = do+ guard (isAcctLvl1 a)+ let txt = X.concat . intersperse (X.singleton ':')+ . map L.unSubAccount $ ls+ return $ '{' `X.cons` txt `X.snoc` '}'++isFirstSubAcctLvl2 :: L.SubAccount -> Bool+isFirstSubAcctLvl2 (L.SubAccount x) = case X.uncons x of+ Nothing -> False+ Just (c, r) -> T.letter c && (X.all T.lvl2AcctOtherChar r)++isOtherSubAcctLvl2 :: L.SubAccount -> Bool+isOtherSubAcctLvl2 (L.SubAccount x) =+ (not . X.null $ x)+ && (X.all T.lvl2AcctOtherChar x)++isAcctLvl2 :: L.Account -> Bool+isAcctLvl2 (L.Account ls) = case ls of+ [] -> False+ x:xs -> isFirstSubAcctLvl2 x && all isOtherSubAcctLvl2 xs++lvl2Acct :: L.Account -> Maybe Text+lvl2Acct a@(L.Account ls) = do+ guard $ isAcctLvl2 a+ return . X.concat . intersperse (X.singleton ':')+ . map L.unSubAccount $ ls++-- | Shows an account, with the minimum level of quoting+-- possible. Fails with an error if any one of the characters in the+-- account name does not satisfy the 'lvl1Char' predicate. Otherwise+-- returns a rendered account, quoted if necessary.+ledgerAcct :: L.Account -> Maybe Text+ledgerAcct a = lvl2Acct a <|> quotedLvl1Acct a++-- * Commodities++-- | Render a quoted Level 1 commodity. Fails if any character does+-- not satisfy lvl1Char.+quotedLvl1Cmdty :: L.Commodity -> Maybe Text+quotedLvl1Cmdty (L.Commodity c) =+ if X.all T.lvl1CmdtyChar c+ then Just $ '"' `cons` c `snoc` '"'+ else Nothing+++-- | Render a Level 2 commodity. Fails if the first character is not a+-- letter or a symbol, or if any other character is a space.+lvl2Cmdty :: L.Commodity -> Maybe Text+lvl2Cmdty (L.Commodity c) = do+ (f, rs) <- X.uncons c+ guard $ T.lvl2CmdtyFirstChar f+ guard . X.all T.lvl2CmdtyOtherChar $ rs+ return c+++-- | Render a Level 3 commodity. Fails if any character is not a+-- letter or a symbol.+lvl3Cmdty :: L.Commodity -> Maybe Text+lvl3Cmdty (L.Commodity c) =+ if (not . X.null $ c) && (X.all T.lvl3CmdtyChar c)+ then return c+ else Nothing+++-- * 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)+++data GroupSpecs = GroupSpecs+ { left :: GroupSpec+ , right :: GroupSpec+ } deriving Show+++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 = show 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+ -> L.Amount+ -> Maybe X.Text+amount gs (L.Amount qt c maySd maySb) =+ 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]++-- * Comments++comment :: Y.Comment -> Maybe X.Text+comment (Y.Comment x) =+ if (not . X.all T.nonNewline $ x)+ then Nothing+ else Just $ '#' `cons` x `snoc` '\n'++-- * DateTime++-- | Render a DateTime. The day is always printed. If the time zone+-- offset is not zero, then the time and time zone offset are both+-- printed. If the time zone offset is zero, then the hours and+-- minutes are printed, but only if the time is not midnight. If the+-- seconds are not zero, they are also printed.++dateTime :: L.DateTime -> X.Text+dateTime (L.DateTime d h m s z) = X.append xd xr+ where+ (iYr, iMo, iDy) = Time.toGregorian d+ xr = hoursMinsSecsZone h m s z+ dash = X.singleton '-'+ xd = X.concat [ showX iYr, dash, pad2 . showX $ iMo, dash,+ pad2 . showX $ iDy ]++pad2 :: X.Text -> X.Text+pad2 = X.justifyRight 2 '0'++pad4 :: X.Text -> X.Text+pad4 = X.justifyRight 4 '0'++showX :: Show a => a -> X.Text+showX = X.pack . show++hoursMinsSecsZone+ :: L.Hours -> L.Minutes -> L.Seconds -> L.TimeZoneOffset -> X.Text+hoursMinsSecsZone h m s z =+ if z == L.noOffset && (h, m, s) == L.midnight+ then X.empty+ else let xhms = X.concat [xh, colon, xm, xs]+ xh = pad2 . showX . L.unHours $ h+ xm = pad2 . showX . L.unMinutes $ m+ xs = let secs = L.unSeconds s+ in if secs == 0+ then X.empty+ else ':' `X.cons` (pad2 . showX $ secs)+ off = L.offsetToMins z+ sign = X.singleton $ if off < 0 then '-' else '+'+ padded = pad4 . showX . abs $ off+ xz = if off == 0+ then X.empty+ else ' ' `X.cons` sign `X.append` padded+ colon = X.singleton ':'+ in ' ' `X.cons` xhms `X.append` xz++-- * Entries++entry+ :: GroupSpecs+ -> L.Entry+ -> Maybe X.Text+entry gs (L.Entry dc a) = do+ amt <- amount gs a+ let dcTxt = X.pack $ case dc of+ L.Debit -> "<"+ L.Credit -> ">"+ return $ X.append (X.snoc dcTxt ' ') amt++-- * Flags++flag :: L.Flag -> Maybe X.Text+flag (L.Flag fl) =+ if X.all T.flagChar fl+ then Just $ '[' `cons` fl `snoc` ']'+ else Nothing++-- * Memos++-- | Renders a postingMemoLine, optionally with trailing+-- whitespace. The trailing whitespace allows the next line to be+-- indented properly if is also a postingMemoLine. This is handled+-- using trailing whitespace rather than leading whitespace because+-- leading whitespace is inconsistent with the grammar.+postingMemoLine+ :: Int+ -- ^ Pad the end of the output with this many spaces+ -> X.Text+ -> Maybe X.Text+postingMemoLine p x =+ if X.all T.nonNewline x+ then let trailing = X.replicate p (X.singleton ' ')+ ls = [X.singleton '\'', x, X.singleton '\n', trailing]+ in Just $ X.concat ls+ else Nothing++-- | Renders a postingMemo. Fails if the postingMemo is empty, as the+-- grammar requires that they have at least one line.+--+-- If the boolean is True, inserts padding after the last+-- postingMemoLine so that the next line is indented by four+-- columns. Use this if the posting memo is followed by another+-- posting. If the last boolean if False, there is no indenting after+-- the last postingMemoLine.+postingMemo :: Bool -> L.Memo -> Maybe X.Text+postingMemo iLast (L.Memo ls) =+ if null ls+ then Nothing+ else let bs = replicate (length ls - 1) 8 ++ [if iLast then 4 else 0]+ in fmap X.concat . sequence $ zipWith postingMemoLine bs ls+++transactionMemoLine :: X.Text -> Maybe X.Text+transactionMemoLine x =+ if X.all T.nonNewline x+ then Just $ ';' `cons` x `snoc` '\n'+ else Nothing++transactionMemo :: L.Memo -> Maybe X.Text+transactionMemo (L.Memo ls) =+ if null ls+ then Nothing+ else fmap X.concat . mapM transactionMemoLine $ ls++-- * Numbers++number :: L.Number -> Maybe Text+number (L.Number t) =+ if X.all T.numberChar t+ then Just $ '(' `cons` t `snoc` ')'+ else Nothing++-- * Payees++quotedLvl1Payee :: L.Payee -> Maybe Text+quotedLvl1Payee (L.Payee p) = do+ guard (X.all T.quotedPayeeChar p)+ return $ '~' `X.cons` p `X.snoc` '~'++lvl2Payee :: L.Payee -> Maybe Text+lvl2Payee (L.Payee p) = do+ (c1, cs) <- X.uncons p+ guard (T.letter c1)+ guard (X.all T.nonNewline cs)+ return p++payee :: L.Payee -> Maybe Text+payee p = lvl2Payee p <|> quotedLvl1Payee p++-- * Prices++price ::+ GroupSpecs+ -> L.PricePoint+ -> Maybe X.Text+price gs 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.Amount q to (L.ppSide pp) (L.ppSpaceBetween pp))+ fromTxt <- mayFromTxt+ return $+ (X.intercalate (X.singleton ' ')+ [X.singleton '@', dateTxt, fromTxt, amtTxt])+ `snoc` '\n'++-- * Tags++tag :: L.Tag -> Maybe X.Text+tag (L.Tag t) =+ if X.all T.tagChar t+ then Just $ X.cons '*' t+ else Nothing++tags :: L.Tags -> Maybe X.Text+tags (L.Tags ts) =+ X.intercalate (X.singleton ' ')+ <$> mapM tag ts++-- * TopLine++-- | Renders the TopLine. Emits trailing whitespace after the newline+-- so that the first posting is properly indented.+topLine :: LT.TopLine -> Maybe X.Text+topLine tl =+ f+ <$> renMaybe (LT.tMemo tl) transactionMemo+ <*> renMaybe (LT.tFlag tl) flag+ <*> renMaybe (LT.tNumber tl) number+ <*> renMaybe (LT.tPayee tl) payee+ where+ f meX flX nuX paX =+ X.concat [ meX, txtWords [dtX, flX, nuX, paX],+ X.singleton '\n',+ X.replicate 4 (X.singleton ' ') ]+ dtX = dateTime (LT.tDateTime tl)++-- * Posting++-- | Renders a Posting. Fails if any of the components+-- fail to render. In addition, if the unverified Posting has an+-- Entry, a Format must be provided, otherwise render fails.+--+-- The columns look like this. Column numbers begin with 0 (like they+-- do in Emacs) rather than with column 1 (like they do in+-- Vim). (Really Emacs is the strange one; most CLI utilities seem to+-- start with column 1 too...)+--+-- > ID COLUMN WIDTH WHAT+-- > ---------------------------------------------------+-- > A 0 4 Blank spaces for indentation+-- > B 4 50 Flag, Number, Payee, Account, Tags+-- > C 54 2 Blank spaces for padding+-- > D 56 NA Entry+--+-- Omit the padding after column B if there is no entry; also omit+-- columns C and D entirely if there is no Entry. (It is annoying to+-- have extraneous blank space in a file).+--+-- This table is a bit of a lie, because the blank spaces for+-- indentation are emitted either by the posting previous to this one+-- (either after the posting itself or after its postingMemo) or by+-- the TopLine.+--+-- Also emits an additional eight spaces after the trailing newline if+-- the posting has a memo. That way the memo will be indented+-- properly. (There are trailing spaces here, as opposed to leading+-- spaces in the posting memo, because the latter would be+-- inconsistent with the grammar.)+--+-- Emits an extra four spaces after the first line if the first+-- paramter is True. However, this is overriden if there is a memo, in+-- which case eight spaces will be emitted. (This allows the next+-- posting to be indented properly.)+posting ::+ GroupSpecs+ -> Bool+ -- ^ If True, emit four spaces after the trailing newline.+ -> L.Posting+ -> Maybe X.Text+posting gs pad p = do+ fl <- renMaybe (LT.pFlag p) flag+ nu <- renMaybe (LT.pNumber p) number+ pa <- renMaybe (LT.pPayee p) quotedLvl1Payee+ ac <- ledgerAcct (LT.pAccount p)+ ta <- tags (LT.pTags p)+ me <- renMaybe (LT.pMemo p) (postingMemo pad)+ mayEn <- case LT.pInferred p of+ LT.Inferred -> return Nothing+ LT.NotInferred -> return (Just . L.pEntry $ p)+ en <- renMaybe mayEn (entry gs)+ return $ formatter pad fl nu pa ac ta en me++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+ -> X.Text -- ^ Payee+ -> X.Text -- ^ Account+ -> X.Text -- ^ Tags+ -> X.Text -- ^ Entry+ -> X.Text -- ^ Memo+ -> X.Text+formatter pad fl nu pa ac ta en me = let+ colBnoPad = txtWords [fl, nu, pa, ac, ta]+ colD = en+ colB = if X.null en+ then colBnoPad+ else X.justifyLeft 50 ' ' colBnoPad+ colC = if X.null en+ then X.empty+ else X.pack (replicate 2 ' ')+ rtn = '\n' `X.cons` trailingWhite+ trailingWhite = case (X.null me, pad) of+ (True, False) -> X.empty+ (True, True) -> X.replicate 4 (X.singleton ' ')+ (False, _) -> X.replicate 8 (X.singleton ' ')+ in X.concat [colB, colC, colD, rtn, me]+++-- * Transaction++transaction ::+ GroupSpecs+ -> L.Transaction+ -> Maybe X.Text+transaction gs txn = do+ let (L.Family tl p1 p2 ps) = LT.unTransaction txn+ tlX <- topLine tl+ p1X <- posting gs True p1+ p2X <- posting gs (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+ return $ X.concat [tlX, p1X, p2X, psX]++-- * Item++item :: GroupSpecs -> Y.Item -> Maybe X.Text+item gs i = case i of+ Y.BlankLine -> Just . X.singleton $ '\n'+ Y.IComment x -> comment x+ Y.PricePoint pp -> price gs pp+ Y.Transaction t -> transaction gs t++-- * Ledger++ledger :: GroupSpecs -> Y.Ledger -> Maybe X.Text+ledger gs (Y.Ledger is) = fmap X.concat . mapM (item gs) $ is
− Penny/Copper/Tags.hs
@@ -1,37 +0,0 @@-module Penny.Copper.Tags (- isTagChar, tags, render- )where--import Control.Applicative ((<$>), (*>), (<*>))-import qualified Data.Text as X-import Text.Parsec (char, satisfy, many, (<?>))-import Text.Parsec.Text ( Parser )--import Penny.Copper.Util (lexeme, rangeLettersNumbers)-import qualified Penny.Lincoln.Bits as B-import qualified Penny.Lincoln.TextNonEmpty as TNE--isTagChar :: Char -> Bool-isTagChar = rangeLettersNumbers--tagChar :: Parser Char-tagChar = satisfy isTagChar--tag :: Parser B.Tag-tag = (char '*' *> (f <$> tagChar <*> many tagChar)) <?> e where- f t ts = B.Tag $ TNE.TextNonEmpty t (X.pack ts)- e = "tag"--tags :: Parser B.Tags-tags = B.Tags <$> many (lexeme tag)--renderTag :: B.Tag -> Maybe X.Text-renderTag (B.Tag t) =- if TNE.all isTagChar t- then Just $ X.cons '*' (TNE.toText t)- else Nothing--render :: B.Tags -> Maybe X.Text-render (B.Tags ts) =- X.intercalate (X.singleton ' ')- <$> mapM renderTag ts
+ Penny/Copper/Terminals.hs view
@@ -0,0 +1,145 @@+module Penny.Copper.Terminals where++invalid :: Char -> Bool+invalid c = c >= '\xD800' && c <= '\xDFFF'++unicode :: Char -> Bool+unicode = not . invalid++newline :: Char -> Bool+newline = (== '\x0A')++space :: Char -> Bool+space = (== '\x20')++tab :: Char -> Bool+tab = (== '\x09')++white :: Char -> Bool+white c = space c || tab c++nonNewline :: Char -> Bool+nonNewline c = unicode c && (not . newline $ c)++nonNewlineNonSpace :: Char -> Bool+nonNewlineNonSpace c = nonNewline c && (not . white $ c)++upperCaseAscii :: Char -> Bool+upperCaseAscii c = c >= 'A' && c <= 'Z'++lowerCaseAscii :: Char -> Bool+lowerCaseAscii c = c >= 'a' && c <= 'z'++digit :: Char -> Bool+digit c = c >= '0' && c <= '9'++nonAscii :: Char -> Bool+nonAscii c = nonNewline c && c > '\x7F'++letter :: Char -> Bool+letter c = upperCaseAscii c || lowerCaseAscii c || nonAscii c++dollar :: Char -> Bool+dollar = (== '$')++colon :: Char -> Bool+colon = (== ':')++openCurly :: Char -> Bool+openCurly = (== '{')++closeCurly :: Char -> Bool+closeCurly = (== '}')++openSquare :: Char -> Bool+openSquare = (== '[')++closeSquare :: Char -> Bool+closeSquare = (== ']')++doubleQuote :: Char -> Bool+doubleQuote = (== '"')++period :: Char -> Bool+period = (== '.')++hash :: Char -> Bool+hash = (== '#')++thinSpace :: Char -> Bool+thinSpace = (== '\x2009')++dateSep :: Char -> Bool+dateSep c = c == '/' || c == '-'++plus :: Char -> Bool+plus = (== '+')++minus :: Char -> Bool+minus = (== '-')++lessThan :: Char -> Bool+lessThan = (== '<')++greaterThan :: Char -> Bool+greaterThan = (== '>')++openParen :: Char -> Bool+openParen = (== '(')++closeParen :: Char -> Bool+closeParen = (== ')')++semicolon :: Char -> Bool+semicolon = (== ';')++apostrophe :: Char -> Bool+apostrophe = (== '\x27')++tilde :: Char -> Bool+tilde = (== '~')++underscore :: Char -> Bool+underscore = (== '_')++asterisk :: Char -> Bool+asterisk = (== '*')++lvl1AcctChar :: Char -> Bool+lvl1AcctChar c = nonNewline c && (not . closeCurly $ c)+ && (not . colon $ c)++lvl2AcctOtherChar :: Char -> Bool+lvl2AcctOtherChar c =+ nonNewline c && (not . white $ c) && (not . colon $ c)+ && (not . asterisk $ c) && (not . greaterThan $ c)+ && (not . lessThan $ c)++lvl1CmdtyChar :: Char -> Bool+lvl1CmdtyChar c =+ nonNewline c && (not . doubleQuote $ c)++lvl2CmdtyFirstChar :: Char -> Bool+lvl2CmdtyFirstChar c = letter c || dollar c++lvl2CmdtyOtherChar :: Char -> Bool+lvl2CmdtyOtherChar c = nonNewline c && (not . white $ c)++lvl3CmdtyChar :: Char -> Bool+lvl3CmdtyChar c = letter c || dollar c++flagChar :: Char -> Bool+flagChar c = nonNewline c && (not . closeSquare $ c)++numberChar :: Char -> Bool+numberChar c = nonNewline c && (not . closeParen $ c)++quotedPayeeChar :: Char -> Bool+quotedPayeeChar c = nonNewline c && (not . tilde $ c)++tagChar :: Char -> Bool+tagChar c = nonNewlineNonSpace c && (not . asterisk $ c)+ && (not . greaterThan $ c) && (not . lessThan $ c)++atSign :: Char -> Bool+atSign = (== '@')
− Penny/Copper/TopLine.hs
@@ -1,59 +0,0 @@-module Penny.Copper.TopLine (- topLine- , render- ) where--import Control.Applicative ((<$>), (<*>), (<*), (<|>), pure)-import qualified Data.Text as X-import Text.Parsec ( optionMaybe, getPosition,- sourceLine, optionMaybe )-import Text.Parsec.Text ( Parser )--import qualified Penny.Copper.DateTime as DT-import qualified Penny.Copper.Memos.Transaction as M-import qualified Penny.Copper.Flag as F-import qualified Penny.Copper.Number as N-import qualified Penny.Copper.Payees as P-import qualified Penny.Lincoln as L-import qualified Penny.Lincoln.Transaction as T-import Penny.Copper.Util (lexeme, eol, renMaybe, txtWords)-import qualified Penny.Lincoln.Transaction.Unverified as U--topLine ::- DT.DefaultTimeZone- -- -> Parser (U.TopLine (Meta.TopMemoLine, Meta.TopLineLine))- -> Parser U.TopLine-topLine dtz =- f- <$> M.memo- <*> getPosition- <*> lexeme (DT.dateTime dtz)- <*> optionMaybe (lexeme F.flag)- <*> optionMaybe (lexeme N.number)- <*> optionMaybe (P.quotedPayee <|> P.unquotedPayee)- <* eol- where- f (me, tml) pos dt fl nu pa = tl where- tl = U.TopLine dt fl nu pa me meta- meta = L.emptyTopLineMeta { L.topMemoLine = Just tml- , L.topLineLine = Just tll }- tll = L.TopLineLine . sourceLine $ pos----render ::- DT.DefaultTimeZone- -> T.TopLine- -> Maybe X.Text-render dtz tl =- f- <$> M.render (T.tMemo tl)- <*> pure (DT.render dtz (T.tDateTime tl))- <*> renMaybe (T.tFlag tl) F.render- <*> renMaybe (T.tNumber tl) N.render- <*> renMaybe (T.tPayee tl) P.smartRender- where- f meX dtX flX nuX paX =- meX- `X.append` (txtWords [dtX, flX, nuX, paX])- `X.snoc` '\n'
− Penny/Copper/Transaction.hs
@@ -1,75 +0,0 @@-module Penny.Copper.Transaction (transaction, render) where--import Control.Applicative ((<$>), (<*>))-import qualified Control.Monad.Exception.Synchronous as Ex-import Data.Foldable (toList)-import qualified Data.Traversable as Tr-import qualified Data.Text as X-import Text.Parsec (many)-import Text.Parsec.Text ( Parser )--import qualified Penny.Copper.DateTime as DT-import qualified Penny.Copper.TopLine as TL-import Penny.Copper.TopLine ( topLine )-import qualified Penny.Copper.Posting as Po-import qualified Penny.Copper.Qty as Qt-import qualified Penny.Lincoln as L-import Penny.Lincoln.Family (orphans)-import qualified Penny.Lincoln.Family.Family as F-import Penny.Lincoln.Family.Family ( Family ( Family ) )-import qualified Penny.Lincoln.Transaction as T-import qualified Penny.Lincoln.Transaction.Unverified as U--errorStr :: T.Error -> String-errorStr e = case e of- T.UnbalancedError -> "postings are not balanced"- T.CouldNotInferError -> "could not infer entry for posting"--mkTransaction ::- U.TopLine- -> U.Posting- -> U.Posting- -> [U.Posting]- -> Ex.Exceptional String L.Transaction-mkTransaction top p1 p2 ps = let- famTrans = Family top p1 p2 ps- errXact = T.transaction famTrans- in case errXact of- Ex.Exception err -> Ex.Exception . errorStr $ err- Ex.Success x -> return x--maybeTransaction ::- DT.DefaultTimeZone- -> Qt.RadGroup- -> Parser (Ex.Exceptional String L.Transaction)-maybeTransaction dtz rg =- mkTransaction- <$> topLine dtz- <*> Po.posting rg- <*> Po.posting rg- <*> many (Po.posting rg)--transaction ::- DT.DefaultTimeZone- -> Qt.RadGroup- -> Parser L.Transaction-transaction dtz rg = do- ex <- maybeTransaction dtz rg- case ex of- Ex.Exception s -> fail s- Ex.Success b -> return b--render ::- DT.DefaultTimeZone- -> (Qt.GroupingSpec, Qt.GroupingSpec)- -> Qt.RadGroup- -> T.Transaction- -> Maybe X.Text-render dtz gs rg txn = do- let txnFam = T.unTransaction txn- tlX <- TL.render dtz (F.parent txnFam)- pstgsX <- Tr.traverse (Po.render gs rg) (orphans txnFam)- return $ tlX `X.append` (X.concat (toList pstgsX))- --
+ Penny/Copper/Types.hs view
@@ -0,0 +1,59 @@+module Penny.Copper.Types where++import Control.Applicative (Applicative (pure))+import Data.Functor ((<$>))+import qualified Data.Text as X+import qualified Data.Traversable as T+import qualified Penny.Lincoln as L+import qualified Data.Monoid as M++newtype Comment = Comment { unComment :: X.Text }+ deriving (Eq, Show)++data Item = BlankLine+ | IComment Comment+ | PricePoint L.PricePoint+ | Transaction L.Transaction+ deriving Show++mapItem+ :: (Comment -> Comment)+ -> (L.PricePoint -> L.PricePoint)+ -> (L.Transaction -> L.Transaction)+ -> Item+ -> Item+mapItem fc fp ft i = case i of+ BlankLine -> BlankLine+ IComment c -> IComment $ fc c+ PricePoint p -> PricePoint $ fp p+ Transaction t -> Transaction $ ft t++mapItemA+ :: Applicative a+ => (Comment -> a Comment)+ -> (L.PricePoint -> a L.PricePoint)+ -> (L.Transaction -> a L.Transaction)+ -> Item+ -> a Item+mapItemA fc fp ft i = case i of+ BlankLine -> pure BlankLine+ IComment c -> IComment <$> fc c+ PricePoint p -> PricePoint <$> fp p+ Transaction t -> Transaction <$> ft t++newtype Ledger = Ledger { unLedger :: [Item] }+ deriving Show++mapLedger :: (Item -> Item) -> Ledger -> Ledger+mapLedger f (Ledger is) = Ledger $ map f is++mapLedgerA+ :: Applicative a+ => (Item -> a Item)+ -> Ledger+ -> a Ledger+mapLedgerA f (Ledger is) = Ledger <$> T.traverse f is++instance M.Monoid Ledger where+ mempty = Ledger []+ mappend (Ledger x) (Ledger y) = Ledger (x ++ y)
− Penny/Copper/Util.hs
@@ -1,150 +0,0 @@-module Penny.Copper.Util where--import Control.Applicative ((<*), pure, (<$))-import qualified Data.Char as C-import qualified Data.Foldable as F-import qualified Data.List.NonEmpty as NE-import qualified Data.Text as X-import qualified Penny.Lincoln.HasText as HT-import qualified Penny.Lincoln.TextNonEmpty as TNE-import Text.Parsec (char, many, skipMany)-import Text.Parsec.Text (Parser)--rangeLettersToSymbols :: Char -> Bool-rangeLettersToSymbols c = case C.generalCategory c of- C.UppercaseLetter -> True- C.LowercaseLetter -> True- C.TitlecaseLetter -> True- C.ModifierLetter -> True- C.OtherLetter -> True- C.DecimalNumber -> True- C.LetterNumber -> True- C.OtherNumber -> True- C.ConnectorPunctuation -> True- C.DashPunctuation -> True- C.OpenPunctuation -> True- C.ClosePunctuation -> True- C.InitialQuote -> True- C.FinalQuote -> True- C.OtherPunctuation -> True- C.MathSymbol -> True- C.CurrencySymbol -> True- C.ModifierSymbol -> True- C.OtherSymbol -> True- _ -> False--rangeLetters :: Char -> Bool-rangeLetters c = case C.generalCategory c of- C.UppercaseLetter -> True- C.LowercaseLetter -> True- C.TitlecaseLetter -> True- C.ModifierLetter -> True- C.OtherLetter -> True- _ -> False--rangeMathCurrency :: Char -> Bool-rangeMathCurrency c = case C.generalCategory c of- C.MathSymbol -> True- C.CurrencySymbol -> True- _ -> False--rangeSymbols :: Char -> Bool-rangeSymbols c = case C.generalCategory c of- C.MathSymbol -> True- C.CurrencySymbol -> True- C.ModifierSymbol -> True- C.OtherSymbol -> True- _ -> False--rangeLettersNumbers :: Char -> Bool-rangeLettersNumbers c = case C.generalCategory c of- C.UppercaseLetter -> True- C.LowercaseLetter -> True- C.TitlecaseLetter -> True- C.ModifierLetter -> True- C.OtherLetter -> True- C.DecimalNumber -> True- C.LetterNumber -> True- C.OtherNumber -> True- _ -> False---- | Creates a new parser that behaves like the old one, but also--- parses any whitespace remaining afterward.-lexeme :: Parser a -> Parser a-lexeme p = p <* skipMany (char ' ')---- | Parses any trailing whitespace followed by a newline followed by--- additional whitespace.-eol :: Parser ()-eol = pure ()- <* skipMany (char ' ')- <* char '\n'- <* skipMany (char ' ')---- | Parses a run of spaces.-spaces :: Parser ()-spaces = () <$ many (char ' ')---- | Applied to a non-empty list of pairs, with the first element of--- the pair being a predicate that returns True if a character is OK--- and the second element being something of an arbitrary type, and to--- something that has a Text. The pairs must be ordered from most--- restrictive to least restrictive predicates. If at least one of the--- predicates indicates that the Text is valid, returns the leftmost b--- associated with that predicate. If none of the predicates indicates--- that the Text is valid, returns the rightmost error.------ Here, most restrictive means the predicate that indicates True for--- the narrowest range of characters, while least restrictive means--- the predicate that indicates True for the widest range of--- characters.-checkText ::- HT.HasText a- => NE.NonEmpty ((Char -> Bool), b)- -> a- -> Maybe b-checkText ps a = let- t = HT.text a- results = fmap (g . f) ps where- f (p, b) = (X.find (not . p) t, b)- g (p, b) = case p of- Nothing -> Right b- Just c -> Left c- folder x y = case x of- Right b -> Right b- Left _ -> y- in case F.foldr1 folder results of- Left _ -> Nothing- Right b -> return b--listIsOK ::- HT.HasTextNonEmptyList a- => (Char -> Bool) -- ^ Returns True for characters that are allowed- -> a- -> Bool-listIsOK p = F.all (TNE.all p) . HT.textNonEmptyList--firstCharOfListIsOK ::- HT.HasTextNonEmptyList a- => (Char -> Bool) -- ^ Returns True if the first character is allowed- -> a- -> Bool-firstCharOfListIsOK p ls = let- firstText = NE.head . HT.textNonEmptyList $ ls- in p (TNE.first firstText)---- | Takes a field that may or may not be present and a function that--- renders it. If the field is not present at all, returns an empty--- Text. Otherwise will succeed or fail depending upon whether the--- rendering function succeeds or fails.-renMaybe :: Maybe a -> (a -> Maybe X.Text) -> Maybe X.Text-renMaybe mx f = case mx of- Nothing -> Just X.empty- Just a -> f a---- | Merges a list of words into one Text; however, if any given Text--- is empty, that Text is first dropped from the list.-txtWords :: [X.Text] -> X.Text-txtWords xs = case filter (not . X.null) xs of- [] -> X.empty- rs -> X.unwords rs
Penny/Liberty.hs view
@@ -8,10 +8,9 @@ -- Cabin will also find useful are relocated here, to Liberty. module Penny.Liberty (- Error(..), MatcherFactory, X.evaluate,- X.Token,+ X.Token(..), FilteredNum(FilteredNum, unFilteredNum), SortedNum(SortedNum, unSortedNum), LibertyMeta(filteredNum, sortedNum),@@ -23,37 +22,33 @@ processPostFilters, parseTokenList, parsePredicate,- + parseInt,+ -- * Parsers Operand,- parseOperand,- parsePostFilter,- parseMatcherSelect,- parseCaseSelect,- parseOperator,- Orderer,- parseSort+ operandSpecs,+ postFilterSpecs,+ matcherSelectSpecs,+ caseSelectSpecs,+ operatorSpecs+ ) where -import Control.Applicative ((<|>))+import Control.Applicative ((<*>), (<$>)) import qualified Control.Monad.Exception.Synchronous as Ex-import Control.Monad.Exception.Synchronous (- Exceptional(Exception)) import Data.Char (toUpper)-import Data.List (isPrefixOf, sortBy)-import Data.Text (Text, pack)+import Data.List (sortBy)+import Data.Text (Text, pack, unpack) import qualified Data.Text as Text import qualified System.Console.MultiArg.Combinator as C-import System.Console.MultiArg.Prim (Parser)+import System.Console.MultiArg.Combinator (OptSpec) import Text.Parsec (parse)- -import Penny.Copper.DateTime (DefaultTimeZone, dateTime)-import Penny.Copper.Qty (RadGroup, qty) +import qualified Penny.Copper.Parsec as Pc+ import Penny.Lincoln.Family.Child (child, parent) import qualified Penny.Lincoln.Predicates as P import qualified Penny.Lincoln as L-import qualified Penny.Lincoln.Queries as Q import qualified Penny.Liberty.Expressions as X import Text.Matchers.CaseSensitive (@@ -99,7 +94,7 @@ -- instances, filters them, post-filters them, sorts them, and places -- them in Box instances with Filtered serials. xactionsToFiltered ::- + (L.PostFam -> Bool) -- ^ The predicate to filter the transactions @@ -111,7 +106,7 @@ -> [L.Transaction] -- ^ The transactions to work on (probably parsed in from Copper)- + -> [L.Box LibertyMeta] -- ^ Sorted, filtered postings @@ -175,24 +170,8 @@ len = ListLength $ length as fn' (_, idx) = fn len (ItemIndex idx) zipped = zip as [0..]- -data Error = MakeMatcherFactoryError Text- | DateParseError- | BadPatternError Text- | BadNumberError Text- | BadQtyError Text- | BadSortKeyError Text- | BadComparator Text- | BadExpression- | BadColorName Text- | BadFieldName Text- | BadBackgroundArg Text- | UnexpectedWord Text Text- | BadCommodityError Text- deriving Show - ------------------------------------------------------------ -- Operands ------------------------------------------------------------@@ -204,49 +183,49 @@ String -> CaseSensitive -> MatcherFactory- -> Ex.Exceptional Error (Text -> Bool)+ -> Ex.Exceptional String (Text -> Bool) -getMatcher s cs f = case f cs (pack s) of- Ex.Exception e -> Ex.Exception $ BadPatternError e- Ex.Success m -> return m+getMatcher s cs f = Ex.mapException e (f cs (pack s))+ where+ e err = "bad pattern: " ++ s ++ " error message: "+ ++ unpack err --- | Parses comparers given on command line to a function.+-- | Parses comparers given on command line to a function. Aborts if+-- the string given is invalid. parseComparer :: (Eq a, Ord a) => String- -> Maybe (a -> a -> Bool)+ -> Ex.Exceptional String (a -> a -> Bool) parseComparer t- | t == "<" = Just (<)- | t == "<=" = Just (<=)- | t == "==" = Just (==)- | t == "=" = Just (==)- | t == ">" = Just (>)- | t == ">=" = Just (>=)- | t == "/=" = Just (/=)- | t == "!=" = Just (/=)- | otherwise = Nothing+ | t == "<" = return (<)+ | t == "<=" = return (<=)+ | t == "==" = return (==)+ | t == "=" = return (==)+ | t == ">" = return (>)+ | t == ">=" = return (>=)+ | t == "/=" = return (/=)+ | t == "!=" = return (/=)+ | otherwise = Ex.throw $ "invalid comparer: " ++ t -parseDate :: DefaultTimeZone -> Text -> Exceptional Error L.DateTime-parseDate dtz t = case parse (dateTime dtz) "" t of- Left _ -> Exception DateParseError- Right d -> return d+parseDate :: String -> Ex.Exceptional String L.DateTime+parseDate =+ Ex.mapException f . Ex.fromEither . parse Pc.dateTime "" . pack+ where+ f msg = "invalid date: " ++ show msg -date :: Parser (DefaultTimeZone -> Ex.Exceptional Error Operand)-date =- let os = C.OptSpec ["date"] ['d'] (C.TwoArg f)- f a1 a2 dtz = do- cmp <- Ex.fromMaybe (BadComparator (pack a1))- (parseComparer a1)- dt <- parseDate dtz (pack a2)- return $ X.Operand (P.date (`cmp` dt))- in C.parseOption [os]+date :: OptSpec (Ex.Exceptional String Operand)+date = C.OptSpec ["date"] ['d'] (C.TwoArg f)+ where+ f a1 a2 = g <$> parseComparer a1 <*> parseDate a2+ g cmp dt = X.Operand (P.date (`cmp` dt)) -current :: Parser (L.DateTime -> Operand)-current =- let os = C.OptSpec ["current"] [] (C.NoArg f)- f dt = X.Operand (P.date (<= dt))- in C.parseOption [os] +current :: L.DateTime -> OptSpec Operand+current dt = C.OptSpec ["current"] [] (C.NoArg f)+ where+ f = X.Operand (P.date (<= dt))++ -- | Creates options that match against fields that are -- colon-separated. The operand added will return True if the -- posting's colon-separated option matches the pattern given, or@@ -254,7 +233,7 @@ sepOption :: String -- ^ Long option name- + -> Maybe Char -- ^ Short option name, if there is one @@ -262,32 +241,27 @@ -- ^ When applied to a text that separates the different segments of -- the field, a matcher, and a posting, this function returns True -- if the posting matches the matcher or False if it does not.- - -> Parser (CaseSensitive- -> MatcherFactory- -> Ex.Exceptional Error Operand)-sepOption str mc f =- let so = case mc of- Nothing -> []- Just c -> [c]- os = C.OptSpec [str] so (C.OneArg g)- g a cs fty = do- m <- getMatcher a cs fty- return $ X.Operand (f sep m)- in C.parseOption [os] + -> OptSpec ( CaseSensitive+ -> MatcherFactory+ -> Ex.Exceptional String Operand )+sepOption str mc f = C.OptSpec [str] so (C.OneArg g)+ where+ so = maybe [] return mc+ g a cs fty = h <$> getMatcher a cs fty+ where+ h mtr = X.Operand (f sep mtr)++ sep :: Text sep = Text.singleton ':' -- | Parses exactly one integer; fails if it cannot read exactly one.-parseInt :: String -> Exceptional Error Int-parseInt t = let ps = reads t in- case ps of- [] -> Exception $ BadNumberError (pack t)- ((i, s):[]) -> if length s /= 0- then Exception $ BadNumberError (pack t)- else return i- _ -> Exception $ BadNumberError (pack t)+parseInt :: String -> Ex.Exceptional String Int+parseInt t =+ case reads t of+ ((i, ""):[]) -> return i+ _ -> Ex.throw ("invalid number: " ++ t) -- | Creates options that take two arguments, with the first argument -- being the level to match, and the second being the pattern that@@ -296,125 +270,121 @@ levelOption :: String -- ^ Long option name- + -> (Int -> (Text -> Bool) -> L.PostFam -> Bool) -- ^ Applied to an integer, a matcher, and a PostingBox, this -- function returns True if a particular field in the posting -- matches the matcher given at the given level, or False otherwise.- - -> Parser (CaseSensitive- -> MatcherFactory -> Exceptional Error Operand)- -levelOption str f =- let os = C.OptSpec [str] [] (C.TwoArg g)- g a1 a2 cs fty = do- n <- parseInt a1- m <- getMatcher a2 cs fty- return $ X.Operand (f n m)- in C.parseOption [os] + -> OptSpec ( CaseSensitive+ -> MatcherFactory+ -> Ex.Exceptional String Operand)++levelOption str f = C.OptSpec [str] [] (C.TwoArg g)+ where+ g a1 a2 cs fty = h <$> parseInt a1 <*> getMatcher a2 cs fty+ h i m = X.Operand (f i m)++ -- | Creates options that add an operand that matches the posting if a -- particluar field matches the pattern given. patternOption :: String -- ^ Long option- + -> Maybe Char -- ^ Short option, if included -> ((Text -> Bool) -> L.PostFam -> Bool) -- ^ When applied to a matcher and a PostingBox, this function -- returns True if the posting matches, or False if it does not.- - -> Parser (CaseSensitive- -> MatcherFactory -> Exceptional Error Operand)-patternOption str mc f =- let so = maybe [] (\c -> [c]) mc- os = C.OptSpec [str] so (C.OneArg g)- g a1 cs fty = do- m <- getMatcher a1 cs fty- return $ X.Operand (f m)- in C.parseOption [os] + -> OptSpec ( CaseSensitive+ -> MatcherFactory+ -> Ex.Exceptional String Operand )+patternOption str mc f = C.OptSpec [str] so (C.OneArg g)+ where+ so = maybe [] (\c -> [c]) mc+ g a1 cs fty = h <$> getMatcher a1 cs fty+ h m = X.Operand (f m)+++ -- | The account option; matches if the pattern given matches the -- colon-separated account name.-account :: Parser (CaseSensitive- -> MatcherFactory -> Exceptional Error Operand)+account :: OptSpec ( CaseSensitive+ -> MatcherFactory+ -> Ex.Exceptional String Operand ) account = sepOption "account" (Just 'a') P.account -- | The account-level option; matches if the account at the given -- level matches.-accountLevel :: Parser (CaseSensitive- -> MatcherFactory -> Exceptional Error Operand)+accountLevel :: OptSpec ( CaseSensitive+ -> MatcherFactory+ -> Ex.Exceptional String Operand) accountLevel = levelOption "account-level" P.accountLevel -- | The accountAny option; returns True if the matcher given matches -- a single sub-account name at any level.-accountAny :: Parser (CaseSensitive- -> MatcherFactory -> Exceptional Error Operand)+accountAny :: OptSpec ( CaseSensitive+ -> MatcherFactory+ -> Ex.Exceptional String Operand ) accountAny = patternOption "account-any" Nothing P.accountAny -- | The payee option; returns True if the matcher matches the payee -- name.-payee :: Parser (CaseSensitive- -> MatcherFactory -> Exceptional Error Operand)+payee :: OptSpec ( CaseSensitive+ -> MatcherFactory+ -> Ex.Exceptional String Operand ) payee = patternOption "payee" (Just 'p') P.payee -tag :: Parser (CaseSensitive- -> MatcherFactory -> Exceptional Error Operand)+tag :: OptSpec ( CaseSensitive+ -> MatcherFactory+ -> Ex.Exceptional String Operand) tag = patternOption "tag" (Just 't') P.tag -number :: Parser (CaseSensitive- -> MatcherFactory -> Exceptional Error Operand)+number :: OptSpec ( CaseSensitive+ -> MatcherFactory+ -> Ex.Exceptional String Operand ) number = patternOption "number" Nothing P.number -flag :: Parser (CaseSensitive- -> MatcherFactory -> Exceptional Error Operand)+flag :: OptSpec ( CaseSensitive+ -> MatcherFactory+ -> Ex.Exceptional String Operand) flag = patternOption "flag" Nothing P.flag -commodity :: Parser (CaseSensitive- -> MatcherFactory -> Exceptional Error Operand)-commodity = sepOption "commodity" Nothing P.commodity--commodityLevel :: Parser (CaseSensitive- -> MatcherFactory -> Exceptional Error Operand)-commodityLevel = levelOption "commodity-level" P.commodityLevel--commodityAny :: Parser (CaseSensitive- -> MatcherFactory -> Exceptional Error Operand)-commodityAny = patternOption "commodity" Nothing P.commodityAny+commodity :: OptSpec ( CaseSensitive+ -> MatcherFactory+ -> Ex.Exceptional String Operand)+commodity = patternOption "commodity" Nothing P.commodity -postingMemo :: Parser (CaseSensitive- -> MatcherFactory -> Exceptional Error Operand)+postingMemo :: OptSpec ( CaseSensitive+ -> MatcherFactory+ -> Ex.Exceptional String Operand) postingMemo = patternOption "posting-memo" Nothing P.postingMemo -transactionMemo :: Parser (CaseSensitive- -> MatcherFactory -> Exceptional Error Operand)+transactionMemo :: OptSpec ( CaseSensitive+ -> MatcherFactory+ -> Ex.Exceptional String Operand) transactionMemo = patternOption "transaction-memo" Nothing P.transactionMemo -debit :: Parser Operand-debit =- let os = C.OptSpec ["debit"] [] (C.NoArg (X.Operand P.debit))- in C.parseOption [os]+debit :: OptSpec Operand+debit = C.OptSpec ["debit"] [] (C.NoArg (X.Operand P.debit)) -credit :: Parser Operand-credit =- let os = C.OptSpec ["credit"] [] (C.NoArg (X.Operand P.credit))- in C.parseOption [os]+credit :: OptSpec Operand+credit = C.OptSpec ["credit"] [] (C.NoArg (X.Operand P.credit)) -qtyOption :: Parser (RadGroup -> Exceptional Error Operand)-qtyOption =- let os = C.OptSpec ["qty"] [] (C.TwoArg f)- f a1 a2 rg = do- q <- case parse (qty rg) "" (pack a2) of- Left _ -> Ex.throw $ BadQtyError (pack a2)- Right g -> return g- cmp <- Ex.fromMaybe (BadComparator (pack a1))- (parseComparer a1)- return $ X.Operand (P.qty (`cmp` q))- in C.parseOption [os]+qtyOption :: OptSpec (Ex.Exceptional String Operand)+qtyOption = C.OptSpec ["qty"] [] (C.TwoArg f)+ where+ f a1 a2 = g <$> parseComparer a1 <*> parseQty a2+ parseQty = Ex.mapException err . Ex.fromEither+ . parse Pc.quantity "" . pack+ err msg = "invalid quantity: " ++ show msg+ g cmp qty = X.Operand (P.qty (`cmp` qty)) + -- | Creates two options suitable for comparison of serial numbers, -- one for ascending, one for descending. serialOption ::@@ -426,24 +396,25 @@ -> String -- ^ Name of the command line option, such as @global-transaction@ - -> Parser (Exceptional Error Operand)+ -> ( OptSpec (Ex.Exceptional String Operand)+ , OptSpec (Ex.Exceptional String Operand) ) -- ^ Parses both descending and ascending serial options. -serialOption getSerial n =- let osA = C.OptSpec [n] []- (C.TwoArg (f L.forward))- osD = C.OptSpec [addPrefix "rev" n] []- (C.TwoArg (f L.backward))- f getInt a1 a2 = do- cmp <- Ex.fromMaybe (BadComparator (pack a1))- (parseComparer a1)- i <- parseInt a2- let op pf = case getSerial pf of- Nothing -> False- Just ser -> getInt ser `cmp` i- return $ X.Operand op- in C.parseOption [osA, osD]+serialOption getSerial n = (osA, osD)+ where+ osA = C.OptSpec [n] []+ (C.TwoArg (f L.forward))+ osD = C.OptSpec [addPrefix "rev" n] []+ (C.TwoArg (f L.backward))+ f getInt a1 a2 = g <$> parseComparer a1 <*> parseInt a2+ where+ g cmp i =+ let op pf = case getSerial pf of+ Nothing -> False+ Just ser -> getInt ser `cmp` i+ in X.Operand op + -- | Takes a string, adds a prefix and capitalizes the first letter of -- the old string. e.g. applied to "rev" and "globalTransaction", -- returns "revGlobalTransaction".@@ -453,240 +424,166 @@ "" -> "" x:xs -> toUpper x : xs -globalTransaction :: Parser (Exceptional Error Operand)+globalTransaction :: ( OptSpec (Ex.Exceptional String Operand)+ , OptSpec (Ex.Exceptional String Operand) ) globalTransaction = let f = fmap L.unGlobalTransaction- . L.globalTransaction- . L.tMeta+ . L.tGlobalTransaction . parent . L.unPostFam in serialOption f "globalTransaction" -globalPosting :: Parser (Exceptional Error Operand)+globalPosting :: ( OptSpec (Ex.Exceptional String Operand)+ , OptSpec (Ex.Exceptional String Operand) ) globalPosting = let f = fmap L.unGlobalPosting- . L.globalPosting- . L.pMeta+ . L.pGlobalPosting . child . L.unPostFam in serialOption f "globalPosting" -filePosting :: Parser (Exceptional Error Operand)+filePosting :: ( OptSpec (Ex.Exceptional String Operand)+ , OptSpec (Ex.Exceptional String Operand) ) filePosting = let f = fmap L.unFilePosting- . L.filePosting- . L.pMeta+ . L.pFilePosting . child . L.unPostFam in serialOption f "filePosting" -fileTransaction :: Parser (Exceptional Error Operand)+fileTransaction :: ( OptSpec (Ex.Exceptional String Operand)+ , OptSpec (Ex.Exceptional String Operand) ) fileTransaction = let f = fmap L.unFileTransaction- . L.fileTransaction- . L.tMeta+ . L.tFileTransaction . parent . L.unPostFam in serialOption f "fileTransaction" --- | Parses operands.-parseOperand ::- Parser (L.DateTime- -> DefaultTimeZone- -> RadGroup- -> CaseSensitive- -> MatcherFactory- -> Ex.Exceptional Error Operand)--parseOperand =- (do { f <- date; return (\_ dtz _ _ _ -> f dtz)})- <|> (do { f <- current; return (\dt _ _ _ _ -> return (f dt)) })- <|> wrapFactArg account- <|> wrapFactArg accountLevel- <|> wrapFactArg accountAny- <|> wrapFactArg payee- <|> wrapFactArg tag- <|> wrapFactArg number- <|> wrapFactArg flag- <|> wrapFactArg commodity- <|> wrapFactArg commodityLevel- <|> wrapFactArg commodityAny- <|> wrapFactArg postingMemo- <|> wrapFactArg transactionMemo- <|> (do { o <- debit; return (\_ _ _ _ _ -> return o) })- <|> (do { o <- credit; return (\_ _ _ _ _ -> return o) })- <|> (do { f <- qtyOption; return (\ _ _ rg _ _ -> f rg)})- <|> wrapNoArg globalTransaction- <|> wrapNoArg globalPosting- <|> wrapNoArg filePosting- <|> wrapNoArg fileTransaction+unDouble+ :: ( OptSpec (Ex.Exceptional String Operand)+ , OptSpec (Ex.Exceptional String Operand) )+ -> [ OptSpec (a -> b -> Ex.Exceptional String Operand) ]+unDouble (o1, o2) = [fmap f o1, fmap f o2]+ where+ f = (\g _ _ -> g) -wrapNoArg ::- Parser (Ex.Exceptional Error Operand)- -> Parser (L.DateTime- -> DefaultTimeZone- -> RadGroup- -> CaseSensitive- -> MatcherFactory- -> Ex.Exceptional Error Operand)-wrapNoArg p = do- o <- p- return (\_ _ _ _ _ -> o)+-- | All operand OptSpec.+operandSpecs+ :: L.DateTime+ -> [OptSpec (CaseSensitive+ -> MatcherFactory+ -> Ex.Exceptional String Operand)] -wrapFactArg ::- Parser (CaseSensitive -> MatcherFactory -> Exceptional Error Operand)- -> Parser (L.DateTime- -> DefaultTimeZone- -> RadGroup- -> CaseSensitive- -> MatcherFactory- -> Ex.Exceptional Error Operand)-wrapFactArg p = do- f <- p- return (\_ _ _ cs fact -> f cs fact)+operandSpecs dt =+ [ fmap (\f _ _ -> f) date+ , fmap (\o _ _ -> return o) (current dt)+ , wrapFactArg account+ , wrapFactArg accountLevel+ , wrapFactArg accountAny+ , wrapFactArg payee+ , wrapFactArg tag+ , wrapFactArg number+ , wrapFactArg flag+ , wrapFactArg commodity+ , wrapFactArg postingMemo+ , wrapFactArg transactionMemo+ , fmap (\f _ _ -> return f) debit+ , fmap (\f _ _ -> return f) credit+ , fmap (\f _ _ -> f) qtyOption+ ]+ ++ unDouble globalTransaction+ ++ unDouble globalPosting+ ++ unDouble filePosting+ ++ unDouble fileTransaction+ where+ wrapFactArg = fmap (\f cs fty -> f cs fty) ------------------------------------------------------------ -- Post filters ------------------------------------------------------------ -optHead :: Parser (Exceptional Error PostFilterFn)-optHead =- let os = C.OptSpec ["head"] [] (C.OneArg f)- f a = do- i <- fmap ItemIndex (parseInt a)- return (\_ idx -> idx < i)- in C.parseOption [os]+optHead :: OptSpec (Ex.Exceptional String PostFilterFn)+optHead = C.OptSpec ["head"] [] (C.OneArg f)+ where+ f a = g <$> parseInt a+ where+ g i _ ii = ii < (ItemIndex i) -optTail :: Parser (Exceptional Error PostFilterFn)-optTail =- let os = C.OptSpec ["tail"] [] (C.OneArg f)- f a = do- i <- parseInt a- let g (ListLength len) (ItemIndex idx) = idx >= len - i- return g- in C.parseOption [os]+optTail :: OptSpec (Ex.Exceptional String PostFilterFn)+optTail = C.OptSpec ["tail"] [] (C.OneArg f)+ where+ f a = g <$> parseInt a+ where+ g i (ListLength len) (ItemIndex ii) = ii >= len - i -parsePostFilter :: Parser (Exceptional Error PostFilterFn)-parsePostFilter = optHead <|> optTail ++postFilterSpecs :: ( OptSpec (Ex.Exceptional String PostFilterFn)+ , OptSpec (Ex.Exceptional String PostFilterFn) )+postFilterSpecs = (optHead, optTail)+ ------------------------------------------------------------ -- Matcher control ------------------------------------------------------------ -noArg :: a -> String -> Parser a-noArg a s = let os = C.OptSpec [s] "" (C.NoArg a)- in C.parseOption [os]+noArg :: a -> String -> OptSpec a+noArg a s = C.OptSpec [s] "" (C.NoArg a) -parseInsensitive :: Parser CaseSensitive+parseInsensitive :: OptSpec CaseSensitive parseInsensitive =- let os = C.OptSpec ["case-insensitive"] ['i'] (C.NoArg Insensitive)- in C.parseOption [os]+ C.OptSpec ["case-insensitive"] ['i'] (C.NoArg Insensitive) -parseSensitive :: Parser CaseSensitive++parseSensitive :: OptSpec CaseSensitive parseSensitive =- let os = C.OptSpec ["case-sensitive"] ['I'] (C.NoArg Sensitive)- in C.parseOption [os]+ C.OptSpec ["case-sensitive"] ['I'] (C.NoArg Sensitive) -within :: Parser MatcherFactory+within :: OptSpec MatcherFactory within = noArg (\c t -> return (TM.within c t)) "within" -pcre :: Parser MatcherFactory+pcre :: OptSpec MatcherFactory pcre = noArg TM.pcre "pcre" -posix :: Parser MatcherFactory+posix :: OptSpec MatcherFactory posix = noArg TM.tdfa "posix" -exact :: Parser MatcherFactory+exact :: OptSpec MatcherFactory exact = noArg (\c t -> return (TM.exact c t)) "exact" -parseMatcherSelect :: Parser MatcherFactory-parseMatcherSelect = within <|> pcre <|> posix <|> exact+matcherSelectSpecs :: [OptSpec MatcherFactory]+matcherSelectSpecs = [within, pcre, posix, exact] -parseCaseSelect :: Parser CaseSensitive-parseCaseSelect = parseInsensitive <|> parseSensitive+caseSelectSpecs :: [OptSpec CaseSensitive]+caseSelectSpecs = [parseInsensitive, parseSensitive] ------------------------------------------------------------ -- Operators ------------------------------------------------------------ -- | Open parentheses-open :: Parser (X.Token a)+open :: OptSpec (X.Token a) open = noArg X.TokOpenParen "open" -- | Close parentheses-close :: Parser (X.Token a)+close :: OptSpec (X.Token a) close = noArg X.TokCloseParen "close" -- | and operator-parseAnd :: Parser (X.Token (a -> Bool))+parseAnd :: OptSpec (X.Token (a -> Bool)) parseAnd = noArg X.tokAnd "and" -- | or operator-parseOr :: Parser (X.Token (a -> Bool))+parseOr :: OptSpec (X.Token (a -> Bool)) parseOr = noArg X.tokOr "or" -- | not operator-parseNot :: Parser (X.Token (a -> Bool))+parseNot :: OptSpec (X.Token (a -> Bool)) parseNot = noArg X.tokNot "not" where -parseOperator :: Parser (X.Token (a -> Bool))-parseOperator =- open <|> close <|> parseAnd <|> parseOr <|> parseNot----------------------------------------------------------------- Sorting--------------------------------------------------------------type Orderer = L.PostFam -> L.PostFam -> Ordering--ordering ::- (Ord b)- => (a -> b)- -> (a -> a -> Ordering)-ordering q = f where- f p1 p2 = compare (q p1) (q p2)---flipOrder :: (a -> a -> Ordering) -> (a -> a -> Ordering)-flipOrder f = f' where- f' p1 p2 = case f p1 p2 of- LT -> GT- GT -> LT- EQ -> EQ--capitalizeFirstLetter :: String -> String-capitalizeFirstLetter s = case s of- [] -> []- (x:xs) -> toUpper x : xs--ordPairs :: [(String, Orderer)]-ordPairs = - [ ("payee", ordering Q.payee)- , ("date", ordering Q.dateTime)- , ("flag", ordering Q.flag)- , ("number", ordering Q.number)- , ("account", ordering Q.account)- , ("drCr", ordering Q.drCr)- , ("qty", ordering Q.qty)- , ("commodity", ordering Q.commodity)- , ("postingMemo", ordering Q.postingMemo)- , ("transactionMemo", ordering Q.transactionMemo) ]--ords :: [(String, Orderer)]-ords = ordPairs ++ uppers where- uppers = map toReversed ordPairs- toReversed (s, f) =- (capitalizeFirstLetter s, flipOrder f)-+operatorSpecs :: [OptSpec (X.Token (a -> Bool))]+operatorSpecs =+ [open, close, parseAnd, parseOr, parseNot] -parseSort :: Parser (Exceptional Error Orderer)-parseSort =- let os = C.OptSpec ["sort"] ['s'] (C.OneArg f)- f a =- let matches = filter (\p -> a `isPrefixOf` (fst p)) ords- in case matches of- [] -> Ex.throw $ BadSortKeyError (pack a)- x:[] -> return $ snd x- _ -> Ex.throw $ BadSortKeyError (pack a)- in C.parseOption [os]
Penny/Liberty/Expressions.hs view
@@ -1,8 +1,8 @@ module Penny.Liberty.Expressions ( I.Precedence(Precedence),- + I.Associativity(ALeft, ARight),- + I.Token(TokOperand, TokUnaryPostfix, TokUnaryPrefix,@@ -10,7 +10,7 @@ TokOpenParen, TokCloseParen), - R.Operand(Operand),+ R.Operand(..), tokAnd, tokOr, tokNot,
Penny/Liberty/Expressions/Infix.hs view
@@ -10,17 +10,17 @@ module Penny.Liberty.Expressions.Infix ( Precedence(Precedence),- + Associativity(ALeft, ARight),- + Token(TokOperand, TokUnaryPostfix, TokUnaryPrefix, TokBinary, TokOpenParen, TokCloseParen),- + infixToRPN ) where
Penny/Liberty/Expressions/RPN.hs view
@@ -6,7 +6,7 @@ -- 4 +@, the @5@ and the @4@ are operands; the @+@ is an operator; -- each of these three is a token. module Penny.Liberty.Expressions.RPN (- Operand(Operand),+ Operand(..), Operator(Unary, Binary), Token(TokOperand, TokOperator), process) where@@ -15,7 +15,7 @@ -- | An operand; for example, in the expression @5 4 +@, @5@ and @4@ -- are operands.-newtype Operand a = Operand a deriving Show+newtype Operand a = Operand { unOperand :: a } deriving Show -- | Operators; for example, in the expression @5 4 +@, @+@ is an -- operator. Because this is RPN, there is no operator precedence.
Penny/Lincoln.hs view
@@ -17,85 +17,94 @@ , B.removeZeroCommodities , B.BottomLine (Zero, NonZero) , B.Column (Column)- + -- * Bits -- ** Accounts- , I.SubAccountName (SubAccountName, unSubAccountName)+ , I.SubAccount (SubAccount, unSubAccount) , I.Account(Account, unAccount)- + -- ** Amounts- , I.Amount (Amount, qty, commodity)- + , I.Amount (Amount, qty, commodity, side, spaceBetween)+ -- ** Commodities , I.Commodity (Commodity, unCommodity)- , I.SubCommodity (SubCommodity, unSubCommodity)- , I.charCommodity- + -- ** DateTime- , I.DateTime- , I.dateTime- , I.localTime- , I.timeZone- , I.TimeZoneOffset+ , I.TimeZoneOffset ( offsetToMins ) , I.minsToOffset- , I.offsetToMins , I.noOffset- + , I.Hours ( unHours )+ , I.intToHours+ , I.Minutes ( unMinutes )+ , I.intToMinutes+ , I.Seconds ( unSeconds )+ , I.intToSeconds+ , I.zeroSeconds+ , I.midnight+ , I.DateTime ( .. )+ , I.dateTimeMidnightUTC+ , I.toUTC+ , I.toZonedTime+ , I.fromZonedTime+ , I.sameInstant+ -- ** Debits and credits , I.DrCr(Debit, Credit) , I.opposite- + -- ** Entries , I.Entry (Entry, drCr, amount)- + -- ** Flag , I.Flag (Flag, unFlag)- + -- ** Memos- , I.MemoLine (MemoLine, unMemoLine) , I.Memo (Memo, unMemo)- + -- ** Number , I.Number (Number, unNumber)- + -- ** Payee , I.Payee (Payee, unPayee)- + -- ** Prices and price points , I.From(From, unFrom) , I.To(To, unTo) , I.CountPerUnit(CountPerUnit, unCountPerUnit) , I.Price(from, to, countPerUnit) , I.newPrice- , I.PricePoint(PricePoint, price, ppMeta)+ , I.PricePoint ( PricePoint, dateTime, price, ppSide,+ ppSpaceBetween, priceLine) - -- ** Quantities + -- ** Quantities , I.Qty- , I.unQty- , I.partialNewQty- , I.newQty+ , I.NumberStr(..)+ , I.toQty+ , I.mantissa+ , I.places , I.add- , I.subt , I.mult- , I.zero , I.difference- , I.Difference(LeftBiggerBy, RightBiggerBy, Equal)- + , I.equivalent+ , I.newQty+ , I.Mantissa, I.Places+ , I.Difference(..)+ , I.allocate+ -- ** Tags , I.Tag(Tag, unTag) , I.Tags(Tags, unTags)- - ++ -- * Builders- , Bd.crashy , Bd.account- + -- * Families -- ** Family types , F.Family(Family) , F.Child(Child) , F.Siblings(Siblings)- + -- ** Manipulating families , F.children , F.orphans@@ -104,27 +113,30 @@ , F.marry , F.divorceWith , F.divorce- + , F.filterChildren+ , F.find+ , F.mapChildren+ , F.mapChildrenA+ , F.mapParent+ , F.mapParentA+ -- * HasText , HT.HasText(text)- , HT.Delimited(Delimited) , HT.HasTextList(textList)- , HT.HasTextNonEmpty(textNonEmpty)- , HT.HasTextNonEmptyList(textNonEmptyList) - -- * TextNonEmpty- , TNE.TextNonEmpty(TextNonEmpty)- -- * Transactions -- ** Postings and transactions , T.Posting , T.Transaction , T.PostFam - -- ** Making transactions+ -- ** Making and deconstructing transactions , T.transaction+ , T.RTransaction(..)+ , T.rTransaction , T.Error ( UnbalancedError, CouldNotInferError)- + , T.toUnverified+ -- ** Querying postings , T.Inferred(Inferred, NotInferred) , T.pPayee@@ -135,8 +147,9 @@ , T.pEntry , T.pMemo , T.pInferred- , T.pMeta- , T.changePostingMeta+ , T.pPostingLine+ , T.pGlobalPosting+ , T.pFilePosting -- ** Querying transactions , T.TopLine@@ -145,42 +158,40 @@ , T.tNumber , T.tPayee , T.tMemo- , T.tMeta- , T.changeTransactionMeta+ , T.tTopLineLine+ , T.tTopMemoLine+ , T.tFilename+ , T.tGlobalTransaction+ , T.tFileTransaction , T.postFam- - -- ** Adding serials to transactions- , T.addSerialsToList- , T.addSerialsToEithers- + -- ** Unwrapping Transactions , T.unTransaction , T.unPostFam- + -- ** Transaction boxes , T.Box (Box, boxMeta, boxPostFam)- ++ -- ** Changing transactions+ , T.TopLineChangeData(..)+ , T.emptyTopLineChangeData+ , T.PostingChangeData(..)+ , T.emptyPostingChangeData+ , T.changeTransaction+ -- * Metadata- , M.TopLineLine(TopLineLine, unTopLineLine)- , M.TopMemoLine(TopMemoLine, unTopMemoLine)- , M.Side(CommodityOnLeft, CommodityOnRight)- , M.SpaceBetween(SpaceBetween, NoSpaceBetween)- , M.Format(Format, side, between)- , M.Filename(Filename, unFilename)- , M.PriceLine(PriceLine, unPriceLine)- , M.PostingLine(PostingLine, unPostingLine)- , M.PriceMeta(PriceMeta, priceLine, priceFormat)- , M.GlobalPosting(GlobalPosting, unGlobalPosting)- , M.FilePosting(FilePosting, unFilePosting)- , M.GlobalTransaction(GlobalTransaction, unGlobalTransaction)- , M.FileTransaction(FileTransaction, unFileTransaction)- , M.PostingMeta(PostingMeta, postingLine, postingFormat,- globalPosting, filePosting)- , M.emptyPostingMeta- , M.TopLineMeta(TopLineMeta, topMemoLine, topLineLine, filename,- globalTransaction, fileTransaction)- , M.emptyTopLineMeta- + , I.TopLineLine(..)+ , I.TopMemoLine(..)+ , I.Side(CommodityOnLeft, CommodityOnRight)+ , I.SpaceBetween(SpaceBetween, NoSpaceBetween)+ , I.Filename(Filename, unFilename)+ , I.PriceLine(PriceLine, unPriceLine)+ , I.PostingLine(PostingLine, unPostingLine)+ , I.GlobalPosting(GlobalPosting, unGlobalPosting)+ , I.FilePosting(FilePosting, unFilePosting)+ , I.GlobalTransaction(GlobalTransaction, unGlobalTransaction)+ , I.FileTransaction(FileTransaction, unFileTransaction)+ -- * PriceDb , DB.PriceDb , DB.emptyDb@@ -188,14 +199,18 @@ , DB.getPrice , DB.PriceDbError(FromNotFound, ToNotFound, CpuNotFound) , DB.convert- + -- * Serials , S.Serial , S.forward , S.backward- , S.serials+ , S.GenSerial+ , S.incrementBack+ , S.getSerial+ , S.makeSerials , S.serialItems- + , S.nSerials+ -- * Matchers , Matchers.Matcher , Matchers.Factory@@ -208,8 +223,6 @@ import qualified Penny.Lincoln.Family as F import qualified Penny.Lincoln.HasText as HT import qualified Penny.Lincoln.Matchers as Matchers-import qualified Penny.Lincoln.Meta as M import qualified Penny.Lincoln.PriceDb as DB import qualified Penny.Lincoln.Serial as S-import qualified Penny.Lincoln.TextNonEmpty as TNE import qualified Penny.Lincoln.Transaction as T
Penny/Lincoln/Balance.hs view
@@ -1,4 +1,4 @@-module Penny.Lincoln.Balance ( +module Penny.Lincoln.Balance ( Balance, unBalance, Balanced(Balanced, Inferable, NotInferable),@@ -20,13 +20,13 @@ import qualified Penny.Lincoln.Bits as B -- | A balance summarizes several entries. You do not create a Balance--- directly. Instead, use 'entryToBalance'. Balance used to be a--- monoid, but there is nothing appropriate for mempty. Instead,--- Balance is really a semigroup, but not a monoid.+-- directly. Instead, use 'entryToBalance'. newtype Balance = Balance (Map B.Commodity BottomLine) deriving (Show, Eq) --- | The map returned by unBalance is never empty.+-- | Returns a map where the keys are the commodities in the balance+-- and the values are the balance for each commodity. If there is no+-- balance at all, this map can be empty. unBalance :: Balance -> Map B.Commodity BottomLine unBalance (Balance m) = m @@ -48,7 +48,7 @@ B.Debit -> B.Credit B.Credit -> B.Debit q = qty col- a = B.Amount q c+ a = B.Amount q c Nothing Nothing in Inferable e _ -> NotInferable @@ -89,19 +89,21 @@ -- the balance. Sometimes you want to know that a commodity was in the -- account but its balance is now zero. addBalances :: Balance -> Balance -> Balance-addBalances (Balance t1) (Balance t2) = +addBalances (Balance t1) (Balance t2) = Balance $ M.unionWith mappend t1 t2 instance Semi.Semigroup Balance where (<>) = addBalances --- | Removes zero balances from a Balance. Will not return a Balance--- with no commodities; instead, returns Nothing if there would be a--- balance with no commodities.-removeZeroCommodities :: Balance -> Maybe Balance+instance Monoid Balance where+ mempty = Balance M.empty+ mappend = addBalances++-- | Removes zero balances from a Balance.+removeZeroCommodities :: Balance -> Balance removeZeroCommodities (Balance m) = let p b = case b of Zero -> False _ -> True m' = M.filter p m- in if M.null m' then Nothing else Just (Balance m')+ in Balance m'
Penny/Lincoln/Bits.hs view
@@ -1,74 +1,96 @@ -- | Essential data types used to make Transactions and Postings. module Penny.Lincoln.Bits ( -- * Accounts- Ac.SubAccountName(SubAccountName, unSubAccountName),- Ac.Account(Account, unAccount),+ O.SubAccount(SubAccount, unSubAccount),+ O.Account(Account, unAccount), -- * Amounts- Am.Amount(Amount, qty, commodity),+ O.Amount(Amount, qty, commodity, side, spaceBetween), -- * Commodities- C.Commodity(Commodity, unCommodity),- C.SubCommodity(SubCommodity, unSubCommodity),- C.charCommodity,+ O.Commodity(Commodity, unCommodity), -- * DateTime- DT.DateTime,- DT.dateTime,- DT.localTime,- DT.timeZone,- DT.TimeZoneOffset,- DT.minsToOffset, DT.offsetToMins, DT.noOffset,+ DT.TimeZoneOffset ( offsetToMins ),+ DT.minsToOffset,+ DT.noOffset,+ DT.Hours ( unHours ),+ DT.intToHours,+ DT.zeroHours,+ DT.Minutes ( unMinutes ),+ DT.intToMinutes,+ DT.zeroMinutes,+ DT.midnight,+ DT.Seconds ( unSeconds ),+ DT.intToSeconds,+ DT.zeroSeconds,+ DT.DateTime ( .. ),+ DT.dateTimeMidnightUTC,+ DT.toUTC,+ DT.toZonedTime,+ DT.fromZonedTime,+ DT.sameInstant, -- * Debits and Credits- DC.DrCr(Debit, Credit),- DC.opposite,- + O.DrCr(Debit, Credit),+ O.opposite,+ -- * Entries- E.Entry(Entry, drCr, amount),+ O.Entry(Entry, drCr, amount), -- * Flag- F.Flag(Flag, unFlag),+ O.Flag(Flag, unFlag), -- * Memos- M.MemoLine(MemoLine, unMemoLine),- M.Memo(Memo, unMemo),+ O.Memo(Memo, unMemo), -- * Number- N.Number(Number, unNumber),+ O.Number(Number, unNumber), -- * Payee- Pa.Payee(Payee, unPayee),+ O.Payee(Payee, unPayee), -- * Prices and price points Pr.From(From, unFrom), Pr.To(To, unTo), Pr.CountPerUnit(CountPerUnit, unCountPerUnit), Pr.Price(from, to, countPerUnit), Pr.convert, Pr.newPrice,- PP.PricePoint(PricePoint, price, ppMeta),+ PricePoint ( .. ), -- * Quantities- Q.Qty, Q.unQty, Q.partialNewQty,- Q.newQty, Q.add, Q.subt, Q.mult, Q.zero,- Q.difference,+ Q.Qty, Q.NumberStr(..), Q.toQty, Q.mantissa, Q.places,+ Q.add, Q.mult, Q.difference, Q.equivalent, Q.newQty,+ Q.Mantissa, Q.Places, Q.Difference(Q.LeftBiggerBy, Q.RightBiggerBy, Q.Equal),+ Q.allocate, -- * Tags- T.Tag(Tag, unTag),- T.Tags(Tags, unTags)) where+ O.Tag(Tag, unTag),+ O.Tags(Tags, unTags), + -- * Metadata+ O.TopLineLine(..),+ O.TopMemoLine(..),+ O.Side(..),+ O.SpaceBetween(..),+ O.Filename(..),+ O.PriceLine(..),+ O.PostingLine(..),+ O.GlobalPosting(..),+ O.FilePosting(..),+ O.GlobalTransaction(..),+ O.FileTransaction(..)+ ) where -import qualified Penny.Lincoln.Bits.Account as Ac-import qualified Penny.Lincoln.Bits.Amount as Am-import qualified Penny.Lincoln.Bits.Commodity as C++import qualified Penny.Lincoln.Bits.Open as O import qualified Penny.Lincoln.Bits.DateTime as DT-import qualified Penny.Lincoln.Bits.DrCr as DC-import qualified Penny.Lincoln.Bits.Entry as E-import qualified Penny.Lincoln.Bits.Flag as F-import qualified Penny.Lincoln.Bits.Memo as M-import qualified Penny.Lincoln.Bits.Number as N-import qualified Penny.Lincoln.Bits.Payee as Pa import qualified Penny.Lincoln.Bits.Price as Pr-import qualified Penny.Lincoln.Bits.PricePoint as PP import qualified Penny.Lincoln.Bits.Qty as Q-import qualified Penny.Lincoln.Bits.Tags as T++data PricePoint = PricePoint { dateTime :: DT.DateTime+ , price :: Pr.Price+ , ppSide :: Maybe O.Side+ , ppSpaceBetween :: Maybe O.SpaceBetween+ , priceLine :: Maybe O.PriceLine }+ deriving (Eq, Show)
− Penny/Lincoln/Bits/Account.hs
@@ -1,12 +0,0 @@-module Penny.Lincoln.Bits.Account where--import Penny.Lincoln.TextNonEmpty (TextNonEmpty)-import Data.List.NonEmpty (NonEmpty)--newtype SubAccountName =- SubAccountName { unSubAccountName :: TextNonEmpty }- deriving (Eq, Ord, Show)--newtype Account = Account { unAccount :: NonEmpty SubAccountName }- deriving (Eq, Show, Ord)-
− Penny/Lincoln/Bits/Amount.hs
@@ -1,8 +0,0 @@-module Penny.Lincoln.Bits.Amount where--import Penny.Lincoln.Bits.Qty ( Qty )-import Penny.Lincoln.Bits.Commodity ( Commodity )--data Amount = Amount { qty :: Qty- , commodity :: Commodity }- deriving (Eq, Show, Ord)
− Penny/Lincoln/Bits/Commodity.hs
@@ -1,19 +0,0 @@-module Penny.Lincoln.Bits.Commodity where--import Penny.Lincoln.TextNonEmpty (- TextNonEmpty ( TextNonEmpty ) )-import Data.List.NonEmpty (NonEmpty((:|)))-import Data.Text ( empty )--newtype Commodity =- Commodity { unCommodity :: NonEmpty SubCommodity }- deriving (Eq, Ord, Show)--newtype SubCommodity =- SubCommodity { unSubCommodity :: TextNonEmpty }- deriving (Eq, Ord, Show)---- | Creates a Commodity whose name is only a single character.-charCommodity :: Char -> Commodity-charCommodity c =- Commodity ((SubCommodity (TextNonEmpty c empty)) :| [])
Penny/Lincoln/Bits/DateTime.hs view
@@ -1,17 +1,23 @@--- | Perhaps this could be called @moment@, as it aims to identify a--- moment in time. A DateTime is a combination of a LocalTime from--- Data.Time and a TimeZoneOffset. Previously a DateTime was simply a--- ZonedTime from Data.Time but ZonedTime has data that Penny does not--- need.-module Penny.Lincoln.Bits.DateTime (- DateTime- , dateTime- , localTime- , timeZone- , TimeZoneOffset- , offsetToMins+module Penny.Lincoln.Bits.DateTime+ ( TimeZoneOffset ( offsetToMins ) , minsToOffset , noOffset+ , Hours ( unHours )+ , intToHours+ , zeroHours+ , Minutes ( unMinutes )+ , intToMinutes+ , zeroMinutes+ , Seconds ( unSeconds )+ , intToSeconds+ , zeroSeconds+ , midnight+ , DateTime ( .. )+ , dateTimeMidnightUTC+ , toUTC+ , toZonedTime+ , fromZonedTime+ , sameInstant ) where import qualified Data.Time as T@@ -35,23 +41,95 @@ noOffset :: TimeZoneOffset noOffset = TimeZoneOffset 0 --- | A DateTime is a UTC time that also remembers the local time from--- which it was set. The Eq and Ord instances will compare two--- DateTimes based on their equivalent UTC times.-data DateTime = DateTime { localTime :: T.LocalTime- , timeZone :: TimeZoneOffset }- deriving Show+newtype Hours = Hours { unHours :: Int }+ deriving (Eq, Ord, Show) --- | Construct a DateTime.-dateTime :: T.LocalTime -> TimeZoneOffset -> DateTime-dateTime = DateTime+newtype Minutes = Minutes { unMinutes :: Int }+ deriving (Eq, Ord, Show) +newtype Seconds = Seconds { unSeconds :: Int }+ deriving (Eq, Ord, Show)++-- | succeeds if 0 <= x < 24+intToHours :: Int -> Maybe Hours+intToHours h =+ if h >= 0 && h < 24 then Just . Hours $ h else Nothing++zeroHours :: Hours+zeroHours = Hours 0++-- | succeeds if 0 <= x < 60+intToMinutes :: Int -> Maybe Minutes+intToMinutes m =+ if m >= 0 && m < 60 then Just . Minutes $ m else Nothing++zeroMinutes :: Minutes+zeroMinutes = Minutes 0++-- | succeeds if 0 <= x < 61 (to allow for leap seconds)+intToSeconds :: Int -> Maybe Seconds+intToSeconds s =+ if s >= 0 && s < 61+ then Just . Seconds $ s+ else Nothing++zeroSeconds :: Seconds+zeroSeconds = Seconds 0++midnight :: (Hours, Minutes, Seconds)+midnight = (zeroHours, zeroMinutes, zeroSeconds)++-- | A DateTime is a a local date and time, along with a time zone+-- offset. The Eq and Ord instances are derived; therefore, two+-- DateTime instances will not be equivalent if the time zone offsets+-- are different, even if they are the same instant. To compare one+-- DateTime to another, you probably want to use 'toUTC' and compare+-- those. To see if two DateTime are the same instant, use+-- 'sameInstant'.+data DateTime = DateTime+ { day :: T.Day+ , hours :: Hours+ , minutes :: Minutes+ , seconds :: Seconds+ , timeZone :: TimeZoneOffset+ } deriving (Eq, Ord, Show)++dateTimeMidnightUTC :: T.Day -> DateTime+dateTimeMidnightUTC d = DateTime d h m s z+ where+ (h, m, s) = midnight+ z = noOffset++toZonedTime :: DateTime -> T.ZonedTime+toZonedTime dt = T.ZonedTime lt tz+ where+ d = day dt+ lt = T.LocalTime d tod+ tod = T.TimeOfDay (unHours . hours $ dt) (unMinutes . minutes $ dt)+ (fromIntegral . unSeconds . seconds $ dt)+ tz = T.TimeZone (offsetToMins . timeZone $ dt) False ""++fromZonedTime :: T.ZonedTime -> Maybe DateTime+fromZonedTime (T.ZonedTime (T.LocalTime d tod) tz) = do+ h <- intToHours . T.todHour $ tod+ m <- intToMinutes . T.todMin $ tod+ let (sWhole, _) = properFraction . T.todSec $ tod+ s <- intToSeconds sWhole+ tzo <- minsToOffset . T.timeZoneMinutes $ tz+ return $ DateTime d h m s tzo+ toUTC :: DateTime -> T.UTCTime-toUTC (DateTime lt (TimeZoneOffset tzo)) = T.localTimeToUTC tz lt where- tz = T.minutesToTimeZone tzo+toUTC dt = T.localTimeToUTC tz lt+ where+ tz = T.minutesToTimeZone . offsetToMins . timeZone $ dt+ tod = T.TimeOfDay (unHours h) (unMinutes m)+ (fromIntegral . unSeconds $ s)+ DateTime d h m s _ = dt+ lt = T.LocalTime d tod -instance Eq DateTime where- l == r = toUTC l == toUTC r+-- | Are these DateTimes the same instant in time, after adjusting for+-- local timezones? -instance Ord DateTime where- compare l r = compare (toUTC l) (toUTC r)+sameInstant :: DateTime -> DateTime -> Bool+sameInstant t1 t2 = toUTC t1 == toUTC t2+
− Penny/Lincoln/Bits/DrCr.hs
@@ -1,9 +0,0 @@-module Penny.Lincoln.Bits.DrCr where--data DrCr = Debit | Credit deriving (Eq, Show, Ord)---- | Debit returns Credit; Credit returns Debit-opposite :: DrCr -> DrCr-opposite d = case d of- Debit -> Credit- Credit -> Debit
− Penny/Lincoln/Bits/Entry.hs
@@ -1,9 +0,0 @@-module Penny.Lincoln.Bits.Entry where--import Penny.Lincoln.Bits.Amount (Amount)-import Penny.Lincoln.Bits.DrCr (DrCr)--data Entry = Entry { drCr :: DrCr- , amount :: Amount }- deriving (Eq, Show, Ord)-
− Penny/Lincoln/Bits/Flag.hs
@@ -1,7 +0,0 @@-module Penny.Lincoln.Bits.Flag where--import Penny.Lincoln.TextNonEmpty (TextNonEmpty)--newtype Flag = Flag { unFlag :: TextNonEmpty }- deriving (Eq, Show, Ord)-
− Penny/Lincoln/Bits/Memo.hs
@@ -1,9 +0,0 @@-module Penny.Lincoln.Bits.Memo where--import Penny.Lincoln.TextNonEmpty (TextNonEmpty)--newtype MemoLine = MemoLine { unMemoLine :: TextNonEmpty }- deriving (Eq, Ord, Show)--newtype Memo = Memo { unMemo :: [MemoLine] }- deriving (Eq, Show, Ord)
− Penny/Lincoln/Bits/Number.hs
@@ -1,6 +0,0 @@-module Penny.Lincoln.Bits.Number where--import Penny.Lincoln.TextNonEmpty (TextNonEmpty)--newtype Number = Number { unNumber :: TextNonEmpty }- deriving (Eq, Show, Ord)
+ Penny/Lincoln/Bits/Open.hs view
@@ -0,0 +1,124 @@+-- | 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+-- that do not have exported constructors.++module Penny.Lincoln.Bits.Open where++import Data.Text (Text)+import qualified Data.Text as X+import qualified Penny.Lincoln.Serial as S+import qualified Penny.Lincoln.Bits.Qty as Q++newtype SubAccount =+ SubAccount { unSubAccount :: Text }+ deriving (Eq, Ord, Show)++newtype Account = Account { unAccount :: [SubAccount] }+ deriving (Eq, Show, Ord)++data Amount = Amount { qty :: Q.Qty+ , commodity :: Commodity+ , side :: Maybe Side+ , spaceBetween :: Maybe SpaceBetween }+ deriving (Eq, Show, Ord)++newtype Commodity =+ Commodity { unCommodity :: Text }+ deriving (Eq, Ord, Show)++data DrCr = Debit | Credit deriving (Eq, Show, Ord)++-- | 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)++newtype Flag = Flag { unFlag :: Text }+ deriving (Eq, Show, Ord)++-- | 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)++newtype Number = Number { unNumber :: Text }+ deriving (Eq, Show, Ord)++newtype Payee = Payee { unPayee :: Text }+ deriving (Eq, Show, Ord)++newtype Tag = Tag { unTag :: Text }+ deriving (Eq, Show, Ord)++newtype Tags = Tags { unTags :: [Tag] }+ deriving (Eq, Show, Ord)++-- Metadata++-- | The line number that the TopLine starts on (excluding the memo+-- accompanying the TopLine).+newtype TopLineLine = TopLineLine { unTopLineLine :: Int }+ deriving (Eq, Show)++-- | The line number that the memo accompanying the TopLine starts on.+newtype TopMemoLine = TopMemoLine { unTopMemoLine :: Int }+ 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+-- (e.g. 2.14 USD).+data Side+ = CommodityOnLeft+ | CommodityOnRight+ deriving (Eq, Show, Ord)++-- | There may or may not be a space in between the commodity and the+-- quantity.+data SpaceBetween+ = SpaceBetween+ | NoSpaceBetween+ deriving (Eq, Show, Ord)++-- | The name of the file in which a transaction appears.+newtype Filename = Filename { unFilename :: X.Text }+ deriving (Eq, Show)++-- | The line number on which a price appears.+newtype PriceLine = PriceLine { unPriceLine :: Int }+ deriving (Eq, Show)++-- | The line number on which a posting appears.+newtype PostingLine = PostingLine { unPostingLine :: Int }+ 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)++-- | The postings in each file are numbered in order.+newtype FilePosting =+ FilePosting { unFilePosting :: S.Serial }+ 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)++-- | The transactions in each file are numbered in order.+newtype FileTransaction =+ FileTransaction { unFileTransaction :: S.Serial }+ deriving (Eq, Show)+
− Penny/Lincoln/Bits/Payee.hs
@@ -1,7 +0,0 @@-module Penny.Lincoln.Bits.Payee where--import Penny.Lincoln.TextNonEmpty ( TextNonEmpty )--newtype Payee = Payee { unPayee :: TextNonEmpty }- deriving (Eq, Show, Ord)-
Penny/Lincoln/Bits/Price.hs view
@@ -6,14 +6,13 @@ convert, newPrice) where -import Penny.Lincoln.Bits.Amount (Amount(Amount))-import Penny.Lincoln.Bits.Commodity (Commodity)+import qualified Penny.Lincoln.Bits.Open as O import Penny.Lincoln.Bits.Qty (Qty, mult) -newtype From = From { unFrom :: Commodity }+newtype From = From { unFrom :: O.Commodity } deriving (Eq, Ord, Show) -newtype To = To { unTo :: Commodity }+newtype To = To { unTo :: O.Commodity } deriving (Eq, Ord, Show) newtype CountPerUnit = CountPerUnit { unCountPerUnit :: Qty }@@ -27,12 +26,12 @@ -- | Convert an amount from the From price to the To price. Fails if -- the From commodity in the Price is not the same as the commodity in -- the Amount.-convert :: Price -> Amount -> Maybe Amount-convert p (Amount q c) =+convert :: Price -> O.Amount -> Maybe O.Amount+convert p (O.Amount q c sd sb) = if (unFrom . from $ p) /= c then Nothing else let q' = q `mult` (unCountPerUnit . countPerUnit $ p)- in Just (Amount q' (unTo . to $ p))+ in Just (O.Amount q' (unTo . to $ p) sd sb) -- | Succeeds only if From and To are different commodities. newPrice :: From -> To -> CountPerUnit -> Maybe Price
− Penny/Lincoln/Bits/PricePoint.hs
@@ -1,10 +0,0 @@-module Penny.Lincoln.Bits.PricePoint where--import Penny.Lincoln.Bits.Price (Price)-import Penny.Lincoln.Bits.DateTime (DateTime)-import qualified Penny.Lincoln.Meta as M--data PricePoint = PricePoint { dateTime :: DateTime- , price :: Price- , ppMeta :: M.PriceMeta }- deriving Show
Penny/Lincoln/Bits/Qty.hs view
@@ -2,13 +2,51 @@ -- fractional) of something. It does not have a commodity or a -- Debit/Credit. module Penny.Lincoln.Bits.Qty (- Qty, unQty, partialNewQty,- newQty, add, subt, mult,- zero, Difference(LeftBiggerBy, RightBiggerBy, Equal),- difference) where+ Qty, NumberStr(..), toQty, mantissa, places, newQty,+ Mantissa, Places,+ add, mult, Difference(LeftBiggerBy, RightBiggerBy, Equal),+ equivalent, difference, allocate,+ TotSeats, PartyVotes, SeatsWon, largestRemainderMethod) where -import Data.Decimal ( DecimalRaw ( Decimal ), Decimal )+import qualified Control.Monad.Exception.Synchronous as Ex+import qualified Data.Foldable as F+import Data.List (genericLength, genericReplicate, genericSplitAt, sortBy)+import Data.List.NonEmpty (NonEmpty((:|)))+import Data.Ord (comparing) +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+++-- | 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+ where+ fromWholeRadFrac w f =+ fmap (\m -> Qty m (genericLength f)) (readInteger (w ++ f))++-- | 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+ -- | 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@@ -22,49 +60,238 @@ -- Debit/Credit - maybe Debit/Credit/Zero. Barring the addition of -- that, though, the best way to indicate a situation such as this -- would be through transaction memos.-newtype Qty = Qty Decimal- deriving (Eq, Ord, Show)+--+-- 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+-- want 'equivalent'.+data Qty = Qty { mantissa :: Integer+ , places :: Integer+ } deriving Eq +type Mantissa = Integer+type Places = Integer++newQty :: Mantissa -> Places -> Maybe Qty+newQty m p+ | m > 0 && p >= 0 = Just $ Qty m p+ | otherwise = Nothing++-- | Shows a quantity, nicely formatted after accounting for both the+-- mantissa and decimal places, e.g. @0.232@ or @232.12@ or whatever.+instance Show Qty where+ show (Qty m e) =+ let man = show m+ 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+++instance Ord Qty where+ compare q1 q2 = compare (mantissa q1') (mantissa q2')+ where+ (q1', q2') = equalizeExponents q1 q2++-- | Adjust the exponents on two Qty so they are equivalent+-- before, but now have the same exponent.+equalizeExponents :: Qty -> Qty -> (Qty, Qty)+equalizeExponents x y = (x', y')+ where+ (ex, ey) = (places x, places y)+ (x', y') = case compare ex ey of+ GT -> (x, increaseExponent (ex - ey) y)+ LT -> (increaseExponent (ey - ex) x, y)+ EQ -> (x, y)+++-- | Increase the exponent by the amount given, so that the new Qty is+-- equivalent to the old one. Takes the absolute value of the+-- adjustment argument.+increaseExponent :: Integer -> Qty -> Qty+increaseExponent i (Qty m e) = Qty m' e'+ where+ amt = abs i+ m' = m * 10 ^ amt+ e' = e + amt++-- | Increases the exponent to the given amount. Does nothing if the+-- exponent is already at or higher than this amount.+increaseExponentTo :: Integer -> Qty -> Qty+increaseExponentTo i q@(Qty _ e) =+ let diff = i - e+ in if diff >= 0 then increaseExponent diff q else q++-- | Compares Qty after equalizing their exponents.+equivalent :: Qty -> Qty -> Bool+equivalent x y = x' == y'+ where+ (x', y') = equalizeExponents x y+ data Difference = LeftBiggerBy Qty | RightBiggerBy Qty | Equal+ deriving (Eq, Show) --- | Subtract the second Qty from the first.+-- | Subtract the second Qty from the first, after equalizing their+-- exponents. difference :: Qty -> Qty -> Difference-difference (Qty q1) (Qty q2) = case compare q1 q2 of- GT -> LeftBiggerBy (Qty $ q1 - q2)- LT -> RightBiggerBy (Qty $ q2 - q1)- EQ -> Equal+difference x y =+ let (x', y') = equalizeExponents x y+ (mx, my) = (mantissa x', mantissa y')+ in case compare mx my of+ GT -> LeftBiggerBy (Qty (mx - my) (places x'))+ LT -> RightBiggerBy (Qty (my - mx) (places x'))+ EQ -> Equal --- | Unwrap a Qty to get the underlying Decimal. This Decimal will--- always be greater than zero.-unQty :: Qty -> Decimal-unQty (Qty d) = d+add :: Qty -> Qty -> Qty+add x y =+ let ((Qty xm e), (Qty ym _)) = equalizeExponents x y+ in Qty (xm + ym) e --- | Make a new Qty. This function is partial. It will call error if--- its argument is less than or equal to zero.-partialNewQty :: Decimal -> Qty-partialNewQty d =- if d <= 0- then error- $ "partialNewQty: argument less than or equal to zero: "- ++ show d- else Qty d+mult :: Qty -> Qty -> Qty+mult (Qty xm xe) (Qty ym ye) = Qty (xm * ym) (xe + ye) --- | Make a new Qty. Returns Nothing if its argument is less than--- zero.-newQty :: Decimal -> Maybe Qty-newQty d = if d <= 0 then Nothing else Just (Qty d) -add :: Qty -> Qty -> Qty-add (Qty q1) (Qty q2) = Qty $ q1 + q2+-- | Allocate a Qty proportionally so that the sum of the results adds+-- up to a given Qty. Fails if the allocation cannot be made (e.g. if+-- it is impossible to allocate without overflowing Decimal.) The+-- result will always add up to the given sum.+allocate+ :: Qty+ -- ^ The result will add up to this Qty. -subt :: Qty -> Qty -> Maybe Qty-subt (Qty q1) (Qty q2) = if q2 > q1 then Nothing else Just $ Qty (q1 - q2)+ -> NonEmpty Qty+ -- ^ Allocate using this list of Qty. -mult :: Qty -> Qty -> Qty-mult (Qty q1) (Qty q2) = Qty $ q1 * q2+ -> NonEmpty Qty+ -- ^ The length of this list will be equal to the length of the list+ -- of allocations. Each item will correspond to the original+ -- allocation. -zero :: Qty-zero = Qty $ Decimal 0 0+allocate tot ls =+ let (tot', ls', e') = sameExponent tot ls+ (tI, lsI) = (mantissa tot', fmap mantissa ls')+ (seats, (p1 :| ps), moreE) = growTarget tI lsI+ adjSeats = seats - (genericLength ps + 1)+ del = largestRemainderMethod adjSeats (p1 : ps)+ totE = e' + moreE+ r1:rs = fmap (\m -> Qty (m + 1) totE) del+ in r1 :| rs+++-- | Given a list of Decimals, and a single Decimal, return Decimals+-- that are equivalent to the original Decimals, but where all+-- Decimals have the same exponent. Also returns new exponent.+sameExponent+ :: Qty+ -> NonEmpty Qty+ -> (Qty, NonEmpty Qty, Integer)+sameExponent dec ls =+ let newExp = max (F.maximum . fmap places $ ls)+ (places dec)+ dec' = increaseExponentTo newExp dec+ ls' = fmap (increaseExponentTo newExp) ls+ in (dec', ls', newExp)+++-- | Given an Integer and a list of Integers, multiply all integers by+-- ten raised to an exponent, so that the first Integer is larger than+-- the count of the number of Integers in the list. Returns+-- the new Integer, new list of Integers, and the exponent used.+--+-- Previously this only grew the first Integer so that it was at least+-- as large as the count of Integers in the list, but this causes+-- problems, as there must be at least one seat for the allocation process.+growTarget+ :: Integer+ -> NonEmpty Integer+ -> (Integer, NonEmpty Integer, Integer)+growTarget target is = go target is 0+ where+ len = genericLength . F.toList $ is+ go t xs c =+ let t' = t * 10 ^ c+ xs' = fmap (\x -> x * 10 ^ c) xs+ in if t' > len+ then (t', xs', c)+ else go t' xs' (c + 1)++-- Largest remainder method: votes for one party is divided by+-- (total votes / number of seats). Result is an integer and a+-- remainder. Each party gets the number of seats indicated by its+-- integer. Parties are then ranked on the basis of the remainders, and+-- those with the largest remainders get an additional seat until all+-- seats have been distributed.+type AutoSeats = Integer+type PartyVotes = Integer+type TotVotes = Integer+type TotSeats = Integer+type Remainder = Rational+type SeatsWon = Integer++-- | Allocates integers using the largest remainder method. This is+-- the method used to allocate parliamentary seats in many countries,+-- so the types are named accordingly.+largestRemainderMethod+ :: TotSeats+ -- ^ Total number of seats in the legislature. This is the integer+ -- that will be allocated. This number must be positive or this+ -- function will fail at runtime.++ -> [PartyVotes]+ -- ^ The total seats will be allocated proportionally depending on+ -- how many votes each party received. The sum of this list must be+ -- positive, and each member of the list must be at least zero;+ -- otherwise a runtime error will occur.++ -> [SeatsWon]+ -- ^ The sum of this list will always be equal to the total number+ -- of seats, and its length will always be equal to length of the+ -- PartyVotes list.++largestRemainderMethod ts pvs =+ let err s = error $ "largestRemainderMethod: error: " ++ s+ in Ex.resolve err $ do+ Ex.assert "TotalSeats not positive" (ts > 0)+ Ex.assert "sum of [PartyVotes] not positive" (sum pvs > 0)+ Ex.assert "negative member of [PartyVotes]" (minimum pvs >= 0)+ return (allocRemainder ts . allocAuto ts $ pvs)++autoAndRemainder+ :: TotSeats -> TotVotes -> PartyVotes -> (AutoSeats, Remainder)+autoAndRemainder ts tv pv =+ let fI = fromIntegral+ quota = if ts == 0+ then error "autoAndRemainder: zero total seats"+ else if tv == 0+ then error "autoAndRemainder: zero total votes"+ else fI tv / fI ts+ in properFraction (fI pv / quota)+++allocAuto :: TotSeats -> [PartyVotes] -> [(AutoSeats, Remainder)]+allocAuto ts pvs = map (autoAndRemainder ts (sum pvs)) pvs++allocRemainder+ :: TotSeats+ -> [(AutoSeats, Remainder)]+ -> [SeatsWon]+allocRemainder ts ls =+ let totLeft = ts - (sum . map fst $ ls)+ (leftForEach, stillLeft) = totLeft `divMod` genericLength ls+ wIndex = zip ([0..] :: [Integer]) ls+ sorted = sortBy (comparing (snd . snd)) wIndex+ wOrder = zip [0..] sorted+ awarder (ord, (ix, (as, _))) =+ if ord < stillLeft+ then (ix, as + leftForEach + 1)+ else (ix, as + leftForEach)+ awarded = map awarder wOrder+ in map snd . sortBy (comparing fst) $ awarded
− Penny/Lincoln/Bits/Tags.hs
@@ -1,10 +0,0 @@-module Penny.Lincoln.Bits.Tags where--import Penny.Lincoln.TextNonEmpty (TextNonEmpty)--newtype Tag = Tag { unTag :: TextNonEmpty }- deriving (Eq, Show, Ord)--newtype Tags = Tags { unTags :: [Tag] }- deriving (Eq, Show, Ord)-
Penny/Lincoln/Builders.hs view
@@ -11,52 +11,19 @@ -- can be tedious if, for example, you want to build some data within -- a short custom Haskell program. Thus, this module. -module Penny.Lincoln.Builders ( - crashy- , account- , commodity+module Penny.Lincoln.Builders+ ( account ) where -import Control.Monad.Exception.Synchronous as Ex import qualified Data.List.Split as S-import Data.List.NonEmpty (NonEmpty((:|))) import qualified Penny.Lincoln.Bits as B-import Penny.Lincoln.TextNonEmpty (TextNonEmpty(TextNonEmpty)) import Data.Text (pack)-import qualified Data.Traversable as T --- | Makes a function partial. Use if you don't want to bother dealing--- with the Exceptional type.-crashy :: Show e => Ex.Exceptional e a -> a-crashy = Ex.resolve (error . show)- -- | Create an Account. You supply a single String, with colons to -- separate the different sub-accounts.-account :: String -> Ex.Exceptional String B.Account-account input = do- subStrs <- case S.splitOn ":" input of- [] -> error "splitOn returned an empty list; should never happen"- []:[] -> Ex.throw "account name is null"- (s:ss) -> return $ s :| ss- let makeSub s = case s of- [] -> Ex.throw $- "sub account name is null from account: " ++ input- (c:cs) -> return $ B.SubAccountName (TextNonEmpty c (pack cs))- subs <- T.traverse makeSub subStrs- return $ B.Account subs- --- | Create a Commodity. You supply a single String, with colons to--- separate the different sub-commodities.-commodity :: String -> Ex.Exceptional String B.Commodity-commodity input = do- subStrs <- case S.splitOn ":" input of- [] -> error "splitOn returned an empty list; should never happen"- []:[] -> Ex.throw "account name is null"- (s:ss) -> return $ s :| ss- let makeSub s = case s of- [] -> Ex.throw $- "sub account name is null from account: " ++ input- (c:cs) -> return $ B.SubCommodity (TextNonEmpty c (pack cs))- subs <- T.traverse makeSub subStrs- return $ B.Commodity subs- +account :: String -> B.Account+account s =+ if null s+ then B.Account []+ else B.Account . map B.SubAccount . map pack . S.splitOn ":" $ s+
Penny/Lincoln/Family.hs view
@@ -11,11 +11,11 @@ F.Family(Family), C.Child(Child), S.Siblings(Siblings),- + -- * Mapping families- F.mapChildrenM,+ F.mapChildrenA, F.mapChildren,- F.mapParentM,+ F.mapParentA, F.mapParent, -- * Functions to manipulate families@@ -26,6 +26,8 @@ marry, divorceWith, divorce,+ F.filterChildren,+ F.find, S.collapse ) where import qualified Penny.Lincoln.Family.Family as F@@ -69,7 +71,7 @@ -> F.Family p2 c2 -> F.Family (p1, p2) (c1, c2) marry = marryWith (,) (,)- + -- | Splits up a family. divorceWith :: (p1 -> (p2, p3)) -> (c1 -> (c2, c3))
Penny/Lincoln/Family/Family.hs view
@@ -1,6 +1,8 @@ module Penny.Lincoln.Family.Family where -import qualified Data.Functor.Identity as I+import Control.Applicative ((<$>), (<*>), pure, Applicative)+import qualified Data.List as L+import Data.Traversable (traverse) -- | A Family has one parent (ah, the anomie, sorry) and at least two -- children.@@ -11,40 +13,64 @@ , children :: [c] } deriving (Eq, Show) --- | Maps over all children in a monad, in order starting with child+-- | Maps over all children, in order starting with child -- 1, then child 2, then the children in the list from left to right.-mapChildrenM ::- Monad m+mapChildrenA ::+ Applicative m => (a -> m b) -> Family p a -> m (Family p b)-mapChildrenM f (Family p c1 c2 cs) = do- c1' <- f c1- c2' <- f c2- cs' <- mapM f cs- return $ Family p c1' c2' cs'+mapChildrenA f (Family p c1 c2 cs) =+ Family p <$> f c1 <*> f c2 <*> traverse f cs --- | Maps over all children, in order starting with child--- 1, then child 2, then the children in the list from left to right.+-- | Maps over all children. mapChildren :: (a -> b) -> Family p a -> Family p b-mapChildren f fam = I.runIdentity (mapChildrenM f' fam) where- f' = return . f+mapChildren f (Family p c1 c2 cs) =+ Family p (f c1) (f c2) (map f cs) --- | Maps over the parent in a monad.-mapParentM ::- Monad m++-- | Maps over the parent in an Applicative.+mapParentA ::+ Applicative m => (a -> m b) -> Family a c -> m (Family b c)-mapParentM f (Family p c1 c2 cs) = do- p' <- f p- return $ Family p' c1 c2 cs+mapParentA f (Family p c1 c2 cs) =+ Family <$> f p <*> pure c1 <*> pure c2 <*> pure cs + -- | Maps over the parent. mapParent :: (a -> b) -> Family a c -> Family b c-mapParent f fam = I.runIdentity (mapParentM f' fam) where- f' = return . f+mapParent f (Family p c1 c2 cs) = Family (f p) c1 c2 cs+++-- | Finds the first child matching a predicate.+find :: (p -> c -> Bool) -> Family p c -> Maybe c+find f (Family p c1 c2 cs)+ | f p c1 = Just c1+ | f p c2 = Just c2+ | otherwise = L.find (f p) cs++-- | Filters the children. Fails if there are not at least two+-- children after filtering. Retains the original order of the+-- children (after removing the children you don't want.)+filterChildren :: (a -> Bool) -> Family p a -> Maybe (Family p a)+filterChildren f (Family p c1 c2 cs) =+ case (f c1, f c2) of+ (True, True) -> Just (Family p c1 c2 (filter f cs))+ (True, False) ->+ case filter f cs of+ [] -> Nothing+ x:xs -> Just (Family p c1 x xs)+ (False, True) ->+ case filter f cs of+ [] -> Nothing+ x:xs -> Just (Family p c2 x xs)+ (False, False) ->+ case filter f cs of+ x1:x2:xs -> Just (Family p x1 x2 xs)+ _ -> Nothing
Penny/Lincoln/HasText.hs view
@@ -1,13 +1,8 @@ module Penny.Lincoln.HasText where -import Data.Foldable (toList)-import Data.List (intersperse)-import Data.List.NonEmpty (NonEmpty)-import Data.Text (Text, cons)-import qualified Data.Text as X+import Data.Text (Text) import qualified Penny.Lincoln.Bits as B-import Penny.Lincoln.TextNonEmpty (TextNonEmpty(TextNonEmpty)) class HasText a where text :: a -> Text@@ -15,91 +10,32 @@ instance HasText Text where text = id -instance HasText TextNonEmpty where- text (TextNonEmpty f r) = f `cons` r--instance HasText B.SubAccountName where- text = text . B.unSubAccountName--instance HasText B.SubCommodity where- text = text . B.unSubCommodity+instance HasText B.SubAccount where+ text = B.unSubAccount instance HasText B.Flag where- text = text . B.unFlag+ text = B.unFlag -instance HasText B.MemoLine where- text = text . B.unMemoLine+instance HasText B.Commodity where+ text = B.unCommodity instance HasText B.Number where- text = text . B.unNumber+ text = B.unNumber instance HasText B.Payee where- text = text . B.unPayee+ text = B.unPayee instance HasText B.Tag where- text = text . B.unTag- --- | Applying 'text' to a Delimited type will give you a single Text--- with the delimiter interspersed between the values of the list.-data Delimited a = Delimited Text [a]- deriving Show--instance HasText a => HasText (Delimited a) where- text (Delimited sep ts) = X.concat . intersperse sep . map text $ ts+ text = B.unTag class HasTextList a where textList :: a -> [Text] -instance HasText a => HasTextList (NonEmpty a) where- textList = map text . toList- instance HasTextList B.Account where- textList = textList . B.unAccount--instance HasTextList B.Commodity where- textList = textList . B.unCommodity+ textList = map text . B.unAccount instance HasTextList B.Tags where textList = map text . B.unTags instance HasTextList B.Memo where- textList = map text . B.unMemo--instance HasText a => HasTextList [a] where- textList = map text--class HasTextNonEmpty a where- textNonEmpty :: a -> TextNonEmpty--instance HasTextNonEmpty TextNonEmpty where- textNonEmpty = id--instance HasTextNonEmpty B.SubAccountName where- textNonEmpty = B.unSubAccountName--instance HasTextNonEmpty B.SubCommodity where- textNonEmpty = B.unSubCommodity--instance HasTextNonEmpty B.Flag where- textNonEmpty = B.unFlag- -instance HasTextNonEmpty B.Number where- textNonEmpty = B.unNumber--instance HasTextNonEmpty B.Payee where- textNonEmpty = B.unPayee--instance HasTextNonEmpty B.Tag where- textNonEmpty = B.unTag--class HasTextNonEmptyList a where- textNonEmptyList :: a -> NonEmpty TextNonEmpty--instance HasTextNonEmpty a => HasTextNonEmptyList (NonEmpty a) where- textNonEmptyList = fmap textNonEmpty--instance HasTextNonEmptyList B.Account where- textNonEmptyList = fmap textNonEmpty . B.unAccount--instance HasTextNonEmptyList B.Commodity where- textNonEmptyList = fmap textNonEmpty . B.unCommodity+ textList = B.unMemo
Penny/Lincoln/Matchers.hs view
@@ -9,10 +9,10 @@ type Matcher = X.Text -> Bool -- | A function that makes Matchers.-type Factory =- MT.CaseSensitive+type Factory+ = MT.CaseSensitive -- ^ Will this matcher be case sensitive?- + -> X.Text -- ^ The pattern to use when testing for a match. For example, this -- might be a regular expression, or simply the text to be matched.
− Penny/Lincoln/Meta.hs
@@ -1,71 +0,0 @@-module Penny.Lincoln.Meta where--import qualified Penny.Lincoln.Serial as S-import qualified Data.Text as X--newtype TopLineLine = TopLineLine { unTopLineLine :: Int }- deriving (Eq, Show)--newtype TopMemoLine = TopMemoLine { unTopMemoLine :: Int }- deriving (Eq, Show)--data Side = CommodityOnLeft | CommodityOnRight deriving (Eq, Show)-data SpaceBetween = SpaceBetween | NoSpaceBetween deriving (Eq, Show)--data Format =- Format { side :: Side- , between :: SpaceBetween }- deriving (Eq, Show)--newtype Filename = Filename { unFilename :: X.Text }- deriving (Eq, Show)--newtype PriceLine = PriceLine { unPriceLine :: Int }- deriving (Eq, Show)--newtype PostingLine = PostingLine { unPostingLine :: Int }- deriving (Eq, Show)--data PriceMeta =- PriceMeta { priceLine :: Maybe PriceLine- , priceFormat :: Maybe Format }- deriving (Eq, Show)--newtype GlobalPosting =- GlobalPosting { unGlobalPosting :: S.Serial }- deriving (Eq, Show)--newtype FilePosting =- FilePosting { unFilePosting :: S.Serial }- deriving (Eq, Show)--emptyPostingMeta :: PostingMeta-emptyPostingMeta = PostingMeta Nothing Nothing Nothing Nothing--data PostingMeta =- PostingMeta { postingLine :: Maybe PostingLine- , postingFormat :: Maybe Format- , globalPosting :: Maybe GlobalPosting- , filePosting :: Maybe FilePosting }- deriving (Eq, Show)--newtype GlobalTransaction =- GlobalTransaction { unGlobalTransaction :: S.Serial }- deriving (Eq, Show)--newtype FileTransaction =- FileTransaction { unFileTransaction :: S.Serial }- deriving (Eq, Show)---emptyTopLineMeta :: TopLineMeta-emptyTopLineMeta = TopLineMeta Nothing Nothing Nothing Nothing Nothing--data TopLineMeta =- TopLineMeta { topMemoLine :: Maybe TopMemoLine- , topLineLine :: Maybe TopLineLine- , filename :: Maybe Filename- , globalTransaction :: Maybe GlobalTransaction- , fileTransaction :: Maybe FileTransaction }- deriving (Eq, Show)-
Penny/Lincoln/NestedMap.hs view
@@ -4,20 +4,21 @@ -- may query the map at any level or insert new labels (and, -- therefore, new keys) at any level. module Penny.Lincoln.NestedMap (- NestedMap ( NestedMap ),- unNestedMap,+ NestedMap ( NestedMap, unNestedMap ), empty, relabel, descend, insert, cumulativeTotal, traverse,- traverseWithTrail ) where+ traverseWithTrail,+ toForest ) where import Control.Applicative ((<*>), (<$>)) import Data.Map ( Map ) import qualified Data.Foldable as F import qualified Data.Traversable as T+import qualified Data.Tree as E import qualified Data.Map as M import Data.Monoid ( Monoid, mconcat, mappend, mempty ) @@ -41,7 +42,7 @@ traverse f (NestedMap m) = let f' (l, m') = (,) <$> f l <*> T.traverse f m' in NestedMap <$> T.traverse f' m- + -- | An empty NestedMap. empty :: NestedMap k l empty = NestedMap (M.empty)@@ -122,7 +123,7 @@ newL mk = case mk of (Just old) -> old `mappend` l Nothing -> mempty `mappend` l- + totalMap :: (Monoid l) => NestedMap k l@@ -230,6 +231,12 @@ (Just a) -> do m' <- traverseWithTrail' f ((k, l):ls) m return (Just (a, m'))++-- | Convert a NestedMap to a Forest.+toForest :: Ord k => NestedMap k l -> E.Forest (k, l)+toForest = map toNode . M.assocs . unNestedMap+ where+ toNode (k, (l, m)) = E.Node (k, l) (toForest m) -- For testing _new :: (k, l) -> (k, (Maybe l -> l))
Penny/Lincoln/Predicates.hs view
@@ -1,15 +1,19 @@ -- | Functions that return a boolean based upon some criterion that--- matches something, often a PostingBox. Useful when filtering+-- matches something, often a PostFam. Useful when filtering -- Postings. module Penny.Lincoln.Predicates where -import Data.Text (Text, singleton) +import Data.List (intersperse)+import Data.Text (Text)+import qualified Data.Text as X+import Data.Time (Day) import qualified Penny.Lincoln.Bits as B-import Penny.Lincoln.HasText (HasText, text, HasTextList, textList,- Delimited(Delimited))+import Penny.Lincoln.HasText (HasText, text, HasTextList, textList)+import qualified Penny.Lincoln.Family.Family as F import qualified Penny.Lincoln.Queries as Q import Penny.Lincoln.Transaction (PostFam)+import qualified Penny.Lincoln.Transaction as T -- * Matching helpers @@ -17,39 +21,42 @@ match f = f . text matchMaybe :: HasText a => (Text -> Bool) -> Maybe a -> Bool-matchMaybe f a = case a of- Nothing -> False- (Just t) -> match f t+matchMaybe f = maybe False (match f) ++-- | Does the given matcher match any of the elements of the Texts in+-- a HasTextList? matchAny :: HasTextList a => (Text -> Bool) -> a -> Bool matchAny f = any f . textList matchAnyMaybe :: HasTextList a => (Text -> Bool) -> Maybe a -> Bool-matchAnyMaybe f ma = case ma of- Nothing -> False- (Just ls) -> matchAny f ls+matchAnyMaybe f = maybe False (matchAny f) ++-- | Does the given matcher match the text that is at the given+-- element of a HasTextList? If the HasTextList does not have a+-- sufficent number of elements to perform this test, returns False. matchLevel :: HasTextList a => Int -> (Text -> Bool) -> a -> Bool matchLevel i f a = let ts = textList a in if i < 0 || i >= length ts then False else f (ts !! i) -matchMemo :: (Text -> Bool) -> B.Memo -> Bool-matchMemo f m = f m' where- m' = text $ Delimited (singleton ' ') (textList m)- +-- | Does the matcher match the text of the memo? Joins each line of+-- the memo with a space.+matchMemo :: (Text -> Bool) -> Maybe B.Memo -> Bool+matchMemo f = maybe False (f . X.intercalate (X.singleton ' ') . B.unMemo) + matchMaybeLevel :: HasTextList a => Int -> (Text -> Bool) -> Maybe a -> Bool-matchMaybeLevel i f ma = case ma of- Nothing -> False- (Just l) -> matchLevel i f l+matchMaybeLevel i f = maybe False (matchLevel i f) + -- * Pattern matching fields payee :: (Text -> Bool) -> PostFam -> Bool@@ -76,6 +83,12 @@ date f c = f (Q.dateTime c) +localDay ::+ (Day -> Bool)+ -> PostFam+ -> Bool+localDay f = f . Q.localDay+ -- * Qty qty ::@@ -102,27 +115,17 @@ => Text -> (Text -> Bool) -> a -> Bool-matchDelimited d f = f . text . Delimited d . textList+matchDelimited d f = f . X.concat . intersperse d . textList -- * Commodity -commodity :: Text -> (Text -> Bool) -> PostFam -> Bool-commodity t f = matchDelimited t f- . B.unCommodity- . Q.commodity--commodityLevel :: Int -> (Text -> Bool) -> PostFam -> Bool-commodityLevel i f = matchLevel i f . Q.commodity--commodityAny :: (Text -> Bool) -> PostFam -> Bool-commodityAny f = matchAny f . Q.commodity+commodity :: (Text -> Bool) -> PostFam -> Bool+commodity f = f . text . Q.commodity -- * Account account :: Text -> (Text -> Bool) -> PostFam -> Bool-account t f = matchDelimited t f- . B.unAccount- . Q.account+account t f = matchDelimited t f . Q.account accountLevel :: Int -> (Text -> Bool) -> PostFam -> Bool accountLevel i f = matchLevel i f . Q.account@@ -134,3 +137,59 @@ tag :: (Text -> Bool) -> PostFam -> Bool tag f = matchAny f . Q.tags +-- * Combining predicates+(&&&) :: (a -> Bool) -> (a -> Bool) -> (a -> Bool)+(&&&) l r = \a -> l a && r a++infixr 3 &&&++(|||) :: (a -> Bool) -> (a -> Bool) -> (a -> Bool)+(|||) l r = \a -> l a || r a++infixr 2 |||+++-- * Clones++-- | Returns True if these two transactions are clones; that is, if+-- they are identical in all respects except some aspects of their+-- metadata. The metadata that is disregarded when testing for clones+-- pertains to the location of the transaction. (Resembles cloned+-- sheep, which are identical but cannot be in exactly the same+-- place.)+clonedTransactions :: T.Transaction -> T.Transaction -> Bool+clonedTransactions a b =+ let (F.Family ta p1a p2a psa) = T.unTransaction a+ (F.Family tb p1b p2b psb) = T.unTransaction b+ in clonedTopLines ta tb+ && clonedPostings p1a p1b+ && clonedPostings p2a p2b+ && (length psa == length psb)+ && (and (zipWith clonedPostings psa psb))++-- | Returns True if two TopLines are clones. Considers only the+-- non-metadata aspects of the TopLine; the metadata all pertains only+-- to the location of the TopLine. The DateTimes are compared based on+-- both the local time and the time zone; that is, two times that+-- refer to the same instant will not compare as identical if they+-- have different time zones.+clonedTopLines :: T.TopLine -> T.TopLine -> Bool+clonedTopLines t1 t2 =+ (T.tDateTime t1 == T.tDateTime t2)+ && (T.tFlag t1 == T.tFlag t2)+ && (T.tNumber t1 == T.tNumber t2)+ && (T.tPayee t2 == T.tPayee t2)+ && (T.tMemo t1 == T.tMemo t2)++-- | Returns True if two Postings are clones. Considers only the+-- non-location-related aspects of the posting metadata.+clonedPostings :: T.Posting -> T.Posting -> Bool+clonedPostings p1 p2 =+ (T.pPayee p1 == T.pPayee p2)+ && (T.pNumber p1 == T.pNumber p2)+ && (T.pFlag p1 == T.pFlag p2)+ && (T.pAccount p1 == T.pAccount p2)+ && (T.pTags p1 == T.pTags p2)+ && (T.pEntry p1 == T.pEntry p2)+ && (T.pMemo p1 == T.pMemo p2)+ && (T.pInferred p1 == T.pInferred p2)
Penny/Lincoln/PriceDb.hs view
@@ -13,10 +13,8 @@ ) where import qualified Control.Monad.Exception.Synchronous as Ex-import qualified Data.Foldable as Fdbl import qualified Data.Map as M import qualified Data.Time as T-import qualified Penny.Lincoln.NestedMap as NM import qualified Penny.Lincoln.Bits as B type CpuMap = M.Map T.UTCTime B.CountPerUnit@@ -24,79 +22,43 @@ -- | The PriceDb holds information about prices. Create an empty one -- using 'emptyDb' then fill it with values using foldl or similar.-newtype PriceDb = PriceDb (NM.NestedMap B.SubCommodity ToMap)+newtype PriceDb = PriceDb (M.Map B.From ToMap) -- | An empty PriceDb emptyDb :: PriceDb-emptyDb = PriceDb NM.empty+emptyDb = PriceDb M.empty -- | Add a single price to the PriceDb. addPrice :: PriceDb -> B.PricePoint -> PriceDb-addPrice (PriceDb db) pp@(B.PricePoint _ pr _) =- PriceDb $ NM.relabel db ls where- ls = firsts ++ [lst]- cmdtyList = Fdbl.toList . B.unCommodity . B.unFrom . B.from $ pr- firsts = map toFst (init cmdtyList) where- toFst cty = (cty, f)- f maybeL = case maybeL of- Nothing -> M.empty- Just m -> m- lst = (last cmdtyList, insertIntoToMap pp)+addPrice (PriceDb db) (B.PricePoint dt pr _ _ _) = PriceDb m'+ where+ m' = M.alter f (B.from pr) db+ utc = B.toUTC dt+ cpu = B.countPerUnit pr+ f k = case k of+ Nothing -> Just $ M.singleton (B.to pr) cpuMap+ where+ cpuMap = M.singleton utc cpu+ Just tm -> Just tm'+ where+ tm' = M.alter g (B.to pr) tm+ g maybeTo = case maybeTo of+ Nothing -> Just $ M.singleton utc cpu+ Just cpuMap -> Just $ M.insert utc cpu cpuMap --- | Returns a function to use when inserting a new value into the--- ToMap label of a PriceDb.-insertIntoToMap ::- B.PricePoint- -> Maybe ToMap- -> ToMap-insertIntoToMap (B.PricePoint dt pr _) =- let toCmdty = B.to pr- newToMap oldMap = M.alter alterTo toCmdty oldMap- alterTo mayCpuMap =- let newKey = dateTimeToUTC dt- newVal = B.countPerUnit pr- in Just $ case mayCpuMap of- Nothing -> M.singleton newKey newVal- Just m -> M.insert newKey newVal m- relabeler maybeToMap =- newToMap (maybe M.empty id maybeToMap)- in relabeler -dateTimeToUTC :: B.DateTime -> T.UTCTime-dateTimeToUTC dt = T.localTimeToUTC tz lt where- tz = T.minutesToTimeZone . B.offsetToMins $ tzo- lt = B.localTime dt- tzo = B.timeZone dt-- -- | Getting prices can fail; if it fails, an Error is returned. data PriceDbError = FromNotFound | ToNotFound | CpuNotFound -- | Looks up values from the PriceDb. Throws "Error" if something -- fails. ----- First, tries to find the best possible From match. For example, if--- From is LUV:2001, first tries to see if there is a From match for--- LUV:2001. If there is not an exact match for LUV:2001 but there--- is a match for LUV, then LUV is used. If there is not a match--- for either LUV:2001 or for LUV, then FromNotFound is thrown.------ The To commodity must match exactly. So, if the TO commodity is--- LUV:2001, only LUV:2001 will do. If the To commodity is not--- found, ToNotFound is thrown.--- -- The DateTime is the time at which to find a price. If a price -- exists for that exact DateTime, that price is returned. If no price -- exists for that exact DateTime, but there is a price for an earlier -- DateTime, the latest possible price is returned. If there are no -- earlier prices, CpuNotFound is thrown.------ There is no backtracking on earlier decisions. For example, if From--- is LUV:2001, and there is indeed at least one From price in the--- PriceDb and CpuNotFound occurs, getPrice does not check to see if--- the computation would have succeeded had it used LUV rather than--- LUV:2001. getPrice :: PriceDb@@ -105,23 +67,18 @@ -> B.DateTime -> Ex.Exceptional PriceDbError B.CountPerUnit getPrice (PriceDb db) fr to dt = do- let utc = dateTimeToUTC dt- subs = Fdbl.toList . B.unCommodity . B.unFrom $ fr- toMap <- case NM.descend subs db of- [] -> Ex.throw FromNotFound- xs -> return . snd . last $ xs- cpuMap <- case M.lookup to toMap of- Nothing -> Ex.throw ToNotFound- Just m -> return m+ let utc = B.toUTC dt+ toMap <- Ex.fromMaybe FromNotFound $ M.lookup fr db+ cpuMap <- Ex.fromMaybe ToNotFound $ M.lookup to toMap let (lower, exact, _) = M.splitLookup utc cpuMap- cpu <- case exact of+ case exact of Just c -> return c Nothing -> if M.null lower then Ex.throw CpuNotFound else return . snd . M.findMax $ lower- return cpu + -- | Given an Amount and a Commodity to convert the amount to, -- converts the Amount to the given commodity. If the Amount given is -- already in the To commodity, simply returns what was passed in. Can@@ -132,8 +89,10 @@ -> B.DateTime -> B.To -> B.Amount- -> Ex.Exceptional PriceDbError B.Amount-convert db dt to (B.Amount qt fr) = do- cpu <- fmap B.unCountPerUnit (getPrice db (B.From fr) to dt)- let qt' = B.mult cpu qt- return $ B.Amount qt' (B.unTo to)+ -> Ex.Exceptional PriceDbError B.Qty+convert db dt to (B.Amount qt fr _ _)+ | fr == B.unTo to = return qt+ | otherwise = do+ cpu <- fmap B.unCountPerUnit (getPrice db (B.From fr) to dt)+ let qt' = B.mult cpu qt+ return qt'
Penny/Lincoln/Queries.hs view
@@ -1,10 +1,10 @@ module Penny.Lincoln.Queries where import qualified Penny.Lincoln.Bits as B-import qualified Penny.Lincoln.Meta as M import Penny.Lincoln.Family.Child (child, parent) import qualified Penny.Lincoln.Transaction as T import Penny.Lincoln.Balance (Balance, entryToBalance)+import qualified Data.Time as Time best :: (T.Posting -> Maybe a)@@ -25,15 +25,18 @@ flag :: T.PostFam -> Maybe B.Flag flag = best T.pFlag T.tFlag -postingMemo :: T.PostFam -> B.Memo+postingMemo :: T.PostFam -> Maybe B.Memo postingMemo = T.pMemo . child . T.unPostFam -transactionMemo :: T.PostFam -> B.Memo+transactionMemo :: T.PostFam -> Maybe B.Memo transactionMemo = T.tMemo . parent . T.unPostFam dateTime :: T.PostFam -> B.DateTime dateTime = T.tDateTime . parent . T.unPostFam +localDay :: T.PostFam -> Time.Day+localDay = B.day . dateTime+ account :: T.PostFam -> B.Account account = T.pAccount . child . T.unPostFam @@ -58,35 +61,32 @@ commodity :: T.PostFam -> B.Commodity commodity = B.commodity . amount -postingMeta :: T.PostFam -> M.PostingMeta-postingMeta = T.pMeta . child . T.unPostFam--topLineMeta :: T.PostFam -> M.TopLineMeta-topLineMeta = T.tMeta . parent . T.unPostFam+topMemoLine :: T.PostFam -> Maybe B.TopMemoLine+topMemoLine = T.tTopMemoLine . parent . T.unPostFam -topMemoLine :: T.PostFam -> Maybe M.TopMemoLine-topMemoLine = M.topMemoLine . topLineMeta+topLineLine :: T.PostFam -> Maybe B.TopLineLine+topLineLine = T.tTopLineLine . parent . T.unPostFam -topLineLine :: T.PostFam -> Maybe M.TopLineLine-topLineLine = M.topLineLine . topLineMeta+filename :: T.PostFam -> Maybe B.Filename+filename = T.tFilename . parent . T.unPostFam -filename :: T.PostFam -> Maybe M.Filename-filename = M.filename . topLineMeta+globalTransaction :: T.PostFam -> Maybe B.GlobalTransaction+globalTransaction = T.tGlobalTransaction . parent . T.unPostFam -globalTransaction :: T.PostFam -> Maybe M.GlobalTransaction-globalTransaction = M.globalTransaction . topLineMeta+fileTransaction :: T.PostFam -> Maybe B.FileTransaction+fileTransaction = T.tFileTransaction . parent . T.unPostFam -fileTransaction :: T.PostFam -> Maybe M.FileTransaction-fileTransaction = M.fileTransaction . topLineMeta+postingLine :: T.PostFam -> Maybe B.PostingLine+postingLine = T.pPostingLine . child . T.unPostFam -postingLine :: T.PostFam -> Maybe M.PostingLine-postingLine = M.postingLine . postingMeta+side :: T.PostFam -> Maybe B.Side+side = B.side . amount -postingFormat :: T.PostFam -> Maybe M.Format-postingFormat = M.postingFormat . postingMeta+spaceBetween :: T.PostFam -> Maybe B.SpaceBetween+spaceBetween = B.spaceBetween . amount -globalPosting :: T.PostFam -> Maybe M.GlobalPosting-globalPosting = M.globalPosting . postingMeta+globalPosting :: T.PostFam -> Maybe B.GlobalPosting+globalPosting = T.pGlobalPosting . child . T.unPostFam -filePosting :: T.PostFam -> Maybe M.FilePosting-filePosting = M.filePosting . postingMeta+filePosting :: T.PostFam -> Maybe B.FilePosting+filePosting = T.pFilePosting . child . T.unPostFam
Penny/Lincoln/Serial.hs view
@@ -1,105 +1,58 @@ module Penny.Lincoln.Serial (- Serial, forward, backward, serials,- serialItems, serialItemsM,- serialChildrenInFamily, serialEithers,- NextFwd, NextBack, initNexts) where--import Control.Monad (zipWithM)-import Control.Monad.Trans.Class (lift)-import qualified Data.Either as E-import qualified Penny.Lincoln.Family as F-import qualified Control.Monad.Trans.State as St---- | A type for serial numbers, used widely for different purposes--- throughout Penny.--data Serial = Serial {- -- | Numbered from first to last, beginning at zero.- forward :: !Int- - -- | Numbered from last to first, ending at zero.- , backward :: !Int+ Serial, forward, backward, GenSerial,+ incrementBack, getSerial, makeSerials, serialItems,+ nSerials ) where - } deriving (Eq, Show)- +import Control.Applicative (Applicative, (<*>), pure, (*>))+import Control.Monad (ap) --- | Label a list of items with serials.-serialItems :: (Serial -> a -> b)- -> [a]- -> [b]-serialItems f as =- let ss = serials as- in zipWith f ss as+data SerialSt = SerialSt+ { nextFwd :: Int+ , nextBack :: Int+ } deriving Show --- | Label a list of items with serials, in a monad.-serialItemsM ::- Monad m- => (Serial -> a -> m b)- -> [a]- -> m [b]-serialItemsM f as =- let ss = serials as- in zipWithM f ss as+data Serial = Serial+ { forward :: Int+ , backward :: Int+ } deriving (Eq, Show, Ord) --- | Applied to a list of items, return a list of Serials usable to--- identify the list of items.-serials :: [a] -> [Serial]-serials as = zipWith Serial fs rs where- len = length as- fs = take len . iterate succ $ 0- rs = take len . iterate pred $ (len - 1)+newtype GenSerial a = GenSerial (SerialSt -> (a, SerialSt)) +instance Functor GenSerial where+ fmap f (GenSerial k) = GenSerial $ \s ->+ let (a', st') = k s+ in (f a', st') -serialChildrenInFamily ::- (Serial -> cOld -> cNew)- -> F.Family p cOld- -> St.State (NextFwd, NextBack) (F.Family p cNew)-serialChildrenInFamily f = F.mapChildrenM (serialItemSt f)+instance Applicative GenSerial where+ pure = return+ (<*>) = ap -newtype NextFwd = NextFwd Int deriving Show-newtype NextBack = NextBack Int deriving Show+instance Monad GenSerial where+ return a = GenSerial $ \s -> (a, s)+ (GenSerial k) >>= f = GenSerial $ \s ->+ let (a, s') = k s+ GenSerial g = f a+ in g s' -serialItemSt ::- (Serial -> cOld -> cNew)- -> cOld- -> St.State (NextFwd, NextBack) cNew-serialItemSt f old = do- (NextFwd fwd, NextBack bak) <- St.get- let s = Serial fwd bak- St.put (NextFwd $ succ fwd, NextBack $ pred bak)- return (f s old)+incrementBack :: GenSerial ()+incrementBack = GenSerial $ \s ->+ let s' = SerialSt (nextFwd s) (nextBack s + 1)+ in ((), s') -newtype Index = Index Int deriving (Eq, Ord, Show)-newtype Total = Total Int deriving (Eq, Ord, Show)+getSerial :: GenSerial Serial+getSerial = GenSerial $ \s ->+ let s' = SerialSt (nextFwd s + 1) (nextBack s - 1)+ in (Serial (nextFwd s) (nextBack s), s') -serialEithers ::- Monad m- => (Serial -> a -> m c)- -> (Serial -> b -> m d)- -> [Either a b]- -> m [Either c d]-serialEithers fa fb ls =- let allA = E.lefts ls- allB = E.rights ls- totA = Total . length $ allA- totB = Total . length $ allB- initState = (0 :: Int, 0 :: Int)- k e = do- (nextA, nextB) <- St.get- case e of- Left a -> do- c <- lift $ fa (serial totA (Index nextA)) a- St.put (succ nextA, nextB)- return $ Left c- Right b -> do- d <- lift $ fb (serial totB (Index nextB)) b- St.put (nextA, succ nextB)- return $ Right d- in St.evalStateT (mapM k ls) initState+makeSerials :: GenSerial a -> a+makeSerials (GenSerial k) =+ let (r, _) = k (SerialSt 0 0) in r -serial :: Total -> Index -> Serial-serial (Total t) (Index i) = Serial i b where- b = t - i - 1+serialItems :: (Serial -> a -> b) -> [a] -> [b]+serialItems f as = zipWith f (nSerials (length as)) as -initNexts :: Int -> (NextFwd, NextBack)-initNexts i = (NextFwd 0, NextBack $ i - 1)+nSerials :: Int -> [Serial]+nSerials n =+ makeSerials $+ (sequence . replicate n $ incrementBack)+ *> (sequence . replicate n $ getSerial)
− Penny/Lincoln/TextNonEmpty.hs
@@ -1,25 +0,0 @@-module Penny.Lincoln.TextNonEmpty (- TextNonEmpty (TextNonEmpty, first, rest),- unsafeTextNonEmpty,- any, all,- toText) where--import Prelude hiding (any, all)-import Data.Text ( Text, pack, cons )-import qualified Data.Text as X--data TextNonEmpty = TextNonEmpty { first :: Char- , rest :: Text }- deriving (Eq, Ord, Show)--unsafeTextNonEmpty :: String -> TextNonEmpty-unsafeTextNonEmpty s = TextNonEmpty (head s) (pack . tail $ s)--any :: (Char -> Bool) -> TextNonEmpty -> Bool-any f (TextNonEmpty c ts) = f c || X.any f ts--all :: (Char -> Bool) -> TextNonEmpty -> Bool-all f (TextNonEmpty c ts) = f c && X.all f ts--toText :: TextNonEmpty -> Text-toText (TextNonEmpty t ts) = t `cons` ts
Penny/Lincoln/Transaction.hs view
@@ -19,50 +19,62 @@ -- posting nor the transaction has a flag. The functions in -- "Penny.Lincoln.Queries" do that. module Penny.Lincoln.Transaction (- + -- * Postings and transactions Posting, Transaction,- PostFam (unPostFam),+ PostFam,+ unPostFam, - -- * Making transactions+ -- * Making and deconstructing transactions transaction,+ RTransaction(..),+ rTransaction, Error ( UnbalancedError, CouldNotInferError),- + toUnverified,+ -- * Querying postings Inferred(Inferred, NotInferred), pPayee, pNumber, pFlag, pAccount, pTags,- pEntry, pMemo, pInferred, pMeta, changePostingMeta,+ pEntry, pMemo, pInferred, pPostingLine,+ pGlobalPosting, pFilePosting, -- * Querying transactions TopLine,- tDateTime, tFlag, tNumber, tPayee, tMemo, tMeta,- unTransaction, postFam, changeTransactionMeta,- - -- * Adding serials- addSerialsToList, addSerialsToEithers,- + tDateTime, tFlag, tNumber, tPayee, tMemo, tTopLineLine,+ tTopMemoLine, tFilename, tGlobalTransaction, tFileTransaction,+ unTransaction, postFam,+ -- * Box- Box ( Box, boxMeta, boxPostFam )- + Box ( Box, boxMeta, boxPostFam ),++ -- * Changers++ -- | Functions allowing you to change aspects of an existing+ -- transaction, without having to destroy and completely rebuild the+ -- transaction. You cannot change the Entry or any of its+ -- components, as changing any of these would unbalance the+ -- Transaction.+ TopLineChangeData(..),+ emptyTopLineChangeData,+ PostingChangeData(..),+ emptyPostingChangeData,+ changeTransaction ) where import qualified Penny.Lincoln.Bits as B import Penny.Lincoln.Family ( children, orphans, adopt )-import qualified Penny.Lincoln.Meta as M import qualified Penny.Lincoln.Family.Family as F import qualified Penny.Lincoln.Family.Child as C import qualified Penny.Lincoln.Family.Siblings as S import qualified Penny.Lincoln.Transaction.Unverified as U import qualified Penny.Lincoln.Balance as Bal-import qualified Penny.Lincoln.Serial as Ser import Control.Monad.Exception.Synchronous ( Exceptional (Exception, Success) , throw ) import qualified Control.Monad.Exception.Synchronous as Ex-import qualified Data.Either as E import qualified Data.Foldable as Fdbl-import Data.Maybe ( catMaybes )+import Data.Maybe ( catMaybes, fromMaybe ) import qualified Data.Traversable as Tr import qualified Control.Monad.Trans.State.Lazy as St import Control.Monad.Trans.Class ( lift )@@ -74,15 +86,18 @@ -- | Each Transaction consists of at least two Postings. data Posting =- Posting { pPayee :: (Maybe B.Payee)- , pNumber :: (Maybe B.Number)- , pFlag :: (Maybe B.Flag)- , pAccount :: B.Account- , pTags :: B.Tags- , pEntry :: B.Entry- , pMemo :: B.Memo+ Posting { pPayee :: Maybe B.Payee+ , pNumber :: Maybe B.Number+ , pFlag :: Maybe B.Flag+ , pAccount :: B.Account+ , pTags :: B.Tags+ , pEntry :: B.Entry+ , pMemo :: Maybe B.Memo , pInferred :: Inferred- , pMeta :: M.PostingMeta }+ , pPostingLine :: Maybe B.PostingLine+ , pGlobalPosting :: Maybe B.GlobalPosting+ , pFilePosting :: Maybe B.FilePosting+ } deriving (Eq, Show) -- | The TopLine holds information that applies to all the postings in@@ -90,11 +105,15 @@ -- appears on the top line.) data TopLine = TopLine { tDateTime :: B.DateTime- , tFlag :: (Maybe B.Flag)- , tNumber :: (Maybe B.Number)- , tPayee :: (Maybe B.Payee)- , tMemo :: B.Memo- , tMeta :: M.TopLineMeta }+ , tFlag :: Maybe B.Flag+ , tNumber :: Maybe B.Number+ , tPayee :: Maybe B.Payee+ , tMemo :: Maybe B.Memo+ , tTopLineLine :: Maybe B.TopLineLine+ , tTopMemoLine :: Maybe B.TopMemoLine+ , tFilename :: Maybe B.Filename+ , tGlobalTransaction :: Maybe B.GlobalTransaction+ , tFileTransaction :: Maybe B.FileTransaction } deriving (Eq, Show) -- | All the Postings in a Transaction must produce a Total whose@@ -103,7 +122,7 @@ newtype Transaction = Transaction { unTransaction :: F.Family TopLine Posting } deriving (Eq, Show)- + -- | Errors that can arise when making a Transaction. data Error = UnbalancedError | CouldNotInferError@@ -127,6 +146,13 @@ -} +-- | Deconstruct a Transaction to a family of unverified data.+toUnverified :: Transaction -> F.Family U.TopLine U.Posting+toUnverified = F.mapParent fp . F.mapChildren fc . unTransaction+ where+ fp tl = toUTopLine tl+ fc p = toUPosting p+ -- | Makes transactions. transaction :: F.Family U.TopLine U.Posting@@ -144,14 +170,14 @@ Fdbl.foldr1 Bal.addBalances . catMaybes . Fdbl.toList- . fmap (fmap Bal.entryToBalance . U.entry)+ . fmap (fmap Bal.entryToBalance . U.pEntry) infer :: U.Posting -> Ex.ExceptionalT Error (St.State (Maybe B.Entry)) Posting infer po =- case U.entry po of+ case U.pEntry po of Nothing -> do st <- lift St.get case st of@@ -160,7 +186,7 @@ lift $ St.put Nothing return $ toPosting po e Inferred (Just e) -> return $ toPosting po e NotInferred- + runInfer :: Maybe B.Entry -> S.Siblings U.Posting@@ -170,7 +196,7 @@ ext = Ex.runExceptionalT (Tr.mapM infer pos) case finalSt of (Just _) -> throw UnbalancedError- Nothing -> case res of + Nothing -> case res of (Exception e) -> throw e (Success g) -> return g @@ -185,86 +211,153 @@ Bal.NotInferable -> throw UnbalancedError runInfer en pos +toUPosting :: Posting -> U.Posting+toUPosting p = U.Posting+ { U.pPayee = pPayee p+ , U.pNumber = pNumber p+ , U.pFlag = pFlag p+ , U.pAccount = pAccount p+ , U.pTags = pTags p+ , U.pEntry = case pInferred p of+ Inferred -> Nothing+ NotInferred -> Just (pEntry p)+ , U.pMemo = pMemo p+ , U.pPostingLine = pPostingLine p+ , U.pGlobalPosting = pGlobalPosting p+ , U.pFilePosting = pFilePosting p+ }++ toPosting :: U.Posting -> B.Entry -> Inferred -> Posting-toPosting (U.Posting p n f a t _ m mt) e i =- Posting p n f a t e m i mt+toPosting u e i =+ Posting+ { pPayee = U.pPayee u+ , pNumber = U.pNumber u+ , pFlag = U.pFlag u+ , pAccount = U.pAccount u+ , pTags = U.pTags u+ , pEntry = e+ , pMemo = U.pMemo u+ , pInferred = i+ , pPostingLine = U.pPostingLine u+ , pGlobalPosting = U.pGlobalPosting u+ , pFilePosting = U.pFilePosting u+ } ++toUTopLine :: TopLine -> U.TopLine+toUTopLine t = U.TopLine+ { U.tDateTime = tDateTime t+ , U.tFlag = tFlag t+ , U.tNumber = tNumber t+ , U.tPayee = tPayee t+ , U.tMemo = tMemo t+ , U.tTopLineLine = tTopLineLine t+ , U.tTopMemoLine = tTopMemoLine t+ , U.tFilename = tFilename t+ , U.tGlobalTransaction = tGlobalTransaction t+ , U.tFileTransaction = tFileTransaction t+ }+ toTopLine :: U.TopLine -> TopLine-toTopLine (U.TopLine d f n p m mt) =- TopLine d f n p m mt+toTopLine t = TopLine+ { tDateTime = U.tDateTime t+ , tFlag = U.tFlag t+ , tNumber = U.tNumber t+ , tPayee = U.tPayee t+ , tMemo = U.tMemo t+ , tTopLineLine = U.tTopLineLine t+ , tTopMemoLine = U.tTopMemoLine t+ , tFilename = U.tFilename t+ , tGlobalTransaction = U.tGlobalTransaction t+ , tFileTransaction = U.tFileTransaction t+ } +fromRPosting :: U.RPosting -> B.Entry -> Inferred -> Posting+fromRPosting u e i = Posting+ { pPayee = U.rPayee u+ , pNumber = U.rNumber u+ , pFlag = U.rFlag u+ , pAccount = U.rAccount u+ , pTags = U.rTags u+ , pEntry = e+ , pMemo = U.rMemo u+ , pInferred = i+ , pPostingLine = U.rPostingLine u+ , pGlobalPosting = U.rGlobalPosting u+ , pFilePosting = U.rFilePosting u+ } --- | Change the metadata on a transaction.-changeTransactionMeta ::- (M.TopLineMeta -> M.TopLineMeta)- -- ^ Function that, when applied to the old top line meta, returns- -- the new meta.- - -> Transaction- -- ^ The old transaction with metadata+fromIPosting :: U.IPosting -> B.Entry -> Inferred -> Posting+fromIPosting u e i = Posting+ { pPayee = U.iPayee u+ , pNumber = U.iNumber u+ , pFlag = U.iFlag u+ , pAccount = U.iAccount u+ , pTags = U.iTags u+ , pEntry = e+ , pMemo = U.iMemo u+ , pInferred = i+ , pPostingLine = U.iPostingLine u+ , pGlobalPosting = U.iGlobalPosting u+ , pFilePosting = U.iFilePosting u+ } - -> Transaction- -- ^ Transaction with new metadata+data RTransaction = RTransaction+ { rtCommodity :: B.Commodity+ -- ^ All postings will have this same commodity -changeTransactionMeta fm (Transaction f) = Transaction f' where- f' = F.Family tl c1 c2 cs- (F.Family p c1 c2 cs) = f- tl = p { tMeta = fm (tMeta tl) }+ , rtSide :: Maybe B.Side+ -- ^ All commodities will be on this side of the amount --- | Change the metadata on a posting.-changePostingMeta ::- (M.PostingMeta -> M.PostingMeta)- -> Transaction- -> Transaction-changePostingMeta f (Transaction fam) =- Transaction . F.mapChildren g $ fam- where- g p = p { pMeta = f (pMeta p) }+ , rtSpaceBetween :: Maybe B.SpaceBetween+ -- ^ All amounts will have this SpaceBetween -addSerials ::- (Ser.Serial -> M.TopLineMeta -> M.TopLineMeta)- -> (Ser.Serial -> M.PostingMeta -> M.PostingMeta)- -> Ser.Serial- -> Transaction- -> St.State (Ser.NextFwd, Ser.NextBack) Transaction-addSerials ft fp s (Transaction fam) = do- let topMapper pm = pm { tMeta = ft s (tMeta pm) }- pstgMapper ser pstg = pstg { pMeta = fp ser (pMeta pstg) }- fam' = F.mapParent topMapper fam- fam'' <- Ser.serialChildrenInFamily pstgMapper fam'- return $ Transaction fam''+ , rtDrCr :: B.DrCr+ -- ^ All postings except the inferred one will have this DrCr -addSerialsToList ::- (Ser.Serial -> M.TopLineMeta -> M.TopLineMeta)- -> (Ser.Serial -> M.PostingMeta -> M.PostingMeta)- -> [Transaction]- -> [Transaction]-addSerialsToList ft fp ls =- let nPstgs = length . concatMap Fdbl.toList . map orphans- . map unTransaction $ ls- initState = Ser.initNexts nPstgs- processor = addSerials ft fp- in St.evalState (Ser.serialItemsM processor ls) initState+ , rtTopLine :: U.TopLine + , rtPosting :: U.RPosting+ -- ^ You must have at least one posting whose quantity you specify -addSerialsToEithers ::- (Ser.Serial -> M.TopLineMeta -> M.TopLineMeta)- -> (Ser.Serial -> M.PostingMeta -> M.PostingMeta)- -> [Either a Transaction]- -> [Either a Transaction]-addSerialsToEithers ft fp ls =- let txns = E.rights ls- nPstgs = length . concatMap Fdbl.toList . map orphans- . map unTransaction $ txns- initState = Ser.initNexts nPstgs- processA _ a = return a- processTxn = addSerials ft fp- k = Ser.serialEithers processA processTxn ls- in St.evalState k initState+ , rtMorePostings :: [U.RPosting]+ -- ^ Optionally you can have additional restricted postings. + , rtIPosting :: U.IPosting+ -- ^ And at least one posting whose quantity and DrCr will be inferred++ } deriving Show++-- | Creates a @restricted transaction@; that is, one in which all the+-- entries will have the same commodity, and in which all but one of+-- the postings will all be debits or credits. The last posting will+-- have no quantity specified at all and will be inferred. Creating+-- these transactions never fails, in contrast to the transactions+-- created by 'transaction', which can fail at runtime.+rTransaction :: RTransaction -> Transaction+rTransaction rt = Transaction (F.Family tl p1 p2 ps)+ where+ tl = toTopLine (rtTopLine rt)+ tot = foldl1 B.add $ (U.rQty . rtPosting $ rt)+ : map U.rQty (rtMorePostings rt)+ sd = rtSide rt+ sb = rtSpaceBetween rt+ inf = fromIPosting (rtIPosting rt)+ ( B.Entry (B.opposite (rtDrCr rt))+ (B.Amount tot (rtCommodity rt) sd sb))+ Inferred+ toPstg p = fromRPosting p+ (B.Entry (rtDrCr rt)+ (B.Amount (U.rQty p) (rtCommodity rt) sd sb)) NotInferred+ p1 = toPstg . rtPosting $ rt+ (p2, ps) = case rtMorePostings rt of+ [] -> (inf, [])+ x:xs -> (toPstg x, (map toPstg xs) ++ [inf])+ -- | A box stores a family of transaction data along with -- metadata. The transaction is stored in child form, indicating a -- particular posting of interest. The metadata is in addition to the@@ -276,3 +369,130 @@ instance Functor Box where fmap f (Box m pf) = Box (f m) pf++-------------------------------------------------------------+-- Changers+-------------------------------------------------------------++-- | Each field in the record is a Maybe. If Nothing, make no change+-- to this part of the TopLine.+data TopLineChangeData = TopLineChangeData+ { tcDateTime :: Maybe B.DateTime+ , tcFlag :: Maybe (Maybe B.Flag)+ , tcNumber :: Maybe (Maybe B.Number)+ , tcPayee :: Maybe (Maybe B.Payee)+ , tcMemo :: Maybe (Maybe B.Memo)+ , tcTopLineLine :: Maybe (Maybe B.TopLineLine)+ , tcTopMemoLine :: Maybe (Maybe B.TopMemoLine)+ , tcFilename :: Maybe (Maybe B.Filename)+ , tcGlobalTransaction :: Maybe (Maybe B.GlobalTransaction)+ , tcFileTransaction :: Maybe (Maybe B.FileTransaction)+ } deriving Show++emptyTopLineChangeData :: TopLineChangeData+emptyTopLineChangeData = TopLineChangeData+ { tcDateTime = Nothing+ , tcFlag = Nothing+ , tcNumber = Nothing+ , tcPayee = Nothing+ , tcMemo = Nothing+ , tcTopLineLine = Nothing+ , tcTopMemoLine = Nothing+ , tcFilename = Nothing+ , tcGlobalTransaction = Nothing+ , tcFileTransaction = Nothing+ }+++applyTopLineChange :: TopLineChangeData -> TopLine -> TopLine+applyTopLineChange c t = TopLine+ { tDateTime = fromMaybe (tDateTime t) (tcDateTime c)+ , tFlag = fromMaybe (tFlag t) (tcFlag c)+ , tNumber = fromMaybe (tNumber t) (tcNumber c)+ , tPayee = fromMaybe (tPayee t) (tcPayee c)+ , tMemo = fromMaybe (tMemo t) (tcMemo c)+ , tTopLineLine = fromMaybe (tTopLineLine t) (tcTopLineLine c)+ , tTopMemoLine = fromMaybe (tTopMemoLine t) (tcTopMemoLine c)+ , tFilename = fromMaybe (tFilename t) (tcFilename c)+ , tGlobalTransaction = fromMaybe (tGlobalTransaction t)+ (tcGlobalTransaction c)+ , tFileTransaction = fromMaybe (tFileTransaction t)+ (tcFileTransaction c)+ }++data PostingChangeData = PostingChangeData+ { pcPayee :: Maybe (Maybe B.Payee)+ , pcNumber :: Maybe (Maybe B.Number)+ , pcFlag :: Maybe (Maybe B.Flag)+ , pcAccount :: Maybe B.Account+ , pcTags :: Maybe B.Tags+ , pcMemo :: Maybe (Maybe B.Memo)+ , pcSide :: Maybe (Maybe B.Side)+ , pcSpaceBetween :: Maybe (Maybe B.SpaceBetween)+ , pcPostingLine :: Maybe (Maybe B.PostingLine)+ , pcGlobalPosting :: Maybe (Maybe B.GlobalPosting)+ , pcFilePosting :: Maybe (Maybe B.FilePosting)+ } deriving Show++emptyPostingChangeData :: PostingChangeData+emptyPostingChangeData = PostingChangeData+ { pcPayee = Nothing+ , pcNumber = Nothing+ , pcFlag = Nothing+ , pcAccount = Nothing+ , pcTags = Nothing+ , pcMemo = Nothing+ , pcSide = Nothing+ , pcSpaceBetween = Nothing+ , pcPostingLine = Nothing+ , pcGlobalPosting = Nothing+ , pcFilePosting = Nothing+ }+++applyPostingChange :: PostingChangeData -> Posting -> Posting+applyPostingChange c p = Posting+ { pPayee = fromMaybe (pPayee p) (pcPayee c)+ , pNumber = fromMaybe (pNumber p) (pcNumber c)+ , pFlag = fromMaybe (pFlag p) (pcFlag c)+ , pAccount = fromMaybe (pAccount p) (pcAccount c)+ , pTags = fromMaybe (pTags p) (pcTags c)+ , pEntry = en+ , pMemo = fromMaybe (pMemo p) (pcMemo c)+ , pInferred = pInferred p+ , pPostingLine = fromMaybe (pPostingLine p) (pcPostingLine c)+ , pGlobalPosting = fromMaybe (pGlobalPosting p) (pcGlobalPosting c)+ , pFilePosting = fromMaybe (pFilePosting p) (pcFilePosting c)+ }+ where+ enOld = pEntry p+ amOld = B.amount enOld+ en = B.Entry (B.drCr enOld) am+ am = B.Amount (B.qty amOld) (B.commodity amOld) sd sb+ sd = fromMaybe (B.side amOld) (pcSide c)+ sb = fromMaybe (B.spaceBetween amOld) (pcSpaceBetween c)++-- | Allows you to change the parts of a transaction that can be+-- chanaged without unbalancing the transaction. You cannot change the+-- DrCr, Qty, or Commodity, as changing these might unbalance the+-- transaction. If there are elements you do not want to change at+-- all, use an 'emptyTopLineChangeData' or an 'emptyPostingChangeData'+-- in the appropriate part of the Family that you pass in. If the+-- Family of change data has more children than the transaction, these+-- extra children are ignored. If the Family in the Transaction has+-- more children than the Family of change data, the extra postings+-- are unchanged. That is, 'changeTransaction' will never delete+-- postings.+changeTransaction+ :: F.Family TopLineChangeData PostingChangeData+ -> Transaction+ -> Transaction+changeTransaction c (Transaction t) =+ let F.Family ctl cp1 cp2 cps = c+ F.Family tl p1 p2 ps = t+ tl' = applyTopLineChange ctl tl+ p1' = applyPostingChange cp1 p1+ p2' = applyPostingChange cp2 p2+ ps' = zipWith applyPostingChange+ (cps ++ repeat emptyPostingChangeData) ps+ in Transaction (F.Family tl' p1' p2' ps')
Penny/Lincoln/Transaction/Unverified.hs view
@@ -1,27 +1,131 @@+-- | Provides record types to hold the data that are in an unverified+-- transaction. Use these records along with the functions in+-- 'Penny.Lincoln.Transaction' to create Transactions. You can create+-- a Transaction only if the postings are balanced.+--+-- The functions that create transactions will fail at runtime if the+-- postings are not balanced (this is impossible to enforce at compile+-- time.) However, if you are creating a transaction in which both of+-- the amounts have the same commodity, for which all but one of the+-- entries will have the same DrCr, and in which one of the postings+-- has no entry at all (that is, its entry will be inferred), then it+-- is possible to create a transaction that is guaranteed to be+-- balanced. Use the RPosting and IPosting types for this purpose. (It+-- is necessary for all the postings except for the inferred one to+-- have the same DrCr because otherwise it would be possible to create+-- a transaction in which the inferred posting would have to have an+-- entry with a quantity of zero, which is impossible. module Penny.Lincoln.Transaction.Unverified where import qualified Penny.Lincoln.Bits as B-import qualified Penny.Lincoln.Meta as M data TopLine = TopLine- B.DateTime- (Maybe B.Flag)- (Maybe B.Number)- (Maybe B.Payee)- B.Memo- M.TopLineMeta- deriving (Eq, Show)+ { tDateTime :: B.DateTime+ , tFlag :: Maybe B.Flag+ , tNumber :: Maybe B.Number+ , tPayee :: Maybe B.Payee+ , tMemo :: Maybe B.Memo+ , tTopLineLine :: Maybe B.TopLineLine+ , tTopMemoLine :: Maybe B.TopMemoLine+ , tFilename :: Maybe B.Filename+ , tGlobalTransaction :: Maybe B.GlobalTransaction+ , tFileTransaction :: Maybe B.FileTransaction+ } deriving (Eq, Show) +emptyTopLine :: B.DateTime -> TopLine+emptyTopLine d = TopLine+ { tDateTime = d+ , tFlag = Nothing+ , tNumber = Nothing+ , tPayee = Nothing+ , tMemo = Nothing+ , tTopLineLine = Nothing+ , tTopMemoLine = Nothing+ , tFilename = Nothing+ , tGlobalTransaction = Nothing+ , tFileTransaction = Nothing+ }+ data Posting = Posting- (Maybe B.Payee)- (Maybe B.Number) - (Maybe B.Flag) - B.Account- B.Tags- (Maybe B.Entry)- B.Memo- M.PostingMeta- deriving (Eq, Show)+ { pPayee :: Maybe B.Payee+ , pNumber :: Maybe B.Number+ , pFlag :: Maybe B.Flag+ , pAccount :: B.Account+ , pTags :: B.Tags+ , pEntry :: Maybe B.Entry+ , pMemo :: Maybe B.Memo+ , pPostingLine :: Maybe B.PostingLine+ , pGlobalPosting :: Maybe B.GlobalPosting+ , pFilePosting :: Maybe B.FilePosting+ } deriving (Eq, Show) -entry :: Posting -> Maybe B.Entry-entry (Posting _ _ _ _ _ e _ _) = e+emptyPosting :: B.Account -> Posting+emptyPosting a = Posting+ { pPayee = Nothing+ , pNumber = Nothing+ , pFlag = Nothing+ , pAccount = a+ , pTags = B.Tags []+ , pEntry = Nothing+ , pMemo = Nothing+ , pPostingLine = Nothing+ , pGlobalPosting = Nothing+ , pFilePosting = Nothing+ }++-- | A @restricted posting@ in which only the quantity is specified;+-- the commodity and DrCr are specified when the transaction is+-- created.+data RPosting = RPosting+ { rPayee :: Maybe B.Payee+ , rNumber :: Maybe B.Number+ , rFlag :: Maybe B.Flag+ , rAccount :: B.Account+ , rTags :: B.Tags+ , rQty :: B.Qty+ , rMemo :: Maybe B.Memo+ , rPostingLine :: Maybe B.PostingLine+ , rGlobalPosting :: Maybe B.GlobalPosting+ , rFilePosting :: Maybe B.FilePosting+ } deriving (Eq, Show)++emptyRPosting :: B.Account -> B.Qty -> RPosting+emptyRPosting a q = RPosting+ { rPayee = Nothing+ , rNumber = Nothing+ , rFlag = Nothing+ , rAccount = a+ , rTags = B.Tags []+ , rQty = q+ , rMemo = Nothing+ , rPostingLine = Nothing+ , rGlobalPosting = Nothing+ , rFilePosting = Nothing+ }++-- | An @inferred posting@ in which no quantity is specified.+data IPosting = IPosting+ { iPayee :: Maybe B.Payee+ , iNumber :: Maybe B.Number+ , iFlag :: Maybe B.Flag+ , iAccount :: B.Account+ , iTags :: B.Tags+ , iMemo :: Maybe B.Memo+ , iPostingLine :: Maybe B.PostingLine+ , iGlobalPosting :: Maybe B.GlobalPosting+ , iFilePosting :: Maybe B.FilePosting+ } deriving (Eq, Show)++emptyIPosting :: B.Account -> IPosting+emptyIPosting a = IPosting+ { iPayee = Nothing+ , iNumber = Nothing+ , iFlag = Nothing+ , iAccount = a+ , iTags = B.Tags []+ , iMemo = Nothing+ , iPostingLine = Nothing+ , iGlobalPosting = Nothing+ , iFilePosting = Nothing+ }+
Penny/Shield.hs view
@@ -48,16 +48,15 @@ , currentTime :: B.DateTime , output :: Output } -zonedToDateTime :: T.ZonedTime -> B.DateTime-zonedToDateTime (T.ZonedTime lt tz) = B.dateTime lt off where- off = maybe (error "could not convert time to local time")- id (B.minsToOffset (T.timeZoneMinutes tz))- runtime :: IO Runtime runtime = Runtime <$> getEnvironment- <*> (zonedToDateTime <$> T.getZonedTime)+ <*> (toDT <$> T.getZonedTime) <*> findOutput+ where+ toDT t = case B.fromZonedTime t of+ Nothing -> error "time conversion error"+ Just ti -> ti findOutput :: IO Output findOutput = do@@ -65,7 +64,7 @@ return $ if isTerm then IsTTY else NotTTY screenLines :: Runtime -> Maybe ScreenLines-screenLines r = +screenLines r = (lookup "LINES" . environment $ r) >>= safeRead >>= return . ScreenLines
Penny/Zinc.hs view
@@ -1,29 +1,716 @@ -- | Zinc - the Penny command-line interface-module Penny.Zinc (runPenny, P.T(..),- P.defaultFromRuntime) where--import qualified Control.Monad.Exception.Synchronous as Ex-import qualified System.Console.MultiArg.Prim as M-import System.Console.MultiArg.GetArgs (getArgs)-import System.Exit (exitFailure)-import System.IO (stderr, hPutStrLn)+module Penny.Zinc+ ( Defaults(..)+ , ColorToFile(..)+ , Matcher(..)+ , SortField(..)+ , runZinc+ ) where +import qualified Penny.Cabin.Chunk as Chk import qualified Penny.Cabin.Interface as I-import qualified Penny.Copper as Cop+import qualified Penny.Cabin.Parsers as P+import qualified Penny.Cabin.Scheme as E+import qualified Penny.Copper as C+import qualified Penny.Liberty as Ly+import qualified Penny.Liberty.Expressions as X+import qualified Penny.Lincoln as L+import qualified Penny.Lincoln.Queries as Q import qualified Penny.Shield as S-import qualified Penny.Zinc.Parser as P -runPenny ::- S.Runtime- -> Cop.DefaultTimeZone- -> Cop.RadGroup- -> (S.Runtime -> P.T)+import Control.Applicative ((<$>), (<*>), pure)+import Control.Monad (when)+import qualified Control.Monad.Trans.State as St+import qualified Control.Monad.Exception.Synchronous as Ex+import Data.Char (toUpper, toLower)+import Data.List (isPrefixOf)+import Data.Maybe (mapMaybe, catMaybes)+import Data.Monoid (mappend, mconcat)+import Data.Ord (comparing)+import Data.Text (Text, pack, unpack)+import qualified Data.Text.IO as TIO+import qualified System.Console.MultiArg as MA+import System.Console.MultiArg.GetArgs (getArgs)+import System.Exit (exitSuccess, exitFailure)+import qualified System.IO as IO+import System.IO (hIsTerminalDevice, stdin, stderr, hPutStrLn)+import qualified Text.Matchers.Text as M++runZinc+ :: Defaults+ -> S.Runtime -> [I.Report] -> IO ()-runPenny rt dtz rg df rs = do+runZinc df rt rs = do as <- getArgs- case M.parse as (P.parser rt dtz rg df rs) of+ parseAndPrint df rt rs as++-- | Whether to use color when standard output is not a terminal.+newtype ColorToFile = ColorToFile { unColorToFile :: Bool }+ deriving (Eq, Show)++data Matcher+ = Within+ | Exact+ | TDFA+ | PCRE+ deriving (Eq, Show)++data SortField+ = Payee+ | Date+ | Flag+ | Number+ | Account+ | DrCr+ | Qty+ | Commodity+ | PostingMemo+ | TransactionMemo+ deriving (Eq, Show, Ord)++data Defaults = Defaults+ { sensitive :: M.CaseSensitive+ , matcher :: Matcher+ , colorToFile :: ColorToFile+ , defaultScheme :: Maybe E.Scheme+ -- ^ If Nothing, no default scheme. If the user does not pick a+ -- scheme, no colors are used.+ , moreSchemes :: [E.Scheme]+ , sorter :: [(SortField, P.SortOrder)]+ -- ^ For example, to sort by date and then by payee if the dates+ -- are equal, use+ --+ -- > [(Date, Ascending), (Payee, Ascending)]+ }++sortPairToFn :: (SortField, P.SortOrder) -> Orderer+sortPairToFn (s, d) = if d == P.Descending then flipOrder r else r+ where+ r = case s of+ Payee -> comparing Q.payee+ Date -> comparing Q.dateTime+ Flag -> comparing Q.flag+ Number -> comparing Q.number+ Account -> comparing Q.account+ DrCr -> comparing Q.drCr+ Qty -> comparing Q.qty+ Commodity -> comparing Q.commodity+ PostingMemo -> comparing Q.postingMemo+ TransactionMemo -> comparing Q.transactionMemo++descPair :: (SortField, P.SortOrder) -> String+descPair (i, d) = desc ++ ", " ++ dir+ where+ dir = case d of+ P.Ascending -> "ascending"+ P.Descending -> "descending"+ desc = case show i of+ [] -> []+ x:xs -> toLower x : xs++descSortList :: [(SortField, P.SortOrder)] -> [String]+descSortList ls = case ls of+ [] -> [" No sorting performed by default"]+ x:xs -> descFirst x : map descRest xs++descFirst :: (SortField, P.SortOrder) -> String+descFirst p = " Default sort order: " ++ descPair p++descRest :: (SortField, P.SortOrder) -> String+descRest p = " then: " ++ descPair p++sortPairsToFn :: [(SortField, P.SortOrder)] -> Orderer+sortPairsToFn = mconcat . map sortPairToFn++data State = State+ { stSensitive :: M.CaseSensitive+ , stFactory :: M.CaseSensitive -> Text+ -> Ex.Exceptional Text (Text -> Bool)+ , stColorToFile :: ColorToFile+ , stScheme :: Maybe E.TextSpecs+ }++stateFromDefaults+ :: Defaults+ -> State+stateFromDefaults df = State+ { stSensitive = sensitive df+ , stFactory = case matcher df of+ Within -> \c t -> return (M.within c t)+ Exact -> \c t -> return (M.exact c t)+ TDFA -> M.tdfa+ PCRE -> M.pcre+ , stColorToFile = colorToFile df+ , stScheme = fmap E.textSpecs . defaultScheme $ df+ }++--+-- ## Option parsing+--++--+-- ## OptResult, and functions dealing with it+--+data OptResult+ = ROperand (M.CaseSensitive+ -> Ly.MatcherFactory+ -> Ex.Exceptional String Ly.Operand)+ | RPostFilter (Ex.Exceptional String Ly.PostFilterFn)+ | RMatcherSelect Ly.MatcherFactory+ | RCaseSelect M.CaseSensitive+ | ROperator (Ly.Token (L.PostFam -> Bool))+ | RSortSpec (Ex.Exceptional String Orderer)+ | RHelp+ | RColorToFile ColorToFile+ | RScheme E.TextSpecs++isHelp :: OptResult -> Bool+isHelp o = case o of { RHelp -> True; _ -> False }++getPostFilters+ :: [OptResult]+ -> Ex.Exceptional String [Ly.PostFilterFn]+getPostFilters =+ sequence+ . mapMaybe f+ where+ f o = case o of+ RPostFilter pf -> Just pf+ _ -> Nothing++getSortSpec+ :: Orderer+ -> [OptResult]+ -> Ex.Exceptional String Orderer+getSortSpec i ls =+ let getSpec o = case o of+ RSortSpec x -> Just x+ _ -> Nothing+ exSpecs = mapMaybe getSpec ls+ in if null exSpecs+ then return i+ else fmap mconcat . sequence $ exSpecs++type Factory = M.CaseSensitive+ -> Text -> Ex.Exceptional Text (Text -> Bool)++makeToken+ :: OptResult+ -> St.State (M.CaseSensitive, Factory)+ (Maybe (Ex.Exceptional String (Ly.Token (L.PostFam -> Bool))))+makeToken o = case o of+ ROperand f -> do+ (s, fty) <- St.get+ let g = fmap h (f s fty)+ h (X.Operand fn) = Ly.TokOperand fn+ return (Just g)+ RMatcherSelect f -> do+ (c, _) <- St.get+ St.put (c, f)+ return Nothing+ RCaseSelect c -> do+ (_, f) <- St.get+ St.put (c, f)+ return Nothing+ ROperator t -> return . Just . return $ t+ _ -> return Nothing+++makeTokens+ :: State+ -> [OptResult]+ -> Ex.Exceptional String ( [Ly.Token (L.PostFam -> Bool)]+ , (M.CaseSensitive, Factory) )+makeTokens df os =+ let initSt = (stSensitive df, stFactory df)+ lsSt = mapM makeToken os+ (ls, st') = St.runState lsSt initSt+ in fmap (\xs -> (xs, st')) . sequence . catMaybes $ ls+++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]+ ++ map (fmap RMatcherSelect) Ly.matcherSelectSpecs+ ++ map (fmap RCaseSelect) Ly.caseSelectSpecs+ ++ map (fmap ROperator) Ly.operatorSpecs+ ++ [fmap RSortSpec sortSpecs]+ ++ [ MA.OptSpec ["help"] "h" (MA.NoArg RHelp)+ , optColorToFile ]+ ++ let ss = moreSchemes df+ in if not . null $ ss then [optScheme ss] else []++optColorToFile :: MA.OptSpec OptResult+optColorToFile = MA.OptSpec ["color-to-file"] "" (MA.ChoiceArg ls)+ where+ ls = [ ("yes", RColorToFile $ ColorToFile True)+ , ("no", RColorToFile $ ColorToFile False) ]++getColorToFile :: State -> [OptResult] -> ColorToFile+getColorToFile d ls =+ case mapMaybe getOpt ls of+ [] -> stColorToFile d+ xs -> last xs+ where+ getOpt o = case o of+ RColorToFile c -> Just c+ _ -> Nothing++optScheme :: [E.Scheme] -> MA.OptSpec OptResult+optScheme ss = MA.OptSpec ["scheme"] "" (MA.ChoiceArg ls)+ where+ ls = map f ss+ f (E.Scheme n _ s) = (n, RScheme s)++getScheme :: State -> [OptResult] -> Maybe E.TextSpecs+getScheme d ls =+ case mapMaybe getOpt ls of+ [] -> stScheme d+ xs -> Just $ last xs+ where+ getOpt o = case o of+ RScheme s -> Just s+ _ -> Nothing++data GlobalResult+ = NeedsHelp+ | RunPenny FilterOpts++-- | Indicates the result of a successful parse of filtering options.+data FilterOpts = FilterOpts+ { _resultFactory :: M.CaseSensitive+ -> Text -> Ex.Exceptional Text (Text -> Bool)+ -- ^ The factory indicated, so that it can be used in+ -- subsequent parses of the same command line.++ , _resultSensitive :: M.CaseSensitive+ -- ^ Indicated case sensitivity, so that it can be used in+ -- subsequent parses of the command line.++ , _sorterFilterer :: [L.Transaction] -> [L.Box Ly.LibertyMeta]+ -- ^ Applied to a list of Transaction, will sort and filter+ -- the transactions and assign them LibertyMeta.++ , foTextSpecs :: Maybe E.TextSpecs++ , foColorToFile :: ColorToFile+ }++processGlobal+ :: Orderer+ -> State+ -> [OptResult]+ -> Ex.Exceptional String GlobalResult+processGlobal srt st os =+ if any isHelp os+ then return NeedsHelp+ else do+ postFilts <- getPostFilters os+ sortSpec <- getSortSpec srt os+ (toks, (rs, rf)) <- makeTokens st os+ let ctf = getColorToFile st os+ sch = getScheme st os+ err = "could not parse filter expression."+ pdct <- Ex.fromMaybe err $ Ly.parsePredicate toks+ let sf = Ly.xactionsToFiltered pdct postFilts sortSpec+ fo = FilterOpts rf rs sf sch ctf+ return $ RunPenny fo++--+-- Ledger parsing+--+warnTerminal :: IO ()+warnTerminal =+ hPutStrLn stderr $ "penny: warning: reading from standard input, "+ ++ "which is a terminal"++data Filename =+ Filename Text+ | Stdin++-- | Converts a Ledgers filename to a Lincoln filename.+convertFilename :: Filename -> L.Filename+convertFilename (Filename x) = L.Filename x+convertFilename Stdin = L.Filename . pack $ "<stdin>"++-- | Actually reads the file off disk. For now just let this crash if+-- any of the IO errors occur.+ledgerText :: Filename -> IO Text+ledgerText f = case f of+ Stdin -> do+ isTerm <- hIsTerminalDevice stdin+ when isTerm warnTerminal+ TIO.hGetContents stdin+ Filename fn -> TIO.readFile (unpack fn)++-- | Converts a string from the command line to a Filename.+toFilename :: String -> Filename+toFilename s =+ if s == "-"+ then Stdin+ else Filename . pack $ s++readLedgers :: [String] -> IO [(Filename, Text)]+readLedgers ss =+ let fns = if null ss then [Stdin] else map toFilename ss+ f fn = (\txt -> (fn, txt)) <$> ledgerText fn+ in mapM f fns+++parseLedgers+ :: [(Filename, Text)]+ -> Ex.Exceptional String ([L.Transaction], [L.PricePoint])+parseLedgers ls =+ let toPair (f, t) = (convertFilename f, C.FileContents t)+ parsed = C.parse (map toPair ls)+ folder i (ts, ps) = case i of+ C.Transaction t -> (t:ts, ps)+ C.PricePoint p -> (ts, p:ps)+ _ -> (ts, ps)+ toResult (C.Ledger is) = foldr folder ([], []) is+ toErr x = "could not parse ledger: "+ ++ (unpack . C.unErrorMsg $ x)+ in Ex.mapExceptional toErr toResult parsed+++data DisplayOpts = DisplayOpts ColorToFile (Maybe E.TextSpecs)++toDisplayOpts :: FilterOpts -> DisplayOpts+toDisplayOpts o = DisplayOpts (foColorToFile o) (foTextSpecs o)++parseCommandLine+ :: Defaults+ -> [I.Report]+ -> S.Runtime+ -> [String]+ -> Ex.Exceptional MA.Error+ (GlobalResult, Either [()] (DisplayOpts, I.ParseResult))+parseCommandLine df rs rt ss =+ let initSt = stateFromDefaults df+ in MA.modes (allOpts (S.currentTime rt) df)+ (processGlobal (sortPairsToFn . sorter $ df) initSt)+ (whatMode rt rs) ss++whatMode+ :: S.Runtime+ -> [I.Report]+ -> GlobalResult+ -> Either (a -> ()) [MA.Mode (DisplayOpts, I.ParseResult)]+whatMode rt pairFns gr =+ case gr of+ NeedsHelp -> Left $ const ()+ RunPenny fo@(FilterOpts fty cs sf _ _) ->+ let prs = map snd (pairFns <*> pure rt)+ <*> pure cs+ <*> pure fty+ <*> pure sf+ in Right $ map (fmap (\r -> ((toDisplayOpts fo), r))) prs++handleParseResult+ :: S.Runtime+ -> Defaults+ -> [I.Report]+ -> Ex.Exceptional MA.Error+ (a, Either b (DisplayOpts, I.ParseResult))+ -> IO ()+handleParseResult rt df rs r =+ let showErr e = do+ IO.hPutStrLn IO.stderr $ "penny: error: " ++ e+ exitFailure+ in case r of Ex.Exception e -> do- hPutStrLn stderr . show $ e+ IO.hPutStr IO.stderr $ MA.formatError "penny" e exitFailure- Ex.Success g -> g+ Ex.Success (_, ei) ->+ case ei of+ Left _ -> putStr (helpText df rt rs) >> exitSuccess+ Right ((DisplayOpts ctf sch), ex) -> case ex of+ Ex.Exception s -> showErr s+ Ex.Success good -> either showHelp runCmd good+ where+ showHelp h = putStr h >> exitSuccess+ runCmd (fns, pr) = do+ ledgers <- readLedgers fns+ (txns, pps) <- Ex.switch showErr return+ $ parseLedgers ledgers+ let term = if unColorToFile ctf+ then Chk.termFromEnv rt+ else Chk.autoTerm rt+ Ex.switch (showErr . unpack)+ (printChunks term sch) $ pr txns pps++printChunks+ :: Chk.Term+ -> Maybe E.TextSpecs+ -> [E.PreChunk]+ -> IO ()+printChunks t mayS =+ Chk.printChunks t+ . map makeChunk+ where+ makeChunk pc = case mayS of+ Nothing -> Chk.chunk Chk.defaultTextSpec (E.text pc)+ Just s -> E.makeChunk s pc++helpText+ :: Defaults+ -> S.Runtime+ -> [I.Report]+ -> String+helpText df rt pairMakers =+ mappend (help df) . mconcat . map addHdr . fmap fst $ pairs+ where+ pairs = pairMakers <*> pure rt+ addHdr s = hdr ++ s+ hdr = unlines [ "", replicate 50 '=' ]+++parseAndPrint+ :: Defaults+ -> S.Runtime+ -> [I.Report]+ -> [String]+ -> IO ()+parseAndPrint df rt rs ss =+ handleParseResult rt df rs+ $ parseCommandLine df rs rt ss++------------------------------------------------------------+-- ## Sorting+------------------------------------------------------------++-- The monoid instance of Ordering takes the first non-EQ item. For+-- example:+--+-- mconcat [EQ, LT, GT] == LT.+--+-- If b is a monoid, then (a -> b) is also a monoid. Therefore (a -> a+-- -> Ordering) is also a monoid. So for example to compare the first+-- element of a pair and then by the second element only if the first+-- element is equal:+--+-- mconcat [comparing fst, comparing snd]++type Orderer = L.PostFam -> L.PostFam -> Ordering++flipOrder :: (a -> a -> Ordering) -> (a -> a -> Ordering)+flipOrder f = f' where+ f' p1 p2 = case f p1 p2 of+ LT -> GT+ GT -> LT+ EQ -> EQ++capitalizeFirstLetter :: String -> String+capitalizeFirstLetter s = case s of+ [] -> []+ (x:xs) -> toUpper x : xs++ordPairs :: [(String, Orderer)]+ordPairs =+ [ ("payee", comparing Q.payee)+ , ("date", comparing Q.dateTime)+ , ("flag", comparing Q.flag)+ , ("number", comparing Q.number)+ , ("account", comparing Q.account)+ , ("drCr", comparing Q.drCr)+ , ("qty", comparing Q.qty)+ , ("commodity", comparing Q.commodity)+ , ("postingMemo", comparing Q.postingMemo)+ , ("transactionMemo", comparing Q.transactionMemo) ]++ords :: [(String, Orderer)]+ords = ordPairs ++ uppers ++ [none] where+ uppers = map toReversed ordPairs+ toReversed (s, f) =+ (capitalizeFirstLetter s, flipOrder f)+ none = ("none", const . const $ EQ)+++-- | True if the first argument matches the second argument. The match+-- on the first letter is case sensitive; the match on the other+-- letters is not case sensitive. True if both strings are empty.+argMatch :: String -> String -> Bool+argMatch s1 s2 = case (s1, s2) of+ (x:xs, y:ys) ->+ (x == y) && ((map toUpper xs) `isPrefixOf` (map toUpper ys))+ _ -> True++sortSpecs :: MA.OptSpec (Ex.Exceptional String Orderer)+sortSpecs = MA.OptSpec ["sort"] ['s'] (MA.OneArg f)+ where+ f a =+ let matches = filter (\p -> a `argMatch` (fst p)) ords+ in case matches of+ x:[] -> return $ snd x+ _ -> Ex.throw $ "invalid sort key: " ++ a++++------------------------------------------------------------+-- ## Help+------------------------------------------------------------++help :: Defaults -> String+help d = unlines $+ [ "usage: penny [posting filters] report [report options] file . . ."+ , ""+ , "Posting filters"+ , "------------------------------------------"+ , ""+ , "Dates"+ , "-----"+ , ""+ , "--date cmp timespec, -d cmp timespec"+ , " Date must be within the time frame given. timespec"+ , " is a day or a day and a time. Valid values for cmp:"+ , " <, >, <=, >=, ==, /=, !="+ , "--current"+ , " Same as \"--date <= (right now) \""+ , ""+ , "Serials"+ , "----------------"+ , "These options take the form --option cmp num; the given"+ , "sequence number must fall within the given range. \"rev\""+ , "in the option name indicates numbering is from end to beginning."+ , ""+ , "--globalTransaction, --revGlobalTransaction"+ , " All transactions, after reading the ledger files"+ , "--globalPosting, --revGlobalPosting"+ , " All postings, after reading the leder files"+ , "--fileTransaction, --revFileTransaction"+ , " Transactions in each ledger file, after reading the files"+ , " (numbering restarts with each file)"+ , "--filePosting, --revFilePosting"+ , " Postings in each ledger file, after reading the files"+ , " (numbering restarts with each file)"+ , ""+ , "Pattern matching"+ , "----------------"+ , ""+ , "-a pattern, --account pattern"+ , " Pattern must match colon-separated account name"+ , "--account-level num pat"+ , " Pattern must match sub account at given level"+ , "--account-any pat"+ , " Pattern must match sub account at any level"+ , "-p pattern, --payee pattern"+ , " Payee must match pattern"+ , "-t pattern, --tag pattern"+ , " Tag must match pattern"+ , "--number pattern"+ , " Number must match pattern"+ , "--flag pattern"+ , " Flag must match pattern"+ , "--commodity pattern"+ , " Pattern must match colon-separated commodity name"+ , "--posting-memo pattern"+ , " Posting memo must match pattern"+ , "--transaction-memo pattern"+ , " Transaction memo must match pattern"+ , ""+ , "Other posting characteristics"+ , "-----------------------------"+ , "--debit"+ , " Entry must be a debit"+ , "--credit"+ , " Entry must be a credit"+ , "--qty cmp number"+ , " Entry quantity must fall within given range"+ , ""+ , "Operators - from highest to lowest precedence"+ , "(all are left associative)"+ , "--------------------------"+ , "--open expr --close"+ , " Force precedence (as in \"open\" and \"close\" parentheses)"+ , "--not expr"+ , " True if expr is false"+ , "expr1 --and expr2 "+ , " True if expr and expr2 are both true"+ , "expr1 --or expr2"+ , " True if either expr1 or expr2 is true"+ , ""+ , "Options affecting patterns"+ , "--------------------------"+ , ""++ , "-i, --case-insensitive"+ , " Be case insensitive"+ ++ ifDefault (sensitive d == M.Insensitive)++ , "-I, --case-sensitive"+ , " Be case sensitive"+ ++ ifDefault (sensitive d == M.Sensitive)++ , ""++ , "--within"+ , " Use \"within\" matcher"+ ++ ifDefault (matcher d == Within)++ , "--pcre"+ , " Use \"pcre\" matcher"+ ++ ifDefault (matcher d == PCRE)++ , "--posix"+ , " Use \"posix\" matcher"+ ++ ifDefault (matcher d == TDFA)++ , "--exact"+ , " Use \"exact\" matcher"+ ++ ifDefault (matcher d == Exact)++ , ""+ , "Removing postings after sorting and filtering"+ , "---------------------------------------------"+ , "--head n"+ , " Keep only the first n postings"+ , "--tail n"+ , " Keep only the last n postings"+ , ""+ , "Sorting"+ , "-------"+ , ""+ , "-s key, --sort key"+ , " Sort postings according to key"+ , ""+ , "Keys:"+ , " payee, date, flag, number, account, drCr,"+ , " qty, commodity, postingMemo, transactionMemo"+ , ""+ , " Ascending order by default; for descending order,"+ , " capitalize the name of the key."+ , " (use \"none\" to leave postings in ledger file order)"+ , ""+ ] ++ descSortList (sorter d) +++ [ ""+ , "Colors"+ , "------"+ , "default scheme:"+ , maybe " (none)" descScheme (defaultScheme d)+ , ""+ ]+ ++ let schs = moreSchemes d+ in (if not . null $ schs+ then+ [ "--scheme SCHEME_NAME"+ , " use color scheme for report. Available schemes:"+ ] ++ map descScheme schs+ else [])+ +++ [ ""+ , "--color-to-file no|yes"+ , " Whether to use color when standard output is not a"+ , " terminal (default: " +++ if unColorToFile . colorToFile $ d then "yes)" else "no)"+ ]++descScheme :: E.Scheme -> String+descScheme (E.Scheme n d _) = " " ++ n ++ " - " ++ d++-- | The string @ (default)@ if the condition is True; otherwise,+-- nothing.+ifDefault :: Bool -> String+ifDefault b = if b then " (default)" else ""
− Penny/Zinc/Error.hs
@@ -1,11 +0,0 @@-module Penny.Zinc.Error where--import Data.Text (Text, pack)--data Error =- ParseError Text- | ReportError Text- deriving Show--printError :: Error -> Text-printError = pack . show
− Penny/Zinc/Help.hs
@@ -1,125 +0,0 @@-module Penny.Zinc.Help where--import Data.Text (Text, pack)--help :: Text-help = pack $ unlines [- "usage: zinc [posting filters] report [report options] file . . .",- "",- "Posting filters",- "------------------------------------------",- "",- "Dates",- "-----",- "",- "--date cmp timespec, -d cmp timespec",- " Date must be within the time frame given. timespec",- " is a day or a day and a time. Valid values for cmp:",- " <, >, <=, >=, ==, /=, !=",- "--current",- " Same as \"--date <= (right now) \"",- "",- "Serials",- "----------------",- "These options take the form --option cmp num; the given",- "sequence number must fall within the given range. \"rev\"",- "in the option name indicates numbering is from end to beginning.",- "",- "--globalTransaction, --revGlobalTransaction",- " All transactions, after reading the ledger files",- "--globalPosting, --revGlobalPosting",- " All postings, after reading the leder files",- "--fileTransaction, --revFileTransaction",- " Transactions in each ledger file, after reading the files",- " (numbering restarts with each file)",- "--filePosting, --revFilePosting",- " Postings in each ledger file, after reading the files",- " (numbering restarts with each file)",- "",- "Pattern matching",- "----------------",- "",- "-a pattern, --account pattern",- " Pattern must match colon-separated account name",- "--account-level num pat",- " Pattern must match sub account at given level",- "--account-any pat",- " Pattern must match sub account at any level",- "-p pattern, --payee pattern",- " Payee must match pattern",- "-t pattern, --tag pattern",- " Tag must match pattern",- "--number pattern",- " Number must match pattern",- "--flag pattern",- " Flag must match pattern",- "--commodity pattern",- " Pattern must match colon-separated commodity name",- "--commodity-level num pattern",- " Pattern must match sub commodity at given level",- "--commodity-any pattern",- " Pattern must match sub commodity at any level",- "--posting-memo pattern",- " Posting memo must match pattern",- "--transaction-memo pattern",- " Transaction memo must match pattern",- "",- "Other posting characteristics",- "-----------------------------",- "--debit",- " Entry must be a debit",- "--credit",- " Entry must be a credit",- "--qty cmp number",- " Entry quantity must fall within given range",- "",- "Operators - from highest to lowest precedence",- "(all are left associative)",- "--------------------------",- "--open expr --close",- " Force precedence (as in \"open\" and \"close\" parentheses)",- "--not expr",- " True if expr is false",- "expr1 --and expr2 ",- " True if expr and expr2 are both true",- "expr1 --or expr2",- " True if either expr1 or expr2 is true",- "",- "Options affecting patterns",- "--------------------------",- "",- "-i, --case-insensitive",- " Be case insensitive (default)",- "-I, --case-sensitive",- " Be case sensitive",- "",- "--within",- " Use \"within\" matcher (default)",- "--pcre",- " Use \"pcre\" matcher",- "--posix",- " Use \"posix\" matcher",- "--exact",- " Use \"exact\" matcher",- "",- "Removing postings after sorting and filtering",- "---------------------------------------------",- "--head n",- " Keep only the first n postings",- "--tail n",- " Keep only the last n postings",- "",- "Sorting",- "-------",- "",- "-s key, --sort key",- " Sort postings according to key",- "",- "Keys:",- " payee, date, flag, number, account, drCr,",- " qty, commodity, postingMemo, transactionMemo",- "",- " Ascending order by default; for descending order,",- " capitalize the name of the key.",- ""- ]
− Penny/Zinc/Parser.hs
@@ -1,86 +0,0 @@-module Penny.Zinc.Parser (- Defaults.T(..)- , Defaults.defaultFromRuntime- , parser- ) where--import qualified Control.Monad.Exception.Synchronous as Ex-import Data.Monoid (mappend, mconcat)-import qualified Data.Text as X-import qualified Data.Text.IO as StrictIO-import qualified Data.Text.Lazy.IO as LazyIO-import System.Console.MultiArg.Prim (Parser)-import System.Exit (exitSuccess, exitFailure)-import System.IO (stderr, hPutStrLn)-import qualified Penny.Cabin.Interface as I-import qualified Penny.Shield as S-import qualified Penny.Zinc.Help as Help-import qualified Penny.Zinc.Parser.Filter as F-import qualified Penny.Zinc.Parser.Report as R-import qualified Penny.Zinc.Parser.Ledgers as L-import qualified Penny.Zinc.Parser.Defaults as Defaults--import Penny.Copper.DateTime (DefaultTimeZone)-import Penny.Copper.Qty (RadGroup)---- | Parses all command line options and arguments. Returns an IO--- action which will print appropriate error messages if something--- failed, or will print a report and exit successfully if everything--- went well.-parser ::- S.Runtime- -> DefaultTimeZone- -> RadGroup- -> (S.Runtime -> Defaults.T)- -> [I.Report]- -> Parser (IO ())-parser rt dtz rg getDf rs = do- let df = getDf rt- errFilt <- F.parseFilter df- case errFilt of- Ex.Exception e -> return $ do- hPutStrLn stderr $ ("penny: error: " ++ show e)- exitFailure- Ex.Success r -> case r of- Left F.NeedsHelp -> return $ do- StrictIO.putStrLn . helpText $ rs- exitSuccess- Right rslt -> parseReportsAndFilesAndPrint rt dtz rg rs rslt- ---- | Returns an IO action that will parse the report options and the--- files on the command line and print the resulting report.-parseReportsAndFilesAndPrint ::- S.Runtime- -> DefaultTimeZone- -> RadGroup- -> [I.Report]- -> F.Result- -> Parser (IO ())-parseReportsAndFilesAndPrint rt dtz rg rs rslt = do- let (F.Result factory sensitive sortFilt) = rslt- parserFunc <- R.report rs- let reportFunc = parserFunc rt sensitive factory- filenames <- L.filenames- return $ do- ledgers <- L.readLedgers filenames- let parsed = L.parseLedgers dtz rg ledgers- case parsed of- Ex.Exception e -> do- hPutStrLn stderr . show $ e- exitFailure- Ex.Success (txns, prices) -> do- let boxes = sortFilt txns- case reportFunc boxes prices of- Ex.Exception bad -> do- StrictIO.putStrLn bad- exitFailure- Ex.Success good -> do- LazyIO.putStr good- exitSuccess--helpText ::- [I.Report]- -> X.Text-helpText = mappend Help.help . mconcat . fmap I.help-
− Penny/Zinc/Parser/Defaults.hs
@@ -1,29 +0,0 @@-module Penny.Zinc.Parser.Defaults where--import qualified Text.Matchers.Text as M-import qualified Penny.Lincoln as L-import qualified Penny.Copper as Cop-import qualified Penny.Shield as S-import qualified Data.Text as X-import qualified Control.Monad.Exception.Synchronous as Ex--data T =- T { sensitive :: M.CaseSensitive- , factory :: M.CaseSensitive -> X.Text- -> Ex.Exceptional X.Text (X.Text -> Bool)- , currentTime :: L.DateTime- , defaultTimeZone :: Cop.DefaultTimeZone- , radGroup :: Cop.RadGroup }--defaultFromRuntime ::- Cop.DefaultTimeZone- -> Cop.RadGroup- -> S.Runtime- -> T-defaultFromRuntime dtz rg rt =- T { sensitive = M.Insensitive- , factory = (\c t -> return (M.within c t))- , currentTime = S.currentTime rt- , defaultTimeZone = dtz- , radGroup = rg }-
− Penny/Zinc/Parser/Filter.hs
@@ -1,265 +0,0 @@-module Penny.Zinc.Parser.Filter (- parseFilter- , Error(LibertyError, TokenParseError)- , NeedsHelp(NeedsHelp)- , Result(Result, resultFactory, resultSensitive, sorterFilterer)- ) where--import Control.Applicative ((<|>), (<$>), Applicative, pure, many)-import Control.Monad ((>=>))-import qualified Control.Monad.Exception.Synchronous as Ex-import Data.Monoid (mempty, mappend)-import Data.Text (Text)-import qualified Text.Matchers.Text as M-import qualified System.Console.MultiArg.Combinator as C-import System.Console.MultiArg.Prim (Parser)--import qualified Penny.Copper as Cop-import qualified Penny.Lincoln as L-import qualified Penny.Liberty as Ly-import qualified Penny.Liberty.Expressions as X--import qualified Penny.Zinc.Parser.Defaults as D-import qualified Penny.Zinc.Parser.Defaults as Defaults---- | Parses all filtering options. Returns a parser that contains an--- Exception if some error occurred after parsing the options, or a--- Success with a result if the parse was successful.-parseFilter ::- Defaults.T- -> Parser (Ex.Exceptional Error (Either NeedsHelp Result))-parseFilter d = fmap f (many parser) where- f ls =- let k = foldl (>=>) return ls- in case k (newState d) of- Ex.Success st' ->- if help st'- then return . Left $ NeedsHelp- else- case Ly.parsePredicate . tokens $ st' of- Nothing -> Ex.throw TokenParseError- Just pdct ->- let fn = Ly.xactionsToFiltered pdct- (postFilter st') (orderer st')- r = Result { resultFactory = factory st'- , resultSensitive = sensitive st'- , sorterFilterer = fn }- in return . Right $ r- Ex.Exception e -> Ex.Exception e--data Error = LibertyError Ly.Error- | TokenParseError- deriving Show---- | Returned if the user requested help.-data NeedsHelp = NeedsHelp- deriving Show---- | Indicates the result of a successful parse of filtering options.-data Result =- Result { resultFactory :: M.CaseSensitive- -> Text -> Ex.Exceptional Text (Text -> Bool)- -- ^ The factory indicated, so that it can be used in- -- subsequent parses of the same command line.-- , resultSensitive :: M.CaseSensitive- -- ^ Indicated case sensitivity, so that it can be used in- -- subsequent parses of the command line.- - , sorterFilterer :: [L.Transaction] -> [L.Box Ly.LibertyMeta]- -- ^ Applied to a list of Transaction, will sort and filter- -- the transactions and assign them LibertyMeta.- }---data State =- State { sensitive :: M.CaseSensitive- , factory :: M.CaseSensitive- -> Text -> Ex.Exceptional Text (Text -> Bool)- , tokens :: [X.Token (L.PostFam -> Bool)]- , postFilter :: [Ly.PostFilterFn]- , orderer :: Ly.Orderer- , help :: Bool- , currentTime :: L.DateTime- , defaultTimeZone :: Cop.DefaultTimeZone- , radGroup :: Cop.RadGroup }--newState ::- Defaults.T- -> State-newState d =- State { sensitive = D.sensitive d- , factory = D.factory d- , tokens = []- , postFilter = []- , orderer = mempty- , help = False- , currentTime = D.currentTime d- , defaultTimeZone = D.defaultTimeZone d- , radGroup = D.radGroup d }--parser :: Parser (State -> Ex.Exceptional Error State)-parser =- operand- <|> parsePostFilter- <|> impurify parseMatcherSelect- <|> impurify parseCaseSelect- <|> impurify parseOperator- <|> parseSort- <|> impurify parseHelp--option :: [String] -> [Char] -> C.ArgSpec a -> Parser a-option ss cs a = C.parseOption [C.OptSpec ss cs a]--operand :: Parser (State -> Ex.Exceptional Error State)-operand = f <$> Ly.parseOperand- where- f lyFn =- let g st =- let r = lyFn (currentTime st) (defaultTimeZone st)- (radGroup st) (sensitive st) (factory st)- in case r of- Ex.Exception e -> Ex.throw . LibertyError $ e- Ex.Success (X.Operand o) ->- let tok' = tokens st ++ [X.TokOperand o]- in return st { tokens = tok' }- in g- -parsePostFilter :: Parser (State -> Ex.Exceptional Error State)-parsePostFilter = f <$> Ly.parsePostFilter- where- f lyResult =- let g st = case lyResult of- Ex.Exception e -> Ex.throw . LibertyError $ e- Ex.Success pf ->- let ls' = postFilter st ++ [pf]- in return st { postFilter = ls' }- in g--impurify ::- (Functor f, Applicative a)- => f (b -> b)- -> f (b -> a b)-impurify = fmap (pure .)--parseMatcherSelect :: Parser (State -> State)-parseMatcherSelect = f <$> Ly.parseMatcherSelect- where- f fty = g- where- g st = st { factory = fty }--parseCaseSelect :: Parser (State -> State)-parseCaseSelect = f <$> Ly.parseCaseSelect- where- f sel = g- where- g st = st { sensitive = sel }--parseOperator :: Parser (State -> State)-parseOperator = f <$> Ly.parseOperator- where- f tok = g- where- g st = st { tokens = tokens st ++ [tok] }--parseSort :: Parser (State -> Ex.Exceptional Error State)-parseSort = f <$> Ly.parseSort- where- f exOrd = g- where- g st = case exOrd of- Ex.Exception e -> Ex.throw . LibertyError $ e- Ex.Success o ->- return st { orderer = mappend o (orderer st) }--parseHelp :: Parser (State -> State)-parseHelp = option ["help"] ['h'] (C.NoArg f)- where- f st = st { help = True }--{--wrapLiberty ::- DefaultTimeZone- -> DateTime- -> RadGroup- -> State- -> ParserE Error State-wrapLiberty dtz dt rg st = let- toLibSt = LF.State { LF.sensitive = sensitive st- , LF.factory = factory st- , LF.tokens = tokens st- , LF.postFilter = postFilter st }- fromLibSt libSt = State { sensitive = LF.sensitive libSt- , factory = LF.factory libSt- , tokens = LF.tokens libSt- , postFilter = LF.postFilter libSt- , orderer = orderer st- , help = help st }- in fromLibSt <$> LF.parseOption dtz dt rg toLibSt--wrapOrderer :: State -> ParserE Error State-wrapOrderer st = mkSt <$> S.sort where- mkSt o = st { orderer = o `mappend` (orderer st) }--helpOpt :: ParserE Error ()-helpOpt = do- let lo = makeLongOpt . pack $ "help"- so = makeShortOpt 'h'- _ <- mixedNoArg lo [] [so]- return ()--wrapHelp :: State -> ParserE Error State-wrapHelp st = (\_ -> st { help = Help }) <$> helpOpt--parseOption ::- DefaultTimeZone- -> DateTime- -> RadGroup- -> State- -> ParserE Error State-parseOption dtz dt rg st =- wrapLiberty dtz dt rg st- <|> wrapOrderer st- <|> wrapHelp st--parseOptions ::- DefaultTimeZone- -> DateTime- -> RadGroup- -> State- -> ParserE Error State-parseOptions dtz dt rg st =- option st $ do- rs <- runUntilFailure (parseOption dtz dt rg) st- if null rs then return st else return (last rs)--parseFilter ::- DefaultTimeZone- -> DateTime- -> RadGroup- -> ParserE Error (Either NeedsHelp Result)-parseFilter dtz dt rg = do- st' <- parseOptions dtz dt rg newState- case help st' of- Help -> return . Left $ NeedsHelp- NoHelp -> do- p <- case Oo.getPredicate (tokens st') of- Just pr -> return pr- Nothing -> throw E.BadExpression- let f = sortFilterAndPostFilter (orderer st') p (postFilter st')- r = Result { resultFactory = factory st'- , resultSensitive = sensitive st'- , sorterFilterer = f }- return . Right $ r--sortFilterAndPostFilter ::- S.Orderer- -> (T.PostingInfo -> Bool)- -> ([T.PostingInfo] -> [T.PostingInfo])- -> [PostingBox] -> [T.PostingInfo]-sortFilterAndPostFilter o p pf =- pf- . filter p- . PSq.sortedPostingInfos o--}
− Penny/Zinc/Parser/Ledgers.hs
@@ -1,79 +0,0 @@-module Penny.Zinc.Parser.Ledgers (- filenames- , parseLedgers- , readLedgers- ) where--import Control.Applicative ((<$>), many, optional)-import Control.Monad (when)-import qualified Control.Monad.Exception.Synchronous as Ex-import Data.Text (Text, pack, unpack)-import qualified Data.Text.IO as TIO--import qualified Penny.Copper as C-import qualified Penny.Lincoln as L-import qualified Penny.Zinc.Error as ZE-import System.Console.MultiArg.Prim (Parser, nextArg)-import System.IO (hIsTerminalDevice, stdin, stderr, hPutStrLn)---warnTerminal :: IO ()-warnTerminal =- hPutStrLn stderr $ "zinc: warning: reading from standard input, "- ++ "which is a terminal"--data Filename =- Filename Text- | Stdin---- | Converts a Ledgers filename to a Lincoln filename.-convertFilename :: Filename -> L.Filename-convertFilename (Filename x) = L.Filename x-convertFilename Stdin = L.Filename . pack $ "<stdin>"---- | Actually reads the file off disk. For now just let this crash if--- any of the IO errors occur.-ledgerText :: Filename -> IO Text-ledgerText f = case f of- Stdin -> do- isTerm <- hIsTerminalDevice stdin- when isTerm warnTerminal- TIO.hGetContents stdin- Filename fn -> TIO.readFile (unpack fn)--readLedgers :: [Filename] -> IO [(Filename, Text)]-readLedgers = mapM f where- f fn = (\txt -> (fn, txt)) <$> ledgerText fn--parseLedgers ::- C.DefaultTimeZone- -> C.RadGroup- -> [(Filename, Text)]- -> Ex.Exceptional ZE.Error ([L.Transaction], [L.PricePoint])-parseLedgers dtz rg ls =- let toPair (f, t) = (convertFilename f, C.FileContents t)- parsed = C.parse dtz rg (map toPair ls)- folder i (ts, ps) = case snd i of- C.Transaction t -> (t:ts, ps)- C.Price p -> (ts, p:ps)- _ -> (ts, ps)- toResult (C.Ledger is) = foldr folder ([], []) is- toErr (C.ErrorMsg x) = ZE.ParseError x- in Ex.mapExceptional toErr toResult parsed---filename :: Parser Filename-filename = f <$> nextArg- where- f a = if a == "-"- then Stdin- else Filename . pack $ a--filenames :: Parser [Filename]-filenames = do- fn1 <- optional filename- case fn1 of- Nothing -> return [Stdin]- Just fn -> do- fns <- many filename- return (fn:fns)
− Penny/Zinc/Parser/Report.hs
@@ -1,30 +0,0 @@-module Penny.Zinc.Parser.Report (report) where--import System.Console.MultiArg.Prim (Parser)-import qualified System.Console.MultiArg.Combinator as C--import qualified Penny.Cabin.Interface as I-import qualified Data.Set as Set---- | Given a Runtime and a list of available Reports, returns a Parser--- that will return a function corresponding to the report the user--- desires. The parser returned assumes that the filtering options--- have already been parsed. The returned parser parses all options--- corresponding to report options in order to return the function.-report ::- [I.Report]- -- ^ List of available Reports-- -> Parser I.ReportFunc-report rs = do- let toPair r = (I.name r, I.parseReport r)- alist = map toPair rs- set = Set.fromList . map fst $ alist- (_, n) <- C.matchApproxWord set- case lookup n alist of- Nothing -> error $ "Penny.Zinc.Parser.Report: error: "- ++ "report not found"- Just rptFunc -> rptFunc---
penny-lib.cabal view
@@ -1,9 +1,9 @@ Name: penny-lib-Version: 0.4.0.0+Version: 0.6.0.0 Cabal-version: >=1.8 Build-Type: Simple-License: MIT-Copyright: 2012 Omari Norman.+License: BSD3+Copyright: 2012-2013 Omari Norman. author: Omari Norman maintainer: omari@smileystation.com stability: Experimental@@ -11,280 +11,130 @@ bug-reports: omari@smileystation.com 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/>. You will want to install the package- penny-bin by using cabal install penny-bin, which will install the- executable program and this library as well. Features:-- .-- * Penny is a double-entry accounting system. It uses traditional- accounting terminology, such as the terms \"Debit\" and- \"Credit\". If you need a refresher on the basics of double-entry- accounting, pick up a used accounting textbook from your favorite- bookseller (they can be had cheaply, for less than ten U.S. dollars- including shipping) or check out- <http://www.principlesofaccounting.com/>, a great free online text.-- .-- * Penny is based around "Penny.Lincoln", a core library to represent- transactions and postings and their components, such as their- amounts and whether they are debits and credits. You can use- Lincoln all by itself even if you don't use the other components- of Penny, which you may find handy if you are a Haskell- programmer. I wrote Penny because I wanted a precise library to- represent my accounting data so I could analyze it programatically- and verify its consistency. I wrote it in Haskell not because I- wanted to write something in Haskell but because Haskell is the- best tool for this job (I used to use the shell, combined with- Ledger, which is a messy combination.)-- .-- * Penny's command line interface, Zinc, and its reports, Cabin, give- you unparalleled flexibility to filter and sort postings. Each- posting within a transaction may have its own flags assigned- (e.g. to indicate whether the posting is cleared) and each posting- may have infinite \"tags\" assigned to it, giving you another way- to categorize your postings. For instance, you might have vacation- related postings in several different accounts, but you can give- them all a \"vacation\" tag.-- .-- * Full Unicode support. Also, you may set which characters you wish- to use to represent the radix point and the digit grouping- character in your ledger file.-- .-- * Penny's reports, in "Penny.Cabin", have color baked in from the- beginning. You do not have to use color, though, which is handy if- you are sending output to a file or if, well, you just don't like- color.-- .-- * Penny's reports are customizable in Haskell, giving you an easy- and powerful way to make your own reports without writing cryptic- formatting strings.-- .-- * Penny handles multiple commodities (for example, multiple- currencies, stocks and bonds, tracking other assets, etc.) in an- easy and transparent way that is consistent with double-entry- accounting principles. It embraces the philosophy outlined in this- tutorial on multiple commodity accounting:- <http://www.mscs.dal.ca/~selinger/accounting/tutorial.html>.-- .-- * Penny stores amounts using only integers, building from the- Data.Decimal library available at- <http://hackage.haskell.org/package/Decimal>. This ensures the- accuracy of your data, as using floating point values to represent- money is a bad idea. Here is one explanation:- <http://stackoverflow.com/questions/3730019/why-not-use-double-or-float-to-represent-currency>. The- use of integer arithmetic also makes Penny simpler internally, as- there is no need for arbitrary rounding to compensate for the- bizarre and inaccurate results that sometimes arise from the use of- floating-point values to represent currencies.-- .-- * Freely licensed under the MIT license. If you take this code,- improve it, lock it up and make it proprietary, and sell it,- AWESOME! I haven't lost anything because I still have my code and,- what's more, then maybe I can buy your product and not have to- maintain this one any more!-- .-- * Uses no GHC extensions. However, the code is only tested under GHC- and for all practical purposes it will only run under GHC at this- time because it uses libraries such as Data.Text that are- available only under GHC. Despite this I expect I will continue to- avoid language extensions.-- .-- Non-features / disadvantages:-- .-- * Written in Haskell. Yes, I think Haskell is the best tool ever,- but its compiler is not as commonly installed as compilers for C- or C++, and non-Haskellers will probably find Penny to be more- difficult to install than Ledger, as the latter is written in C++.-- .-- * Handling commodities requires that you set up multiple accounts;- some might find this cumbersome.-- .-- * Young and not well tested yet. Also, only tested on Unix. It- probably would not be difficult to make Penny run on Windows; if- someone wants to do that, go ahead.-- .-- * Full Penny functionality is available without a Haskell compiler;- you could even use a pre-compiled binary. However, to fully- customize Penny, you will need a Haskell compiler installed.-- .-- * Can be slow and memory hungry with large data sets. I have a- ledger file with about 28,000 lines. On my least capable machine- (which has an Intel Core 2 Duo at 1.6 GHz) this takes about 1.4- seconds to parse. Not horrible but not instantaneous- either. Generating a report about all these transactions can take- about seven seconds and a little less than 300 MB of memory. I- have eliminated all the obvious slowness from the code and- attempted a rewrite of the parser, which made no difference; other- ideas to speed up Penny with large data sets would involve- substantial changes and this is not at the top of my list because- the program is currently usable with relatively recent hardware.+ at <http://ledger-cli.org/>. . - More details about the organization of the Penny modules is- available by examining the top "Penny" module.+ This package is a library. To start using Penny you will want to+ install the penny-bin package, which has the executable programs. It+ is available on Hackage and by running "cabal install penny-bin". . - This is only a library. For the executable package, which also- includes more documentation, search for the penny-bin package on- Hackage.+ The Penny library is a full system to work with double-entry+ accounting transactions and postings and to make reports with them. source-repository head- type: git- location: git://github.com/massysett/penny.git+ type: git+ location: git://github.com/massysett/penny.git Library- Build-depends:- base ==4.*,- text ==0.11.*,- explicit-exception ==0.1.*,- containers ==0.4.*,- transformers == 0.2.*,- time ==1.2.*,- Decimal >= 0.2.2 && < 0.3,- parsec >= 3.1.2 && < 3.2,- multiarg ==0.4.*,- matchers ==0.2.*,- old-locale ==1.0.*,- semigroups ==0.8.*,- split ==0.1.*+ Build-depends:+ base ==4.*+ , bytestring ==0.9.*+ , cereal ==0.3.*+ , containers ==0.4.*+ , explicit-exception ==0.1.*+ , matchers ==0.4.*+ , monad-loops ==0.3.*+ , multiarg ==0.8.*+ , old-locale ==1.0.*+ , parsec >= 3.1.2 && < 3.2+ , pretty-show ==1.2.*+ , semigroups ==0.8.*+ , split ==0.2.*+ , strict ==0.3.*+ , terminfo == 0.3.*+ , text ==0.11.*+ , time ==1.4.*+ , transformers == 0.3.* - Exposed-modules:- Penny,- Penny.Cabin,- Penny.Cabin.Allocate,- Penny.Cabin.Balance,- Penny.Cabin.Balance.Parser,- Penny.Cabin.Balance.Help,- Penny.Cabin.Balance.Tree,- Penny.Cabin.Chunk,- Penny.Cabin.Chunk.Switch,- Penny.Cabin.Colors,- Penny.Cabin.Colors.DarkBackground,- Penny.Cabin.Colors.LightBackground,- Penny.Cabin.Interface,- Penny.Cabin.Meta,- Penny.Cabin.Options,- Penny.Cabin.Posts,- Penny.Cabin.Posts.Allocate,- Penny.Cabin.Posts.Allocated,- Penny.Cabin.Posts.BottomRows,- Penny.Cabin.Posts.Fields,- Penny.Cabin.Posts.Growers,- Penny.Cabin.Posts.Chunk,- Penny.Cabin.Posts.Help,- Penny.Cabin.Posts.Meta,- Penny.Cabin.Posts.Parser,- Penny.Cabin.Posts.Spacers,- Penny.Cabin.Posts.Types,- Penny.Cabin.Row,- Penny.Cabin.TextFormat,- Penny.Copper,- Penny.Copper.Account,- Penny.Copper.Amount,- Penny.Copper.Comments,- Penny.Copper.Commodity,- Penny.Copper.DateTime,- Penny.Copper.Entry,- Penny.Copper.Flag,- Penny.Copper.Item,- Penny.Copper.Memos.Posting,- Penny.Copper.Memos.Transaction,- Penny.Copper.Number,- Penny.Copper.Payees,- Penny.Copper.Posting,- Penny.Copper.Price,- Penny.Copper.Qty,- Penny.Copper.Tags,- Penny.Copper.TopLine,- Penny.Copper.Transaction,- Penny.Copper.Util,- Penny.Liberty,- Penny.Liberty.Expressions,- Penny.Liberty.Expressions.Infix,- Penny.Liberty.Expressions.RPN,- Penny.Lincoln,- Penny.Lincoln.Balance,- Penny.Lincoln.Bits,- Penny.Lincoln.Bits.Account,- Penny.Lincoln.Bits.Amount,- Penny.Lincoln.Bits.Commodity,- Penny.Lincoln.Bits.DateTime,- Penny.Lincoln.Bits.DrCr,- Penny.Lincoln.Bits.Entry,- Penny.Lincoln.Bits.Flag,- Penny.Lincoln.Bits.Memo,- Penny.Lincoln.Bits.Number,- Penny.Lincoln.Bits.Payee,- Penny.Lincoln.Bits.Price,- Penny.Lincoln.Bits.PricePoint,- Penny.Lincoln.Bits.Qty,- Penny.Lincoln.Bits.Tags,- Penny.Lincoln.Builders,- Penny.Lincoln.Family,- Penny.Lincoln.Family.Child,- Penny.Lincoln.Family.Family,- Penny.Lincoln.Family.Siblings,- Penny.Lincoln.HasText,- Penny.Lincoln.Matchers,- Penny.Lincoln.Meta,- Penny.Lincoln.NestedMap,- Penny.Lincoln.Predicates,- Penny.Lincoln.PriceDb,- Penny.Lincoln.Queries,- Penny.Lincoln.Serial,- Penny.Lincoln.TextNonEmpty,- Penny.Lincoln.Transaction,- Penny.Lincoln.Transaction.Unverified,- Penny.Shield,- Penny.Zinc,- Penny.Zinc.Error,- Penny.Zinc.Help,- Penny.Zinc.Parser,- Penny.Zinc.Parser.Defaults,- Penny.Zinc.Parser.Filter,- Penny.Zinc.Parser.Ledgers,- Penny.Zinc.Parser.Report+ Exposed-modules:+ Penny+ , Penny.Brenner+ , Penny.Brenner.Amex+ , Penny.Brenner.BofA+ , Penny.Brenner.Clear+ , Penny.Brenner.Database+ , Penny.Brenner.Import+ , Penny.Brenner.Merge+ , Penny.Brenner.Print+ , Penny.Brenner.Types+ , Penny.Brenner.Util+ , Penny.Cabin+ , Penny.Cabin.Balance+ , Penny.Cabin.Balance.Convert+ , Penny.Cabin.Balance.Convert.Chunker+ , Penny.Cabin.Balance.Convert.Options+ , Penny.Cabin.Balance.Convert.Parser+ , Penny.Cabin.Balance.MultiCommodity+ , Penny.Cabin.Balance.MultiCommodity.Chunker+ , Penny.Cabin.Balance.MultiCommodity.Parser+ , Penny.Cabin.Balance.Util+ , Penny.Cabin.Chunk+ , Penny.Cabin.Chunk.Switch+ , Penny.Cabin.Interface+ , Penny.Cabin.Meta+ , Penny.Cabin.Options+ , Penny.Cabin.Parsers+ , Penny.Cabin.Posts+ , Penny.Cabin.Posts.Allocated+ , Penny.Cabin.Posts.BottomRows+ , Penny.Cabin.Posts.Fields+ , Penny.Cabin.Posts.Growers+ , Penny.Cabin.Posts.Chunk+ , Penny.Cabin.Posts.Meta+ , Penny.Cabin.Posts.Parser+ , Penny.Cabin.Posts.Spacers+ , Penny.Cabin.Posts.Types+ , Penny.Cabin.Row+ , Penny.Cabin.Scheme+ , Penny.Cabin.Scheme.Schemes+ , Penny.Cabin.TextFormat+ , Penny.Copper+ , Penny.Copper.Parsec+ , Penny.Copper.Render+ , Penny.Copper.Terminals+ , Penny.Copper.Types+ , Penny.Liberty+ , Penny.Liberty.Expressions+ , Penny.Liberty.Expressions.Infix+ , Penny.Liberty.Expressions.RPN+ , Penny.Lincoln+ , Penny.Lincoln.Balance+ , Penny.Lincoln.Bits+ , Penny.Lincoln.Bits.DateTime+ , Penny.Lincoln.Bits.Open+ , Penny.Lincoln.Bits.Price+ , Penny.Lincoln.Bits.Qty+ , Penny.Lincoln.Builders+ , Penny.Lincoln.Family+ , Penny.Lincoln.Family.Child+ , Penny.Lincoln.Family.Family+ , Penny.Lincoln.Family.Siblings+ , Penny.Lincoln.HasText+ , Penny.Lincoln.Matchers+ , Penny.Lincoln.NestedMap+ , Penny.Lincoln.Predicates+ , Penny.Lincoln.PriceDb+ , Penny.Lincoln.Queries+ , Penny.Lincoln.Serial+ , Penny.Lincoln.Transaction+ , Penny.Lincoln.Transaction.Unverified+ , Penny.Shield+ , Penny.Zinc - ghc-options: -Wall- if flag(debug)- ghc-options: -auto-all -caf-all + ghc-options: -Wall+ if flag(debug)+ ghc-options: -auto-all -caf-all+ Flag debug- Description: turns on some debugging options- Default: False+ Description: turns on some debugging options+ Default: False+