penny-bin 0.20.0.0 → 0.22.0.0
raw patch · 7 files changed
+99/−281 lines, 7 filesdep ~multiargdep ~penny-lib
Dependency ranges changed: multiarg, penny-lib
Files
- doc/ledger-grammar.org +13/−0
- penny-bin.cabal +13/−165
- penny-diff.hs +38/−45
- penny-main.hs +4/−19
- penny-reconcile.hs +7/−13
- penny-reprint.hs +8/−14
- penny-selloff.hs +16/−25
doc/ledger-grammar.org view
@@ -16,6 +16,7 @@ Letter = UpperCaseAscii ∪ LowerCaseAscii ∪ NonAscii Dollar = { '$' } Colon = { ':' }+Comma = { ',' } OpenCurly = { '{' } CloseCurly = { '}' } OpenSquare = { '[' }@@ -105,6 +106,18 @@ <lvl3Cmdty> ::= Lvl3CmdtyChar+ * Quantities++<digListSpace> ::= Space Digit++<digListThinSpace> ::= ThinSpace Digit++<digListComma> ::= Comma Digit++<digListPeriod> ::= Period Digit+++<digGrpSpace> ::= Digit+ <digListSpace>++<digGrpThinSpace> ::= Digit+ <digListThinSpace>++<digGrpComma> ::= Digit+ <digListComma>++<digGrpPeriod> ::= Digit+ <digListPeriod>+++ <digitGroup> ::= ThinSpace Digit+ <digitSequence> ::= Digit+ <digitGroup>*
penny-bin.cabal view
@@ -1,5 +1,5 @@ Name: penny-bin-Version: 0.20.0.0+Version: 0.22.0.0 Cabal-version: >=1.8 Build-Type: Simple License: BSD3@@ -11,162 +11,10 @@ bug-reports: omari@smileystation.com Category: Console, Finance License-File: LICENSE-synopsis: Extensible double-entry accounting system - binary and documentation--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/>. Installing this package with cabal- install will install the executable program and the necessary- libraries.-- .-- * 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.-- .-- * Penny's command line interface and its reports give you- great 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.-- .-- * You can easily build a program to process downloads of Open- Financial Exchange data from your financial institution. Your- program will merge new transactions into your ledger- automatically.-- .-- * Full Unicode support.-- .-- * Penny's reports have color baked in from the beginning. You do not- have to use color, which is handy if you are sending- output to a file or if, well, you just don't like color.-- .-- * Penny's reports automatically adjust themselves to the width of- your screen. You can easily specify how much or how little data to- see with command line options.-- .-- * 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. 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!-- .-- * Tested using QuickCheck. The tests are available in the Git- repository that also contains the main library. Not everything- is tested, but the tests that exist so far have already rooted- out some strange corner-case bugs.-- .-- 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.-- .-- * Runs only on Unix-like operating systems.-- .-- * Full Penny functionality is available without a Haskell compiler;- you could even use a pre-compiled binary. However, Penny does not- read configuration files at runtime; instead, to change the- default settings, you will need to have GHC installed so that you- can compile a custom binary.-- .-- * 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.-- .+synopsis: Deprecated - use penny package instead - You can install this binary simply by typing "cabal install- penny-bin", which will install this binary along with all the- necessary dependencies. The penny-bin package also has all- documentation. Unfortunately running "cabal install" will not- install the documentation, so you will need to find the downloaded- archive (usually in- "$HOME/.cabal/packages/hackage.haskell.org/penny-bin") and unpack it- to see the documentation. You will want to start by reading the- README file, which will point you to additional documentation and- how to install it if you wish.+description:+ This package is now deprecated. Use the penny package instead. extra-source-files: install-docs@@ -186,7 +34,7 @@ Executable penny Build-depends: base ==4.*,- penny-lib ==0.20.*+ penny-lib ==0.22.* Main-is: penny-main.hs Other-modules: Paths_penny_bin@@ -200,13 +48,13 @@ Executable penny-selloff Build-depends: base == 4.*,- penny-lib ==0.20.*,+ penny-lib ==0.22.*, explicit-exception ==0.1.*, containers ==0.5.*, semigroups ==0.9.*, text ==0.11.*, parsec ==3.1.*,- multiarg ==0.16.*,+ multiarg ==0.18.*, transformers ==0.3.* Main-is: penny-selloff.hs@@ -220,9 +68,9 @@ Executable penny-diff Build-depends: base ==4.*,- penny-lib ==0.20.*,+ penny-lib ==0.22.*, text ==0.11.*,- multiarg ==0.16.*,+ multiarg ==0.18.*, explicit-exception == 0.1.* Main-is: penny-diff.hs@@ -237,8 +85,8 @@ Executable penny-reprint Build-depends: base ==4.*- , multiarg ==0.16.*- , penny-lib ==0.20.*+ , multiarg ==0.18.*+ , penny-lib ==0.22.* , pretty-show ==1.5.* , text ==0.11.* @@ -251,9 +99,9 @@ Executable penny-reconcile Build-depends: base ==4.*- , penny-lib ==0.20.*+ , penny-lib ==0.22.* , text ==0.11.*- , multiarg ==0.16.*+ , multiarg ==0.18.* , explicit-exception ==0.1.* main-is: penny-reconcile.hs
penny-diff.hs view
@@ -1,7 +1,6 @@ module Main where import Control.Arrow (first, second)-import Data.Either (partitionEithers) import Data.Maybe (fromJust) import Data.List (deleteFirstsBy) import qualified System.Console.MultiArg as M@@ -19,11 +18,8 @@ import qualified Paths_penny_bin as PPB -groupingSpecs :: CR.GroupSpecs-groupingSpecs = CR.GroupSpecs CR.NoGrouping CR.NoGrouping- main :: IO ()-main = runPennyDiff groupingSpecs+main = runPennyDiff help :: String -> String help pn = unlines@@ -49,11 +45,8 @@ optFile2 :: M.OptSpec Args optFile2 = M.OptSpec [] "2" (M.NoArg (ArgFile File2)) -allOpts :: [M.OptSpec (Either (IO ()) Args)]-allOpts = [ fmap Right optFile1- , fmap Right optFile2- , fmap Left (Ly.version PPB.version)- ]+allOpts :: [M.OptSpec Args]+allOpts = [ optFile1 , optFile2 ] data File = File1 | File2 deriving (Eq, Show)@@ -94,11 +87,10 @@ -- the line number for the top line for that; otherwise, shows the -- line number for the TopLine. renderTransaction- :: CR.GroupSpecs- -> File+ :: File -> L.Transaction -> Maybe X.Text-renderTransaction gs f t = fmap addHeader $ CR.transaction gs (noMeta t)+renderTransaction f t = fmap addHeader $ CR.transaction Nothing (noMeta t) where lin = case L.tMemo . L.tlCore . fst . L.unTransaction $ t of Nothing -> L.unTopLineLine . L.tTopLineLine . fromJust@@ -109,56 +101,60 @@ noMeta txn = let (tl, es) = L.unTransaction txn in (L.tlCore tl, fmap L.pdCore es) -renderPrice :: CR.GroupSpecs -> File -> L.PricePoint -> Maybe X.Text-renderPrice gs f p = fmap addHeader $ CR.price gs p+renderPrice :: File -> L.PricePoint -> Maybe X.Text+renderPrice f p = fmap addHeader $ CR.price p where lin = L.unPriceLine . fromJust . L.priceLine $ p addHeader x = (showLineNum f lin) `X.append` x -renderNonBlankItem :: CR.GroupSpecs -> File -> NonBlankItem -> Maybe X.Text-renderNonBlankItem gs f =- S.caseS3 (renderTransaction gs f) (renderPrice gs f) CR.comment+renderNonBlankItem+ :: File+ -> NonBlankItem+ -> Maybe X.Text+renderNonBlankItem f =+ S.caseS3 (renderTransaction f) (renderPrice f) CR.comment -runPennyDiff :: CR.GroupSpecs -> IO ()-runPennyDiff co = do+runPennyDiff :: IO ()+runPennyDiff = do (f1, f2, dts) <- parseCommandLine l1 <- C.open [f1] l2 <- C.open [f2] let (r1, r2) = doDiffs l1 l2- showDiffs co dts (r1, r2)+ showDiffs dts (r1, r2) case (r1, r2) of ([], []) -> E.exitSuccess _ -> E.exitWith (E.ExitFailure 1) showDiffs- :: CR.GroupSpecs- -> DiffsToShow+ :: DiffsToShow -> ([NonBlankItem], [NonBlankItem]) -> IO ()-showDiffs co dts (l1, l2) =+showDiffs dts (l1, l2) = case dts of File1Only -> showFile1 File2Only -> showFile2 BothFiles -> showFile1 >> showFile2 where- showFile1 = showNonBlankItems co File1 l1- showFile2 = showNonBlankItems co File2 l2+ showFile1 = showNonBlankItems File1 l1+ showFile2 = showNonBlankItems File2 l2 failure :: String -> IO a failure s = IO.hPutStrLn IO.stderr s >> E.exitWith (E.ExitFailure 2) showNonBlankItems- :: CR.GroupSpecs- -> File+ :: File -> [NonBlankItem] -> IO ()-showNonBlankItems o f ls =- mapM_ (showNonBlankItem o f) ls+showNonBlankItems f ls =+ mapM_ (showNonBlankItem f) ls -showNonBlankItem :: CR.GroupSpecs -> File -> NonBlankItem -> IO ()-showNonBlankItem co f nbi = maybe e TIO.putStr- (renderNonBlankItem co f nbi)+showNonBlankItem+ :: File+ -> NonBlankItem+ -> IO ()+showNonBlankItem f nbi = maybe e TIO.putStr+ (renderNonBlankItem f nbi) where e = failure $ "could not render item: " ++ show nbi @@ -181,22 +177,19 @@ -- an indication of which differences to show. parseCommandLine :: IO (String, String, DiffsToShow) parseCommandLine = do- as <- M.simpleWithHelp help M.Intersperse allOpts- (return . Right . Filename)- let (doVer, args) = partitionEithers as- toFilename a = case a of+ as <- M.simpleHelpVersion help (Ly.version PPB.version)+ allOpts M.Intersperse+ (return . Filename)+ let toFilename a = case a of Filename s -> Just s _ -> Nothing- case doVer of- [] -> return ()- x:_ -> x- (fn1, fn2) <- case mapMaybe toFilename args of+ (fn1, fn2) <- case mapMaybe toFilename as of x:y:[] -> return (x, y) _ -> failure "penny-diff: error: you must supply two filenames." let getDiffs- | ((ArgFile File1) `elem` args)- && ((ArgFile File2) `elem` args) = BothFiles- | ((ArgFile File1) `elem` args) = File1Only- | ((ArgFile File2) `elem` args) = File2Only+ | ((ArgFile File1) `elem` as)+ && ((ArgFile File2) `elem` as) = BothFiles+ | ((ArgFile File1) `elem` as) = File1Only+ | ((ArgFile File2) `elem` as) = File2Only | otherwise = BothFiles return (fn1, fn2, getDiffs)
penny-main.hs view
@@ -46,10 +46,10 @@ -- If this list is empty, then by default postings are left in the -- same order as they appear in the ledger files. - , balanceFormat = const defaultQtyFormat- -- ^ How to format balances in the balance report. Change this- -- function if, for example, you want to allow for digit- -- grouping. The default function does not perform digit grouping.+ , formatQty = qtyFormatter $ S3a Period+ -- ^ How to format quantities. This affects only quantities that+ -- are not parsed from the ledger. Examples include calculated+ -- totals and inferred quantities. Affects all reports. , balanceShowZeroBalances = False -- ^ Show zero balances in the balance report? If True, show them;@@ -86,11 +86,6 @@ -- SortByQty -- SortByName - , convertFormat = const defaultQtyFormat- -- ^ How to format balances in the convert report. For instance,- -- this function might perform digit grouping. The default- -- function does not perform any digit grouping.- , postingsFields = fields -- ^ Fields to show by default in the postings report. @@ -105,16 +100,6 @@ , postingsDateFormat = yearMonthDay -- ^ How to format dates in the postings report.-- , postingsQtyFormat = qtyAsIs- -- ^ 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 = balanceAsIs- -- ^ How to format balance totals in the postings report. , postingsSubAccountLength = 2 -- ^ Account names in the postings report are shortened if
penny-reconcile.hs view
@@ -1,5 +1,6 @@ module Main where +import Data.Either (partitionEithers) import Data.Maybe (fromMaybe, fromJust) import qualified Data.Text as X import Control.Monad (guard)@@ -56,27 +57,20 @@ , " --version - Show version and exit" ] -groupSpecs :: C.GroupSpecs-groupSpecs = C.GroupSpecs C.NoGrouping C.NoGrouping--type ShowVer = IO () type Printer = X.Text -> IO () type PosArg = String-type Arg = S.S3 ShowVer Printer PosArg+type Arg = Either Printer PosArg allOpts :: [MA.OptSpec Arg]-allOpts =- [ fmap S.S3a $ Ly.version PPB.version- , fmap S.S3b Ly.output- ]+allOpts = [ fmap Left Ly.output ] main :: IO () main = do- as <- MA.simpleWithHelp help MA.Intersperse allOpts (fmap return S.S3c)- let (showVer, printers, posArgs) = S.partitionS3 as- sequence_ showVer+ as <- MA.simpleHelpVersion help (Ly.version PPB.version)+ allOpts MA.Intersperse (return . Right)+ let (printers, posArgs) = partitionEithers as led <- C.open posArgs let led' = map (S.mapS4 changeTransaction id id id) led- rend = fromJust $ mapM (C.item groupSpecs) (map C.stripMeta led')+ rend = fromJust $ mapM (C.item Nothing) (map C.stripMeta led') let txt = X.concat rend in txt `seq` (Ly.processOutput printers txt)
penny-reprint.hs view
@@ -1,11 +1,11 @@ module Main where +import Data.Either (partitionEithers) import qualified Penny.Copper as C import qualified Penny.Copper.Render as R import qualified Penny.Liberty as Ly import qualified Data.Text as X import qualified System.Console.MultiArg as MA-import qualified Penny.Steel.Sums as S import qualified Paths_penny_bin as PPB @@ -30,28 +30,22 @@ , " --version - show version and exit" ] -groupSpecs :: R.GroupSpecs-groupSpecs = R.GroupSpecs R.NoGrouping R.NoGrouping--type MaybeShowVer = IO () type Printer = X.Text -> IO () type PosArg = String -type Arg = S.S3 MaybeShowVer Printer PosArg+type Arg = Either Printer PosArg allOpts :: [MA.OptSpec Arg]-allOpts =- [ fmap S.S3a $ Ly.version PPB.version- , fmap S.S3b $ Ly.output- ]+allOpts = [ fmap Left Ly.output ] main :: IO () main = do- as <- MA.simpleWithHelp help MA.Intersperse allOpts (fmap return S.S3c)- let (showVers, printers, posArgs) = S.partitionS3 as- sequence_ showVers+ as <- MA.simpleHelpVersion help (Ly.version PPB.version)+ allOpts MA.Intersperse+ (return . Right)+ let (printers, posArgs) = partitionEithers as l <- C.open posArgs- case mapM (R.item groupSpecs) (map C.stripMeta l) of+ case mapM (R.item Nothing) (map C.stripMeta l) of Nothing -> error "could not render final ledger." Just x -> let txt = X.concat x
penny-selloff.hs view
@@ -117,9 +117,7 @@ import qualified System.Console.MultiArg as MA import qualified Text.Parsec as Parsec import qualified Paths_penny_bin as PPB--groupingSpec :: CR.GroupSpecs-groupingSpec = CR.GroupSpecs CR.NoGrouping CR.NoGrouping+import qualified Penny as P type Err = Ex.Exceptional Error @@ -146,24 +144,13 @@ newtype InputFilename = InputFilename { _unInputFilename :: String } deriving (Eq, Show) -type MaybeShowVer = IO ()-type Opts = (MaybeShowVer, [String])--posArg :: String -> Opts -> Opts-posArg s (a, ss) = (a, s:ss)--allOpts :: [MA.OptSpec (Opts -> Opts)]-allOpts = [ fmap (\a (_, ss) -> (a, ss)) $ Ly.version PPB.version ]- data ParseResult = ParseResult ProceedsAcct [Cop.LedgerItem] parseCommandLine :: IO ParseResult parseCommandLine = do- as <- MA.simpleWithHelp help MA.Intersperse allOpts- (fmap return posArg)- let opts = foldr ($) (return (), []) as- fst opts- x:xs <- case snd opts of+ as <- MA.simpleHelpVersion help (Ly.version PPB.version)+ [] MA.Intersperse return+ x:xs <- case as of [] -> fail (show NoInputArgs) r -> return r a <- Ex.switch (fail . show . ProceedsParseFailed) return@@ -197,11 +184,11 @@ newtype SaleDate = SaleDate { unSaleDate :: L.DateTime } deriving (Show, Eq) -newtype SelloffStock = SelloffStock { unSelloffStock :: L.Amount }+newtype SelloffStock = SelloffStock { unSelloffStock :: L.Amount L.Qty } deriving (Show, Eq) newtype SelloffCurrency- = SelloffCurrency { unSelloffCurrency :: L.Amount }+ = SelloffCurrency { unSelloffCurrency :: L.Amount L.Qty } deriving (Show, Eq) data SelloffInfo = SelloffInfo@@ -481,7 +468,7 @@ :: SelloffInfo -> PurchaseDate -> BasisRealiztn- -> ((L.Entry, L.PostingData), (L.Entry, L.PostingData))+ -> ((L.Entry L.Qty, L.PostingData), (L.Entry L.Qty, L.PostingData)) basisOffsets s pd p = (po enDr, po enCr) where ac = L.Account [basis, grp, dt]@@ -538,7 +525,7 @@ :: GainOrLoss -> SelloffCurrency -> CapitalChange- -> L.Entry+ -> L.Entry L.Qty capChangeEntry gl sc cc = L.Entry dc (L.Amount qt cy) where dc = case gl of@@ -552,7 +539,7 @@ -> GainOrLoss -> CapitalChange -> PurchaseInfo- -> (L.Entry, L.PostingData)+ -> (L.Entry L.Qty, L.PostingData) capChangePstg si gl cc p = (en, emptyPostingData ac) where ac = capChangeAcct gl si p@@ -563,7 +550,7 @@ proceedsPstgs :: SelloffInfo- -> ((L.Entry, L.PostingData), (L.Entry, L.PostingData))+ -> ((L.Entry L.Qty, L.PostingData), (L.Entry L.Qty, L.PostingData)) proceedsPstgs si = (po dr, po cr) where po en = (en, emptyPostingData ac)@@ -597,7 +584,7 @@ where (b1, b2) = basisOffsets si (piDate p) br c = capChangePstg si gl cc p- entInputs = map (first Just) (p1:p2:ps)+ entInputs = map (first (Just . Right)) (p1:p2:ps) makeOutput :: ProceedsAcct@@ -605,6 +592,7 @@ -> Err X.Text makeOutput pa ldgr = do let bals = calcBalances ldgr+ formatter = P.getQtyFormat defaultRadGroup ldgr si <- selloffInfo pa bals let basisAccts = findBasisAccounts (siGroup si) bals purchInfos <- mapM (purchaseInfo (siStock si) (siCurrency si))@@ -614,7 +602,7 @@ return . (`X.snoc` '\n') . fromMaybe (error "makeOutput: transaction did not render")- . CR.transaction groupingSpec+ . (CR.transaction (Just formatter)) . (\t -> let (tl, es) = L.unTransaction t in (L.tlCore tl, fmap L.pdCore es)) . mkTxn si@@ -628,3 +616,6 @@ handleParseResult :: ParseResult -> IO () handleParseResult (ParseResult pa ldgr) = Ex.switch (error . show) TIO.putStr . makeOutput pa $ ldgr++defaultRadGroup :: S.S3 L.Radix L.PeriodGrp L.CommaGrp+defaultRadGroup = S.S3a L.Period